1. 前言
对于那些曾经体验过Linux强大串口Shell命令控制台的朋友们来说,是否非常羡慕它的功能?如果我们的低端MCU,比如STM32F系列,也能拥有这样的交互能力,将会大大简化调试、维护和配置的繁琐工作。例如,启动/关闭调试或自检模式,打印调试信息,配置系统参数和传输文件等等。虽然有一些朋友凭借自己出色的编程能力实现了这些功能,但在这里我提出自己的解决方案,以供交流讨论。
我的解决方案是基于xc_shell平台,具备以下性能特点:
-
主要代码与具体硬件无关,具有很强的移植性,代码文件数量较少。 -
只在处理用户输入命令时占用CPU资源,代码可裁剪至1KB SRAM和4KB Flash。 -
用户可以非常灵活地添加按模板编写的命令脚本文件,具有自定义扩展能力。 -
支持操作系统和非操作系统两种应用场景。 -
支持Ymodem文件传输协议。 -
支持将Flash的扇区作为参数存储区,实现本地/远程升级。 -
提供实用的LED灯信号管理功能,可以将65535个虚拟信号灯映射到一个实际的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回调处理方法,则会在中断中执行用户的回调处理。
/*---------------------*
* 指正函数定义
*----------------------*/
typedef void (*pvFunDummy)(void);
//输入整行,输出逻辑
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);
//输入整行,输出逻辑
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);
//输入整形指针,输出逻辑
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);
//输入数据帧,输出逻辑
typedef bool (*pbFun_Buffx)(void * pcBuff, uint16_t len );
typedef bool (*pbFun_Bytex)(uint8_t * pcByte, uint16_t len );
/*---------------------*
* TTYx 句柄结构
*----------------------*/
typedef struct TTYx_HANDLE_STRUCT
{
const char * const name; //驱动器名
const uint16_t rxSize; //接收大小
const uint16_t txSize; //发送大小
//------------------------------------------------------
//step1: 用户可用API
const pvFunWord init; //初始化.
const pbFun_Bytex api_TxdFrame; //发送数据帧. (发送帧)
const pbFunChar api_TxdByte; //发送数据字节
//------------------------------------------------------
//step2: 注入回调函数
pbFun_Bytex inj_RcvFrame; //(ISR)接收数据帧. (接收帧)
pvFunDummy inj_TxdReady; //(ISR)发送完毕回调
//------------------------------------------------------
//step3: 接收回调函数
struct TTYx_HANDLE_STRUCT * pvNext; //连接到下一个指令
}TTYx_HANDLE;
-
可注入的命令脚本(CLI)实现
命令CLI也是一个结构体对象:
/*---------------------*
* CLI指令
*----------------------*/
typedef struct
{
const char * const pcCmdStr; //指令字符串(只能为小写字母)
const char * const pcHelpStr; //指令描述,必须以:"\r\n结束". 比如:"help: Returns a list\r\n".
const pFunHook pxCmdHook; //指向回调函数的指针,处理成功返回真否者返回0;
uint8_t ucExpParam; //指令期望的参数个数
const MEDIA_HANDLE *phStorage; //指向存储介质,没有的话填充NULL
}Cmd_Typedef_t;
各位朋友可能会使用到非常多的自定义CLI命令, 格式诸如这个网卡的命令:
const Cmd_Typedef_t CLI_WizMsg=
{
//识别关键字
.pcCmdStr = "wiz",
//帮助内容
.pcHelpStr =
"[WIZ contorls]\r\n"
" wiz help\r\n"
" wiz rd info\r\n"
" wiz reset\r\n"
" wiz wr ip ...\r\n"
" wiz wr mask ...\r\n"
" wiz wr way ...\r\n"
" wiz wr mac -----\r\n"
" wiz wr port \r\n"
" wiz wr sip ... \r\n"
" wiz wr cip ... \r\n"
" wiz load default\r\n"
"[WIZ Test mode]\r\n"
" wiz loop open\r\n"
" wiz loop close\r\n"
"\r\n",
//处理函数
.pxCmdHook = &Shell_WIZ_Service, //见实体函数
//附带数据
.ucExpParam = 0,
#ifdef SHELL_USE_YMODEM
//存储介质
.phStorage = NULL,
#endif
};
/*---------------------*
* CLI 链表节
*----------------------*/
Cmd_List_t WizList = { &CLI_WizMsg, NULL,}; //Shell指令的头
如配置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源代码:
/*********************************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
*******************************************************************************/
/******************************************************************************
更新说明:
******************************************************************************/
/******************************************************************************
********************************* 编 译 控 制 ********************************
******************************************************************************/
#define MCU_SHELL //注释掉时屏蔽iap shell功能
#include "xc_shell.h" //Shell支持文件,含bool,uint8_t..以及串口数据收发操作
/******************************************************************************
********************************* 文件引用部分 ********************************
******************************************************************************/
/*---------------------*
* 模块驱动引用
*----------------------*/
//#include "net_w5500.h"
#ifdef MCU_SHELL
/******************************************************************************
********************************** Shell实例 **********************************
******************************************************************************/
/*---------------------*
* CLI指令服务
*----------------------*/
extern bool Shell_MCU_Service(void * pcBuff, uint16_t len );
/*---------------------*
* CLI 结构
*----------------------*/
const Cmd_Typedef_t CLI_McuMsg=
{
//识别关键字
"mcu",
//帮助内容
"[mcu contorls]\r\n"
" mcu rd \t\t- Read FLASH information.\r\n"
"\r\n",
//处理函数
&Shell_MCU_Service,
//附带数据
0,
#ifdef SHELL_USE_YMODEM
//存储介质
NULL,
#endif
};
/*---------------------*
* CLI链表节(输出)
*----------------------*/
Cmd_List_t McuList = {&CLI_McuMsg ,NULL}; //IAP指令链表
/******************************************************************************
********************************* 函 数 声 明 *********************************
******************************************************************************/
/******************************************************************************
/ 函数功能:STM32F103控制函数
/ 修改日期:2015/7/14 20:22:02
/ 输入参数:none
/ 输出参数:none
/ 使用说明:需要执行约10s
******************************************************************************/
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的字节数
//step1: 检查参数
if(!param)return false;
//step2: 处理数据
switch(cmd){
case 0 : { //获取FLASH的的UID
uint16_t * ptDst = (uint16_t *)((uint32_t)param+1);
*ptDst = *(uint16_t *)UID_ADDR;
*(uint8_t *)param = UID_SIZE;
return true;
}
case 1 : { //获取芯片的MAC地址
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;
}
}
/******************************************************************************
/ 函数功能:文件系统Shel指令处理
/ 修改日期:2013/9/10 19:04:15
/ 输入参数:输入当前的程序版本
/ 输出参数:none
/ 使用说明:none
******************************************************************************/
bool Shell_MCU_Service(void * pcBuff, uint16_t len )
{
uint8_t *ptRxd; //用于接收指令处理
int i;
uint16_t retval;
uint8_t buff[32];
//处理指令
//--------------------------------------------------------------------------
ptRxd = (uint8_t *)pcBuff;
if(StrComp(ptRxd,"rd ")) //读取FLASH信息
{
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; iprintf("%02X-",buff[i+1]);}
printf("%02X\r\n",buff[i+1]);
return true;
}
else return false;
}
else if(StrComp(ptRxd,"help\r\n")) //指令帮助
{
shell_SendStr((void *)CLI_McuMsg.pcHelpStr);
return true;
}
else return false;
}
/******************************************************************************
*********************************** END ************************************
******************************************************************************/
#endif
5.2 实现步骤
1) 将该文件添加到工程下。
2) 在main.c中用extern 引用McuList,源代码为:
/*---------------------*
* Shell指令链表
*----------------------*/
extern Cmd_List_t McuList;
3)在main.c初始化时添加:
//----------------------------------------------------------
//step1: shell初始化
shell_Init(115200,ledx_cfg); //初始化shell接口
CLI_AddCmd(&McuList); //添加模块指令到链表
4)编译工程文件。
5)下载到开发板运行即可在终端下看到新支持的CLI指令:
以上就是良许教程网为各位朋友分享的Linu系统相关内容。想要了解更多Linux相关知识记得关注公众号“良许Linux”,或扫描下方二维码进行关注,更多干货等着你 !