0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

實(shí)現(xiàn)printf打印到串口

我快閉嘴 ? 來(lái)源:輕松學(xué)單片機(jī) ? 作者:輕松學(xué)單片機(jī) ? 2022-09-05 14:43 ? 次閱讀

嗨,又見(jiàn)面了。九月的秋風(fēng)吹過(guò),微微涼意。

閑話少敘,回歸正題。今天我們繼續(xù)玩串口,主題包括:

  1. 實(shí)現(xiàn)printf打印到串口。

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

  3. 識(shí)別上位機(jī)下發(fā)的固定多字節(jié)命令。


一、 實(shí)現(xiàn)printf打印到串口

C語(yǔ)言程序設(shè)計(jì)課程,同學(xué)們肯定用過(guò)printf在控制臺(tái)打印過(guò)“Hello world!”。

printf("Hello world!");

這次,我們用printf在串口打印“Hello world”。我們會(huì)付出2K程序空間的代價(jià)。但,我樂(lè)意!對(duì),我樂(lè)意。

還記得嗎, printf函數(shù)在stdio.h頭文件中定義。那我們先添加頭文件到主程序代碼。

#include "stdio.h"

為了實(shí)現(xiàn)printf重定位到串口,即把數(shù)據(jù)送到串口,我們需要重寫putchar函數(shù)。putchar定義如下:

char putchar(char c){    //初始重新定向到串口    uart_sendUchar(c);    //返回字符到調(diào)用者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函數(shù),我們首先初始化串口(uart_init),然后打印hello world, 接著是溫度(temperature,浮點(diǎn)數(shù),但只打印三位小數(shù))和計(jì)數(shù)器值(count,整數(shù))。

虛擬終端顯示結(jié)果如下。

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

關(guān)于printf函數(shù)的使用,參考:c stdio.h printf Programming | Library | Reference - Code-Reference.com

二、 VSPD和串口助手的使用

VSPD是虛擬串口軟件,用于在同一臺(tái)PC調(diào)試串口程序。串口通信雙方的程序都運(yùn)行在同一臺(tái)PC上。

VSPD官網(wǎng)提供14天試用版本,無(wú)功能限制。

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

  1. 下載并安裝VSPD軟件,然后添加一個(gè)虛擬串口對(duì):COM1-COM2。安裝過(guò)程略,配置過(guò)程如下:

  1. 修改仿真電路圖,刪除虛擬終端,添加COMPIM。修改后的仿真電路圖如下。注意,連接單片機(jī)和COMPIM時(shí),RXD連接RXD,TXD連接TXD,相當(dāng)于把51單片機(jī)的串口綁定到COMPIM。

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

3. 配置COMPIM,使用虛擬串口對(duì)中的一個(gè)端口,這里選擇了COM1。

4. 修改主函數(shù)代碼,重新編譯。代碼如下:

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. 在仿真電路中雙擊單片機(jī),選擇重新編譯好的程序。

6. 運(yùn)行仿真。

7. 運(yùn)行STC-ISP燒錄軟件,切換到串口助手,設(shè)置串口,選擇COM2(虛擬串口對(duì)的另一個(gè)),波特率9600,如下圖所示。

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

8. 單擊串口助手的打開(kāi)串口按鈕。在接收緩沖區(qū)可以收到51單片機(jī)串口發(fā)送的數(shù)據(jù)。

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

演示視頻

三、識(shí)別上位機(jī)下發(fā)的固定多字節(jié)命令。

來(lái)而不往非禮也。下面我們實(shí)現(xiàn)單片機(jī)接收并執(zhí)行上位機(jī)串口下發(fā)的命令。

假設(shè)命令是2個(gè)字節(jié)的,第一個(gè)字節(jié)表示命令類型,第二個(gè)字節(jié)表示命令參數(shù),如下表所示。

MSB

LSB

命令類型(1字節(jié))

命令參數(shù)(1字節(jié))

命令常用于控制單片機(jī)外設(shè)或設(shè)置單片機(jī)功能。在本例中,我們計(jì)劃通過(guò)串口助手下發(fā)命令控制蜂鳴器和清零count。

具體命令定義如下:

01 00H:關(guān)閉蜂鳴器

01 01H:?jiǎn)?dòng)蜂鳴器

02 00H:清零count值

F0 0FH:不玩了,關(guān)閉串口

如何實(shí)現(xiàn)兩個(gè)字節(jié)命令的接收和解釋執(zhí)行呢?

思路:我們知道上位機(jī)只會(huì)發(fā)兩個(gè)字節(jié)的命令數(shù)據(jù)到單片機(jī),因此可以對(duì)串口接收字節(jié)進(jìn)行計(jì)數(shù)。

