开发板移植RTOS操作系统,RTOS操作系统适配开发板整理大全_use_rtos(1)

img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Go语言开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以戳这里获取

http://www.FreeRTOS.org/FAQHelp.html - Having a problem?  Start by reading
the FAQ page "My application does not run, what could be wrong?".  Have you
defined configASSERT()?

http://www.FreeRTOS.org/support - In return for receiving this top quality
embedded software for free we request you assist our global community by
participating in the support forum.

http://www.FreeRTOS.org/training - Investing in training allows your team to
be as productive as possible as early as possible.  Now you can receive
FreeRTOS training directly from Richard Barry, CEO of Real Time Engineers
Ltd, and the world's leading authority on the world's leading RTOS.

http://www.FreeRTOS.org/plus - A selection of FreeRTOS ecosystem products,
including FreeRTOS+Trace - an indispensable productivity tool, a DOS
compatible FAT file system, and our tiny thread aware UDP/IP stack.

http://www.FreeRTOS.org/labs - Where new FreeRTOS products go to incubate.
Come and try FreeRTOS+TCP, our new open source TCP/IP stack for FreeRTOS.

http://www.OpenRTOS.com - Real Time Engineers ltd. license FreeRTOS to High
Integrity Systems ltd. to sell under the OpenRTOS brand.  Low cost OpenRTOS
licenses offer ticketed support, indemnification and commercial middleware.

http://www.SafeRTOS.com - High Integrity Systems also provide a safety
engineered and independently SIL3 certified version for use in safety and
mission critical applications that require provable dependability.

*/

#ifndef FREERTOS_CONFIG_H
#define FREERTOS_CONFIG_H

#include “stm32f10x.h”
#include “bsp_usart.h”

//针对不同的编译器调用不同的stdint.h文件
#if defined(ICCARM) || defined(__CC_ARM) || defined(GNUC)
#include <stdint.h>
extern uint32_t SystemCoreClock;
#endif

//断言
#define vAssertCalled(char,int) printf(“Error:%s,%d\r\n”,char,int)
#define configASSERT(x) if((x)==0) vAssertCalled(FILE,LINE)

/************************************************************************

  •           FreeRTOS基础配置配置选项 
    

********************************************************************/
/
置1:RTOS使用抢占式调度器;置0:RTOS使用协作式调度器(时间片)
*

  • 注:在多任务管理机制上,操作系统可以分为抢占式和协作式两种。
  • 协作式操作系统是任务主动释放CPU后,切换到下一个任务。
  • 任务切换的时机完全取决于正在运行的任务。
    */
    #define configUSE_PREEMPTION 1

//1使能时间片调度(默认式使能的)
#define configUSE_TIME_SLICING 1

/* 某些运行FreeRTOS的硬件有两种方法选择下一个要执行的任务:

  • 通用方法和特定于硬件的方法(以下简称“特殊方法”)。
  • 通用方法:
  •  1.configUSE_PORT_OPTIMISED_TASK_SELECTION 为 0 或者硬件不支持这种特殊方法。
    
  •  2.可以用于所有FreeRTOS支持的硬件
    
  •  3.完全用C实现,效率略低于特殊方法。
    
  •  4.不强制要求限制最大可用优先级数目
    
  • 特殊方法:
  •  1.必须将configUSE_PORT_OPTIMISED_TASK_SELECTION设置为1。
    
  •  2.依赖一个或多个特定架构的汇编指令(一般是类似计算前导零[CLZ]指令)。
    
  •  3.比通用方法更高效
    
  •  4.一般强制限定最大可用优先级数目为32
    
  • 一般是硬件计算前导零指令,如果所使用的,MCU没有这些硬件指令的话此宏应该设置为0!
    */
    #define configUSE_PORT_OPTIMISED_TASK_SELECTION 1

/* 置1:使能低功耗tickless模式;置0:保持系统节拍(tick)中断一直运行

  • 假设开启低功耗的话可能会导致下载出现问题,因为程序在睡眠中,可用以下办法解决
  • 下载方法:
  •  1.将开发版正常连接好
    
  •  2.按住复位按键,点击下载瞬间松开复位按键
    
  •  1.通过跳线帽将 BOOT 0 接高电平(3.3V)
    
  •  2.重新上电,下载
    
  •  	1.使用FlyMcu擦除一下芯片,然后进行下载
    
  •  	STMISP -> 清除芯片(z)
    

*/
#define configUSE_TICKLESS_IDLE 0

/*

  • 写入实际的CPU内核时钟频率,也就是CPU指令执行频率,通常称为Fclk
  • Fclk为供给CPU内核的时钟信号,我们所说的cpu主频为 XX MHz,
  • 就是指的这个时钟信号,相应的,1/Fclk即为cpu时钟周期;
    */
    #define configCPU_CLOCK_HZ (SystemCoreClock)

//RTOS系统节拍中断的频率。即一秒中断的次数,每次中断RTOS都会进行任务调度
#define configTICK_RATE_HZ (( TickType_t )1000)

//可使用的最大优先级
#define configMAX_PRIORITIES (32)

//空闲任务使用的堆栈大小
#define configMINIMAL_STACK_SIZE ((unsigned short)128)

//任务名字字符串长度
#define configMAX_TASK_NAME_LEN (16)

//系统节拍计数器变量数据类型,1表示为16位无符号整形,0表示为32位无符号整形
#define configUSE_16_BIT_TICKS 0

//空闲任务放弃CPU使用权给其他同优先级的用户任务
#define configIDLE_SHOULD_YIELD 1

//启用队列
#define configUSE_QUEUE_SETS 0

//开启任务通知功能,默认开启
#define configUSE_TASK_NOTIFICATIONS 1

//使用互斥信号量
#define configUSE_MUTEXES 0

//使用递归互斥信号量
#define configUSE_RECURSIVE_MUTEXES 0

//为1时使用计数信号量
#define configUSE_COUNTING_SEMAPHORES 0

/* 设置可以注册的信号量和消息队列个数 */
#define configQUEUE_REGISTRY_SIZE 10

#define configUSE_APPLICATION_TASK_TAG 0

/*****************************************************************
FreeRTOS与内存申请有关配置选项
*****************************************************************/
//支持动态内存申请
#define configSUPPORT_DYNAMIC_ALLOCATION 1
//支持静态内存
#define configSUPPORT_STATIC_ALLOCATION 0
//系统所有总的堆大小
#define configTOTAL_HEAP_SIZE ((size_t)(36*1024))

/***************************************************************
FreeRTOS与钩子函数有关的配置选项
*************************************************************/
/
置1:使用空闲钩子(Idle Hook类似于回调函数);置0:忽略空闲钩子
*

  • 空闲任务钩子是一个函数,这个函数由用户来实现,
  • FreeRTOS规定了函数的名字和参数:void vApplicationIdleHook(void ),
  • 这个函数在每个空闲任务周期都会被调用
  • 对于已经删除的RTOS任务,空闲任务可以释放分配给它们的堆栈内存。
  • 因此必须保证空闲任务可以被CPU执行
  • 使用空闲钩子函数设置CPU进入省电模式是很常见的
  • 不可以调用会引起空闲任务阻塞的API函数
    */
    #define configUSE_IDLE_HOOK 0

/* 置1:使用时间片钩子(Tick Hook);置0:忽略时间片钩子
*
*

  • 时间片钩子是一个函数,这个函数由用户来实现,
  • FreeRTOS规定了函数的名字和参数:void vApplicationTickHook(void )
  • 时间片中断可以周期性的调用
  • 函数必须非常短小,不能大量使用堆栈,
  • 不能调用以”FromISR" 或 "FROM_ISR”结尾的API函数
    */
    /xTaskIncrementTick函数是在xPortSysTickHandler中断函数中被调用的。因此,vApplicationTickHook()函数执行的时间必须很短才行/
    #define configUSE_TICK_HOOK 0

//使用内存申请失败钩子函数
#define configUSE_MALLOC_FAILED_HOOK 0

/*

  • 大于0时启用堆栈溢出检测功能,如果使用此功能
  • 用户必须提供一个栈溢出钩子函数,如果使用的话
  • 此值可以为1或者2,因为有两种栈溢出检测方法 */
    #define configCHECK_FOR_STACK_OVERFLOW 0

/********************************************************************
FreeRTOS与运行时间和任务状态收集有关的配置选项
*********************************************************************/
//启用运行时间统计功能
#define configGENERATE_RUN_TIME_STATS 0
//启用可视化跟踪调试
#define configUSE_TRACE_FACILITY 0
/
与宏configUSE_TRACE_FACILITY同时为1时会编译下面3个函数

  • prvWriteNameToBuffer()
  • vTaskList(),
  • vTaskGetRunTimeStats()
    */
    #define configUSE_STATS_FORMATTING_FUNCTIONS 1

/********************************************************************
FreeRTOS与协程有关的配置选项
*********************************************************************/
//启用协程,启用协程以后必须添加文件croutine.c
#define configUSE_CO_ROUTINES 0
//协程的有效优先级数目
#define configMAX_CO_ROUTINE_PRIORITIES ( 2 )

/***********************************************************************
FreeRTOS与软件定时器有关的配置选项
*********************************************************************/
//启用软件定时器
#define configUSE_TIMERS 0
//软件定时器优先级
#define configTIMER_TASK_PRIORITY (configMAX_PRIORITIES-1)
//软件定时器队列长度
#define configTIMER_QUEUE_LENGTH 10
//软件定时器任务堆栈大小
#define configTIMER_TASK_STACK_DEPTH (configMINIMAL_STACK_SIZE
2)

/************************************************************
FreeRTOS可选函数配置选项
************************************************************/
#define INCLUDE_xTaskGetSchedulerState 1
#define INCLUDE_vTaskPrioritySet 1
#define INCLUDE_uxTaskPriorityGet 1
#define INCLUDE_vTaskDelete 1
#define INCLUDE_vTaskCleanUpResources 1
#define INCLUDE_vTaskSuspend 1
#define INCLUDE_vTaskDelayUntil 1
#define INCLUDE_vTaskDelay 1
#define INCLUDE_eTaskGetState 1
#define INCLUDE_xTimerPendFunctionCall 0
//#define INCLUDE_xTaskGetCurrentTaskHandle 1
//#define INCLUDE_uxTaskGetStackHighWaterMark 0
//#define INCLUDE_xTaskGetIdleTaskHandle 0

/******************************************************************
FreeRTOS与中断有关的配置选项
******************************************************************/
#ifdef __NVIC_PRIO_BITS
#define configPRIO_BITS __NVIC_PRIO_BITS
#else
#define configPRIO_BITS 4
#endif
//中断最低优先级
#define configLIBRARY_LOWEST_INTERRUPT_PRIORITY 15

//系统可管理的最高中断优先级
#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 5

#define configKERNEL_INTERRUPT_PRIORITY ( configLIBRARY_LOWEST_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) ) /* 240 */

#define configMAX_SYSCALL_INTERRUPT_PRIORITY ( configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )

/****************************************************************
FreeRTOS与中断服务函数有关的配置选项
****************************************************************/
#define xPortPendSVHandler PendSV_Handler
#define vPortSVCHandler SVC_Handler

/* 以下为使用Percepio Tracealyzer需要的东西,不需要时将 configUSE_TRACE_FACILITY 定义为 0 */
#if ( configUSE_TRACE_FACILITY == 1 )
#include “trcRecorder.h”
#define INCLUDE_xTaskGetCurrentTaskHandle 1 // 启用一个可选函数(该函数被 Trace源码使用,默认该值为0 表示不用)
#endif

#endif /* FREERTOS_CONFIG_H */

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315


