0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

实现printf打印到串口

我快闭嘴 来源:轻松学单片机 作者:轻松学单片机 2022-09-05 14:43 次阅读

嗨,又见面了。九月的秋风吹过,微微凉意。

闲话少叙,回归正题。今天我们继续玩串口,主题包括:

  1. 实现printf打印到串口。

  2. VSPD和串口助手的使用。

  3. 识别上位机下发的固定多字节命令。


一、 实现printf打印到串口

C语言程序设计课程,同学们肯定用过printf在控制台打印过“Hello world!”。

printf("Hello world!");

这次,我们用printf在串口打印“Hello world”。我们会付出2K程序空间的代价。但,我乐意!对,我乐意。

还记得吗, printf函数在stdio.h头文件中定义。那我们先添加头文件到主程序代码

#include "stdio.h"

为了实现printf重定位到串口,即把数据送到串口,我们需要重写putchar函数。putchar定义如下:

char putchar(char c){    //初始重新定向到串口    uart_sendUchar(c);    //返回字符到调用者printf    return c;}

试一试吧。Hello world代码如下:

#include "uart.h"

void main(){    float temperature = 21.0/13;    unsigned int count = 123;      uart_init();    printf("Hello world!
");    printf("Temperature: %.3f 
 count: %d.
", temperature, count);    printf("printf demo. 2022-9-1 Guilin,China.");    while(1);

}

在main函数,我们首先初始化串口(uart_init),然后打印hello world, 接着是温度(temperature,浮点数,但只打印三位小数)和计数器值(count,整数)。

虚拟终端显示结果如下。

b2daf04c-2ac6-11ed-ba43-dac502259ad0.png

关于printf函数的使用,参考:c stdio.h printf Programming | Library | Reference - Code-Reference.com

二、 VSPD和串口助手的使用

VSPD是虚拟串口软件,用于在同一台PC调试串口程序。串口通信双方的程序都运行在同一台PC上。

VSPD官网提供14天试用版本,无功能限制。

b3030834-2ac6-11ed-ba43-dac502259ad0.jpg

  1. 下载并安装VSPD软件,然后添加一个虚拟串口对:COM1-COM2。安装过程略,配置过程如下:

  1. 修改仿真电路图,删除虚拟终端,添加COMPIM。修改后的仿真电路图如下。注意,连接单片机和COMPIM时,RXD连接RXD,TXD连接TXD,相当于把51单片机的串口绑定到COMPIM。

b31fbd6c-2ac6-11ed-ba43-dac502259ad0.png

3. 配置COMPIM,使用虚拟串口对中的一个端口,这里选择了COM1。

4. 修改主函数代码,重新编译。代码如下:

void main(){    float temperature = 21.0/13;    unsigned int count = 123;      uart_init();    while(1)    {        printf("Hello world!
");        printf("Temperature: %.3f 
 count: %d.
", temperature, count);        printf("printf demo. 2022-9-1 Guilin,China.");        delayMS(1000);        count++;    }}

5. 在仿真电路中双击单片机,选择重新编译好的程序。

6. 运行仿真。

7. 运行STC-ISP烧录软件,切换到串口助手,设置串口,选择COM2(虚拟串口对的另一个),波特率9600,如下图所示。

b335592e-2ac6-11ed-ba43-dac502259ad0.png

8. 单击串口助手的打开串口按钮。在接收缓冲区可以收到51单片机串口发送的数据。

b37b512c-2ac6-11ed-ba43-dac502259ad0.png

演示视频

三、识别上位机下发的固定多字节命令。

来而不往非礼也。下面我们实现单片机接收并执行上位机串口下发的命令。

假设命令是2个字节的,第一个字节表示命令类型,第二个字节表示命令参数,如下表所示。

MSB

LSB

命令类型(1字节)

命令参数(1字节)

命令常用于控制单片机外设或设置单片机功能。在本例中,我们计划通过串口助手下发命令控制蜂鸣器和清零count。

具体命令定义如下:

01 00H:关闭蜂鸣器

01 01H:启动蜂鸣器

02 00H:清零count值

F0 0FH:不玩了,关闭串口

如何实现两个字节命令的接收和解释执行呢?

思路:我们知道上位机只会发两个字节的命令数据到单片机,因此可以对串口接收字节进行计数。

当连续收到两个字节时,表示收到命令。然后判断第一个字节获得命令类型,再执行相应动作即可。

unsigned char uart_rx_buffer[2]; //全局变量,串口接收缓存//串口中断函数void isr_uart() interrupt 4{  static unsigned char rx_byte_count = 0;  if(RI) //收到数据  {     uart_rx_buffer[rx_byte_count] = SBUF;     rx_byte_count++;     RI = 0;     if(rx_byte_count == 2) //接收到2个字节数据     {        rx_byte_count = 0;        //下面是命令解析及执行~魔幻的if else        if(uart_rx_buffer[0] == 0x01 && uart_rx_buffer[1] == 0x00)        {            beeper_en = 1; //beeper off                printf("执行命令:关闭蜂鸣器!
");        }        else if    (uart_rx_buffer[0] == 0x01 && uart_rx_buffer[1] == 0x01)        {          beeper_en = 0; //beeper on              printf("执行命令:开启蜂鸣器!
");            }        else if    (uart_rx_buffer[0] == 0x02 && uart_rx_buffer[1] == 0x00)        {            count = 0;            printf("执行命令:清零count!
");        }            else if    (uart_rx_buffer[0] == 0xF0 && uart_rx_buffer[1] == 0x0F)        {            printf("将关闭串口,再见!
");            TR1 = 0;        }     }  }}

编译并仿真,单片机能够正确接收并执行命令,如下。

哈哈,执行完F0 0FH命令,要想再次使用串口,只能重启仿真了。

结束语

今天的内容有点多,结束语就短点吧。

附上本次串口源码,如下。如果你觉得有所帮助,请点赞,请打赏。

如果需要仿真电路和串口工程源码,请在后台留言。

uart.h头文件

//uart.h#include "reg51.h"#include "stdio.h"
#ifndef _UART_H#define _UART_H#define uchar unsigned char#define uint unsigned intvoid uart_init();      //串口初始化函数void uart_sendByte(uchar c);  //发送单字节函数void uart_sendChar(char c);  //发送char数据函数void uart_sendUchar(uchar c); //发送unsigned char数据函数void uart_sendUint(uint num);  //发送unsigned int 数据函数void uart_sendInt(int num);   //发送int数据函数void uart_sendFloat(float num); //发送float数据函数void uart_sendLong(long num); //发送long数据函数void uart_sendDouble(double num); //发送double数据void uart_sendString(uchar* pStr); //发送字符串函数char putchar(char c); //重写printf的重定向putchar函数#endif

uart.c源码

#include "uart.h"#include "reg51.h"
//串口初始化函数void uart_init(){  //串口初始化:工作方式1(10-bit), 9600bps @11.0592MHz  SCON = 0x50;  //TX and RX  EA = 1;  ES = 1;  TMOD = TMOD|0x20; //定时器T1 8位自动重载  TL1 = 0xFD;   //初值@9600bps  TH1 = 0xFD;  TR1 = 1;     //启动定时器T1  RI = 0;  TI = 0; //清零 }
//发送char数据函数  void uart_sendChar(char c){  uchar *p;  p = &c;  uart_sendUchar(*p);}//发送unsigned char数据函数 void uart_sendUchar(uchar c){   ES = 0; //关串口中断   SBUF = c;    while(TI==0); //等待发送完成   TI = 0;     //清零发送标志   ES = 1; //恢复串口中断    } //发送unsigned int 数据函数//先传输MSB字节void uart_sendUint(uint num){  uchar *p;    p = #    uart_sendUchar(*p);  p++;  uart_sendUchar(*p);}  //发送int数据函数void uart_sendInt(int num){  uchar *p;    p = # //指向MSB字节    uart_sendUchar(*p);  p++; //指向下一个字节  uart_sendUchar(*p);}//发送float数据函数, MSB Byte first  void uart_sendFloat(float num){  uchar *p;  uchar i;    p = #  for(i=0; i<4;i++)  {     uart_sendUchar(*(p++));      }    } //发送字符串函数, 字符串以''结尾void uart_sendString(uchar* pStr){    while(*pStr != '')  {     uart_sendUchar(*pStr);     pStr++; //指向下一个字符  } }
/**************************重写stdio.h中的putchar函数,实现调用printf函数打印字符串到串口必须包含stdio.h头文件**************************/char putchar(char c){  //初始重新定向到串口   uart_sendUchar(c);  //返回字符到调用者printf  return c;}




主程序源码

//uart_firstdemo.c
#include "uart.h"//#include"reg51.h"
sbit beeper_en = P2^0;sbit key_s1 = P1^0;char msg[] = "Welcome back.
";unsigned char uart_rx_buffer[2]; unsigned int count = 0;//函数定义void delayMS(unsigned int nms);void keyScan();
void delayMS(unsigned int nms){   unsigned int i,j;  for(i=0;i    for(j=0;j<130;j++);}
void main(){  float temperature = 21.0/13;     uart_init();
    while(1)  {    keyScan(); //按键扫描    printf("Hello world!
");      printf("Temperature: %.3f 
 count: %d.
", temperature, count);      printf("printf demo. 2022-9-1 Guilin,China.
");    delayMS(1000);    count++;      }}
void keyScan(){   if(key_s1 == 0)  {     delayMS(10); //消抖    if(key_s1 == 0)    {             printf("S1按下!!");    }  }}

//串口中断函数void isr_uart() interrupt 4{ staticunsignedcharrx_byte_count=0;//接收字节计数  if(RI) //收到数据  {uart_rx_buffer[rx_byte_count]=SBUF;//存储收到的数据   rx_byte_count++;   RI = 0;   if(rx_byte_count == 2) //已完成2个字节数据的接收   {    rx_byte_count = 0;    //下面是命令解析及执行~魔幻的if else    if(uart_rx_buffer[0] == 0x01 && uart_rx_buffer[1] == 0x00)    {      beeper_en = 1; //beeper off        printf("执行命令:关闭蜂鸣器!");    }    else if  (uart_rx_buffer[0] == 0x01 && uart_rx_buffer[1] == 0x01)    {      beeper_en = 0; //beeper on        printf("执行命令:开启蜂鸣器!");      }    else if  (uart_rx_buffer[0] == 0x02 && uart_rx_buffer[1] == 0x00)    {      count = 0;       printf("执行命令:清零count!");    }      else if  (uart_rx_buffer[0] == 0xF0 && uart_rx_buffer[1] == 0x0F)    {      printf("将关闭串口,再见!");      TR1 = 0;           }   }  }
}

审核编辑:汤梓红

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 单片机
    +关注

    关注

    6002

    文章

    43981

    浏览量

    620891
  • 串口
    +关注

    关注

    14

    文章

    1485

    浏览量

    74525
  • Printf
    +关注

    关注

    0

    文章

    79

    浏览量

    13481

原文标题:C51编程入门(二十二)串口编程入门--串口应用协议(一)

文章出处:【微信号:轻松学单片机,微信公众号:轻松学单片机】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    如何用printf打印到终端?

    从示例程序uARTHARTX01开始,我成功地将其输出打印到TelaTm窗口。我用FTIDUSB到TTL串行电缆。但是我想用PrimTF将浮点数字打印到终端。项目构建好:使用PrtTf(“测试
    发表于 09-30 06:54

    求一种用printf打印到串口的解决方案

    如何用printf函数打印到串口呢?有哪几种方案呢?
    发表于 11-30 07:30

    如何用printf打印到串口

    如何用printf打印到串口
    发表于 12-01 06:39

    如何使printf打印到单片机的外设中?

    如何使printf打印到单片机的外设中?
    发表于 12-01 07:43

    怎样将printf打印到单片机的外设中去呢

    所谓重定向是指什么?怎样将printf打印到单片机的外设中去呢?
    发表于 12-02 06:40

    怎样去实现RFID-RC522的ID读取并打印到串口

    怎样去实现RFID-RC522的ID读取并打印到串口呢?有哪些使用步骤呢?
    发表于 12-14 06:23

    单片机是如何实现printf打印到串口

    软件显示区了! 和电脑端一样用!串口初始化代码部分,以STM32为例,其他单片机也一样,只是修改成对应的单片机寄存器即可,整个逻辑是一样的若只是实现printf打印到
    发表于 02-16 07:10

    怎样使用printf函数将字符串打印到串口

    怎样使用printf函数将字符串打印到串口呢?怎样去重新定向printf函数呢?
    发表于 02-24 06:50

    STM8S串口打印调试信息(不使用printf)

    STM8S串口打印调试信息(不使用printf),感兴趣可以看看。
    发表于 07-25 18:52 51次下载

    Keil C51重定向printf串口的程序免费下载

    进行C/C++开发的时候我们都会需要打印调试信息,打印调试信息时我们习惯使用printf函数,但是在Keil C51环境下,由于我们的程序是下载到单片机里,使用printf函数时不能直
    发表于 07-19 17:38 11次下载
    Keil C51重定向<b class='flag-5'>printf</b>到<b class='flag-5'>串口</b>的程序免费下载

    STM32中使用printf打印串口数据的实现原理及方法

    STM32中使用printf打印串口数据的实现原理 在C库中,printf()等输出流函数都是通过fputc()这个函数
    的头像 发表于 07-22 11:12 1.4w次阅读

    单片机实现 printf 打印输出,和电脑端一样用

    软件显示区了! 和电脑端一样用!串口初始化代码部分,以STM32为例,其他单片机也一样,只是修改成对应的单片机寄存器即可,整个逻辑是一样的若只是实现printf打印到
    发表于 12-17 18:32 1次下载
    单片机<b class='flag-5'>实现</b> <b class='flag-5'>printf</b> <b class='flag-5'>打印</b>输出,和电脑端一样用

    STM32串行通讯时打印到多个USART串口

    在我们使用STM32串行通讯时,可能用到多个USART串口,而此时printf只能向一个串口打印,见上篇,此时我们需要按照如下编辑个新的函数,实现
    发表于 12-29 19:31 11次下载
    STM32串行通讯时<b class='flag-5'>打印到</b>多个USART<b class='flag-5'>串口</b>

    如何使用printf函数将字符串打印到串口

    如何使用printf函数将字符串打印到串口该函数名已经在标准输入输出库头文 件 stdio.h 中定义,原型为 int fputc(int ch, FILE *f)。在usart.c中添加
    发表于 01-12 18:53 1次下载
    如何使用<b class='flag-5'>printf</b>函数将字符串<b class='flag-5'>打印到</b><b class='flag-5'>串口</b>

    stm32使用printf实现串口打印原理

      标准库函数的默认输出设备是显示器, 要实现串口或 LCD 输出,必须重定义标准库函数里调用的与输出设备相关的函数 .例如 :printf 输出到串口,需要将 fputc 里面的输
    发表于 01-13 14:55 5次下载
    stm32使用<b class='flag-5'>printf</b><b class='flag-5'>实现</b><b class='flag-5'>串口</b><b class='flag-5'>打印</b>原理