GD32移植FreeRTOS+CLI过程记录

news2024/9/25 1:23:45

背景

之前我只在STM32F0上基于HAL库和CubeMX移植FreeRTOS,但最近发现国产化替代热潮正盛,许多项目都有国产化器件指标,而且国产单片机确实比意法的便宜,所以也买了块兆易创新的GD32F303开发板,试一试它的优劣。虽然GD32F系列基本都可以和同名的ST单片机pin-to-pin替换,程序也通用,但GD自己也开发了一套库函数,基于GD的库函数开发有利于熟悉GD设计的逻辑,对于未来学习GD的RISC-V单片机也有帮助。另外,不用ST的HAL库,也能降低代码量。

FreeRTOS我们已经很熟悉了,官方又提供了FreeRTOS-Plus库,简化应用的开发。其中FreeRTOS-Plus-CLI,即 command line interpreter,命令行翻译器,可以为运行FreeRTOS的嵌入式设备提供一套命令接口,便于设备在线配置。移植FreeRTOS-Plus-CLI显然对改善嵌入式产品很有帮助。
串口运行输入和输出
(串口运行输入和输出)

GD32串口输入输出和编译器配置

大多数开发板会引出USART0,的通信管脚以便ISP下载。为便于插线,我就直接用USART0进行数据的输入输出,其中输出无需中断,输入可以用中断给出信号量。在系统初始化时,我们应该避免用户输入,故把输入和输出的初始化分开成两个函数:

/*!
    \brief      uart_init function
    \param[in]  none
    \param[out] none
    \retval     none
*/
void uart_init(void)
{
    // enable GPIO and USART RCU clock
    rcu_periph_clock_enable(USART0_RCU_PORT);
    rcu_periph_clock_enable(RCU_USART0);
    
    gpio_init(USART0_PORT,GPIO_MODE_AF_PP,GPIO_OSPEED_50MHZ,USART0_TX_PIN);
    gpio_init(USART0_PORT,GPIO_MODE_IN_FLOATING,GPIO_OSPEED_50MHZ,USART0_RX_PIN);
    
    usart_deinit(USART0);
    usart_baudrate_set(USART0,UASRT0_BAUDRATE);
    usart_word_length_set(USART0,USART_WL_8BIT);
    usart_stop_bit_set(USART0,USART_STB_1BIT);
    usart_parity_config(USART0,USART_PM_NONE);
    usart_hardware_flow_rts_config(USART0,USART_RTS_DISABLE);
    usart_hardware_flow_cts_config(USART0,USART_CTS_DISABLE);
    usart_transmit_config(USART0,USART_TRANSMIT_ENABLE);
    
    usart_enable(USART0);
}
/*!
    \brief      uart_recv_init function
    \param[in]  none
    \param[out] none
    \retval     none
*/
void uart_recv_init(void)
{
    usart_disable(USART0);
    usart_receive_config(USART0,USART_RECEIVE_ENABLE);
    usart_enable(USART0);
    nvic_irq_enable(USART0_IRQn, 15, 0);
	usart_interrupt_enable(USART0, USART_INT_RBNE);
}

这里有关中断优先级的设置在后面再讲解,此处先按下。在GD官方的许多例程中,都通过重载fputc把标准库函数的输出定向到串口,在我们这里,就按如下定向到USART0:

/* retarget the C library printf function to the USART */
int fputc(int ch, FILE *f)
{
    usart_data_transmit(USART0, (uint8_t)ch);
    while(RESET == usart_flag_get(USART0, USART_FLAG_TBE));
    return ch;
}

需要注意的是,标准库太大,直接按默认方式编译可能塞不进单片机中。为此,要在 Options for Targets 中 Target一栏下, Code Generation 卡中做如下设置,启用MicroLIB:
在这里插入图片描述

FreeRTOS的进程和信号量设计

