【项目移植】国民技术N32G4FR开发板:RT-Thread Nano移植FinSH

描述

开发环境:

  • MDK:Keil 5.30

  • 开发板:N32G4FRML-STB 开发板

  • MCU:N32G4FRMEL7

  • RT-Thread版本:3.1.5


FinSH简介

FinSH是RT-Thread的命令行外壳(shell),提供一套供用户在命令行的操作接口,主要用于调试、查看系统信息。在大部分嵌入式系统中,一般开发调试都使用硬件调试器和printf日志打印,在有些情况下,这两种方式并不是那么好用。比如对于RT-Thread这个多线程系统,我们想知道某个时刻系统中的线程运行状态、手动控制系统状态。如果有一个shell,就可以输入命令,直接相应的函数执行获得需要的信息,或者控制程序的行为。这无疑会十分方便。硬件拓扑结构如下图所示:


电子发烧友论坛


用户在控制终端输入命令,控制终端通过串口、USB、网络等方式将命令传给设备里的 FinSH,FinSH 会读取设备输入命令,解析并自动扫描内部函数表,寻找对应函数名,执行函数后输出回应,回应通过原路返回,将结果显示在控制终端上。


当使用串口连接设备与控制终端时,FinSH 命令的执行流程,如下图所示:


电子发烧友论坛


FinSH 支持权限验证功能,系统在启动后会进行权限验证,只有权限验证通过,才会开启 FinSH 功能,提升系统输入的安全性。


FinSH 支持自动补全、查看历史命令等功能,通过键盘上的按键可以很方便的使用这些功能,FinSH 支持的按键如下表所示:


电子发烧友论坛


FinSH支持两种模式,分别是传统命令行模式和 C 语言解释器模式:


  1. C语言解释器模式, 为行文方便称之为c-style;C 语言解释器模式下,FinSH 能够解析执行大部分 C 语言的表达式,并使用类似 C 语言的函数调用方式访问系统中的函数及全局变量,此外它也能够通过命令行方式创建变量。在该模式下,输入的命令必须类似 C 语言中的函数调用方式,即必须携带 () 符号,例如,要输出系统当前所有线程及其状态,在 FinSH 中输入 list_thread() 即可打印出需要的信息。FinSH 命令的输出为此函数的返回值。对于一些不存在返回值的函数(void 返回值),这个打印输出没有意义。

  2. 传统命令行模式,此模式又称为msh(module shell)。C语言表达式解释模式下, finsh能够解析执行大部分C语言的表达式,并使用类似C语言的函数调用方式访问系统中的函数及全局变量,此外它也能够通过命令行方式创建变量。在msh模式下,finsh运行方式类似于dos/bash等传统shell。例如,可以通过 cd / 命令将目录切换至根目录。msh 通过解析,将输入字符分解成以空格区分开的命令和参数。其命令执行格式如下所示:

command [arg1] [arg2] [...]


其中 command 既可以是 RT-Thread 内置的命令,也可以是可执行的文件。


最初 FinSH 仅支持 C-Style 模式,后来随着 RT-Thread 的不断发展,C-Style 模式在运行脚本或者程序时不太方便,而使用传统的 shell 方式则比较方便。另外,C-Style 模式下,FinSH 占用体积比较大。出于这些考虑,在 RT-Thread 中增加了 msh 模式,msh 模式体积小,使用方便,推荐大家使用 msh 模式。


如果在 RT-Thread 中同时使能了这两种模式,那它们可以动态切换,在 msh 模式下输入 exit 后回车,即可切换到 C-Style 模式。在 C-Style 模式输入 msh() 后回车,即可进入 msh 模式。两种模式的命令不通用,msh 命令无法在 C-Style 模式下使用,反之同理。


FinSH的移植分为两个部分:第一部分是实现 UART 控制台,该部分只需要实现两个函数即可完成 UART 控制台打印功能。第二部分是实现移植 FinSH 组件,实现在控制台输入命令调试系统,该部分实现基于第一部分,只需要添加 FinSH 组件源码并再对接一个系统函数即可实现。下面将对这两部分进行说明。


在Nano上添加UART控制台

在 RT-Thread Nano 上添加 UART 控制台打印功能后,就可以在代码中使用 RT-Thread 提供的打印函数 rt_kprintf() 进行信息打印,从而获取自定义的打印信息,方便定位代码 bug 或者获取系统当前运行状态等。实现控制台打印,需要完成基本的硬件初始化,以及对接一个系统输出字符的函数。


2.1 串口初始化

