前言
距离上一次更新GD32系列的文章已经过了一年有余。按照之前的想法,仅仅介绍到GD32中常用的模块就结束了。在后续的开发中,有幸再次能使用这颗IC作为主控。所以既为了自己做个随笔,也为方便各位同行或是同学借鉴,这段时间我会编写几篇文章主要以GD32F303为主控对FreeRTOS、LVGL、FreeModbus和CJson的移植和简单使用方法。因为使用了RTOS,后面三个开源库就不再介绍单独的裸机移植方法。
接下来我们开始正式进入RTOS的移植。
FreeRTOS
- 一款开源的实时操作系统,能方便的编写业务需求而较少的考虑模块间存在的耦合问题,相比于裸机对CPU的管理更有效,广泛运用于各类电子行业。
- 这里不作盲目推荐,RTOS不是只有FreeRTOS,也不是所有的方向上的产品开发都需要使用RTOS,比如一些对实时性要求极高(包含极端保护功能)的产品用上RTOS基本会死的透透的。
- 对RTOS的详细介绍可自行查阅网上资料,这里不再过多赘述。
移植准备
移植前请准备好以下内容:
- GD32F303包含其对应标准库的keil工程
工程可使用官方的例程或可按照GD32F303调试小记(零)之工程创建与编译创建。 - FreeRTOS源码
我使用的是V10.4.0,源码在GITHUB上。其内核源码我替换成了V10.4.3。 - 一块由GD32F303主控的硬件板子,并包含对应的输入输出控件。
移植步骤
一、FreeRTOS并入KEIL工程
- 解压文档
2. 重点关注Source目录下的文件
- FreeRTOS移植的核心源码就是include文件夹、7个C文件和portable文件夹中的部分文件
- 在工程目录中新建FreeRTOS文件夹并分好类别
- 类别分好后,将源码中include文件夹和7个C文件无增减的分别复制到工程中FreeRTOS->include文件夹和FreeRTOS->Src文件夹
- 接下来还剩port文件夹和1个FreeRTOSConfig.h的头文件
- 我们先解决头文件的问题,这里可以从之前下载的官方源码例子中复制,也可套用我下面的头文件
#ifndef FREERTOS_CONFIG_H
#define FREERTOS_CONFIG_H
/*-----------------------------------------------------------
* Application specific definitions.
*
* These definitions should be adjusted for your particular hardware and
* application requirements.
*
* THESE PARAMETERS ARE DESCRIBED WITHIN THE 'CONFIGURATION' SECTION OF THE
* FreeRTOS API DOCUMENTATION AVAILABLE ON THE FreeRTOS.org WEB SITE.
*
* See http://www.freertos.org/a00110.html
*----------------------------------------------------------*/
#define configUSE_IDLE_HOOK 0
#define configUSE_TICK_HOOK 0
#define configCPU_CLOCK_HZ ( ( unsigned long ) 108000000 )
#define configTICK_RATE_HZ ( ( TickType_t ) 1000 )
#define configMAX_PRIORITIES ( 5 )
#define configMINIMAL_STACK_SIZE ( ( unsigned short ) 128 )
#define configTOTAL_HEAP_SIZE ( ( size_t ) ( 4 * 1024U ) )
#define configMAX_TASK_NAME_LEN ( 16 )
#define configUSE_TRACE_FACILITY 0
#define configUSE_16_BIT_TICKS 0
#define configUSE_PREEMPTION 1
#define configUSE_TIME_SLICING 1
#define configIDLE_SHOULD_YIELD 1
/* Co-routine definitions. */
#define configUSE_CO_ROUTINES 0
#define configMAX_CO_ROUTINE_PRIORITIES ( 2 )
/* Set the following definitions to 1 to include the API function, or zero
to exclude the API function. */
#define INCLUDE_vTaskPrioritySet 1
#define INCLUDE_uxTaskPriorityGet 1
#define INCLUDE_vTaskDelete 1
#define INCLUDE_vTaskCleanUpResources 0
#define INCLUDE_vTaskSuspend 1
#define INCLUDE_vTaskDelayUntil 1
#define INCLUDE_vTaskDelay 1
/* This is the raw value as per the Cortex-M3 NVIC. Values can be 255
(lowest) to 0 (1?) (highest). */
#define configKERNEL_INTERRUPT_PRIORITY 255
/* !!!! 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
#define vPortSVCHandler SVC_Handler
#define xPortPendSVHandler PendSV_Handler
//#define xPortSysTickHandler SysTick_Handler
#endif /* FREERTOS_CONFIG_H */
- 还剩一个port文件夹,port文件夹需要解决两个问题,RTOS内部的内存管理以及跟我们实际用到的IC硬件上的接口
- 内存管理部分,官方提供了5种方法,我们只使用第4种,不过我们还是都复制过来
- IC硬件接口文件的选择又取决于两个问题:我们用的芯片内核是什么?对于这款IC我们使用的开发(编译)环境又是什么?
- 对于上述两个问题可以好好思考下,理解了这两个,即使再换一颗你没用过的芯片想跑上FreeRTOS都不是问题。
- GD32F303这款我直接说答案了,在FreeRTOS里的分类是ARM-CortexM4不带MPU的核,由于Keil工程里我使用的是V5编译器,移植的文件选择RVDS里的文件。
- 将所有需要的源码复制至工程里后,我们要在Keil工程同样配置关联这些文件
- 先确认自己用的编译器版本是否是V5
- 创建工程目录里的文件夹并添加文件
- 添加上述文件的路径
- 做完上述步骤,还剩下port.c中几个重要的接口函数
-
port.c中有3个比较重要的函数
-
我们需要将FreeRTOS与MCU相关的接口函数名关联起来,网上方法有很多,这里选择最便捷的方法通过宏对函数做个重命名
-
在gd32f30x_it.c中注释掉SVC_Handler();PendSV_Handler();
/*!
\brief this function handles PendSV exception
\param[in] none
\param[out] none
\retval none
*/
//void PendSV_Handler(void)
//{
//}
/*!
\brief this function handles PendSV exception
\param[in] none
\param[out] none
\retval none
*/
//void PendSV_Handler(void)
//{
//}
- 在gd32f30x_it.c中的SysTick_Handler()添入xPortSysTickHandler();
- 这里也建议让INCLUDE_xTaskGetSchedulerState 这个宏值为 1,判断RTOS是否先创建任务,再执行调度。
extern void xPortSysTickHandler(void);
/*!
\brief this function handles SysTick exception
\param[in] none
\param[out] none
\retval none
*/
void SysTick_Handler(void)
{
if(0U != delay)
delay--;
#if (INCLUDE_xTaskGetSchedulerState == 1 )
if (xTaskGetSchedulerState() != taskSCHEDULER_NOT_STARTED)
{
#endif
xPortSysTickHandler();
#if (INCLUDE_xTaskGetSchedulerState == 1 )
}
#endif
}
- 调整启动文件中的堆栈大小
- 这里我调的都比较大,实际可以再调小些,我用的这颗IC有96k的SRAM,所以可劲躁
- 完成上述步骤后,添加对应的头文件,编译就能正常使用FreeRTOS了
二、板子验证
- 先创建3个任务
- 创建LCD_BG_task任务,用于调整屏背光的亮灭,产生呼吸效果
- lcd_backlight_breathe()用于调整背光的PWM
- xSemaphoreGive()用与释放一个二值信号量,让任务同步
void LCD_BG_task(void * pvParameters)
{
const TickType_t xTicksToWait = pdMS_TO_TICKS( 1UL );
for(;;)
{
if(pdTRUE == lcd_backlight_breathe())
xSemaphoreGive(xBinarySemaphore);
vTaskDelay(xTicksToWait);
}
}
- 创建Usart_Send_task任务,用于定时发送串口信息
void Usart_Send_task(void * pvParameters)
{
const TickType_t xTicksToWait = pdMS_TO_TICKS( 1000UL );
TickType_t xLastWakeTime;
/* 获得当前的Tick Count */
xLastWakeTime = xTaskGetTickCount();
while(1)
{
Usartx_Transmit_DMA(USART1,(uint8_t *)"FreeRTOS_1S_Task!\n",18);
xTaskDelayUntil(&xLastWakeTime,xTicksToWait);
}
}
- 创建Lcd_Refresh_task任务,用于刷新屏幕显示内容
- xSemaphoreTake()用于获取任务1给到的同步信息,在灭屏时刷新显示内容
- LCD_Clear()用于刷屏,将屏变成指定的颜色
void Lcd_Refresh_task(void * pvParameters)
{
static uint8_t i=0;
while(1)
{
if( xSemaphoreTake(xBinarySemaphore, portMAX_DELAY) == pdTRUE )
{
if(i<2) i++;
else i=0;
if(i==0) LCD_Clear(0xF800); /* 红色 */
else if(i==1) LCD_Clear(0x001F); /* 蓝色 */
else if(i==2) LCD_Clear(0xFFE0); /* 黄色 */
}
}
}
- 主函数
- 创建任务并执行调度
#include "gd32f30x.h"
#include "gd32f30x_libopt.h"
#include "main.h"
FLAG_BIT Module;
extern uint32_t delay;
uint8_t lcd_backlight_breathe(void);
#define LCD_BG_TASK_PRIO ( tskIDLE_PRIORITY + 2 )
#define UART_TASK_PRIO ( tskIDLE_PRIORITY + 2 )
#define LCD_REFRESH_TASK_PRIO ( tskIDLE_PRIORITY + 2 )
TaskHandle_t LCDBGTask_Handle;
void LCD_BG_task(void * pvParameters);
void Usart_Send_task(void * pvParameters);
void Lcd_Refresh_task(void * pvParameters);
/* 队列句柄, 创建队列时会设置这个变量 */
QueueHandle_t xQueue;
/* 二进制信号量句柄 */
SemaphoreHandle_t xBinarySemaphore;
int main()
{
SystemTick_Init();
SystemClock_Reconfig();
GPIO_Init();
Timer3_Init();
SPIx_Init();
USARTx_Init();
// ADCx_Init();
DMA_Init();
NVIC_Init();
LCD_Init();
/* 创建二进制信号量 */
xBinarySemaphore = xSemaphoreCreateBinary( );
if( xBinarySemaphore != NULL )
{
xTaskCreate(LCD_BG_task, "LcdBG_Task", configMINIMAL_STACK_SIZE, NULL, LCD_BG_TASK_PRIO, (TaskHandle_t* )&LCDBGTask_Handle);
xTaskCreate(Usart_Send_task, "Uart_Task", configMINIMAL_STACK_SIZE, NULL, UART_TASK_PRIO, NULL);
xTaskCreate(Lcd_Refresh_task,"LCD_Refresh_Task",configMINIMAL_STACK_SIZE, NULL, LCD_REFRESH_TASK_PRIO, NULL);
vTaskStartScheduler();
}
else
{
}
while(1)
{
}
}
- 最终效果
- 任务2发送的串口数据在上位机的效果如下:
- 任务1、3在实物板子上的显示效果如下:
GD32F303+FreeRTOS刷屏显示
- 这里留个思考,视频里屏的颜色变化为绿->蓝->黄->红->蓝->黄->红,为什么不是绿->红->蓝->黄->红->蓝->黄->红?
工程文件
- 这里也提供本文的工程源码,不免费提供,有需要的可以下载