* 替换掉 FreeRTOSConfig.h 中的所有内容。  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20201203124622329.png)
* 然后再编译一下,会有两个错误如下图,原因是 SVC\_Handler 、PendSV\_Handler 被重复实现了。  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20201203122421209.png)
* 打开 stm32f10x\_it.c 文件,将这两个函数注释掉,然后再编译,无错。  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20201203122736325.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3NzajkyNTMxOQ==,size_16,color_FFFFFF,t_70)


10、修改 stm32f10x\_it.c 文件


* 添加两个头文件。



#include “FreeRTOS.h” //FreeRTOS使用
#include “task.h”
12


* 如下图所示:  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20201203163321909.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3NzajkyNTMxOQ==,size_16,color_FFFFFF,t_70)
* 修改 SysTick\_Handler 函数体。



//由于该函数是在外部,需要声明一下
extern void xPortSysTickHandler(void);

void SysTick_Handler(void)
{
#if (INCLUDE_xTaskGetSchedulerState == 1 )
if (xTaskGetSchedulerState() != taskSCHEDULER_NOT_STARTED)
{
#endif /* INCLUDE_xTaskGetSchedulerState /
xPortSysTickHandler();//调用systick中断处理函数
#if (INCLUDE_xTaskGetSchedulerState == 1 )
}
#endif /
INCLUDE_xTaskGetSchedulerState */
}
1234567891011121314


* 如下图所示:  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20201203163414125.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3NzajkyNTMxOQ==,size_16,color_FFFFFF,t_70)
* 这里说明一下,xPortSysTickHandler 函数时在 port.c 文件中,如下图所示:  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20201203163857395.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3NzajkyNTMxOQ==,size_16,color_FFFFFF,t_70)
* 编译一下工程,没有错误。  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20201203163930311.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3NzajkyNTMxOQ==,size_16,color_FFFFFF,t_70)


到此为止,整个的 FreeRTOS 的源码移植完毕了,接下来就在 main.c 函数写 3 个周期性 task ,也可以自己再另外创建一个 .c 文件用于编写这 3 个 tack ,记住在 main.c 中引用头文件就行了。


### 二、多任务程序


1、编写 main.c 的代码


* 将 main.c 文件的内容全部修改成如下内容:



#include “FreeRTOS.h”
#include “task.h”
/* 开发板硬件bsp头文件 */
#include “bsp_led.h”
#include “bsp_usart.h”

/**************************** 任务句柄 *******************************/
/

  • 任务句柄是一个指针,用于指向一个任务,当任务创建好之后,它就具有了一个任务句柄
  • 以后我们要想操作这个任务都需要通过这个任务句柄,如果是自身的任务操作自己,那么
  • 这个句柄可以为NULL。
    /
    /
    创建任务句柄 /
    static TaskHandle_t AppTaskCreate_Handle = NULL;
    /
    LED1任务句柄 */
    static TaskHandle_t LED1_Task_Handle = NULL;
    //串口通讯任务句柄
    static TaskHandle_t USART_Task_Handle = NULL;
    //AHT20采集一次温湿度数据任务句柄
    static TaskHandle_t AHT20_Task_Handle = NULL;;

static void AppTaskCreate(void);/* 用于创建任务 /
/
LED1_Task任务实现 /
static void LED1_Task(void
pvParameters);
//串口通信任务实现
static void USART_Task(void* parameter);
//AHT20采集一次温湿度数据任务实现
static void AHT20_Task(void* parameter);

static void BSP_Init(void);/* 用于初始化板载相关资源 */

int main(void)
{
BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */

/* 开发板硬件初始化 /
BSP_Init();
printf(“这是一个[野火]-STM32全系列开发板-FreeRTOS-动态创建多任务实验!\r\n”);
/
创建AppTaskCreate任务 /
xReturn = xTaskCreate((TaskFunction_t )AppTaskCreate, /
任务入口函数 /
(const char
)“AppTaskCreate”,/* 任务名字 /
(uint16_t )512, /
任务栈大小 /
(void
)NULL,/* 任务入口函数参数 /
(UBaseType_t )1, /
任务的优先级 /
(TaskHandle_t
)&AppTaskCreate_Handle);/* 任务控制块指针 /
/
启动任务调度 /
if(pdPASS == xReturn)
vTaskStartScheduler(); /
启动任务,开启调度 */
else
return -1;

while(1); /* 正常不会执行到这里 */
}

//所有的任务创建函数
static void AppTaskCreate(void)
{
BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */

taskENTER_CRITICAL(); //进入临界区

/* 创建LED_Task任务 /
xReturn = xTaskCreate((TaskFunction_t )LED1_Task, /
任务入口函数 /
(const char
)“LED1_Task”,/* 任务名字 /
(uint16_t )512, /
任务栈大小 /
(void
)NULL, /* 任务入口函数参数 /
(UBaseType_t )2, /
任务的优先级 /
(TaskHandle_t
)&LED1_Task_Handle);/* 任务控制块指针 /
if(pdPASS == xReturn)
printf(“创建LED1_Task任务成功!\r\n”);
//创建串口通讯任务
xReturn = xTaskCreate((TaskFunction_t )USART_Task,
(const char
)“USART_Task”,
(uint16_t )512,
(void* )NULL,
(UBaseType_t )3,
(TaskHandle_t* )&USART_Task_Handle);
if(pdPASS == xReturn)
printf(“创建USART_Task任务成功!\r\n”);
//AHT20采集一次温湿度数据
xReturn = xTaskCreate((TaskFunction_t )AHT20_Task,
(const char* )“AHT20_Task”,
(uint16_t )512,
(void* )NULL,
(UBaseType_t )4,
(TaskHandle_t* )&AHT20_Task_Handle);
if(pdPASS == xReturn)
printf(“创建AHT20_Task任务成功!\r\n”);

vTaskDelete(AppTaskCreate_Handle); //删除AppTaskCreate任务

taskEXIT_CRITICAL(); //退出临界区
}

//LED小灯
static void LED1_Task(void* parameter)
{
while (1)
{
LED1_ON;
vTaskDelay(500); /* 延时500个tick */
printf(“LED1_Task Running,LED1_ON\r\n”);

    LED1_OFF;     
    vTaskDelay(500);   /* 延时500个tick */		 		
    printf("LED1_Task Running,LED1_OFF\r\n");
}

}

//串口通信
static void USART_Task(void* parameter)
{
while (1)
{
vTaskDelay(2000);
printf(“helloworld!\r\n”);
}
}

//AHT20采集一次温湿度数据
static void AHT20_Task(void* parameter)
{
while (1)
{
vTaskDelay(5000);
printf(“采集的温度为10℃\r\n”);
}
}

static void BSP_Init(void)
{
/*
* STM32中断优先级分组为4,即4bit都用来表示抢占优先级,范围为:0~15
* 优先级分组只需要分组一次即可,以后如果有其他的任务需要用到中断,
* 都统一用这个优先级分组,千万不要再分组,切忌。
*/
NVIC_PriorityGroupConfig( NVIC_PriorityGroup_4 );

/* LED 初始化 */
LED_GPIO_Config();

/* 串口初始化	*/
USART_Config();

}

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149


* 如下图所示:  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20201203165736360.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3NzajkyNTMxOQ==,size_16,color_FFFFFF,t_70)


2、烧录程序到 stm32 指南者中


`说明:`在烧录过程请参考博客:[stm32 应用实例—— USART 串口通讯]( ),这篇博客写得很详细,里面有 keil 烧录前的设置、烧录的方法、串口调试助手等等。


3、烧录结果显示


* LED 等闪烁:  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20201203175335389.gif#pic_center)
* 串口助手显示:  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20201203174454472.gif#pic_center)


### 三、总结


FreeRTOS 的移植看起来繁琐,但是熟练了以后就轻松了。采用嵌入式实时操作系统(RTOS)可以更合理、更有效地利用CPU的资源,简化应用软件的设计,缩短系统开发时间,更好地保证系统的实时性和可靠性。这里我们使用 FreeRTOS ,这是国外的,而国内也有这样的实时操作系统——RT-Thread,是适合我们国内的。


### 四、参考资料


1、[野火 FreeRTOS视频教学 配套书籍《FreeRTOS内核实现与应用开发实战指南》配套例程源码]( )  
 2、[STM32指南者完成基于FreeRTOS的多任务程序]( )


## 五、手把手教你使用RT-Thread制作GD32系列BSP


已剪辑自: https://zhuanlan.zhihu.com/p/452434318


熟悉RT-Thread的朋友都知道,RT-Thread提供了许多BSP,但不是所有的板子都能找到相应的BSP,这时就需要移植新的BSP。RT-Thread的所有BSP中,最完善的BSP就是STM32系列,但从2020年下半年开始,国内出现史无前例的芯片缺货潮,芯片的交期和价格不断拉升,STM32的价格也是水涨船高,很多朋友也在考虑使用国产替代,笔者使用的兆易创新的GD32系列,我看了下RT-Thread中GD系列BSP,都是玩家各自为政,每个人都是提交自己使用的板子的BSP,充斥着大量冗余的代码,对于有强迫症的我就非常不爽,就根据手头的板子,参看STM32的BSP架构,构建了GD32的BSP架构。


笔者使用的开发板是兆易创新设计的GD32407V-START开发板。其主控芯片为GD32F407VKT6,主频168MHz,内部3072K Flash,192KB SRAM,资源相当丰富。


