目录
前言
本章所运用的知识点都是博主从各个网站搜集来的(侵删@野火),也是做个笔记方便以后看。本章所用到的开发板是野火的霸道F103系列开发板,需要完整可运行代码的同学也可以找@我拿。
上一章我们学习了如何去应用外部中断去做一些项目需求,今天我们来学习整个单片机学习里面的重点也是最基础的,串口通讯USART(欢迎大佬指出错误)。
一、串口通讯协议简介
串口通讯 (Serial Communication) 是一种设备间非常常用的串行通讯方式,因为它简单便捷,因此 大部分电子设备都支持该通讯方式,电子工程师在调试设备时也经常使用该通讯方式输出调试 信息。
在计算机科学里,大部分复杂的问题都可以通过分层来简化。如芯片被分为内核层和片上外设; STM32 标准库则是在寄存器与用户代码之间的软件层。对于通讯协议,我们也以分层的方式来 理解,最基本的是把它分为物理层和协议层。物理层规定通讯系统中具有机械、电子功能部分的 特性,确保原始数据在物理媒体的传输。协议层主要规定通讯逻辑,统一收发双方的数据打包、 解包标准。简单来说物理层规定我们用嘴巴还是用肢体来交流,协议层则规定我们用中文还是英 文来交流。
下面我们分别对串口通讯协议的物理层及协议层进行讲解。
二、物理层
串口通讯的物理层有很多标准及变种,我们主要讲解 RS-232 标准,RS-232 标准主要规定了信号 的用途、通讯接口以及信号的电平标准。
使用 RS-232 标准的串口设备间常见的通讯结构见图串口通讯结构图。如下:
在上面的通讯方式中,两个通讯设备的“DB9 接口”之间通过串口信号线建立起连接,串口信号 线中使用“RS-232 标准”传输数据信号。由于 RS-232 电平标准的信号不能直接被控制器直接识 别,所以这些信号会经过一个“电平转换芯片”转换成控制器能识别的“TTL 标准”的电平信号, 才能实现通讯。
2.1 电平标准
根据通讯使用的电平标准不同,串口通讯可分为 TTL 标准及 RS-232 标准,见表 TTL 电平标准与 RS232 电平标准。
上图所示:我们知道常见的电子电路中常使用 TTL 的电平标准,理想状态下,使用 5V 表示二进制逻辑 1, 使用 0V 表示逻辑 0;而为了增加串口通讯的远距离传输及抗干扰能力,RS232使用-15V 表示逻辑 1, +15V 表示逻辑 0
使用 RS232 与 TTL 电平校准表示同一个信号时的对比见图 RS-232 与 TTL 电平标准下表示同一个信号。
因为控制器一般使用 TTL 电平标准,所以常常会使用 MAX3232 芯片对 TTL 及 RS-232 电平的信 号进行互相转换。
2.2 RS-232 信号线
在最初的应用中,RS-232 串口标准常用于计算机、路由与调制调解器 (MODEN,俗称“猫”) 之 间的通讯,在这种通讯系统中,设备被分为数据终端设备 DTE(计算机、路由) 和数据通讯设备 DCE(调制调解器)。我们以这种通讯模型讲解它们的信号线连接方式及各个信号线的作用。
在旧式的台式计算机中一般会有 RS-232 标准的 COM 口 (也称 DB9 接口),见图电脑主板上的 COM 口及串口线:
其中接线口以针式引出信号线的称为公头,以孔式引出信号线的称为母头。在计算机中一般引出 公头接口,而在调制调解器设备中引出的一般为母头,使用上图中的串口线即可把它与计算机连 接起来。通讯时,串口线中传输的信号就是使用前面讲解的 RS-232 标准调制的。
在这种应用场合下,DB9 接口中的公头及母头的各个引脚的标准信号线接法见图 DB9 标准的公 头及母头接法 及表 DB9 信号线说明。
上表中的是计算机端的 DB9 公头标准接法,由于两个通讯设备之间的收发信号 (RXD 与 TXD) 应交叉相连,所以调制调解器端的 DB9 母头的收发信号接法一般与公头的相反,两个设备之间连接时,只要使用“直通型”的串口线连接起来即可,见图计算机与调制调解器的信号线连接。
串口线中的 RTS、CTS、DSR、DTR 及 DCD 信号,使用逻辑 1 表示信号有效,逻辑 0 表示信号无 效。例如,当计算机端控制 DTR 信号线表示为逻辑 1 时,它是为了告知远端的调制调解器,本 机已准备好接收数据,0 则表示还没准备就绪。 在目前的其它工业控制使用的串口通讯中,一般只使用 RXD、TXD 以及 GND 三条信号线,直 接传输数据信号,而 RTS、CTS、DSR、DTR 及 DCD 信号都被裁剪掉了。
三、协议层
串口通讯的数据包由发送设备通过自身的 TXD 接口传输到接收设备的 RXD 接口。在串口通讯 的协议层中,规定了数据包的内容,它由启始位、主体数据、校验位以及停止位组成,通讯双方 的数据包格式要约定一致才能正常收发数据
数据包组成
3.1 波特率
本章中主要讲解的是串口异步通讯,异步通讯中由于没有时钟信号 (如前面讲解的 DB9 接口中是 没有时钟信号的),所以两个通讯设备之间需要约定好波特率,即每个码元的长度,以便对信号 进行解码,图串口数据包的基本组成 中用虚线分开的每一格就是代表一个码元。常见的波特率 为 4800、9600、115200 等。
3.2 通讯的起始和停止信号
串口通讯的一个数据包从起始信号开始,直到停止信号结束。数据包的起始信号由一个逻辑 0 的 数据位表示,而数据包的停止信号可由 0.5、1、1.5 或 2 个逻辑 1 的数据位表示,只要双方约定 一致即可。
3.3 有效数据(长度)
在数据包的起始位之后紧接着的就是要传输的主体数据内容,也称为有效数据,有效数据的长度 常被约定为 5、6、7 或 8 位长。
3.4 数据校验
在有效数据之后,有一个可选的数据校验位。由于数据通信相对更容易受到外部干扰导致传输 数据出现偏差,可以在传输过程加上校验位来解决这个问题。校验方法有奇校验 (odd)、偶校验 (even)、0 校验 (space)、1 校验 (mark) 以及无校验 (noparity)。
奇校验要求有效数据和校验位中“1”的个数为奇数,比如一个 8 位长的有效数据为:01101001, 此时总共有 4 个“1”,为达到奇校验效果,校验位为“1”,最后传输的数据将是 8 位的有效数据 加上 1 位的校验位总共 9 位。
偶校验与奇校验要求刚好相反,要求帧数据和校验位中“1”的个数为偶数,比如数据帧:11001010, 此时数据帧“1”的个数为 4 个,所以偶校验位为“0”。
0 校验是不管有效数据中的内容是什么,校验位总为“0”,1 校验是校验位总为“1”。
四、STM32的USART应用(HAL库与标准库对比)
上面讲了那么多,无非就是从物理层和协议层去了解串口的工作方式以及数据的组成,下面我来正式的去了解一下STM32的USART的应用
在此之前,我们要先了解一下USART的中断:
这些中断标志我们在实际的项目中用的最多的就是发送、接收和空闲这三个中断
4.1 标准库:
标准库函数对每个外设都建立了一个初始化结构体,比如 USART_InitTypeDef,结构体成员用于 设置外设工作参数,并由外设初始化配置函数,比如 USART_Init() 调用,这些设定参数将会设置 外设相应的寄存器,达到配置外设工作环境的目的。初始化结构体定义在 stm32f10x_usart.h 文件中,初始化库函数定义在 stm32f10x_usart.c 文件中,编程时我们可以结合这两个文件内注释使用。
USART 初始化结构体
1) USART_BaudRate:波特率设置。一般设置为 2400、9600、19200、115200。标准库函数会根据 设定值计算得到 USARTDIV 值,从而设置 USART_BRR 寄存器值。
2) USART_WordLength:数据帧字长,可选 8 位或 9 位。它设定 USART_CR1 寄存器的 M 位的值。 如果没有使能奇偶校验控制,一般使用 8 数据位;如果使能了奇偶校验则一般设置为 9 数据位。
3) USART_StopBits:停止位设置,可选 0.5 个、1 个、1.5 个和 2 个停止位,它设定 USART_CR2 寄存器的 STOP[1:0] 位的值,一般我们选择 1 个停止位。
4) USART_Parity:奇偶校验控制选择,可选 USART_Parity_No(无校验)、USART_Parity_Even(偶 校验) 以及 USART_Parity_Odd(奇校验),它设定 USART_CR1 寄存器的 PCE 位和 PS 位的值。
5) USART_Mode:USART 模式选择,有 USART_Mode_Rx 和 USART_Mode_Tx,允许使用逻辑或 运算选择两个,它设定 USART_CR1 寄存器的 RE 位和 TE 位。
6) USART_HardwareFlowControl:硬件流控制选择,只有在硬件流控制模式才有效,可选有使能 RTS、使能 CTS、同时使能 RTS 和 CTS、不使能硬件流。
当使用同步模式时需要配置 SCLK 引脚输出脉冲的属性,标准库使用一个时钟初始化结构体 USART_ClockInitTypeDef 来设置,该结构体内容也只有在同步模式才需要设置。
USART 时钟初始化结构体
1) USART_Clock:同步模式下 SCLK 引脚上时钟输出使能控制,可选禁止时钟输出 (USART_Clock_Disable) 或开启时钟输出 (USART_Clock_Enable);如果使用同步模式发送,一般都 需要开启时钟。它设定 USART_CR2 寄存器的 CLKEN 位的值。
2) USART_CPOL:同步模式下 SCLK 引脚上输出时钟极性设置,可设置在空闲时 SCLK 引脚为低 电平 (USART_CPOL_Low) 或高电平 (USART_CPOL_High)。它设定 USART_CR2 寄存器的 CPOL 位的值。
3) USART_CPHA:同步模式下 SCLK 引脚上输出时钟相位设置,可设置在时钟第一个变化沿捕 获数据 (USART_CPHA_1Edge) 或在时钟第二个变化沿捕获数据。它设定 USART_CR2 寄存器的 CPHA 位的值。USART_CPHA 与 USART_CPOL 配合使用可以获得多种模式时钟关系。
4) USART_LastBit:选择在发送最后一个数据位的时候时钟脉冲是否在 SCLK 引脚输出,可以是 不输出脉冲 (USART_LastBit_Disable)、输出脉冲 (USART_LastBit_Enable)。它设定 USART_CR2 寄 存器的 LBCL 位的值。
4.2 HAL库:
HAL 库函数对每个外设都建立了一个初始化结构体,比如 USART_InitTypeDef,结构体成员用于 设置外设工作参数,并由外设初始化配置函数,比如 USART_Init() 调用,这些设定参数将会设置 外设相应的寄存器,达到配置外设工作环境的目的。
初始化结构体定义在 stm32f1xx_hal_usart.h 文件中,初始化 库函数定义在 stm32f1xx_hal_usart.c 文件中,编程时我们可以结合这两个文件内注释使用。
USART 初始化结构体
typedef struct {
uint32_t BaudRate; //波特率
uint32_t WordLength; //字长
uint32_t StopBits; //停止位
uint32_t Parity; //校验位
uint32_t Mode; //UART 模式
uint32_t HwFlowCtl; //硬件流控制
uint32_t OverSampling; // 过采样模式
uint32_t CLKLastBit; // 最尾位时钟脉冲
} USART_InitTypeDef;
与标准库配置结构体差不多
五、编程的思路
- 使能 RX 和 TX 引脚 GPIO 时钟和 USART 时钟;
- 初始化 GPIO,并将 GPIO 复用到 USART 上;
- 配置 USART 参数;
- 配置中断控制器并使能 USART 接收中断;
- 使能 USART;
- 在 USART 接收中断服务函数实现数据接收和发送;
5.1 标准库编程
嵌套向量中断控制器 NVIC 配置
/**
* @brief 配置嵌套向量中断控制器NVIC
* @param 无
* @retval 无
*/
static void NVIC_Configuration(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
/* 嵌套向量中断控制器组选择 */
/* 提示 NVIC_PriorityGroupConfig() 在整个工程只需要调用一次来配置优先级分组*/
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
/* 配置USART为中断源 */
NVIC_InitStructure.NVIC_IRQChannel = DEBUG_USART_IRQ;
/* 抢断优先级*/
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
/* 子优先级 */
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
/* 使能中断 */
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
/* 初始化配置NVIC */
NVIC_Init(&NVIC_InitStructure);
}
USART GPIO 配置,工作参数配置
/**
* @brief USART GPIO 配置,工作参数配置
* @param 无
* @retval 无
*/
void USART_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
// 打开串口GPIO的时钟
DEBUG_USART_GPIO_APBxClkCmd(DEBUG_USART_GPIO_CLK, ENABLE);
// 打开串口外设的时钟
DEBUG_USART_APBxClkCmd(DEBUG_USART_CLK, ENABLE);
// 将USART Tx的GPIO配置为推挽复用模式
GPIO_InitStructure.GPIO_Pin = DEBUG_USART_TX_GPIO_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(DEBUG_USART_TX_GPIO_PORT, &GPIO_InitStructure);
// 将USART Rx的GPIO配置为浮空输入模式
GPIO_InitStructure.GPIO_Pin = DEBUG_USART_RX_GPIO_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(DEBUG_USART_RX_GPIO_PORT, &GPIO_InitStructure);
// 配置串口的工作参数
// 配置波特率
USART_InitStructure.USART_BaudRate = DEBUG_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_HardwareFlowControl =
USART_HardwareFlowControl_None;
// 配置工作模式,收发一起
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
// 完成串口的初始化配置
USART_Init(DEBUG_USARTx, &USART_InitStructure);
// 串口中断优先级配置
NVIC_Configuration();
// 使能串口接收中断
USART_ITConfig(DEBUG_USARTx, USART_IT_RXNE, ENABLE);
// 使能串口
USART_Cmd(DEBUG_USARTx, ENABLE);
}
编写中断服务函数(基本的收发)
void DEBUG_USART_IRQHandler(void)
{
uint8_t ucnume=0;
if(USART_GetITStatus(DEBUG_USARTx,USART_IT_RXNE)!=RESET)
{
ucnume = USART_ReceiveData(DEBUG_USARTx);
USART_SendData(DEBUG_USARTx,ucTemp);
}
if(USART_GetITStatus(DEBUG_USARTx,USART_IT_IDLE)!=RESET)
{
ucnume = USART_ReceiveData(DEBUG_USARTx);//先读DR 在读SR,软件清标志位
USART_ClearITPendingBit(DEBUG_USARTx,USART_IT_IDLE);
}
}
5.2 HAL库编程
/**
* @brief DEBUG_USART GPIO 配置,工作模式配置。115200 8-N-1
* @param 无
* @retval 无
*/
void DEBUG_USART_Config(void)
{
UartHandle.Instance = DEBUG_USART;
UartHandle.Init.BaudRate = DEBUG_USART_BAUDRATE;
UartHandle.Init.WordLength = UART_WORDLENGTH_8B;
UartHandle.Init.StopBits = UART_STOPBITS_1;
UartHandle.Init.Parity = UART_PARITY_NONE;
UartHandle.Init.HwFlowCtl = UART_HWCONTROL_NONE;
UartHandle.Init.Mode = UART_MODE_TX_RX;
HAL_UART_Init(&UartHandle);
/*使能串口接收断 */
__HAL_UART_ENABLE_IT(&UartHandle,UART_IT_RXNE);
}
/**
* @brief UART MSP 初始化
* @param huart: UART handle
* @retval 无
*/
void HAL_UART_MspInit(UART_HandleTypeDef *huart)
{
GPIO_InitTypeDef GPIO_InitStruct;
DEBUG_USART_CLK_ENABLE();
DEBUG_USART_RX_GPIO_CLK_ENABLE();
DEBUG_USART_TX_GPIO_CLK_ENABLE();
/**USART1 GPIO Configuration
PA9 ------> USART1_TX
PA10 ------> USART1_RX
*/
/* 配置Tx引脚为复用功能 */
GPIO_InitStruct.Pin = DEBUG_USART_TX_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(DEBUG_USART_TX_GPIO_PORT, &GPIO_InitStruct);
/* 配置Rx引脚为复用功能 */
GPIO_InitStruct.Pin = DEBUG_USART_RX_PIN;
GPIO_InitStruct.Mode=GPIO_MODE_AF_INPUT; //模式要设置为复用输入模式!
HAL_GPIO_Init(DEBUG_USART_RX_GPIO_PORT, &GPIO_InitStruct);
//抢占优先级0,子优先级1
HAL_NVIC_SetPriority(DEBUG_USART_IRQ ,0,1);
//使能USART1中断通道
HAL_NVIC_EnableIRQ(DEBUG_USART_IRQ );
}
中断服务函数
extern uint8_t Rxflag;
void DEBUG_USART_IRQHandler(void)
{
uint8_t ch=0;
if(__HAL_UART_GET_FLAG( &UartHandle, UART_FLAG_RXNE ) != RESET)
{
ch=( uint16_t)READ_REG(UartHandle.Instance->DR);
WRITE_REG(UartHandle.Instance->DR,ch);
}
}
总结
UART的应用和实操差不多就这么多,后续最终的还是对于数据流的处理,DMA搬运,指针,回调,以及队列等去处理数据,希望这些对大家有帮助,个人当成笔记使用,若有错误欢迎指出,谢谢大家。