基础篇
工程参考代码链接 Github Gitee
使用CubeMX建立Keil5工程
这里我们偷懒直接使用点亮LED灯的工程
配置串口并生成工程
按照上图配置完串口后点击右上角生成工程
硬件连接串口
同样拿最便宜的DAPlink举例
TX --> PA10(RX) 电脑发送单片机接收
RX --> PA9(TX) 电脑接收单片机发送
GND --> GND 与单片机共地(已共地的可以不接)
打开工程分析生成的代码
使用到的HAL库函数说明
点击跳转F4的UASRT手册
1 2 3 4 5 6 HAL_StatusTypeDef HAL_UART_Init (UART_HandleTypeDef *huart) ; HAL_StatusTypeDef HAL_UART_Transmit (UART_HandleTypeDef *huart, const uint8_t *pData, uint16_t Size, uint32_t Timeout) ; HAL_StatusTypeDef HAL_USART_Receive (USART_HandleTypeDef *husart, uint8_t *pRxData, uint16_t Size, uint32_t Timeout) ;
学习将函数应用到代码实现通信
完成连接后电脑应该可以识别到串口连接(查看设备管理器可以看到识别的硬件串口,如果旁边有感叹号说明需要安装对应的驱动),并打开串口软件进行通信,推荐串口软件Jcom
单片机发送电脑接收
尝试用单片机发送串口信息到电脑上
1 2 3 4 5 6 7 8 MX_GPIO_Init(); MX_USART1_UART_Init(); HAL_UART_Transmit(&huart1, (uint8_t *)"Hello" , 5 , 0xFF );
编译烧录程序后查看串口软件,成功接收到了Hello信息
电脑发送单片机接收
尝试用电脑发送串口信息到单片机上
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 MX_GPIO_Init(); MX_USART1_UART_Init(); HAL_UART_Transmit(&huart1, (uint8_t *)"Hello" , 5 , 0xFF ); char recv_str[10 ]; HAL_UART_Receive(&huart1, recv_str, 5 , 0xFFFFFFFF ); HAL_UART_Transmit(&huart1, recv_str, 5 , 0xFF );
编译烧录程序后查看串口软件,成功接收到了Hello信息,然后我们发送12345给单片机
串口常用技巧
变量波形显示
比如我希望输出一个连续的三角波幅值在[0, 99],周期为1000ms,并同时输出一个幅值一样频率加倍的锯齿波
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 MX_GPIO_Init(); MX_USART1_UART_Init(); HAL_UART_Transmit(&huart1, (uint8_t *)"Hello" , 5 , 0xFF ); char wave_out = 0 ;uint16_t led_time = 0 ;while (1 ){ if (led_time >= 500 ) { led_time = 0 ; HAL_GPIO_TogglePin(LED_G_GPIO_Port, LED_G_Pin); } if (wave_out >= 100 ) { HAL_UART_Transmit(&huart1, (uint8_t []){0xD2 , (199 - wave_out), 0xD3 }, 3 , 0xFF ); }else { HAL_UART_Transmit(&huart1, (uint8_t []){0xD2 , wave_out, 0xD3 }, 3 , 0xFF ); } if (wave_out >= 100 ) { HAL_UART_Transmit(&huart1, (uint8_t []){0xCE , (wave_out % 100 ), 0xCF }, 3 , 0xFF ); }else { HAL_UART_Transmit(&huart1, (uint8_t []){0xCE , wave_out, 0xCF }, 3 , 0xFF ); } wave_out++; if (wave_out >= 200 ) { wave_out = 0 ; } HAL_Delay(5 ); led_time += 5 ; }
以三角波解析为例按图配置,包头包尾并不是必须的,但是如果没有包头包尾很有可能数据会错乱,甚至必要情况下需要增加校验码
打开曲线后可以看到输出的变量曲线
可读日志输出(非重定向)
如果我们直接使用HAL_UART_Transmit函数打印可读日志会导致代码移植性差、可读性差、代码量增加,所以很多人会选择使用重定向,把串口发送重定向到printf函数,使用printf来打印日志,但是这样就需要开启微库(Micro LIB)支持,容易造成死机问题,而且这样的printf依然有使用限制。所以我通常会构造类printf函数来代替串口发送函数 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 #include "stdio.h" #include "stdarg.h" #define CONSOLEBUF_SIZE 512 static char Uart_buf[CONSOLEBUF_SIZE];void PrintfDebug (const char *fmt, ...) { va_list args; va_start(args, fmt); int length = vsnprintf(Uart_buf, sizeof (Uart_buf) - 1 , fmt, args); va_end(args); HAL_UART_Transmit(&huart1,(uint8_t *)Uart_buf, length, 0xff ); }
构造了一个PrintfDebug函数,尝试在代码中输出LED引脚状态
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 MX_GPIO_Init(); MX_USART1_UART_Init(); HAL_UART_Transmit(&huart1, (uint8_t *)"Hello" , 5 , 0xFF ); while (1 ){ PrintfDebug("LED Green State %d\r\n" , HAL_GPIO_ReadPin(LED_G_GPIO_Port, LED_G_Pin)); HAL_GPIO_TogglePin(LED_G_GPIO_Port, LED_G_Pin); HAL_Delay(500 ); }
然后打开串口软件就可以看到打印的信息
练习部分
那我再提几个问题大家可以有空思考怎么做
F4的手册内有大量关于Usart的函数和宏命令,有很多命令是有妙用的
使用HAL_UART_Transmit发送串口数据会堵塞程序的执行,是否能使用DMA来发送数据就不会堵塞程序执行
如何像cmd窗口一样跟单片机进行交互,可以了解一下 Letter-Shell
通过串口来收发送文件,例如Ymodel协议
进阶篇
先预告,等基础部分搞得差不多的时候开工