在此,首先感謝CSDN的無痕幽雨,他的博客給了我很大的啟發,貼上他博客的網址:https://blog.csdn.net/wuhenyouyuyouyu/article/details/52585835
我的學習總是斷斷續續的,學了半年STM32后又轉去做FPGA,學了一年FPGA后又回來用STM32,以前對單片機的概念是用來做些簡單的事情,最重要的是能夠配置好寄存器驅動外設,但是現在拿起來做機械臂的控制時,我立馬懵掉了,機械臂這么多個姿態,要想自動抓取那么動作必然是跟隨一定流程的,特殊的動作,如果簡單的驅動外設很明顯無法完成這個艱巨的任務,除此以外,要想接收PYNQ串口發來的指令,這些指令能夠告訴我的STM32什么時候開始抓,目標轉向角是多少,目標位置是多少,等等。我們以前做儀器常用的是SPI,通過先寫地址再寫數據的方式非常容易修改寄存器變量,可是串口,就只能自己制造協議了,根據隊友的提醒以及做FPGA的知識,我采用了狀態機的寫法(C語言版),最終實現了這個自定義的串口協議,並且讓自己的C水平提高了一些。
廢話不多說了,本次要寫的代碼流程圖如下所示:
當然了,這個是我比賽串口的流程圖,最終我整理出來的代碼是重新寫的,因此賦值有所不同。
對於嵌入式來說,外設的驅動是基本功,在這里我驅動了UART1來編寫我的協議,並驅動了RGB燈進行驗證, 在這里直接貼出我的驅動代碼:
<LED.h>
#ifndef INC_LED_H_ #define INC_LED_H_ #include "stm32f10x.h" #include "stm32f10x_conf.h" #define LED_Pin_Port GPIOB #define LED_Green_Pin GPIO_Pin_0 #define LED_Blue_Pin GPIO_Pin_1 #define LED_Red_Pin GPIO_Pin_5 #define LED_Green_ON GPIO_ResetBits(LED_Pin_Port,LED_Green_Pin) #define LED_Green_OFF GPIO_SetBits(LED_Pin_Port,LED_Green_Pin) #define LED_Blue_ON GPIO_ResetBits(LED_Pin_Port,LED_Blue_Pin) #define LED_Blue_OFF GPIO_SetBits(LED_Pin_Port,LED_Blue_Pin) #define LED_Red_ON GPIO_ResetBits(LED_Pin_Port,LED_Red_Pin) #define LED_Red_OFF GPIO_SetBits(LED_Pin_Port,LED_Red_Pin) void LED_Init(void); #endif /* INC_LED_H_ */
<LED.c>
#include "LED.h" void LED_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE); GPIO_InitStructure.GPIO_Pin = LED_Green_Pin; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(LED_Pin_Port,&GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = LED_Blue_Pin; GPIO_Init(LED_Pin_Port,&GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = LED_Red_Pin; GPIO_Init(LED_Pin_Port,&GPIO_InitStructure); }
<uart.h>
#ifndef INC_UART_H_ #define INC_UART_H_ #include "stm32f10x.h" #include "stm32f10x_conf.h" void Uart1_Init(void); void Uart1_SendByte(u8 Data); u8 Uart1_ReceiveByte(void); void Uart1_SendString(char *str); #endif /* INC_UART_H_ */
<uart.c>
#include "uart.h" void Uart1_Init(void) { GPIO_InitTypeDef Uart_GPIO_InitStructure; USART_InitTypeDef Uart1_InitStructure; NVIC_InitTypeDef NVIC_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA,ENABLE); Uart_GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; Uart_GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; Uart_GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA,&Uart_GPIO_InitStructure); Uart_GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; Uart_GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; Uart_GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA,&Uart_GPIO_InitStructure); Uart1_InitStructure.USART_BaudRate = 115200; Uart1_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; Uart1_InitStructure.USART_Mode = USART_Mode_Rx|USART_Mode_Tx; Uart1_InitStructure.USART_Parity = USART_Parity_No; Uart1_InitStructure.USART_StopBits = USART_StopBits_1; Uart1_InitStructure.USART_WordLength = USART_WordLength_8b; USART_Init(USART1,&Uart1_InitStructure); NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4); NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; NVIC_Init(&NVIC_InitStructure); USART_ITConfig(USART1,USART_IT_RXNE,ENABLE); USART_Cmd(USART1,ENABLE); } void Uart1_SendByte(u8 Data) { USART_SendData(USART1,Data); while(USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET); } void Uart1_SendString(char *str) { while((*str)!='\0') { Uart1_SendByte(*str); str++; } while(USART_GetFlagStatus(USART1,USART_FLAG_TC) == RESET); } u8 Uart1_ReceiveByte(void) { u8 Receive_Data; Receive_Data = USART_ReceiveData(USART1); // while(USART_GetFlagStatus(USART1,USART_FLAG_RXNE) == RESET); return Receive_Data; }
到了這里,就能夠驅動好我的串口1和LED燈了,接下來就是重點所在FSM
盜個圖,這個就是時序邏輯電路的經典結構框圖,因此使用Verilog描述狀態機的時候,我都喜歡使用三段式狀態機,因為它有這個味道,很好地反映了時序邏輯電路的結構,而一段式狀態機如果不是Verilog的並行態,它早沒了,而到了C語言,emm,本來就是順序執行的,就會有一些問題,比如這樣:
if(state == Idle) { ........ } else if(state == State1) { ........ } else if(state == State2) { ........ } else { ......... }
眾所周知,C語言的if else if語句是從第一條開始判斷的,如果符合條件的那一行永遠在后面幾行,那么就要每次多執行很多次的if .....而if是判斷語句,括號內是要執行運算的,即使是單周期指令的MCU,在進行乘除運算等都需要消耗多個時鍾周期,因此,每次多執行1次判斷至少浪費一個時鍾周期甚至更多,因此,這樣子的狀態機無疑是效率低下的,恐怖的是,網上一大堆所謂的C語言狀態機都是采用這種做法,讓我一開始也蒙圈了很久。
后來我還看到一個貼子,qiuri2008的https://www.cnblogs.com/jiangzhaowei/p/9129024.html,他的FSM在原來的基礎上做了一點改進,使用了結構體封裝每一個狀態的信息,並且使用了函數指針的方式進行構造狀態機的核,我在模仿它的代碼的時候感覺到了進步但是還是很疑惑,這樣和我上述所說的有區別嗎?
盜用一下他的代碼:
這一段相當於定義了一個數據控制塊,類似於OS中的TCB
而這里則利用數組構成了一個數據控制塊的表,記錄着狀態機的信息
好的,這個就是他的核心代碼啦,還是很值得學習的,這個是狀態機的思想,用一個表來存儲狀態信息,並通過組合邏輯來判斷狀態是否跳轉,通過函數指針來執行函數,就可以在不同的狀態下執行不同的函數了。可是我模仿它的代碼寫的時候,卻發現了一樣致命的事情——Event!
從表中可以看到,當前狀態相同而下一狀態不同的EVENT是不一樣的值,既然如此,根據狀態機的定義,要判斷EVENT是什么就需要這樣寫:
void EventJudge(u8 ReceiveData) { switch(CurState) { case Idle: if(ReceiveData == '1') ..... else ...... case State1: if(ReceiveData == '2')) ....... default: ...... } }
那么這樣子寫的意義又何在呢?加上之前的For查找Event並且比對,其實這樣子寫效率什么還沒有直接的if else if高呢
而CSDN的無痕幽雨卻這樣構造狀態機核:
typedef State(*Procedure)(void*);
Procedure Steps[] = {Idle_Fun , State1_Fun.......};
typedef struct{
u8 Target1;
u8 Target2;
}SM_Arg_T
void BestStateMachine(void * invar) { static State NS = s_init; //定義下一狀態 NS = Steps[NS](invar); }
這里采用函數內static,靜態局部變量,這樣做可以讓函數每一次重新進入該函數的時候使用上一次修改的值,類似於全局變量,有利於函數模塊化。而使用函數指針數組則可以直接通過索引找到對應函數,因此,若我們每一個狀態的函數返回一個狀態參量,那么就可以直接索引至對應的函數指針,這樣的查找表方式,相較於for循環查找表可謂快了很多很多倍,因此,每一個狀態函數都應該這樣寫
State Uart_Idle_Fun(void *arg) { Uart_SM_Arg_t* Uart_SM_Arg = (Uart_SM_Arg_t*)arg; if(Uart_SM_Arg->ReceiveData == 0xAA) { return Type_Judge; } else { return Uart_Idle; } }
注意這里的入口參數,采用指針當入口參數,這樣子函數內就可以很方便的修改以及讀取參數,並且不用占用大量的棧空間,FreeRTOS的郵箱也是一樣的道理,使用了消息隊列,也是傳的指針,而不是傳一大塊數據
最后,貼下自己的FSM代碼實現吧
<UART1_FSM.h>
#ifndef INC_UART1_FSM_H_ #define INC_UART1_FSM_H_ #define Instruction_Type 1 #define Data_Type 2 #include "uart.h" typedef u8 State; enum{ Uart_Idle, Type_Judge, InstructionAssignment, DataAttributionJudge, DataAssignment }; typedef State(*Uart_Procedure)(void*); typedef struct{ u8 ReceiveData; u8 Type; u8 Instruction; u8 DataAttribution; u8 Data; }Uart_SM_Arg_t; State Uart_Idle_Fun(void *arg); State Uart_Type_Judge_Fun(void* arg); State Uart_InstructionAssignment_Fun(void* arg); State Uart_DataAttributionJudge_Fun(void* arg); State Uart_DataAssignment_Fun(void* arg); void UartStateMachine(Uart_SM_Arg_t *arg); extern Uart_SM_Arg_t Uart1_SM_Arg; extern u8 Target1,Target2,Target3; #endif /* INC_UART1_FSM_H_ */
<UART1_FSM.c>
#include "UART1_FSM.h" u8 Target1,Target2,Target3; Uart_Procedure Uart_Steps[] = { Uart_Idle_Fun, Uart_Type_Judge_Fun, Uart_InstructionAssignment_Fun, Uart_DataAttributionJudge_Fun, Uart_DataAssignment_Fun}; Uart_SM_Arg_t Uart1_SM_Arg ={ .Data = 0, .DataAttribution = 0, .Instruction = 0, .ReceiveData = 0, .Type = 0 }; State Uart_Idle_Fun(void *arg) { Uart_SM_Arg_t* Uart_SM_Arg = (Uart_SM_Arg_t*)arg; if(Uart_SM_Arg->ReceiveData == 0xAA) { return Type_Judge; } else { return Uart_Idle; } } State Uart_Type_Judge_Fun(void* arg) { Uart_SM_Arg_t* Uart_SM_Arg = (Uart_SM_Arg_t*)arg; if(Uart_SM_Arg->ReceiveData == 0x0A) { Uart_SM_Arg->Type = Instruction_Type; return InstructionAssignment; } else if(Uart_SM_Arg->ReceiveData == 0x0F) { Uart_SM_Arg->Type = Data_Type; return DataAttributionJudge; } else { return Uart_Idle; } } State Uart_InstructionAssignment_Fun(void* arg) { Uart_SM_Arg_t* Uart_SM_Arg = (Uart_SM_Arg_t*)arg; if(Uart_SM_Arg->Type == Instruction_Type) { Uart_SM_Arg->Instruction = Uart_SM_Arg->ReceiveData; return Uart_Idle; } else { return Uart_Idle; } } State Uart_DataAttributionJudge_Fun(void* arg) { Uart_SM_Arg_t* Uart_SM_Arg = (Uart_SM_Arg_t*)arg; if(Uart_SM_Arg->Type == Data_Type) { Uart_SM_Arg->DataAttribution = Uart_SM_Arg->ReceiveData; return DataAssignment; } else { return Uart_Idle; } } State Uart_DataAssignment_Fun(void* arg) { Uart_SM_Arg_t* Uart_SM_Arg = (Uart_SM_Arg_t*)arg; if(Uart_SM_Arg->DataAttribution == 0xA1) { Uart_SM_Arg->Data = Uart_SM_Arg->ReceiveData; // Uart_SM_Arg->MachineBi.RealSense_Target.RealSense_TargetBeta = Uart_SM_Arg->Data; Target1 = Uart_SM_Arg->Data; return Uart_Idle; } else if(Uart_SM_Arg->DataAttribution == 0xA2) { Uart_SM_Arg->Data = Uart_SM_Arg->ReceiveData; // Uart_SM_Arg->MachineBi.RealSense_Target.RealSense_TargetX = Uart_SM_Arg->Data; Target2 = Uart_SM_Arg->Data; return Uart_Idle; } else if(Uart_SM_Arg->DataAttribution == 0xA3) { Uart_SM_Arg->Data = Uart_SM_Arg->ReceiveData; // Uart_SM_Arg->MachineBi.RealSense_Target.RealSense_TargetY = Uart_SM_Arg->Data; Target3 = Uart_SM_Arg->Data; return Uart_Idle; } else { return Uart_Idle; } } void UartStateMachine(Uart_SM_Arg_t *arg) { static State Next_State = Uart_Idle; Next_State = Uart_Steps[Next_State](arg); }
驗證代碼
int main(void) { Uart1_Init(); LED_Init(); /* TODO - Add your application code here */ /* Infinite loop */ while (1) { if(Target1 == 0xA0) { LED_Red_ON; LED_Green_OFF; LED_Blue_OFF; } else if(Target2 == 0x0F) { LED_Red_OFF; LED_Green_ON; LED_Blue_OFF; } else { LED_Red_OFF; LED_Green_OFF; LED_Blue_ON; } } }
初始狀態:
串口調試助手輸入:
實驗現象:
大功告成!