使用串口对接控制台的打印,首先需要初始化串口,如引脚、波特率等。需要在 board.c 中的 rt_hw_board_init() 函数中调用串口初始化。


电子发烧友论坛


2.2 实现 rt_hw_console_output

实现 finsh 组件输出一个字符,即在该函数中实现 uart 输出字符:

/*输出一个字符,系统函数,函数名不可更改 */
void rt_hw_console_output(const char *str);

(左右移动查看全部内容)


示例代码:如下是基于N32G4FRMEL7的串口驱动对接的 rt_hw_console_output() 函数,实现控制台字符输出,示例仅做参考。


电子发烧友论坛


以上代码很简单,就是将裸机的字符输出的内容使用rt_hw_console_output()函数实现,笔者使用的是串口1作为调试串口。


注意:RT-Thread 系统中已有的打印均以 结尾,而并非 ,所以在字符输出时,需要在输出 之前输出 ,完成回车与换行,否则系统打印出来的信息将只有换行。


上面实现了rt_hw_console_output()函数,也就实现了rt_kprintf()函数,在kservice.c中调用了rt_hw_console_output()函数。


电子发烧友论坛


以下代码就是在调用rt_hw_console_output()。


电子发烧友论坛


RT_CONSOLEBUF_SIZE定义缓冲区的最大长度为,默认配置的大小为128。


电子发烧友论坛


2.3 结果验证

在应用代码中编写含有 rt_kprintf() 打印的代码,编译下载,打开串口助手进行验证。如下图是一个在 main() 函数中每隔 1 秒进行循环打印 Hello RT-Thread 的示例效果:


电子发烧友论坛


在Nano上添加FinSH组件

RT-Thread FinSH 是 RT-Thread 的命令行组件(shell),提供一套供用户在命令行调用的操作接口,主要用于调试或查看系统信息。它可以使用串口 / 以太网 / USB 等与 PC 机进行通信,使用 FinSH 组件基本命令的效果图如下所示。


电子发烧友论坛


本文以串口 UART 作为 FinSH 的输入输出端口与 PC 进行通信,描述如何在 Nano 上实现 FinSH shell 功能。


在 RT-Thread Nano 上添加 FinSH 组件,实现 FinSH 功能的步骤主要如下:

  1. 添加 FinSH 源码到工程。

  2. 实现函数对接。

  3. 1 Keil添加 FinSH 源码工程


把 FinSH 组件的源码到工程。


电子发烧友论坛


另外还需要配置Finsh的头文件路径。


电子发烧友论坛


3.2 实现 rt_hw_console_getchar

要实现 FinSH 组件功能:既可以打印也能输入命令进行调试,控制台已经实现了打印功能,现在还需要在 board.c 中对接控制台输入函数,实现字符输入:

/* finsh 获取一个字符,系统函数,函数名不可更改 */
char rt_hw_console_getchar(void)

(左右移动查看全部内容)


rt_hw_console_getchar():控制台获取一个字符,即在该函数中实现 uart 获取字符,可以使用查询方式获取(注意不要死等,在未获取到字符时,需要让出 CPU),也可以使用中断方式获取。


3.2.1查询方式

如下是基于N32G4FRMEL7的串口驱动对接的 rt_hw_console_getchar(),完成对接 FinSH 组件,其中获取字符采用查询方式,示例仅做参考。

/* 移植 FinSH,实现命令行交互, 需要添加 FinSH 源码,然后再对接 rt_hw_console_getchar */
char rt_hw_console_getchar(void)
{
   int ch = -1;
   if (USART_GetFlagStatus(USART1, USART_FLAG_RXDNE) != RESET)
   {
       ch = (int)USART_ReceiveData(USART1);
   }
   else
   {
       if(USART_GetFlagStatus(USART1, USART_FLAG_OREF) != RESET)
       {
           USART_ClrFlag(USART1,USART_FLAG_TXC);
       }
       rt_thread_mdelay(10);
   }
   return ch;  
}

(左右移动查看全部内容)


3.2.2 中断方式

如下是基于 N32G4FRMEL7串口驱动,实现控制台输出与 FinSH Shell,其中获取字符采用中断方式。原理是,在 uart 接收到数据时产生中断,在中断中把数据存入 ringbuffer 缓冲区,然后释放信号量,tshell 线程接收信号量,然后读取存在 ringbuffer 中的数据。

/* 第一部分:ringbuffer 实现部分 */
#include
#include


#define rt_ringbuffer_space_len(rb) ((rb)->buffer_size - rt_ringbuffer_data_len(rb))


struct rt_ringbuffer
{
   rt_uint8_t *buffer_ptr;


