還在用傳統(tǒng)方式驅(qū)動一個通信模組?TOS的AT模組框架了解下吧!
本節(jié)基于TOS的AT框架,我實現(xiàn)了一個基于MX+開發(fā)板的demo,用于控制之前搭的智能小車,效果如下,詳細源碼及實驗例程請參考文末碼云倉庫鏈接:
動手智能小車記(5)-坦克底盤硬件模塊大雜燴
1、什么是AT指令?
在嵌入式開發(fā)過程中,我們有時候要使用一些通信模組,比如藍牙、WIFI、4G、NBIOT等等,這些模組內(nèi)部固件已經(jīng)將協(xié)議棧封裝好了,然后模組硬件向外部提供了標(biāo)準(zhǔn)的串口,這樣通過與模組的串口相連接,就可以與模組進行通信;一般情況下,廠商都會提供模組的使用手冊,只要照著流程去操作模組就可以正常通信了,如下圖所示,這是一個MCU、藍牙模組、手機之間的通信案例:
2、為什么要有AT框架?
一般情況下,在一些物聯(lián)網(wǎng)產(chǎn)品的項目上可能這樣的需求,比如:
常規(guī)的一些傳感器設(shè)備,需要監(jiān)測環(huán)境溫度、濕度等等這樣的情況:
-
實現(xiàn)數(shù)據(jù)上傳
共享單車、智能門鎖
-
實現(xiàn)開鎖的邏輯
等等。。。
這些需求看起來就非常簡單,比如我就用ESP8266+一個后臺服務(wù)器來實現(xiàn)這樣的需求吧,只要后臺提供好API接口,那么這類簡單的需求分分鐘搞定,完全沒有任何難度,在應(yīng)用程序上編寫好模組的驅(qū)動接口和通信邏輯就可以了。
但是,如果換一個呢??再換一個呢?有可能實現(xiàn)同樣的需求,我們還要去實現(xiàn)不一樣的驅(qū)動流程,這是不是顯得很麻煩?基于這樣的問題誕生,于是各個廠商分別提出了對應(yīng)的AT框架思維,那么這種AT框架思維具體是什么樣的呢?以驅(qū)動ESP8266為例,一般有以下幾種模式:
-
AP模式 -
STA模式 -
AP+STA模式
以STA模式為例,最后要和云端服務(wù)器進行對接,我們首先要完成初始化流程,一般要發(fā)以下幾個指令:
-
AT+RESTORE\r\n 模組復(fù)位
-
ATE0\r\n 關(guān)閉回顯
-
AT+CWMODE=1\r\n 設(shè)置多連接
-
AT+CIPMODE=0\r\n 關(guān)閉透傳模式
-
AT+CIPMUX=1\r\n 開始多連接模式
-
AT+CWJAP="TOS","12345678"\r\n 連接熱點
這樣就基本完成了模組的初始化流程,初始化完畢以后,就可以進入數(shù)據(jù)傳輸?shù)牧?,連接服務(wù)器,然后開啟透傳模式,進入透傳模式,然后就可以把數(shù)據(jù)直接傳送到后臺了,此時還可以讀取后臺的消息,當(dāng)我們不需要需要模組的時候,還可以將模組掉電;所以,我們可以在這個基礎(chǔ)上把這個驅(qū)動流程框架化,即是擁有初始化、連接服務(wù)器、發(fā)送、接收、關(guān)閉等等這些接口。
3、TencetOS tiny AT框架
在TencentOS tiny中,內(nèi)部就集成了一套簡單易用的AT框架,哪怕是不一樣的指令,我們也只需要填充對應(yīng)的方法,然后注冊到框架上,就可以順利與模組進行通信了,以下是TencentOS tiny AT框架的基本組成圖:
上圖來源于汪兄講解的PPT
對于應(yīng)用開發(fā)者來說,我們最關(guān)注的是SAL interface、也就是網(wǎng)絡(luò)適配框架,只要模組注網(wǎng)成功,那么在這一層,我們不需要具體去關(guān)注模組到底是怎么用AT指令去通信的,我們只需要調(diào)用SAL interface的socket、connect、send、recv、close等等接口完成我們與后臺的通信或者與別的通信方式的邏輯即可,但是調(diào)用SAL接口口還需要去與各個模組進行適配。
typedef?struct?sal_module_st?{
????int?(*init)(void);
????int?(*get_local_mac)(char?*mac);
????int?(*get_local_ip)(char?*ip,?char?*gw,?char?*mask);
????int?(*parse_domain)(const?char?*host_name,?char?*host_ip,?size_t?host_ip_len);
????int?(*connect)(const?char?*ip,?const?char?*port,?sal_proto_t?proto);
????int?(*send)(int?sock,?const?void?*buf,?size_t?len);
????int?(*recv_timeout)(int?sock,?void?*buf,?size_t?len,?uint32_t?timeout);
????int?(*recv)(int?sock,?void?*buf,?size_t?len);
????int?(*sendto)(int?sock,?char?*ip,?char?*port,?const?void?*buf,?size_t?len);
????int?(*recvfrom)(int?sock,?void?*buf,?size_t?len);
????int?(*recvfrom_timeout)(int?sock,?void?*buf,?size_t?len,?uint32_t?timeout);
????int?(*close)(int?sock);
}?sal_module_t;
對于怎么去綁定(適配)模組和SAL interface,在此之前那我們還需要完成AT framework與HAL(uart)的適配,然后提供SAL interface需要的接口,注冊上去,這樣我們就可以在SAL上愉快的進行操作了,接下來AT框架具體是怎么解析每個AT指令我們就不需要特別去關(guān)心了,感興趣的可以去研究一下tos_at.c、tos_at.h這兩個文件。
在TencentOS tiny SDK中,騰訊官方已經(jīng)提供了一些熱門模組的操作例程,比如esp8266,它是怎么與SAL interface完成適配的呢?如下:
上圖來源于戴兄講解的PPT
這樣的話,我們就可以調(diào)用TOS提供的SAL接口進行通信了,如下,在sal_module_wrapper.h中查看,詳細實現(xiàn)在sal_module_wrapper.c:
/**
?*?@brief?Convert?domain?to?ip?address.
?*
?*?@attention?None
?*
?*?@param[in]???host_name???domain?name?of?the?host
?*?@param[out]??host_ip?????ip?address?of?the?host
?*?@param[out]??host_ip_len?ip?address?buffer?length
?*
?*?@return??errcode
?*/
int?tos_sal_module_parse_domain(const?char?*host_name,?char?*host_ip,?size_t?host_ip_len);
/**
?*?@brief?Connect?to?remote?host.
?*
?*?@attention?None
?*
?*?@param[in]???ip??????ip?address?of?the?remote?host
?*?@param[in]???port????port?number?of?the?remote?host
?*?@param[in]???proto???protocol?of?the?connection(TCP/UDP)
?*
?*?@return??socket?id?if?succuss,?-1?if?failed.
?*/
int?tos_sal_module_connect(const?char?*ip,?const?char?*port,?sal_proto_t?proto);
/**
?*?@brief?Send?data?to?the?remote?host(TCP).
?*
?*?@attention?None
?*
?*?@param[in]???sock????socket?id
?*?@param[in]???buf?????data?to?send
?*?@param[in]???len?????data?length
?*
?*?@return??data?length?sent
?*/
int?tos_sal_module_send(int?sock,?const?void?*buf,?size_t?len);
/**
?*?@brief?Receive?data?from?the?remote?host(TCP).
?*
?*?@attention?None
?*
?*?@param[in]???sock????socket?id
?*?@param[in]???buf?????data?buffer?to?hold?the?data?received
?*?@param[in]???len?????data?buffer?length
?*
?*?@return??data?length?received
?*/
int?tos_sal_module_recv(int?sock,?void?*buf,?size_t?len);
/**
?*?@brief?Receive?data?from?the?remote?host(TCP).
?*
?*?@attention?None
?*
?*?@param[in]???sock????socket?id
?*?@param[in]???buf?????data?buffer?to?hold?the?data?received
?*?@param[in]???len?????data?buffer?length
?*?@param[in]???timeout?timeout
?*
?*?@return??data?length?received
?*/
int?tos_sal_module_recv_timeout(int?sock,?void?*buf,?size_t?len,?uint32_t?timeout);
/**
?*?@brief?Send?data?to?the?remote?host(UDP).
?*
?*?@attention?None
?*
?*?@param[in]???sock????socket?id
?*?@param[in]???ip??????ip?address?of?the?remote?host
?*?@param[in]???port????port?number?of?the?remote?host
?*?@param[in]???buf?????data?to?send
?*?@param[in]???len?????data?length
?*
?*?@return??data?length?sent
?*/
int?tos_sal_module_sendto(int?sock,?char?*ip,?char?*port,?const?void?*buf,?size_t?len);
/**
?*?@brief?Receive?data?from?the?remote?host(UDP).
?*
?*?@attention?None
?*
?*?@param[in]???sock????socket?id
?*?@param[in]???buf?????data?buffer?to?hold?the?data?received
?*?@param[in]???len?????data?buffer?length
?*
?*?@return??data?length?received
?*/
int?tos_sal_module_recvfrom(int?sock,?void?*buf,?size_t?len);
/**
?*?@brief?Receive?data?from?the?remote?host(UDP).
?*
?*?@attention?None
?*
?*?@param[in]???sock????socket?id
?*?@param[in]???buf?????data?buffer?to?hold?the?data?received
?*?@param[in]???len?????data?buffer?length
?*?@param[in]???timeout?timeout
?*
?*?@return??data?length?received
?*/
int?tos_sal_module_recvfrom_timeout(int?sock,?void?*buf,?size_t?len,?uint32_t?timeout);
/**
?*?@brief?Close?the?connection.
?*
?*?@attention?None
?*
?*?@param[in]???sock????socket?id
?*
?*?@return??errcode
?*/
int?tos_sal_module_close(int?sock);
但是你以為這就完了嗎?為了和Posix API無差異化調(diào)用,TencentOS tiny官方開發(fā)人員開發(fā)了一套近似于通用網(wǎng)絡(luò)操作接口,就類似操作一個文件一樣open、read、write、close,這不就更簡單了嘛?我們來一睹為快:
tos_at_socket.h
#ifndef??_TOS_AT_SOCKET_H_
#define??_TOS_AT_SOCKET_H_
#include?"tos_at_socket_lib.h"
#include?"tos_at_socket_types.h"
#define?AF_INET?????????????0
#define?AF_INET6????????????1
#define?AF_UNIX?????????????2
/*?Provides?sequenced,?reliable,?bidirectional,?connection-mode?byte?streams,?and?may?provide?a?transmission?mechanism?for?out-of-band?data.?*/
#define?SOCK_STREAM?????????0
/*?Provides?datagrams,?which?are?connectionless-mode,?unreliable?messages?of?fixed?maximum?length.?*/
#define?SOCK_DGRAM??????????1
/*?Peeks?at?an?incoming?message.?The?data?is?treated?as?unread?and?the?next?recv()?or?similar?function?shall?still?return?this?data.?*/
#define?MSG_PEEK????????????0x01
/*?Requests?out-of-band?data.?The?significance?and?semantics?of?out-of-band?data?are?protocol-specific.?*/
#define?MSG_OOB?????????????0x02
/*?On?SOCK_STREAM?sockets?this?requests?that?the?function?block?until?the?full?amount?of?data?can?be?returned.?The?function?may?return?the?smaller?amount?of?data?if?the?socket?is?a?message-based?socket,?if?a?signal?is?caught,?if?the?connection?is?terminated,?if?MSG_PEEK?was?specified,?or?if?an?error?is?pending?for?the?socket.?*/
#define?MSG_WAITALL?????????0x04
int?socket(int?domain,?int?type,?int?protocol);
int?connect(int?socket,?const?struct?sockaddr?*address,?socklen_t?address_len);
int?recv(int?socket,?void?*buffer,?size_t?length,?int?flags);
int?recvfrom(int?socket,?void?*?buffer,?size_t?length,?int?flags,?struct?sockaddr?*address,?socklen_t?*address_len);
int?send(int?socket,?const?void?*buffer,?size_t?length,?int?flags);
int?sendto(int?socket,?const?void?*message,?size_t?length,?int?flags,?const?struct?sockaddr?*dest_addr,?socklen_t?dest_len);
int?shutdown(int?socket,?int?how);
int?read(int?socket,?void?*buffer,?size_t?length);
int?close(int?socket);
int?write(int?socket,?const?void?*buffer,?size_t?length);
#endif?/*?_TOS_AT_SOCKET_H_?*/
只要注冊框架等流程完成以后,模組注網(wǎng)成功,后面就可以直接這樣常規(guī)操作了:
void?network_demo(void)
{
????int?recv_len?=?-1;
????int?fd,?rc,?cnt?=?0;
????struct?sockaddr_in?addr;
????bzero(&addr,?sizeof(addr));
????addr.sin_family?=?AF_INET;
????addr.sin_addr.s_addr?=?inet_addr(SERVER_IP);
????addr.sin_port?=?htons(SERVER_PORT);
????fd?=?socket(AF_INET,?SOCK_STREAM,?0);
????if?(fd?0)
????{
????????printf("socket?failed\n");
????????return;
????}
????rc?=?connect(fd,?(struct?sockaddr?*)&addr,?sizeof(addr));
????if?(rc?0)
????{
????????printf("connect?failed\n");
????????close(fd);
????????return;
????}
?
? while(1)
? {
?? //調(diào)用send發(fā)送數(shù)據(jù)
?? //調(diào)用recv接收并處理數(shù)據(jù)
? }
?
? close(fd);
}
基于TencentOS tiny AT框架的基礎(chǔ)上,我編寫了一個基于MX+開發(fā)板的控制小車?yán)?,源碼已更新到碼云個人倉庫。
個人碼云倉庫地址:
https://gitee.com/morixinguan
我還將之前做的一些項目以及練習(xí)例程在近期內(nèi)全部上傳完畢,與大家一起分享交流:
全文參考資料
騰訊物聯(lián)網(wǎng)終端操作系統(tǒng)SDK文檔.pdf
騰訊物聯(lián)網(wǎng)終端操作系統(tǒng)開發(fā)指南.pdf
TencentOS tiny技術(shù)講解與開發(fā)實踐PPT.pdf
云加社區(qū)沙龍(騰訊物聯(lián)網(wǎng)操作系統(tǒng)TencentOS tiny架構(gòu)解析與實踐).pdf
公眾號粉絲福利時刻
這里我給大家申請到了福利,本公眾號讀者購買小熊派開發(fā)板可享受9折優(yōu)惠,有需要購買小熊派以及騰訊物聯(lián)網(wǎng)開發(fā)板的朋友,淘寶搜索即可,跟客服說你是公眾號:嵌入式云IOT技術(shù)圈 的粉絲,立享9折優(yōu)惠!
往期精彩
TencentOS tiny RTOS快速入門
網(wǎng)紅騰訊物聯(lián)網(wǎng)開發(fā)板終極開箱評測,讓我們一睹為快!
Linux進程間通信(中)之信號、信號量實踐
Linux進程間通信(下)之共享內(nèi)存實踐
STM32硬核DIY機械鍵盤|藍牙USB雙模|燈控
覺得本次分享的文章對您有幫助,隨手點[在看]
并轉(zhuǎn)發(fā)分享,也是對我的支持。
免責(zé)聲明:本文內(nèi)容由21ic獲得授權(quán)后發(fā)布,版權(quán)歸原作者所有,本平臺僅提供信息存儲服務(wù)。文章僅代表作者個人觀點,不代表本平臺立場,如有問題,請聯(lián)系我們,謝謝!