文章中的部分概念可参考第8课【通讯的基本概念】串行并行 单工半双工全双工 同步异步 比特率波特率
串口通讯指的设备与设备之间通过串行接口(Communication Port),串行通讯协议进行通信的通讯方式,也是串行通讯的一种实现方式。由于其成本低,结构简单,传输距离长等优点,在工业领域被广泛运用;开发者在日常的调试工作中也经常会用到
在分析复杂的问题或设计庞大的系统时,为了能对问题有更快更清晰的理解,通常会使用分层的思想将问题按照一定的类别进行拆分。对于串口通讯系统,可以将其分为两层:
以下内容主要讨论RS232标准+UART协议实现的串口通讯
串口通讯的物理层有很多标准和标准变种,RS232是较为经典的物理层标准,它起源于19世纪60年代,如今已经被广泛地运用于计算机同各种电子设备通讯的场合。它的通讯模型是这样的:
此外RS-232标准还有以下特点:
RS-232通讯通常用于计算机,手机,PDA和路由,调制解调设备之间。计算机,手机,PDA这类设备被称为DTE(Data Terminal Equipment 数据终端设备),可以理解为生成/接收数据的设备,例如你通过手机向服务器发起获取网页的请求(手机作为请求数据的起点),或者手机接收服务器发来的网页(手机作为网页数据的终点);路由,调制解调设备被称为DCE(Data Cicuit-terminal Equipment 数据电路终端设备),可以理解为负责信号传递,放大,调制解调等辅助信号传输的设备,它可以连接两个/多个DTE设备,并负责设备间的信号处理
物理层的本质目的是要传输原始数据,即0/1组成的比特流,所以在物理层中需要规定0/1比特流的表示方法:通过电流/电压(电平)高低差异去表示0/1信号,通过电流表示的成为电流环路标准,通过电压表示的成为电平标准。对于不同物理层的信号表示方法不同,常见的RS-232标准,TTL标准都是通过电平标准来定义0/1比特流。具体电平规定如下图:
通讯标准 | 电平标准 |
---|---|
5V TTL | 逻辑1:2.4V~5V 逻辑0:0~0.5V |
RS-232 | 逻辑1:-15V~-3V 逻辑0:+3V~+15V |
可以看出,5V TTL和RS-232电平标准的电压范围以及逻辑0/1的判断范围都有很大不同。这是因为5V TTL电平标准适用于近距离(同一块电路板上),抗干扰能力要求不高,功耗要求较严格的场景;而RS-232电平标准适用于远距离(15米左右),抗干扰能力要求高,功耗要求不高的场景
通讯标准中,连接器通常用于连接需要建立通讯的设备或器件,以便进行数据传输。它为数据传输的流程提供了物理上的信号通路。RS-232标准中没有指定使用哪种连接器,只给出了连接器应该有的最低标准,例如引脚至少需要9根。而DB9连接器是RS-232连接器实现最广泛使用的一种,它由EIA-574标准定义的,符合RS-232标准的要求,开发者也经常称其为串口线。串口线的外观如下图:
串口线分为公头(Male)和母头(Female),公头引出针式信号线用于传输信号,如上图中印有UGreen字样一端的串口线接头,母头引出孔氏信号线,如上图中印有RS-232字样一端的串口线接头。一般DTE设备上配置公头,DCE设备上配置母头,两设备之间通过公头-母头串口线连接到一起。通讯时,传续的信号按照RS-232标准要求调制,通过串口线进行传输
串口线公头/母头的9个接线方式/引脚定义如下图:
引脚编号 | 功能 | 解释 |
---|---|---|
1 | Carrier Detect (CD) | 数据载波检测,用于检测传输线路上是否有数据载波信号 |
2 | Receive Data (RD) | 接收数据,用于接收发送方发送过来的数据 |
3 | Transmit Data (TD) | 发送数据,用于向接收方发送数据 |
4 | Data Terminal Ready (DTR) | 数据终端就绪,用于告诉接收方,发送方已经准备好进行数据通信 |
5 | Signal Ground (SG) | 信号地,用于连接信号线的地线 |
6 | Data Set Ready (DSR) | 数据设备就绪, 用于告诉发送方,接收方已经准备好进行数据通信 |
7 | Request to Send (RTS) | 请求发送,接收方通过RTS,告知发送方已经准备好发送数据 |
8 | Clear to Send (CTS) | 清除发送,发送方检测CTS,判断接收方是否准备好接收数据 |
9 | Ring Indicator (RI) | 响铃指示,用于指示对端设备正在呼叫本地设备 |
引脚定义中,需要注意DTE,DCE之间TXD和RXD线的接线顺序,DTE的TXD对应DCE的RXD,DTE的RXD对应DCE的TXD,双线全双工通信的情况下,接线如下图:
只有将数接收信号线,数据发送信号线相互交叉,才能确保数据能在DTE和DCE设备之间正常传输。在目前的工业控制领域,串口线通常只会使用3个引脚:TXD,RXD,GND,直接进行信号传输,其余引脚会被裁剪掉
硬件流控制下的TXD,RXD接线
在双线全双工,还额外使能了硬件流控制的情况下,DTE/DCE会同时扮演数据发送设备和接收设备。所以还需要连接RTS,CTS引脚,RTS,CTS引脚的链接和TXD,RXD的相互交叉连接类似,如上图虚线部分。数据接收方会通过拉低RTS表示允许发送数据,将RTS拉高表示禁止发送数据,而数据发送方会在发送数据前检测CTS,CTS为低时进行数据发送操作,CTS为高时,等待接收方允许数据发送
设备之间以RS-232标准调制信息通过串口线进行数据传输时,并不是胡乱地发送二进制数据,而是有组织的通过数据包的形式进行数据的传输。对于UART协议,它的串口数据包基本组成为:
UART协议(Universal Asynchronous Receiver and Transmitter 通用异步传输协议)是USART协议(Universal Synchronous Asynchronous Receiver and Transmitter 通用同步异步传输协议)的一种实现方式,USART协议满足工业标准的NRZ串行数据格式要求,兼容多种硬件标准,同时还支持异步通讯或同步通讯。UART协议在USART协议的基础上裁剪掉了同步传输功能,只支持异步传输,其余的特性都基本和USART协议基本保持一致
USART/UART为了提高对硬件的兼容性,很多传输参数可以由开发者自定义,包括但不限于:
UART是异步通讯,不需要额外的时钟信号线。但是需要通信双方指定一个通信波特率,数据发送方按照波特率发送数据,数据接收方按照波特率解析数据即可。常用的波特率有4800bps,9600bps,115200bps
不同波特率对UART通讯的影响
波特率代表着单位时间内传输的码元数量的多少,对于二进制数据传输而言,波特率=比特率。波特率越高,说明单位时间内传输的码元数量越多,也就是每个码元的持续时间会变短,会导致信号传输中的衰减和噪声的影响就会得越明显,同时对线路传输线缆的要求就会提高。所以在选择波特率时,应当考虑传输距离等外部因素
小数波特率发生器
USART协议中还规定了小数波特率发生器的实现,小数波特率发生器可以让波特率的范围可以更随意的调节,提高了USART协议的兼容性
起始位/停止位表示一个数据包的开始与结束。这对信号对于数据接收端尤为重要,由于异步通信不存在时钟信号线,所以需要通过起始位和停止位的上升下降沿,以及设定的波特率来同步时钟信号,根据时钟信号对有效数据部分进行分割和提取。数据包的起始位用一个单位时间的逻辑0表示,数据包的结束位用0.5,1,1.5或2个单位时间的逻辑1表示
有效数据位即开发者需要传输的数据,长度可以约定为5,6,7或8bits。通常使用8bits
正常环境中,数据通信可能会受到外部的干扰,导致数据出现偏差,所以经常会在有效数据位之后加上一个可选的数据校验位,用于校验有效数据位的数据是否传输正确。常见的校验方法有:
USART功能模块有以下引脚,分布在框图左右两侧:
STM32的部分GPIO引脚可复用为USART或UART功能,具体到STM32F103VET野火指南者这块MCU上,可复用的引脚如下:
可以看出野火指南者的MCU支持3组USART,2组UART,其中USART1复用的GPIO属于APB2总线下的外设,时钟源也来源于APB2总线时钟,最高频率可达72MHz。而其余的USART2,USART3,UART4,UART5属于APB1总线下的外设,时钟源来源于APB1总线时钟,最高频率可达36MHz。UART由于不需要时钟线,所以没有SCLK引脚
USART_DR数据寄存器有32bits,其中只有低九位有效。一般传输的数据长度都为8bits,仅使用数据寄存器的D0~D8,是否使用D9,取决于USART_CR1控制寄存器的M位设置,M位设置为0时,传输数据长度为8bits,M位设置为1时,传输数据长度为9bits
USART_DR数据寄存器可写可读,但是读取/写入操作对应的是不同的功能,写入数据对应发送数据功能,而读取操作对应读取数据功能。这是因为USART_DR数据寄存器实际上是由两个寄存器组成:分别是专门用于发送数据的发送数据寄存器TDR和专门用于接收数据的接收数据寄存器RDR,对应框图中偏上灰色虚线框部分。作为辅助,这两个寄存器还分别搭配了发送数据移位寄存器和接收数据移位寄存器
当写入数据到USART_DR数据寄存器,总线上的数据会先保存到发送数据寄存器TDR,当发送移位寄存器可用时,将数据发送到发送移位寄存器,默认按照MSB-LSB的顺序传输。当从USART_DR数据寄存器读取数据时,恰恰和写入数据相反,先将数据一位一位保存到接收移位寄存器,数据存满后,将数据转移到接收数据寄存器RDR,再发送到总线
USART功能模块的数据发送/接收/中断/波特率调整/数据校验/唤醒等功能,主要由USART_CR1,USART_CR2,USART_CR3,GTPR四个寄存器控制,位于功能框图的中部位置
使用USART功能模块前,要先将USART_CR1控制寄存器中的UE位置1,以启用USART模块的时钟
数据的主要发送流程如下:
以上内容中总结成表:
寄存器 | 标志位操作 | 功能 |
---|---|---|
USART_CR1 | TE | 启用数据发送功能 |
USART_CR1 | TXIE | 启用数据发送完成触发中断 |
USART_DR | - | 写入数据 |
USART_SR | TC | 判断发送操作是否完成 |
通过控制寄存器还可以对数据包中的部分发送数据的参数进行调整:
数据的主要接收流程如下:
将以上内容总结成表:
寄存器 | 标志位操作 | 功能 |
---|---|---|
USART_CR1 | RE | 启用数据接收功能 |
USART_CR2 | RXNEIE | 启用数据接收完成触发中断 |
USART_DR | - | 读取数据 |
USART_SR | RXNE | 判断发送操作是否完成 |
USART模块的发送与接收使用相同的波特率,计算公式如下:
其中fck为USART模块的时钟频率。USARTDIV为无符号定点数,由USART_BRR波特率寄存器控制,波特率寄存器有16bits,分为整数部分DIV_Fraction[11:0]和小数部分DIV_Fraction[3:0]
举例说明,波特率的计算/USART_BSS寄存器配置
STM32F103VET野火指南者的USART模块支持奇偶校验。启动奇偶校验要通过USART_CR1寄存器的PCE位来控制,PCE位置1启用奇偶校验功能,奇偶校验由硬件自动完成,发送数据时自动添加校验位,接收数据时自动判断校验位。需要注意的是,启动奇偶校验时,必须同时将通过USART_CR1控制寄存器将M位置1时,调整传输数据长度为9bits,相当于8bits有效数据+1bit校验位;如果接收数据时,还将USART_SR寄存器的PE位置1,那么奇偶校验失败还会触发中断
USART模块可以触发的中断类型总结如下表:
中断请求名称 | 中断类型 | 中断触发条件 | 中断功能 |
---|---|---|---|
USART_TXE | 发送中断 | USART的数据寄存器为空 | 触发数据发送操作 |
USART_TC | 发送完成中断 | USART成功发送完一个数据帧 | 发送完成后的处理操作 |
USART_RXNE | 接收中断 | USART接收到新的数据 | 触发数据接收操作 |
USART_IDLE | 空闲中断 | USART接收线路上出现空闲状态 | 空闲状态检测和处理 |
USART_ORE | 溢出中断 | USART的数据寄存器溢出 | 处理数据溢出情况 |
以下内容基于STM32F103VET野火指南者开发板,实现将按键的按下/释放消息发送到PC端,同时PC端发送任意数据到开发板会回传回PC的功能。使用到了开发板的按键模块,USB串口模块(实际上是USART模块+CH340G芯片),原理图分别如下:
实现思路为:
首先,需要解析标准库提供的USART初始化结构体,以及相关操作函数。外设初始化结构体+外设相关操作函数是使用标准库的精髓所在:
/**
* @brief USART Init Structure definition
*/
typedef struct
{
uint32_t USART_BaudRate; /*!< This member configures the USART communication baud rate.
The baud rate is computed using the following formula:
- IntegerDivider = ((PCLKx) / (16 * (USART_InitStruct->USART_BaudRate)))
- FractionalDivider = ((IntegerDivider - ((u32) IntegerDivider)) * 16) + 0.5 */
uint16_t USART_WordLength; /*!< Specifies the number of data bits transmitted or received in a frame.
This parameter can be a value of @ref USART_Word_Length */
uint16_t USART_StopBits; /*!< Specifies the number of stop bits transmitted.
This parameter can be a value of @ref USART_Stop_Bits */
uint16_t USART_Parity; /*!< Specifies the parity mode.
This parameter can be a value of @ref USART_Parity
@note When parity is enabled, the computed parity is inserted
at the MSB position of the transmitted data (9th bit when
the word length is set to 9 data bits; 8th bit when the
word length is set to 8 data bits). */
uint16_t USART_Mode; /*!< Specifies wether the Receive or Transmit mode is enabled or disabled.
This parameter can be a value of @ref USART_Mode */
uint16_t USART_HardwareFlowControl; /*!< Specifies wether the hardware flow control mode is enabled
or disabled.
This parameter can be a value of @ref USART_Hardware_Flow_Control */
} USART_InitTypeDef;
其中结构体成员的含义分别如下:
之后,建立UART相关板级支持文件bsp_uart.h,bsp_uart.c,内容分别如下:
#ifndef __BSP_UART_H__
#define __BSP_UART_H__
#include "stm32f10x_usart.h"
// GPIO for USART1
#define USART_GPIO_CLK (RCC_APB2Periph_GPIOA)
#define USART_GPIO_APBxClkCmd RCC_APB2PeriphClockCmd
#define USART_TX_GPIO_PORT GPIOA
#define USART_TX_GPIO_PIN GPIO_Pin_9
#define USART_RX_GPIO_PORT GPIOA
#define USART_RX_GPIO_PIN GPIO_Pin_10
// USART1
#define USARTx USART1
#define USART_CLK RCC_APB2Periph_USART1
#define USART_APBxClkCmd RCC_APB2PeriphClockCmd
#define USART_BAUDRATE 115200
// USART1 Interrupt
#define USART_IRQ USART1_IRQn
#define USART_IRQHandler USART1_IRQHandler
static void NVIC_Configuration(void);
void USART_Config(void);
void USART_SendByte(USART_TypeDef *pUSARTx, uint8_t ch);
void USART_SendString(USART_TypeDef *pUSARTx, char *str);
#endif
#include "misc.h"
#include "core_cm3.h"
#include "stm32f10x.h"
#include "stm32f10x_gpio.h"
#include "bsp_uart.h"
#include "stm32f10x_usart.h"
static void NVIC_Configuration(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
// Priority group config
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
// USART NVIC param config
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
void USART_Config(void)
{
// GPIO
GPIO_InitTypeDef GPIO_InitStructure;
// USART GPIO CLK enable
USART_GPIO_APBxClkCmd(USART_GPIO_CLK, ENABLE);
// USART TX GPIO config
GPIO_InitStructure.GPIO_Pin = USART_TX_GPIO_PIN;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(USART_TX_GPIO_PORT, &GPIO_InitStructure);
// USART RX GPIO config
GPIO_InitStructure.GPIO_Pin = USART_RX_GPIO_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(USART_RX_GPIO_PORT, &GPIO_InitStructure);
// USART1
USART_InitTypeDef USART_InitStructure;
// USART1 CLK enable
USART_APBxClkCmd(USART_CLK, ENABLE);
// USART1 param config
USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;
USART_InitStructure.USART_BaudRate = USART_BAUDRATE;
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
USART_InitStructure.USART_Parity = USART_Parity_No;
USART_InitStructure.USART_StopBits = USART_StopBits_1;
USART_Init(USARTx, &USART_InitStructure);
// Interupt
// Interupt Priority
NVIC_Configuration();
// Interupt enable
USART_ITConfig(USARTx, USART_IT_RXNE, ENABLE);
// USART enable
USART_Cmd(USARTx, ENABLE);
}
void USART_SendByte(USART_TypeDef *pUSARTx, uint8_t ch)
{
USART_SendData(pUSARTx, ch);
while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET);
}
void USART_SendString(USART_TypeDef *pUSARTx, char *str)
{
unsigned int bit = 0;
do
{
USART_SendByte(pUSARTx, str[bit]);
bit++;
}while (str[bit] != '\0');
while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TC) == RESET);
}
USART数据发送中寄存器TXE和TC位的区别
在以上函数USART_SendByte和USART_SendString中,分别通过TXE和TC位取值来判断数据是否发送完成。初学者再次容易混淆概念,认为这两者都是用于判断数据发送完成情况的,其实并不是!!!
- TXE:这个位由硬件控制,置1时表示数据发送寄存器TDR的数据已经转移到了发送数据移位寄存器中,数据发送寄存器为空,可以重新写入数据
- TC:这个位由硬件控制,置1时表示发送缓冲区中的所有数据都已经通过发送数据移位寄存器发送到数据接收方,此次发送操作完成
可以看出,TXE位置1的顺序应该是要比TC要早的,TXE位置1时,数据发送操作还没有完成,数据仅仅是传输到了发送数据移位寄存器中,而TC位置1才真正代表着所有数据都通过发送数据移位寄存器发送出去了
通过板级支持文件,可以完善相应的上层功能需求:
uint8_t KEY1_STATE = KEY_ON;
int userapp(void)
{
USART_Config();
Key_GPIO_Init();
uint8_t Key1_state;
while (1)
{
// Get key state, detect edge only
Key1_state = Key_Scan(KEY1_INT_GPIO_PORT, KEY1_GPIO_PIN, &KEY1_STATE);
if (Key1_state == KEY_PRESS)
{
USART_SendString(USARTx, "KEY 1 Pressed\n");
}
else if (Key1_state == KEY_RELEASE)
{
USART_SendString(USARTx, "KEY 1 Released\n");
}
}
void USART1_IRQHandler(void)
{
uint8_t rev_char;
// Is receive buffer empty?
if (USART_GetFlagStatus(USARTx, USART_IT_RXNE))
{
// Get receive data & send it back
rev_char = USART_ReceiveData(USARTx);
USART_SendData(USARTx, rev_char);
}
}
示例代码已上传Github:Sinuxtm32