基于stm32cubeMX配置生成RT-thread-nano的工程、实现shell指令串口控制台(nucleo-g070rb开发板为例)

目录

 

前言

准备

nucleo-g070RB开发板的简单介绍

cube配置

IAR工程配置

执行程序

整个移植工作总结


前言

   RT-thread近年来频繁出现在嵌入式开发者的视野中,他们的大力推广以及技术支持受到了很多人的广泛支持与关注。本人在去年(还是一个职业小白)也参加了rt的教学实验,体验过env工具的方便以及如何结合stm32cubemx实现快速建立rt工程。不过,rt官方主要打造针对一些较高资源的ic的开发生态环境,比如stm32f407或者stm32f103zet6等高ram和高rom的ic(品牌开发板标配)。但是实际工作中,对于我们公司开发的ic并不需要很大资源的ic去开发,比如物联网项目我们往往是只用stm32f030或者很高资源用的stm32f103RB等,其最大rom保持在128k以下,甚至更低,而ram更加是只需要20k。可是基于rt的开发生态通过env工具建立工程时,当移植完部分组件之后还未开发应用时便几乎超过了20k的ram了。因此往往我们还是拿rtthread的nano精简版本进行开发。原因是其配合stm32cubemx开发太方便了。

准备

软件:IAR、stm32cubeMX

硬件:nucleo-g070RB开发板(参加rt峰会送的,随便拿一个开发板来搭建环境)

nucleo-g070RB开发板的简单介绍

该开发板板载了stlink烧录调试器,因此我们只需要准备一个miniusb数据线即可实现电脑pc和开发板的调试对接。又因为其stlinkv2-1版本又支持扩展串口功能,这个开发板实现了stlink拓展串口连接到stm32g070rb主控芯片的uart2串口的引脚。因此我们不仅不需要自备烧录器,也不需要自备ch340等串口硬件工具,只需要直接准备一根micro数据线完成烧录、调试及串口通讯。其他具体资源介绍可参考以下链接:nucleo-g070rb开发板数据手册

发牢骚、这里本人想吐槽一下:给那些职业小白和大学生谈的话,很多开发板(正点、野火等那些配套齐全的开发板)因为帮忙做好了太多资源,导致许多大学生理解不了硬件接线原理,实际上开发板不可能用作产品而使用,产品是不需要板载烧录调试器的,产品往往是由最小系统根据需求不断拓展功能而设计的,而最小系统肯定不需要烧录器,因为批量生产上,不是拿着电脑给一个又一个硬件烧录的,而是有专门的烧录编辑器(通过电脑端把程序导入到烧录编辑器中)去烧录的,同时烧录编辑器还能附有烧录id的功能(说白了就是能够选择一个flash地址烧写id,本质和烧录程序一样,需要保证程序flash不覆盖你写的id地址的位置),给每个设备赋予一个唯一id标识,不少大学生玩了正点的f3,f4系列带有配套齐全的开发板后,理解不了烧录原理,有些是st-link烧录、有些又是jlink烧录、jlink又分有swd和jtag烧录方式。(本人就是个例子)。工作了才明白中间实际有很多需要我们去熟悉的。具体原理和区别就不多说了,网上很多也有。

回归正题,上面说过,只需要一根数据线就可以开发了。插到电脑上如下图:

设备管理器已经检测到了stlink硬件。

cube配置

  • 打开cube新建工程,本人选择stm32f070rb型号

 

  • 工程预览

 

  • 时钟源选择,外部时钟源本人全部失能,准备启用内部时钟源配置。

  • SW调试使能,勾选Serial Wire选项,基础时钟选择系统滴答systick

  • 串口异步配置,默认配置。

  • 添加RT软件包,找到软件软件包选项点击,然后在选择组件

  • 本人已经下载好了rt组件,勾选其组件,点击ok退出。

