|
1. 前言 玩过Linux的朋友, 是不是对Linux无所不能的串口Shell命令控制台羡慕不已, 要是自己做的STM32F系列低档次的MCU也有这种控制交互能力, 会给调试/维护和配置省下多少麻烦事呀, 比如启动/关闭调试或自检模式, 打印调试信息, 配置系统参数, 传输文件等等, 也有相当多的朋友凭借自己出色的编程能力可以实现这些功能, 这里提出我的这个解决方案, 以作交流. 本平台(xc_shell)具备以下性能特点: 大量主要代码, 和具体硬件无关, 移植性强,代码文件少. 只有在处理用户的输入命令时, 才占用CPU资源, 且代码可裁剪到1KB SRAM和4KB Flash; 用户可以非常灵活的添加按模板编写的命令脚本文件, 自定义扩张能力强. 支持操作系统和非操作系统两种场景应用. 支持Ymodem文件传输协议 支持将Flash的扇区开辟为参数区, 可实现本地/远程升级。 实用Led灯信号管理, 可将65535虚拟信号灯选择输出到1个实体LED灯上, 调试时序和状态非常有用 拥有基础的LED管理, 调试模式设置, 命令帮助指令, 复位指令等基础功能
功能越多设计会越复杂, 为了解释清楚代码, 先向大家解释一下以上功能的基础实现原理, 并提供一个最小的的源码工程。
2. xc_shell平台介绍2.1 如何实现硬件无关类比Linux会发现, 设备的硬件接口往往会被虚拟成一个文件(驱动), 而Linux内核完全与硬件系统无任何字节关联, 不同平台驱动不同而已, 故而本xc_shell的串口驱动也采用了相似的思路: 1) 串口驱动用一个结构体描述, 这样只需在xc_shell.c中用指针指向这个TTYx_HANDLE结构体对象就可以将串口(tty)硬件与内核联系在一起, 聪明的朋友可能会想到, 假如我将带网络的开发板按此结构体,虚拟一个TTY对象, 岂不是就可以实现一个网络远程控制台了! 这点确实是可以的! 2) 当然诸如多TTY串口实现接口互换等, 都是一个指针和step2中的注入回调处理交换的问题。 3)用户在使用api_TxdFrame或api_TxdByte时”bsp_ttyX.c“,会驱动具体MCU的串口将数据发送出去, 收到一帧数据后,若用户设置了inj_RcvFrame回调处理方法,则会在中断中执行用户的回调处理。 - <i>/*---------------------*
- * 指正函数定义
- *----------------------*/</i>
- typedef void (*pvFunDummy)(void);
-
- <i>//输入整行,输出逻辑</i>
- typedef void (*pvFunVoid) (void);
- typedef void (*pvFunBool) (bool bVal);
- typedef void (*pvFunChar) (uint8_t cVal);
- typedef void (*pvFunShort)(uint16_t sVal);
- typedef void (*pvFunWord) (uint32_t wVal);
-
- <i>//输入整行,输出逻辑</i>
- typedef bool (*pbFunVoid) (void);
- typedef bool (*pbFunBool) (bool bVal);
- typedef bool (*pbFunChar) (uint8_t cVal);
- typedef bool (*pbFunShort)(uint16_t sVal);
- typedef bool (*pbFunWord) (uint32_t wVal);
-
- <i>//输入整形指针,输出逻辑</i>
- typedef bool (*pbFun_pVoid) (void * pVoid);
- typedef bool (*pbFun_pChar) (uint8_t * pStr);
- typedef bool (*pbFun_pShort)(uint16_t * pShor);
- typedef bool (*pbFun_pWord) (uint32_t * pWord);
-
- <i>//输入数据帧,输出逻辑</i>
- typedef bool (*pbFun_Buffx)(void * pcBuff, uint16_t len );
- typedef bool (*pbFun_Bytex)(uint8_t * pcByte, uint16_t len );
- <i>/*---------------------*
- * TTYx 句柄结构
- *----------------------*/</i>
- typedef struct TTYx_HANDLE_STRUCT
- {
- const char * const name; <i>//驱动器名</i>
- const uint16_t rxSize; <i>//接收大小</i>
- const uint16_t txSize; <i>//发送大小</i>
-
- <i>//------------------------------------------------------</i>
- <i>//step1: 用户可用API</i>
- const pvFunWord init; <i>//初始化.</i>
- const pbFun_Bytex api_TxdFrame; <i>//发送数据帧. (发送帧)</i>
- const pbFunChar api_TxdByte; <i>//发送数据字节</i>
-
- <i>//------------------------------------------------------</i>
- <i>//step2: 注入回调函数</i>
- pbFun_Bytex inj_RcvFrame; <i>//(ISR)接收数据帧. (接收帧)</i>
- pvFunDummy inj_TxdReady; <i>//(ISR)发送完毕回调</i>
-
- <i>//------------------------------------------------------</i>
- <i>//step3: 接收回调函数</i>
- struct TTYx_HANDLE_STRUCT * pvNext; <i>//连接到下一个指令 </i>
- }TTYx_HANDLE;
复制代码 可注入的命令脚本(CLI)实现
- <i>/*---------------------*
- * CLI指令
- *----------------------*/</i>
- typedef struct
- {
- const char * const pcCmdStr; <i>//指令字符串(只能为小写字母)</i>
- const char * const pcHelpStr; <i>//指令描述,必须以:"\r\n结束". 比如:"help: Returns a list\r\n".</i>
- const pFunHook pxCmdHook; <i>//指向回调函数的指针,处理成功返回真否者返回0;</i>
- uint8_t ucExpParam; <i>//指令期望的参数个数</i>
- const MEDIA_HANDLE *phStorage; <i>//指向存储介质,没有的话填充NULL </i>
- }Cmd_Typedef_t;
复制代码
各位朋友可能会使用到非常多的自定义CLI命令, 格式诸如这个网卡的命令: - const Cmd_Typedef_t CLI_WizMsg=
- {
- <i>//识别关键字</i>
- .pcCmdStr = "wiz",
- <i>//帮助内容</i>
- .pcHelpStr =
- "[WIZ contorls]\r\n"
- " wiz help\r\n"
- " wiz rd info\r\n"
- " wiz reset\r\n"
- " wiz wr ip <D0>.<D1>.<D2>.<D3>\r\n"
- " wiz wr mask <D0>.<D1>.<D2>.<D3>\r\n"
- " wiz wr way <D0>.<D1>.<D2>.<D3>\r\n"
- " wiz wr mac <H0>-<H1>-<H2>-<H3>-<H4>-<H5>\r\n"
- " wiz wr port <udp> <bak> <vol> <pic>\r\n"
- " wiz wr sip <D0>.<D1>.<D2>.<D3> <port>\r\n"
- " wiz wr cip <D0>.<D1>.<D2>.<D3> <port>\r\n"
- " wiz load default\r\n"
- "[WIZ Test mode]\r\n"
- " wiz loop open\r\n"
- " wiz loop close\r\n"
- "\r\n",
-
- <i>//处理函数</i>
- .pxCmdHook = &Shell_WIZ_Service, <i>//见实体函数</i>
-
- <i>//附带数据</i>
- .ucExpParam = 0,
-
- #ifdef SHELL_USE_YMODEM
- <i>//存储介质</i>
- .phStorage = NULL,
- #endif
- };
- <i>/*---------------------*
- * CLI 链表节
- *----------------------*/</i>
- Cmd_List_t WizList = { &CLI_WizMsg, NULL,}; <i>//Shell指令的头</i>
复制代码
如配置IP地址输入“wiz wr ip 192.168.1.250\r\n”则可以了 3. 环境准备
3.1 硬件开发环境- STM32F103系列开发板一块, 带USART1接口。
- USB转串口线一根。
3.2 软件开发环境- MDK4.72或以上
- SecureCRT串口超级终端
3.3 软件配置在xc_shell使用“/r/n”作为命令的结束符, 而SecureCRT按下Enter不是输入“/r/n”故而需要按下图设置:此外在《终端》/仿真/高级中选取【本地回显】 4. 代码介绍
4.1 目录结构□ XC_SHELL
- ├──┬── BSP_LIB BSP库,硬件相关驱动代买
- │ ├──── bsp_ledx.c 基础LED驱动
- │ └──── bsp_tty0.c 调试串口驱动
- │
- ├──┬──MDK-ARM 工程文件
- │ └──── Project.uvproj
- │
- ├──┬──SHELL_CFG SHELL配置头文件
- │ └──── user_eval.h
- │
- ├──┬──SHELL_CORE SHELL内核文件
- │ ├──── xc_shell.c SHELL内核文件
- │ ├──── xc_ymodem.c Ymodem传输协议(默认关闭,在xc_shell.h中启动)
- │ ├──── xc_iap.c Flash的IAP操作,需要bsp_flash.c驱动支持
- │ └──── shell_iap.c shell的用户脚本模板
- │
- ├──┬──SHELL_INC SHELL头文件
- │ ├──── bsp_type.h 驱动结构定义
- │ ├──── xc_shell.h SHELL头文件
- │ └──── xconfig.h 硬件无关配置文件
- │
- ├──┬──STM32F10x_StdPeriph_Lib_V3.5.0 STM32的标准外设库
- │ └──── ......
- │
- └──┬──USER 用户文件
- ├─ .....
- └──── main.c main文件
复制代码
4.2 工程设置要点1) 设置使用微库: 2)配置包含和路径,注意使用了“--c99”标准,如图 3) 编译工程,无错误警告后烧写程序到开发板运行。 4.3 最终效果按图输入一下指令,SHELL平台会回复相关信息。其中输入“led set 0=1”会将信号1分配到物理LED0上;输入“led set 0=2”会将信号2分配到物理LED0上。这样用户编写程序代码时相当于拥有了超级多的LED信号可用,调试时序非常有用。 5. 添加自己的指令脚本5.1 源代码示例假设我要编写一个自己的指令脚本, 来读取MCU的关键信息,关键字为mcu, 文件命名为shell_mcu.c;当输入“mcu rd 0”时显示MCU的FLASH大小,输入“mcu rd 1”时读取MCU的唯一ID信息。shell_mcu.c源代码: - <i>/*********************************Copyright (c)*********************************
- **
- ** FIVE工作组
- **
- **---------------------------------File Info------------------------------------
- ** File Name: shell_mcu.c
- ** Last modified Date: 2017/9/17 15:13:57
- ** Last Version: V1.0
- ** Description: shell测试
- **
- **------------------------------------------------------------------------------
- ** Created By: wanxuncpx
- ** Created date: 2017/9/17 15:14:08
- ** Version: V1.0
- ** Descriptions: none
- **------------------------------------------------------------------------------
- ** HW_CMU: STM32F103
- ** Libraries: STM32F10x_StdPeriph_Lib_V3.5.0
- ** version V3.5
- *******************************************************************************/</i>
-
- <i>/******************************************************************************
- 更新说明:
- ******************************************************************************/</i>
-
- <i>/******************************************************************************
- ********************************* 编 译 控 制 ********************************
- ******************************************************************************/</i>
- #define MCU_SHELL //注释掉时屏蔽iap shell功能
-
- #include "xc_shell.h" //Shell支持文件,含bool,uint8_t..以及串口数据收发操作
- <i>/******************************************************************************
- ********************************* 文件引用部分 ********************************
- ******************************************************************************/</i>
- <i>/*---------------------*
- * 模块驱动引用
- *----------------------*/</i>
- <i>//#include "net_w5500.h"</i>
-
- #ifdef MCU_SHELL
- <i>/******************************************************************************
- ********************************** Shell实例 **********************************
- ******************************************************************************/</i>
- <i>/*---------------------*
- * CLI指令服务
- *----------------------*/</i>
- extern bool Shell_MCU_Service(void * pcBuff, uint16_t len );
-
- <i>/*---------------------*
- * CLI 结构
- *----------------------*/</i>
- const Cmd_Typedef_t CLI_McuMsg=
- {
- <i>//识别关键字</i>
- "mcu",
-
- <i>//帮助内容</i>
- "[mcu contorls]\r\n"
- " mcu rd <d>\t\t- Read FLASH information.\r\n"
- "\r\n",
-
- <i>//处理函数</i>
- &Shell_MCU_Service,
-
- <i>//附带数据</i>
- 0,
- #ifdef SHELL_USE_YMODEM
- <i>//存储介质</i>
- NULL,
- #endif
- };
-
- <i>/*---------------------*
- * CLI链表节(输出)
- *----------------------*/</i>
- Cmd_List_t McuList = {&CLI_McuMsg ,NULL}; <i>//IAP指令链表</i>
-
- <i>/******************************************************************************
- ********************************* 函 数 声 明 *********************************
- ******************************************************************************/</i>
- <i>/******************************************************************************
- / 函数功能:STM32F103控制函数
- / 修改日期:2015/7/14 20:22:02
- / 输入参数:none
- / 输出参数:none
- / 使用说明:需要执行约10s
- ******************************************************************************/</i>
- static bool FLASH_ioctl(uint8_t cmd,void * param)
- {
- #define UID_ADDR 0x1FFFF7E0 //闪存容量寄存器,值对应KB单位
- #define MAC_ADDR 0x1FFFF7E8 //MCU的唯一ID号,共12个字节
- #define UID_SIZE 2 //UID的字节数
- #define MAC_SIZE 12 //MAC的字节数
-
- <i>//step1: 检查参数</i>
- if(!param)return false;
-
- <i>//step2: 处理数据</i>
- switch(cmd){
- case 0 : { <i>//获取FLASH的的UID</i>
- uint16_t * ptDst = (uint16_t *)((uint32_t)param+1);
-
- *ptDst = *(uint16_t *)UID_ADDR;
- *(uint8_t *)param = UID_SIZE;
- return true;
- }
- case 1 : { <i>//获取芯片的MAC地址</i>
- uint32_t * ptDst = (uint32_t *)((uint32_t)param+1);
- uint32_t * ptSrc = (uint32_t *)MAC_ADDR;
-
- *ptDst++ = *ptSrc++;
- *ptDst++ = *ptSrc++;
- *ptDst++ = *ptSrc++;
- *(uint8_t *)param = MAC_SIZE;
- return true;
- }
- default:return false;
- }
- }
-
- <i>/******************************************************************************
- / 函数功能:文件系统Shel指令处理
- / 修改日期:2013/9/10 19:04:15
- / 输入参数:输入当前的程序版本
- / 输出参数:none
- / 使用说明:none
- ******************************************************************************/</i>
- bool Shell_MCU_Service(void * pcBuff, uint16_t len )
- {
- uint8_t *ptRxd; <i>//用于接收指令处理</i>
- int i;
- uint16_t retval;
- uint8_t buff[32];
-
- <i>//处理指令</i>
- <i>//--------------------------------------------------------------------------</i>
- ptRxd = (uint8_t *)pcBuff;
-
- if(StrComp(ptRxd,"rd ")) <i>//读取FLASH信息</i>
- {
- int wval;
-
- if(1 != sscanf((void *)ptRxd,"%*s%d",&wval) )return false;
- if( wval>2 )return false;
- if(0==wval) {
- FLASH_ioctl(0,buff);
- retval = *(uint16_t *)(buff+1) ;
- printf("->Flash:\t%dKB\r\n",retval);
- return true;
- }
- else if(1==wval) {
- FLASH_ioctl(1,buff);
- printf("->MAC:\t ");
- for(i=0; i<buff[0]-1; i++){printf("%02X-",buff[i+1]);}
- printf("%02X\r\n",buff[i+1]);
- return true;
- }
- else return false;
- }
- else if(StrComp(ptRxd,"help\r\n")) <i>//指令帮助</i>
- {
- shell_SendStr((void *)CLI_McuMsg.pcHelpStr);
- return true;
- }
- else return false;
- }
-
- <i>/******************************************************************************
- *********************************** END ************************************
- ******************************************************************************/</i>
- #endif
复制代码
5.2 实现步骤1) 将该文件添加到工程下。 2) 在main.c中用extern 引用McuList,源代码为: - <i>/*---------------------*
- * Shell指令链表
- *----------------------*/</i>
- extern Cmd_List_t McuList;
复制代码
3)在main.c初始化时添加: - <i>//----------------------------------------------------------</i>
- <i>//step1: shell初始化</i>
- shell_Init(115200,ledx_cfg); <i>//初始化shell接口</i>
- CLI_AddCmd(&McuList); <i>//添加模块指令到链表</i>
复制代码
4)编译工程文件。 5)下载到开发板运行即可在终端下看到新支持的CLI指令:
|