STM32学习笔记(6)——USART串口通信
一、基础知识
本节主要写一下通信的一些基础知识,简单过一遍,防忘。
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)的小数部分。
波特比率的计算公式为:
fCK:串口的时钟,需要注意不同USART有不同AHB时钟频率。
USARTDIV:分频器除法因子,为无符号的定点数,最后为写入寄存器的值。
例如设置波特率为115200的计算过程:
使用十进制小数转换为二进制小数的方法进行。
2. USART的结构体定义和相关库函数
本节内容位于头文件stm32f10x_usart.h中。
USART的初始化结构体定义:
typedef struct
{
//波特率设置
uint32_t USART_BaudRate;
//字长设置
uint16_t USART_WordLength;
//停止位设置
uint16_t USART_StopBits;
//校验位设置
uint16_t USART_Parity;
//USART模式设置
uint16_t USART_Mode;
//硬件流控制选择
uint16_t USART_HardwareFlowControl;
} USART_InitTypeDef;
USART的相关常用且重要库函数(可查看库函数手册):
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的流程
void USART_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
// 1. 开启USART时钟和对应的引脚时钟
// USART1
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
// 2. 配置USART引脚
// USART1-TX发送 GPIOA.9 复用推挽模式
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);
// USART1-RX接收 GPIOA.10 浮空输入模式
GPIO_InitStructure.GPIO_Pin = USART1_RX_GPIO_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(USART1_RX_GPIO_PORT, &GPIO_InitStructure);
// 3. 配置USART1串口参数
USART_InitStructure.USART_BaudRate = USART_BAUDRATE; // 3.1 配置波特率
USART_InitStructure.USART_WordLength = USART_WordLength_8b; // 3.2 配置数据字长
USART_InitStructure.USART_StopBits = USART_StopBits_1; // 3.3 配置停止位
USART_InitStructure.USART_Parity = USART_Parity_No; // 3.4 配置校验位
USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx; // 3.5 配置工作模式
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; // 3.6 配置硬件控制流
USART_Init(USART1, &USART_InitStructure); // 4. 初始化串口
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//USART中断使能
USART_Cmd(USART1, ENABLE); // 5. 使能串口
}
三、实例:单片机向PC机(上位机)发送数据
注意,电脑需要安装CH340驱动和串口调试助手工具,并在工具中设置好波特率、校验位等参数。单片机要用USB(注意是接到单片机USB232接口)连接电脑而不是用STLINK!!!
1. 发送一个字节
void USART_SendByte(USART_TypeDef* pUSARTx, uint8_t data)
{
USART_SendData(pUSARTx, data); //发送数据
while(USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET); //等待发送完成
}
在main函数中进行调用:
USART_Config();
USART_SendByte(USART1, 0x34);
若选择16进制显示,则串口软件会显示34。否则会按照ASCII码转换为相应的字符。
2. 发送两个字节
这里需要说明一下,由于一次只能发送一个字节(4位),所以需要把数据源分段发送。
void USART_SendHalfWord(USART_TypeDef* pUSARTx, uint16_t data)
{
uint8_t temp_h, temp_l;
temp_h = (data & 0xFF00) >> 8; //截取高4位
temp_l = data & 0x00FF; //截取低4位
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位数据的数组
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. 串口发送字符串
中文字符也行。
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的代码更少,资源占用更少。
//重定向c库函数printf到串口,重定向后可使用printf、putchar函数
int fputc(int ch, FILE *f)
{
/* 发送一个字节数据到串口 */
USART_SendData(USART1, (uint8_t)ch);
/* 等待发送完毕 */
while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
return (ch);
}
在main函数中调用以下语句时会输出到串口软件:
printf("我永远喜欢伊蕾娜!!!\n");
putchar('A');
四、实例:单片机从PC机(上位机)接收数据
1. 重定向scanf函数输入到串口
//重定向c库函数scanf到串口,重写向后可使用scanf、getchar函数
int fgetc(FILE *f)
{
/* 等待串口输入数据 */
while (USART_GetFlagStatus(USART1, USART_FLAG_RXNE) == RESET);
return (int)USART_ReceiveData(USART1);
}
2. 使用中断服务函数
//stm32f10x_it.c
void USART1_IRQHandler(void)
{
uint8_t a;
if( USART_GetFlagStatus(USART1, USART_FLAG_RXNE) != RESET ) //如果接收寄存器接收到数据
{
a = USART_ReceiveData(USART1);
USART_SendData(USART1, a); //将接收的数据发送
}
}
需要配置NVIC:
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反转,输入其他向上位机提示输入有误。部分代码如下:
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;
}
}
}