FreeRTOS移植到GD32的文章很多,因此这里不多赘述,具体移植过程可参考
freertos学习01-移植到gd32。在这个系统中,我给输入输出各准备一个buffer,然后编写两个task分别处理单字节输入(uart_one_char_fxn())和单行输入(uart_one_line_fxn())。具体来说,串口每接收到一个字节就发生一次中断,在中断中将收到的字节移入uart_rxbuffer,然后提供一个提示收到单字节的信号量(命名为sem_uart_one_char);进程uart_one_char_fxn()接收该信号量,并在检测到回车时,释放单行信号量sem_uart_one_line供单行处理函数uart_one_line_fxn()做命令解释。以上两个信号量都设计为Counting型信号量。这样整个系统中就有两个信号量和两个线程。我为此额外多写了一个线程作为初始化线程,该线程最先被挂载,并在运行结束后自行删除(为了支持删除,采用heap4.c的堆栈结构):

#include <stdio.h>
#include <string.h>
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"
TaskHandle_t TaskStart_Handler;
void task_start_fxn(void *pvParameter)
{
    BaseType_t isPass;
    taskENTER_CRITICAL(); // ENTER_CRITICAL, 防止初始化被打断
    
    sem_uart_one_char = xSemaphoreCreateCounting(MAX_RX_BUF_LEN,0);
    if (sem_uart_one_char == NULL) printf("Error in creating sem.\n");
    sem_uart_one_line = xSemaphoreCreateCounting(10,0);
    if (sem_uart_one_char == NULL) printf("Error in creating sem.\n");
    isPass = xTaskCreate(uart_one_char_fxn, "task_one_char", 128, NULL, 1, &Task_uart_one_char);
    if (isPass != pdPASS) printf("Error in creating task.\n");
    isPass = xTaskCreate(uart_one_line_fxn, "task_one_line", 128, NULL, 2, &Task_uart_one_line);
    if (isPass != pdPASS) printf("Error in creating task.\n");
    
    uart_recv_init(); // 都初始化完成后再允许接收中断
    printf("# "); // the first # waiting for command input
    vTaskDelete(TaskStart_Handler);
    
    taskEXIT_CRITICAL();
}

串口的中断函数如下:

#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"
void USART0_IRQHandler(void)
{
    BaseType_t xHigherPriorityTaskWoken = pdFALSE;
    
    if(RESET != usart_interrupt_flag_get(USART0, USART_INT_FLAG_RBNE)){
        /* receive data */
        uart_rxbuffer[uart_rxcount] = usart_data_receive(USART0); // 数据放入rx_buffer
        xSemaphoreGiveFromISR(sem_uart_one_char,&xHigherPriorityTaskWoken); // 给信号量
        uart_rxcount++;
    }
    // portYIELD_FROM_ISR(xHigherPriorityTaskWoken); //该行注释掉,表明不要求退出中断时重新调度task
}

单字符处理task的函数如下:

void uart_one_char_fxn(void *pvParameter)
{
    while(1)
    {
        xSemaphoreTake(sem_uart_one_char,portMAX_DELAY); // 接收中断给出的信号量 sem_uart_one_char
        usart_data_transmit(USART0, uart_rxbuffer[uart_rtn_print_count]); // 数据回显
        if (uart_rtn_print_count!=0 && uart_rxbuffer[uart_rtn_print_count]==0x08) // 检测backspace
        {
            uart_rtn_print_count--;
            uart_rxcount = uart_rxcount -2;
        }
        else if (uart_rxbuffer[uart_rtn_print_count]== '\n') // 回车时释放单行信号量sem_uart_one_line
        {
            xSemaphoreGive(sem_uart_one_line);
        }
        else
        {
            uart_rtn_print_count++;
        }
    }
}

中断设置

FreeRTOS官方文档指出,对于STM32,应采用NVIC_PriorityGroup_4,即4位抢占优先级0~15:

If you are using an STM32 with the STM32 driver library then ensure all the priority bits are assigned to be preempt priority bits by calling NVIC_PriorityGroupConfig( NVIC_PriorityGroup_4 ); before the RTOS is started.
如果您使用 STM32 和 STM32 驱动器库, 请通过 调用 NVIC_PriorityGroupConfig( NVIC_PriorityGroup_4 ) 来确保所有优先级位都被指定为抢占优先级位,这一步需要 在启动 RTOS 前完成。

