shell

在前面的章节中我们介绍了MM32 USB各种功能类型,也介绍了如何通过串口或者J-Link RTT方式实现shell辅助调试方式,但是其都需要依赖额外的工具,比如串口方式就需要USB转TTL,J-Link RTT需要使用J-Link下载器,所以希望有新的方法实现shell,本次我们介绍USB CDC的方式来实现shell功能。

本次我们采用MM32L373 MiniBoard作为测试开发板,验证USB CDC的方式来实现shell功能。

前面已经为大家讲解了shell的串口方法,其实原理一样,只是用MM32 USB枚举成串口设备替代USB转TTL,直接从USB获取数据到MCU,也不需要额外占用MCU的串口,节省资源和硬件,相关的代码都可以从之前的文章获取,本次只是融合两者,改变实现接口,具体代码参考如下:

对于CDC部分,其函数初始化配置及相关全局变量定义内容,代码如下:

#define USBD_POWER 0

#define USBD_MAX_PACKET0 64

#define USBD_DEVDESC_IDVENDOR 0x2F81 //0x0D28

#define USBD_DEVDESC_IDPRODUCT 0x0001 //0x0204

以上是定义的MM32 MCU CDC设备VID和PID,灵动微电子已经获得USB组织授权的VID和PID。当设备插入电脑上,可以查看到如上标识的CDC设备,如图1所示:

图1 PC设备管理器列表

对于MM32 MCU的CDC功能来说,

在使用CDC功能之前先调用USB初始化函数来初始化USB协议栈。

int main(void)

{

// USB Device Initialization and connect

usbd_init();

usbd_connect(__TRUE);

while (!usbd_configured()) // Wait for USB Device to configure
{
}
while (1)
{
}
}

对于shell部分其函数初始化配置及相关全局变量定义内容,代码如下:

typedef struct

{

char *command; // shell命令提示符

char buffer[SHELL_COMMAND_MAX_LENGTH]; // shell命令缓冲buffer

unsigned short length; // shell命令长度大小

unsigned short cursor; // shell光标位置偏移

char *param[SHELL_PARAMETER_MAX_NUMBER]; // shell参数变量

char history[SHELL_HISTORY_MAX_NUMBER][SHELL_COMMAND_MAX_LENGTH]; // 历史记录区域

unsigned short historyCount; // 历史记录数量

short historyFlag; // 当前记录偏移位置

short historyOffset; // 历史记录偏移大小

SHELL_CommandTypeDef *commandBase; // 命令表基地址

unsigned short commandNumber; // 命令数量

int keyFuncBase; // 按键响应表基地址

unsigned short keyFuncNumber; // 按键响应数量

SHELL_InputMode status; // shell输入状态

unsigned char isActive; //是不是当前激活的shell

shellRead read; // shell读函数接口

shellWrite write; // shell写函数接口

}SHELL_TypeDef;

如上所示,为对象的定义接口,移植的步骤先定义一个shell对象,即:SHELL_TypeDef cdc_shell,然后实例化对象的操作接口,具体说明看注释,对于其中我们需要关注的是shell的读写接口。由于本次我们使用USB CDC接收和发送数据,所以我们只需要在USB CDC的函数中处理接收到的数据即可,我们使用shellHandler(&cdc_shell, EP2RXBuff[i]);来处理数据的交互,具体函数代码参考串口shell代码。

shell的发送接口,只需要把数据拷贝到buffer即可。

shell的读写接口移植到CDC上,代码如下:

void USBD_CDC_TASK(void)

