一、C语言入门
程序结构
//file: main.c
#include<stdio.h>
int main()
{
printf("Hello World");
return 0;
}
一个C程序由若干头文件和函数组成。
#include <stdio.h>是一条预处理命令, 它的作用是通知C语言编译系统,在对C程序进行正式编译之前需做一些预处理工作。函数是实现代码逻辑的一个小的单元。
编程规范
注释
参考STM32CubeMX生成的模版程序
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file : main.c
* @brief : Main program body
******************************************************************************
* @attention
*
* Copyright (c) 2023 STMicroelectronics.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "tim.h"
#include "gpio.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "driver_tim_pwm.h"
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
/* USER CODE END PTD */
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
/* USER CODE END PM */
/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN PV */
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */
/* USER CODE END PFP */
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
/* USER CODE END 0 */
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
if(IS_ENGINEERING_BOOT_MODE())
{
/* Configure the system clock */
SystemClock_Config();
}
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_TIM2_Init();
/* USER CODE BEGIN 2 */
TIM_LED3_PWM_Start();
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
/**
* @brief System Clock Configuration
* @retval None
*/
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
/** Initializes the RCC Oscillators according to the specified parameters
* in the RCC_OscInitTypeDef structure.
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.HSIDivValue = RCC_HSI_DIV1;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE;
RCC_OscInitStruct.PLL2.PLLState = RCC_PLL_NONE;
RCC_OscInitStruct.PLL3.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL3.PLLSource = RCC_PLL3SOURCE_HSI;
RCC_OscInitStruct.PLL3.PLLM = 4;
RCC_OscInitStruct.PLL3.PLLN = 26;
RCC_OscInitStruct.PLL3.PLLP = 2;
RCC_OscInitStruct.PLL3.PLLQ = 2;
RCC_OscInitStruct.PLL3.PLLR = 2;
RCC_OscInitStruct.PLL3.PLLRGE = RCC_PLL3IFRANGE_1;
RCC_OscInitStruct.PLL3.PLLFRACV = 1024;
RCC_OscInitStruct.PLL3.PLLMODE = RCC_PLL_FRACTIONAL;
RCC_OscInitStruct.PLL4.PLLState = RCC_PLL_NONE;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
/** RCC Clock Config
*/
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_ACLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2
|RCC_CLOCKTYPE_PCLK3|RCC_CLOCKTYPE_PCLK4
|RCC_CLOCKTYPE_PCLK5;
RCC_ClkInitStruct.AXISSInit.AXI_Clock = RCC_AXISSOURCE_HSI;
RCC_ClkInitStruct.AXISSInit.AXI_Div = RCC_AXI_DIV1;
RCC_ClkInitStruct.MCUInit.MCU_Clock = RCC_MCUSSOURCE_PLL3;
RCC_ClkInitStruct.MCUInit.MCU_Div = RCC_MCU_DIV1;
RCC_ClkInitStruct.APB4_Div = RCC_APB4_DIV1;
RCC_ClkInitStruct.APB5_Div = RCC_APB5_DIV1;
RCC_ClkInitStruct.APB1_Div = RCC_APB1_DIV2;
RCC_ClkInitStruct.APB2_Div = RCC_APB2_DIV2;
RCC_ClkInitStruct.APB3_Div = RCC_APB3_DIV2;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct) != HAL_OK)
{
Error_Handler();
}
}
/* USER CODE BEGIN 4 */
/* USER CODE END 4 */
/**
* @brief This function is executed in case of error occurrence.
* @retval None
*/
void Error_Handler(void)
{
/* USER CODE BEGIN Error_Handler_Debug */
/* User can add his own implementation to report the HAL error return state */
__disable_irq();
while (1)
{
}
/* USER CODE END Error_Handler_Debug */
}
#ifdef USE_FULL_ASSERT
/**
* @brief Reports the name of the source file and the source line number
* where the assert_param error has occurred.
* @param file: pointer to the source file name
* @param line: assert_param error line source number
* @retval None
*/
void assert_failed(uint8_t *file, uint32_t line)
{
/* USER CODE BEGIN 6 */
/* User can add his own implementation to report the file name and line number,
ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
/* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */
标识符
C语言规定,标识符可以是字母(A~Z,a~z)、数字(0~9)、下划线_组成的字符串,并且第一个字符必须是字母或下划线。
其他注意点:
- 标识符的长度最好不要超过8位,因为在某些版本的C中规定标识符前8位有效
- 标识符是严格区分大小写的
- 见名思意,不使用中文
- 不能使用关键字
- 下划线开头标识符一般定义为特殊含义变量
变量及赋值
使用变量之前必须先定义变量,要区分变量名和变量值是两个不同的概念。
注意:在定义中不允许连续赋值,如int a=b=c=5;是不合法的。
基本数据类型
格式化输出
int a = 10;
float b = 66.6;
char x = 'c';
printf("整数:%d,小数:%f,字符:%c",a ,b, x);
不可改变常量
- 字面量
- 整型常量 13、0、-13;
- 实型常量:13.33、-24.4;
- 字符常量:‘a’、‘M’
- 字符串常量:”I love moon!”
- 符号常量
- #define 标识符 常量值
类型转换
自动转换发生在不同数据类型运算时,在编译的时候自动完成。
- 字节小的可以向字节大的自动转换,但字节大的不能向字节小的自动转换。
- char可以转换为int,int可以转换为double,char可以转换为double。但是不可以反向。
运算符
- 自增与自减运算符
- 关系运算符
> 、 >= 、 < 、 <= 、 == 、 !=
关系表达式的值是真和假,在C程序用整数1和0表示。注意:>=, <=, ==, !=这种符号之间不能存在空格。
- 逻辑运算符
&&、|| 、!
- 三目运算符
表达式1 ? 表达式2 : 表达式3;
先判断表达式1的值是否为真,如果是真的话执行表达式2;如果是假的话执行表达式3。
#include <stdio.h>
int main()
{
double money =12.0 ;
double cost =11.5 ;
printf("小编能不能打车回家呢:");
//输出y小编就打车回家了,输出n小编就不能打车回家
printf("%c\n",money >= cost ? 'y' : 'n' );
return 0;
}
分支结构
if(表达式)
{
执行代码块;
}
if(表达式)
{
执行代码块1;
}
else
{
执行代码块2;
}
switch(表达式)
{
case 常量表达式1:
执行代码块1;
break;
case 常量表达式2:
执行代码块2;
break;
//...
default:
执行代码块n+1;
}
注意:
- 在case后的各常量表达式的值不能相同,否则会出现错误。
- 在case子句后如果没有break;会一直往后执行一直到遇到break;才会跳出switch语句。
- switch后面的表达式语句只能是整型或者字符类型。
- 在case后,允许有多个语句,可以不用{}括起来。
- 各case和default子句的先后顺序可以变动,而不会影响程序执行结果。
- default子句可以省略不用。
#include <stdio.h>
int main()
{
/* 定义需要计算的日期 */
int date = 0;
int year = 2008;
int month = 8;
int day = 8;
switch(month)
{
case 12:
date+=30;
case 11:
date+=31;
case 10:
date+=30;
case 9:
date+=31;
case 8:
date+=31;
case 7:
date+=30;
case 6:
date+=31;
case 5:
date+=30;
case 4:
date+=31;
case 3:
if((year%4==0&&year%100!=0)||year%400==0)
{
date+=29;
}
else
{
date+=28;
}
case 2:
date+=31;
case 1:
date += day;
printf("%d年%d月%d日是该年的第%d天",year,month,day,date);
break;
default:
printf("error");
break;
}
return 0;
}
循环结构
while(表达式)
{
执行代码块
}
do
{
执行代码块
}while(表达式);
for(表达式1;表达式2;表达式3)
{
执行代码块
}
do-while :
先执行循环中的执行代码块,然后再判断while中表达式是否为真,如果为真则继续循环;如果为假,则终止循环。因此,do-while循环至少要执行一次循环语句。
在for循环中:
- 表达式1是一个或多个赋值语句,它用来控制变量的初始值;
- 表达式2是一个关系表达式,它决定什么时候退出循环;
- 表达式3是循环变量的步进值,定义控制循环变量每循环一次后按什么方式变化。
在知道循环次数的情况下更适合使用for循环;在不知道循环次数的情况下适合使用while或者do-while循环:在不知道循环次数的情况下适合使用while或者do-while循环,如果至少循环一次应考虑使用do-while循环。
结束语句之break语句
在没有循环结构的情况下,break不能用在单独的if-else语句中。在多层循环中,一个break语句只跳出当前循环。
结束语句之continue语句
continue语句的作用是结束本次循环开始执行下一次循环。
break是跳出当前整个循环,continue是结束本次循环开始下一次循环。
自创函数
格式:
[数据类型说明] 函数名称 ([参数])
{
变量定义部分
语句部分
return(表达式);
}
自定义函数放在main函数后面的话, 需要在main函数之前先声明自定义函数。声明格式:
[数据类型说明] 函数名称 ([参数]);
函数调用
函数名称 ([参数]);
有参与无参
形参与实参
函数的返回值
return 表达式;
return (表达式);
递归函数
局部变量和全局变量
C语言中的变量,按作用域范围可分为两种,即局部变量和全局变量。局部变量也称为内部变量。全局变量也称为外部变量,它是在函数外部定义的变量。
C语言根据变量的生存周期来划分,可以分为静态存储方式和动态存储方式。静态存储方式:是指在程序运行期间分配固定的存储空间的方式。静态存储区中存放了在整个程序执行过程中都存在的变量,如全局变量。动态存储方式:是指在程序运行期间根据需要进行动态的分配存储空间的方式。动态存储区中存放的变量是根据程序运行的需要而建立和释放的,通常包括:函数形式参数;自动变量;函数调用时的现场保护和返回地址等。
1、全局变量在整个工程文件内都有效。
2、静态全局变量只在定义它的文件内有效。
3、静态局部变量只在定义它的函数内有效,且程序仅分配一次内存,函数返回后,该变量不会消失;局部变量在定义它的函数内有效,但是函数返回后失效。
4、全局变量和静态变量如果没有手工初始化,则由编译器初始化为0。局部变量的值不可知。
5、静态局部变量与全局变量共享全局数据区,但静态局部变量只在定义它的函数中可见。静态局部变量与局部变量在存储位置上不同,使得其存在的时限也不同,导致对这两者操作的运行结果也不同。
#include <stdio.h>
void func();//函数声明,主要是因为被调用的函数定义写在了main函数后面了
int n = 1; //全局变量
int main(void)
{
static int a; // 静态局部变量,但静态局部变量只在定义它的函数中可见,并且只初始化一次
int b = -10; // 局部变量
printf("main: a=%d, b=%d, n=%d\n",a,b,n);
b += 4;
func();
printf("main: a=%d, b=%d, n=%d\n",a,b,n);
n += 10;
func();
printf("main: a=%d, b=%d, n=%d\n",a,b,n);
}
void func()
{
static int a = 2; // 静态局部变量
int b = 5; // 局部变量
a += 2;
n += 12;
b += 5;
printf("func: a=%d, b=%d, n=%d\n",a,b,n);
}
static在文件作用域和代码块作用域的意义是不同的:在文件作用域用于限定函数和变量的外部链接性(能否被其它文件访问), 在代码块作用域则用于将变量分配到静态存储区。
外部变量
程序的编译单位是源程序文件,一个源文件可以包含一个或若干个函数。在函数内定义的变量是局部变量,而在函数之外定义的变量则称为外部变量,外部变量也就是我们所讲的全局变量。它的存储方式为静态存储,其生存周期为整个程序的生存周期。全局变量可以为本文件中的其他函数所共用,它的有效范围为从定义变量的位置开始到本源文件结束1。
如果全局变量不在文件的开头定义,有效的作用范围将只限于其定义处到文件结束。如果在定义点之前的函数想引用该全局变量,则应该在引用之前用关键字 extern 对该变量作“外部变量声明”,表示该变量是一个已经定义的外部变量。有了此声明,就可以从“声明”处起,合法地使用该外部变量。
如果整个工程由多个源文件组成,在一个源文件中想引用另外一个源文件中已经定义的外部变量,同样只需在引用变量的文件中用 extern 关键字加以声明即可。
变量存储类别
C语言中存储类别又分为四类:
- 自动(auto)
- 静态(static)
- 寄存器的(register)
- 外部的(extern)
字符串与数组
c语言获取数组长度
int length = sizeof(arr)/sizeof(arr[0]);
#include <stdio.h>
int main()
{
double arr[]={1.78, 1.77, 1.82, 1.79, 1.85, 1.75, 1.86, 1.77, 1.81, 1.80};
int i,j;
printf("\n************排队前*************\n");
for(i=0;i<10;i++)
{
if(i != 9)
printf("%1.2f, ", arr[i]); //%1.2f表示小数点前一位,小数点后精确到两位
else
printf("%1.2f", arr[i]); //%1.2f表示小数点前一位,小数点后精确到两位
}
for(i=8; i>=0; i--)
{
for(j=0;j<=i;j++)
{
if( arr[j]>arr[j+1]) //当前面的数比后面的数大时
{
double temp; //定义临时变量temp
temp=arr[j];//将前面的数赋值给temp
arr[j]=arr[j+1]; //前后之数颠倒位置
arr[j+1]=temp;//将较大的数放在后面
}
}
}
printf("\n************排队后*************\n");
for(i=0;i<10;i++)
{
if(i != 9)
printf("%1.2f, ", arr[i]); //%1.2f表示小数点前一位,小数点后精确到两位
else
printf("%1.2f", arr[i]); //%1.2f表示小数点前一位,小数点后精确到两位
}
return 0;
}
C语言中,是没有办法直接定义字符串数据类型的,但是我们可以使用数组来定义我们所要的字符串。一般有以下两种格式:
char 字符串名称[长度] = “字符串值”;
char 字符串名称[长度] = {‘字符1’,‘字符2’,…,‘字符n’,'\0'};
采用第2种方式的时候最后一个元素必须是'\0','\0’表示字符串的结束标志;
字符串处理函数
易变变量
关键字 volatile 告诉编译器,它后面所定义的变量为“易失性变量”,随时都有可能被修改,因此编译器必须确保每次使用变量时都要主动从内存中、根据存储变量的地址读取数据,而不是使用寄存器中的缓存值。
宏定义 #define __IO volatile 的作用是将标记为 “__IO” 的变量设置成可读可写的,具有在任何时候均可读写的权限,本质上,它是一个缩写,意味着变量可以在不需要在代码中显式标明 “volatile” 的情况下使用2。
typedef unsigned int uint32_t;
#define __IO volatile /* defines 'read / write' permissions */
void Delay(__IO uint32_t nCount) //简单的延时函数
{
for(; nCount != 0; nCount--);
}
二、嵌入式C开发基础
关键字
sizeof
sizeof是C语言的关键字,它用来计算变量(或数据类型)在当前系统中占用内存的字节数3。sizeof不是函数,sizeof有两种写法:
- 用于数据类型
sizeof(数据类型);
数据类型必须用括号括住。
printf("字符型变量占用的内存是=%d\n",sizeof(char)); // 输出:字符型变量占用的内存是=1
printf("整型变量占用的内存是=%d\n",sizeof(int)); // 输出:整型变量占用的内存是=4
- 用于变量
sizeof(变量名);
sizeof 变量名;
变量名可以不用括号括住,带括号的用法更普遍,大多数程序员采用这种形式。
sizeof(结构体)
/*
* 程序名:book90.c,此程序用于演示C语言的结构体占用内存的情况
* 作者:C语言技术网(www.freecplus.net) 日期:20190525
*/
#include <stdio.h>
// #pragma pack(1) # 告诉编译器内存按1字节对齐。
struct st_girl
{
char name[50]; // 姓名
int age; // 年龄
int height; // 身高,单位:厘米cm
char sc[30]; // 身材,火辣;普通;飞机场。
char yz[30]; // 颜值,漂亮;一般;歪瓜裂枣。
};
int main()
{
struct st_girl queen;
printf("sizeof(struct st_girl) %d\n",sizeof(struct st_girl));
printf("sizeof(queen) %d\n",sizeof(queen));
}
数据类型
内存管理和存储架构
//main.c
#include <stdio.h>
#include <stdlib.h>
static int st_val; //静态全局变量 -- 静态存储区
int ex_val; //全局变量 -- 静态存储区
int main(void)
{
int a = 0; //局部变量 -- 栈上申请
int *ptr = NULL; //指针变量
static int local_st_val = 0; //静态变量
local_st_val += 1;
a = local_st_val;
ptr = (int *)malloc(sizeof(int)); //从堆上申请空间
if(ptr != NULL)
{
printf("*p value:%d", *ptr);
free(ptr);
ptr = NULL;
//free后需要将ptr置空,否则会导致后续ptr的校验失效,出现野指针
}
}
指针和数组
结构类型和对齐
typedef enum {
spring = 1,
summer,
autumn,
winter
}season;
season s1 = summer;
typedef union{
char c;
short s;
int i;
}UNION_VAL;
UNION_VAL val;
结构体则是将具有共通特征的变量组成的集合,比起C++的类来说,它没有安全访问的限制,不支持直接内部带函数,但通过自定义数据类型,函数指针,仍然能够实现很多类似于类的操作,对于大部分嵌入式项目来说,结构化处理数据对于优化整体架构以及后期维护大有便利。
typedef int (*pfunc)(int, int);
typedef struct{
int num;
int profit;
pfunc get_total;
}STRUCT_VAL;
int GetTotalProfit(int a, int b)
{
return a*b;
}
int main(void){
STRUCT_VAL Val;
STRUCT_VAL *pVal;
Val.get_total = GetTotalProfit;
Val.num = 1;
Val.profit = 10;
printf("Total:%d\n", Val.get_total(Val.num, Val.profit)); //变量访问
pVal = &Val;
printf("Total:%d\n", pVal->get_total(pVal->num, pVal->profit)); //指针访问
}
C语言的结构体支持指针和变量的方式访问,通过转换可以解析任意内存的数据(如我们之前提到的通过指针强制转换解析协议),另外通过将数据和函数指针打包,在通过指针传递,是实现驱动层实接口切换的重要基础,有着重要的实践意义,另外基于位域,联合体,结构体,可以实现另一种位操作,这对于封装底层硬件寄存器具有重要意义,实践如下:
typedef unsigned char uint8_t;
union reg
{
struct
{
uint8_t bit0:1;
uint8_t bit1:1;
uint8_t bit2_6:5;
uint8_t bit7:1;
}bit;
uint8_t all;
};
int main(void)
{
union reg RegData;
RegData.all = 0;
RegData.bit.bit0 = 1;
RegData.bit.bit7 = 1;
printf("0x%x\n", RegData.all);
RegData.bit.bit2_6 = 0x3;
printf("0x%x\n", RegData.all);
}
通过联合体和位域操作,可以实现对数据内bit的访问,这在寄存器以及内存受限的平台,提供了简便且直观的处理方式。
预处理机制
模块化编程
一般项目结构,采用模块化思想组织项目。.c 文件主要负责实现,定义函数;.h 文件主要负责声明,比如函数声明、宏定义等。若采用面向对象编程思想,.h文件存放数据(结构体)和数据操作接口(函数),.c文件负责实现具体的功能函数。
project
module1
inc
header1.h
header2.h
src
func1.c
func2.c
module2
inc
src
main.c
config.xml
docs
设计自己的库
1、原则
- 每个库有一个自己的主题
- 要考虑通用性
2、设计库接口和函数实现
- 接口/头文件(.h)
//file:random.h
//随机函数库的头文件
#ifndef _random_h
#define _random_h
//函数:RandomInit
//用法:RandomInit()
//作用:此函数初始化随机数发生器,需要用随机数的程序在第一次生成随机数前,必须先调用一次本函数
void RandomInit();
//函数:RandomInteger
//用法:n = RandomInteger(int low, int high);
//作用:此函数返回一个low到hign之间的随机数,包括low和high
int RandomInteger(int low, int high);
//函数:RandomDouble
//用法:n = RandomDouble(low, high)
//作用:此函数返回一个大于等于low,且小于high的随机实数
double RandomDouble(double low,double high);
#endif
- 实现/源代码(.c)
//file:random.cpp
//该文件实现了random库
#include <cstdlib>
#include <ctime>
#include <random.h>
//函数:RandomInit
//该函数取当前系统时间作为随机数发生器种子
void RandomInit()
{
srand(time(NULL));
}
//函数:RandomInteger
//该函数将0到RAND_MAX的区间划分成high-low+1个子区间
//当产生的随机数逻在第一个子区间时,则映射成low。当落在最后一个子区间时,
//则映射成high。当落在第i个子区间时(i从0到high-low),则映射到low+1
int RandomInteger(int low, int high)
{
return (low + (high - low + 1)*rand() / (RAND_MAX +1));
}
//函数:RandomDouble
//该函数将产生的随机数映射为[0,1]之间的实数,然后转换到区间[low,high]
double RandomDouble(double low, double high)
{
double d = (double rand() / (RAND_MAX + 1));
return (low + (high - low +1) * d);
}
三、嵌入式系统基础
嵌入式系统基础
通信基础
在嵌入式中,通信将主控芯片与传感器、存储芯片、外围控制芯片等连接了起来,使得功能不再受限于主控本身。主控既从其它设备获取信息,也将自己的信息传递给其它设备。如果通信没处理好,将直接影响整个系统的功能,由此可见通信技术的重要性4。
相关概念有:
- 串行/并行
- 全双工/半双工/单工
- 同步/异步
- 同步通信的做法是加一个时钟信号,发送方和接收方在这个时钟的节拍下传输数据,比如常见的 SPI、I2C。而异步通信的做法是对数据进行封装,在数据开头加上起始信号,在数据结尾加上终止信号,双方就按这个规则传输数据,比如 UART、1-Wire。因此,可以通过是否有时钟信号,初步判断是何种数据同步方式。
- 对于同步通信,通信速率由时钟信号决定,时钟信号越快,传输速度就越快。对于异步通信,需要收发双方提前统一通信速率,这也就是我们串口调试时,波特率不对显示乱码的原因。
- 电平标准
- 说到串口,经常提到 TTL、RS232、RS422、RS485,简单的说,就是为了适应不同的环境条件,使用了不同的电平标准。假如微处理器和板载的蓝牙透传模块通信时,一般就使用 TTL 电平,引脚直接连接即可。假如微处理器在工业现场,需要连接一个几十米外的装置,则应该考虑将 TTL 电平转为 RS232、RS422、RS485。
...