   rt_uint16_t read_mirror : 1;
   rt_uint16_t read_index : 15;
   rt_uint16_t write_mirror : 1;
   rt_uint16_t write_index : 15;


   rt_int16_t buffer_size;
};


enum rt_ringbuffer_state
{
   RT_RINGBUFFER_EMPTY,
   RT_RINGBUFFER_FULL,
   /* half full is neither full nor empty */
   RT_RINGBUFFER_HALFFULL,
};


rt_inline enum rt_ringbuffer_state rt_ringbuffer_status(struct rt_ringbuffer *rb)
{
   if (rb->read_index == rb->write_index)
   {
       if (rb->read_mirror == rb->write_mirror)
           return RT_RINGBUFFER_EMPTY;
       else
           return RT_RINGBUFFER_FULL;
   }
   return RT_RINGBUFFER_HALFFULL;
}


/**
* get the size of data in rb
*/
rt_size_t rt_ringbuffer_data_len(struct rt_ringbuffer *rb)
{
   switch (rt_ringbuffer_status(rb))
   {
   case RT_RINGBUFFER_EMPTY:
       return 0;
   case RT_RINGBUFFER_FULL:
       return rb->buffer_size;
   case RT_RINGBUFFER_HALFFULL:
   default:
       if (rb->write_index > rb->read_index)
           return rb->write_index - rb->read_index;
       else
           return rb->buffer_size - (rb->read_index - rb->write_index);
   };
}


void rt_ringbuffer_init(struct rt_ringbuffer *rb,
                       rt_uint8_t           *pool,
                       rt_int16_t            size)
{
   RT_ASSERT(rb != RT_NULL);
   RT_ASSERT(size > 0);


   /* initialize read and write index */
   rb->read_mirror = rb->read_index = 0;
   rb->write_mirror = rb->write_index = 0;


   /* set buffer pool and size */
   rb->buffer_ptr = pool;
   rb->buffer_size = RT_ALIGN_DOWN(size, RT_ALIGN_SIZE);
}


/**
* put a character into ring buffer
*/
rt_size_t rt_ringbuffer_putchar(struct rt_ringbuffer *rb, const rt_uint8_t ch)
{
   RT_ASSERT(rb != RT_NULL);


   /* whether has enough space */
   if (!rt_ringbuffer_space_len(rb))
       return 0;


   rb->buffer_ptr[rb->write_index] = ch;


   /* flip mirror */
   if (rb->write_index == rb->buffer_size-1)
   {
       rb->write_mirror = ~rb->write_mirror;
       rb->write_index = 0;
   }
   else
   {
       rb->write_index++;
   }


   return 1;
}
/**
* get a character from a ringbuffer
*/
rt_size_t rt_ringbuffer_getchar(struct rt_ringbuffer *rb, rt_uint8_t *ch)
{
   RT_ASSERT(rb != RT_NULL);


   /* ringbuffer is empty */
   if (!rt_ringbuffer_data_len(rb))
       return 0;


   /* put character */
   *ch = rb->buffer_ptr[rb->read_index];


   if (rb->read_index == rb->buffer_size-1)
   {
       rb->read_mirror = ~rb->read_mirror;
       rb->read_index = 0;
   }
   else
   {
       rb->read_index++;
   }


   return 1;
}


/* 第二部分:finsh 移植对接部分 */
#define UART_RX_BUF_LEN 16
rt_uint8_t uart_rx_buf[UART_RX_BUF_LEN] = {0};
struct rt_ringbuffer  uart_rxcb;         /* 定义一个 ringbuffer cb */
static struct rt_semaphore shell_rx_sem; /* 定义一个静态信号量 */


/**
 * [url=home.php?mod=space&uid=2666770]@Brief[/url]  配置USART接收中断
 * [url=home.php?mod=space&uid=3142012]@param[/url]  uint8_t IRQChannel, uint8_t PreemptionPriority, uint8_t SubPriority
 * @retval None
 */
static void BSP_USART_NVIC_Configuration(uint8_t IRQChannel, uint8_t PreemptionPriority, uint8_t SubPriority)
{
   NVIC_InitType NVIC_InitStructure;


   /* Enable the USARTy Interrupt */
   NVIC_InitStructure.NVIC_IRQChannel = IRQChannel;
   NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = PreemptionPriority;
   NVIC_InitStructure.NVIC_IRQChannelSubPriority = SubPriority;
   NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
   NVIC_Init(&NVIC_InitStructure);
}