{

uint8_t i, count;

NotifyOnStatusChange();

if (CDC_UART ->ISR & 0x08)

{

CDC_UART ->GCR &= ~(3 << 3);

CDC_UART ->GCR = 3 << 3;

UART_ClearITPendingBit(CDC_UART, UART_OVER_ERR);

}

// USB -> UART

if (EP2ReceiveFlag == 1)

{

EP2ReceiveFlag = 0;

for (i = 0; i < RxBufLen; i++)

shellHandler(&cdc_shell, EP2RXBuff[i]);

}

// UART -> USB

if (EP2TransferFlag == 1)

{

if (TxBufLen > 0)

{

while (USB->rEP2_CTRL & 0x80);

if (TxBufLen > 64)

{

UART_ReadData(EP2TXBuff, 64);

count = 64;

TxBufLen -= 64;

}

else

{

UART_ReadData(EP2TXBuff, TxBufLen);

count = TxBufLen;

TxBufLen = 0;

}

usb_buf_busy_flag = 1;

for (i = 0; i < count; i++)

{

USB->rEP2_FIFO = *(EP2TXBuff + i);

}

if ((USB ->rEP2_AVIL & 0x3f) == count)

{

USB->rEP2_CTRL = 0x80 | count;

}

else

{

USB->rTOP |= 1 << 3;

USB->rTOP &= ~(1 << 3);

}

USB->rEP2_CTRL = 0x80 | count;

if (0 == TxBufLen)

EP2TransferFlag = 0;

}

}

}

如上,我们就完成通过MM32 MCU的CDC实现shell调试功能,用串口助手打开虚拟串口,用CDC shell测试发送数据,结果如下:

图2 功能演示

以上就是MM32 MCU USB的CDC shell功能。

本文转自:灵动MM32MCU

围观 220

对于做linux开发的研发人员来说,大家都喜欢通过输入指令符来执行一些命令操作,如果在MCU编程过程中有一个类似linux的shell命令工具可以通过串口调试助手输入命令然后运行一些调试函数,将会为编程提供极大的帮助。

对于MCU来说,每次修改程序调试就需要重新下载,有时候仅仅是调试修改几个参数,这样反复修改程序编译下载就显得很繁琐,浪费时间,而且在调试电机、电源等高压电源类应用时,如果出现操作错误,有可能会造成炸机毁坏电脑等危险,如果有一个类似linux的命令行操作,这样就可以省出很多时间,但是由于MCU存储资源和运算速度等限制,所以想实现一个同样功能的操作,就需要做一些处理优化。

目前有很多类似的shell代码,本次介绍如何在MM32 MCU上使用shell来辅助开发,由于篇幅过长,所以将分三个章节讲述实现方式,本章节将教大家基础的软件配置,后面章节将带领大家学习如何配置Jlink RTT和shell的程序配置。

硬件资源如下:

本次实验将在基于MM32L073的MiniBoard上进行测试验证,实现shell的通信端口可以使用任意通信方式,如UART、USB、SPI、IIC、485等方式,本次使用MM32L073PF的UART1作为shell输入输出通道,如图2。PA15作为状态指示 LED1的控制引脚,如图3。


图1 串口硬件原理图


图2 LED硬件原理图

软件资源如下:

结合上述使用到的硬件资源,下面我们着重介绍软件实现流程以及相关配置代码,主要涉及如何移植shell的输入输出以及如何执行命令。

以下为主函数初始化配置及相关全局变量定义内容,代码如下:

//>>>状态指示 LED1的初始化配置>>>

void LED_Init(void)

{

GPIO_InitTypeDef  GPIO_InitStructure;

     

RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE); //开启GPIOA时钟

GPIO_InitStructure.GPIO_Pin  =  GPIO_Pin_15;

GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;

GPIO_Init(GPIOA, &GPIO_InitStructure);

LED1_OFF();

}

 

void uart_nvic_init(u32 bound)

{

//>>>GPIO端口设置>>>

GPIO_InitTypeDef GPIO_InitStructure;

UART_InitTypeDef UART_InitStructure;

NVIC_InitTypeDef NVIC_InitStructure;

//>>>使能外设时钟>>>

RCC_APB2PeriphClockCmd(RCC_APB2Periph_UART1, ENABLE); //使能UART1

RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE);  //开启GPIOA时钟

//>>>UART1 NVIC 配置>>>

NVIC_InitStructure.NVIC_IRQChannel = UART1_IRQn;

NVIC_InitStructure.NVIC_IRQChannelPriority = 3;  //子优先级3

NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能

NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化NVIC寄存器

//>>>UART的GPIO复用功能配置>>>

GPIO_PinAFConfig(GPIOA, GPIO_PinSource9, GPIO_AF_1);