rt组件就出现在cube配置下,勾选它然后如下配置(根据需求参考)

  • 再到NVIC配置取消掉硬件错误中断,因为rt组件会帮你配置,不消掉iar工程编译会报错,也可以iar工程注释掉其中断。

  • 时钟树配置,内部高速时钟源,选择最快的时钟频率64Mhz,不考虑低功耗。

  • 工程管理,添加工程名

  • 代码生成配置,勾选如下图所示的箭头,这样驱动文件更加模块化分配到各个c文件中。

  • 预设置,把两个初始化去掉,这样main里就不会生成这两个初始化调用,因为到时rt配置的时候会重复,不勾的话最终在工程还是要删除掉,否则程序功能不正常。

  • 点击生成工程GENEEATE CODE,生成IAR工程。

  • 打开open project,即可开始配置IAR工程。
     

IAR工程配置

第一步,修改Options配置,较新的版本cube默认不配置选型。配置选择STM32G070RB,之后即可编译。

但是这样直接编译运行,是没有如官方所示会打印RT logo的。原因是还没实现finsh接口,实现finsh接口的函数有:

void rt_hw_console_output(const char *str)  //输出接口,finsh里面一些debug信息依赖这个接口打印。

char rt_hw_console_getchar(void) //输入接口,串口接收实现,看他返回的是一个char说明只需要实现读取串口一个数据的功能

官方推荐rt_hw_console_output驱动如下:

void rt_hw_console_output(const char *str) {
    rt_size_t i = 0, size = 0;
    char a = '\r';
    __HAL_UNLOCK(&huartx);
    size = rt_strlen(str);
    for (i = 0; i < size; i++)
    {
        if (*(str + i) == '\n') {
            HAL_UART_Transmit(&huartx, (uint8_t *)&a, 1, 1);
        }
        HAL_UART_Transmit(&huartx, (uint8_t *)(str + i), 1, 1);
    } 
}

接收部分rt_hw_console_getchar官方两个例程一个是用查询方法来接收、一个是中断结合信号量接收。

中断结合信号量接收参考官方链接如下:

finsh控制台实现

但是,本人觉得两者都存在缺陷:查询方法的缺陷,实时系统多线程的环境下,可能会有查漏的情况,所以在越复杂的功能环境下越可能漏接数据。而中断信号量结合的方法,需要用到回环缓冲区,类似fifo缓冲区。代码量较大,实际上信号量加缓冲区的功能不就是消息队列功能吗,所以本人再次基础上做了一个翻新的改动。这样代码则更加简洁清晰。代码如下:

char rt_hw_console_getchar(void) {
    char ch ;
    //rt_sem_take(&shell_rx_sem, RT_WAITING_FOREVER); //接 收 信 号 量
    //ch = huartx.Instance->DR & 0xff; //读 取 数 据
    if(rt_mq_recv(&shell_rx_mq, &ch,1, RT_WAITING_FOREVER) == RT_EOK) {
        
    }
    return ch;
}

void USARTx_IRQHandler(void) {
    int ch = -1;
    if((__HAL_UART_GET_FLAG(&huartx, UART_FLAG_RXNE) != RESET) &&
    (__HAL_UART_GET_IT_SOURCE(&(huartx), UART_IT_RXNE) != RESET)) //接 收 中 断 
    {
        __HAL_UART_CLEAR_FLAG(&(huartx), UART_FLAG_RXNE); //清 除 中 断
        __HAL_UART_CLEAR_PEFLAG(&(huartx)) ;
#if defined(STM32G0xx_HAL_H)
        ch = huartx.Instance->RDR & 0xff; //读 取 数 据
#elif defined(__STM32F1xx_HAL_H)
        ch = huartx.Instance->DR & 0xff; //读 取 数 据
#else
#error no sure ic type
#endif
        rt_mq_send(&shell_rx_mq, &ch,1);

    } 
    __HAL_UART_CLEAR_PEFLAG(&huartx) ;
}

对比官方驱动,我们不需要实现ringbuf环缓冲区等功能。

上面代码中的shell_rx_mq消息队列需要初始化,实现finsh驱动函数之后,需要对串口进行初始化实现如下(直接通过cube的生成usart.c代码中复制出来):

void lc_shell_uart_init(void) {

    //rt_sem_init(&(shell_rx_sem), "shell_rx", 0, 0);
    rt_mq_init(&shell_rx_mq,
                        "shell_rx",
                        shell_rx_buf,                                            /* 内存池指向 shell_rx_buf */
                        1,                                                       /* 每个消息的大小是 1 字节 */
                        sizeof(shell_rx_buf),                                   /* 内存池的大小是 shell_rx_buf 的大小 */
                        RT_IPC_FLAG_FIFO);                                       /* 如果有多个线程等待,按照先来先得到的方法分配消息 */
    MX_USART2_UART_Init();
}