當(dāng)連續(xù)收到兩個(gè)字節(jié)時(shí),表示收到命令。然后判斷第一個(gè)字節(jié)獲得命令類型,再執(zhí)行相應(yīng)動(dòng)作即可。

unsigned char uart_rx_buffer[2]; //全局變量,串口接收緩存//串口中斷函數(shù)void isr_uart() interrupt 4{  static unsigned char rx_byte_count = 0;  if(RI) //收到數(shù)據(jù)  {     uart_rx_buffer[rx_byte_count] = SBUF;     rx_byte_count++;     RI = 0;     if(rx_byte_count == 2) //接收到2個(gè)字節(jié)數(shù)據(jù)     {        rx_byte_count = 0;        //下面是命令解析及執(zhí)行~魔幻的if else        if(uart_rx_buffer[0] == 0x01 && uart_rx_buffer[1] == 0x00)        {            beeper_en = 1; //beeper off                printf("執(zhí)行命令:關(guān)閉蜂鳴器!
");        }        else if    (uart_rx_buffer[0] == 0x01 && uart_rx_buffer[1] == 0x01)        {          beeper_en = 0; //beeper on              printf("執(zhí)行命令:開(kāi)啟蜂鳴器!
");            }        else if    (uart_rx_buffer[0] == 0x02 && uart_rx_buffer[1] == 0x00)        {            count = 0;            printf("執(zhí)行命令:清零count!
");        }            else if    (uart_rx_buffer[0] == 0xF0 && uart_rx_buffer[1] == 0x0F)        {            printf("將關(guān)閉串口,再見(jiàn)!
");            TR1 = 0;        }     }  }}

編譯并仿真,單片機(jī)能夠正確接收并執(zhí)行命令,如下。

哈哈,執(zhí)行完F0 0FH命令,要想再次使用串口,只能重啟仿真了。

結(jié)束語(yǔ)

今天的內(nèi)容有點(diǎn)多,結(jié)束語(yǔ)就短點(diǎn)吧。

附上本次串口源碼,如下。如果你覺(jué)得有所幫助,請(qǐng)點(diǎn)贊,請(qǐng)打賞。

如果需要仿真電路和串口工程源碼,請(qǐng)?jiān)诤笈_(tái)留言。

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();      //串口初始化函數(shù)void uart_sendByte(uchar c);  //發(fā)送單字節(jié)函數(shù)void uart_sendChar(char c);  //發(fā)送char數(shù)據(jù)函數(shù)void uart_sendUchar(uchar c); //發(fā)送unsigned char數(shù)據(jù)函數(shù)void uart_sendUint(uint num);  //發(fā)送unsigned int 數(shù)據(jù)函數(shù)void uart_sendInt(int num);   //發(fā)送int數(shù)據(jù)函數(shù)void uart_sendFloat(float num); //發(fā)送float數(shù)據(jù)函數(shù)void uart_sendLong(long num); //發(fā)送long數(shù)據(jù)函數(shù)void uart_sendDouble(double num); //發(fā)送double數(shù)據(jù)void uart_sendString(uchar* pStr); //發(fā)送字符串函數(shù)char putchar(char c); //重寫printf的重定向putchar函數(shù)#endif

uart.c源碼

