==
在C语言的学习过程中,我们经常会使用printf
函数打印变量来进行程序调试,在单片机中我们同样可以这样操作。首先先来看代码。
serve.h
#ifndef __SERVE_H__
#define __SERVE_H__
/*
datatype
*/
typedef unsigned char uint8_t;
typedef unsigned int uint16_t;
typedef unsigned long uint32_t;
typedef signed char int8_t;
typedef signed int int16_t;
typedef signed long int32_t;
typedef float fp32;
typedef unsigned char bool_t;
/*
funtion:delay
explanation:
model:stc89
crystal frequency:11.0592MHz
*/
extern void delay_ms(uint32_t ms);
/*
funtion:for quick debugging
explanation:
bps:9600
timer1:mode2
UART:mode1
switch:#define _DEBUG_(before #include"serve.h")
remember to use uart_init before!
*/
#ifdef _DEBUG_
#include <stdio.h>
#include <reg52.h>
#define debug_d8(x)\
do{\
TI = 1;\
printf("%s\n", __FILE__);\
printf("%s\n", __DATE__);\
printf("%s\n", __TIME__);\
printf("%s = %bd\n\n", #x, x);\
while(!TI);\
TI = 0;\
}while(0U)
#define debug_d16(x)\
do{\
TI = 1;\
printf("%s\n", __FILE__);\
printf("%s\n", __DATE__);\
printf("%s\n", __TIME__);\
printf("%s = %d\n\n", #x, x);\
while (!TI);\
TI = 0;\
} while (0U)
#define debug_d32(x)\
do{\
TI = 1;\
printf("%s\n", __FILE__);\
printf("%s\n", __DATE__);\
printf("%s\n", __TIME__);\
printf("%s = %ld\n\n", #x, x);\
while(!TI);\
TI = 0;\
}while(0U)
#define debug_f32(x)\
do{\
TI = 1;\
printf("%s\n", __FILE__);\
printf("%s\n", __DATE__);\
printf("%s\n", __TIME__);\
printf("%s = %f\n\n", #x, x);\
while(!TI);\
TI = 0;\
}while(0U)
#else
#define debug_d8(x)
#define debug_d16(x)
#define debug_d32(x)
#define debug_f32(x)
#endif
/*
explanation:
crystal frequency:11.0592MHz
bps:9600
timer1:mode2
UART:mode1
*/
extern void uart_init(void);
#endif
serve.c
#include <reg52.h>
#include "serve.h"
void uart_init(void)
{
TMOD = 0x20;
TH1 = 0xfd;
TL1 = 0xfd;
TR1 = 1;
REN = 1;
SM0 = 0;
SM1 = 1;
EA = 1;
ES = 1;
}
void delay_ms(uint32_t ms)
{
uint32_t i,j;
for( i = ms; i > 0; i--)
{
for( j = 110; j > 0; j--);
}
}
main.c(demo)
#define _DEBUG_
#include <reg52.h>
#include "serve.h"
int main(void)
{
uint8_t a = 1;
uint16_t b = 2;
uint32_t c = 3;
fp32 d = 4.5;
uart_init();
while(1)
{
debug_d8(a);
debug_d16(b);
debug_d32(c);
debug_f32(d);
delay_ms(500);
}
}
我们在电脑上打开串口助手,就可以看到下面图片这样的效果了(注意时间为代码编译的时间)。
1.只需要将这三个文件包含进工程,编译下载即可运行。我使用的单片机是STC89C52,晶振频率为11.0592MHz,使用串口1,波特率为9600。如果使用的是其他系列的单片机,只需要更改串口1初始化函数和头文件即可。
2.我是采用宏定义的方式实现打印变量,之所以这样做,是为了方便考虑。在程序中我们想要查看变量值的地方,只需要调用debug
函数就可以了,而宏定义开关的优点就是,当我们不想调试时只需要把程序第一行的#define _DEBUG_
注释掉即可,而不需要跑到程序里,把所有debug
函数删掉。
3.但是串口初始化的函数我并没有使用宏定义,那是因为不少功能都需要串口的初始化,如果我们都写进宏定义里面,显然会造成重复定义。我们只需要把串口初始化的实现放到源文件中,然后在头文件中声明该函数,这样我们想要初始化串口只需要在主函数中调用该函数即可。
4.调用printf
函数之前需要先把TI
置一,否则程序就会卡在printf
函数中,这个和getchar
函数的实现有关,具体原因在第三部分说明。
5.头文件的宏定义中,作者使用的是do{...}while(0U)
的格式,这样做是代码规范考虑,因为我们在主程序中需要调用debug_d8(x);
语句,使用上述格式可以在不破坏每一条小语句的情况下,最后不会多出来一个分号。(不用这种格式,现在的编译器也会自动优化代码,不会报错)
高系列的51单片机,具有多个串口,像我们上面那样实现,只能用串口1进行输出,如果想要用其他串口输出变量,我们就需要对printf
函数进行重定向。
从keil的帮助文档里我们可以看到,printf
是基于putchar
实现的,所以我们只要重新实现putchar
,就可以实现printf
的重定向,即可以将printf
用在其他串口上。
```c
#include <reg51.h>
#define XON 0x11
#define XOFF 0x13
/*
* putchar (full version): expands '\n' into CR LF and handles
* XON/XOFF (Ctrl+S/Ctrl+Q) protocol
*/
char putchar (char c) {
if (c == '\n') {
if (RI) {
if (SBUF == XOFF) {
do {
RI = 0;
while (!RI);
}
while (SBUF != XON);
RI = 0;
}
}
while (!TI);
TI = 0;
SBUF = 0x0d; /* output CR */
}
if (RI) {
if (SBUF == XOFF) {
do {
RI = 0;
while (!RI);
}
while (SBUF != XON);
RI = 0;
}
}
while (!TI);
TI = 0;
return (SBUF = c);
}
我们来分析一下代码,首先我们先不管那两个if
判断,putchar
函数总是要执行的语句其实只有三句,首先先等待上一个数据发送完毕,将标志位置零以后,再发送下一个数据。这也解释了为什么我们在第一次调用printf
函数时要先把TI
置1,因为STC单片机复位以后TI
的值为0,直接调用printf
函数就会一直卡死在while(!TI)
里面了。
while(!TI);
TI = 0;
return (SBUF = c);
if(c == '\n')
部分是判断是否接收到换行符,如果接收到换行符以后,就会输出CR+LF。SBUF = 0X0d
是在输出CR,推测LF是在putchar
函数之外输出的。
最后if(RI)
部分是软件流控制。当接收端数据缓存区满了以后,就会向发送端发送XOFF
标志,发送端接收到XOFF
以后停止发送数据。接收端处理完数据以后,会向发送端发送XON
标志,表示可以继续发送数据。
使用流控制可以有效的防止数据传输过程中的丢失情况。
分析完官方的putchar
函数以后,我们需要自己写一个putchar
函数来调用串口3,这里要说明一下,如果我们的工程中包含了putchar
函数,编译器会优先使用我们所定义的函数,而不会去使用…/C51/LIB下的putchar
函数。
下面是我写的putchar
函数,因为没有那么高的要求,所以我并没有使用流控制,其次我的代码是先发送数据,再检测标志位,我认为这样做更符合我们平时的使用习惯。
char putchar(char c)
{
if (c == '\n')
{
S3BUF = 0x0d;
while (!(S3CON & S3TI)); //等待发送成功
S3CON &= ~S3TI;
/* output CR */
}
S3BUF = c;
while (!(S3CON & S3TI)); //等待发送成功
S3CON &= ~S3TI; //清除发送中断标志
return c;
}
我们只需要将这个putchar
函数包含在工程中,然后初始化串口3,便可以利用printf
打印串口了。如果想使用上面的debug
函数,还需要对头文件进行更改。