考虑到GD32和STM32基本一致,我们也采用这种中断优先级设置,在GD32的标准库中由下列语句实现:

nvic_priority_group_set(NVIC_PRIGROUP_PRE4_SUB0);

同样我们可以直接抄STM32的FreeRTOSConfig.h,但这里有两个宏要注意:

/* !!!! configMAX_SYSCALL_INTERRUPT_PRIORITY must not be set to zero !!!!
See http://www.FreeRTOS.org/RTOS-Cortex-M3-M4.html. */
#define configMAX_SYSCALL_INTERRUPT_PRIORITY 	191 /* equivalent to 0xb0, or priority 11. */

/* This is the value being used as per the ST library which permits 16
priority values, 0 to 15.  This must correspond to the
configKERNEL_INTERRUPT_PRIORITY setting.  Here 15 corresponds to the lowest
NVIC value of 255. */
#define configLIBRARY_KERNEL_INTERRUPT_PRIORITY	15

调用了FreeRTOS API的中断的优先级数不能小于configMAX_SYSCALL_INTERRUPT_PRIORITY 的前4bit所定义的优先级,这里是11;又不能大于configLIBRARY_KERNEL_INTERRUPT_PRIORITY 定义的优先级(这里是最小的15),具体原因该博客有讲述。回看前面我设置的串口优先级就是15:

nvic_irq_enable(USART0_IRQn, 15, 0); // 对 NVIC_PRIGROUP_PRE4_SUB0, 最后一个参数无意义

其实我最初设置成nvic_irq_enable(USART0_IRQn, 0, 0),结果发生中断时调用xSemaphoreGiveFromISR(),被甩到 FreeRTOS底层接口代码port.c中的函数vPortValidateInterruptPriority(void)中,无法继续运行,原因也在这段代码的注释中给出:

#if( configASSERT_DEFINED == 1 )
	void vPortValidateInterruptPriority( void )
	{
	uint32_t ulCurrentInterrupt;
	uint8_t ucCurrentPriority;
		/* Obtain the number of the currently executing interrupt. */
		ulCurrentInterrupt = vPortGetIPSR();
		/* Is the interrupt number a user defined interrupt? */
		if( ulCurrentInterrupt >= portFIRST_USER_INTERRUPT_NUMBER )
		{
			/* Look up the interrupt's priority. */
			ucCurrentPriority = pcInterruptPriorityRegisters[ ulCurrentInterrupt ];

			/* The following assertion will fail if a service routine (ISR) for
			an interrupt that has been assigned a priority above
			configMAX_SYSCALL_INTERRUPT_PRIORITY calls an ISR safe FreeRTOS API
			function.  ISR safe FreeRTOS API functions must *only* be called
			from interrupts that have been assigned a priority at or below
			configMAX_SYSCALL_INTERRUPT_PRIORITY. 
			... */
			configASSERT( ucCurrentPriority >= ucMaxSysCallPriority ); // 运行卡死到此处
		}
		configASSERT( ( portAIRCR_REG & portPRIORITY_GROUP_MASK ) <= ulMaxPRIGROUPValue );
	}

#endif /* configASSERT_DEFINED */

CLI移植和单行处理函数设计

FreeRTOS-Plus-CLI可以从FreeRTOS官方包中的FreeRTOS-Plus文件夹里找到,其实只有两个文件FreeRTOS_CLI.c和FreeRTOS_CLI.h。将这两个文件加入Keil工程中的代码目录和include目录后就可以使用。FreeRTOS_CLI的命令行解释器函数原型如下:

BaseType_t FreeRTOS_CLIProcessCommand( const char * const pcCommandInput, char * pcWriteBuffer, size_t xWriteBufferLen  );

其中,pcCommandInput是输入的命令,pcWriteBuffer是执行输出的缓存区,xWriteBufferLen 则是容许写入缓存区的长度。其返回值有pdTRUEpdFALSE两种可能,pdTRUE表示还没输出完,让我们先把pcWriteBuffer里的数据都打印掉,保留pcCommandInput,然后再执行一次该函数;pdFALSE则表示输出已结束,不必再执行。这个设计乍看是有点反直觉的,基于此我编写的单行处理task如下:

#include <stdio.h>
#include <string.h>
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"
#include "FreeRTOS_CLI.h"
TaskHandle_t Task_uart_one_line;
void uart_one_line_fxn(void *pvParameter)
{
    uint16_t i;
    BaseType_t CLIProcessCommandRet;
    while(1) 
    {
        xSemaphoreTake(sem_uart_one_line,portMAX_DELAY);
        // 保证FreeRTOS_CLIProcessCommand()至少执行一次
        CLIProcessCommandRet = pdTRUE;
        // disable USART0 interrupt
        usart_interrupt_disable(USART0, USART_INT_RBNE);
        
        for (i=0;i<uart_rxcount;i++) command_buffer[i] = uart_rxbuffer[i]; // 复制命令到command_buffer
        command_buffer[i-1] = 0;
        // 清零输入buffer
        uart_rxcount = 0;
        uart_rtn_print_count = 0;
        while(CLIProcessCommandRet == pdTRUE) // 检测是否要执行CLI
        {
            CLIProcessCommandRet = FreeRTOS_CLIProcessCommand( (char *)command_buffer, (char *)write_buffer, MAX_TX_BUF_LEN);
            printf(write_buffer); // 输出缓存
        }
        printf("# "); // 类似linux的输入提示符
        // re-enable USART0 interrupt
        usart_interrupt_enable(USART0, USART_INT_RBNE);
    }
}

典型需要输出多行的命令就是FreeRTOS-Plus-CLI官方提供的help命令。

自己设计的echo命令

FreeRTOS-Plus-CLI允许开发者自己加入自定义的命令,我也加入echo命令,验证移植是否成功。echo,回传,是最简单验证命令的输入输出功能是否正常的一种功能。自定义命令的回调函数原型应满足:

typedef BaseType_t (*pdCOMMAND_LINE_CALLBACK)( char *pcWriteBuffer, size_t xWriteBufferLen, const char *pcCommandString );

其中pcWriteBufferxWriteBufferLen就是翻译器函数FreeRTOS_CLIProcessCommand()传入的同名参数,供我们自己写入;pcCommandString也将传入FreeRTOS_CLIProcessCommand()的最后一个参数pcCommandInput,供用户程序提取输入参数。我编写的echo回答函数比较简单:

#include <string.h>
#include "FreeRTOS.h"
#include "FreeRTOS_CLI.h"
BaseType_t prvEchoCommand ( char *pcWriteBuffer, size_t xWriteBufferLen, const char *pcCommandString )
{
    char * parameter;
    BaseType_t pxParameterStringLength;
    
    parameter = (char *)FreeRTOS_CLIGetParameter(pcCommandString,1,&pxParameterStringLength);
    strncpy(pcWriteBuffer,parameter,xWriteBufferLen);
    strncat(pcWriteBuffer,"\r\n\r\n\0",5);
    return pdFALSE;
}

命令需要注册,注册前先在需要定义出如下的结构体:

const CLI_Command_Definition_t xEchoCommand =
{
	"echo",
	"echo:\r\n Echo the parameter(s).\r\n\r\n",
	prvEchoCommand,
	-1 // -1 allow a variable number of parameters
};

其中第一行是命令原文字符,第二行是help输出信息,第三行是回调函数,第四行指定该命令要输入多少个参数,如果是-1则不限参数数目。我写的echo其实是接收了第一个参数开始的指针,然后把后面整个字符串(到\0为止)全部输出完了事,简单但满足的echo的需要。命令注册也是在初始化定义其他task和信号量时,通过调用CLI的API完成:

isPass = FreeRTOS_CLIRegisterCommand(&xEchoCommand);
if (isPass != pdPASS) printf("Error in registering command.\n");