#include "uart.h"#include "reg51.h"
//串口初始化函數(shù)void uart_init(){  //串口初始化:工作方式1(10-bit), 9600bps @11.0592MHz  SCON = 0x50;  //TX and RX  EA = 1;  ES = 1;  TMOD = TMOD|0x20; //定時(shí)器T1 8位自動(dòng)重載  TL1 = 0xFD;   //初值@9600bps  TH1 = 0xFD;  TR1 = 1;     //啟動(dòng)定時(shí)器T1  RI = 0;  TI = 0; //清零 }
//發(fā)送char數(shù)據(jù)函數(shù)  void uart_sendChar(char c){  uchar *p;  p = &c;  uart_sendUchar(*p);}//發(fā)送unsigned char數(shù)據(jù)函數(shù) void uart_sendUchar(uchar c){   ES = 0; //關(guān)串口中斷   SBUF = c;    while(TI==0); //等待發(fā)送完成   TI = 0;     //清零發(fā)送標(biāo)志   ES = 1; //恢復(fù)串口中斷    } //發(fā)送unsigned int 數(shù)據(jù)函數(shù)//先傳輸MSB字節(jié)void uart_sendUint(uint num){  uchar *p;    p = #    uart_sendUchar(*p);  p++;  uart_sendUchar(*p);}  //發(fā)送int數(shù)據(jù)函數(shù)void uart_sendInt(int num){  uchar *p;    p = # //指向MSB字節(jié)    uart_sendUchar(*p);  p++; //指向下一個(gè)字節(jié)  uart_sendUchar(*p);}//發(fā)送float數(shù)據(jù)函數(shù), MSB Byte first  void uart_sendFloat(float num){  uchar *p;  uchar i;    p = #  for(i=0; i<4;i++)  {     uart_sendUchar(*(p++));      }    } //發(fā)送字符串函數(shù), 字符串以''結(jié)尾void uart_sendString(uchar* pStr){    while(*pStr != '')  {     uart_sendUchar(*pStr);     pStr++; //指向下一個(gè)字符  } }
/**************************重寫stdio.h中的putchar函數(shù),實(shí)現(xiàn)調(diào)用printf函數(shù)打印字符串到串口必須包含stdio.h頭文件**************************/char putchar(char c){  //初始重新定向到串口   uart_sendUchar(c);  //返回字符到調(diào)用者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;//函數(shù)定義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按下??!");    }  }}

//串口中斷函數(shù)void isr_uart() interrupt 4{ staticunsignedcharrx_byte_count=0;//接收字節(jié)計(jì)數(shù)  if(RI) //收到數(shù)據(jù)  {uart_rx_buffer[rx_byte_count]=SBUF;//存儲(chǔ)收到的數(shù)據(jù)   rx_byte_count++;   RI = 0;   if(rx_byte_count == 2) //已完成2個(gè)字節(jié)數(shù)據(jù)的接收   {    rx_byte_count = 0;    //下面是命令解析及執(zhí)行~魔幻的if else    if(uart_rx_buffer[0] == 0x01 && uart_rx_buffer[1] == 0x00)    {      beeper_en = 1; //beeper off        printf("執(zhí)行命令:關(guān)閉蜂鳴器!");    }    else if  (uart_rx_buffer[0] == 0x01 && uart_rx_buffer[1] == 0x01)    {      beeper_en = 0; //beeper on        printf("執(zhí)行命令:開(kāi)啟蜂鳴器!");      }    else if  (uart_rx_buffer[0] == 0x02 && uart_rx_buffer[1] == 0x00)    {      count = 0;       printf("執(zhí)行命令:清零count!");    }      else if  (uart_rx_buffer[0] == 0xF0 && uart_rx_buffer[1] == 0x0F)    {      printf("將關(guān)閉串口,再見(jiàn)!");      TR1 = 0;           }   }  }
}

審核編輯:湯梓紅

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問(wèn)題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • 單片機(jī)
    +關(guān)注

    關(guān)注

    6026

    文章

    44452

    瀏覽量

    630797
  • 串口
    +關(guān)注

    關(guān)注

    14

    文章

    1535

    瀏覽量

    75876
  • Printf
    +關(guān)注

    關(guān)注

    0

    文章

    81

    瀏覽量

    13588

原文標(biāo)題:C51編程入門(二十二)串口編程入門--串口應(yīng)用協(xié)議(一)

文章出處:【微信號(hào):輕松學(xué)單片機(jī),微信公眾號(hào):輕松學(xué)單片機(jī)】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。

