如果不想看的可以直接使用git把我的代碼下載出來,里面工程挺全的,后期會慢慢的補注釋之類的
碼云地址:stm32學習筆記: stm32學習筆記源碼
如果不會使用git快速下載可以選擇直接下載壓縮包或者去看看git的使用
git的使用(下載及上傳_gitcode怎么下載文件_是小劉不是劉的博客-CSDN博客
版權聲明:本文為CSDN博主「是小劉不是劉」的原創(chuàng)文章,遵循CC 4.0 BY-SA版權協(xié)議,轉(zhuǎn)載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/qq_51426845/article/details/130949055
因為現(xiàn)在很多設備都要的是多字節(jié)發(fā)送,和接收,所以我們需要學習如何去接收一些設備返回的多字節(jié)參數(shù)然后去對他解析。
1、通過串口收發(fā)HEX格式數(shù)據(jù)包
2、通過串口收發(fā)字符格式數(shù)據(jù)包并且點燈
3、既能接收HEX又能接收字符格式數(shù)據(jù)包
目錄
一 、數(shù)據(jù)包理論部分
1、hex數(shù)據(jù)包
?2 文本數(shù)據(jù)包
?3 hex的數(shù)據(jù)接收
?4、接收文本數(shù)據(jù)包
?二、代碼部分
1、數(shù)據(jù)包的發(fā)送
2 數(shù)據(jù)包的接收
1 中斷方式接收HEX數(shù)據(jù)包
?2 文本數(shù)據(jù)包
3 既實現(xiàn)接收字符串又能實現(xiàn)接收HEX
一 、數(shù)據(jù)包理論部分
1、hex數(shù)據(jù)包
數(shù)據(jù)包一般會包含包頭和包尾,但是在一些設備的協(xié)議中,只有包頭,包的結(jié)束由一個時間段內(nèi)沒有收到數(shù)據(jù)為結(jié)束信號。
在傳輸中怎么防止數(shù)據(jù)和包頭包尾重復呢,這里兩個方法就是:一個是讓其固定包長,一個是加長幀頭,讓數(shù)據(jù)和包頭包尾的重復率降低。
?2 文本數(shù)據(jù)包
這里也加入包長包尾,但是文本解析率比較低,因為一個字符他就是八位。
?3 hex的數(shù)據(jù)接收
這里可以寫一個狀態(tài)機,用這個狀態(tài)機來判斷是否接收到了幀頭楨尾,以及是否接收數(shù)據(jù)完成。
首先判斷接收到的數(shù)據(jù)是否位0xFF,如果不是就讓s一直為0繼續(xù)等待數(shù)據(jù)
若收到FF后將其置為1,后面檢測其是否收夠四個數(shù)據(jù)收夠為2(這里是固定包長的想法
之后等待包尾,若收到包尾,就將s為0,重新接收包頭,否則循環(huán)接收包尾
?4、接收文本數(shù)據(jù)包
這里就是可變包長的想法了
首先還是當s=0時,等待包頭,若包頭一直不為@則一直等待,為@后將s置為1,接收數(shù)據(jù),這時候就等待包尾如果收到了\r就將s置為2,到下一個接收包尾的環(huán)節(jié)(因為這里是有兩個包尾的,然后程序一次又只能判斷一個字節(jié),如果只有一個包尾,那就接收到包尾之后直接將s=0,就不需要最后的一步等待包尾了。
?二、代碼部分
1、數(shù)據(jù)包的發(fā)送
上面沒寫數(shù)據(jù)包的發(fā)送,因為發(fā)送沒什么限制,就是把接收到的數(shù)據(jù),或者你自己寫入的數(shù)據(jù)直接發(fā)送就好,不需要那么多判斷幀頭尾的判斷。
這里也就不接收傳感器數(shù)據(jù)再轉(zhuǎn)發(fā)了,這里我們先直接寫個發(fā)送,后面寫完接收再寫接收之后返回。往TXBUF里面寫入四個16進制,要帶上0x前綴哦不然代碼會被默認為10進制,10進制是沒有abcdef這些字母的,會直接報錯。
//usart.c
u8 serial_TxPack[4]={0};
void Send_Pack(void)
{
Send_Byte(0xFF);
Send_Array(serial_RxPack,4);
Send_Byte(0xFE);
}
//main.c
int main(void)
{
Usart_Config();
serial_RxPack[0]=0xf1;
serial_RxPack[1]=0x02;
serial_RxPack[2]=0x03;
serial_RxPack[3]=0x04;
Send_Pack();
while(1)
{
}
}
這樣我們直接發(fā)送就能從串口測試有沒有發(fā)送成功了
2 數(shù)據(jù)包的接收
1、中斷方式接收HEX數(shù)據(jù)包
邏輯就是首先判斷接收到的數(shù)據(jù)是不是包頭,然后接收數(shù)據(jù)之后,判斷數(shù)據(jù)個數(shù),之后判斷包尾,和上面的流程圖是一樣的。
首先寫一個串口中斷,這個在前面的單字節(jié)接收里面已經(jīng)寫過了,stm32f103系列USART串口收發(fā)(單字節(jié)_八月風賊冷的博客-CSDN博客
如果還不會配置串口中斷的可以去看一下,然后就是寫一個狀態(tài)機了,這注釋寫的還是挺詳細的,按照上面寫的那個狀態(tài)轉(zhuǎn)移表的邏輯來寫的代碼,則例用了兩個靜態(tài)變量
1、基本概念
靜態(tài)存儲方式:指在程序運行時,給變量分配固定的存儲空間的方式
2、 靜態(tài)存儲區(qū)存放以下變量:
全局變量:在程序開始執(zhí)行時給全局變量分配存儲區(qū),程序運行完畢之后釋放。在程序運行過程中它們占據(jù)固定的存儲單元而不動態(tài)進行分配和釋放。
靜態(tài)變量:有時希望變量的值在函數(shù)調(diào)用結(jié)束后不消失而保留原值,這時就應該指定變量為“靜態(tài)變量”,用關鍵字static進行命名
有時候,我們希望函數(shù)中局部變量的值在函數(shù)調(diào)用結(jié)束之后不會消失,而仍然保留其原值。即它所占用的存儲單元不釋放,在下一次調(diào)用該函數(shù)時,其局部變量的值仍然存在,也就是上一次函數(shù)調(diào)用結(jié)束時的值。這時候,我們就應該將該局部變量用關鍵字 static 聲明為“靜態(tài)局部變量“。
因為我們這里需要每一次都進串口并且讓其他文件不能調(diào)用,所以我們這里可以使用靜態(tài)變量。
uint8_t RxData; //數(shù)據(jù)轉(zhuǎn)存
u8 serial_RxPack[4]={0}; //接收數(shù)據(jù)的數(shù)組
u8 Serial_RXFlag=0; //接收完成標志位
void USART1_IRQHandler(void)
{
static u8 RxState=0;
static u8 pRxPacket=0;
if(USART_GetFlagStatus(USART1,USART_FLAG_RXNE)==SET)
{
//接收串口發(fā)來的數(shù)據(jù)
RxData=USART_ReceiveData(USART1);
//判斷數(shù)據(jù)是否為包頭
if(RxState==0)
{
if(RxData==0xFF)
{
RxState=1;
}
else
{
}
}
else if(RxState==1)
{
//接收到了包頭,可以開始接收數(shù)據(jù)之后進行組包
serial_RxPack[pRxPacket]=RxData;
pRxPacket++;
//接收到數(shù)據(jù)為4個將狀態(tài)轉(zhuǎn)移為2
if(pRxPacket==4)
{
RxState=2;
pRxPacket=0;
}
}
else if(RxState==2)
{
//判斷是否接收到包尾
if(RxData==0xFE)
{
RxState=0;
Serial_RXFlag=1;
}
else
{
}
}
}
}
?之后我們寫一個判斷是否接收完成的標志位,通過這個標志位我們可以調(diào)用這個標志位來判斷是否完成,這個標志位的定義在上面那段代碼。
u8 Serial_GetRxFlag(void)
{
if(Serial_RXFlag == 1)
{
Serial_RXFlag=0;
return 1;
}
return 0;
}
之后就能寫主函數(shù)了,判斷接收完成標志位是否為1,因為前面狀態(tài)機寫的如果接收到了結(jié)束幀就會將其寫為1。然后為1我們就將接收到的數(shù)據(jù)發(fā)送出去。并且將標志位重新寫為0。
int main(void)
{
Usart_Config();
while(1)
{
//判斷是否接收完成
if(Serial_RXFlag==1)
{
//將接收完成標志位置0
Serial_GetRxFlag();
//將接收的數(shù)據(jù)發(fā)送給串口顯示出來
Send_Array(serial_RxPack,4);
}
}
}
運行效果,前面幾次發(fā)送了11 22 33 44 后面為了測試數(shù)據(jù)不會和包頭沖突發(fā)送了ff 22 33 44
?2 文本數(shù)據(jù)包
文本數(shù)據(jù)包和hex的寫法基本差不多但是也有些要注意的點
首先我們還是寫一個串口中斷的狀態(tài)機
這里我們就還是以上面的狀態(tài)轉(zhuǎn)移圖來寫代碼了,首先起始幀為@我們判斷是否為@,然后給數(shù)組賦值接著判斷包尾,這里要注意要給字符串一個’0‘因為c語言中字符串要有一個\0的結(jié)束位,不然后面我們使用這個字符串的時候就無法判斷是否結(jié)束,中斷函數(shù)如下。
uint8_t RxData;
void USART1_IRQHandler(void)
{
static u8 RxState=0;
static u8 pRxPacket=0;
if(USART_GetFlagStatus(USART1,USART_FLAG_RXNE)==SET)
{
//接收串口發(fā)來的數(shù)據(jù)
RxData=USART_ReceiveData(USART1);
//判斷數(shù)據(jù)是否為包頭
if(RxState==0)
{
if(RxData=='@')
{
RxState=1;
}
else{
}
}
else if(RxState==1)
{
//判斷數(shù)據(jù)是不是包尾,不是的話就接收數(shù)據(jù)
if(RxData=='\r')
{
RxState=2;
}
else
{
serial_RxPack[pRxPacket]=RxData;
pRxPacket++;
}
}
else if(RxState==2)
{
//判斷數(shù)據(jù)是不是'\n'
if(RxData=='\n')
{
//狀態(tài)轉(zhuǎn)移為0并且將
RxState=0;
serial_RxPack[pRxPacket]='\0'; //給字符串一個結(jié)束符
Serial_RXFlag=1;
pRxPacket=0;
}
else{
}
}
}
}
之后我們在主函數(shù)調(diào)用,這樣就能打印剛剛接收到的字符串了
while(1)
{
//判斷是否接收完成
if(Serial_RXFlag==1)
{
//將接收完成標志位置0
Serial_GetRxFlag();
//將接收的數(shù)據(jù)發(fā)送給串口顯示出來
Send_String(serial_RxPack);
}
}
打印結(jié)果如下,這里發(fā)了三次所以打印的多了幾個
?成功發(fā)送,之后我們測試發(fā)送命令控制板子上的LED燈的開關
這里我們先打開自己板子燈的GPIO這些操作,這個我說在PWM那一節(jié)的制作呼吸燈寫過了可以去參考一下stm32f103配置PWM及實踐_stm32pwm配置詳解_是小劉不是劉的博客-CSDN博客
然后就是燈,這個可以自己直接配置和,調(diào)用set和rest兩個點燈,也可以直接去調(diào)用有些公司寫好的庫,這里我們就自己寫一下把
這里我還是使用了PB0的藍色燈,可以根據(jù)自己的板子選擇。
首先初始化我們的GPIOB和引腳0,記得先自己去創(chuàng)建一個led的板級支持包哦別都寫在串口的支持包里面去了?
//led.c
void LED_Config(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
//配置GPIO
GPIO_InitStruct.GPIO_Mode=GPIO_Mode_Out_PP;
GPIO_InitStruct.GPIO_Pin=GPIO_Pin_1;
GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOB,&GPIO_InitStruct);
}
開啟GPIO之后我做一個led的封裝這樣等會好調(diào)用,這樣我們就能在主函數(shù)很好的實現(xiàn)開燈了哈
//led.h
#define ON 0
#define OFF 1
#define LEDG(a) if (a) \
GPIO_SetBits(GPIOB,GPIO_Pin_1);\
else \
GPIO_ResetBits(GPIOB,GPIO_Pin_1)
然后寫完在主函數(shù)測試一下能不能點亮,別等下寫串口點燈寫了半天發(fā)現(xiàn)燈本來就點不亮= =。
之后我們開始寫接收字符串點燈,這里我們使用一個字符串比較函數(shù)strcmp,這個是c語言函數(shù),不了解的可以百度一下。
我們還是在串口接收字符的基礎上寫,這里就寫一些判斷就好,很簡單的邏輯,使用這個函數(shù)記得調(diào)用string的庫,這個之前寫串口單字節(jié)的時候也用過這個庫來計算平方。
int main(void)
{
Usart_Config();
LED_Config();
printf("串口打印測試");
while(1)
{
//判斷是否接收完成
if(Serial_RXFlag==1)
{
//將接收完成標志位置0
Serial_GetRxFlag();
//實現(xiàn)字符串比較
if(strcmp(serial_RxPack,"LED_ON")==0)
{
LEDG(ON);
printf("LED_ON\r\n");
}
else if(strcmp(serial_RxPack,"LED_OFF")==0)
{
LEDG(OFF);
printf("LED_OFF\r\n");
}
else
{
printf("command erro\r\n");
}
}
}
}
然后附上兩張硬件效果圖
開燈
關燈
?
具體實現(xiàn)就這樣了
3 既實現(xiàn)接收字符串又能實現(xiàn)接收HEX
思路還是寫一個狀態(tài)機,但是狀態(tài)會多一點
首先在狀態(tài)0時同時判斷是@或者FF,若接收到得為@則進入狀態(tài)1 這里接收字符,若字符為\r則進入狀態(tài)3等待\n若為\n則返回狀態(tài)0表示字符串接收完成
若接收到得為FF則跳到狀態(tài)2接收HEX,但是現(xiàn)在是不定長16進制,所以數(shù)據(jù)為和包尾標志一點不可以重復,這時接收到FE則代表數(shù)據(jù)接收結(jié)束,回到狀態(tài)0.
代碼部分,兩個數(shù)組分別存儲字符串和HEX數(shù)據(jù)包
//main.c
int main(void)
{
Usart_Config();
LED_Config();
printf("串口打印測試");
while(1)
{
//判斷是否接收完成
if(Serial_RXFlag==1)
{
//將接收完成標志位置0
Serial_GetRxFlag();
Send_Array(modubus_RxPack,3);
Send_String(serial_RxPack);
}
}
}
//usart.c
char serial_RxPack[64]={0};
u8 modubus_RxPack[64]={0};
u8 Serial_RXFlag=0;
uint8_t RxData;
void USART1_IRQHandler(void)
{
static u8 RxState=0;
static u8 pRxPacket=0;
if(USART_GetFlagStatus(USART1,USART_FLAG_RXNE)==SET)
{
//接收串口發(fā)來的數(shù)據(jù)
RxData=USART_ReceiveData(USART1);
//判斷數(shù)據(jù)是否為包頭
if(RxState==0)
{
if(RxData=='@')
{
RxState=1; //狀態(tài)1接收字符
}
else if(RxData==0xFF)
{
RxState=2; //狀態(tài)2接收到HEX
}
}
else if(RxState==1)
{
//判斷數(shù)據(jù)是不是包尾,不是的話就接收數(shù)據(jù)
if(RxData=='\r')
{
RxState=3;
}
else
{
serial_RxPack[pRxPacket]=RxData;
pRxPacket++;
}
}
else if(RxState==2)
{
if(RxData==0xFE)
{
Serial_RXFlag=1;
RxState=0; //重新接收包
pRxPacket=0;
}
else
{
modubus_RxPack[pRxPacket]=RxData;
pRxPacket++;
}
}
else if(RxState==3)
{
//判斷數(shù)據(jù)是不是'\n'
if(RxData=='\n')
{
//狀態(tài)轉(zhuǎn)移為0并且將
RxState=0;
serial_RxPack[pRxPacket]='\0'; //給字符串一個結(jié)束符
Serial_RXFlag=1;
pRxPacket=0;
}
else{
}
}
}
}
?主函數(shù)還是只顯示了3位數(shù)組,但是沒關系的- -主要是為了回顯,到時候處理數(shù)據(jù)的時候單片機直接接收到了之后處理就是了開不開回顯都沒關系,切換兩個發(fā)送接收模式,測試是沒有問題的。文章來源:http://www.zghlxwxcb.cn/news/detail-741628.html
文章來源地址http://www.zghlxwxcb.cn/news/detail-741628.html
到了這里,關于stm32 串口多字節(jié)接收的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網(wǎng)!