- 1 编写韦东山老师的嵌入式书籍的输入系统章节
- 2 应本公众号粉丝要求,准备造一台智能小车并开源分享
- 3 自己工作上项目的学习:机器视觉,图像处理,TKM32F499芯片入门等
4 PID温控套件测试(后面会分享PID算法的实战使用)
所以最近会比较忙一些,也就不会更新太频啦,但是我还是会用心分享我的所见所闻及所经历的东西,希望各位谅解!
在日常工作中,我们经常会跟各种协议打交道,最常见的就是串口协议了,接下来我们将通过几个案例来实现串口解析命令,以下案例基于STM32L431RCT6
小熊派开发板。
案例一
实现需求:
协议制定:
指令(字符串) | 含义 |
led_on | 打开灯 |
led_off | 关闭灯 |
motor_on | 打开电机 |
motor_off | 关闭电机 |
1、硬件配置
1、STM32CubeMX软件配置
1.1、RCC配置
1.2、串口配置
1.3、LED和电机配置
1.4、工程生成设置
2、软件核心功能实现
main.c
typedef void (*cmd_func)(void); typedef struct __CMD_PARSE { const char *cmd_type; cmd_func fun_ptr; } CMD_PARSE; /*命令表定义===>表驱动法*/ 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 */ /*如果串口数据接收完成了,则进行处理*/ 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("当前指令列表无该指令\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 /*串口接收结构体*/ typedef struct __CMD { /*接收计数*/ int rx_count ; /*接收单个字符*/ uint8_t Res ; /*是否已经接收完成?*/ uint8_t BufferReady : 1 ; /*数据缓存区*/ uint8_t cmd_buffer[CMD_STR_SIZE] ; /*数据备份缓存区*/ 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,则上一个接收的数据为'\r'结束 if('\r' == cmd_parse_typedef.cmd_buffer_temp[cmd_parse_typedef.rx_count - 1]) { //添加结束符 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) { /*如果发生了串口溢出,则清溢出标志后再次开启终端接收*/ 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); } }
在案例一
中,软件逻辑实现较为简单,即通过串口中断接收协议数据,然后通过遍历调用对应的函数执行相应的逻辑,但是如果想把项目做得更具可复用性的话,我们还需要改改代码,让它更低耦合。
执行效果:
案例二
基于案例一
,我们对代码做一些升华,修改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]) //初始化命令,这里注册了一个默认的list_cmd命令,用于查询当前结构体中所有注册的指令 void Cmd_init(void) { Register_Cmd("list_cmd", "list all cmd info", list_cmd_callback); } //注册命令 /* cmd:指令 cmd_help:指令功能描述 ptr:该指令对应的执行函数 */ 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); //开机上电即执行list_cmd,打印当前已经注册的所有指令 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("当前指令列表无该指令\n"); else printf("当前输入:%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
里的数据,而是通过一个函数,调用一次则自动往后增加一个指令,这样用起来显然会比较舒服一些,我自己项目也经常会这么用,在不考虑代码执行性能的条件下相当灵活。
运行结果:
案例三
一个超牛逼的命令解析器:cmd-parser
由物联网大佬杰杰所造,他也是我们开源以及嵌入式社区的朋友,不得不说这个解析器做得真香!
以下是他的Github,有兴趣的朋友也可以关注一下,杰杰在开源软件方面在同龄人里做得东西都相当出色,大家要多多向他学习!
Github仓库地址
https://github.com/jiejieTop/cmd-parser
解析器功能
简单来说,我希望我的开发板,可以通过命令执行一些处理,比如说我用串口发一个命令A,开发板就执行A的一些处理,或者,在调试某些AT模组的时候,当我收到模组返回的一些指令后,自动执行一些处理。当然,还有其他的地方可以用得上的,兄弟们自行挖掘!!
解析器特色
- 用户无需关心命令的存储区域与大小,由编译器静态分配。
- 加入哈希算法超快速匹配命令,时间复杂度从O(n*m)变为O(n)。
- 命令支持忽略大小写。
- 非常易用与非常简洁的代码(不足150行)。
使用方法
1、注册命令 在工程中的任意位置均可调用(在函数外)
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 */ }
有木有很轻量?看起来简直舒服爆啦!
执行结果:
当然,除了杰杰开源的cmd-parser,还有很多优秀的指令解析器,比如RT-Thread的finsh,还有比如世伟兄之前发的一期项目源码分析的letter-shell,原理都差不多:
第2期 | letter-shell,一个功能强大的嵌入式shell
这些都是非常优秀的作品,大家都可以学习使用下,有那么好用的轮子为啥不用?所以咱们在工作中要避免重复造轮子,这样才能提高工作效率,做出漂亮的产品!
项目下载
公众号后台回复:命令 即可获取这几个案例的下载链接。