??在單片機(jī)開發(fā)中,串口是最常用的和外界交換數(shù)據(jù)的渠道,要使用串口,那必不可少的就是通信協(xié)議,通信協(xié)議就是單片機(jī)和外界通信的語(yǔ)言,要想正常和其他設(shè)備正常交流,首先語(yǔ)言必須相通。
??在實(shí)際開發(fā)過程中由于各種原因,導(dǎo)致很多時(shí)候單片機(jī)和外界其他設(shè)備協(xié)議不兼容,在使用的時(shí)候就比較麻煩。比如單片機(jī)要和兩個(gè)設(shè)備通信,但是這兩個(gè)設(shè)備的通信協(xié)議的不一樣,在使用時(shí)單片機(jī)就必須使用兩個(gè)串口分別和兩個(gè)設(shè)備通信。如果這兩個(gè)設(shè)備同時(shí)使用時(shí)還不感覺到資源浪費(fèi),如果每次只接一個(gè)設(shè)備,那么另一個(gè)串口也不能作為其他功能使用,還得留著備用。這樣的話單片機(jī)的資源就被白白浪費(fèi)掉了。于是想著能不能在一個(gè)串口上支持兩個(gè)協(xié)議,讓單片機(jī)自動(dòng)去識(shí)別接收到的數(shù)據(jù)使用的是哪個(gè)協(xié)議。
??一般使用通信協(xié)議接收數(shù)據(jù)時(shí),都需要通過判斷數(shù)據(jù)頭和數(shù)據(jù)尾來確定什么時(shí)候開始接收數(shù)據(jù),什么時(shí)候停止接收數(shù)據(jù)。但是如果要兼容多個(gè)協(xié)議的話,就不能使用這個(gè)方式去接收數(shù)據(jù)。必須先將一組數(shù)據(jù)接收完畢,然后根據(jù)數(shù)據(jù)的特點(diǎn)去分析數(shù)據(jù)使用的是哪個(gè)協(xié)議。
??首先要實(shí)現(xiàn)的就是如何判斷一組數(shù)據(jù)是否接收完畢。
??實(shí)現(xiàn)的大概思路就是,單片機(jī)使用中斷去接收數(shù)據(jù),同時(shí)記錄接收到的數(shù)據(jù)長(zhǎng)度,在主函數(shù)中循環(huán)的去讀取串口接收到的字符長(zhǎng)度,如果超過一定時(shí)間之后,串口中接收到的數(shù)據(jù)長(zhǎng)度沒有發(fā)生變化,就說明一組數(shù)據(jù)接收完畢了。
??比如在主函數(shù)中讀取到了當(dāng)前串口數(shù)據(jù)長(zhǎng)度不為0,說明此時(shí)串口正在接收數(shù)據(jù),此時(shí)記錄下當(dāng)前串口數(shù)據(jù)長(zhǎng)度,延時(shí)一段時(shí)間再去讀取一次串口接收數(shù)據(jù)的長(zhǎng)度,如果此時(shí)數(shù)據(jù)長(zhǎng)度和上一次數(shù)據(jù)長(zhǎng)度一樣,說明串口接收數(shù)據(jù)結(jié)束了,就可以去處理接收到的數(shù)據(jù)了,如果此時(shí)數(shù)據(jù)長(zhǎng)度和上一次的數(shù)據(jù)長(zhǎng)度不一樣,說明串口正在接收數(shù)據(jù),接收數(shù)據(jù)還未結(jié)束,不能去處理數(shù)據(jù)。
??下面就通過一個(gè)工程案例來演示一個(gè)串口兼容兩種協(xié)議的使用方法。
??設(shè)備默認(rèn)使用的是自定義協(xié)議,協(xié)議格式如下:
[ 頭1 ] (0xA5) [ 頭2 ] (0x5A) [ 地址 ] [ 命令 ] [ 數(shù)據(jù)高位 ] [ 數(shù)據(jù)低位 ] [ 尾1 ] (0x55) [尾2] (0xAA)
??后來設(shè)備需要和市場(chǎng)上其他的工業(yè)設(shè)備對(duì)接,而大多數(shù)工業(yè)設(shè)備使用的都是Modbus協(xié)議,如果直接將設(shè)備協(xié)議修改為Modbus協(xié)議的話,那么好多舊設(shè)備和新設(shè)備就會(huì)不兼容,為了兼容舊設(shè)備同時(shí)又要對(duì)接其他工業(yè)設(shè)備,那么設(shè)備要在自定義協(xié)議的基礎(chǔ)上兼容Modbus協(xié)議。Modbus協(xié)議格式如下:
[ 地址 ] [ 功能碼 ] [ 起始地址高 ] [ 起始地址低] [ 總寄存器數(shù)高 ] [ 總寄存器數(shù)低 ] [ CRC低 ] [ CRC高 ]
??下面分析這兩種協(xié)議的特點(diǎn)。
??首先看自定義協(xié)議,自定義協(xié)議的數(shù)據(jù)長(zhǎng)度是固定的,同時(shí)數(shù)據(jù)的開始和結(jié)尾都是由兩個(gè)字節(jié)標(biāo)識(shí)。那么識(shí)別自定義協(xié)議就和容易了,直接判斷數(shù)據(jù)頭和數(shù)據(jù)為就行了。當(dāng)串口接收一組數(shù)據(jù)結(jié)束后,判斷接收到的數(shù)據(jù) 是不是以 0xA5和0X5A 開頭,同時(shí)以 0x55 和0xAA結(jié)尾。如果是那么就使用自定義協(xié)議去解析數(shù)據(jù)。
??接下來分析Modbus協(xié)議,由于設(shè)備都是單獨(dú)使用的,不需要級(jí)聯(lián),所以設(shè)備的地址是固定的0x01,同時(shí)由于設(shè)備的功能比較簡(jiǎn)單,所以在使用Modubus協(xié)議的時(shí)候,只用到了讀保持寄存器(0x03)和寫保持寄存器(0x06)這兩個(gè)功能,所以Modbus的數(shù)據(jù)開頭只有兩種情況 0x01 0x03 和 0x01 0x06,如果數(shù)據(jù)開頭是這兩種情況,那么就使用Modbus協(xié)議去解析數(shù)據(jù)。
??通過對(duì)協(xié)議的分析,思路已經(jīng)很清晰了,接下來使用代碼來實(shí)現(xiàn)。
struct uart_info
{
u8 cnt;
u8 rec_buf[10];
};
struct uart_info uart1;
//在Library Options中將Printf formatter改成Large
//重新定向putchar函數(shù),使支持printf函數(shù)
int putchar( int ch )
{
while( !( UART1_SR & 0X80 ) ); //循環(huán)發(fā)送,直到發(fā)送完畢
UART1_DR = ( u8 ) ch;
return ch;
}
static void uart_io_init( void )
{
PD_DDR |= ( 1 << 5 ); //輸出模式 TXD
PD_CR1 |= ( 1 << 5 ); //推挽輸出
PD_DDR &= ~( 1 << 6 ); //輸入模式 RXD
PD_CR1 &= ~( 1 << 6 ); //浮空輸入
}
//波特率最大可以設(shè)置為38400
void uart_init( unsigned int baudrate )
{
unsigned int baud;
uart_io_init();
baud = 16000000 / baudrate;
UART1_CR1 = 0;
UART1_CR2 = 0;
UART1_CR3 = 0;
UART1_BRR2 = ( unsigned char )( ( baud & 0xf000 ) >> 8 ) | ( ( unsigned char )( baud & 0x000f ) );
UART1_BRR1 = ( ( unsigned char )( ( baud & 0x0ff0 ) >> 4 ) );
UART1_CR2_bit.REN = 1; //接收使能
UART1_CR2_bit.TEN = 1; //發(fā)送使能
UART1_CR2_bit.RIEN = 1; //接收中斷使能
}
//接收中斷函數(shù) 中斷號(hào)18
#pragma vector = 20 // IAR中的中斷號(hào),要在STVD中的中斷號(hào)上加2
__interrupt void UART1_Handle( void )
{
unsigned char res = 0;
UART1_SR &= ~( 1 << 5 ); //RXNE 清零
res = UART1_DR;
if( uart1.cnt < 9 )
uart1.rec_buf[uart1.cnt++] = res;
}
??定義一個(gè)結(jié)構(gòu)體來存儲(chǔ)接收到的數(shù)據(jù)長(zhǎng)度和數(shù)據(jù),由于自定義協(xié)議和Modbus協(xié)議最長(zhǎng)的數(shù)據(jù)只有8個(gè)字節(jié),所以這里的數(shù)組長(zhǎng)度設(shè)置10就可以了。下面初始化串口使用的IO口,設(shè)置數(shù)據(jù)位和波特率。最后在中斷中接收數(shù)據(jù)并存儲(chǔ)到數(shù)組中。
??接下來在編寫一個(gè)函數(shù)用來分析接收到的數(shù)據(jù)。
//檢測(cè)串口數(shù)據(jù)
void read_uart( void )
{
static u8 recevie_buf[10];
static u8 recevie_cnt = 0;
u8 i = 0;
delay_ms( 10 ); //隔一段時(shí)間檢測(cè)一次串口數(shù)據(jù)長(zhǎng)度,如果數(shù)據(jù)長(zhǎng)度沒有發(fā)生變化說明串口接收數(shù)據(jù)完成
if( ( uart1.cnt != recevie_cnt ) && ( uart1.cnt > 6 ) )
{
recevie_cnt = uart1.cnt;
for( i = 0; i < recevie_cnt; i++ ) //拷貝數(shù)據(jù)
{
recevie_buf[i] = uart1.rec_buf[i];
}
//根據(jù)接收到的數(shù)據(jù)區(qū)分協(xié)議
//自定義協(xié)議
if( ( recevie_buf[0] == 0xA5 ) && ( recevie_buf[1] == 0x5A ) )
{
self_define_protocol( recevie_buf, recevie_cnt );
}
//modbus協(xié)議
if( ( recevie_buf[0] == 0x01 ) && ( ( recevie_buf[1] == 0x03 ) || ( recevie_buf[1] == 0x06 ) ) )
{
modbus_protocol( recevie_buf, recevie_cnt );
}
//清空數(shù)組
for( i = 0; i < 10; i++ )
{
uart1.rec_buf[i] = 0;
recevie_buf[i] = 0;
}
uart1.cnt = recevie_cnt = 0;
}
}
??由于此設(shè)備中兩種協(xié)議的數(shù)據(jù)長(zhǎng)度都比較小,所以這里并沒有分兩次去判斷數(shù)據(jù)長(zhǎng)度,然后根據(jù)數(shù)據(jù)長(zhǎng)度來判斷數(shù)據(jù)接收是否完畢。只是延時(shí)10ms之后去判斷數(shù)據(jù)長(zhǎng)度,當(dāng)接收的數(shù)據(jù)長(zhǎng)度大于6時(shí),說明數(shù)據(jù)基本已經(jīng)接收完成了。由于數(shù)據(jù)的最大長(zhǎng)度是8,所以接收的數(shù)據(jù)長(zhǎng)度大于6時(shí),基本數(shù)據(jù)已經(jīng)接收完了,在中斷中接收8個(gè)數(shù)據(jù)速度還是非??斓?。如果數(shù)據(jù)量比較大,數(shù)據(jù)比較長(zhǎng)時(shí),最好還是通過數(shù)據(jù)長(zhǎng)度去判斷比較可靠。這里為了編寫方便,就簡(jiǎn)單的時(shí)候延時(shí)去判斷了。
??當(dāng)串口接收的數(shù)據(jù)長(zhǎng)度大于6時(shí),說明一組數(shù)據(jù)已經(jīng)接收完畢了,此時(shí)需要將串口接收的數(shù)據(jù)拷貝一份出來在使用。那為什么要將數(shù)據(jù)拷貝出來,而不是直接使用串口接收緩存區(qū)的數(shù)據(jù)呢?這是為了防止在處理數(shù)據(jù)的過程中國(guó),串口又接收到了新的數(shù)據(jù),這樣新數(shù)據(jù)就會(huì)將舊的數(shù)據(jù)覆蓋掉,有可能導(dǎo)致數(shù)據(jù)異常。為了數(shù)據(jù)的安全性將數(shù)據(jù)拷貝一份使用,即使在處理數(shù)據(jù)過程中串口緩存區(qū)的數(shù)據(jù)發(fā)生了變化,也不會(huì)破壞掉上一次接收的數(shù)據(jù)。
??數(shù)據(jù)拷貝結(jié)束后,就根據(jù)數(shù)據(jù)的特點(diǎn)來判斷當(dāng)前接收到的數(shù)據(jù)使用的是哪種協(xié)議。如果接收到的數(shù)據(jù)前兩個(gè)是 0xA5和0x5A那么就直接調(diào)用自定義協(xié)議處理函數(shù),如果前面的數(shù)據(jù)是0x01和0x03或者是0x01和0x06那么就直接調(diào)用Modbus協(xié)議去處理函數(shù)。
??接下來就可以單獨(dú)編寫兩個(gè)函數(shù)分別處理這這種協(xié)議。
//處理自定義協(xié)議
// [頭1](0xA5) [頭2](0x5A) [地址] [命令] [數(shù)據(jù)高位] [數(shù)據(jù)低位] [尾1](0x55) [尾2](0xAA)
//A5 5A 00 01 01 90 55 AA
void self_define_protocol( u8 arr[], u8 size )
{
if( ( arr[0] == 0xA5 ) && ( arr[1] == 0x5A ) && ( arr[6] == 0x55 ) && ( arr[7] == 0xAA ) )
{
//根據(jù)命令值執(zhí)行不同的動(dòng)作
}
}
//處理modbus協(xié)議
//[地址][功能碼][起始地址高][起始地址低][總寄存器數(shù)高][總寄存器數(shù)低][CRC低][CRC高]
//01 03 00 00 00 01 84 0A
//01 06 00 00 03 E8 89 74
void modbus_protocol( u8 arr[], u8 size )
{
//調(diào)用 modbus相關(guān)處理代碼
}
??將數(shù)據(jù)協(xié)議識(shí)別出來之后,就可以按照通常處理協(xié)議的方式去處理數(shù)據(jù)了。最后在主函數(shù)中循環(huán)的調(diào)用數(shù)據(jù)查詢函數(shù),檢查串口是否有接收到數(shù)據(jù)。
void main( void )
{
__asm( "sim" ); //禁止中斷
SysClkInit();
delay_init( 16 );
LED_GPIO_Init();
uart_init( 9600 );
__asm( "rim" ); //開啟中斷
while( 1 )
{
read_uart();
}
}
??接下來分別用這兩種協(xié)議測(cè)試代碼。
??使用串口助手發(fā)送自定義協(xié)議時(shí),代碼就會(huì)調(diào)用自定義協(xié)議處理函數(shù)。
??發(fā)送Modbus協(xié)議時(shí),代碼就會(huì)調(diào)用Modbus協(xié)議處理函數(shù)。
??這樣根據(jù)協(xié)議的特點(diǎn)可以通過代碼自動(dòng)去識(shí)別協(xié)議的類型,用一個(gè)串口就可以實(shí)現(xiàn)不同協(xié)議的解析。按照同樣的方法還可以解析更多的協(xié)議。當(dāng)前在不同的協(xié)議使用的波特率要相同,否則波特率不同解析出來的數(shù)據(jù)就會(huì)出現(xiàn)錯(cuò)誤,導(dǎo)致協(xié)議解析失敗。
上一篇:IAR軟件中直接查看編譯后代碼大小
下一篇:在STM8單片機(jī)中自己實(shí)現(xiàn) printf()函數(shù)功能
推薦閱讀
史海拾趣
設(shè)計(jì)資源 培訓(xùn) 開發(fā)板 精華推薦
- 神經(jīng)形態(tài)芯片可能是革新機(jī)器人實(shí)時(shí)電機(jī)控制的未來
- 從三個(gè)方面理解ARM嵌入式系統(tǒng)
- 自動(dòng)報(bào)警 基于MCU的家庭防盜報(bào)警系統(tǒng)的設(shè)計(jì)
- 存儲(chǔ)控制器及其訪問外設(shè)的原理
- 基于51系列單片機(jī)的智能照明控制系統(tǒng)設(shè)計(jì)方案
- 基于STM32的四旋翼飛行器控制系統(tǒng)
- 單片機(jī)應(yīng)用編程技巧解析
- 基于89C52的教室智能節(jié)能照明系統(tǒng)設(shè)計(jì)
- 一種新型的雨量光照傳感器的設(shè)計(jì)
- 阿里黑科技落地!夸克AI眼鏡全球首發(fā),高德、淘寶、支付寶都能用
- 化繁為簡(jiǎn), 適配復(fù)雜磁場(chǎng)環(huán)境,MT73xx 3D雙路輸出霍爾鎖存器賦能車規(guī)電機(jī)精準(zhǔn)控制
- 9.5億美元收購(gòu)恩智浦MEMS傳感器業(yè)務(wù),意法半導(dǎo)體 在傳感器領(lǐng)域的地位再升級(jí)
- 高性能電動(dòng)滑板車 BLDC 電機(jī)驅(qū)動(dòng)器:技術(shù)解析與應(yīng)用展望
- 5G工業(yè)網(wǎng)關(guān)的“邊緣計(jì)算+AI推理”一體化設(shè)計(jì),PLC協(xié)議解析與缺陷檢測(cè)的實(shí)時(shí)聯(lián)動(dòng)
- AR眼鏡的“工業(yè)指令投射”系統(tǒng),SLAM的空間定位、PLC數(shù)據(jù)實(shí)時(shí)疊加顯示
- 多光譜氣體傳感器的抗交叉干擾設(shè)計(jì)
- 多模態(tài)融合感知的“語(yǔ)義-幾何”聯(lián)合建模
- 工業(yè)觸摸屏的“壓感-手勢(shì)”多模態(tài)交互設(shè)計(jì)
- 工業(yè)機(jī)器人高精度力控的“雙模融合”傳感器設(shè)計(jì)
- “TI 中國(guó)大學(xué)計(jì)劃” 知多少——答題贏好禮!
- 《模擬對(duì)話》50周年大合集(2013-2016)
- 英飛凌角度傳感器應(yīng)用解鎖機(jī)看看你能找到幾種?
- 有獎(jiǎng)直播【Keysight World Tech Day 2023分論壇——汽車自動(dòng)駕駛與新能源】
- 傾聽您的聲音——TI 電機(jī)驅(qū)動(dòng)主題有獎(jiǎng)?wù){(diào)查
- “感謝有你,感恩有禮”——感恩節(jié)搶樓&送謝禮活動(dòng)
- TI|痛點(diǎn)解鎖機(jī):你的電源設(shè)計(jì)痛點(diǎn),我們懂!解鎖、評(píng)論贏好禮!
- 免費(fèi)申請(qǐng)TI 樣片,曬單贏好禮!
- 有獎(jiǎng)國(guó)產(chǎn)芯直播:先楫800MHz RISC-V MCU高能秀,豈止控4只伺服電機(jī),干貨多多
- 嵌入式Rust修煉營(yíng):動(dòng)手寫串口燒錄工具和MCU例程,Rust達(dá)人Hunter直播帶你入門Rust
- 衡量半導(dǎo)體工藝進(jìn)步的最好方法(下)
- ADI、Wisar等公司利用物聯(lián)網(wǎng)改善烏干達(dá)飲水狀況
- 機(jī)器人真的能使使機(jī)床自動(dòng)化?
- 解放雙手 用機(jī)器人垃圾分類!Alphabet這款機(jī)械臂你值得擁有
- 日本軟銀正試圖出售ARM部分或全部股份,最終結(jié)局還未可知
- 親身試風(fēng)口,那些在自動(dòng)駕駛?cè)?chuàng)業(yè)的“百度人”近況如何?
- STM32F103之實(shí)驗(yàn)2控制1個(gè)電機(jī)并采用編碼器讀取電機(jī)轉(zhuǎn)速
- 關(guān)于STM32的編碼器計(jì)數(shù)及溢出處理調(diào)試總結(jié)
- stm32中編碼器模式讀出“負(fù)數(shù)”的問題
- stm32f407多個(gè)定時(shí)器產(chǎn)生PWM(TIM1、TIM4、TIM9)