MCU软件最佳实践——使用printf打印数据
阅读原文时间:2023年07月09日阅读:2

在mcu上开发应用时,使用串口打印调试信息是最常用的调试手段之一。printf是c标准库提供的函数,可以方便输出格式化的信息。但针对不同的mcu芯片,printf函数要能正常工作,需要做一些移植和适配工作。本文以at89c51为例,讲解printf的适配。

1. printf的原理

printf是一个可变参数函数,它根据用户提供的格式化字符串、可变参数,构造出一个最终要输出的字符串,然后调用stdio库的putchar函数打印输出信息到相应的设备。putchar针对不同的平台、不同的应用场景有不同的实现。例如在pc上,putchar打印输出到屏幕。

2. mcu上串口通信的配置

#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输出
}

keil提供的putchar,带流控功能,同时,必须在初始化uart时候,保证调用了TI = 1以启动发送,否则printf无法打印输出。当然,用户可以自定义putchar函数,简单的实现如下:

char putchar(char c)
{
    SBUFF = c;
    while(!TI);
    TI = 0;
    return c;
}

附:51单片机常用波特率初指表(晶振11.0592MHz情形)

手机扫一扫

移动阅读更方便

阿里云服务器
腾讯云服务器
七牛云服务器

你可能感兴趣的文章