NB-IOT開發|nbiot開發教程《三》AT指令類模組驅動-STM32實現AT指令狀態機

嵌入式開發中我們要時刻保持代碼的高效與整潔

看之前,先點贊
好習慣,要養成

一、前言

        嵌入式開發中我們要時刻保持代碼的高效與整潔。在第一節中“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再上傳吧,要不然沒有動力寫下去了。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章