void MX_USART2_UART_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    __HAL_RCC_USART2_CLK_ENABLE();

    __HAL_RCC_GPIOA_CLK_ENABLE();
    /**USART2 GPIO Configuration
    PA2     ------> USART2_TX
    PA3     ------> USART2_RX
    */
    GPIO_InitStruct.Pin = DEBUG_TX_Pin;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
    HAL_GPIO_Init(DEBUG_TX_GPIO_Port, &GPIO_InitStruct);

    GPIO_InitStruct.Pin = DEBUG_RX_Pin;
    GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
    GPIO_InitStruct.Pull = GPIO_PULLUP;
    HAL_GPIO_Init(DEBUG_RX_GPIO_Port, &GPIO_InitStruct);

    huartx.Instance = USART2;
    huartx.Init.BaudRate = 115200;
    huartx.Init.WordLength = UART_WORDLENGTH_8B;
    huartx.Init.StopBits = UART_STOPBITS_1;
    huartx.Init.Parity = UART_PARITY_NONE;
    huartx.Init.Mode = UART_MODE_TX_RX;
    huartx.Init.HwFlowCtl = UART_HWCONTROL_NONE;
    huartx.Init.OverSampling = UART_OVERSAMPLING_16;
    if (HAL_UART_Init(&huartx) != HAL_OK)
    {
//        Error_Handler();
    }

    __HAL_UART_ENABLE_IT(&huartx, UART_IT_RXNE);//中断使能
    HAL_NVIC_EnableIRQ(USART2_IRQn);
    HAL_NVIC_SetPriority(USART2_IRQn, 3, 3);
}

然后在board.c中的初始化rt_hw_board_init函数添加lc_shell_uart_init()函数即可。

cube生成RT组件配置的潜规则:前面提到时钟源及时钟配置初始化SystemClock_Config()就是需要替换掉rt_hw_board_init()里的systemcoreclockupdate()函数的。同时需要把主函数里的HAL_Init()移动到rt_hw_board_init()里来。因为实际上main函数并不是启动入口,IAR启动入口会定位到__low_level_init(void)这里。

rtthread_startup其调用结构大致如下:

rtthread_startup()

{

    rt_hw_interrupt_disable();

    rt_hw_board_init();//因此时钟初始化需要在里面重新配置(用cube生成配置代替其rt的默认配置),同时HAL_init硬件初始化从main中也搬运过来。

    。。。

    rt_application_init();----》main线程

    。。。

    rt_system_scheduler_start();//启用系统调度器

}

最后还需要在rtconfig.h头文件添加一个RT_USING_FINSH宏

最后在main主函数里需要添加delay延时,保证让出运行线程给其他任务执行(finsh任务)

执行程序

当有些时候想在其他串口助手调试也可以,只是没那么可观了,但是其他串口工具有保存指令功能,做项目时备份指令比较方便。

 

整个移植工作总结

1.完成cube配置生成工程,包含串口、时钟、调试使能、RT启用、硬件中断弃用。

2.IAR工程:添加RT_USING_FINSH宏、实现finsh的输出输入函数两个函数且还需实现串口接收中断函数、实现串口初始化函数。

3.时钟配置、硬件初始化搬运到board.c中,添加串口初始化函数。

当然模块化配置还是需要有的,因此本人已经将finsh实现的内容模块化成一个组件中

同时配有移植使用说明:

 

需要用时,将这5个文件添加到项目中,现已实现了stm32f103xx或者stm32g0xx的支持(实际添加支持特别简单,改动特别少,一般只改头文件包含和部分寄存器,比如g070的串口接收寄存器和f103的接收寄存器变量名不同,代码中已经体现了),只需要修改lc_shell_cfg.h文件配置之后,在应用中添加lc_shell_uart_init()即可快速移植成功。链接如下:

https://download.csdn.net/download/fangjiaze444/12858497

 

 

 

 

 

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值