【凌鸥LKS32】基础篇(四)·UART串口收发运用

目录
1. 简介
串口通讯 (Serial Communication) 是一种设备间非常常用的串行通讯方式,因为它简单便捷,因此大部分电子设备都支持该通讯方式,电子工程师在调试设备时也经常使用该通讯方式输出调试信息。
在计算机科学里,大部分复杂的问题都可以通过分层来简化。如芯片被分为内核层和片上外设。
对于通讯协议,我们也以分层的方式来理解,最基本的是把它分为物理层和协议层。物理层规定通讯系统中具有机械、电子功能部分的特性,确保原始数据在物理媒体的传输。协议层主要规定通讯逻辑,统一收发双方的数据打包、解包标准。简单来说物理层规定我们用嘴巴还是用肢体来交流,协议层则规定我们用中文还是英文来交流。
1.1 物理层
根据通讯使用的电平标准不同,串口通讯可分为 TTL 标准及 RS-232 标准,见表 TTL 电平标准与 RS-232 电平标准。
| 标准 | 逻辑 1(高电平) | 逻辑 0(低电平) | 典型应用场景 | 传输距离 |
| TTL | +3.3V ~ +5V | 0V | 单片机内部、板级通信 | 短(厘米级) |
| RS-232 | -3V ~ -15V | +3V ~ +15V | 工业控制、老式外设(如调制解调器) | 较长(可达 15 米) |

因为控制器一般采用 TTL 电平标准,因此需借助MAX3232 芯片实现TTL 电平与 RS-232 电平的双向转换,这是串口通讯中实现控制器与 RS-232 设备互联的关键环节。
1.2 协议层
串口通讯的数据包由发送设备通过自身的TXD 接口传输到接收设备的RXD 接口。在协议层,数据包由起始位、主体数据、校验位和停止位组成,通讯双方必须约定一致的数据包格式,才能正常收发数据。

| 组成部分 | 作用 | 典型配置 |
| 起始位 | 标志一个数据包的开始,使接收方同步接收数据 | 固定为 1 位,逻辑电平为低电平 |
| 主体数据 | 实际传输的有效信息,长度可变 | 常见为 8 位(1 字节) |
| 校验位 | 用于检测传输过程中是否发生错误,可选奇校验、偶校验或无校验 | 1 位或无 |
| 停止位 | 标志一个数据包的结束,提供接收方的缓冲时间 | 常见为 1 位或 2 位,逻辑电平为高电平 |
2. UART
通用异步收发传输器(Universal Asynchronous Receiver/Transmitter),通常称作 UART,是一种异步收发传输器。
UART 主要特征如下:
- 支持全双工工作
- 支持单线半双工工作
- 支持 8/9 位数据位
- 支持 1/2 停止位
- 支持奇 / 偶 / 无校验模式
- 带 1 字节发送缓存
- 带 1 字节接收缓存
- 支持 LIN 模式 break character 收发
- 支持空闲帧检测
- 支持一主多从的 Multi-drop Slave/Master 模式
2.1 发送
UART 包括一个字节发送缓冲区,当发送缓冲区有数据时,UART 将发送缓冲区的数据移入串行移位寄存器,并通过UART_TXD端口发送出去。只要UART发送缓冲区有数据,UART就会自动进行发送。 完成加载后,产生发送缓冲区空中断,此时,用户可以往发送缓冲区填入下一个需要发送的字节,这样,发送完成后,UART将加载这个字节进行发送。
完成发送后,会产生发送完成中断。
2.2 接收
UART 包括一个字节的接收缓冲区,当完成一个字节的接收后,会产生接收中断,并将接收到字节存储到接收缓冲区,用户应当在UART接收完成下一个字节前完成此字节的读取,否则缓冲区会被写入新接收的字节。接收部分内部使用高速时钟对 UART_RX 信号进行过采样来判定稳态的数据信号,防止噪声干扰造成错误接收。

2.3 波特率配置
UART输入时钟为系统主时钟,波特率通过两级分频实现。
波特率=UART模块时钟/(256*DIVH+DIVL+1)
可以通过SYS_CLK_DIV2对UART模块时钟进行分频。
UART模块时钟=系统主时钟/(1+SYS_CLK_DIV2)
波特率配置实例如下:
| UART 波特率 | SYS_CLK_DIV2 | UART_DIVH | UART_DIVL |
|---|---|---|---|
| 300 | 0x0007 | 0x9C | 0x3F |
| 600 | 0x0003 | 0x9C | 0x3F |
| 1200 | 0x0001 | 0x9C | 0x3F |
| 2400 | 0x0000 | 0x9C | 0x3F |
| 4800 | 0x0000 | 0x4E | 0x1F |
| 9600 | 0x0000 | 0x27 | 0x0F |
| 19200 | 0x0000 | 0x13 | 0x87 |
| 38400 | 0x0000 | 0x09 | 0xC3 |
| 43000 | 0x0000 | 0x08 | 0xB8 |
| 56000 | 0x0000 | 0x06 | 0xB1 |
| 57600 | 0x0000 | 0x06 | 0x82 |
| 115200 | 0x0000 | 0x03 | 0x40 |
注意,同一波特率,其配置系数可能不唯一。
2.4 收发端口互换(TX/RX互换)
UART模块支持Tx与Rx端口互换。通过将Tx对应的GPIO配置为输入使能,Rx对应的GPIO 配置为输出使能,即可实现Tx与Rx端口的互换。此时,GPIO第二功能仍选择为UART,UART本 身配置无需修改。
此外,如果要使用一个GPIO同时作为Tx和Rx,需要将IO分时复用为输入或输出,对应Rx 或Tx,即可实现单口半双工逻辑。
3. 代码编写
这里直接沿用上一章,移植好的代码:
找到带有UART功能的引脚,这里选择UART0也就是P1_0和P0_15引脚:

为了方便查看宏定义一下:
#define UART0_RX_PIN GPIO_Pin_15
#define UART0_RX_PORT GPIO0
#define UART0_RX_Source GPIO_PinSource_15
#define UART0_TX_PIN GPIO_Pin_0
#define UART0_TX_PORT GPIO1
#define UART0_TX_Source GPIO_PinSource_0
3.1 UART0引脚初始化
初始化P0.15和P1.0引脚,并将引脚复用为串口功能:
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_StructInit(&GPIO_InitStruct); //初始化结构体
//配置UART0_RXD P0.15
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN;
GPIO_InitStruct.GPIO_Pin = UART0_RX_PIN;
GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_Init(UART0_RX_PORT, &GPIO_InitStruct);
//配置UART0_TXD P1.0
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT;
GPIO_InitStruct.GPIO_Pin = UART0_TX_PIN;
GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_Init(UART0_TX_PORT, &GPIO_InitStruct);
GPIO_PinAFConfig(UART0_RX_PORT, UART0_RX_Source, AF4_UART);
GPIO_PinAFConfig(UART0_TX_PORT, UART0_TX_Source, AF4_UART);
MCU 的每个引脚通常具有多种功能(GPIO、UART、SPI、TIM 等),这里通过 GPIO_PinAFConfig 将 P0.15 和 P1.0 的复用功能指定为 AF4_UART(第4类复用功能 = UART 串口),这样引脚在硬件层面就连接到 UART0 外设的内部信号线上了。
3.2 UART0外设参数配置
和STM32差不多,8数据位,1停止位,无校验,使能串口收发中断:
UART_InitTypeDef UART_InitStruct;
UART_StructInit(&UART_InitStruct);
UART_InitStruct.BaudRate = 38400; /* 设置波特率38400 */
UART_InitStruct.WordLength = UART_WORDLENGTH_8b; /* 发送数据长度8位 */
UART_InitStruct.StopBits = UART_STOPBITS_1b; /* 停止位1位 */
UART_InitStruct.FirstSend = UART_FIRSTSEND_LSB; /* 先发送LSB */
UART_InitStruct.ParityMode = UART_Parity_NO; /* 无奇偶校验 */
UART_InitStruct.IRQEna = UART_IRQEna_SendOver | UART_IRQEna_RcvOver; /* 串口中断使能 */
UART_Init(UART0, &UART_InitStruct);
3.3 使能中断
然后在硬件初始化部分,将初始化函数调用,并初始化UART0的中断:
void Hardware_init(void)
{
__disable_irq(); /* 关闭中断 中断总开关 */
SYS_WR_PROTECT = 0x7a83; /* 解除系统寄存器写保护 */
FLASH_CFG |= 0x00080000; /* enable prefetch ,FLASH预取加速使能*/
IWDG_DISABLE(); /* 关闭看门狗*/
GPIO_init(); /* GPIO初始化 */
GPIO_init_key(); /* KEY初始化 */
uart0_init();
SoftDelay(100); /* 延时等待硬件初始化稳定 */
NVIC_SetPriority(GPIO_IRQn, 0); /* 设置GPIO中断优先级为最高 */
NVIC_EnableIRQ(GPIO_IRQn); /* 使能GPIO中断 */
NVIC_SetPriority(UART0_IRQn, 3); /* 设置UART0中断优先级为3 */
NVIC_EnableIRQ(UART0_IRQn); /* 使能UART0中断 */
__enable_irq(); /* 开启总中断 */
}
3.4 中断服务函数
完成功能实现当接收到数据后,将接受到的数据重新发送出来:
u8 Uart0_Buff = 0;
void UART0_IRQHandler(void)
{
if (UART_GetIRQFlag(UART0, UART_IF_SendOver)) // 发送完成中断
{
UART_ClearIRQFlag(UART0, UART_IF_SendOver); // 清除发送完成标志位
}
if (UART_GetIRQFlag(UART0, UART_IF_RcvOver)) // 接收完成中断
{
UART_ClearIRQFlag(UART0, UART_IF_RcvOver); // 清除接收完成标志位
Uart0_Buff = UART_ReadData(UART0); // 接收 1 Byte数据
UART_SendData(UART0, Uart0_Buff); // 发送数据
}
}
验证一下:

4. 进阶功能:printf重定向
上面我们只是完成简单的数据收发,如果我们想要打印一部分数据的时候,可以使用printf进行数据打印,将下面代码复制到uart.c即可:
/**
* fputc 重定向
* 标准库的 printf/puts 等函数内部都会调用 fputc
*/
int fputc(int ch, FILE *f)
{
uart_putchar((uint8_t)ch);
return ch;
}
然后找到图示位置,勾选上:

来到按键部分,找到GPIO中断服务函数,每当按键按下,通过printf打印提示信息,流水灯方向反转了:
extern u8 flow_direction;
void GPIO_IRQHandler(void)
{
if(EXTI_GetIRQFlag(KEY_STOP_PORT,KEY_STOP_Source))
{
flow_direction = !flow_direction; /* 切换流水灯方向 */
printf("按键按下,流水灯方向反转:%d\r\n",flow_direction); /* 打印流水灯方向 */
EXTI_ClearIRQFlag(KEY_STOP_PORT,KEY_STOP_Source);
}
}
看一下效果:



更多推荐
所有评论(0)