串口通讯
STM32串口通讯有3种形式:轮询(阻塞式)、中断、DMA。我不知道中断方式的串口通讯有什么适合的应用场景:每接收/发送一个字节,就要发生一次中断,这对CPU反而是一种浪费。使用Cube HAL,轮询式的串口通讯最简单了,发送和接收数据分别有一个函数:
HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout); HAL_StatusTypeDef HAL_UART_Receive(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout);
Timeout 参数给 HAL_MAX_DELAY 常量,则无限超时,函数一直阻塞,直到发出/接收数据完成。
STM32F303RE 有3个 USART 和 2个 UART,而 USART 也可以工作在异步模式,相当于一个 UART。Nucleo 的 ST-LINK 部分也同时实现了 USB-TTL 转换,与 MCU 的 USART2 连接。因此,将 C 的标准输入输出(<stdio.h>)重定向到 USART2 似乎是一个挺不错的调试选项。
ARM GCC 使用的是 newlib-nano 标准 C 库。要使用 newlib-nano,并将 stdio 重定向到串口,gcc 选项和重定向的实现可参考 ARM GCC 附带的 retarget 示例,其路径为 share\gcc-arm-none-eabi\samples\src\retarget。概括起来:
- 指定 C 编译器选项: -fno-builtin
- 指定 linker(ld.exe)选项: --specs=nano.specs、--specs=nosys.specs
- 实现函数 _read()、_write() ,其原型如下。_read() 函数从串口读取数据,_write() 将数据写到串口。stdio 函数如 printf()、getchar() 等将最终调用这两个函数实现输入输出
int _read (int fd, char *ptr, int len); int _write (int fd, char *ptr, int len);
在 main() 函数中,首先打印一个字符串,然后进入主循环,将接收到的字符原样返回。如下:
... puts("Good judgments come from experiences. "); uint8_t buf[32]; while (1) { HAL_UART_Receive(&huart2, buf, 1, HAL_MAX_DELAY); putchar(buf[0]); fflush(stdout); }
我发现,在写出数据(字符/字符串)时,直到 \n 字符时,数据才真正写到串口。这应该是 stdio 的缓冲机制。puts() 自动在字符串末尾增加了一个 \n,因此都回立即写出到串口。而 putchar() 则需要显式 flush 一下。
_write() 重定向实现很简单,直接调用 HAL_UART_Transmit():
#include "main.h" #include "stm32f3xx_hal.h" #include "usart.h" int _write (int fd, char *ptr, int len) { /* Write "len" of char from "ptr" to file id "fd" * Return number of char written. * Need implementing with UART here. */ HAL_UART_Transmit(&huart2, (uint8_t *) ptr, len, HAL_MAX_DELAY); return len; }