实现printf打印到串口

电子说

1.2w人已加入

描述

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

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

  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,整数)。

虚拟终端显示结果如下。 

串口

 

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

 

二、 VSPD和串口助手的使用

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

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

 

 

串口

 

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

 

 

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

 

串口

 

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,如下图所示。

   串口

 

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

 

串口

 

 

演示视频:

 

 

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

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

假设命令是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{   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;           }   }  }
}

 

 

 

 

 

 

  审核编辑:汤梓红
 

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

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分