最近更文頻率少了,但依然會保持一周一篇原創(chuàng)文章,主要是在做這幾個活:
-
1 編寫韋東山老師的嵌入式書籍的輸入系統(tǒng)章節(jié)
-
2 應(yīng)本公眾號粉絲要求,準(zhǔn)備造一臺智能小車并開源分享
-
3 自己工作上項(xiàng)目的學(xué)習(xí):機(jī)器視覺,圖像處理,TKM32F499芯片入門等
-
4 PID溫控套件測試(后面會分享PID算法的實(shí)戰(zhàn)使用)
所以最近會比較忙一些,也就不會更新太頻啦,但是我還是會用心分享我的所見所聞及所經(jīng)歷的東西,希望各位諒解!
在日常工作中,我們經(jīng)常會跟各種協(xié)議打交道,最常見的就是串口協(xié)議了,接下來我們將通過幾個案例來實(shí)現(xiàn)串口解析命令,以下案例基于STM32L431RCT6
小熊派開發(fā)板。
案例一
實(shí)現(xiàn)需求:
協(xié)議制定:
指令(字符串) | 含義 |
---|---|
led_on | 打開燈 |
led_off | 關(guān)閉燈 |
motor_on | 打開電機(jī) |
motor_off | 關(guān)閉電機(jī) |
1、硬件配置
1、STM32CubeMX軟件配置
1.1、RCC配置
1.2、串口配置
1.3、LED和電機(jī)配置
1.4、工程生成設(shè)置
2、軟件核心功能實(shí)現(xiàn)
main.c
typedef void (*cmd_func)(void);
typedef struct __CMD_PARSE
{
const char *cmd_type;
cmd_func fun_ptr;
} CMD_PARSE;
/*命令表定義===>表驅(qū)動法*/
CMD_PARSE CMD_TABLE[CMD_SIZE] =
{
{"led_on", led_on_process},
{"led_off", led_off_process},
{"motor_on", motor_on_process},
{"motor_off", motor_off_process},
};
/*命令匹配*/
int Command_Matching(char *cmd_type)
{
uint8_t cmd_at = 0 ;
uint8_t cmd_match_flag = 0 ;
uint8_t type_num = sizeof(CMD_TABLE) / sizeof(CMD_PARSE);
for(cmd_at = 0 ; cmd_at < type_num ; cmd_at++)
{
if(0 == strcmp(CMD_TABLE[cmd_at].cmd_type, cmd_type))
{
cmd_match_flag = 1 ;
break ;
}
}
if(1 == cmd_match_flag)
{
cmd_match_flag = 0 ;
CMD_TABLE[cmd_at].fun_ptr();
}
else
return -1 ;
return 0 ;
}
int main(void)
{
/* USER CODE BEGIN 1 */
uint8_t cmd_at = 0 ;
int find_cmd_Index = 0;
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_USART1_UART_Init();
/* USER CODE BEGIN 2 */
printf("命令解析器\n");
/*使能串口接收*/
HAL_UART_Receive_IT(&huart1, (uint8_t *)&cmd_parse_typedef.Res, 1);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
/*如果串口數(shù)據(jù)接收完成了,則進(jìn)行處理*/
if(1 == cmd_parse_typedef.BufferReady)
{
for(cmd_at = 0 ; cmd_at < NR(CMD_TABLE) ; cmd_at++)
{
if(strcmp((char *)cmd_parse_typedef.cmd_buffer, CMD_TABLE[cmd_at].cmd_type) == 0)
{
find_cmd_Index = cmd_at ;
break ;
}
else
find_cmd_Index = -1 ;
}
if(-1 == find_cmd_Index)
printf("當(dāng)前指令列表無該指令\n");
else
printf("接收到指令%d:%s\n", find_cmd_Index, cmd_parse_typedef.cmd_buffer);
Command_Matching((char *)cmd_parse_typedef.cmd_buffer);
memset(&cmd_parse_typedef, 0, sizeof(cmd_parse_typedef));
}
}
/* USER CODE END 3 */
}
stm32l4xx_it.h
#include <stdint.h>
#define CMD_STR_SIZE 30
/*串口接收結(jié)構(gòu)體*/
typedef struct __CMD
{
/*接收計(jì)數(shù)*/
int rx_count ;
/*接收單個字符*/
uint8_t Res ;
/*是否已經(jīng)接收完成?*/
uint8_t BufferReady : 1 ;
/*數(shù)據(jù)緩存區(qū)*/
uint8_t cmd_buffer[CMD_STR_SIZE] ;
/*數(shù)據(jù)備份緩存區(qū)*/
uint8_t cmd_buffer_temp[CMD_STR_SIZE] ;
}CMDSTR_PARSE ;
extern CMDSTR_PARSE cmd_parse_typedef ;
stm32l4xx_it.c
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *UartHandle)
{
if(UartHandle->Instance == USART1)
{
HAL_UART_Receive_IT(&huart1, (uint8_t *)&cmd_parse_typedef.Res, 1);
if('\n' != cmd_parse_typedef.Res)
{
if(cmd_parse_typedef.rx_count < CMD_STR_SIZE - 1)
cmd_parse_typedef.cmd_buffer_temp[cmd_parse_typedef.rx_count++] = cmd_parse_typedef.Res ;
else
cmd_parse_typedef.rx_count = 0 ;
}
else
{
//如果接收的是\n,則上一個接收的數(shù)據(jù)為'\r'結(jié)束
if('\r' == cmd_parse_typedef.cmd_buffer_temp[cmd_parse_typedef.rx_count - 1])
{
//添加結(jié)束符
cmd_parse_typedef.cmd_buffer_temp[cmd_parse_typedef.rx_count - 1] = 0x00 ;
memcpy(cmd_parse_typedef.cmd_buffer, cmd_parse_typedef.cmd_buffer_temp, CMD_STR_SIZE);
cmd_parse_typedef.BufferReady = 1;
}
}
}
}
void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart)
{
/*如果發(fā)生了串口溢出,則清溢出標(biāo)志后再次開啟終端接收*/
if(__HAL_UART_GET_FLAG(huart, UART_FLAG_ORE) != RESET)
{
memset(&cmd_parse_typedef, 0, sizeof(cmd_parse_typedef));
__HAL_UART_CLEAR_OREFLAG(huart);
HAL_UART_Receive_IT(&huart1, (uint8_t *)&cmd_parse_typedef.Res, 1);
}
}
在案例一
中,軟件邏輯實(shí)現(xiàn)較為簡單,即通過串口中斷接收協(xié)議數(shù)據(jù),然后通過遍歷調(diào)用對應(yīng)的函數(shù)執(zhí)行相應(yīng)的邏輯,但是如果想把項(xiàng)目做得更具可復(fù)用性的話,我們還需要改改代碼,讓它更低耦合。
執(zhí)行效果:
案例二
基于案例一
,我們對代碼做一些升華,修改main.c部分如下:
main.c
typedef void (*cmd_func)(void);
typedef struct __CMD_PARSE
{
const char *cmd_type;
const char *cmd_help;
cmd_func fun_ptr;
} CMD_PARSE;
CMD_PARSE CMD_TABLE[CMD_SIZE];
#define NR(array) sizeof(array) / sizeof(array[0])
//初始化命令,這里注冊了一個默認(rèn)的list_cmd命令,用于查詢當(dāng)前結(jié)構(gòu)體中所有注冊的指令
void Cmd_init(void)
{
Register_Cmd("list_cmd", "list all cmd info", list_cmd_callback);
}
//注冊命令
/*
cmd:指令
cmd_help:指令功能描述
ptr:該指令對應(yīng)的執(zhí)行函數(shù)
*/
int Register_Cmd(char *cmd, char *cmd_help, cmd_func ptr)
{
static uint8_t cmd_index = 0 ;
if(cmd_index > CMD_SIZE - 1)
{
printf("注冊命令失敗,表越界\n");
return -1 ;
}
CMD_TABLE[cmd_index].cmd_type = cmd ;
CMD_TABLE[cmd_index].cmd_help = cmd_help ;
CMD_TABLE[cmd_index].fun_ptr = ptr ;
cmd_index++ ;
return cmd_index ;
}
/*命令匹配*/
int Command_Matching(char *cmd_type)
{
uint8_t cmd_at = 0 ;
uint8_t cmd_match_flag = 0 ;
uint8_t type_num = NR(CMD_TABLE);
for(cmd_at = 0 ; cmd_at < type_num ; cmd_at++)
{
if(0 == strcmp(CMD_TABLE[cmd_at].cmd_type, cmd_type))
{
cmd_match_flag = 1 ;
break ;
}
}
if(1 == cmd_match_flag)
{
cmd_match_flag = 0 ;
CMD_TABLE[cmd_at].fun_ptr();
}
else
return -1 ;
return 0 ;
}
int main(void)
{
/* USER CODE BEGIN 1 */
uint8_t cmd_at = 0 ;
int find_cmd_Index = 0;
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_USART1_UART_Init();
/* USER CODE BEGIN 2 */
//開啟串口接收中斷
HAL_UART_Receive_IT(&huart1, (uint8_t *)&cmd_parse_typedef.Res, 1);
//初始化list_cmd
Cmd_init();
//注冊指令
Register_Cmd("led_on", "Open BearPi IA1 LED", led_on_process);
Register_Cmd("led_off", "Close BearPi IA1 LED", led_off_process);
Register_Cmd("motor_on", "Open BearPi IA1 MOTOR", motor_on_process);
Register_Cmd("motor_off", "Close BearPi IA1 MOTOR", motor_off_process);
//開機(jī)上電即執(zhí)行l(wèi)ist_cmd,打印當(dāng)前已經(jīng)注冊的所有指令
list_cmd_callback();
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
if(1 == cmd_parse_typedef.BufferReady)
{
for(cmd_at = 0 ; cmd_at < NR(CMD_TABLE) ; cmd_at++)
{
if(strcmp((char *)cmd_parse_typedef.cmd_buffer, CMD_TABLE[cmd_at].cmd_type) == 0)
{
find_cmd_Index = cmd_at ;
break ;
}
else
find_cmd_Index = -1 ;
}
if(-1 == find_cmd_Index)
printf("當(dāng)前指令列表無該指令\n");
else
printf("當(dāng)前輸入:%s指令\n", cmd_parse_typedef.cmd_buffer);
Command_Matching((char *)cmd_parse_typedef.cmd_buffer);
memset(&cmd_parse_typedef, 0, sizeof(cmd_parse_typedef));
}
}
/* USER CODE END 3 */
}
針對案例一
的修改以后,不用再直接修改命令表CMD_TABLE
里的數(shù)據(jù),而是通過一個函數(shù),調(diào)用一次則自動往后增加一個指令,這樣用起來顯然會比較舒服一些,我自己項(xiàng)目也經(jīng)常會這么用,在不考慮代碼執(zhí)行性能的條件下相當(dāng)靈活。
運(yùn)行結(jié)果:
案例三
一個超牛逼的命令解析器:cmd-parser
由物聯(lián)網(wǎng)大佬杰杰所造,他也是我們開源以及嵌入式社區(qū)的朋友,不得不說這個解析器做得真香!
以下是他的Github,有興趣的朋友也可以關(guān)注一下,杰杰在開源軟件方面在同齡人里做得東西都相當(dāng)出色,大家要多多向他學(xué)習(xí)!
Github倉庫地址
https://github.com/jiejieTop/cmd-parser
解析器功能
簡單來說,我希望我的開發(fā)板,可以通過命令執(zhí)行一些處理,比如說我用串口發(fā)一個命令A(yù),開發(fā)板就執(zhí)行A的一些處理,或者,在調(diào)試某些AT模組的時候,當(dāng)我收到模組返回的一些指令后,自動執(zhí)行一些處理。當(dāng)然,還有其他的地方可以用得上的,兄弟們自行挖掘?。?/p>
解析器特色
-
用戶無需關(guān)心命令的存儲區(qū)域與大小,由編譯器靜態(tài)分配。 -
加入哈希算法超快速匹配命令,時間復(fù)雜度從O(n*m)變?yōu)镺(n)。 -
命令支持忽略大小寫。 -
非常易用與非常簡潔的代碼(不足150行)。
使用方法
1、注冊命令 在工程中的任意位置均可調(diào)用(在函數(shù)外)
REGISTER_CMD(test1, test1_cmd);
2、cmd初始化
cmd_init();
3、解析命令
cmd_parsing("test1");
目前本代碼只支持MDK與IAR的編譯器,對于GCC還沒有移植,不過我想要移植也不困難!歡迎大家一起提交pr!
1、在小熊派上使用cmd-parser
1.1 添加頭文件及路徑到Keil MDK
1.2、編寫源代碼
這里還是一樣,借用案例一
的工程,對main.c做下改造。
main.c
#include "cmd.h"
void led_on_process(void);
void led_off_process(void);
void motor_on_process(void);
void motor_off_process(void);
/*注冊命令*/
REGISTER_CMD(led_on, led_on_process);
REGISTER_CMD(led_off, led_off_process);
REGISTER_CMD(motor_on, motor_on_process);
REGISTER_CMD(motor_off, motor_off_process);
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_USART1_UART_Init();
/* USER CODE BEGIN 2 */
/*命令初始化*/
cmd_init();
HAL_UART_Receive_IT(&huart1, (uint8_t *)&cmd_parse_typedef.Res, 1);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
if(1 == cmd_parse_typedef.BufferReady)
{
printf("接收到指令:%s\n", cmd_parse_typedef.cmd_buffer);
cmd_parsing((char *)cmd_parse_typedef.cmd_buffer);
memset(&cmd_parse_typedef, 0, sizeof(cmd_parse_typedef));
}
}
/* USER CODE END 3 */
}
有木有很輕量?看起來簡直舒服爆啦!
執(zhí)行結(jié)果:
當(dāng)然,除了杰杰開源的cmd-parser,還有很多優(yōu)秀的指令解析器,比如RT-Thread的finsh,還有比如世偉兄之前發(fā)的一期項(xiàng)目源碼分析的letter-shell,原理都差不多:
第2期 | letter-shell,一個功能強(qiáng)大的嵌入式shell
這些都是非常優(yōu)秀的作品,大家都可以學(xué)習(xí)使用下,有那么好用的輪子為啥不用?所以咱們在工作中要避免重復(fù)造輪子,這樣才能提高工作效率,做出漂亮的產(chǎn)品!
項(xiàng)目下載
公眾號后臺回復(fù):命令 即可獲取這幾個案例的下載鏈接。
往期精彩
華為LiteOS智慧路燈項(xiàng)目案例學(xué)習(xí)筆記(一)
最近收集的開源項(xiàng)目專欄(持續(xù)更新,收好車輪,方便造車)
推薦三個我工作中經(jīng)常使用的驅(qū)動大全wiki(建議收藏并轉(zhuǎn)發(fā)讓更多人知道!)
覺得本次分享的文章對您有幫助,隨手點(diǎn)[在看]
并轉(zhuǎn)發(fā)分享,也是對我的支持。
免責(zé)聲明:本文內(nèi)容由21ic獲得授權(quán)后發(fā)布,版權(quán)歸原作者所有,本平臺僅提供信息存儲服務(wù)。文章僅代表作者個人觀點(diǎn),不代表本平臺立場,如有問題,請聯(lián)系我們,謝謝!