嗨,又見面了。九月的秋風吹過,微微涼意。
閑話少敘,回歸正題。今天我們繼續玩串口,主題包括:
-
實現printf打印到串口。
-
VSPD和串口助手的使用。
-
識別上位機下發的固定多字節命令。
一、 實現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);//返回字符到調用者printfreturn 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天試用版本,無功能限制。

-
下載并安裝VSPD軟件,然后添加一個虛擬串口對:COM1-COM2。安裝過程略,配置過程如下:

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 elseif(uart_rx_buffer[0] == 0x01 && uart_rx_buffer[1] == 0x00){beeper_en = 1; //beeper offprintf("執行命令:關閉蜂鳴器! ");}else if (uart_rx_buffer[0] == 0x01 && uart_rx_buffer[1] == 0x01){beeper_en = 0; //beeper onprintf("執行命令:開啟蜂鳴器! ");}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.hvoid 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函數
uart.c源碼
//串口初始化函數void uart_init(){//串口初始化:工作方式1(10-bit), 9600bps @11.0592MHzSCON = 0x50; //TX and RXEA = 1;ES = 1;TMOD = TMOD|0x20; //定時器T1 8位自動重載TL1 = 0xFD; //初值@9600bpsTH1 = 0xFD;TR1 = 1; //啟動定時器T1RI = 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 firstvoid 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);//返回字符到調用者printfreturn c;}
主程序源碼
//uart_firstdemo.c//#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;ifor(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 elseif(uart_rx_buffer[0] == 0x01 && uart_rx_buffer[1] == 0x00){beeper_en = 1; //beeper offprintf("執行命令:關閉蜂鳴器!");}else if (uart_rx_buffer[0] == 0x01 && uart_rx_buffer[1] == 0x01){beeper_en = 0; //beeper onprintf("執行命令:開啟蜂鳴器!");}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;}}}}
-
單片機
+關注
關注
6076文章
45495瀏覽量
670313 -
串口
+關注
關注
15文章
1619瀏覽量
82815 -
Printf
+關注
關注
0文章
84瀏覽量
14734
原文標題:C51編程入門(二十二)串口編程入門--串口應用協議(一)
文章出處:【微信號:輕松學單片機,微信公眾號:輕松學單片機】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
如何用printf打印到終端?
單片機是如何實現printf打印到串口的
什么是串口通信?基于STM32的printf打印輸出
Keil C51重定向printf到串口的程序免費下載
STM32中使用printf打印串口數據的實現原理及方法
單片機實現 printf 打印輸出,和電腦端一樣用
STM32串行通訊時打印到多個USART串口
如何使用printf函數將字符串打印到串口
stm32使用printf實現串口打印原理
實現printf打印到串口
評論