在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情形)

MCU软件最佳实践——使用printf打印数据的更多相关文章

  1. MCU软件最佳实践——独立按键

    1. 引子 在进行mcu驱动和应用开发时,经常会遇到独立按键驱动的开发,独立按键似乎是每一个嵌入式工程师的入门必修课.笔者翻阅了许多书籍(包括上大学时候用的书籍)同时查阅了网上许多网友的博客,无一例外 ...

  2. MCU软件最佳实践——矩阵键盘驱动

    1.矩阵键盘vs独立按键 在mcu应用开发过程中,独立按键比较常见,但是在需要的按键数比较多时,使用矩阵键盘则可以减少io占用,提高系统资源利用率.例如,某mcu项目要求有16个按钮,如果采用独立按键 ...

  3. 大规模使用 Apache Kafka 的20个最佳实践

    必读 | 大规模使用 Apache Kafka 的20个最佳实践 配图来源:书籍<深入理解Kafka> Apache Kafka是一款流行的分布式数据流平台,它已经广泛地被诸如New Re ...

  4. Kafka在大型应用中的 20 项最佳实践

    原标题:Kafka如何做到1秒处理1500万条消息? Apache Kafka 是一款流行的分布式数据流平台,它已经广泛地被诸如 New Relic(数据智能平台).Uber.Square(移动支付公 ...

  5. Salesforce 开发整理(五)代码开发最佳实践

    在Salesforce项目实施过程中,对项目代码的维护可以说占据极大的精力,无论是因为项目的迭代,还是需求的变更,甚至是项目组成员的变动,都不可避免的需要维护之前的老代码,而事实上,几乎没有任何一个项 ...

  6. .Net最佳实践3:使用性能计数器收集性能数据

    本文值得阅读吗? 本文讨论我们如何使用性能计数器从应用程序收集数据.我们将先了解的基本知识,然后我们将看到一个简单的示例,我们将从中收集一些性能数据. 介绍: - 我的应用程序的性能是最好的,像火箭 ...

  7. Atitit.列表页面and条件查询的实现最佳实践(1)------设置查询条件and提交查询and返回json数据

    Atitit.列表页面and条件查询的实现最佳实践(1)------设置查询条件and提交查询and返回json数据 1. 1. 配置条件字段@Conditional 1 1 2. 2. 配置条件字段 ...

  8. 基于开源软件在Azure平台建立大规模系统的最佳实践

    作者 王枫 发布于2014年5月28日 前言 Microsoft Azure 是微软公有云的唯一解决方案.借助这一平台,用户可以以多种方式部署和发布自己的应用. 这是一个开放的平台,除了对于Windo ...

  9. Atitit.列表页and查询条件的最佳实践(1)------设定搜索条件and提交查询and返回json数据

    Atitit.列表页and查询条件的最佳实践(1)------设置查询条件and提交查询and返回json数据 1. 1. 配置条件字段@Conditional 1 1 2. 2. 配置条件字段显示类 ...

随机推荐

  1. LuoguP6861 [RC-03] 难题 题解

    Update \(\texttt{2020.10.21}\) 删除了不需要的 \(n=1\) 的特判,并在符号与字母之间添加了空格. Content 给定一个数 \(n\),试找到一对数 \(a,b( ...

  2. Asp.Net Core基础篇之:白话管道中间件

    在Asp.Net Core中,管道往往伴随着请求一起出现.客户端发起Http请求,服务端去响应这个请求,之间的过程都在管道内进行. 举一个生活中比较常见的例子:旅游景区. 我们都知道,有些景区大门离景 ...

  3. java 理论基础 类的初始化(加载、连接(验证、准备、解析)、初始化)

    一个进程就有一个JVM,每个进程之间资源独立 当调用java命令来启动某个Java程序的时候,该命令创建一个独立的进程来运行我们的Java程序.而这个独立的进程里面就包含一个Java虚拟机.不管该程序 ...

  4. ssh框架从页面传中文发生乱码时怎么解决,就是添加一个字符编码拦截器。用springframework自带的便可

    ssh框架从页面传中文发生乱码时怎么解决,就是添加一个字符编码拦截器.用springframework自带的便可

  5. c++ 设计模式概述之享元

    类写的不够规范,目的是为了缩短篇幅,实际中其不要这样做. 参考文章: 1. http://c.biancheng.net/view/1371.html 1.概述 A.享元,我的理解是: 共享的模块单元 ...

  6. 【九度OJ】题目1474:矩阵幂 解题报告

    [九度OJ]题目1474:矩阵幂 解题报告 标签(空格分隔): 九度OJ http://ac.jobdu.com/problem.php?pid=1474 题目描述: 给定一个n*n的矩阵,求该矩阵的 ...

  7. 【LeetCode】338. Counting Bits 解题报告(Python & Java & C++)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 日期 题目描述 Given a non negati ...

  8. 【LeetCode】343. Integer Break 解题报告(Python & C++)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 数学解法 动态规划 日期 题目地址:https:// ...

  9. 51Nod 1264:线段相交(计算几何)

    51Nod 1264:线段相交 Decision 给出平面上两条线段的两个端点,判断这两条线段是否相交(有一个公共点或有部分重合认为相交). 如果相交,输出"Yes",否则输出&q ...

  10. MySQL 尽量避免使用 TIMESTAMP

    MySQL 中常见的时间类型有三种DATE, DATETIME和 TIMESTAMP,其中DATE类型用于表示日期,但是不会包含时间,格式为YYYY-MM-DD,而DATETIME和TIMESTAMP ...