零、基础知识
本节主要写一下通信的一些基础知识,简单过一遍,防忘。
1. 通信基本知识
(1)数据传送方式
分类:串行和并行。
(2)数据通信方向
分类:全双工、半双工和单工。
(3)数据同步方式
分类:同步和异步。
(4)通信速率
比特率(Bitrate) :每秒钟传输的二进制位数,单位为比特每秒(bit/s)。
波特率(Baudrate) :表示每秒钟传输的码元个数,单位为波特(B),没有每秒! (常用的波特率:4800、9600、19200、38400、43000、56000、57600、115200)
码元 :举个例子,当我们用一个二进制数(0或1)表示信息时,0或1为一个码元,它只有一个位数;当用两个二进制数(00,01,10,11)表示信息时,譬如01,就是一个码元,它有两个位数。
2. 串口通信协议
(1)物理层
物理层:规定通讯系统中具有机械、电子功能部分的特性,确保原始数据在物理媒体的传输。其实就是硬件部分 。
RS-232标准
RS232标准串口主要用于工业设备直接通信。电平转换芯片一般有MAX3232,SP3232。
TTL电平标准是5V表示逻辑1,0V表示逻辑0;而为了增加串口通信的远距离传输以及抗干扰能力,RS-232使用-15V表示逻辑1,+15V表示逻辑0。
信号通过DB9串口连接器传输入设备,使用的是RS-232标准电平,因此还有电平转换芯片将其转为TTL电平。
USB转串口
USB转串口主要用于设备跟电脑通信,电平转换芯片一般有CH340等,所以电脑要安装CH340电平转换芯片的驱动程序(之后我们会用到)。
原生串口转串口
原生的串口通信主要是控制器跟串口的设备或者传感器 通信,不需要经过电平转换芯片来转换电平,直接用TTL电平通信。
(2)协议层
串口通信一般是以帧 格式传输数据,即一帧一帧传输,每帧包含有起始信号、数据信息、停止信息,可能还有校验信息。
串口数据包的组成:
起始位 :1个逻辑0表示。
有效数据 :在起始位后紧接着的就是有效数据,有效数据的长度常被约定为5、 6 、 7 或 8 位长。
校验位 :用于数据的抗干扰性。
五种校验方法:
奇校验(odd,有效数据和校验位中“ 1”的个数为奇数)
偶校验(even,有效数据和校验位中“ 1”的个数为偶数)
0校验(space,校验位总为0)
1校验(mark,校验位总为1)
无校验(noparity,不包含校验位)
结束位 :由 0.5、 1、 1.5 或 2 个逻辑1的数据位表示。
二、USART串口通信详解
USART(Universal Synchronous/Asynchronous Receiver/Transmitter )为通用同步异步收发器,支持同步单向通信和半双工单线通信。还有一种“阉割版”——UART(Universal Asynchronous Receiver/Transmitter),去掉了同步,为异步收发器。
【以下内容来自STM32中文参考手册25章通用同步异步收发器(USART) 】USART 满足外部设备对工业标准NRZ异步串行数据格式的要求,并且使用了小数波特率发生器,可以提供多种波特率,使得它的应用更加广泛。USART支持同步单向通信和半双工单线通信;还支持局域互连网络 LIN、智能卡(SmartCard)协议与lrDA(红外线数据协会) SIR ENDEC规范。USART支持使用DMA,可实现高速数据通信。
1. USART功能框图
本节内容可参考STM32中文参考手册25.3节USART功能概述 。我们将框图分为4个部分进行讲解。
(1)引脚
TX:发送数据输出
RX:接收数据串行输入
SCLK(位于最右边):发送器时钟输出,仅同步通信时使用
nRTS:请求发送(Request To Send)
nCTS:允许发送(Clear To Send)
SW_RX:数据接收引脚,属于内部引脚。
引脚对应的编号如下:
(2)数据寄存器
数据寄存器(USART_DR)只有低9位有效,实际上它包含一个发送数据寄存器USART_TDR和一个接收数据寄存器USART_RDR。TDR和RDR都是介于系统总线和移位寄存器之间。这里比较特别:一个地址对应了两个物理内存 。
当进行发送操作时,往USART_DR写入数据会自动存储在 TDR内,然后把内容转移到发送移位寄存器,最后通过模块发送到TX引脚;
当进行读取操作时,信息从RX引脚进入,通过模块后存入接受移位寄存器,然后把内容转移到RDR内,最后USART_DR提取RDR数据。
(3)控制器
本部分内容可参考STM32中文参考手册25.6节USART寄存器描述。
USART有专门控制发送的发送器、控制接收的接收器,还有唤醒单元、中断控制等。
控制寄存器1(USART_CR1)的TE位负责使能发送器,发送器就会“叫醒”发送移位寄存器。
控制寄存器1(USART_CR1)的RX位负责使能接收器,接收器就会“叫醒”接收移位寄存器。
控制寄存器1的TXEIE或RXNEIE置1可以产生中断。
控制寄存器1其他位:TXE:发送移位寄存器为空,发送单个字节时使用。TC:发送完成,发送多个字节数据时候使用。TXIE:发送完成中断使能。
控制寄存器1的M位设置的是字长,该位定义了数据字的长度,由软件对其设置和清零。设置0:一个起始位,8个数据位;设置1:一个起始位,9个数据位。之前已经说过,数据寄存器(USART_DR)只有低9位有效,并且第9位数据是否有效要取决于控制寄存器1的M位设置。
控制寄存器2(USART_CR2)的STOP[1:0]位用于设置停止位,可选0.5个、1个、1.5个、2个停止位。默认使用1个停止位。2个停止位适用于正常USART模式、单线模式和调制解调器模式。0.5和1.5个停止位用于智能卡模式。
(4)波特率
通过波特率寄存器(USART_BRR)可设置波特率。由于计算出的分频因子有小数,因此寄存器分为两个部分:
DIV_Mantissa[11:0]:USARTDIV的整数部分,这12位定义了USART分频器除法因子(USARTDIV)的整数部分。
DIV_Fraction[3:0]:USARTDIV的小数部分,这4位定义了USART分频器除法因子(USARTDIV)的小数部分。
波特比率的计算公式为:
Tx/Rx 波特率 = f c k 16 × USARTDIV \text{Tx/Rx 波特率} = \frac{f_{ck}}{16 \times \text{USARTDIV}}
Tx/Rx 波特率 = 16 × USARTDIV f c k
f c k f_{ck} f c k :串口的时钟,需要注意不同USART有不同AHB时钟频率。
USARTDIV \text{USARTDIV} USARTDIV :分频器除法因子,为无符号的定点数,最后为写入寄存器的值。
例如设置波特率为115200的计算过程:
115200 = 72000000 16 × USARTDIV 115200 = \frac{72000000}{16 \times \text{USARTDIV}}
115200 = 16 × USARTDIV 72000000
解得USARTDIV = 39.0625 \quad \text{USARTDIV} = 39.0625 USARTDIV = 39.0625 ,DIV_Fraction = 0.0625 × 16 = 1 = 0 x 01 \text{DIV\_Fraction} = 0.0625 \times 16 = 1 = 0\text{x}01 DIV_Fraction = 0.0625 × 16 = 1 = 0 x 01 ,DIV_Mantissa = 39 = 0 x 27 \text{DIV\_Mantissa} = 39 = 0\text{x}27 DIV_Mantissa = 39 = 0 x 27 ,即应该设置 U S A R T B R R USART_{BRR} U S A R T B R R 的值为 0x271。
其中小数部分的转换,应使用十进制小数转换为二进制小数的方法进行。
2. USART的结构体定义和相关库函数
本节内容位于头文件stm32f10x_usart.h 中。
USART的初始化结构体定义:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 typedef struct { uint32_t USART_BaudRate; uint16_t USART_WordLength; uint16_t USART_StopBits; uint16_t USART_Parity; uint16_t USART_Mode; uint16_t USART_HardwareFlowControl; } USART_InitTypeDef;
USART的相关常用且重要库函数(可查看库函数手册):
1 2 3 4 5 6 7 8 9 10 11 12 13 void USART_DeInit (USART_TypeDef* USARTx) ;void USART_Init (USART_TypeDef* USARTx, USART_InitTypeDef* USART_InitStruct) ;void USART_StructInit (USART_InitTypeDef* USART_InitStruct) ;void USART_Cmd (USART_TypeDef* USARTx, FunctionalState NewState) ;void USART_ITConfig (USART_TypeDef* USARTx, uint16_t USART_IT, FunctionalState NewState) ;void USART_SendData (USART_TypeDef* USARTx, uint16_t Data) ;uint16_t USART_ReceiveData (USART_TypeDef* USARTx) ;FlagStatus USART_GetFlagStatus (USART_TypeDef* USARTx, uint16_t USART_FLAG) ; void USART_ClearFlag (USART_TypeDef* USARTx, uint16_t USART_FLAG) ;ITStatus USART_GetITStatus (USART_TypeDef* USARTx, uint16_t USART_IT) ; void USART_ClearITPendingBit (USART_TypeDef* USARTx, uint16_t USART_IT) ;
3. 配置USART的流程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 void USART_Config (void ) { GPIO_InitTypeDef GPIO_InitStructure; USART_InitTypeDef USART_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE); GPIO_InitStructure.GPIO_Pin = USART1_TX_GPIO_PIN; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(USART1_TX_GPIO_PORT, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = USART1_RX_GPIO_PIN; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_Init(USART1_RX_GPIO_PORT, &GPIO_InitStructure); USART_InitStructure.USART_BaudRate = USART_BAUDRATE; USART_InitStructure.USART_WordLength = USART_WordLength_8b; USART_InitStructure.USART_StopBits = USART_StopBits_1; USART_InitStructure.USART_Parity = USART_Parity_No; USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx; USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; USART_Init(USART1, &USART_InitStructure); USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); USART_Cmd(USART1, ENABLE); }
三、实例:单片机向PC机(上位机)发送数据
注意,电脑需要安装CH340驱动和串口调试助手工具 ,并在工具中设置好波特率、校验位等参数。单片机要用USB(注意是接到单片机USB232接口)连接电脑而不是用STLINK!!!
1. 发送一个字节
1 2 3 4 5 void USART_SendByte (USART_TypeDef* pUSARTx, uint8_t data) { USART_SendData(pUSARTx, data); while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET); }
在main函数中进行调用:
1 2 USART_Config(); USART_SendByte(USART1, 0x34 );
若选择16进制显示,则串口软件会显示34。否则会按照ASCII码转换为相应的字符。
2. 发送两个字节
这里需要说明一下,由于一次只能发送一个字节(4位),所以需要把数据源分段发送。
1 2 3 4 5 6 7 8 9 10 11 12 void USART_SendHalfWord (USART_TypeDef* pUSARTx, uint16_t data) { uint8_t temp_h, temp_l; temp_h = (data & 0xFF00 ) >> 8 ; temp_l = data & 0x00FF ; USART_SendData(pUSARTx, temp_h); while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET); USART_SendData(pUSARTx, temp_l); while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET); }
3. 串口发送8位数据的数组
1 2 3 4 5 6 7 8 9 void USART_SendArray (USART_TypeDef* pUSARTx, uint8_t * Array, uint8_t num) { uint8_t i; for ( i = 0 ; i < num; i++) { USART_SendData(pUSARTx, Array[i]); while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET); } }
4. 串口发送字符串
中文字符也行。
1 2 3 4 5 6 7 8 void USART_SendStr (USART_TypeDef* pUSARTx, uint8_t * str) { while ( *(str) != '\0' ) { USART_SendData(pUSARTx, *(str++)); while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET); } }
5. 重定向printf函数输出到串口
注意需要在魔法棒Target勾选Use MicroLIB,然后重新编译一次。
MicroLib是对标准C库进行了高度优化之后的库,供MDK默认使用,相比之下,MicroLIB的代码更少,资源占用更少。
1 2 3 4 5 6 7 8 9 10 11 int fputc (int ch, FILE *f) { USART_SendData(USART1, (uint8_t )ch); while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET); return (ch); }
在main函数中调用以下语句时会输出到串口软件:
1 2 printf ("我永远喜欢伊蕾娜!!!\n" );putchar ('A' );
四、实例:单片机从PC机(上位机)接收数据
1. 重定向scanf函数输入到串口
1 2 3 4 5 6 7 8 int fgetc (FILE *f) { while (USART_GetFlagStatus(USART1, USART_FLAG_RXNE) == RESET); return (int )USART_ReceiveData(USART1); }
2. 使用中断服务函数
1 2 3 4 5 6 7 8 9 10 void USART1_IRQHandler (void ) { uint8_t a; if ( USART_GetFlagStatus(USART1, USART_FLAG_RXNE) != RESET ) { a = USART_ReceiveData(USART1); USART_SendData(USART1, a); } }
需要配置NVIC:
1 2 3 4 5 6 7 8 9 10 11 12 void NVIC_Config (void ) { NVIC_InitTypeDef NVIC_InitStructure; NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQ; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1 ; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1 ; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); }
在main函数调用scanf函数,PC输入数据时,单片机会回复同样的数据给PC。
3. 上位机控制LED
功能实现:输入1时实现LED0反转,输入2时实现LED1反转,输入其他向上位机提示输入有误。部分代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 int main (void ) { uint8_t ch; USART_Config(); LED_Init(); while (1 ) { ch = getchar(); printf ("你选择了%c灯!\n" , ch); switch (ch) { case '1' : LED0 = !LED0; break ; case '2' : LED1 = !LED1; break ; default : printf ("输入有误!请重新输入!\n" ); break ; } } }