收藏 人收藏

    評(píng)論

    相關(guān)推薦

    如何用printf打印到終端?

    從示例程序uARTHARTX01開(kāi)始,我成功地將其輸出打印到TelaTm窗口。我用FTIDUSB到TTL串行電纜。但是我想用PrimTF將浮點(diǎn)數(shù)字打印到終端。項(xiàng)目構(gòu)建好:使用PrtTf(“測(cè)試
    發(fā)表于 09-30 06:54

    求一種用printf打印到串口的解決方案

    如何用printf函數(shù)打印到串口呢?有哪幾種方案呢?
    發(fā)表于 11-30 07:30

    如何用printf打印到串口?

    如何用printf打印到串口?
    發(fā)表于 12-01 06:39

    如何使printf打印到單片機(jī)的外設(shè)中?

    如何使printf打印到單片機(jī)的外設(shè)中?
    發(fā)表于 12-01 07:43

    單片機(jī)是如何實(shí)現(xiàn)printf打印到串口

    軟件顯示區(qū)了! 和電腦端一樣用!串口初始化代碼部分,以STM32為例,其他單片機(jī)也一樣,只是修改成對(duì)應(yīng)的單片機(jī)寄存器即可,整個(gè)邏輯是一樣的若只是實(shí)現(xiàn)printf打印到
    發(fā)表于 02-16 07:10

    怎樣使用printf函數(shù)將字符串打印到串口

    怎樣使用printf函數(shù)將字符串打印到串口呢?怎樣去重新定向printf函數(shù)呢?
    發(fā)表于 02-24 06:50

    STM8S串口打印調(diào)試信息(不使用printf)

    STM8S串口打印調(diào)試信息(不使用printf),感興趣可以看看。
    發(fā)表于 07-25 18:52 ?51次下載

    什么是串口通信?基于STM32的printf打印輸出

    平時(shí)我們進(jìn)行c語(yǔ)言編程的時(shí)候會(huì)經(jīng)常用到printf函數(shù)進(jìn)行打印輸出,來(lái)調(diào)試代碼??墒沁@個(gè)printf函數(shù)C庫(kù)已經(jīng)幫我們實(shí)現(xiàn)好了,通常只需要直接調(diào)用即可,但是如果在一個(gè)新的開(kāi)發(fā)平臺(tái),如果
    發(fā)表于 06-22 09:08 ?1.4w次閱讀
    什么是<b class='flag-5'>串口</b>通信?基于STM32的<b class='flag-5'>printf</b><b class='flag-5'>打印</b>輸出

    Keil C51重定向printf串口的程序免費(fèi)下載

    進(jìn)行C/C++開(kāi)發(fā)的時(shí)候我們都會(huì)需要打印調(diào)試信息,打印調(diào)試信息時(shí)我們習(xí)慣使用printf函數(shù),但是在Keil C51環(huán)境下,由于我們的程序是下載到單片機(jī)里,使用printf函數(shù)時(shí)不能直
    發(fā)表于 07-19 17:38 ?14次下載
    Keil C51重定向<b class='flag-5'>printf</b>到<b class='flag-5'>串口</b>的程序免費(fèi)下載

    使用MicroLIB+fputc的方式實(shí)現(xiàn)串口打印功能

    實(shí)現(xiàn)fputc函數(shù)的原因是:printf函數(shù)依賴于fputc函數(shù),重新實(shí)現(xiàn)fputc內(nèi)部從串口發(fā)送數(shù)據(jù)即可間接地實(shí)現(xiàn)
    的頭像 發(fā)表于 08-05 10:52 ?4647次閱讀
    使用MicroLIB+fputc的方式<b class='flag-5'>實(shí)現(xiàn)</b><b class='flag-5'>串口</b><b class='flag-5'>打印</b>功能

    STM32中使用printf打印串口數(shù)據(jù)的實(shí)現(xiàn)原理及方法

    STM32中使用printf打印串口數(shù)據(jù)的實(shí)現(xiàn)原理 在C庫(kù)中,printf()等輸出流函數(shù)都是通過(guò)fputc()這個(gè)函數(shù)
    的頭像 發(fā)表于 07-22 11:12 ?1.5w次閱讀

    單片機(jī)實(shí)現(xiàn) printf 打印輸出,和電腦端一樣用

    軟件顯示區(qū)了! 和電腦端一樣用!串口初始化代碼部分,以STM32為例,其他單片機(jī)也一樣,只是修改成對(duì)應(yīng)的單片機(jī)寄存器即可,整個(gè)邏輯是一樣的若只是實(shí)現(xiàn)printf打印到
    發(fā)表于 12-17 18:32 ?1次下載
    單片機(jī)<b class='flag-5'>實(shí)現(xiàn)</b> <b class='flag-5'>printf</b> <b class='flag-5'>打印</b>輸出,和電腦端一樣用

    STM32串行通訊時(shí)打印到多個(gè)USART串口

    在我們使用STM32串行通訊時(shí),可能用到多個(gè)USART串口,而此時(shí)printf只能向一個(gè)串口打印,見(jiàn)上篇,此時(shí)我們需要按照如下編輯個(gè)新的函數(shù),實(shí)現(xiàn)
    發(fā)表于 12-29 19:31 ?11次下載
    STM32串行通訊時(shí)<b class='flag-5'>打印到</b>多個(gè)USART<b class='flag-5'>串口</b>

    如何使用printf函數(shù)將字符串打印到串口

    如何使用printf函數(shù)將字符串打印到串口該函數(shù)名已經(jīng)在標(biāo)準(zhǔn)輸入輸出庫(kù)頭文 件 stdio.h 中定義,原型為 int fputc(int ch, FILE *f)。在usart.c中添加
    發(fā)表于 01-12 18:53 ?1次下載
    如何使用<b class='flag-5'>printf</b>函數(shù)將字符串<b class='flag-5'>打印到</b><b class='flag-5'>串口</b>

    stm32使用printf實(shí)現(xiàn)串口打印原理

    ??標(biāo)準(zhǔn)庫(kù)函數(shù)的默認(rèn)輸出設(shè)備是顯示器, 要實(shí)現(xiàn)串口或 LCD 輸出,必須重定義標(biāo)準(zhǔn)庫(kù)函數(shù)里調(diào)用的與輸出設(shè)備相關(guān)的函數(shù) .例如 :printf 輸出到串口,需要將 fputc 里面的輸
    發(fā)表于 01-13 14:55 ?5次下載
    stm32使用<b class='flag-5'>printf</b><b class='flag-5'>實(shí)現(xiàn)</b><b class='flag-5'>串口</b><b class='flag-5'>打印</b>原理