至此,一个具备基础功能的FreeRTOS+CLI模板就搭好了,可以根据不同应用场景再添加额外功能使用。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1415212.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【开源】基于JAVA的班级考勤管理系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 系统基础支持模块2.2 班级学生教师支持模块2.3 考勤签到管理2.4 学生请假管理 三、系统设计3.1 功能设计3.1.1 系统基础支持模块3.1.2 班级学生教师档案模块3.1.3 考勤签到管理模块3.1.4 学生请假管理模块 3.2 数据库设…

【系统备份/迁移】解决克隆win10系统分区后,进系统黑屏、有鼠标指针(无需修改注册表)

【解法】简单来说就是&#xff0c;在PE系统中修复引导&#xff0c;修复成功后再进入系统就正常了。 1、问题 笔者通过DiskGenius克隆系统分区来备份自己的win10系统。克隆完成后&#xff0c;进入新系统里&#xff0c;发现是黑屏&#xff0c;移动鼠标时可以看到鼠标指针&#x…

通过铭文赛道的深度链接,XDIN3 与 opBNB 的双向奔赴

​进入到 2024 年以来&#xff0c;随着铭文市场基建设施的不断完善&#xff0c;铭文正在被赋予捕获价值与流动性的能力&#xff0c;并且铭文投资者们也正在趋于理性&#xff0c;这也意味着铭文赛道正在向价值回归的全新方向发展。 XDIN3 是推动铭文资产捕获价值的重要基建设施&…

第7章 面向对象基础(下)

第7章 面向对象基础&#xff08;下&#xff09; 学习目标 会区分静态的类变量和非静态的实例变量 会区分静态的类方法和非静态的实例方法 了解类初始化 认识枚举类型 会使用枚举类型 认识包装类 会使用包装类进行处理字符串 会分析包装类的相关面试题 能够声明抽象类 能够说出…

ZigBee学习——浅析协议栈

✨记录学习过程 文章目录 一、初识OSAL1.1 Z-Stack和Zigbee的OSAL是什么关系&#xff1f;1.2 OSAL可以解决Z-stack在不同厂商的芯片上的使用吗&#xff1f; 二、协议栈运行机制2.1 初始化涉及内容2.2 初始化过程 一、初识OSAL OSAL&#xff0c;全称是操作系统抽象层&#xff0…

【驱动系列】C#获取电脑硬件显卡核心代号信息

欢迎来到《小5讲堂》&#xff0c;大家好&#xff0c;我是全栈小5。 这是《驱动系列》文章&#xff0c;每篇文章将以博主理解的角度展开讲解&#xff0c; 特别是针对知识点的概念进行叙说&#xff0c;大部分文章将会对这些概念进行实际例子验证&#xff0c;以此达到加深对知识点…

代码随想录算法训练营day4 | 链表(2)

一、LeetCode 24 两两交换链表中的节点 题目链接&#xff1a;24.两两交换链表中的节点https://leetcode.cn/problems/swap-nodes-in-pairs/ 思路&#xff1a;设置快慢指针&#xff0c;暂存节点逐对进行交换。 代码优化前&#xff1a; /*** Definition for singly-linked list…

总结和考试

总结和考试 1. 代码规范1.1 名称1.2 注释1.3 todo1.4 条件嵌套1.5 简单逻辑先处理1.6 循环1.7 变量和值 2.知识补充2.1 pass2.2 is 比较2.3 位运算 3.阶段总结4.考试题 1. 代码规范 程序员写代码是有规范的&#xff0c;不只是实现功能而已。 1.1 名称 在Python开发过程中会创…

EndNote20 添加GBT7714文献格式

GBT 7714格式是中国国家标准《文后参考文献著录规则》的规定&#xff0c;用于指导学术论文、期刊文章等文献的参考文献著录。GBT 7714标准规定了参考文献的格式、内容和著录要求&#xff0c;以确保文献的一致性和标准化。 在EndNote 20中&#xff0c;若需要按照GBT 7714格式在W…

JavaScript 之 作用域变量提升闭包

