嵌入式開發中我們要時刻保持代碼的高效與整潔
看之前,先點贊
好習慣,要養成
一、前言
嵌入式開發中我們要時刻保持代碼的高效與整潔。在第一節中“NB-IOT開發|nbiot開發教程《一》AT指令類模組驅動解析”我們說到AT指令模組最好的驅動-狀態機。本節我們就開始編寫狀態機。
目前網上可以看到的狀態機如下圖:
u8 NB_IoT_ack_chack(u8 *str)
{
delay_ms(10);
if(USART2_RX_STA!=0)
{
USART2_RX_STA=0;
if(strstr((const char*)USART2_RX_BUF,(const char*)str))//符合預期
{
memset(USART2_RX_BUF,0, sizeof USART2_RX_BUF);
return 0;
}
else //不符合預期
{
memset(USART2_RX_BUF,0, sizeof USART2_RX_BUF);
return 1;
}
}
else
{
memset(USART2_RX_BUF,0, sizeof USART2_RX_BUF); //清空數組
return 1;
}
}
u8 NB_IoT_ZDFW()
{
u8 x=0;
cmd1: send_NB_IoT("AT+NCONFIG=AUTOCONNECT,TRUE\r\n") ;
if(!NB_IoT_ack_chack("OK")) x++ ;
else goto cmd1;
cmd2: send_NB_IoT("AT+CFUN=1\r\n") ;
if(!NB_IoT_ack_chack("OK")) x++ ;
else goto cmd2;
cmd3: send_NB_IoT("AT+NRB\r\n") ;
if(!NB_IoT_ack_chack("REBOOTING")) x++ ;
else goto cmd3;
}
delay_ms(10); 對,你沒有看錯就是delay,死等,多麼可怕的應用。而且死等10ms就一定能收到數據嗎,有些模組中的指令返回時間並不是固定的,可見delay並不是很合適的使用,如果延時1s呢?????,如果此時有按鍵或者有屏幕刷新,1s的延時能接受嗎??,顯然是不能的。本次狀態機拒絕使用死等!
二、代碼實現
舉例:
狀態:1.發送AT確認模組是否正常;2.配置模組參數;3.發送數據;(暫定三個狀態)
動作:1.發送AT確認模組是否正常->通過串口發送AT\r\n,模組正常會返回OK,模組不正常返回非OK數據或者不返回。
2.配置模組參數->通過串口發送AT+PARAM=10\r\n,模組正常會返回OK,模組不正常返回非OK數據或者不返回。
3.發送數據->通過串口發送AT+SEND=2030559498473738292929394\r\n,模組正常會返回OK,模組不正常返回非OK數據或者不返回。
事件:狀態機進行狀態切換需要事件驅動。
事件1:狀態強制切換事件(event_change_state),用於將狀態機強制切換到某個指定狀態或者下一狀態。
事件2:串口接收到完整數據包事件(event_uart_data),模組返回數據。
事件3:超時事件(event_timeout),例如發送AT後模組在1s或者指定時間內沒有返回任何數據。
狀態:
typedef enum
{
STATE_HAL_RESET= 0x01, /*模組復位*/
STATE_AT, /*發送AT指令測試*/
STATE_SET_PARAM, /*設置參數*/
STATE_SEND_DATA, /*發送數據*/
STATE_IDEL, /*空閒狀態*/
} nbiot_state_e;
結合上圖1中狀態機我們用語言描述下AT指令。例如發送AT模組返回OK,首先這個具有兩個狀態,一個是當前狀態,一個是下一狀態;具有的動作就是發送AT,模組返回OK,兩個執行動作;在實際使用時可能會存在模組不回覆或者回復錯誤的時候,所以要嘗試,嘗試要記錄重試次數try_cnt;發送完AT後模組要有一個反應時間,就是MCU等待時間,這個時間不確定,每條指令等待時間不一樣,計做wait_time;
現在用C語言結構體描述上述文字
typedef struct
{
nbiot_state_e cur_state; /*當前狀態*/
nbiot_state_e next_state; /*下一個狀態*/
int try_cnt; /*重試次數*/
int wait_time; /*等待時間*/
int (*action1)(void); /*動作1:發送AT*/
int (*action2)(const void *arg,int len); /*動作2:判斷接收到的數據是否爲OK或者未接收到*/
} nbiot_fsm_state_t;
狀態描述完了,還需要有一個遊動指針指向當前正在執行的狀態。
typedef struct
{
int cur_state; /*當前狀態*/
int trycnt; /*當前狀態已經重試的次數*/
uint32_t init; /*當前狀態是執行action1還是執行action2*/
const nbiot_fsm_state_t *fsm_state; /*當前狀態是功能參數*/
} nbiot_fsm_state_index_t;
static nbiot_fsm_state_index_t nbiot_fsm_state_index;
狀態中的兩個執行動作C語言描述:action1->發送AT
action2:->判斷接收到的數據是否爲OK,如果是則執行下一狀態,如果不是則等待,如果超時未收到數據就重試。
/*
* AT
*/
static int at_action1(void)
{
const char *cmd = "AT\r\n";
return cola_device_write(uart_dev,0,cmd,strlen(cmd));
}
static int at_action2(const void *arg,int len)
{
if(!len)
return STATE_RETRY;
char *pt = memmem(arg, len, "OK", strlen("OK"));
if (pt)
{
return STATE_NEXT;
}
return STATE_WAIT;
}
每一個狀態都寫兩個執行動作。
全部的狀態列表:
static const nbiot_fsm_state_t nbiot_state_list[] =
{
{STATE_HAL_RESET,STATE_AT, 1, 300,nbiot_reset_action1,nbiot_reset_action2},
{STATE_AT, STATE_SET_PARAM,3, 3000,at_action1, at_action2 },
{STATE_SET_PARAM,STATE_SEND_DATA,4, 3000,set_param_action1, set_param_action2 },
{STATE_SEND_DATA,STATE_IDEL, 5, 3000,send_data_action1, send_data_action2 },
{STATE_IDEL, STATE_IDEL, 100,3000,state_idel_action1, state_idel_action2 },
};
狀態寫完了接下來還需要一個狀態管理的函數,負責管理是執行action1還是執行action2;
狀態管理函數:狀態管理函數中等待模組返回數據採用的是軟定時器,該方式很好的避免了死等問題。
狀態和執行動作完成了,接下來就是事件,驅動狀態機運行的驅動力--------事件。
事件1:狀態強制切換事件
更新遊動狀態指針,並且給任務發送狀態改變的信號cola_set_event(&nbiot_task,SIG_CHANGE_STATE);
static void nbiot_change_state(nbiot_state_e state)
{
const nbiot_fsm_state_t *fsm_state = nbiot_get_state(state);
if (fsm_state)
{
nbiot_fsm_state_index.init = ACTION1;
if (nbiot_fsm_state_index.cur_state != state)
{
nbiot_fsm_state_index.cur_state = state;
nbiot_fsm_state_index.trycnt = 0;
nbiot_fsm_state_index.fsm_state = fsm_state;
}
cola_set_event(&nbiot_task,SIG_CHANGE_STATE);
}
}
事件2:串口接收到完整數據包事件
上一節“NB-IOT開發|nbiot開發教程《二》AT指令類模組驅動-STM32串口實現接收不定長度數據”中提到的,不明白可以再看下。
串口接收到完整數據包後通知nbiot任務,cola_set_event(uart1_dev.owner,SIG_DATA);
static void uart_timer_cb(uint32_t event)
{
if(uart1_dev.owner)
{
cola_set_event(uart1_dev.owner,SIG_DATA);
}
}
事件3:超時事件
static void nbiot_timer_cb(uint32_t event)
{
cola_set_event(&nbiot_task,SIG_TIMEOUT);
}
三個驅動狀態機運行的時間就完成了,既然這三個事件是驅動力,那麼就需要在任務中管理這三個驅動事件。
事件管理
static void nbiot_task_cb(uint32_t event)
{
int err = 0;
if(event & SIG_CHANGE_STATE)
{
nbiot_fsm_schedule(NULL,0);
}
if(event & SIG_TIMEOUT)
{
nbiot_fsm_schedule(NULL,0);
}
if (event & SIG_DATA)
{
err = cola_device_read(uart_dev,0,g_buf,sizeof(g_buf));
if(err)
{
nbiot_fsm_schedule(g_buf,err);
}
}
}
三、調試
狀態機已經跑起來了,確實按照我們狀態列表中編寫的在跑,首先運行第一個狀態硬件復位,300ms後執行AT指令發送,發送一次AT,嘗試3次,而且等待時間爲3000ms,如果嘗試超過最大次數,則從新復位nbiot模組。如果接收到OK則執行下一個狀態。
四、代碼下載
重要的就是代碼下載了,不知道有木有人在看呀,等評論超100再上傳吧,要不然沒有動力寫下去了。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。