GPIO_PinAFConfig(GPIOA, GPIO_PinSource10, GPIO_AF_1);

//>>>UART 初始化设置>>>

UART_InitStructure.UART_BaudRate = bound;//串口波特率

UART_InitStructure.UART_WordLength = UART_WordLength_8b;//字长为8位数据格式

UART_InitStructure.UART_StopBits = UART_StopBits_1;//一个停止位

UART_InitStructure.UART_Parity = UART_Parity_No;//无奇偶校验位

UART_InitStructure.UART_HardwareFlowControl = UART_HardwareFlowControl_None;//无硬件数据流控制

UART_InitStructure.UART_Mode = UART_Mode_Rx | UART_Mode_Tx; //收发模式

UART_Init(UART1, &UART_InitStructure); //初始化串口1

 

UART_ITConfig(UART1, UART_IT_RXIEN, ENABLE);//开启串口接受中断

UART_Cmd(UART1, ENABLE);                    //使能串口1

 

//>>>UART1_TX   GPIOA.9>>> UART1_RX    GPIOA.10>>>

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //PA.9

GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出

GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.9

 

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;//PA10

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入

GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.10

}

//>>>UART发送一个字节 >>>

void Uart_PutChar(char chByte)

{

while ((UART1->CSR & UART_IT_TXIEN) == 0);

UART1->TDR = (chByte & (uint16_t)0x00FF);

}

//>>>UART 发送字符串>>>

void Uart_PutBuff(uint8_t *pchBuff, uint32_t wLen)

{

while (wLen--) {

Uart_PutChar(*pchBuff);

pchBuff++;

}

}

//>>>main主函数 >>>

int main(void)

{

delay_init();

LED_Init();

uart_nvic_init(115200);  //串口初始化为115200

//uart_shell.read = shellRead;

uart_shell.write = Uart_PutChar;

shellInit(&uart_shell);//shell初始化

    

while (1)

{

}

}

移植的步骤先定义一个shell对象,即:SHELL_TypeDef uart_shell;

然后实例化对象的操作接口,对于本次我们采用中断接收,所以不用调用读取接口函数,所以接收接口如下修改:

//uart_shell.read = shellRead; 注释掉读取接口,采用中断处理代码如下:

void UART1_IRQHandler(void)                 //串口1中断服务程序

{

u8 Res;

if (UART_GetITStatus(UART1, UART_IT_RXIEN)  != RESET){

UART_ClearITPendingBit(UART1, UART_IT_RXIEN);

Res = UART_ReceiveData(UART1);//读取接收到的数据

shellHandler(&uart_shell, Res);//shell处理函数

}

}

然后实例化发送接口,代码如下:uart_shell.write = Uart_PutChar;

最后实例化对象,代码如下:shellInit(&uart_shell);

完成shell对象的全部实例化,那么我们如何加入我们需要的命令函数呢?很简单,有多种方式,本次我们介绍最简单的一个,即SHELL_EXPORT_CMD();其它参考源码,本次我们加入测试代码如下:

void led1_on(void)

{

LED1_ON();

}

SHELL_EXPORT_CMD(led1_on, led1_on, led1_on);//三个变量:命令,功能,描述

 

void led1_off(void)

{

LED1_OFF();

}

SHELL_EXPORT_CMD(led1_off, led1_off, led1_off);//同上

 

void led1_toggle(void)

{

LED1_TOGGLE();

}

SHELL_EXPORT_CMD(led1_toggle, led1_toggle, led1_toggle);

 

void reboot(void)

{

NVIC_SystemReset();

}

SHELL_EXPORT_CMD(reboot, reboot, reboot);

如上完成所有代码后下载烧写进入,然后打开PuTTY,设置为115200,界面如下:


图3 命令列表

选择输入对应的指令命令,MCU执行对应的指令操作;比如输入led1_on即可打开led1,输入led1_off关闭led1,输入led1_toggle反转led1亮灭,输入reboot则MCU重启,输入cls清除当前窗口。


图4 控制LED1亮

本次实验的的外设配置及操作如上所述,在下一章节将带领大家学习shell的程序和Jlink RTT的配置等。

来源:灵动MM32MCU

围观 136
订阅 RSS - shell