MCU软件最佳实践——使用printf打印数据

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

  1. printf的原理

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

  1. 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                                     */
/*                                                                     */
/*  To link the modified PUTCHAR.OBJ file to your application use the  */
/*  following Lx51 invocation:                                         */
/*     Lx51 , PUTCHAR.OBJ             */
/*                                                                     */
/***********************************************************************/
#include
#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情形)

MCU软件最佳实践——使用printf打印数据

Original: https://www.cnblogs.com/arminker/p/14695130.html
Author: 流云的博客
Title: MCU软件最佳实践——使用printf打印数据

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/573754/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • Oracle用户、角色、权限

    一、Oracle权限 &#x7CFB;&#x7EDF;&#x6743;&#x9650;&#xFF1A;&#x7CFB;&#x…

    Java 2023年6月8日
    0100
  • 2、oracle安装出现的问题

    1、问题:scott用户在安装时未解锁的问题 方案: 2、适配器错误 方案:确保勾选的服务是启动状态 3、 posted @2022-09-23 17:09 红酒人生 阅读(46 …

    Java 2023年6月13日
    078
  • Mac系统下Datagrip打不开、点击没反应?

    有没有可能是因为你从网上下载了一些破解软件导致的? Mac系统下JB公司家的IDEA、 Datagrip、PyCharm 或 Goland 打不开点击没反应…&#823…

    Java 2023年6月14日
    089
  • 敏捷培训有感

    一周前参加了个关于敏捷的培训,今天回想起来,记忆最深的是两个游戏环节。 游戏一 组装 10 只同样小狗,每只小狗需要 5 块积木,流水线上 5 个人,每人负责固定的一块积木的拼接。…

    Java 2023年6月16日
    079
  • 我们小公司使用了6年的项目部署方案,打包 + 一键部署详解,还挺方便

    时间就如白驹过隙,转眼间已经是 2028 年了。小二入职一家初创公司已经 6 年了,眼瞅着开发团队从 3 个人壮大到 54 人,心里有时候会感觉挺不可思议的。 这些年,身边的同事来…

    Java 2023年6月9日
    0124
  • 单例模式–还没从工厂中逃脱出来?看来是注定单身了..

    前言 上次我们聊了聊一个略微重量级的工厂模式,不知道你是否消化完从工厂中逃脱出来了呢?不是我说,今天的单例模式,恰恰好相反了,孤孤单单,看来是注定单身了.. 先来看看单例模式在jd…

    Java 2023年6月5日
    093
  • ReentrantLock可重入、可打断、Condition原理剖析

    本文紧接上文的AQS源码,如果对于ReentrantLock没有基础可以先阅读我的上一篇文章学习ReentrantLock的源码 重入加锁其实就是将AQS的state进行加一操作 …

    Java 2023年6月16日
    064
  • java.util.function 中的 Function、Predicate、Consumer

    函数式接口: 函数式接口(Functional Interface)就是一个有且仅有一个抽象方法,但可以有多个非抽象方法的接口。 函数式接口可以被隐式转换为 Lambda 表达式。…

    Java 2023年5月29日
    081
  • HBase二级索引

    Phoenix 二级索引 可以将全表扫描优化为范围扫描 全局索引 将全表扫描转化为范围扫描。额外创建一个表,作为索引表。当创建了全局索引,先去利用算法找所要查询的列, 默认的索引格…

    Java 2023年6月8日
    091
  • 分布式系统中数据存储方案实践

    数据膨胀的时候,必然放大细节。 一、背景简介 在项目研发的过程中,对于数据存储能力的依赖无处不在,项目初期,相比系统层面的组件选型与框架设计,由于数据体量不大,在存储管理方面通常容…

    Java 2023年6月15日
    079
  • netty搭建rpc框架

    netty想必大家都不陌生,我就不废话介绍了…(主要是懒,网上资料很多的) 本文主要使用netty搭建rpc远程调用框架,实现了个注册中心微服务,整合了springbo…

    Java 2023年6月7日
    095
  • Mysql之DML操作(SELECT,INSERT,UPDATE,DELETE)

    Mysql之DML操作(SELECT,INSERT,UPDATE,DELETE) 什么是DML 数据操纵语言(Data Manipulation Language, DML)是用于…

    Java 2023年6月8日
    099
  • Markdown随时记录

    Markdown学习 推荐文本编译器 Typora 标题(支持六级) 一级标题:# + 空格 + 内容 二级标题:## + 空格 + 内容 三级标题:### + 空格 + 内容 ….

    Java 2023年6月14日
    087
  • Spring Boot MongoDB

    Linux下启动MongoDB并使用mongosh连接 启动方式有两种: systemctl start mongod mongod 启动的时候有可能会报类似如下的错误: Exec…

    Java 2023年6月7日
    087
  • 快速排序法

    介绍: 快速排序(Quicksort)是对 冒泡排序的一种改进。基本思想是:通过一趟排序将要排序的数据分割成独立的两 部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后…

    Java 2023年6月15日
    0116
  • 14.将对象的字符串写入文件并保存到盘符

    public JsonVo savaCloudScript(ScriptModelVO scriptModelVo) throws RollbackableBizException…

    Java 2023年6月13日
    089
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球