項(xiàng)目硬件開發(fā)總結(jié)
下面從開發(fā)者的角度對(duì)本項(xiàng)目進(jìn)行總結(jié):(因?yàn)椴皇菍憄aper所以比較隨意哈~)
項(xiàng)目采用的IoT架構(gòu),底層是STM32L475VET6潘多拉開發(fā)板+RT-Thread,對(duì)于RT-Thread的資源使用情況在論文中也有提到,這里直接截個(gè)圖:
RT-Thread使用情況詳情
內(nèi)核層的信號(hào)量、郵箱、消息隊(duì)列等機(jī)制是用于線程同步以及線程通信的,中斷一開始是用于檢測(cè)PIN設(shè)備的IRQ,但是后來去掉了,原因是不好用,確實(shí)是用中斷的時(shí)候自己懶得調(diào)試了,程序運(yùn)行會(huì)出現(xiàn)很多問題,所以直接開了個(gè)線程,這個(gè)后面會(huì)講到。
設(shè)備與驅(qū)動(dòng)層中I/O設(shè)備模型是最基本的設(shè)備Model,所以不再贅述。UART設(shè)備是值得一提的哈。因?yàn)檫@個(gè)項(xiàng)目需要用到三個(gè)串口,即串口1用于Finsh組件調(diào)試,串口2用于NB-IoT通信,串口3用于GPS數(shù)據(jù)的URC解析,但是官方給的BSP中沒有添加串口3設(shè)備,所以需要自己添加UART設(shè)備。
添加UART設(shè)備
在RT-Thread文檔中心已經(jīng)給出了詳細(xì)的添加步驟,所在路徑如下:
首先,需要本地安裝Cubemx,然后打開裁剪過的BSP目錄中的board文件夾
【關(guān)于BSP的裁剪工作可以通過ENV工具,使用scons—dist完成】
然后找到該文件夾
打開完成后如果Cube版本與官方制作BSP使用的Cube版本不同時(shí)會(huì)彈出如下提示框:
找到UART3選項(xiàng),設(shè)置為異步模式
同時(shí),需要注意的是USART3默認(rèn)使用的端口,潘多拉開發(fā)板并沒有引出,所以需要到引出的PB10和PB11單獨(dú)設(shè)置端口的模式:
最后,需要打開board文件夾中的Kconfig文件,添加UART3選項(xiàng):【建議使用Notepad打開,這樣打開的文件格式對(duì)稱,便于復(fù)制】
添加完成后,打開ENV工具輸入menuconfig命令,依次進(jìn)入Hardware Drivers Config—->On-chip Peripheral Drivers—->Enable UART就可以看到新添加的UART3選項(xiàng):
選中后重新生成工程,打開后記得重新編譯。這樣,UART3設(shè)備驅(qū)動(dòng)已經(jīng)添加成功。
適配軟件包
回顧一下本項(xiàng)目需要完成的功能,這里直接貼出了論文截圖:(懶了~)
AHT10軟件包的使用
將AHT10軟件包添加后,只需要關(guān)心應(yīng)用層的邏輯即可。
我的應(yīng)用層線程初始化都是在main線程中完成的,有關(guān)于AHT10數(shù)據(jù)采集線程的初始化如下:
線程入口函數(shù)如下:
static void aht10_thread_entry()
{
rt_device_t dev_temp = RT_NULL;
rt_device_t dev_humi = RT_NULL;
struct tmp_msg msg;
struct rt_sensor_data sensor_data;
rt_size_t res_temp,res_humi;
rt_err_t res;
res = rt_sem_take(send_AHT_sem, RT_WAITING_FOREVER);
if(res != RT_EOK)
{
rt_kprintf("getGps_thread take a nb semaphore, failed.n");
return;
}
dev_temp = rt_device_find("temp_aht");
dev_humi = rt_device_find("humi_aht");
if (dev_temp == RT_NULL)
{
rt_kprintf("Can't find device:dev_tempn");
return;
}
if (rt_device_open(dev_temp, RT_DEVICE_FLAG_RDWR) != RT_EOK)
{
rt_kprintf("open device dev_temp failed!n");
return;
}
if (dev_humi == RT_NULL)
{
rt_kprintf("Can't find dev_humi devicen");
return;
}
if (rt_device_open(dev_humi, RT_DEVICE_FLAG_RDWR) != RT_EOK)
{
rt_kprintf("open device dev_humi failed!n");
return;
}
while(1)
{
res_temp = rt_device_read(dev_temp, 0, &sensor_data, 1);
if (res_temp != 1)
{
rt_kprintf("read temp data failed!");
rt_device_close(dev_temp);
return;
}
else
{
// rt_kprintf("temp:%3dCn",abs(sensor_data.data.temp)/10);
msg.temp_value=(abs(sensor_data.data.temp)/10);
}
rt_thread_mdelay(10);
res_humi = rt_device_read(dev_humi, 0, &sensor_data, 1);
if (res_humi != 1)
{
rt_kprintf("read humi data failed!n");
rt_device_close(dev_humi);
return;
}
else
{
// rt_kprintf("hum:%2d%, timestamp:%5dn",abs(sensor_data.data.humi)/10);
msg.humi_value=abs(sensor_data.data.humi)/10;
}
rt_mq_send(tmp_msg_mq, &msg,sizeof(msg));
// rt_kprintf("======aht10-Thread Send a mq,msg.tem:%d,msg.hum:%d======rn",msg.temp_value,msg.humi_value);
rt_thread_mdelay(300);
}
}
在上述代碼中首先是獲取NB初始化完成后release的信號(hào)量;獲取成功后,再獲取注冊(cè)到Sensor框架中的溫濕度傳感器(注意:這里的溫濕度傳感器是分開的,可以通過list_device在Finshi終端查看);然后是打開設(shè)備并讀取溫濕度信息;最后將采集到的數(shù)據(jù)以郵箱機(jī)制發(fā)送到NB發(fā)送線程。這是整個(gè)AHT10軟件包的添加和數(shù)據(jù)的讀取工作(需要注意:AHT10是通過I2C進(jìn)行通信的,所以需要開啟I2C設(shè)備框架)
lwgps軟件包的使用
lwgps軟件包是一個(gè)輕量級(jí)的gps的URC解析包,支持NEMA格式。在使用軟件包的時(shí)候也遇到過不少的坑哈,但是慶幸的是都已經(jīng)解決了
可以直接使用該作者提供的驅(qū)動(dòng)框架。特別需要注意的是:這里有一個(gè)小坑,可能很多人會(huì)忽略,軟件包已經(jīng)使用了INIT_APP_EXPORT(lwgps2rtt_init);添加了lwgps的初始化,因此不需要在應(yīng)用層調(diào)用,如果調(diào)用會(huì)出現(xiàn)我之前出現(xiàn)的報(bào)錯(cuò)信息:
線程入口函數(shù)如下:
static void getGps_thread_entry()
{
lwgps_t gps_info;
struct gps_msg gpsmsg;
float lati , longi;
rt_err_t res;
// res = rt_sem_take(send_Gps_sem, RT_WAITING_FOREVER);
// if(res != RT_EOK)
// {
// rt_kprintf("getGps_thread take a nb semaphore, failed.n");
// return;
// }
while(1)
{
lwgps2rtt_get_gps_info(&gps_info);
gpsmsg.lati_value=(gps_info.latitude);
gpsmsg.longi_value=(gps_info.longitude);
gpsmsg.hour=(gps_info.hours);
// rt_kprintf("GPS-Data:hour-->%drn",gpsmsg.hour);
rt_mq_send(gps_msg_mq,&gpsmsg,sizeof(gpsmsg));
rt_thread_delay(500);
}
}
使用PIN設(shè)備——MQ2數(shù)據(jù)采集
關(guān)于MQ2的數(shù)據(jù)讀取,并沒有使用到ADC設(shè)備,因?yàn)槲蚁肟s短開發(fā)周期趕論文┭┮﹏┭┮。我選用的是MQ2的DO輸出模式,通過調(diào)整電位器設(shè)置閾值,實(shí)現(xiàn)原理比較簡(jiǎn)單哈,不再贅述。同時(shí),它的軟件讀取工作也比較簡(jiǎn)單。但是由于項(xiàng)目實(shí)時(shí)性要求,上行數(shù)據(jù)流是采用JSON封裝的,方便小程序端解析,所以需要持續(xù)發(fā)送采集數(shù)據(jù),因此無論是檢測(cè)到可燃?xì)怏w還是沒有檢測(cè)到都要發(fā)送消息隊(duì)列。
詳細(xì)代碼如下:
static void test_thread_entry()
{
char TR_ARRAY[]="true";
char FA_ARRAY[]="false";
struct mq_msg mq2_msg;
while(1)
{
// rt_kprintf("testrn");
if(rt_pin_read(MQ2_PIN_NUM)==PIN_LOW)
{
memcpy(mq2_msg.msg,TR_ARRAY,sizeof(TR_ARRAY));
rt_mq_send(mq2_msg_mq,&mq2_msg,sizeof(mq2_msg));
rt_pin_write(BEEP_PIN_NUM, PIN_HIGH);
rt_thread_mdelay(500);
rt_pin_write(BEEP_PIN_NUM, PIN_LOW);
}
else{
memcpy(mq2_msg.msg,FA_ARRAY,sizeof(FA_ARRAY));
rt_mq_send(mq2_msg_mq,&mq2_msg,sizeof(mq2_msg));
}
rt_thread_mdelay(200);
}
}
使用PIN設(shè)備——紅外對(duì)射數(shù)據(jù)采集
紅外模塊采用的“消抖”操作,因?yàn)橛锌赡苘囬T位置經(jīng)過的人會(huì)一直停留,所以按照按鍵消抖處理的,詳細(xì)的流程不再說明,直接上代碼了:
/* 紅外檢測(cè)線程入口函數(shù)*/
static void hw_thread_entry(void parameter)
{
static rt_uint8_t hw_up = 1; / 無人標(biāo)志 /
/ 初始化紅外對(duì)射模塊 /
rt_pin_mode(PIN_NUM_ADD, PIN_MODE_INPUT);
rt_pin_mode(PIN_NUM_SUB, PIN_MODE_INPUT);
rt_pin_mode(LED0_PIN, PIN_MODE_OUTPUT);
tmp_msg_mb = rt_mb_create("temp_mb0", MB_LEN, RT_IPC_FLAG_FIFO);
while (1)
{
/ 檢測(cè)無人標(biāo)志 /
if (hw_up && ((rt_pin_read(PIN_NUM_ADD) == PIN_LOW) ||
(rt_pin_read(PIN_NUM_SUB) == PIN_LOW)))
{
rt_thread_mdelay(50); / 延時(shí)消抖*/
hw_up = 0;
if (rt_pin_read(PIN_NUM_SUB) == PIN_LOW)
{
rt_kprintf("Having person-SUB!n");
rt_pin_write(LED0_PIN, PIN_HIGH);
if(people_num<=0)
{
rt_kprintf("The number of people is emptyn");
continue;
}
else{
people_num--;
rt_kprintf("The num of people is %drn",people_num);
//rt_mb_send(tmp_msg_mb,people_num);
// rt_mb_send(tmp_msg_mb,people_num);
}
}
else if (rt_pin_read(PIN_NUM_ADD) == PIN_LOW)
{
rt_kprintf("Having person-ADD!n");
rt_pin_write(LED0_PIN, PIN_LOW);//點(diǎn)亮
if(people_num>=30)
{
rt_kprintf("The number of people is full!n");
continue;
}
else{
people_num++;
rt_kprintf("The num of people is %drn",people_num);
// rt_mb_send(tmp_msg_mb,people_num);
}
}
}
else if((rt_pin_read(PIN_NUM_ADD) == PIN_HIGH) &&
(rt_pin_read(PIN_NUM_SUB) == PIN_HIGH))
{
hw_up = 1; /*無人標(biāo)志 */
// rt_mb_send(tmp_msg_mb,people_num);
}
rt_mb_send(tmp_msg_mb,people_num);
rt_thread_mdelay(100);
}
}
使用AT設(shè)備——NB模塊初始化與NB模塊數(shù)據(jù)發(fā)送
首先需要在項(xiàng)目中添加AT組件,同時(shí)添加M5311軟件包,添加完成后,在應(yīng)用層main線程中開啟NB初始化以及NB訂閱和發(fā)送線程(采用MQTT協(xié)議)
初始化線程入口函數(shù)如下:
//NB初始化線程:新建MQTT機(jī)制+連接MQTT服務(wù)器
static void NB_mqtt_thread_entery()
{
nb_client = at_client_get("uart2");
nb_resp = at_create_resp(1024, 0, rt_tick_from_millisecond(300));
if(at_obj_exec_cmd(nb_client,nb_resp,arv)!=RT_EOK)
{
LOG_E("The MQTT haven't inited successrn");
}
else{
LOG_E("MQTT HAVE INITED SUCCESSrn");
if(at_obj_exec_cmd(nb_client,nb_resp,"AT+MQTTOPEN=1,1,0,0,0,'',''")!=RT_EOK)
{
LOG_E("The MQTT haven't inited successrn");
}
else{//真正完成了新建MQTT機(jī)制和連接服務(wù)器
LOG_E("The MQTT haven inited successrn");
rt_pin_write(BEEP_PIN_NUM, PIN_HIGH);
rt_thread_mdelay(500);
rt_pin_write(BEEP_PIN_NUM, PIN_LOW);
rt_sem_release(nb_sem);//釋放信號(hào)量
rt_sem_release(send_Gps_sem);
rt_sem_release(send_AHT_sem);
}
}
rt_thread_mdelay(1000);
}
訂閱和發(fā)送線程入口函數(shù)如下:
static void NB_mqtt_send_thread_entery()
{
struct tmp_msg msg;
struct gps_msg GPS;
struct mq_msg mq2msg;
static rt_err_t result;
int pep_num=0;
result = rt_sem_take(nb_sem, RT_WAITING_FOREVER);
if(result != RT_EOK)
{
rt_kprintf("NB_mqtt_send_thread take a nb semaphore, failed.n");
return;
}else{
for(;;)
{
if(at_obj_exec_cmd(nb_client,nb_resp,"AT+MQTTSUB="pyr",1")==RT_EOK)//訂閱主題
{
rt_kprintf("The NB-IoT have subscribed the pyr topicrn");
break;
}
}
while(1)
{
if((rt_mq_recv(gps_msg_mq,&GPS,sizeof(GPS),RT_WAITING_FOREVER)==RT_EOK)&&
(rt_mq_recv(tmp_msg_mq, &msg, sizeof(msg), RT_WAITING_FOREVER)==RT_EOK)&&
(rt_mq_recv(mq2_msg_mq, &mq2msg, sizeof(mq2msg), RT_WAITING_FOREVER)==RT_EOK)&&
(rt_mb_recv(tmp_msg_mb,(int*)&pep_num,RT_WAITING_FOREVER)==RT_EOK))
{
rt_kprintf("---------->NB Send Thread Receive the data-hour:%d,tmp:%d,mq2:%s,pep_num:%drn",GPS.hour,msg.temp_value,mq2msg.msg,pep_num);
if(at_obj_exec_cmd(nb_client,nb_resp,"AT+MQTTPUB="pyr",1,1,0,0,"{"temp":%d,"hum":%d,"lati":%f,"longi":%f,"mq2":%s,"pep_num":%d}"",msg.temp_value,msg.humi_value,GPS.lati_value,GPS.longi_value,mq2msg.msg,pep_num)!=RT_EOK)
{
LOG_E("Send the MEssage of the MQTT failedrn");
}
// if(at_obj_exec_cmd(nb_client,nb_resp,"AT+MQTTPUB="pyr",1,1,0,0,"{"latitude":37.3862770000,"longitude":117.9898270000,"temp":23,"humi":60,"person":15,"smoke":false}"")!=RT_EOK)
// {
// LOG_E("Send the MEssage of the MQTT failedrn");
// }
}
rt_thread_mdelay(500);
// rt_thread_mdelay(1000); 有延時(shí),所以采用0.5S-Debug測(cè)出來的
}
}
}
項(xiàng)目軟件開發(fā)總結(jié)
項(xiàng)目的應(yīng)用軟件使用的是微信小程序,涉及到的內(nèi)容包括:小程序適配MQTT客戶端連接服務(wù)器以及訂閱和發(fā)布消息、小程序云開發(fā)模式Serverless、小程序使用Map組件、小程序使用騰訊云SMS服務(wù)(個(gè)人版)、小程序?qū)崿F(xiàn)左滑刪除樣式等內(nèi)容… …另外,MQTT服務(wù)器使用的是EMQ搭建的免費(fèi)版MQTT服務(wù)器。
這是使用的MQTT地址,目前仍然可以使用:EMQ服務(wù)器IP地址 ,支持端口號(hào)18083、8083、8084、18084訪問,同時(shí)提供匿名訪問.
-
PIN管
+關(guān)注
關(guān)注
0文章
36瀏覽量
6274 -
UART接口
+關(guān)注
關(guān)注
0文章
124瀏覽量
15199 -
RT-Thread
+關(guān)注
關(guān)注
31文章
1239瀏覽量
39426 -
STM32L4
+關(guān)注
關(guān)注
1文章
42瀏覽量
9350 -
MQTT協(xié)議
+關(guān)注
關(guān)注
0文章
93瀏覽量
5306
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論