嗨,又見(jiàn)面了。九月的秋風(fēng)吹過(guò),微微涼意。
閑話少敘,回歸正題。今天我們繼續(xù)玩串口,主題包括:
-
實(shí)現(xiàn)printf打印到串口。
-
VSPD和串口助手的使用。
-
識(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ù)送到串口,我們需要重寫(xiě)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é)果如下。
關(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ú)功能限制。
-
下載并安裝VSPD軟件,然后添加一個(gè)虛擬串口對(duì):COM1-COM2。安裝過(guò)程略,配置過(guò)程如下:
-
修改仿真電路圖,刪除虛擬終端,添加COMPIM。修改后的仿真電路圖如下。注意,連接單片機(jī)和COMPIM時(shí),RXD連接RXD,TXD連接TXD,相當(dāng)于把51單片機(jī)的串口綁定到COMPIM。
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,如下圖所示。
8. 單擊串口助手的打開(kāi)串口按鈕。在接收緩沖區(qū)可以收到51單片機(jī)串口發(fā)送的數(shù)據(jù)。
演示視頻:
三、識(shí)別上位機(jī)下發(fā)的固定多字節(jié)命令。
來(lái)而不往非禮也。下面我們實(shí)現(xiàn)單片機(jī)接收并執(zhí)行上位機(jī)串口下發(fā)的命令。
假設(shè)命令是2個(gè)字節(jié)的,第一個(gè)字節(jié)表示命令類(lèi)型,第二個(gè)字節(jié)表示命令參數(shù),如下表所示。
MSB |
LSB |
命令類(lèi)型(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é)獲得命令類(lèi)型,再執(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
void 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); //重寫(xiě)printf的重定向putchar函數(shù)
uart.c源碼
//串口初始化函數(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è)字符
}
}
/**************************
重寫(xiě)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"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;
}
}
}
}
-
單片機(jī)
+關(guān)注
關(guān)注
6058文章
44822瀏覽量
644798 -
串口
+關(guān)注
關(guān)注
14文章
1580瀏覽量
78347 -
Printf
+關(guān)注
關(guān)注
0文章
83瀏覽量
14013
原文標(biāo)題:C51編程入門(mén)(二十二)串口編程入門(mén)--串口應(yīng)用協(xié)議(一)
文章出處:【微信號(hào):輕松學(xué)單片機(jī),微信公眾號(hào):輕松學(xué)單片機(jī)】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
如何用printf打印到終端?
單片機(jī)是如何實(shí)現(xiàn)printf打印到串口的
什么是串口通信?基于STM32的printf打印輸出

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

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

STM32中使用printf打印串口數(shù)據(jù)的實(shí)現(xiàn)原理及方法
單片機(jī)實(shí)現(xiàn) printf 打印輸出,和電腦端一樣用

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

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

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

評(píng)論