单片机的开发中,很多时候我们需要看单片机的工作情况,比如看寄存器的变化,看局部变量或者全局变量,又或者程序的逻辑设计有问题,没有按预设进行某一个步骤,此时要查看程序跑到哪一个步骤里去了,等等,我们都需要查看与验证。通常我们会查用两种办法,一种是debug,一种是把要查看的信息利用某种数据接口打印出来。
debug的好处很多,不足之处也有,好处是直接看内存、看寄存器(要编译器支持,比如keil就支持STM32,51类的),用断点方式查看程序死在哪个位置,但是debug的不足之处,它有时会造成编译器崩掉等,有的编译器看局部变量只能是在打断点时才能看到。所以就要利用别的手段,就是利用某种数据接口把要查看的信息打印出来。
这里的某种数据接口是什么呢?常见的如下:
1.RS232、RS422、RS485,其实质都是UART(串口)
2.Jlink调试口
3.Ethernet(以太网接口,即网线接口)
4.WiFi
5.CAN
6.蓝牙
以上6种都是常见的数据接口。这6种接口里,第3至第6种都是属于要配合规范协议使用的接口,在它们上面使用printf会稍显麻烦(对于高手来说却是easy的),必须按规范协议来组织数据帧,自由度不高,所以单片机上常用的是串口和jlink调试口。本文就介绍如何基于串口与Jlink实现printf函数。
第一篇:基于串口的printf
既然是基于串口,当然要把串口部份的代码先调试好,即起码的收一个字节和发一个字节要没有问题才行。如何写串口的收发函数,请自行在网上搜索,这里就不跨界了。
单片机的收发数据有两种方式,一种的用中断实现,一种不用中断,printf使用的是不用中断(划重点)。不要问为什么,照着做就行了。我经常说学东西,有时不要问为什么,先照着做就行了,做多了自然就能领悟为什么。就像我们学拿筷子夹东西,不要问为什么拇指在这个位置,食指在那个位置,要论起其中的力学原理,写好多篇论文都可以。各位想想,小时候学用筷子时老是搞不利索,急得用手抓,学会了后你还会想筷子该怎么拿吗,为什么这样握吗?
下面讲具体步骤:
1.更改编译器的设置,让编译器使用Micro LIB(这一个C语言库,具体干什么的自行搜索)。以keil为例,如图:勾选Micro LIB。(下图红色框中左侧的按钮)
2.串口非中断方式的,发一个字节的函数(代码)已调试好
3.printf使用的是非中断方式的、发一个字节的函数
4.包含stdio.h这个头文件。哪一个C文件中要使用printf,就必须要包含stdio.h。如图:
5.编写代码
先看代码,如下:
1. #ifdef __GNUC__
2. #define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
3. #else
4. #define PUTCHAR_PROTOTYPE int fputc(int ch, FILE* f)
5. #endif /* __GNUC__ */
6.
7. #ifdef __cplusplus
8. extern "C" {
9. #endif //__cplusplus
10.
11. PUTCHAR_PROTOTYPE
12. {
13. RS485A_DE = DOsnt; //发送脚使能,RS485为发送数据状态
14. delay_us(10); //等待发送脚的电平稳定
15. HAL_UART_Transmit(&huart5, (uint8_t *)&ch, 1, 0xFFFF); //调用STM32的HAL库,发送一个字节
16. delay_us(10); //避免数据信号震荡造成回环数据
17. RS485A_DE = DOrec; //发送脚除能,RS485恢复到接收数据状态
18. return (ch);
19. }
20.
21. #ifdef __cplusplus
22. }
23. #endif //__cplusplus
⑴ 这段代码放在哪里。我只介绍我的经验,我的硬件是STM32的串口5 + RS485,实质是基于串口5的RS485,故这段代码放在了我的RS485.C这个文件中,因为在RS485.C文件中,我可以直接调用串口发送函数。
如果你的硬件是RS232、RS422,甚至是TTL电平(即直接使用串口),那你就自行修改。
题外话:RS232、RS422、RS485、TTL均是描述硬件的电平的,而非通常讨论的协议(如ModBus),很多人都这两个搅和在一起,一张口就是RS485协议。所谓的RS485协议也说的是OSI结构的物理层,即电路,切记。
⑵ 读者要修改的部份:紫色部份。
因为RS485的半双工通讯的,所以要把发送脚使能脚变成使能,具体的逻辑请看上面代码中的注释。如果你的电路是RS232或者TTL电平的,那这种电路是全双工的,自然就不用这一句。
关键来了,第15行的代码,它是串口发一个字节的函数。因为我用的是STM32的HAL库,所以我这里调用的是库中发送一个字节的函数:HAL_UART_Transmit()函数,如果你的代码用的是标准库,请用标准库的代码。如果是纯寄存器操作,就调用寄存器的。总之,就像第二点所说,发一个字节的函数(代码)已调试好。
其它单片机请自行修改。
如此准备工作就已经完成,这时,只要包含了stdio.h这个头文件的C文件中,均可以使用printf函数了,你想打印什么信息,就能打印什么,数据、文字、符号都可以。不知道怎么打印数据、文字、符号的小白,请上网搜索,或者去问问你们的计算机课老师,此处不赘述。
然后,讲一下为何要包含stdio.h,因为printf实质是intfputc(int ch, FILE* f)函数,见代码的第4行,是在stdio.h文件中,把printf重定向了,指向了fputc()函数;而fputc()函数调用的是你串口发送一个字节(也可称字符)的函数。