一、JavaScript 代码的执行 浏览器内核是由两部分组成的&#xff0c;以 webkit 为例 WebCore&#xff1a;负责HTML解析、布局、渲染等等相关的工作JavaScriptCore&#xff1a;解析、执行 JavaScript 代码 另外一个强大的 JavaScript 引擎就是 V8 引擎 二、深入 V8 引擎原理 …

Django介绍

一、介绍 Django是Python语言中的一个Web框架,Python语言中主流的web框架有Django、Tornado、Flask 等多种 优势:大而全,框架本身集成了ORM、模型绑定、模板引擎、缓存、Session等功能,是一个全能型框架,拥有自己的Admin数据管理后台,第三方工具齐全,性能折中 缺点:…

用ChatGPT写申请文书写进常春藤联盟?

一年前&#xff0c;ChatGPT 的发布引发了教育工作者的恐慌。现在&#xff0c;各大学正值大学申请季&#xff0c;担心学生会利用人工智能工具伪造入学论文。但是&#xff0c;聊天机器人创作的论文足以骗过大学招生顾问吗&#xff1f; ChatGPT简介 ChatGPT&#xff0c;全称聊天生…

C++:引用

目录 概念&#xff1a; 引用的使用格式&#xff1a; 引用特性&#xff1a; 常引用 使用场景&#xff1a; 1、做参数 二级指针时的取别名 一级指针取别名 一般函数取别名 2、做返回值 函数返回值的原理&#xff1a; 引用的返回值使用&#xff1a; 引用和指针的对比&…

基于 SpringBoot+Vue 的免税商品商城系统的研究与实现

博主介绍&#xff1a;✌程序员徐师兄、7年大厂程序员经历。全网粉丝30W、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;…

免费电视TV盒子软件,好用的免费电视盒子软件大全,免费电视盒子APP大全,2024最新整理

1、TVbox下载地址、影视接口、配置教程 下载地址 TVbox TVbox可用接口地址合集 注&#xff1a;接口均来源于互联网收集分享&#xff01;所有接口都是经过测试的&#xff0c;如果出现加载失败等情况&#xff0c;可能是因为接口针对的盒子有兼容问题&#xff0c;可以多试试几…

(七)springboot实战——springboot3集成R2DBC实现webflux响应式编程服务案例

前言 本节主要内容是关于使用新版springboot3集成响应式数据库R2DBC,完成响应式web服务案例。需要注意的是&#xff0c;此次项目使用的JDK版本是JDK17&#xff0c;springboot版本使用3.2.2版本&#xff0c;数据库使用关系型数据库mysql。WebFlux 是一个基于响应式编程模型的框…

redis过期事件监听、可以做延时任务 第二篇(简单)

在使用redis时&#xff0c;所有的key都要设置过期时间&#xff0c;过期之后&#xff0c;redis就会把对应的key清除掉。 此方法可以监听redis的key失效&#xff0c;在失效时做一些逻辑处理 redis过期监听 不像mq有保证 不推荐用来弄需要有保证的业务 现象&#xff1a; redis …

P1045 [NOIP2003 普及组] 麦森数题解

题目 形如的素数称为麦森数&#xff0c;这时P一定也是个素数。但反过来不一定&#xff0c;即如果P是个素数&#xff0c;不一定也是素数。到1998年底&#xff0c;人们已找到了37个麦森数。最大的一个是P3021377&#xff0c;它有909526位。麦森数有许多重要应用&#xff0c;它与…

Linux版本下载Centos操作

目录 一、Centos7 二、下载Centos7镜像 三、下载Centos7 买了个硬件安装裸机&#xff08;一堆硬件&#xff09; 把安装盘放到虚拟机里面&#xff0c;给机器加电 配置设置 ​编辑 网络配置 开启网络功能 四、安装linux客户端 Xshell是什么 Xshell使用&#xff08;连接…

GLog开源库使用

Glog地址&#xff1a;https://github.com/google/glog 官方文档&#xff1a;http://google-glog.googlecode.com/svn/trunk/doc/glog.html 1.利用CMake进行编译&#xff0c;生成VS解决方案 &#xff08;1&#xff09;在glog-master文件夹内新建一个build文件夹&#xff0c;用…