LV16-15-串口通信-02-串口接收发送实现
本文主要是STM32开发——串口通信基础知识的一些相关笔记,若笔记中有错误或者不合适的地方,欢迎批评指正😃。
点击查看使用工具及版本
Windows | windows11 |
Ubuntu | Ubuntu16.04的64位版本 |
VMware® Workstation 16 Pro | 16.2.3 build-19376536 |
SecureCRT | Version 8.7.2 (x64 build 2214) - 正式版-2020年5月14日 |
开发板 | 正点原子 i.MX6ULL Linux阿尔法开发板 |
uboot | NXP官方提供的uboot,NXP提供的版本为uboot-imx-rel_imx_4.1.15_2.1.0_ga(使用的uboot版本为U-Boot 2016.03) |
linux内核 | linux-4.15(NXP官方提供) |
STM32开发板 | 正点原子战舰V3(STM32F103ZET6) |
点击查看本文参考资料
- 通用
分类 | 网址 | 说明 |
官方网站 | https://www.arm.com/ | ARM官方网站,在这里我们可以找到Cotex-Mx以及ARMVx的一些文档 |
https://www.st.com/content/st_com/zh.html | ST官方网站,在这里我们可以找到STM32的相关文档 | |
https://www.stmcu.com.cn/ | 意法半导体ST中文官方网站,在这里我们可以找到STM32的相关中文参考文档 | |
http://elm-chan.org/fsw/ff/00index_e.html | FatFs文件系统官网 | |
教程书籍 | 《ARM Cortex-M3权威指南》 | ARM公司专家Joseph Yiu(姚文祥)的力作,中文翻译是NXP的宋岩 |
《ARM Cortex-M0权威指南》 | ||
《ARM Cortex-M3与Cortex-M4权威指南》 | ||
开发论坛 | http://47.111.11.73/forum.php | 开源电子网,正点原子的资料下载及问题讨论论坛 |
https://www.firebbs.cn/forum.php | 国内Kinetis开发板-野火/秉火(刘火良)主持的论坛,现也做STM32和i.MX RT | |
https://www.amobbs.com/index.php | 阿莫(莫进明)主持的论坛,号称国内最早最火的电子论坛,以交流Atmel AVR系列单片机起家,现已拓展到嵌入式全平台,其STM32系列帖子有70W+。 | |
http://download.100ask.net/index.html | 韦东山嵌入式资料中心,有些STM32和linux的相关资料也可以来这里找。 | |
博客参考 | http://www.openedv.com/ | 开源网-原子哥个人博客 |
http://blog.chinaaet.com/jihceng0622 | 博主是原Freescale现NXP的现场应用工程师 | |
cortex-m-resources | 这其实并不算是一个博客,这是ARM公司专家Joseph Yiu收集整理的所有对开发者有用的官方Cortex-M资料链接(也包含极少数外部资源链接) |
- STM32
STM32 | STM32 HAL库开发实战指南——基于F103系列开发板 | 野火STM32开发教程在线文档 |
STM32库开发实战指南——基于野火霸道开发板 | 野火STM32开发教程在线文档 |
- SD卡
SD Association | 提供了SD存储卡和SDIO卡系统规范 |
点击查看相关文件下载
STM32F103xx英文数据手册 | STM32F103xC/D/E系列的英文数据手册 |
STM32F103xx中文数据手册 | STM32F103xC/D/E系列的中文数据手册 |
STM32F10xxx英文参考手册(RM0008) | STM32F10xxx系列的英文参考手册 |
STM32F10xxx中文参考手册(RM0008) | STM32F10xxx系列的中文参考手册 |
Arm Cortex-M3 处理器技术参考手册-英文版 | Cortex-M3技术参考手册-英文版 |
STM32F10xxx Cortex-M3编程手册-英文版(PM0056) | STM32F10xxx/20xxx/21xxx/L1xxxx系列Cortex-M3编程手册-英文版 |
SD卡相关资料——最新版本 | 有关SD卡的一些资料可以从这里下载 |
SD卡相关资料——历史版本 | 有关SD卡的一些历史版本资料可以从这里下载,比如后边看的SD卡2.0协议 |
SD 2.0 协议标准完整版 | 这是一篇关于SD卡2.0协议的中文文档,还是比较有参考价值的,可以一看 |
一、串口打印实现
说明,这里使用STM32CubeM进行配置和导出工程。
1. 硬件设计
我使用的是正点原子的战舰V3,串口部分电路如下,串口 1 与 USB 串口并没有在 PCB 上连接在一起,需要通过跳线帽来连接一下。这里我们把 P4 的 RXD 和 TXD 用跳线帽与 PA9 和 PA10 连接起来。
2. STM32CubeMX配置
注意这里的时钟都没配置,会默认开启的,这一部分不涉及中断相关内容。
- (1)PA9配置(TXD)
- (2)PA10配置(RXD)
- (3)USART1配置
- (4)导出工程即可
3. 函数实现
上边只是完成了基础的初始化等功能,具体的接收,发送,库里边有对应的函数,我们自己写一下熟悉一下过程。
3.1 HAL库函数收发数据
HAL库提供了两个函数,让我们可以获取和发送数据,它们定义在 stm32f1xx_hal_uart.c:
1 | HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout); |
3.2 发送一个字节数据
1 | void Uart_Putchar(uint8_t ch) |
3.3 接收一个字节数据
1 | uint8_t Uart_Getchar(void) |
3.4 printf实现
我们在编写C语言的时候,经常使用printf,还可以格式化打印,这是不是很方便呢?我们也可以在STM32中使用printf来调用串口,我们只需要重写下边这个函数就可以啦:
1 | int fputc(int ch, FILE *f); |
我们重新定义该函数如下:
1 |
|
4. printf的一个坑
当我们重写了fputc后,只要调用printf函数,程序直接卡死,有两种解决办法,第二种方法中说明了产生这个问题的原因。
4.1 使用微库
在MDK5中点击【options for target】(常见的哪个魔术棒),在【Target】→【Use MicroLIB】勾选,使用微库。
4.2 避免使用半主机模式
4.2.1 半主机模式
半主机是用于 ARM 目标的一种机制,可将来自应用程序代码的输入/输出请求传送至运行调试器的主机。 例如,使用此机制可以启用 C 库中的函数,如 printf() 和 scanf(),来使用主机的屏幕和键盘,而不是在目标系统上配备屏幕和键盘。这种机制很有用,因为开发时使用的硬件通常没有最终系统的所有输入和输出设备。 半主机可让主机来提供这些设备。简单的来说,半主机模式就是通过仿真器实现开发板在电脑上的输入和输出。和半主机模式功能相同的是ITM调试机制。
在嵌入式的编程中我们要使用printf、fopen、fclose等函数的,但是因为嵌入式的程序中并没有对这些函数的底层实现,使得设备运行时会进入软件中断BAEB处,这时就需要__use_no_semihosting_swi 这个声明,使程序遇到这些文件操作函数时不停在此中断处。当目标板脱离仿真器(jlink/ulink)单独运行时,不能使用半主机模式。否则进入软件中断BAEB处,无法再执行下去,这就导致了卡死问题
4.2.2 关闭半主机模式
1 |
这条语句可以关闭半主机模式,只需要在任意一个C文件中加入即可。还有在使用keil编程的过程中还会遇到以下错误:
1 | ..\OBJ\USART.axf: Error: L6915E: Library reports error: __use_no_semihosting was requested, but _ttywrch was referenced |
说的大概的意思就是关掉了半主机模式,但是函数__ttywrch被引用了,这时要把函数重写一遍,当然出现其他的函数被要求的时候,可以参考上面的函数进行编写,只要放到任意一个.c源文件之中即可。所以我们可以在任意一个.c文件中添加如下内容:
1 | //加入以下代码,支持printf函数,而不需要选择use MicroLIB |
二、开启串口中断
前边我们已经知道如何收发数据了,下边我们来看一下串口的中断操作。
1. 一般步骤
(1)使能相应的时钟
(2)配置GPIO管脚为串口功能
(3)设置中断优先级
(4)使能相应的中断
(5)实现中断服务程序
2. STM32CubeMX中断配置
对于GPIO等的配置,和前边保持一致,我们这里再加上中断的配置即可:
- NVIC配置
- Code generation
3. 编写中断服务函数
我们找到最终的中断服务函数回调函数:
1 | USART1_IRQHandler()--->HAL_UART_IRQHandler()--->UART_Receive_IT()--->HAL_UART_RxCpltCallback() |
我们只需要在 HAL_UART_RxCpltCallback() 编写我们的接收中断发生后要实现的功能即可:
1 | uint8_t RX[10] = {0}; |
4. 串口中断中的一个坑
这坑出现在接收数据的时候,有这样一个函数,这个函数是在发生接收中断的时候获取接收到的数据:
1 | HAL_StatusTypeDef HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size); |
最开始,我以为这个函数仅仅就是单纯的接收数据用的,但是,坑来了,我配置完串口以及串口中断后,无法产生串口中断。我去检查了串口引脚、串口是否配置了异步模式、串口全局中断是否打开、NVIC是否进行了配置,结果发现这些配置都正确,但是就是不发生接收中断。查阅各种资料及例程后,发现了这个函数的含义并不是我所想象的那么简单,我们来看一下函数原型:
这个函数是以非阻塞模式接收一定数量的数据,第一个参数就是串口的句柄,我们使用的哪个串口就填那个串口的句柄地址,第二个参数是一个缓冲区,当中断发生的时候,接收到的数据会被存放在这里,第三个参数是大小,单位是字节,当我们填1的时候,接收到一个字节就会调用一次回调函数,当我们填10的时候,接收到的数据若不足10个字节,那么就不会调用回调函数,但是依然会产生接收中断。
我们可以试一下,若是我们程序从头到尾都没有调用这个函数的话,我们的接收中断是不会进入的,即便我们全局中断和串口中断都开起来了,我们仔细看上边的图,会发现内部调用了一个 UART_Start_Receive_IT () 函数,函数里边又调用了三个含有ENABLE_IT的函数,做的是不同的使能。
我们看注释,会发现,第三个在使能UART_IT_RXNE,我们来看一下他是啥,其实看注释也能知道,不过我们来追踪一下,印象深刻一些。我们可以定位到以下宏:
1 |
我们再来看一下CR1寄存器:
其中的 bit[5]:RXNEIE,接收缓冲区非空中断使能 (RXNE interrupt enable) ,该位由软件设置或清除。 0:禁止产生中断; 1:当USART_SR中的ORE或者RXNE为’1’时,产生USART中断。
到这里就清晰了,这个函数不仅仅会获取数据,还会开启接收中断,为什么这里要开启?我们再来往前看,我们看一下这个函数:
1 | static HAL_StatusTypeDef UART_Receive_IT(UART_HandleTypeDef *huart) |
额,中间太多没用的占行数,就都删了,大致有这个关系就行了,我们看一下这个的定义 RxXferCount :UART Rx Transfer Counter翻译过来是 UART Rx传输计数器,我们看逻辑大概就是当这个值减到0的时候才会调用回调函数,这样不就联系起来了吗?我们通过 HAL_UART_Receive_IT 设置了串口的这个参数的值,然后每发生一次中断这个数就会自减1,当减到0的时候,调用回调函数,这是对于 HAL_UART_Receive_IT 第三个参数的理解,那么前边说的需要重新开启中断呢?我们看这个函数里,在 RxXferCount 减到0的时候,它先去关闭了接收中断,这样的话,每调用一次回调函数,就会关闭一次中断,我们要是不再开启接收中断的话,下一次接收就不会产生中断啦,这样就理清楚了这个关系了。
【总结】
(1)使用接收中断时,必须在初始化完成后调用一次 HAL_UART_Receive_IT() 函数来开启接收中断(其他地方实际上是没有开启接收中断的,这样才能产生第一次中断)
(2)每次调用回调函数的完成后一定要再调用一次 HAL_UART_Receive_IT() 来重新开启接收中断。
(3)HAL_UART_Receive_IT()只要调用,就会开启接收中断,但是中断多少次之后调用回调函数,由第三个参数决定,当第三个参数为1时,每接收到一个字节数据,都会调用一次回调函数,当不为1时,每接受一个字节都会产生一次中断,但是,接收到对应字节数的数据时才会调用一次回调函数。
5. 运行结果
我们将接收的字节数定义为 2,也就是接收到两个字节数据时才产生中断:
1 | void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) |
接下来我们来看看效果:
我们发送一个2,会发现没有输出,我们再点一次发送,下方状态栏的S变为2,此时上方显示出我们发送出去的两个2,这也验证了我们之前的分析过程。有一点需要注意,在串口调试助手中,空格可能不被识别的,我们关注下方发送的字节数即可:
三、串口接收设计
1. 相关定义
1 |
|
2. 接收中断回调函数
1 | /** |
【注意】一定要注意回调函数执行完毕后,要重新调用一次HAL_UART_Receive_IT()函数。
四、printf问题总结
前边我们在keil中编程的时候使用的是STM32CubeMX生成的工程,或者说自己使用标准库新建工程,出现重定向printf后无法打印的情况可以使用下边的处理方式1,但是后来我发现,在STM32CubeIDE中创建的工程,使用处理方式一后依然无法打印,后来上网搜了一下,又添加了一些东西,作为参考吧。
1. MDK中无法打印处理
串口是我们经常要用到的,我们可以将fputc一起写到避免半主机模式的代码中,这样,后续我们只需要赋值这一段代码到工程就可以啦:
1 | //加入以下代码,支持printf函数,而不需要选择use MicroLIB |
2. STM32CubeIDE中无法打印处理
这种方式主要是针对我们在STM32CubeIDE新建的工程中无法使用printf打印的处理,这里多定义了一些东西,其实这样更通用一些,至少这样可以解决问题。
1 |
|
3. Makefile工程无法打印处理
我们使用STM32CubeMX生成的Makefile工程,也是无法使用printf的,但是使用上边的两种方式都不行,makefile使用的交叉编译工具链为
1 | $ arm-none-eabi-gcc -v |
无法打印的处理方式为:
1 |
|
注意在要保证这个sys.c文件包含的有串口对应的头文件,我这里是因为包含了main.h,在main.h中包含了usart.h头文件。这个比起另外两种倒是简单了不少。