/**
 * @brief  USART GPIO 配置
 * @param  ST_BSP_USART_Dev *BSP_USART_Dev, uint32_t BaudRate, uint8_t PreemptionPriority, uint8_t SubPriority
 * @retval None
 */
void BSP_USART_Init(ST_BSP_USART_Dev *BSP_USART_Dev, uint32_t BaudRate, uint8_t PreemptionPriority, uint8_t SubPriority)
{
   GPIO_InitType GPIO_InitStructure;
   USART_InitType USART_InitStructure;


   /* config USART GPIO clock */
   RCC_EnableAPB2PeriphClk(BSP_USART_Dev->usart_rx_gpio_clk | BSP_USART_Dev->usart_tx_gpio_clk, ENABLE);


   /* config USART clock */
   RCC_EnableAPB2PeriphClk(BSP_USART_Dev->usart_clk, ENABLE);


   /* 初始化串口接收 ringbuffer  */
   rt_ringbuffer_init(&uart_rxcb, uart_rx_buf, UART_RX_BUF_LEN);


   /* 初始化串口接收数据的信号量 */
   rt_sem_init(&(shell_rx_sem), "shell_rx", 0, 0);


   /* USART GPIO config */
   /* Configure USART Tx as alternate function push-pull */
   GPIO_InitStructure.Pin = BSP_USART_Dev->usart_tx_pin;
   GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
   GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
   GPIO_InitPeripheral(BSP_USART_Dev->usart_tx_port, &GPIO_InitStructure);    
   /* Configure USART Rx as input floating */
   GPIO_InitStructure.Pin = BSP_USART_Dev->usart_rx_pin;
   GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
   GPIO_InitPeripheral(BSP_USART_Dev->usart_rx_port, &GPIO_InitStructure);


   USART_DeInit(BSP_USART_Dev->usart);
   /* USART mode config */
   USART_InitStructure.BaudRate = BaudRate;
   USART_InitStructure.WordLength = USART_WL_8B;
   USART_InitStructure.StopBits = USART_STPB_1;
   USART_InitStructure.Parity = USART_PE_NO ;
   USART_InitStructure.HardwareFlowControl = USART_HFCTRL_NONE;
   USART_InitStructure.Mode = USART_MODE_RX | USART_MODE_TX;
   USART_Init(BSP_USART_Dev->usart, &USART_InitStructure);


   // 配置中断
   BSP_USART_NVIC_Configuration(BSP_USART_Dev->usart_irqn, PreemptionPriority, SubPriority);
   /* 使能串口接收中断 */
   USART_ConfigInt(BSP_USART_Dev->usart, USART_INT_RXDNE, ENABLE);
   //USART_ConfigInt(BSP_USART_Dev->usart, USART_INT_IDLEF, ENABLE); // 空闲中断


   USART_Enable(BSP_USART_Dev->usart, ENABLE);
}




/*输出一个字符,系统函数,函数名不可更改 */
void rt_hw_console_output(const char *str)
{
   rt_size_t  i = 0, size = 0;
   char a = ' ';
   
   /*清楚标志位*/
   USART_ClrFlag(USART1,USART_FLAG_TXC);
   
   size = rt_strlen(str);
   for (i = 0; i < size; i++)
   {
       if (*(str + i) == ' ')
       {
           /* 发送一个字节数据到USART1 */
           USART_SendData(USART1, (uint8_t) a);
           /* 等待发送完毕 */
           while (USART_GetFlagStatus(USART1, USART_FLAG_TXC) == RESET);            
       }
       /* 发送一个字节数据到USART1 */
       USART_SendData(USART1, (uint8_t) *(str+i));
       /* 等待发送完毕 */        
       while (USART_GetFlagStatus(USART1, USART_FLAG_TXC) == RESET);    
   }
}


/* 移植 FinSH,实现命令行交互, 需要添加 FinSH 源码,然后再对接 rt_hw_console_getchar */
/* 中断方式 */
char rt_hw_console_getchar(void)
{
   char ch = 0;


   /* 从 ringbuffer 中拿出数据 */
   while (rt_ringbuffer_getchar(&uart_rxcb, (rt_uint8_t *)&ch) != 1)
   {
       rt_sem_take(&shell_rx_sem, RT_WAITING_FOREVER);
   }
   return ch;  
}