![img](https://img-blog.csdnimg.cn/img_convert/a6cbf6defde610482c63d7661e1b6fcc.jpeg)


### 1 BSP 框架制作


在具体移植GD32407V-START的BSP之前,先做好GD32的BSP架构。BSP 框架结构如下图所示:


![img](https://img-blog.csdnimg.cn/img_convert/1ca93e056a3277618fb390889a4d06a5.jpeg)


GD32的BSP架构主要分为三个部分:libraries、tools和具体的Boards,其中libraries包含了GD32的通用库,包括每个系列的Firmware Library以及适配RT-Thread的drivers;tools是生成工程的Python脚本工具;另外就是Boards文件,当然这里的Boards有很多,我这里值列举了GD32407V-START。


这里先谈谈libraries和tools的构建,然后在后文单独讨论具体板级BSP的制作。


#### 1.1 Libraries构建


Libraries文件夹包含兆易创新提供的HAL库,这个直接在兆易创新的官网就可以下载。


下载地址:[http://www.gd32mcu.com/cn/download/]( )


然后将GD32F4xx\_Firmware\_Library复制到libraries目录下,其他的系列类似。


![img](https://img-blog.csdnimg.cn/img_convert/2abe22969ab8d86be1bb49fe0bb083c2.jpeg)


GD32F4xx\_Firmware\_Library就是官方的文件,基本是不用动的,只是在文件夹中需要添加构建工程的脚本文件SConscript,其实也就是Python脚本。


![img](https://img-blog.csdnimg.cn/img_convert/72cafc65ca97c387966363b080b93922.jpeg)


SConscript文件的内容如下:



import rtconfig #导包
from building import *

get current directory

cwd = GetCurrentDir() #获取当然路径

The set of source files associated with this SConscript file.

src = Split(‘’’
CMSIS/GD/GD32F4xx/Source/system_gd32f4xx.c
GD32F4xx_standard_peripheral/Source/gd32f4xx_gpio.c
GD32F4xx_standard_peripheral/Source/gd32f4xx_rcu.c
GD32F4xx_standard_peripheral/Source/gd32f4xx_exti.c
GD32F4xx_standard_peripheral/Source/gd32f4xx_misc.c
GD32F4xx_standard_peripheral/Source/gd32f4xx_syscfg.c
‘’')#将括号中的字符串分割后成列表(list),以便包含到工程中

if GetDepend([‘RT_USING_SERIAL’]):#如果打开了RT_USING_SERIAL的宏,则会包含以下源文件
src += [‘GD32F4xx_standard_peripheral/Source/gd32f4xx_usart.c’]

if GetDepend([‘RT_USING_I2C’]):
src += [‘GD32F4xx_standard_peripheral/Source/gd32f4xx_i2c.c’]

if GetDepend([‘RT_USING_SPI’]):
src += [‘GD32F4xx_standard_peripheral/Source/gd32f4xx_spi.c’]

if GetDepend([‘RT_USING_CAN’]):
src += [‘GD32F4xx_standard_peripheral/Source/gd32f4xx_can.c’]

if GetDepend([‘BSP_USING_ETH’]):
src += [‘GD32F4xx_standard_peripheral/Source/gd32f4xx_enet.c’]

if GetDepend([‘RT_USING_ADC’]):
src += [‘GD32F4xx_standard_peripheral/Source/gd32f4xx_adc.c’]

if GetDepend([‘RT_USING_DAC’]):
src += [‘GD32F4xx_standard_peripheral/Source/gd32f4xx_dac.c’]

if GetDepend([‘RT_USING_RTC’]):
src += [‘GD32F4xx_standard_peripheral/Source/gd32f4xx_rtc.c’]

if GetDepend([‘RT_USING_WDT’]):
src += [‘GD32F4xx_standard_peripheral/Source/gd32f4xx_wwdgt.c’]
src += [‘GD32F4xx_standard_peripheral/Source/gd32f4xx_fwdgt.c’]

if GetDepend([‘RT_USING_SDIO’]):
src += [‘GD32F4xx_standard_peripheral/Source/gd32f4xx_sdio.c’]

#头文件路径
path = [
cwd + ‘/CMSIS/GD/GD32F4xx/Include’,
cwd + ‘/CMSIS’,
cwd + ‘/GD32F4xx_standard_peripheral/Include’,]

CPPDEFINES = [‘USE_STDPERIPH_DRIVER’]
#定义一个组,组名为’Libraries’, depend为空表示依赖任何一个其他宏,另外当前的头文件路径添加到工程中
group = DefineGroup(‘Libraries’, src, depend = [‘’], CPPPATH = path, CPPDEFINES = CPPDEFINES)

Return(‘group’)


该文件主要的作用就是添加库文件和头文件路径,一部分文件是属于基础文件,因此直接调用Python库的Split包含,另外一部分文件是根据实际的应用需求添加的。


这里是以GD32F4来举例的,其他系列的都是类似的。


接下来说说Kconfig文件,这里是对内核和组件的功能进行配置,对RT-Thread的组件进行自由裁剪。


如果使用RT-Thread studio,则通过RT-Thread Setting可以体现Kconfig文件的作用。


![img](https://img-blog.csdnimg.cn/img_convert/cb04376c33a5cd62f7354f9c0f6b3929.jpeg)


如果使用ENV环境,则在使用 menuconfig配置和裁剪 RT-Thread时体现。


![img](https://img-blog.csdnimg.cn/img_convert/39b05c398765be84bff40efd06ca020e.jpeg)


后面所有的Kconfig文件都是一样的逻辑。下表列举一些常用的Kconfig句法规则。




| 关键词 | 说明 |
| --- | --- |
| config | 此关键字定义了一新的配置选项 |
| menuconfig | 此关键字和前面的关键字很相似,但它在前面的基础上要求所有的子选项作为独立的行显示。 |
| choice/endchoice | 该关键字定义了一组选择项。 |
| comment | 这里定义了在配置过程中显示给用户的注释,该注释还将写进输出文件中。格式说明: comment “eg: description content” |
| menu / endmenu | 这里定义了一个菜单,所有依赖于此菜单的选项都是它的子选项。 |
| if/endif | 这里定义了if结构。 |
| source | 读取其他具体的配置文件,其他配置文件会被解析。 |


Kconfig的语法规则网上资料很多,自行去学习吧。


bsp/gd32/libraries/Kconfig内容如下:



config SOC_FAMILY_GD32
bool

config SOC_SERIES_GD32F4
bool
select ARCH_ARM_CORTEX_M4
select SOC_FAMILY_GD32


因为该架构目前笔者只移植了GD32F4的,因此这里的内容比较少,如果有些的系列,直接参考F4的配置例子在这里加就可以了。


![img](https://img-blog.csdnimg.cn/img_convert/3f7d68653782c0b8777487b5b9f0cab2.jpeg)


最后谈谈gd32\_drivers,这个文件夹就是GD32的外设驱动文件夹,为上层应用提供调用接口。


该文件夹是整个GD32共用的,因此在编写和修改都要慎重。关于drv\_xxx文件在后句具体移植BSP的时候讲解,这里主要将整体架构,SConscript和Kconfig的作用和前面的一样,只是具体的内容不同罢了。


好了,先看bsp/gd32/libraries/gd32\_drivers/SConscript文件。



import(‘RTT_ROOT’)
import(‘rtconfig’)
from building import *

cwd = GetCurrentDir()

add the general drivers.

src = Split(“”"
“”")

add pin drivers.

if GetDepend(‘RT_USING_PIN’):
src += [‘drv_gpio.c’]

add usart drivers.

if GetDepend([‘RT_USING_SERIAL’]):
src += [‘drv_usart.c’]

add adc drivers.

if GetDepend(‘RT_USING_ADC’):
src += [‘drv_adc.c’]

add i2c drivers.

if GetDepend([‘RT_USING_I2C’, ‘RT_USING_I2C_BITOPS’]):
if GetDepend(‘BSP_USING_I2C0’) or GetDepend(‘BSP_USING_I2C1’) or GetDepend(‘BSP_USING_I2C2’) or GetDepend(‘BSP_USING_I2C3’):
src += [‘drv_soft_i2c.c’]

add spi drivers.

if GetDepend(‘RT_USING_SPI’):
src += [‘drv_spi.c’]

add spi flash drivers.

if GetDepend(‘RT_USING_SFUD’):
src += [‘drv_spi_flash.c’, ‘drv_spi.c’]

add hwtimer drivers.

if GetDepend(‘RT_USING_HWTIMER’):
src += [‘drv_hwtimer.c’]

add rtc drivers.

if GetDepend(‘RT_USING_RTC’):
src += [‘drv_rtc.c’]

add iwdt drivers.

if GetDepend(‘RT_USING_WDT’):
src += [‘drv_iwdt.c’]

path = [cwd]

group = DefineGroup(‘Drivers’, src, depend = [‘’], CPPPATH = path)

Return(‘group’)


和GD32F4xx\_Firmware\_Library文件夹中的SConscript是类似的。


bsp/gd32/libraries/gd32\_drivers/Kconfig文件结构如下:



if BSP_USING_USBD
config BSP_USBD_TYPE_FS
bool

“USB Full Speed (FS) Core”

config BSP_USBD_TYPE_HS
bool

“USB High Speed (HS) Core”

config BSP_USBD_SPEED_HS
bool

“USB High Speed (HS) Mode”

config BSP_USBD_SPEED_HSINFS
bool

“USB High Speed (HS) Core in FS mode”

config BSP_USBD_PHY_EMBEDDED
bool

“Using Embedded phy interface”

config BSP_USBD_PHY_UTMI
bool

“UTMI: USB 2.0 Transceiver Macrocell Interace”

config BSP_USBD_PHY_ULPI
bool

“ULPI: UTMI+ Low Pin Interface”

endif


#### 1.2 Tools构建


该文件夹就是工程构建的脚本,



import os
import sys
import shutil

cwd_path = os.getcwd()
sys.path.append(os.path.join(os.path.dirname(cwd_path), ‘rt-thread’, ‘tools’))

def bsp_update_board_kconfig(dist_dir):

change board/kconfig path

if not os.path.isfile(os.path.join(dist_dir, ‘board/Kconfig’)):
return

with open(os.path.join(dist_dir, ‘board/Kconfig’), ‘r’) as f:
data = f.readlines()
with open(os.path.join(dist_dir, ‘board/Kconfig’), ‘w’) as f:
for line in data:
if line.find(‘…/libraries/gd32_drivers/Kconfig’) != -1:
position = line.find(‘…/libraries/gd32_drivers/Kconfig’)
line = line[0:position] + ‘libraries/gd32_drivers/Kconfig"\n’
f.write(line)

BSP dist function

def dist_do_building(BSP_ROOT, dist_dir):
from mkdist import bsp_copy_files
import rtconfig

print(“=> copy gd32 bsp library”)
library_dir = os.path.join(dist_dir, ‘libraries’)
library_path = os.path.join(os.path.dirname(BSP_ROOT), ‘libraries’)
bsp_copy_files(os.path.join(library_path, rtconfig.BSP_LIBRARY_TYPE),
os.path.join(library_dir, rtconfig.BSP_LIBRARY_TYPE))

print(“=> copy bsp drivers”)
bsp_copy_files(os.path.join(library_path, ‘gd32_drivers’), os.path.join(library_dir, ‘gd32_drivers’))
shutil.copyfile(os.path.join(library_path, ‘Kconfig’), os.path.join(library_dir, ‘Kconfig’))

bsp_update_board_kconfig(dist_dir)


以上代码很简单,主要使用了Python的OS模块的join函数,该函数的作用就是连接两个或更多的路径名。最后将BSP依赖的文件复制到指定目录下。


在使用scons --dist 命令打包的时候,就是依赖的该脚本,生成的dist 文件夹的工程到任何目录下使用,也就是将BSP相关的库以及内核文件提取出来,可以将该工程任意拷贝。


需要注意的是,使用scons --dist打包后需要修改board/Kconfig中的库路径,因此这里调用了bsp\_update\_board\_kconfig方法修改。


![img](https://img-blog.csdnimg.cn/img_convert/8d95136234934da85ea9732fa58c2574.jpeg)


#### 1.3 gd32407v-start构建


该文件夹就gd32407v-start的具体BSP文件,文件结构如下:


![img](https://img-blog.csdnimg.cn/img_convert/91a32452bfcf74b34acc28dc5da42c42.jpeg)


在后面将具体讲解如何构建该部分内容。


### 2 BSP移植


#### 2.1 Keil环境准备


目前市面通用的MDK for ARM版本有Keil 4和Keil 5:使用Keil 4建议安装4.74及以上;使用Keil 5建议安装5.20以上版本。笔者的MDK是5.30。


从MDK的官网可以下载得到MDK的安装包,然后安装即可,关于的MDK安装请看笔者的教程。


MDK安装教程:[https://blog.csdn.net/bruceoxl/article/details/108548573]( )


MDK下载地址:[https://www.keil.com/download/product/]( )


![img](https://img-blog.csdnimg.cn/img_convert/3169aea9177c65fc5bbc1919adaeb9fa.jpeg)


安装完成后会自动打开,我们将其关闭。


接下来我们下载GD32F30x的软件支持包。


下载地址:[http://www.gd32mcu.com/cn/download]( )


![img](https://img-blog.csdnimg.cn/img_convert/398c8fbaccdced214147ca652d682e05.png)


下载好后双击GigaDevice.GD32F4xx\_DFP.2.1.0.pack运行即可:


![img](https://img-blog.csdnimg.cn/img_convert/984ca28a47131b1399ad53202b32a7b1.jpeg)


点击[Next]即可安装完成。


![img](https://img-blog.csdnimg.cn/img_convert/b3e0ce74bed9971bb18c1abaa5383588.jpeg)


安装成功后,重新打开Keil,则可以在File->Device Database中出现Gigadevice的下拉选项,点击可以查看到相应的型号。


![img](https://img-blog.csdnimg.cn/img_convert/05f5df7c6b02d9bb5fda6982f8b743af.jpeg)


#### 2.2 BSP工程制作


**1.构建基础工程**


首先看看RT-Thread代码仓库中已有很多BSP,而我要移植的是Cortex-M4内核。这里我找了一个相似的内核,把它复制一份,并修改文件名为:gd32407v-start。这样就有一个基础的工程。然后就开始增删改查,完成最终的BSP,几乎所有的BSP的制作都是如此。


**2.修改BSP构建脚本**


bsp/gd32/gd32407v-start/Kconfig修改后的内容如下:



mainmenu “RT-Thread Configuration”

config BSP_DIR
string
option env=“BSP_ROOT”
default “.”

config RTT_DIR
string
option env=“RTT_ROOT”
default “…/…/…”

config PKGS_DIR
string
option env=“PKGS_ROOT”
default “packages”

source “ R T T D I R / K c o n f i g " s o u r c e " RTT_DIR/Kconfig" source " RTTDIR/Kconfig"source"PKGS_DIR/Kconfig”
source “…/libraries/Kconfig”
source “board/Kconfig”


该文件是获取所有路径下的Kconfig。


bsp/gd32/gd32407v-start/SConscript修改后的内容如下:



for module compiling

import os
Import(‘RTT_ROOT’)
from building import *

cwd = GetCurrentDir()
objs = []
list = os.listdir(cwd)

for d in list:
path = os.path.join(cwd, d)
if os.path.isfile(os.path.join(path, ‘SConscript’)):
objs = objs + SConscript(os.path.join(d, ‘SConscript’))

Return(‘objs’)


该文件是用于遍历当前目录的所有文件夹。


bsp/gd32/gd32407v-start/SConstruct修改后的内容如下:



import os
import sys
import rtconfig

if os.getenv(‘RTT_ROOT’):
RTT_ROOT = os.getenv(‘RTT_ROOT’)
else:
RTT_ROOT = os.path.normpath(os.getcwd() + ‘/…/…/…’)

sys.path = sys.path + [os.path.join(RTT_ROOT, ‘tools’)]
try:
from building import *
except:
print(‘Cannot found RT-Thread root directory, please check RTT_ROOT’)
print(RTT_ROOT)
exit(-1)

TARGET = ‘rtthread.’ + rtconfig.TARGET_EXT

DefaultEnvironment(tools=[])
env = Environment(tools = [‘mingw’],
AS = rtconfig.AS, ASFLAGS = rtconfig.AFLAGS,
CC = rtconfig.CC, CCFLAGS = rtconfig.CFLAGS,
AR = rtconfig.AR, ARFLAGS = ‘-rc’,
CXX = rtconfig.CXX, CXXFLAGS = rtconfig.CXXFLAGS,
LINK = rtconfig.LINK, LINKFLAGS = rtconfig.LFLAGS)
env.PrependENVPath(‘PATH’, rtconfig.EXEC_PATH)

if rtconfig.PLATFORM == ‘iar’:
env.Replace(CCCOM = [‘$CC $CCFLAGS $CPPFLAGS $_CPPDEFFLAGS $_CPPINCFLAGS -o $TARGET $SOURCES’])
env.Replace(ARFLAGS = [‘’])
env.Replace(LINKCOM = env[“LINKCOM”] + ’ --map rtthread.map’)

Export(‘RTT_ROOT’)
Export(‘rtconfig’)

SDK_ROOT = os.path.abspath(‘./’)

if os.path.exists(SDK_ROOT + ‘/libraries’):
libraries_path_prefix = SDK_ROOT + ‘/libraries’
else:
libraries_path_prefix = os.path.dirname(SDK_ROOT) + ‘/libraries’

SDK_LIB = libraries_path_prefix
Export(‘SDK_LIB’)

prepare building environment

objs = PrepareBuilding(env, RTT_ROOT, has_libcpu=False)

gd32_library = ‘GD32F4xx_Firmware_Library’
rtconfig.BSP_LIBRARY_TYPE = gd32_library

include libraries

objs.extend(SConscript(os.path.join(libraries_path_prefix, gd32_library, ‘SConscript’)))

include drivers

objs.extend(SConscript(os.path.join(libraries_path_prefix, ‘Drivers’, ‘SConscript’)))

make a building

DoBuilding(TARGET, objs)


该文件用于链接所有的依赖文件,并调用make进行编译。


**3.修改开发环境信息**


bsp/gd32/gd32407v-start/cconfig.h修改后的内容如下:



#ifndef CCONFIG_H__
#define CCONFIG_H__
/* Automatically generated file; DO NOT EDIT. /
/
compiler configure file for RT-Thread in GCC*/

#define HAVE_NEWLIB_H 1
#define LIBC_VERSION “newlib 2.4.0”

#define HAVE_SYS_SIGNAL_H 1
#define HAVE_SYS_SELECT_H 1
#define HAVE_PTHREAD_H 1

#define HAVE_FDSET 1
#define HAVE_SIGACTION 1
#define GCC_VERSION_STR “5.4.1 20160919 (release) [ARM/embedded-5-branch revision 240496]”
#define STDC “2011”

#endif


该文件是是编译BSP的环境信息,需根据实时修改。


**4.修改KEIL的模板工程**


双击:template.uvprojx即可修改模板工程。


修改为对应芯片设备:


![img](https://img-blog.csdnimg.cn/img_convert/d5b6baf2eb79a1768f7eab95828c978b.jpeg)


修改FLASH和RAM的配置:


![img](https://img-blog.csdnimg.cn/img_convert/fe5dee9d08ed88f8ca1fe981bb44b872.jpeg)


修改可执行文件名字:


![img](https://img-blog.csdnimg.cn/img_convert/bc909d1a1323f435f0796aaea6ed6608.jpeg)


修改默认调试工具:CMSIS-DAP Debugger。


![img](https://img-blog.csdnimg.cn/img_convert/6d8215dd75b22b055231fa527a926444.jpeg)


修改编程算法:GD32F4xx FMC。


![img](https://img-blog.csdnimg.cn/img_convert/be26aae5bd9d0facc6ba21e990d676a7.jpeg)


**5.修改board文件夹**


(1) 修改bsp/gd32/gd32407v-start/board/linker\_scripts/link.icf


修改后的内容如下:



/###ICF### Section handled by ICF editor, don’t touch! /
/
-Editor annotation file-
/
/
IcfEditorFile=“ T O O L K I T D I R TOOLKIT_DIR TOOLKITDIR\config\ide\IcfEditor\cortex_v1_0.xml” /
/
-Specials-
/
define symbol ICFEDIT_intvec_start = 0x08000000;
/
-Memory Regions-/
define symbol ICFEDIT_region_ROM_start = 0x08000000;
define symbol ICFEDIT_region_ROM_end = 0x082FFFFF;
define symbol ICFEDIT_region_RAM_start = 0x20000000;
define symbol ICFEDIT_region_RAM_end = 0x2002FFFF;
/
-Sizes-/
define symbol ICFEDIT_size_cstack = 0x2000;
define symbol ICFEDIT_size_heap = 0x2000;
/
*** End of ICF editor section. ###ICF###*/

export symbol ICFEDIT_region_RAM_end;

define symbol region_RAM1_start = 0x10000000;
define symbol region_RAM1_end = 0x1000FFFF;

define memory mem with size = 4G;
define region ROM_region = mem:[from ICFEDIT_region_ROM_start to ICFEDIT_region_ROM_end];
define region RAM_region = mem:[from ICFEDIT_region_RAM_start to ICFEDIT_region_RAM_end];
define region RAM1_region = mem:[from region_RAM1_start to region_RAM1_end];

define block CSTACK with alignment = 8, size = ICFEDIT_size_cstack { };
define block HEAP with alignment = 8, size = ICFEDIT_size_heap { };

initialize by copy { readwrite };
do not initialize { section .noinit };

keep { section FSymTab };
keep { section VSymTab };
keep { section .rti_fn* };
place at address mem:ICFEDIT_intvec_start { readonly section .intvec };

place in ROM_region { readonly };
place in RAM_region { readwrite,
block CSTACK, block HEAP };
place in RAM1_region { section .sram };


该文件是IAR编译的链接脚本,根据《GD32F407xx\_Datasheet\_Rev2.1》可知,GD32F407VKT6的flash大小为3072KB,SRAM大小为192KB,因此需要设置ROM和RAM的起始地址和堆栈大小等。


(2) 修改bsp/gd32/gd32407v-start/board/linker\_scripts/link.ld


修改后的内容如下:



/* Program Entry, set to mark it as “used” and avoid gc /
MEMORY
{
CODE (rx) : ORIGIN = 0x08000000, LENGTH = 3072k /
3072KB flash /
DATA (rw) : ORIGIN = 0x20000000, LENGTH = 192k /
192KB sram */
}
ENTRY(Reset_Handler)
_system_stack_size = 0x200;

SECTIONS
{
.text :
{
. = ALIGN(4);
_stext = .;
KEEP((.isr_vector)) / Startup code */
. = ALIGN(4);
(.text) / remaining code /
(.text.) /
remaining code */
(.rodata) / read-only data (constants) */
(.rodata)
*(.glue_7)
*(.glue_7t)
(.gnu.linkonce.t)

/* section information for finsh shell /
. = ALIGN(4);
__fsymtab_start = .;
KEEP(
(FSymTab))
__fsymtab_end = .;
. = ALIGN(4);
__vsymtab_start = .;
KEEP(*(VSymTab))
__vsymtab_end = .;
. = ALIGN(4);

/* section information for initial. /
. = ALIGN(4);
__rt_init_start = .;
KEEP(
(SORT(.rti_fn*)))
__rt_init_end = .;
. = ALIGN(4);

. = ALIGN(4);
_etext = .;
} > CODE = 0

/* .ARM.exidx is sorted, so has to go in its own output section. /
__exidx_start = .;
.ARM.exidx :
{
(.ARM.exidx .gnu.linkonce.armexidx.
)

/* This is used by the startup in order to initialize the .data secion */
_sidata = .;
} > CODE
__exidx_end = .;

/* .data section which is used for initialized data */

.data : AT (_sidata)
{
. = ALIGN(4);
/* This is used by the startup in order to initialize the .data secion */
_sdata = . ;

*(.data)
(.data.)
(.gnu.linkonce.d)

. = ALIGN(4);
/* This is used by the startup in order to initialize the .data secion */
_edata = . ;
} >DATA

.stack :
{
. = . + _system_stack_size;
. = ALIGN(4);
_estack = .;
} >DATA

__bss_start = .;
.bss :
{
. = ALIGN(4);
/* This is used by the startup in order to initialize the .bss secion */
_sbss = .;

*(.bss)
(.bss.)
*(COMMON)

. = ALIGN(4);
/* This is used by the startup in order to initialize the .bss secion */
_ebss = . ;

*(.bss.init)
} > DATA
__bss_end = .;

_end = .;

/* Stabs debugging sections. */
.stab 0 : { *(.stab) }
.stabstr 0 : { *(.stabstr) }
.stab.excl 0 : { *(.stab.excl) }
.stab.exclstr 0 : { *(.stab.exclstr) }
.stab.index 0 : { *(.stab.index) }
.stab.indexstr 0 : { *(.stab.indexstr) }
.comment 0 : { (.comment) }
/
DWARF debug sections.

  • Symbols in the DWARF debugging sections are relative to the beginning
  • of the section so we begin them at 0. /
    /
    DWARF 1 */
    .debug 0 : { *(.debug) }
    .line 0 : { (.line) }
    /
    GNU DWARF 1 extensions */
    .debug_srcinfo 0 : { *(.debug_srcinfo) }
    .debug_sfnames 0 : { (.debug_sfnames) }
    /
    DWARF 1.1 and DWARF 2 */
    .debug_aranges 0 : { *(.debug_aranges) }
    .debug_pubnames 0 : { (.debug_pubnames) }
    /
    DWARF 2 */
    .debug_info 0 : { (.debug_info .gnu.linkonce.wi.) }
    .debug_abbrev 0 : { *(.debug_abbrev) }
    .debug_line 0 : { *(.debug_line) }
    .debug_frame 0 : { *(.debug_frame) }
    .debug_str 0 : { *(.debug_str) }
    .debug_loc 0 : { *(.debug_loc) }
    .debug_macinfo 0 : { (.debug_macinfo) }
    /
    SGI/MIPS DWARF 2 extensions */
    .debug_weaknames 0 : { *(.debug_weaknames) }
    .debug_funcnames 0 : { *(.debug_funcnames) }
    .debug_typenames 0 : { *(.debug_typenames) }
    .debug_varnames 0 : { *(.debug_varnames) }
    }

该文件是GCC编译的链接脚本,根据《GD32F407xx\_Datasheet\_Rev2.1》可知,GD32F407VKT6的flash大小为3072KB,SRAM大小为192KB,因此CODE和DATA 的LENGTH分别设置为3072KB和192KB,其他芯片类似,但其实地址都是一样的。


(3) 修改bsp/gd32/gd32407v-start/board/linker\_scripts/link.sct


该文件是MDK的连接脚本,根据《GD32F407xx\_Datasheet\_Rev2.1》手册,因此需要将 LR\_IROM1 和 ER\_IROM1 的参数设置为 0x00300000;RAM 的大小为192k,因此需要将 RW\_IRAM1 的参数设置为 0x00030000。



; *************************************************************
; *** Scatter-Loading Description File generated by uVision ***
; *************************************************************

LR_IROM1 0x08000000 0x00300000 { ; load region size_region
ER_IROM1 0x08000000 0x00300000 { ; load address = execution address
*.o (RESET, +First)
*(InRoot$$Sections)
.ANY (+RO)
}
RW_IRAM1 0x20000000 0x00030000 { ; RW data
.ANY (+RW +ZI)
}
}


(4) 修改bsp/gd32/gd32407v-start/board/board.h文件


修改后内容如下:



#ifndef BOARD_H
#define BOARD_H

#include “gd32f4xx.h”
#include “drv_usart.h”
#include “drv_gpio.h”

#include “gd32f4xx_exti.h”

#define EXT_SDRAM_BEGIN (0xC0000000U) /* the begining address of external SDRAM /
#define EXT_SDRAM_END (EXT_SDRAM_BEGIN + (32U * 1024 * 1024)) /
the end address of external SDRAM */

// Internal SRAM memory size[Kbytes] <8-64>
// Default: 64
#ifdef ICCARM
// Use *.icf ram symbal, to avoid hardcode.
extern char ICFEDIT_region_RAM_end;
#define GD32_SRAM_END &ICFEDIT_region_RAM_end
#else
#define GD32_SRAM_SIZE 192
#define GD32_SRAM_END (0x20000000 + GD32_SRAM_SIZE * 1024)
#endif

#ifdef __CC_ARM
extern int Image R W I R A M 1 RW_IRAM1 RWIRAM1ZIKaTeX parse error: Expected 'EOF', got '#' at position 8: Limit; #̲define HEAP_BEG…RW_IRAM1 Z I ZI ZILimit)
#elif ICCARM
#pragma section=“HEAP”
#define HEAP_BEGIN (__segment_end(“HEAP”))
#else
extern int __bss_end;
#define HEAP_BEGIN (&__bss_end)
#endif

#define HEAP_END GD32_SRAM_END

#endif


值得注意的是,不同的编译器规定的堆栈内存的起始地址 HEAP\_BEGIN 和结束地址 HEAP\_END。这里 HEAP\_BEGIN 和 HEAP\_END 的值需要和前面的链接脚本是一致的,需要结合实际去修改。


(5) 修改bsp/gd32/gd32407v-start/board/board.c文件


修改后的文件如下:



#include <stdint.h>
#include <rthw.h>
#include <rtthread.h>
#include <board.h>

/**

  • @brief This function is executed in case of error occurrence.
  • @param None
  • @retval None
    /
    void Error_Handler(void)
    {
    /
    USER CODE BEGIN Error_Handler /
    /
    User can add his own implementation to report the HAL error return state /
    while (1)
    {
    }
    /
    USER CODE END Error_Handler */
    }

/** System Clock Configuration
*/
void SystemClock_Config(void)
{
SysTick_Config(SystemCoreClock / RT_TICK_PER_SECOND);
NVIC_SetPriority(SysTick_IRQn, 0);
}

/**

  • This is the timer interrupt service routine.

/
void SysTick_Handler(void)
{
/
enter interrupt */
rt_interrupt_enter();

rt_tick_increase();

/* leave interrupt */
rt_interrupt_leave();
}

/**

  • This function will initial GD32 board.
    /
    void rt_hw_board_init()
    {
    /
    NVIC Configuration /
    #define NVIC_VTOR_MASK 0x3FFFFF80
    #ifdef VECT_TAB_RAM
    /
    Set the Vector Table base location at 0x10000000 /
    SCB->VTOR = (0x10000000 & NVIC_VTOR_MASK);
    #else /
    VECT_TAB_FLASH /
    /
    Set the Vector Table base location at 0x08000000 */
    SCB->VTOR = (0x08000000 & NVIC_VTOR_MASK);
    #endif

SystemClock_Config();

#ifdef RT_USING_COMPONENTS_INIT
rt_components_board_init();
#endif

#ifdef RT_USING_CONSOLE
rt_console_set_device(RT_CONSOLE_DEVICE_NAME);
#endif

#ifdef BSP_USING_SDRAM
rt_system_heap_init((void *)EXT_SDRAM_BEGIN, (void *)EXT_SDRAM_END);
#else
rt_system_heap_init((void *)HEAP_BEGIN, (void *)HEAP_END);
#endif
}


该文件重点关注的就是SystemClock\_Config配置,SystemCoreClock的定义在system\_gd32f4xx.c中定义的。


(6) 修改bsp/gd32/gd32407v-start/board/Kconfig文件


修改后内容如下:



menu “Hardware Drivers Config”

config SOC_GD32407V
bool
select SOC_SERIES_GD32F4
select RT_USING_COMPONENTS_INIT
select RT_USING_USER_MAIN
default y

menu “Onboard Peripheral Drivers”

endmenu

menu “On-chip Peripheral Drivers”

config BSP_USING_GPIO
bool “Enable GPIO”
select RT_USING_PIN
default y

menuconfig BSP_USING_UART
bool “Enable UART”
default y
select RT_USING_SERIAL
if BSP_USING_UART
config BSP_USING_UART1
bool “Enable UART1”
default y

config BSP_UART1_RX_USING_DMA
bool “Enable UART1 RX DMA”
depends on BSP_USING_UART1 && RT_SERIAL_USING_DMA
default n
endif

menuconfig BSP_USING_SPI
bool “Enable SPI BUS”
default n
select RT_USING_SPI
if BSP_USING_SPI
config BSP_USING_SPI1
bool “Enable SPI1 BUS”
default n

config BSP_SPI1_TX_USING_DMA
bool “Enable SPI1 TX DMA”
depends on BSP_USING_SPI1
default n

config BSP_SPI1_RX_USING_DMA
bool “Enable SPI1 RX DMA”
depends on BSP_USING_SPI1
select BSP_SPI1_TX_USING_DMA
default n
endif

menuconfig BSP_USING_I2C1
bool “Enable I2C1 BUS (software simulation)”
default n
select RT_USING_I2C
select RT_USING_I2C_BITOPS
select RT_USING_PIN
if BSP_USING_I2C1
config BSP_I2C1_SCL_PIN
int “i2c1 scl pin number”
range 1 216
default 24
config BSP_I2C1_SDA_PIN
int “I2C1 sda pin number”
range 1 216
default 25
endif
source “…/libraries/gd32_drivers/Kconfig”

endmenu

menu “Board extended module Drivers”

endmenu

endmenu


这个文件就是配置板子驱动的,这里可根据实际需求添加。


(7) 修改bsp/gd32/gd32407v-start/board/SConscript文件


修改后内容如下:



import os
import rtconfig
from building import *

Import(‘SDK_LIB’)

cwd = GetCurrentDir()

add general drivers

src = Split(‘’’
board.c
‘’')

path = [cwd]

startup_path_prefix = SDK_LIB

if rtconfig.CROSS_TOOL == ‘gcc’:
src += [startup_path_prefix + ‘/GD32F4xx_Firmware_Library/CMSIS/GD/GD32F4xx/Source/GCC/startup_gd32f4xx.s’]
elif rtconfig.CROSS_TOOL == ‘keil’:
src += [startup_path_prefix + ‘/GD32F4xx_Firmware_Library/CMSIS/GD/GD32F4xx/Source/ARM/startup_gd32f4xx.s’]
elif rtconfig.CROSS_TOOL == ‘iar’:
src += [startup_path_prefix + ‘/GD32F4xx_Firmware_Library/CMSIS/GD/GD32F4xx/Source/IAR/startup_gd32f4xx.s’]

CPPDEFINES = [‘GD32F407’]
group = DefineGroup(‘Drivers’, src, depend = [‘’], CPPPATH = path, CPPDEFINES = CPPDEFINES)

Return(‘group’)


该文件主要添加board文件夹的.c文件和头文件路径。另外根据开发环境选择相应的汇编文件,和前面的libraries的SConscript语法是一样,文件的结构都是类似的,这里就没有注释了。


到这里,基本所有的依赖脚本都配置完成了,接下来将通过menuconfig配置工程。


6\*\*.menuconfig配置\*\*


关闭套接字抽象层。


![img](https://img-blog.csdnimg.cn/img_convert/aca37c3a7c4bba4046cb1476d904621e.png)


关闭网络设备接口。


![img](https://img-blog.csdnimg.cn/img_convert/5d70e4e99663a8b977ef6761b8615f26.png)


关闭LWIP协议栈。


![img](https://img-blog.csdnimg.cn/img_convert/d679a2404bff383a96413b928c92fb79.jpeg)


GD32407V-START板载没有以太网,因此这里主要是关闭网络相关的内容,当然GD32407V-START的资源丰富,不关这些其实也不影响,如果是其他MCU,根据实际需求自行修改吧。


**7.驱动修改**


一个基本的BSP中,串口是必不可少的,所以还需要编写串口驱动,这里使用的串口2作为调试串口。


板子上还有LED灯,主要要编写GPIO驱动即可。


关于串口和LED的驱动可以查看源码,这里就不贴出来了。


**8.应用开发**


笔者在applications的main.c中添加LED的应用代码,



#include <stdio.h>
#include <rtthread.h>
#include <rtdevice.h>
#include <board.h>

/* defined the LED2 pin: PC6 */
#define LED2_PIN GET_PIN(C, 6)

int main(void)
{
int count = 1;

/* set LED2 pin mode to output */
rt_pin_mode(LED2_PIN, PIN_MODE_OUTPUT);

while (count++)
{
rt_pin_write(LED2_PIN, PIN_HIGH);
rt_thread_mdelay(500);
rt_pin_write(LED2_PIN, PIN_LOW);
rt_thread_mdelay(500);
}

return RT_EOK;
}


当然,这需要GPIO驱动的支持。


**9.使用ENV编译工程**


在env中执行:scons


![img](https://img-blog.csdnimg.cn/img_convert/958d39fe68eba9c36d5e3ddb5f73ae99.png)


编译成功打印信息如下:


![img](https://img-blog.csdnimg.cn/img_convert/c650a9d34336dad03ec2c7cf56d7249d.png)


**10.使用env生成MDK工程**


在env中执行:scons --target=mdk5


![img](https://img-blog.csdnimg.cn/img_convert/ae8dbb6feab220150a79218df8733a7a.jpeg)


生成MDK工程后,打开MDK工程进行编译


![img](https://img-blog.csdnimg.cn/img_convert/1e69ef5811ff6c65f3e22582a63f6000.png)


成功编译打印信息如下:


![img](https://img-blog.csdnimg.cn/img_convert/f03d3f3fe04fc73e8c2abdf6ab8ca511.png)


【注】笔者没有IAR环境,有兴趣的朋友自行去开发吧。


#### 2.3使用GD-Link 下载调试GD32


前面使用ENV和MDK成功编译可BSP,那么接下来就是下载调试环节,下载需要下载器,而GD32部分开发板自带GD-link,可以用开发板上自带的GD-link调试仿真代码,不带的可外接GD-link模块,还是很方便的。具体操作方法如下。


1.第一次使用GD-link插入电脑后,会自动安装驱动。


在Options for Target -> Debug 中选择“CMSIS-DAP Debugger”,部分客户反馈找不到这一驱动器选项,那是因为MDK版本过低,只有Keil4.74以上的版本和Keil5才支持CMSIS-DAP Debugger选项。


![img](https://img-blog.csdnimg.cn/img_convert/79bc01fe51b7d1d04677f5ec77122559.jpeg)


2.在Options for Target -> Debug ->Settings勾选SWJ、 Port选择 SW。右框IDcode会出现”0xXBAXXXXX”。


![img](https://img-blog.csdnimg.cn/img_convert/9b331da678e17c8e37c1f243b3e3519b.jpeg)


3.在Options for Target -> Debug ->Settings -> Flash Download中添加GD32的flash算法。


![img](https://img-blog.csdnimg.cn/img_convert/90fe203146910e8b845269851ae6aade.jpeg)


4.单击下图的快捷方式“debug”, 即可使用GD-Link进行仿真。


![img](https://img-blog.csdnimg.cn/img_convert/a417a7de3101c83360ddff1d7fdd5a42.jpeg)


当然啦,也可使用GD-Link下载程序。


![img](https://img-blog.csdnimg.cn/img_convert/e1e6ff01a18b9d8e2e02186094bf9487.png)


下载程序成功后,打印信息如下:


![img](https://img-blog.csdnimg.cn/img_convert/220bc2dbec03a02a206e45842a716ae1.jpeg)


接上串口,打印信息如下:


![img](https://img-blog.csdnimg.cn/img_convert/22e2630692e3f14102c33daa13c91fe3.jpeg)


同时LED会不断闪烁。


#### 2.4 RT-Thread studio开发


当然,该工程也可导出使用rt-thread studio开发。


先使用scons --dist导出工程。


![img](https://img-blog.csdnimg.cn/img_convert/f616b2337fe753ed004d20265641c838.jpeg)


再将工程导入rt-thread studio中


![img](https://img-blog.csdnimg.cn/img_convert/543b08d57bc9b7f8a7c055fa6a8a6f5c.jpeg)


最后,就可在rt-thread studio就可进行开发工作了。


![img](https://img-blog.csdnimg.cn/img_convert/909e59db383f813fe2eb0481d493769e.jpeg)


当然啦,后面也可在rt-thread studio中新建工程时选择笔者提交的GD32407V-START的BSP。


关于BSP的移植就到这里了,当然还有很多内容,这里只是抛砖引玉。最后希望更多的朋友加入进来,为国产RTOS贡献自己的力量吧。


GD32 BSP: [https://gitee.com/ouxiaolong/GD32\_RT-Thread]( )


RT-Thread :[https://github.com/Ouxiaolong/rt-thread/tree/master/bsp/gd32]( )


## 六、RT-Thread官方的移植方法


### STM32系列驱动介绍


在 RT-Thread 实时操作系统中,各种各样的设备驱动是通过一套 I/O 设备管理框架来管理的。设备管理框架给上层应用提供了一套标准的设备操作 API,开发者通过调用这些标准设备操作 API,可以高效地完成和底层硬件外设的交互。设备管理框架的结构如下图所示:


![rt_device](https://img-blog.csdnimg.cn/img_convert/925c58852ce963c818ca846093aee346.png)


使用 I/O 设备管理框架开发应用程序,有如下优点:


* 使用同一套标准的 API 开发应用程序,使应用程序具有更好的移植性
* 底层驱动的升级和修改不会影响到上层代码
* 驱动和应用程序相互独立,方便多个开发者协同开发


### [1. 驱动分类介绍]( )


本小节介绍 BSP 提供的不同类别驱动的概念,对一个 BSP 而言,有如下三类驱动:


* **板载外设驱动**:指 MCU 之外,开发板上外设,例如 TF 卡、以太网和 LCD 等
* **片上外设驱动**:指 MCU 芯片上的外设,例如硬件定时器、ADC 和看门狗等
* **扩展模块驱动**:指可以通过扩展接口或者杜邦线连接的开发板的模块,例如 ESP8266 模块


这三种外设的示意图如下所示:


![Peripheral](https://img-blog.csdnimg.cn/img_convert/7809461d128a6258e0aa6ba379a3e27a.png)


### [2. 外设驱动的使用方法]( )


当前 RT-Thread 提供的驱动库已经支持 STM32 多个系列的 BSP。点击下表中的驱动名称,即可跳转到对应驱动框架的介绍文档。开发者可以通过阅读相关资料,了解如何在应用开发中通过设备驱动框架来使用这些外设驱动。


#### [2.1 片上外设]( )




| 序号 | 驱动 | 简介 |
| --- | --- | --- |
| 1 | [GPIO]( ) | 操作 GPIO 管脚 |
| 2 | [UART]( ) | 通过串口收发数据 |
| 3 | [soft I2C]( ) | 通过软件 I2C 收发数据 |
| 4 | [SPI]( ) | 通过 SPI 收发数据 |
| 5 | [ADC]( ) | 测量管脚上的模拟量 |
| 6 | SDIO | 通过 SDIO 读写数据 |
| 7 | [TIMER]( ) | 使用硬件定时器实现测量时间和定时执行回调函数功能 |
| 8 | [PWM]( ) | 在特定的管脚输出 PWM 波形 |
| 9 | [RTC]( ) | 设置和读取时间 |
| 10 | [WDT]( ) | 看门狗驱动 |
| 11 | [QSPI]( ) | 通过 SPI(1、2、4线) 收发数据 |


#### [2.2 板载外设]( )




| 序号 | 驱动 | 简介 |
| --- | --- | --- |
| 1 | SD | 适用于 SPI 接口或 SDIO 接口的 SD(TF) 卡 |
| 2 | ETH PHY | 以太网 |
| 3 | USB PHY | USB |
| 4 | LCD | 显示屏 |


#### [2.3 扩展模块]( )




| 序号 | 驱动 | 简介 |
| --- | --- | --- |
| 1 | ESP8266 | 串口转 WIFI 模块 |
| 2 | ENC28J60 | SPI 接口的以太网控制器 |


#### [2.4 驱动示例代码]( )


在 RT-Thread 的 `examples\test` 目录下,有 RT-Thread 提供的基于不同外设驱动的示例代码。在 env 工具中开启 BSP 中要测试的驱动,并将 `examples\test` 中对应的驱动框架测试文件加入工程,即可快速测试 BSP 中提供的驱动。


### STM32 系列 BSP 制作教程


已剪辑自: <https://www.rt-thread.org/document/site/#/rt-thread-version/rt-thread-standard/tutorial/make-bsp/stm32-bsp/STM32%E7%B3%BB%E5%88%97BSP%E5%88%B6%E4%BD%9C%E6%95%99%E7%A8%8B>


为了让广大开发者更好、更方便地使用 BSP 进行开发,RT-Thread 开发团队重新整理了现有的 STM32 系列的 BSP,推出了新的 BSP 框架。新的 BSP 框架在易用性、移植便利性、驱动完整性、代码规范性等方面都有较大提升,在新的 BSP 框架下进行开发,可以大大提高应用的开发效率。


和 RT-Thread 以往提供的 BSP 不同,在新的 BSP 文件夹中将不会包含固件库、外设驱动等可以被多个 BSP 引用的代码文件。而是将这些通用的文件统一存放在 Library 文件夹中,通过在特定 BSP 中引用这些文件的方式,来包含 BSP 中所需的库文件或者驱动文件。这种方式不仅大大提高了代码复用率,降低了 BSP 的维护成本,而且可以更方便地给开发者提供更丰富的驱动文件,让开发者可以更容易地找到自己需要的资源。


新的 BSP 框架还引入了 CubeMX 工具,可以使用该工具来对 BSP 中使用的外设引脚进行配置。CubeMX 工具提供了图形化的配置界面,这种图形化的配置方式对开发者来说更加直观,不仅可以让开发者灵活地配置 BSP 中使用的资源,并且可以让开发者对资源的使用情况一目了然。


新 BSP 框架的主要特性如下:


* 提供多系列 BSP 模板,大大降低新 BSP 的添加难度;
* 每个 BSP 都配有齐全的驱动文件,开发者可以方便地使用所有驱动;
* 开发者可以使用 CubeMX 工具对 BSP 进行图形化配置;


### [1. BSP 框架介绍]( )


BSP 框架结构如下图所示:


![BSP 框架图](https://img-blog.csdnimg.cn/img_convert/b79cf5ea32c9b796f0cfb3e699871abc.png)


每一个 STM32 系列的 BSP 由三部分组成,分别是通用库、BSP 模板和特定开发板 BSP,下面的表格以 F1 系列 BSP 为例介绍这三个部分:


项目 文件夹 说明




|  |  |  |
| --- | --- | --- |
| 通用库 | stm32/libraries | 用于存放 HAL 库以及基于 HAL 库的多系列通用外设驱动文件 |
| F1 系列 BSP 工程模板 | stm32/libraries/templates/stm32f10x | F1系列 BSP 模板,可以通过修改该模板制作更多 F1系列 BSP |
| 特定开发板 BSP | stm32/stm32f103-atk-nano | 在 BSP 模板的基础上修改而成 |


### [2. 知识准备]( )


制作一个 BSP 的过程就是构建一个新系统的过程,因此想要制作出好用的 BSP,要对 RT-Thread 系统的构建过程有一定了解,需要的知识准备如下所示:


* 掌握 STM32 系列 BSP 的使用方法

 了解 BSP 的使用方法,可以阅读 [BSP 说明文档]( ) 中使用教程表格内的文档。了解外设驱动的添加方法可以参考《外设驱动添加指南》。
* 了解 Scons 工程构建方法

 RT-Thread 使用 Scons 作为系统的构建工具,因此了解 Scons 的常用命令对制作新 BSP 是基本要求。
* 了解设备驱动框架

 在 RT-Thread 系统中,应用程序通过设备驱动框架来操作硬件,因此了解设备驱动框架,对添加 BSP 驱动是很重要的。
* 了解 Kconfig 语法

 RT-Thread 系统通过 menuconfig 的方式进行配置,而 menuconfig 中的选项是由 Kconfig 文件决定的,因此想要对 RT-Thread 系统进行配置,需要对 kconfig 语法有一定了解。
* 熟悉 CubeMX 工具的使用

 在新的 STM32 系列 BSP 中利用了 CubeMX 工具对底层硬件进行配置,因此需要了解 CubeMX 工具的使用方法。


### [3. BSP 制作方法]( )


本节以制作正点原子 `stm32f103-atk-nano` 开发板的 BSP 为例,讲解如何为一个新的开发板添加 BSP。


BSP 的制作过程分为如下五个步骤:


1. 复制通用模板
2. 使用 CubeMX 工具配置工程
3. 修改 BSP 中的 Kconfig 文件
4. 修改构建工程相关文件
5. 重新生成工程


在接下来的章节中将会详细介绍这五个步骤,帮助开发者快速创建所需要的 BSP。


#### [3.1 复制通用模板]( )


制作新 BSP 的第一步是复制一份同系列的 BSP 模板作为基础,通过对 BSP 模板的修改来获得新 BSP。目前提供的 BSP 模板系列如下表所示:


工程模板 说明




|  |  |
| --- | --- |
| libraries/templates/stm32f0xx | F0 系列 BSP 模板 |
| libraries/templates/stm32f10x | F1 系列 BSP 模板 |
| libraries/templates/stm32f4xx | F4 系列 BSP 模板 |
| libraries/templates/stm32f7xx | F7 系列 BSP 模板 |
| libraries/templates/stm32l4xx | L4 系列 BSP 模板 |


本次示例所用的 F1 系列 BSP 模板文件夹结构如下所示:


![F1 系列 BSP 模板文件夹内容](https://img-blog.csdnimg.cn/img_convert/4ec01e2bffde01bd6c09537542299200.png)


本次制作的 BSP 为 F1 系列,因此拷贝模板文件夹下的 `stm32f10x` 文件夹,并将该文件夹的名称改为 `stm32f103-atk-nano` ,如下图所示:


![复制通用模板](https://img-blog.csdnimg.cn/img_convert/12fa852d1d304aa54b7015edde166e72.png)


在接下来的 BSP 的制作过程中,将会修改 board 文件夹内的配置文件,将 F1 系列的 BSP 模板变成一个适用于正点原子 `stm32f103-atk-nano` 开发板的 BSP ,下表总结了 board 文件夹中需要修改的内容:


项目 需要修改的内容说明




|  |  |
| --- | --- |
| CubeMX\_Config (文件夹) | CubeMX 工程 |
| linker\_scripts (文件夹) | BSP 特定的链接脚本 |
| board.c/h | 系统时钟、GPIO 初始化函数、芯片存储器大小 |
| Kconfig | 芯片型号、系列、外设资源 |
| SConscript | 芯片启动文件、目标芯片型号 |


#### [3.2 使用 CubeMX 配置工程]( )


在制作 BSP 的第二步,需要创建一个基于目标芯片的 CubeMX 工程。默认的 CubeMX 工程在 **CubeMX\_Config** 文件夹中,双击打开 `CubeMX_Config.ioc` 工程,如下图所示:


![open_cubemx](https://img-blog.csdnimg.cn/img_convert/c7f3c11912aeb57a76e1d20b64f30ac0.png)


在 CubeMX 工程中将芯片型号为修改芯片型号为 STM32F103RBTx 。


##### [3.2.1 生成 CubeMX 工程]( )


配置系统时钟、外设引脚等,步骤如下图所示:


1. 打开外部时钟、设置下载方式、打开串口外设(注意只需要选择串口外设引脚即可,无需配置其他参数):


![配置芯片引脚](https://img-blog.csdnimg.cn/img_convert/7f4cd69056307d569e5dcd2d7e643643.png)


1. 配置系统时钟:


![配置系统时钟](https://img-blog.csdnimg.cn/img_convert/0ded2249b9920b2b01cc77d539e7753e.png)


1. 设置项目名称,并在指定地址重新生成 CubeMX 工程:


![生成对应的配置代码](https://img-blog.csdnimg.cn/img_convert/917ed19e13133766647a167e73fefe96.png)


注意:在生成代码时,不要勾选以下选项(即:不让其生成单独的 .c/.h 驱动文件,直接全部更新到 rt-thread 要使用的 stm32xxx\_hal\_msp.c 文件中)


![generate-code](https://img-blog.csdnimg.cn/img_convert/9832ce30257e6a6e8368726d17f58f7c.png)


最终 CubeMX 生成的工程目录结构如下图所示:


![CubeMX 图7](https://img-blog.csdnimg.cn/img_convert/74edea1764462ebae9b3a213c92a4cc9.png)


##### [3.2.2 拷贝初始化函数]( )


在 **board.c** 文件中存放了函数 `SystemClock_Config()` ,该函数负责初始化系统时钟。当使用 CubeMX 工具对系统时钟重新配置的时候,需要更新这个函数。


该函数由 CubeMX 工具生成,默认存放在`board/CubeMX_Config/Src/main.c` 文件中。但是该文件并没有被包含到我们的工程中,因此需要将这个函数从 main.c 中拷贝到 board.c 文件中。在整个 BSP 的制作过程中,这个函数是唯一要拷贝的函数,该函数内容如下所示:


![board_1](https://img-blog.csdnimg.cn/img_convert/328f1229efae7fd7dfd50882a399e3b4.png)


在 **board.h** 文件中配置了 FLASH 和 RAM 的相关参数,这个文件中需要修改的是 `STM32_FLASH_SIZE` 和 `STM32_SRAM_SIZE` 这两个宏控制的参数。本次制作的 BSP 所用的 STM32F103RBTx 芯片的 flash 大小为 128k,ram 的大小为 20k,因此对该文件作出如下的修改:


![修改 board.h](https://img-blog.csdnimg.cn/img_convert/ab1155739854821b90bb6b2c265b56f0.png)


##### [3.2.3 堆内存配置讲解]( )


通常情况下,系统 RAM 中的一部分内存空间会被用作堆内存。下面代码的作用是,在不同编译器下规定堆内存的起始地址 **HEAP\_BEGIN** 和结束地址 **HEAP\_END**。这里 **HEAP\_BEGIN** 和 **HEAP\_END** 的值需要和后面 [3.4.1 修改链接脚本](# 3.4.1 修改链接脚本) 章节所修改的配置相一致。


在某些系列的芯片中,芯片 RAM 可能分布在不连续的多块内存区域上。此时堆内存的位置可以和系统内存在同一片连续的内存区域,也可以存放在一片独立的内存区域中。例如在 L4 系列的芯片上,就可以将堆内存配置在起始地址为 `0x20000000` 的大小为 96k 的内存空间,而将 `0x10000000` 开始的 32k 内存空间用作系统运行内存。


![heap_config](https://img-blog.csdnimg.cn/img_convert/21cf980e81bd8e284e8c8ce607dced99.png)


#### [3.3 修改 Kconfig 选项]( )


在本小节中修改 `board/Kconfig` 文件的内容有如下两点:


* 芯片型号和系列
* BSP 上的外设支持选项


芯片型号和系列的修改如下表所示:


宏定义 意义 格式




|  |  |  |
| --- | --- | --- |
| SOC\_STM32F103RB | 芯片型号 | SOC\_STM32xxx |
| SOC\_SERIES\_STM32F1 | 芯片系列 | SOC\_SERIES\_STM32xx |


关于 BSP 上的外设支持选项,一个初次提交的 BSP 仅仅需要支持 GPIO 驱动和串口驱动即可,因此在配置选项中只需保留这两个驱动配置项,如下图所示:


![修改 Kconfig](https://img-blog.csdnimg.cn/img_convert/fe2664bd07bc16edb3aa0f60e4168af5.png)


#### [3.4 修改工程构建相关文件]( )


接下来需要修改用于构建工程相关的文件。


##### [3.4.1 修改链接脚本]( )


**linker\_scripts** 链接文件如下图所示:


![需要修改的链接脚本](https://img-blog.csdnimg.cn/img_convert/891863649355a30e3a4c68ee72bded54.png)


下面以 MDK 使用的链接脚本 link.sct 为例,演示如何修改链接脚本:


![linkscripts_change](https://img-blog.csdnimg.cn/img_convert/c9c3dff99dcecbe2f03cac9e669a6a36.png)


本次制作 BSP 使用的芯片为 STM32F103RB,FLASH 为 128k,因此修改 LR\_IROM1 和 ER\_IROM1 的参数为 0x00020000。RAM 的大小为20k, 因此修改 RW\_IRAM1 的参数为 0x00005000。这样的修改方式在一般的应用下就够用了,后续如果有特殊要求,则需要按照链接脚本的语法来根据需求修改。修改链接脚本时,可以参考 [**3.2.3 堆内存配置讲解**](# 3.2.3 堆内存配置讲解) 章节来确定 BSP 的内存分配。


其他两个链接脚本的文件分别为 IAR 使用的 link.icf 和 gcc 编译器使用的 link.lds,修改的方式也是类似的,如下图所示:


* link.icf 修改内容

 ![link_icf](https://img-blog.csdnimg.cn/img_convert/4ab0149da704db111c6d18f1444ee999.png)
* link.lds 修改内容

 ![link_lds](https://img-blog.csdnimg.cn/img_convert/56e2ab96c8762310ef0ed03414aee9b2.png)


##### [3.4.2 修改构建脚本]( )


**SConscript** 脚本决定 MDK/IAR 工程的生成以及编译过程中要添加文件。


在这一步中需要修改芯片型号以及芯片启动文件的地址,修改内容如下图所示:


![修改启动文件和芯片型号](https://img-blog.csdnimg.cn/img_convert/1b4ff1e38bb2e931197d769a7b4cd7a9.png)


注意:如果在文件夹中找不到相应系列的 .s 文件,可能是多个系列的芯片重用了相同的启动文件,此时可以在 CubeMX 中生成目标芯片的工程,查看使用了哪个启动文件,然后再修改启动文件名。


##### [3.4.3 修改工程模板]( )


**template** 文件是生成 MDK/IAR 工程的模板文件,通过修改该文件可以设置工程中使用的芯片型号以及下载方式。MDK4/MDK5/IAR 的工程模板文件,如下图所示:


![MDK/IAR 工程模板](https://img-blog.csdnimg.cn/img_convert/082f439d3523865a32587d2dcb854c0a.png)


下面以 MDK5 模板的修改为例,介绍如何修改模板配置:


![选择芯片型号](https://img-blog.csdnimg.cn/img_convert/ff447a2b951b8cbefaa1101c5c4141c2.png)


修改程序下载方式:


![配置下载方式](https://img-blog.csdnimg.cn/img_convert/f918c8d93228f508e9d2433f4de9ac46.png)


#### [3.5 重新生成工程]( )


重新生成工程需要使用 Env 工具。


在 Env 界面输入命令 menuconfig 对工程进行配置,并生成新的 rtconfig.h 文件。如下图所示:


![输入menuconfig进入配置界面](https://img-blog.csdnimg.cn/img_convert/83f19d1d0f5b2a428aaf9c23e9a66131.png)


![选择要打开的外设](https://img-blog.csdnimg.cn/img_convert/d5fc4e965ddad962e51c2c61c7a4844e.png)


下面以重新生成 MDK 工程为例,介绍如何重新生成 BSP 工程。


使用 env 工具输入命令 `scons --target=mdk5` 重新生成工程,如下图所示:


![重新生成 BSP 工程](https://img-blog.csdnimg.cn/img_convert/3f6ec99a0a66b050b5f51bc0232d8094.png)


重新生成工程成功:


![重新生成 BSP 工程](https://img-blog.csdnimg.cn/img_convert/4abc93d7360a7332892fbbe964c2391b.png)


到这一步为止,新的 BSP 就可以使用了。


接下来我们可以分别使用命令 `scons --target=mdk4` 和 `scons --target=iar`,来更新 MDK4 和 IAR 的工程,使得该 BSP 变成一个完整的,可以提交到 GitHub 的 BSP (MDK4工程的制作为可选)。


感谢每一位贡献代码的开发者,RT-Thread 将与你一同成长。


### [4. 规范]( )


本章节介绍 RT-Thread STM32 系列 BSP 制作与提交时应当遵守的规范 。开发人员在 BSP 制作完成后,可以根据本规范提出的检查点对制作的 BSP 进行检查,确保 BSP 在提交前有较高的质量 。


#### [4.1 BSP 制作规范]( )


STM32 BSP 的制作规范主要分为 3 个方面:工程配置,ENV 配置和 IDE 配置。在已有的 STM32 系列 BSP 的模板中,已经根据下列规范对模板进行配置。在制作新 BSP 的过程中,拷贝模板进行修改时,需要注意的是不要修改这些默认的配置。BSP 制作完成后,需要对新制作的 BSP 进行功能测试,功能正常后再进行代码提交。


下面将详细介绍 BSP 的制作规范。


##### [4.1.1 工程配置]( )


* 遵从RT-Thread 编码规范,代码注释风格统一
* main 函数功能保持一致
	+ 如果有 LED 的话,main 函数里**只放一个** LED 1HZ 闪烁的程序
* 在 `rt_hw_board_init` 中需要完成堆的初始化:调用 `rt_system_heap_init`
* 默认只初始化 GPIO 驱动和 FinSH 对应的串口驱动,不使用 DMA
* 当使能板载外设驱动时,应做到不需要修改代码就能编译下载使用
* 提交前应检查 GCC/MDK/IAR 三种编译器直接编译或者重新生成后编译是否成功
* 使用 `dist` 命令对 BSP 进行发布,检查使用 `dist` 命令生成的工程是否可以正常使用


##### [4.1.2 ENV 配置]( )


* 系统心跳统一设置为 1000(宏:RT\_TICK\_PER\_SECOND)
* BSP 中需要打开调试选项中的断言(宏:RT\_DEBUG)
* 系统空闲线程栈大小统一设置为 256(宏:IDLE\_THREAD\_STACK\_SIZE)
* 开启组件自动初始化(宏:RT\_USING\_COMPONENTS\_INIT)
* 需要开启 user main 选项(宏:RT\_USING\_USER\_MAIN)
* FinSH 默认只使用 MSH 模式(宏:FINSH\_USING\_MSH\_ONLY)


##### [4.1.3 IDE 配置]( )


* 使能下载代码后自动运行
* 使能 C99 支持
* 使能 One ELF Section per Function(MDK)
* MDK/IAR 生成的临时文件分别放到build下的 MDK/IAR 文件夹下
* MDK/GCC/IAR 生成 bin 文件名字统一成 rtthread.bin


#### [4.2 BSP 提交规范]( )


* 提交前请认真修改 BSP 的 README.md 文件,README.md 文件的外设支持表单只填写 BSP 支持的外设,可参考其他 BSP 填写。查看文档[《STM32系列驱动介绍》]( )了解驱动分类。
* 提交 BSP 分为 2 个阶段提交:
	+ 第一阶段:基础 BSP 包括串口驱动和 GPIO 驱动,能运行 FinSH 控制台。完成 MDK4、MDK5 、IAR 和 GCC 编译器支持,如果芯片不支持某款编译器(比如MDK4)可以不用做。 BSP 的 README.md 文件需要填写第二阶段要完成的驱动。
	+ 第二阶段:完成板载外设驱动支持,所有板载外设使用 menuconfig 配置后就能直接使用。若开发板没有板载外设,则此阶段可以不用完成。不同的驱动要分开提交,方便 review 和合并。
* 只提交 BSP 必要的文件,删除无关的中间文件,能够提交的文件请对照其他 BSP。
* 提交 STM32 不同系列的 Library 库时,请参考 f1/f4 系列的 HAL 库,删除多余库文件
* 提交前要对 BSP 进行编译测试,确保在不同编译器下编译正常
* 提交前要对 BSP 进行功能测试,确保 BSP 的在提交前符合工程配置章节中的要求


我有疑问: [RT-Thread 官方论坛]( )


### STM32 系列外设驱动添加指南


已剪辑自: <https://www.rt-thread.org/document/site/#/rt-thread-version/rt-thread-standard/tutorial/make-bsp/stm32-bsp/STM32%E7%B3%BB%E5%88%97%E5%A4%96%E8%AE%BE%E9%A9%B1%E5%8A%A8%E6%B7%BB%E5%8A%A0%E6%8C%87%E5%8D%97>


本文档是为需要给现有的 STM32 BSP 添加更多外设驱动的开发者准备的。通过阅读本文,开发者可以按照自己的实际情况给现有 BSP 添加自己需要的驱动。


* 熟练使用 ENV 工具,参考:[RT-Thread env 工具用户手册]( )
* 熟悉 Kconfig 语法
* 熟悉 STM32CubeMX 工具
* 对 RT-Thread 设备驱动框架有一定了解


本章节以添加片上外设驱动为例,讲解如何为 BSP 添加更多可用驱动。如果想使用的片上外设是 `片上外设配置菜单` 里没有的,就需要开发者自己添加了。下面我们将演示如何为 stm32f429-atk-apollo BSP 添加 SPI3 驱动。


没有安装 stm32cubemx 软件的可以访问 STM32cube中文网:http://www.stm32cube.com/ ,在 `资源下载` 里下载 stm32cubemx 软件。


阿波罗 BSP 默认只支持 SPI1、SPI2 和 SPI5,是不支持 SPI3 的。开发者如果需要使用 SPI3,则需要自己添加。


![spi_config](https://img-blog.csdnimg.cn/img_convert/da2d262a1ac01bf158ae4c5f8ea94686.png)


添加 SPI3 的外设支持需要以下几步:


#### [1)打开 STM32CubeMX 工程]( )


打开 BSP 的 STM32CubeMX 配置文件。


![1543486779576](https://img-blog.csdnimg.cn/img_convert/6ae5ca2040d57b91a03cf6ae41450d35.png)


按图示顺序配置 SPI3,并生成代码。


![1543487684698](https://img-blog.csdnimg.cn/img_convert/63e5ac057376467029ecbfd75830b5c1.png)


为 BSP 添加驱动时,STM32CubeMX 工具可以快速的完成**使能外设**和**配置管脚**的工作。而外设初始化,中断配置,DMA配置等等则由 RT-Thread 提供的驱动文件来完成。也就是说,虽然 STM32CubeMX 生成了多个文件用来初始化外设,但 RT-Thread 只使用了 STM32CubeMX 生成的 `stm32fxx_hal_msp.c` 文件和 `stm32fxx_hal_conf.h` 文件。


对于不同的外设驱动,通过 STM32CubeMX 工具配置的内容也不一样。开发者可以参考本文档的附录 CubeMX 配置说明章节来了解不同外设的配置方法。


#### [3)修改 Kconfig 文件]( )


打开 board 文件夹下的 Konfig 文件,拷贝 SPI2 的配置项,并重命名 SPI2 为 SPI3。


![1543542657074](https://img-blog.csdnimg.cn/img_convert/fcf59d67f96e5712bd1e8d78bae2183c.png)



![img](https://img-blog.csdnimg.cn/img_convert/7651c2b4bea181039631c1046795f014.png)
![img](https://img-blog.csdnimg.cn/img_convert/f5a0c2991c11a5a77907f542fb5b31e7.png)
![img](https://img-blog.csdnimg.cn/img_convert/79b6c211014abe9c114338be129f9b49.png)

**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Go语言开发知识点,真正体系化!**

**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新**

**[如果你需要这些资料,可以戳这里获取](https://bbs.csdn.net/topics/618658159)**

* 提交 BSP 分为 2 个阶段提交:
	+ 第一阶段:基础 BSP 包括串口驱动和 GPIO 驱动,能运行 FinSH 控制台。完成 MDK4、MDK5 、IAR 和 GCC 编译器支持,如果芯片不支持某款编译器(比如MDK4)可以不用做。 BSP 的 README.md 文件需要填写第二阶段要完成的驱动。
	+ 第二阶段:完成板载外设驱动支持,所有板载外设使用 menuconfig 配置后就能直接使用。若开发板没有板载外设,则此阶段可以不用完成。不同的驱动要分开提交,方便 review 和合并。
* 只提交 BSP 必要的文件,删除无关的中间文件,能够提交的文件请对照其他 BSP。
* 提交 STM32 不同系列的 Library 库时,请参考 f1/f4 系列的 HAL 库,删除多余库文件
* 提交前要对 BSP 进行编译测试,确保在不同编译器下编译正常
* 提交前要对 BSP 进行功能测试,确保 BSP 的在提交前符合工程配置章节中的要求


我有疑问: [RT-Thread 官方论坛]( )


### STM32 系列外设驱动添加指南


已剪辑自: <https://www.rt-thread.org/document/site/#/rt-thread-version/rt-thread-standard/tutorial/make-bsp/stm32-bsp/STM32%E7%B3%BB%E5%88%97%E5%A4%96%E8%AE%BE%E9%A9%B1%E5%8A%A8%E6%B7%BB%E5%8A%A0%E6%8C%87%E5%8D%97>


本文档是为需要给现有的 STM32 BSP 添加更多外设驱动的开发者准备的。通过阅读本文,开发者可以按照自己的实际情况给现有 BSP 添加自己需要的驱动。


* 熟练使用 ENV 工具,参考:[RT-Thread env 工具用户手册]( )
* 熟悉 Kconfig 语法
* 熟悉 STM32CubeMX 工具
* 对 RT-Thread 设备驱动框架有一定了解


本章节以添加片上外设驱动为例,讲解如何为 BSP 添加更多可用驱动。如果想使用的片上外设是 `片上外设配置菜单` 里没有的,就需要开发者自己添加了。下面我们将演示如何为 stm32f429-atk-apollo BSP 添加 SPI3 驱动。


没有安装 stm32cubemx 软件的可以访问 STM32cube中文网:http://www.stm32cube.com/ ,在 `资源下载` 里下载 stm32cubemx 软件。


阿波罗 BSP 默认只支持 SPI1、SPI2 和 SPI5,是不支持 SPI3 的。开发者如果需要使用 SPI3,则需要自己添加。


![spi_config](https://img-blog.csdnimg.cn/img_convert/da2d262a1ac01bf158ae4c5f8ea94686.png)


添加 SPI3 的外设支持需要以下几步:


#### [1)打开 STM32CubeMX 工程]( )


打开 BSP 的 STM32CubeMX 配置文件。


![1543486779576](https://img-blog.csdnimg.cn/img_convert/6ae5ca2040d57b91a03cf6ae41450d35.png)


按图示顺序配置 SPI3,并生成代码。


![1543487684698](https://img-blog.csdnimg.cn/img_convert/63e5ac057376467029ecbfd75830b5c1.png)


为 BSP 添加驱动时,STM32CubeMX 工具可以快速的完成**使能外设**和**配置管脚**的工作。而外设初始化,中断配置,DMA配置等等则由 RT-Thread 提供的驱动文件来完成。也就是说,虽然 STM32CubeMX 生成了多个文件用来初始化外设,但 RT-Thread 只使用了 STM32CubeMX 生成的 `stm32fxx_hal_msp.c` 文件和 `stm32fxx_hal_conf.h` 文件。


对于不同的外设驱动,通过 STM32CubeMX 工具配置的内容也不一样。开发者可以参考本文档的附录 CubeMX 配置说明章节来了解不同外设的配置方法。


#### [3)修改 Kconfig 文件]( )


打开 board 文件夹下的 Konfig 文件,拷贝 SPI2 的配置项,并重命名 SPI2 为 SPI3。


![1543542657074](https://img-blog.csdnimg.cn/img_convert/fcf59d67f96e5712bd1e8d78bae2183c.png)



[外链图片转存中...(img-WiYkGl4D-1715549559151)]
[外链图片转存中...(img-sU7ZNTAF-1715549559151)]
[外链图片转存中...(img-JyjfK2v5-1715549559152)]

**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Go语言开发知识点,真正体系化!**

**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新**

**[如果你需要这些资料,可以戳这里获取](https://bbs.csdn.net/topics/618658159)**

  • 18
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值