目录一、UART协议二、UART模块介绍1移位寄存器2控制电路3波特率4C语言接口三、串口的引脚初始化1引脚分布表2重映射表3GPIO配置表4C语言接口使用四、标志位的使用1读写标志位2错误标志位五、发送数据代码六、接收数据代码七、补充说明1下降沿触发器2波特率发生器3窗口采样电路4停止位和空闲位一、UART协议在学习网络通信的时候我们曾认识到两台主机想要进行通信必须要有协议的存在否则对方根本不知道你说了啥因为在网络中没有信息的概念只有二进制电平信号。如果不规定协议对方的操作系统就不知道如何解析该二进制信号。于是演变出来OSI的7层模型每一层都相当于一层协议对数据包进行封装从而让对方的同层协议可以解析提取真正想要的数据。OSI下的7层网络模型虽然串口通信不属于其中任何一种但我们可以类比。那么我们STM32单片机自然也可以类比认为属于网络协议中的物理层协议。因为他并没有经过交换机路由器等设备仅仅只是把两台主机直连所以他并不可能在数据链路层、或者网络层。但是也有人认为他属于数据链路层以为他有数据帧封装尽管这个封装十分简单。而且他没有mac地址其实是因为他没有负责的连接环境但是本质上他这个简单的协议是可以做到传输mac地址的。其实这种理解也是有道理的因为规定上物理层没有任何协议存在是单纯的数据流传输。真实的物理连接图正是因为它属于物理层协议那么可以预见的是他的协议必然很简单。我们来看看他的协议格式它的协议格式非常简单由一位低电平起始位7-8位数据位一位校验位一位高电平停止位组成。其中这里的数据位为7-8位是可以选择的不过我们一般都是要传递一个字节所以一般情况下只会选择8位数据位至于校验位如果你的精确度要求高可以选择通常来说并无较大差异。UART数据线在不传输数据的时候保持高电平当开始发送数据的时候发送端UART会在一个时钟周期内将高电平拉低而接收端检测到这个电平变化后会开始以约定好的波特率读取数据线中的每一位电平信号。而这一步骤是由硬件自动完成的软层层面无法体现。二、UART模块介绍在结构图中可以看到UART有三个APB1和APB2线上都存在。可以根据你对时钟频率的不同选取。1移位寄存器在这幅图中有两个移位寄存器他的作用就是接收和发送数据。当我们想要发送数据的时候就把值填入发送寄存器。之后该模块会自动把该寄存器的数据在时钟的频率下一位位得把数据的二进制流交给控制电路。反过来接收数据的时候是先交给到了控制电路控制电路对接收到的数据帧进行解包去除起始位、停止位、并检验校验位是否正确把数据取出交给接收寄存器然后由该寄存器把数据向上传递即可。在这个过程中两个寄存器的作用主要是串转并、并转串。因为在物理线路中的传递都是二进制流的串行数据而用户想看到的则是8位一起的并行数据。2控制电路控制电路主要就是进行封包和解包分用的。在这里可以通过配置寄存器来决定数据包的格式数据位是7位还是8位有没有校验位、停止位是多少个时钟周期的高电平3波特率波特率指的是一分钟的时间比特位传输的数量。比如你的串口选择的波特率是9600那么他一分钟能传递的比特位就是9600个即9600/81200个字节。大多数情况下波特率都会被配置成9600、115200、921600这个和时钟的频率有关尽可能选择可以由时钟频率直接分频得到的精确度较高。在这幅图中波特率是由时钟经过波特率寄存器BRR和一个16分频的分频器得到的。因为STM32的时钟频率往往是36、72MHz比较大经过这些分频降低频率更好用。不过如果你对数据的传输时间限制很短可以尝试较高的频率。4C语言接口在库函数中可以找到一个USART_Init他就是用来配置UART上述模块的函数。可以在这里设置波特率、数据位长度、校验方式、接收还是发送数据等。当然还需要一个时钟总开关使能USART_Cmd(USART1,ENABLE);//使能USART模块三、串口的引脚初始化我们在上面已经了解了UART模块的配置方法。那么既然要向外传输数据必然会有引脚暴露出来供我们使用那么他们是谁呢USART的引脚有这5个其中Tx和Rx最为熟悉是用来传输数据的。而硬件流控我们暂时并不会涉及到也不过多讲解。同步模式的时钟线则是如果你想让两个STM32单片机进行数据交流则可以用这个时钟线把两台主机连接到一起让他们都遵循一个时钟在该时钟的指导下进行串口通信保证数据的准确性。1引脚分布表在数据手册中有这样两幅图他们表示了当前芯片的封装情况、重映射情况。由于他比较多我直接把和UASRT相关的引脚内容挑出来看所以知道了我们在不使用重映射的情况下USART的输入输出引脚是接在PA9和PA10上面的。如果你就想使用默认情况则直接配置PA9和PA10两个引脚即可。当然在重映射后他们的位置则变为了PB6和PB7。注意你如果把他们配置成了USART的引脚则原本的GPIO功能是无法使用的。2重映射表在上面直接看引脚分布表比较麻烦可以直接到使用手册中查看。内容是一样的。3GPIO配置表我们已经找到了其引脚配置成什么模式呢在使用手册中同样有一个表格明确说明了各种外设在使用GPIO的时候应当被配置为什么模式。在这里我们看到全双工模式下Tx要配置成推挽复用输出、Rx要配置成上拉输入。复用推挽输出和通用推挽输出的区别就是一个是由CPU直接控制引脚的电平高低另一个把引脚电平的控制权交给外设模块自己操作更加方便。4C语言接口使用默认情况重映射情况四、标志位的使用在USART的框图中我们曾有一部分没有讲解就是右上角的标志位。通过读取标志位可以让我们时刻检查到USART模块的运行情况。1读写标志位TxE检查能否填入数据到发送寄存器TC检查档次发送过程是否结束RxNE2错误标志位我们在通过USART协议传输数据的时候难免会出现某些原因使得数据的发送端和接收端并不一致我们就认为他出错了。虽然USART的控制电路会自动检测错误但是我们上层应用要想知道仍需要查看这些标志位来获取状态。错误标志位是一个协议中非常重要的存在。通过检验错误标志位你可以对不同的错误做出不同的处理比如如果接受数据错误你可以选择向对端发送消息告诉他你的数据错了请重新发。有没有回想到我们之前学习TCP也是这样的PEFE比如没有接收到停止位就是一种帧格式错误。NE我们实际在接受一个USART协议的信号的时候会在一个时钟周期内多次检测看看每次检测的结果是不是一样的如果不一样则表示有噪音该数据不可信。ORE:当接受寄存器中有数据没有被上层应用取走他会停留在原地字节1如果有数据又来了他就保存在了接收端的移位寄存器字节2此时仍然没有问题。但是如果继续来了数据字节3他就会覆盖掉移位寄存器中的字节2此时控制电路就会监测到该情况并把ORE置1。五、发送数据代码向发送寄存器写入数据的函数我们把这个接口拿出来看看其实现可以发现他仅仅是把数据写入到发送寄存器中而这里的0x01ff则表示只有9位数据有效。之后发送寄存器将数据交给硬件电路发送而硬件电路会根据你的配置选择8/9位有效数据并自动添加起始位、停止位。硬件的自动操作让软件层可以更少的关注具体协议方便了开发者的使用。对上述代码进行封装void Init_USART() { //USART配置 RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE); USART_InitTypeDef USART_InitStruct; USART_InitStruct.USART_BaudRate115200; USART_InitStruct.USART_ModeUSART_Mode_Tx | USART_Mode_Rx; USART_InitStruct.USART_WordLengthUSART_WordLength_8b; USART_InitStruct.USART_ParityUSART_Parity_No; USART_InitStruct.USART_StopBitsUSART_StopBits_1; USART_Init(USART1,USART_InitStruct); //GPIO配置 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); GPIO_InitTypeDef GPIO_InitStruct; GPIO_InitStruct.GPIO_ModeGPIO_Mode_AF_PP; GPIO_InitStruct.GPIO_PinGPIO_Pin_9; GPIO_InitStruct.GPIO_SpeedGPIO_Speed_10MHz; GPIO_Init(GPIOA,GPIO_InitStruct); GPIO_InitStruct.GPIO_ModeGPIO_Mode_IPU; GPIO_InitStruct.GPIO_PinGPIO_Pin_10; GPIO_InitStruct.GPIO_SpeedGPIO_Speed_10MHz; GPIO_Init(GPIOA,GPIO_InitStruct); //使能USART USART_Cmd(USART1,ENABLE); } void My_USART_SendBytes(USART_TypeDef* USARTx,uint8_t* pData,uint16_t Number) { for(uint16_t i0;iNumber;i) { //不为空的时候就在while循环中出不来 while(USART_GetFlagStatus(USART1,USART_FLAG_TXE)RESET); //走到这说明发送寄存器为空了 USART_SendData(USARTx,pData[i]); //如果移位寄存器没有清空就不退出这个函数 while(USART_GetFlagStatus(USART1,USART_FLAG_TC)RESET); } } int main(void) { Init_USART(); uint8_t datas[]{10,50,90,100,66}; while(1) { My_USART_SendBytes(USART1,datas,5); } }如果你上述步骤都正确的话你是可以成功的通过STM32单片机向电脑主机发送消息的。结果如下六、接收数据代码读接口也只是把数据从寄存器拿出并不涉及停止位、起始位的解包分用。这一过程中寄存器的状态标志位都是硬件自动完成的无需用户手动清除。具体使用如下示例代码void Init_USART() { //USART配置 RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE); USART_InitTypeDef USART_InitStruct; USART_InitStruct.USART_BaudRate115200; USART_InitStruct.USART_ModeUSART_Mode_Tx | USART_Mode_Rx; USART_InitStruct.USART_WordLengthUSART_WordLength_8b; USART_InitStruct.USART_ParityUSART_Parity_No; USART_InitStruct.USART_StopBitsUSART_StopBits_1; USART_Init(USART1,USART_InitStruct); //GPIO配置 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); GPIO_InitTypeDef GPIO_InitStruct; GPIO_InitStruct.GPIO_ModeGPIO_Mode_AF_PP; GPIO_InitStruct.GPIO_PinGPIO_Pin_9; GPIO_InitStruct.GPIO_SpeedGPIO_Speed_10MHz; GPIO_Init(GPIOA,GPIO_InitStruct); GPIO_InitStruct.GPIO_ModeGPIO_Mode_IPU; GPIO_InitStruct.GPIO_PinGPIO_Pin_10; GPIO_InitStruct.GPIO_SpeedGPIO_Speed_10MHz; GPIO_Init(GPIOA,GPIO_InitStruct); //使能USART USART_Cmd(USART1,ENABLE); } void My_USART_SendBytes(USART_TypeDef* USARTx,uint8_t* pData,uint16_t Number) { for(uint16_t i0;iNumber;i) { //不为空的时候就在while循环中出不来 while(USART_GetFlagStatus(USART1,USART_FLAG_TXE)RESET); //走到这说明发送寄存器为空了 USART_SendData(USARTx,pData[i]); //如果移位寄存器没有清空就不退出这个函数 while(USART_GetFlagStatus(USART1,USART_FLAG_TC)RESET); } } void My_Led_Init(void) { //GPIO配置 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE); GPIO_InitTypeDef GPIO_InitStruct; GPIO_InitStruct.GPIO_ModeGPIO_Mode_Out_OD; GPIO_InitStruct.GPIO_PinGPIO_Pin_13; GPIO_InitStruct.GPIO_SpeedGPIO_Speed_2MHz; GPIO_Init(GPIOC,GPIO_InitStruct); //初始设置为灭,即GPIOC_Pin_13为高电平 GPIO_WriteBit(GPIOC,GPIO_Pin_13,Bit_SET); } int main(void) { My_Led_Init(); Init_USART(); uint8_t datas[]{10,50,90,100,66}; while(1) { // My_USART_SendBytes(USART1,datas,5); //接收数据并处理 while(USART_GetFlagStatus(USART1,USART_FLAG_RXNE)RESET); uint8_t byteEecvUSART_ReceiveData(USART1); //处理 //0灭 if(byteEecv0) { GPIO_WriteBit(GPIOC,GPIO_Pin_13,Bit_SET); } //1亮 else if(byteEecv1) { GPIO_WriteBit(GPIOC,GPIO_Pin_13,Bit_RESET); } } }如果你的操作正确则可以看到用串口控制芯片上的LED了。七、补充说明在学习协议的时候我一直有一个疑问为什么硬件可以检测并区分起始位、停止位而不会与数据位弄混淆后来我查阅了相关资料获知关键硬件电路有下降沿触发器、波特率发生器等。1下降沿触发器最开始我以为UART模块是无时无刻都处于工作检测状态的就好像一个人24小时待命一样疲惫不堪。但是下降沿触发器就完美的解决了这个问题。最开始UART模块处于睡眠状态只有其中的下降沿触发器处于工作状态而其他的模块如波特率发生器窗口采样都不工作。当检测到一个下降沿的时候下降沿触发器会在1~2个时钟周期小于100ns内唤醒窗口采样电路和波特率发生器。2波特率发生器因为UART是没有时钟线的所以双方设备必须要约定一个时钟----即波特率每秒传输多少个比特位。而波特率发生器是在自己设备的晶振下分频得到波特率时钟的电路。当下降沿触发器唤醒波特率发生器的时候开始工作。把波特率时钟交给采样电路使用。3窗口采样电路在被下降沿触发器唤醒之后进入采样工作因为数据报文格式已经约定好了所以它在波特率时钟下每一个时钟采样一个数据填入移位寄存器中。然后会把最后一位判定为停止位停止位用于让UART陷入睡眠状态从而避免睡眠时间的毛刺信号。4停止位和空闲位为什么每次发送一个数据报之后需要有停止位它的一个关键作用就是防止粘包问题如果没有停止位则一方会一直发消息导致上层应用必须设计解包分用的逻辑。通常情况下停止位的可以缓解波特率误差为通信提供可靠性。通常会在停止位之后重新校准波特率时钟即停止位越长误差越小根据实际测算2位停止位几乎没有误差可靠性非常高。但另一方面停止位越长说明同一时间内的有效数据则会变少所以如何取舍停止位长度与数据速率是一个值得讨论的话题。当停止位结束之后久久没有下一帧数据的到来我们则称这段没有数据帧的时间为空闲位。比如1s发送一个数据帧且一帧数据需要1ms之间那么空闲位就是999ms。这段时间可以让UART模块陷入睡眠降低功耗。但是唤醒时候需要重新同步时钟反而可能增加误差风险。
UART串口通信协议
目录一、UART协议二、UART模块介绍1移位寄存器2控制电路3波特率4C语言接口三、串口的引脚初始化1引脚分布表2重映射表3GPIO配置表4C语言接口使用四、标志位的使用1读写标志位2错误标志位五、发送数据代码六、接收数据代码七、补充说明1下降沿触发器2波特率发生器3窗口采样电路4停止位和空闲位一、UART协议在学习网络通信的时候我们曾认识到两台主机想要进行通信必须要有协议的存在否则对方根本不知道你说了啥因为在网络中没有信息的概念只有二进制电平信号。如果不规定协议对方的操作系统就不知道如何解析该二进制信号。于是演变出来OSI的7层模型每一层都相当于一层协议对数据包进行封装从而让对方的同层协议可以解析提取真正想要的数据。OSI下的7层网络模型虽然串口通信不属于其中任何一种但我们可以类比。那么我们STM32单片机自然也可以类比认为属于网络协议中的物理层协议。因为他并没有经过交换机路由器等设备仅仅只是把两台主机直连所以他并不可能在数据链路层、或者网络层。但是也有人认为他属于数据链路层以为他有数据帧封装尽管这个封装十分简单。而且他没有mac地址其实是因为他没有负责的连接环境但是本质上他这个简单的协议是可以做到传输mac地址的。其实这种理解也是有道理的因为规定上物理层没有任何协议存在是单纯的数据流传输。真实的物理连接图正是因为它属于物理层协议那么可以预见的是他的协议必然很简单。我们来看看他的协议格式它的协议格式非常简单由一位低电平起始位7-8位数据位一位校验位一位高电平停止位组成。其中这里的数据位为7-8位是可以选择的不过我们一般都是要传递一个字节所以一般情况下只会选择8位数据位至于校验位如果你的精确度要求高可以选择通常来说并无较大差异。UART数据线在不传输数据的时候保持高电平当开始发送数据的时候发送端UART会在一个时钟周期内将高电平拉低而接收端检测到这个电平变化后会开始以约定好的波特率读取数据线中的每一位电平信号。而这一步骤是由硬件自动完成的软层层面无法体现。二、UART模块介绍在结构图中可以看到UART有三个APB1和APB2线上都存在。可以根据你对时钟频率的不同选取。1移位寄存器在这幅图中有两个移位寄存器他的作用就是接收和发送数据。当我们想要发送数据的时候就把值填入发送寄存器。之后该模块会自动把该寄存器的数据在时钟的频率下一位位得把数据的二进制流交给控制电路。反过来接收数据的时候是先交给到了控制电路控制电路对接收到的数据帧进行解包去除起始位、停止位、并检验校验位是否正确把数据取出交给接收寄存器然后由该寄存器把数据向上传递即可。在这个过程中两个寄存器的作用主要是串转并、并转串。因为在物理线路中的传递都是二进制流的串行数据而用户想看到的则是8位一起的并行数据。2控制电路控制电路主要就是进行封包和解包分用的。在这里可以通过配置寄存器来决定数据包的格式数据位是7位还是8位有没有校验位、停止位是多少个时钟周期的高电平3波特率波特率指的是一分钟的时间比特位传输的数量。比如你的串口选择的波特率是9600那么他一分钟能传递的比特位就是9600个即9600/81200个字节。大多数情况下波特率都会被配置成9600、115200、921600这个和时钟的频率有关尽可能选择可以由时钟频率直接分频得到的精确度较高。在这幅图中波特率是由时钟经过波特率寄存器BRR和一个16分频的分频器得到的。因为STM32的时钟频率往往是36、72MHz比较大经过这些分频降低频率更好用。不过如果你对数据的传输时间限制很短可以尝试较高的频率。4C语言接口在库函数中可以找到一个USART_Init他就是用来配置UART上述模块的函数。可以在这里设置波特率、数据位长度、校验方式、接收还是发送数据等。当然还需要一个时钟总开关使能USART_Cmd(USART1,ENABLE);//使能USART模块三、串口的引脚初始化我们在上面已经了解了UART模块的配置方法。那么既然要向外传输数据必然会有引脚暴露出来供我们使用那么他们是谁呢USART的引脚有这5个其中Tx和Rx最为熟悉是用来传输数据的。而硬件流控我们暂时并不会涉及到也不过多讲解。同步模式的时钟线则是如果你想让两个STM32单片机进行数据交流则可以用这个时钟线把两台主机连接到一起让他们都遵循一个时钟在该时钟的指导下进行串口通信保证数据的准确性。1引脚分布表在数据手册中有这样两幅图他们表示了当前芯片的封装情况、重映射情况。由于他比较多我直接把和UASRT相关的引脚内容挑出来看所以知道了我们在不使用重映射的情况下USART的输入输出引脚是接在PA9和PA10上面的。如果你就想使用默认情况则直接配置PA9和PA10两个引脚即可。当然在重映射后他们的位置则变为了PB6和PB7。注意你如果把他们配置成了USART的引脚则原本的GPIO功能是无法使用的。2重映射表在上面直接看引脚分布表比较麻烦可以直接到使用手册中查看。内容是一样的。3GPIO配置表我们已经找到了其引脚配置成什么模式呢在使用手册中同样有一个表格明确说明了各种外设在使用GPIO的时候应当被配置为什么模式。在这里我们看到全双工模式下Tx要配置成推挽复用输出、Rx要配置成上拉输入。复用推挽输出和通用推挽输出的区别就是一个是由CPU直接控制引脚的电平高低另一个把引脚电平的控制权交给外设模块自己操作更加方便。4C语言接口使用默认情况重映射情况四、标志位的使用在USART的框图中我们曾有一部分没有讲解就是右上角的标志位。通过读取标志位可以让我们时刻检查到USART模块的运行情况。1读写标志位TxE检查能否填入数据到发送寄存器TC检查档次发送过程是否结束RxNE2错误标志位我们在通过USART协议传输数据的时候难免会出现某些原因使得数据的发送端和接收端并不一致我们就认为他出错了。虽然USART的控制电路会自动检测错误但是我们上层应用要想知道仍需要查看这些标志位来获取状态。错误标志位是一个协议中非常重要的存在。通过检验错误标志位你可以对不同的错误做出不同的处理比如如果接受数据错误你可以选择向对端发送消息告诉他你的数据错了请重新发。有没有回想到我们之前学习TCP也是这样的PEFE比如没有接收到停止位就是一种帧格式错误。NE我们实际在接受一个USART协议的信号的时候会在一个时钟周期内多次检测看看每次检测的结果是不是一样的如果不一样则表示有噪音该数据不可信。ORE:当接受寄存器中有数据没有被上层应用取走他会停留在原地字节1如果有数据又来了他就保存在了接收端的移位寄存器字节2此时仍然没有问题。但是如果继续来了数据字节3他就会覆盖掉移位寄存器中的字节2此时控制电路就会监测到该情况并把ORE置1。五、发送数据代码向发送寄存器写入数据的函数我们把这个接口拿出来看看其实现可以发现他仅仅是把数据写入到发送寄存器中而这里的0x01ff则表示只有9位数据有效。之后发送寄存器将数据交给硬件电路发送而硬件电路会根据你的配置选择8/9位有效数据并自动添加起始位、停止位。硬件的自动操作让软件层可以更少的关注具体协议方便了开发者的使用。对上述代码进行封装void Init_USART() { //USART配置 RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE); USART_InitTypeDef USART_InitStruct; USART_InitStruct.USART_BaudRate115200; USART_InitStruct.USART_ModeUSART_Mode_Tx | USART_Mode_Rx; USART_InitStruct.USART_WordLengthUSART_WordLength_8b; USART_InitStruct.USART_ParityUSART_Parity_No; USART_InitStruct.USART_StopBitsUSART_StopBits_1; USART_Init(USART1,USART_InitStruct); //GPIO配置 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); GPIO_InitTypeDef GPIO_InitStruct; GPIO_InitStruct.GPIO_ModeGPIO_Mode_AF_PP; GPIO_InitStruct.GPIO_PinGPIO_Pin_9; GPIO_InitStruct.GPIO_SpeedGPIO_Speed_10MHz; GPIO_Init(GPIOA,GPIO_InitStruct); GPIO_InitStruct.GPIO_ModeGPIO_Mode_IPU; GPIO_InitStruct.GPIO_PinGPIO_Pin_10; GPIO_InitStruct.GPIO_SpeedGPIO_Speed_10MHz; GPIO_Init(GPIOA,GPIO_InitStruct); //使能USART USART_Cmd(USART1,ENABLE); } void My_USART_SendBytes(USART_TypeDef* USARTx,uint8_t* pData,uint16_t Number) { for(uint16_t i0;iNumber;i) { //不为空的时候就在while循环中出不来 while(USART_GetFlagStatus(USART1,USART_FLAG_TXE)RESET); //走到这说明发送寄存器为空了 USART_SendData(USARTx,pData[i]); //如果移位寄存器没有清空就不退出这个函数 while(USART_GetFlagStatus(USART1,USART_FLAG_TC)RESET); } } int main(void) { Init_USART(); uint8_t datas[]{10,50,90,100,66}; while(1) { My_USART_SendBytes(USART1,datas,5); } }如果你上述步骤都正确的话你是可以成功的通过STM32单片机向电脑主机发送消息的。结果如下六、接收数据代码读接口也只是把数据从寄存器拿出并不涉及停止位、起始位的解包分用。这一过程中寄存器的状态标志位都是硬件自动完成的无需用户手动清除。具体使用如下示例代码void Init_USART() { //USART配置 RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE); USART_InitTypeDef USART_InitStruct; USART_InitStruct.USART_BaudRate115200; USART_InitStruct.USART_ModeUSART_Mode_Tx | USART_Mode_Rx; USART_InitStruct.USART_WordLengthUSART_WordLength_8b; USART_InitStruct.USART_ParityUSART_Parity_No; USART_InitStruct.USART_StopBitsUSART_StopBits_1; USART_Init(USART1,USART_InitStruct); //GPIO配置 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); GPIO_InitTypeDef GPIO_InitStruct; GPIO_InitStruct.GPIO_ModeGPIO_Mode_AF_PP; GPIO_InitStruct.GPIO_PinGPIO_Pin_9; GPIO_InitStruct.GPIO_SpeedGPIO_Speed_10MHz; GPIO_Init(GPIOA,GPIO_InitStruct); GPIO_InitStruct.GPIO_ModeGPIO_Mode_IPU; GPIO_InitStruct.GPIO_PinGPIO_Pin_10; GPIO_InitStruct.GPIO_SpeedGPIO_Speed_10MHz; GPIO_Init(GPIOA,GPIO_InitStruct); //使能USART USART_Cmd(USART1,ENABLE); } void My_USART_SendBytes(USART_TypeDef* USARTx,uint8_t* pData,uint16_t Number) { for(uint16_t i0;iNumber;i) { //不为空的时候就在while循环中出不来 while(USART_GetFlagStatus(USART1,USART_FLAG_TXE)RESET); //走到这说明发送寄存器为空了 USART_SendData(USARTx,pData[i]); //如果移位寄存器没有清空就不退出这个函数 while(USART_GetFlagStatus(USART1,USART_FLAG_TC)RESET); } } void My_Led_Init(void) { //GPIO配置 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE); GPIO_InitTypeDef GPIO_InitStruct; GPIO_InitStruct.GPIO_ModeGPIO_Mode_Out_OD; GPIO_InitStruct.GPIO_PinGPIO_Pin_13; GPIO_InitStruct.GPIO_SpeedGPIO_Speed_2MHz; GPIO_Init(GPIOC,GPIO_InitStruct); //初始设置为灭,即GPIOC_Pin_13为高电平 GPIO_WriteBit(GPIOC,GPIO_Pin_13,Bit_SET); } int main(void) { My_Led_Init(); Init_USART(); uint8_t datas[]{10,50,90,100,66}; while(1) { // My_USART_SendBytes(USART1,datas,5); //接收数据并处理 while(USART_GetFlagStatus(USART1,USART_FLAG_RXNE)RESET); uint8_t byteEecvUSART_ReceiveData(USART1); //处理 //0灭 if(byteEecv0) { GPIO_WriteBit(GPIOC,GPIO_Pin_13,Bit_SET); } //1亮 else if(byteEecv1) { GPIO_WriteBit(GPIOC,GPIO_Pin_13,Bit_RESET); } } }如果你的操作正确则可以看到用串口控制芯片上的LED了。七、补充说明在学习协议的时候我一直有一个疑问为什么硬件可以检测并区分起始位、停止位而不会与数据位弄混淆后来我查阅了相关资料获知关键硬件电路有下降沿触发器、波特率发生器等。1下降沿触发器最开始我以为UART模块是无时无刻都处于工作检测状态的就好像一个人24小时待命一样疲惫不堪。但是下降沿触发器就完美的解决了这个问题。最开始UART模块处于睡眠状态只有其中的下降沿触发器处于工作状态而其他的模块如波特率发生器窗口采样都不工作。当检测到一个下降沿的时候下降沿触发器会在1~2个时钟周期小于100ns内唤醒窗口采样电路和波特率发生器。2波特率发生器因为UART是没有时钟线的所以双方设备必须要约定一个时钟----即波特率每秒传输多少个比特位。而波特率发生器是在自己设备的晶振下分频得到波特率时钟的电路。当下降沿触发器唤醒波特率发生器的时候开始工作。把波特率时钟交给采样电路使用。3窗口采样电路在被下降沿触发器唤醒之后进入采样工作因为数据报文格式已经约定好了所以它在波特率时钟下每一个时钟采样一个数据填入移位寄存器中。然后会把最后一位判定为停止位停止位用于让UART陷入睡眠状态从而避免睡眠时间的毛刺信号。4停止位和空闲位为什么每次发送一个数据报之后需要有停止位它的一个关键作用就是防止粘包问题如果没有停止位则一方会一直发消息导致上层应用必须设计解包分用的逻辑。通常情况下停止位的可以缓解波特率误差为通信提供可靠性。通常会在停止位之后重新校准波特率时钟即停止位越长误差越小根据实际测算2位停止位几乎没有误差可靠性非常高。但另一方面停止位越长说明同一时间内的有效数据则会变少所以如何取舍停止位长度与数据速率是一个值得讨论的话题。当停止位结束之后久久没有下一帧数据的到来我们则称这段没有数据帧的时间为空闲位。比如1s发送一个数据帧且一帧数据需要1ms之间那么空闲位就是999ms。这段时间可以让UART模块陷入睡眠降低功耗。但是唤醒时候需要重新同步时钟反而可能增加误差风险。