/* 第三部分:中断部分*/
void USART1_IRQHandler(void)
{
   int ch = -1;  

   /* enter interrupt */
   rt_interrupt_enter();          //在中断中一定要调用这对函数,进入中断
   
   if( (USART_GetIntStatus(USART1, USART_INT_RXDNE) != RESET) && (USART_GetFlagStatus(USART1, USART_FLAG_RXDNE) != RESET) )
   {    
           while (1)      
           {
               ch = -1;
               if(USART_GetIntStatus(USART1, USART_INT_RXDNE) != RESET)
               {
                   ch = USART_ReceiveData(USART1);
               }
               if(ch ==-1)
               {
                   break;
               }
               /* 读取到数据,将数据存入 ringbuffer */
               rt_ringbuffer_putchar(&uart_rxcb, ch);
           }
           rt_sem_release(&shell_rx_sem);
   }
   /* leave interrupt */
   rt_interrupt_leave();    //在中断中一定要调用这对函数,离开中断
}

(左右移动查看全部内容)


【注】需要确认 rtconfig.h 中已使能 RT_USING_CONSOLE 宏定义


移植完成后,将程序下载到板子中,打开串口助手,在发送去输入字符,点击发送即可进行交互。注意一定要有换行符。


电子发烧友论坛


这里推荐使用xshell等工具,用起来就有种Linux终端的感觉。


电子发烧友论坛


FinSH实例

前文移植了FinSH,接下来我门通过一个实例来讲解如果使用自定义 msh 命令。本节我们来读取芯片闪存容量寄存器和芯片ID,寄存器地址描述如下:


  • 存储器容量寄存器


电子发烧友论坛


  • 产品唯一身份标识寄存器(96位)


产品唯一的身份标识应用如下:

  • 用来作为序列号(例如USB字符序列号或者其他的终端应用)

  • 用来作为密码,在编写闪存时,将此唯一标识与软件加解密算法结合使用,提高代码在闪存存储器内的安全性。

  • 用来激活带安全机制的自举过程


96位的产品唯一身份标识所提供的参考号码对任意一个STM32微控制器,在任何情况下都是唯一的。用户在何种情况下,都不能修改这个身份标识。


这个96位的产品唯一身份标识,按照用户不同的用法,可以以字节(8位)为单位读取,也可以以半字(16位)或者全字(32位)。


实现读取芯片闪存容量寄存器和芯片ID很简单,代码如下:

uint32_t ChipUniqueID[3];


void GetChipID(void)
{
   ChipUniqueID[0] = *(volatile uint32_t *)(0x1FFFF7F0);//ID最高位
   ChipUniqueID[1] = *(volatile uint32_t *)(0x1FFFF7EC);//ID最高位
   ChipUniqueID[2] = *(volatile uint32_t *)(0x1FFFF7E8);//ID最高位


   rt_kprintf(" Chip ID id:0x%08X-0x%08X-0x%08X ",ChipUniqueID[0],ChipUniqueID[1],ChipUniqueID[2]);
}


MSH_CMD_EXPORT(GetChipID, Get 96 bit unique chip ID);


void GetFlashCapactity(void)
{
   rt_kprintf(" Chip  flash capacity is:%dK ",*(volatile uint16_t *)(0x1FFFF7E0));
}


MSH_CMD_EXPORT(GetFlashCapactity, Get Chip flash capacity);

(左右移动查看全部内容)


这里需要关注宏定义MSH_CMD_EXPORT,我们在Finsh命令行中就可调用我们自定义的命令,如下所示:


电子发烧友论坛


从结果可以看出闪存存储器容量是512K,芯片唯一序列号是:39FFDF054E42323210611451,以上结果是完全符合预期的。



本文由电子发烧友社区发布,转载请注明以上来源。如需社区合作及入群交流,请添加微信EEFans0806,或者发邮箱liuyong@huaqiu.com


电子发烧友论坛


 

 热门推荐干货好文 


1、社区精选!PCB多层板设计挑战赛作品集合

2、超强性能AI芯片,OpenHarmony多系统支持,可定制高性能AP(附10+开发Demo)

3、从零入门物联网OH开源平台,从简单到高阶项目,创客、电子爱好者都爱用!

4、低成本ESP32方案,支持OpenHarmony系统开发(附10+项目样例Demo)

5、从0到1玩转瑞萨RA4系列开发板,教你变着花样玩板子

6、四核64位,超强CPU ,看RK3568“竞”开发板DEMO!

7、人工智能也能这么玩, 简单快速入手,还能自定义AI运算

8、业界首款!支持富设备开发,OpenHarmony开发者都选它!

9、高性能双核RISC-V,满足大多数开发,这款国产MCU工程师都爱


原文标题:【项目移植】国民技术N32G4FR开发板:RT-Thread Nano移植FinSH

文章出处:【微信公众号:电子发烧友论坛】欢迎添加关注!文章转载请注明出处。

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分