在mcu上開發應用時,使用串口打印調試信息是最常用的調試手段之一。printf是c標准庫提供的函數,可以方便輸出格式化的信息。但針對不同的mcu芯片,printf函數要能正常工作,需要做一些移植和適配工作。本文以at89c51為例,講解printf的適配。
1. printf的原理
printf是一個可變參數函數,它根據用戶提供的格式化字符串、可變參數,構造出一個最終要輸出的字符串,然后調用stdio庫的putchar函數打印輸出信息到相應的設備。putchar針對不同的平台、不同的應用場景有不同的實現。例如在pc上,putchar打印輸出到屏幕。
2. mcu上串口通信的配置
2.1 使用keil提供的putchar.c
#include "reg51.h"
#include "stdio.h"
// 已at89c51為例,波特率的設置參照文章結尾附表
void uart_init(void)
{
// 串口工作在10bit模式
// 采用可變波特率,波特率為定時器T1溢出率
// SM0 SM1 SM2 REN TB8 RB8 TI RI
// SM0 SM1:
// 00--同步移位方式
// 01--10bit異步收發,可變波特率,T1溢出率決定
// 10--11bit異步收發,固定波特率
// 11--11bit異步收發,波特率可變,T1溢出率決定
SCON = 0X50;
// 設置波特率為9600bps,假設外部晶振為11.0592MHz
// 1.T1工作在模式2,8bit自動充裝模式
// 2.T1初始值為0xfd
// GATE C/#T M1 M0 GATE C/#T M1 M0
// M1M0:
// 00--13bit計數模式,
// 01--16bit計數模式,
// 10--8bit自動重裝
TMOD = (TMOD & 0X0F) | (1 << 5);
TH1 = TL1 = 0XFD;
TR1 = 1; // 啟動定時器
ES = 1;
EA = 1;
}
// 開啟了串口中斷,需要編寫串口中斷服務程序,否則程序跑飛
// 如果沒有開啟串口中斷,可以不用下面的中斷服務函數
void uart_isr(void) interrupt 4
{
if(RI) // 中斷是接收數據觸發
{
// 處理數據
}
if(TI) // 中斷是發送數據觸發
{
}
}
void main(void)
{
uart_init();
while(1)
{
printf("hello,uart\r\n");
}
}
編譯上述代碼,運行,發現單片機串口沒有輸出數據。
進入mdk安裝目錄,進入c51/lib目錄下,發現有一個文件為putchar.c,打開putchar.c,定義如下:
/***********************************************************************/
/* This file is part of the C51 Compiler package */
/* Copyright KEIL ELEKTRONIK GmbH 1990 - 2002 */
/***********************************************************************/
/* */
/* PUTCHAR.C: This routine is the general character output of C51. */
/* You may add this file to a uVision2 project. */
/* */
/* To translate this file use C51 with the following invocation: */
/* C51 PUTCHAR.C <memory model> */
/* */
/* To link the modified PUTCHAR.OBJ file to your application use the */
/* following Lx51 invocation: */
/* Lx51 <your object file list>, PUTCHAR.OBJ <controls> */
/* */
/***********************************************************************/
#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 0 // comment out versions below
/*
* putchar (basic version): expands '\n' into CR LF
*/
char putchar (char c) {
if (c == '\n') {
while (!TI);
TI = 0;
SBUF = 0x0d; /* output CR */
}
while (!TI);
TI = 0;
return (SBUF = c);
}
/*
* putchar (mini version): outputs charcter only
*/
char putchar (char c) {
while (!TI);
TI = 0;
return (SBUF = c);
}
#endif
分析代碼后發現,程序運行到while(!TI)停止在該句,因為初始化后TI默認為0,而且還沒有發送過數據,TI一直為0,因此程序不會繼續向下執行。
解決方法:修改uart_init函數,添加TI = 1啟動發送。
void uart_init(void)
{
// 串口工作在10bit模式
// 采用可變波特率,波特率為定時器T1溢出率
SCON = 0X50;
// 設置波特率為9600bps,假設外部晶振為11.0592MHz
// 1.T1工作在模式2,8bit自動充裝模式
// 2.T1初始值為0xfd
TMOD = (TMOD & 0X0F) | (1 << 5);
TH1 = TL1 = 0XFD;
TR1 = 1; // 啟動定時器
ES = 1;
EA = 1;
TI = 1; //#!!! 必須加這句,以啟動發送,否則無法使用printf輸出
}
2.2 用戶自定義putchar函數
keil提供的putchar,帶流控功能,同時,必須在初始化uart時候,保證調用了TI = 1以啟動發送,否則printf無法打印輸出。當然,用戶可以自定義putchar函數,簡單的實現如下:
char putchar(char c)
{
SBUFF = c;
while(!TI);
TI = 0;
return c;
}
附:51單片機常用波特率初指表(晶振11.0592MHz情形)

