文章目录
- STM32 串口
- 1. 数据通信的基本概念
- 2. 串口通信协议
- 3. 串口
- 4. 相关寄存器
- 5. MSP回调机制
- 6. HAL库中断回调机制
- 7. USART/UART异步通信配置步骤
- 8. IO引脚复用功能
- 9. 代码实现
STM32 串口
1. 数据通信的基本概念
-
通信方式:
-
数据传输方向:
-
数据同步方式:
-
通信速率:
在数字通信系统中,通信速率(传输速率)指数据在信道中传输的速度,分为传信率和传码率。
传信率(比特率):每秒钟传输的信息量,即每秒钟传输的二进制位数,单位为bit/s;
传码率(波特率):每秒钟传输的码元个数,单位为Band.
比特率与波特率之间的关系:
比特率 = 波特率 * log2M
M表示码元承载的信息量,可以理解为码元的进制数,码元采用二进制编码时,数值上波特率与比特率相等。
-
常见的串行通信接口
通信接口 接口引脚 数据同步方式 数据传输方向 UART (通用异步收发器) TXD:发送端 RXD:接收端 GND:公共地 异步通信 全双工 1-wire DQ:发送/接收端 异步通信 半双工 IIC SCL:同步时钟 SDA:数据输入/输出端 同步通信 半双工 SPI SCK:同步时钟 MISO:主机输入,从机输出 MOSI:主机输出,从机输入 CS:片选信号 同步通信 全双工
2. 串口通信协议
串行通信协议是指规定了数据包的内容,内容包含了起始位、主体数据、校验位、停止位,双方需要约定一致的数据包格式才能正常收发数据的有关规范。
- 波特率:波特率表示每秒传送的码元符号的个数,决定数据帧里面一个位的时间长度,两个要通信的设备的波特率一定要设置相同,常见的波特率是4800,9600,115200.
- 数据帧格式:起始位、停止位、有效数据位、校验位。
- 起始位和停止位:串行通信的一个数据帧是从起始位开始,直到停止位。数据帧中的起始位是由一个逻辑0的数据位表示,而数据帧的停止位可以是0.5、1、1.5、2个逻辑1的数据位表示,只要双方约定一致即可。
- 有效数据位:数据帧的起始位之后,紧接着数据位,这是我们发送的有效数据,有效数据位是低位在前,高位在后。
- 校验位:校验位可以认为是一个特殊的数据位。一般用来判断接收的数据位有无错误,检验的方法有:奇校验、偶校验、0校验、1校验。奇校验代表的是数据位和校验位中1的个数为奇数;偶校验代表的是数据位和校验位中1的个数为偶数;0 校验是指不管有效数据中的内容是什么,校验位总为0,1 校验是校验位总为1。 无校验是指数据帧中不包含校验位。
3. 串口
-
串口:串行通信接口,指按位发送和接收的接口。
-
RS232接口
通信示意图:
-
USB接口
-
USART框图
-
简化版框图
-
设置波特率
- 波特率相关寄存器
4. 相关寄存器
- 控制寄存器1(
CR1
)
-
控制寄存器2(
CR2
)
-
控制寄存器3(
CR3
)
-
数据寄存器(
DR
)
-
状态寄存器(
SR
)
5. MSP回调机制
6. HAL库中断回调机制
7. USART/UART异步通信配置步骤
-
配置串口工作参数
HAL_UART_Init()
-
串口底层初始化
HAL_UART_MspInit()
-
开启串口异步接收中断
HAL_UART_Receive_IT()
-
设置优先级,使能中断
HAL_NVIC_SetPriority() HAL_NVIC_EnableIRQ()
-
编写中断服务函数
USARTx_IRQHandler() UARTx_IRQHandler()
-
串口数据发送
HAL_UART_Transmit()
8. IO引脚复用功能
-
通用与复用
通用:IO端口的输入或输出是由GPIO外设控制
复用:IO端口的输入或输出由其它非GPIO外设控制
-
IO复用功能冲突问题:同一时间IO只能用作一种复用功能,否则会发生冲突,当遇到冲突时,可以考虑重映射功能。
-
实现:IO复用功能的实现是通过AFIO寄存器,STM32F103ZET6共有6个AFIO的寄存器,事件控制寄存器
AFIO_EVCR
,复用重映射和调试I/O配置寄存器AFIO_MAPR
,外部中断配置寄存器AFIO_EXTICR1
,外部中断配置寄存器AFIO_EXTICR2
,外部中断配置寄存器AFIO_EXTICR3
,外部中断配置寄存器AFIO_EXTICR4
。复用重映射和调试I/O配置寄存器AFIO_MAPR寄存器用来实现引脚的重映射功能。
例如
AFIO_MAPR
寄存器位 2 是对 USART1 的重映射,置 0: 没有重映像(TX/PA9,RX/PA10
); 置 1: 重映像(TX/PB6,RX/PB7
)。默认情况下,PA9 和 PA10 是作为串口 1 的引脚使用,假如 PA9 和 PA10 被用作其它地方,但还是需要用到串口 1,那么就可以在 AFIO_MAPR 的位 2 置 1,把串口 1 的引脚重映射到 PB6和PB7。
9. 代码实现
-
主要实现功能:通过串口接收或发送一个字符。
-
硬件原理图
电脑的USB引脚CH340 D+
与CH340 D-
通过CH340C
芯片与串口的TXD与RXD相连接,然后通过一个端子连接到单片机的USART1 RX PA10
和USART1 TX PA9
引脚,从而完成单片机的串口与电脑的USB口的连接。
-
代码实现
-
配置串口工作参数
void usart_init(uint32_t baudrate) { /*UART 初始化设置*/ g_uart1_handle.Instance = USART1; /* USART_UX */ g_uart1_handle.Init.BaudRate = baudrate; /* 波特率 */ g_uart1_handle.Init.WordLength = UART_WORDLENGTH_8B; /* 字长为8位数据格式 */ g_uart1_handle.Init.StopBits = UART_STOPBITS_1; /* 一个停止位 */ g_uart1_handle.Init.Parity = UART_PARITY_NONE; /* 无奇偶校验位 */ g_uart1_handle.Init.HwFlowCtl = UART_HWCONTROL_NONE; /* 无硬件流控 */ g_uart1_handle.Init.Mode = UART_MODE_TX_RX; /* 收发模式 */ HAL_UART_Init(&g_uart1_handle); /* HAL_UART_Init()会使能UART1 */ /* 该函数会开启接收中断:标志位UART_IT_RXNE,并且设置接收缓冲以及接收缓冲接收最大数据量 */ HAL_UART_Receive_IT(&g_uart1_handle, (uint8_t *)g_rx_buffer, RXBUFFERSIZE); }
-
串口底层初始化
void HAL_UART_MspInit(UART_HandleTypeDef *huart) { GPIO_InitTypeDef gpio_init_struct; if (huart->Instance == USART1) /* 如果是串口1,进行串口1 MSP初始化 */ { USART_TX_GPIO_CLK_ENABLE(); /* 使能串口TX脚时钟 */ USART_RX_GPIO_CLK_ENABLE(); /* 使能串口RX脚时钟 */ USART_UX_CLK_ENABLE(); /* 使能串口时钟 */ gpio_init_struct.Pin = USART_TX_GPIO_PIN; /* 串口发送引脚号 */ gpio_init_struct.Mode = GPIO_MODE_AF_PP; /* 复用推挽输出 */ gpio_init_struct.Pull = GPIO_PULLUP; /* 上拉 */ gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH; /* IO速度设置为高速 */ HAL_GPIO_Init(USART_TX_GPIO_PORT, &gpio_init_struct); gpio_init_struct.Pin = USART_RX_GPIO_PIN; /* 串口RX脚 模式设置 */ gpio_init_struct.Mode = GPIO_MODE_AF_INPUT; /* 串口RX脚 必须设置成输入模式 */ gpio_init_struct.Pull = GPIO_PULLUP; //设置上拉输入 HAL_GPIO_Init(USART_RX_GPIO_PORT, &gpio_init_struct); #if USART_EN_RX HAL_NVIC_EnableIRQ(USART_UX_IRQn); /* 使能USART1中断通道 */ HAL_NVIC_SetPriority(USART_UX_IRQn, 3, 3); /* 组2,最低优先级:抢占优先级3,子优先级3 */ #endif } }
-
开启串口异步接收中断
在1.中实现
-
设置中断优先级
在2.中实现
-
编写中断服务函数
void USART1_IRQHandler(void) { HAL_UART_IRQHandler(&g_uart1_handle); /* 调用HAL库中断处理公用函数 */ }
-
编写接收完成回调函数
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { if (huart->Instance == USART1) /* 如果是串口1 */ { if ((g_usart_rx_sta & 0x8000) == 0) /* 接收未完成 */ { if (g_usart_rx_sta & 0x4000) /* 接收到了0x0d(即回车键) */ { if (g_rx_buffer[0] != 0x0a) /* 接收到的不是0x0a(即不是换行键) */ { g_usart_rx_sta = 0; /* 接收错误,重新开始 */ } else /* 接收到的是0x0a(即换行键) */ { g_usart_rx_sta |= 0x8000; /* 接收完成了 */ } } else /* 还没收到0X0d(即回车键) */ { if (g_rx_buffer[0] == 0x0d) g_usart_rx_sta |= 0x4000; else { g_usart_rx_buf[g_usart_rx_sta & 0X3FFF] = g_rx_buffer[0]; g_usart_rx_sta++; if (g_usart_rx_sta > (USART_REC_LEN - 1)) { g_usart_rx_sta = 0; /* 接收数据错误,重新开始接收 */ } } } } HAL_UART_Receive_IT(&g_uart1_handle, (uint8_t *)g_rx_buffer, RXBUFFERSIZE); } }
在接收完成回调函数中,首先判断串口的类型,然后按照接收的数据格式进行判断,以回车符(0x0d)和换行符(0x0a)为结尾,如果未接收到回车符和换行符,表示数据未接收完成,
g_usart_rx_sta
为串口接收标志,用于判断是否接收完成;g_rx_buffer
数据接收缓冲区,串口发送过来的数据会存放在该缓冲区,然后把接收到的数据放在g_usart_rx_buf
数组里面。在接收完成后,开启串口异步接收中断,等待下一次数据的接收。 -
主函数的编写
// 1. 定义串口接收数据长度的变量和超时时间变量 uint8_t len; uint16_t times = 0; //2. 进行各种外设的初始化操作,打开相关外设的时钟 HAL_Init(); /* 初始化HAL库 */ sys_stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */ delay_init(72); /* 延时初始化 */ led_init(); /* LED初始化 */ key_init(); //按键初始化 beep_init(); //蜂鸣器初始化 exti_init(); //外部中断初始化 usart_init(115200); //串口初始化 //3. 循环主体 while (1) { if (g_usart_rx_sta & 0x8000) /* 接收到了数据 */ { len = g_usart_rx_sta & 0x3fff; /* 得到此次接收到的数据长度 */ printf("\r\n您发送的消息为:\r\n"); HAL_UART_Transmit(&g_uart1_handle,(uint8_t*)g_usart_rx_buf, len, 1000); /* 发送接收到的数据 */ while(__HAL_UART_GET_FLAG(&g_uart1_handle, UART_FLAG_TC) != SET); /* 等待发送结束 */ printf("\r\n\r\n"); /* 插入换行 */ g_usart_rx_sta = 0; } else { times++; if (times % 30 == 0) LED_toggle(); /* 闪烁LED,提示系统正在运行. */ delay_ms(10); } }
主函数的循环里面主要是判断是否接收到数据,然后把接收到的数据发送出去,通过
SR
寄存器的TC
位判断数据是否发送完成;同时使用LED灯代表系统在正常运行。
-
声明:资料来源(战舰STM32F103ZET6开发板资源包)
- Cortex-M3权威指南(中文).pdf
- STM32F10xxx参考手册_V10(中文版).pdf
- STM32F103 战舰开发指南V1.3.pdf
- STM32F103ZET6(中文版).pdf
- 战舰V4 硬件参考手册_V1.0.pdf