文章目录
前言
对于从事单片机的开发人员,操作系统可以说是绕不过的一个必修课程。在稍复杂的应用开发中,一个好的操作系统可以帮助我们将单片机的资源最大化的利用起来,而系统提供的各种API接口也可以可靠地帮我们实现各种应用逻辑功能。日常生活里,在各种各样的电子设备中,操作系统被广泛地应用,常见的有Linux,ucos,以及现在在各种物联网设备中被广泛应用的freertos,RT-Thread等。本文主要从裸机工程开始,一步步介绍RT-Thread系统的移植。
一、RT-Thread简介
RT-Thread是一款开源的嵌入式实时操作系统,作为一款国产的操作系统,经过10余年的发展,RT-Thread逐渐得到了市场的认可,开始被广泛应用在智能家电,智能穿戴设备中。相对于其他的RTOS,RT-Thread最大的特点就是有着丰富的中间层组件的支持,我们可以根据产品功能的需要,移植各种组件,打造出自己需要的系统。
具体的介绍就不多说了,有兴趣了解的朋友可以自己去看官方介绍。
官方介绍
二、rtthread的移植
1.裸机例程
在移植RT-Thread前,我们需要一个能正常运行的裸机工程,同时为了方便后续的调试,我们还需要一个USART串口作为一个调试口。从开发板的原理图上我们可以知道,板子上有两路LED可供驱动,并且选择PB0和PB2作为调试串口。
参考开发板提供的例程,(例程可以到华大官网下载官方例程),完成对应的功能,代码如下:
LED驱动部分
/* LED0 Port/Pin definition */
#define LED0_PORT (PortE)
#define LED0_PIN (Pin06)
#define LED0_ON() (PORT_SetBits(LED0_PORT, LED0_PIN))
#define LED0_OFF() (PORT_ResetBits(LED0_PORT, LED0_PIN))
#define LED0_TOGGLE() (PORT_Toggle(LED0_PORT, LED0_PIN))
/* LED1 Port/Pin definition */
#define LED1_PORT (PortA)
#define LED1_PIN (Pin07)
#define LED1_ON() (PORT_SetBits(LED1_PORT, LED1_PIN))
#define LED1_OFF() (PORT_ResetBits(LED1_PORT, LED1_PIN))
#define LED1_TOGGLE() (PORT_Toggle(LED1_PORT, LED1_PIN))
void LedInit(void)
{
stc_port_init_t stcPortInit;
/* configuration structure initialization */
MEM_ZERO_STRUCT(stcPortInit);
stcPortInit.enPinMode = Pin_Mode_Out;
/* LED0 Port/Pin initialization */
LED0_OFF();
PORT_Init(LED0_PORT, LED0_PIN, &stcPortInit);
/* LED1 Port/Pin initialization */
LED1_OFF();
PORT_Init(LED1_PORT, LED1_PIN, &stcPortInit);
}
USART驱动部分
/* USART channel definition */
#define DEBUG_USART_CH (M4_USART1)
/* USART baudrate definition */
#define DEBUG_USART_BAUDRATE (115200ul)
/* USART RX Port/Pin definition */
#define DEBUG_USART_RX_PORT (PortB)
#define DEBUG_USART_RX_PIN (Pin02)
#define DEBUG_USART_RX_FUNC (Func_Usart1_Rx)
/* USART TX Port/Pin definition */
#define DEBUG_USART_TX_PORT (PortB)
#define DEBUG_USART_TX_PIN (Pin00)
#define DEBUG_USART_TX_FUNC (Func_Usart1_Tx)
void DebugUsartInit(void)
{
en_result_t enRet = Ok;
uint32_t u32Fcg1Periph = PWC_FCG1_PERIPH_USART1 | PWC_FCG1_PERIPH_USART2 | \
PWC_FCG1_PERIPH_USART3 | PWC_FCG1_PERIPH_USART4;
const stc_usart_uart_init_t stcInitCfg = {
UsartIntClkCkNoOutput,
UsartClkDiv_1,
UsartDataBits8,
UsartDataLsbFirst,
UsartOneStopBit,
UsartParityNone,
UsartSampleBit8,
UsartStartBitFallEdge,
UsartRtsEnable,
};
/* Enable peripheral clock */
PWC_Fcg1PeriphClockCmd(u32Fcg1Periph, Enable);
/* Initialize USART IO */
PORT_SetFunc(DEBUG_USART_RX_PORT, DEBUG_USART_RX_PIN, DEBUG_USART_RX_FUNC, Disable);
PORT_SetFunc(DEBUG_USART_TX_PORT, DEBUG_USART_TX_PIN, DEBUG_USART_TX_FUNC, Disable);
/* Initialize UART */
enRet = USART_UART_Init(DEBUG_USART_CH, &stcInitCfg);
if (enRet != Ok)
{
while (1)
{
}
}
/* Set baudrate */
enRet = USART_SetBaudrate(DEBUG_USART_CH, DEBUG_USART_BAUDRATE);
if (enRet != Ok)
{
while (1)
{
}
}
/*Enable RX && TX function*/
USART_FuncCmd(DEBUG_USART_CH, UsartRx, Enable);
USART_FuncCmd(DEBUG_USART_CH, UsartTx, Enable);
}
在PC上开发我们习惯使用C库中的printf做输出和调试,而将printf重定向为串口输出的方法,就是需要重新实现fputc()这个函数。在华大的驱动库中,我们可以看到官方已经帮我们实现了相关的功能(感兴趣的可以自行查看官方代码hc32f460_utility.c,这里不做讲解),而我们所需要做的,就是调用官方的API接口,注册对应的串口驱动,就可以在我们的工程中使用printf函数做输出和调试。最后代码如下:
main.c
/**
* @brief BSP clock initialize.
* Set board system clock to MPLL@200MHz
* @param None
* @retval None
*/
void BSP_CLK_Init(void)
{
stc_clk_sysclk_cfg_t stcSysClkCfg;
stc_clk_xtal_cfg_t stcXtalCfg;
stc_clk_mpll_cfg_t stcMpllCfg;
stc_sram_config_t stcSramConfig;
MEM_ZERO_STRUCT(stcSysClkCfg);
MEM_ZERO_STRUCT(stcXtalCfg);
MEM_ZERO_STRUCT(stcMpllCfg);
MEM_ZERO_STRUCT(stcSramConfig);
/* Set bus clk div. */
stcSysClkCfg.enHclkDiv = ClkSysclkDiv1;
stcSysClkCfg.enExclkDiv = ClkSysclkDiv2;
stcSysClkCfg.enPclk0Div = ClkSysclkDiv1;
stcSysClkCfg.enPclk1Div = ClkSysclkDiv2;
stcSysClkCfg.enPclk2Div = ClkSysclkDiv4;
stcSysClkCfg.enPclk3Div = ClkSysclkDiv4;
stcSysClkCfg.enPclk4Div = ClkSysclkDiv2;
CLK_SysClkConfig(&stcSysClkCfg);
/* Config Xtal and Enable Xtal */
stcXtalCfg.enMode = ClkXtalModeOsc;
stcXtalCfg.enDrv = ClkXtalLowDrv;
stcXtalCfg.enFastStartup = Enable;
CLK_XtalConfig(&stcXtalCfg);
CLK_XtalCmd(Enable);
/* sram init include read/write wait cycle setting */
stcSramConfig.u8SramIdx = Sram12Idx | Sram3Idx | SramHsIdx | SramRetIdx;
stcSramConfig.enSramRC = SramCycle2;
stcSramConfig.enSramWC = SramCycle2;
SRAM_Init(&stcSramConfig);
/* flash read wait cycle setting */
EFM_Unlock();
EFM_SetLatency(EFM_LATENCY_5);
EFM_Lock();
/* MPLL config (XTAL / pllmDiv * plln / PllpDiv = 200M). */
stcMpllCfg.pllmDiv = 1ul;
stcMpllCfg.plln = 50ul;
stcMpllCfg.PllpDiv = 2ul;
stcMpllCfg.PllqDiv = 2ul;
stcMpllCfg.PllrDiv = 2ul;
CLK_SetPllSource(ClkPllSrcXTAL);
CLK_MpllConfig(&stcMpllCfg);
/* Enable MPLL. */
CLK_MpllCmd(Enable);
/* Wait MPLL ready. */
while(Set != CLK_GetFlagStatus(ClkFlagMPLLRdy))
{
;
}
/* Switch driver ability */
PWC_HS2HP();
/* Switch system clock source to MPLL. */
CLK_SetSysClkSource(CLKSysSrcMPLL);
}
/**
*******************************************************************************
** \brief Main function of template project
**
** \param None
**
** \retval int32_t return value, if needed
**
******************************************************************************/
int32_t main(void)
{
BSP_CLK_Init(); //系统时钟初始化
DDL_PrintfInit(DEBUG_USART_CH, DEBUG_USART_BAUDRATE, DebugUsartInit);
LedInit();
DDL_Printf("hc32 example. \r\n");
/* add your code here */
while (1)
{
LED0_TOGGLE();
LED1_TOGGLE();
Ddl_Delay1ms(500);
}
}
运行,通过pc端可以看到串口有输出,同时LED灯也有闪烁。
2.RT-Thread下载
RT-Thread针对不同的应用场景,提供了三个主版本:RT-Thread 标准版、RT-Thread Nano 和 RT-Thread Smart。
下载地址
这里根据项目需求,我们选择RT-Thread Nano(目前版本更新到3.1.5)。
下载后解压缩,目录结构如下:
3.RT-Thread移植
准备工作都已经做好了,接下来可以开始RT-Thread的移植了。
将rt-thread整个文件夹拷贝到我们的工程目录下,为了工程的精简,我们可以删掉一些我们不需要的文件。
将RT-Thread添加到我们的工程中。
添加头文件路径
移植RT-Thread,我们主要需要修改的是board.c和rtconfig.h这两个文件,board.c主要是配置系统时钟和OS_TICK(为操作系统提供心跳),而rtconfig.h则是可以根据需要配置系统的各项功能的。这里我们的配置如下(仅供参考):
rtconfig.h
/* RT-Thread config file */
#ifndef __RTTHREAD_CFG_H__
#define __RTTHREAD_CFG_H__
#include <rtthread.h>
#if defined(__CC_ARM) || defined(__CLANG_ARM)
//#include "RTE_Components.h"
#if defined(RTE_USING_FINSH)
#define RT_USING_FINSH
#endif //RTE_USING_FINSH
#endif //(__CC_ARM) || (__CLANG_ARM)
// <<< Use Configuration Wizard in Context Menu >>>
// <h>Basic Configuration
// <o>Maximal level of thread priority <8-256>
// <i>Default: 32
#define RT_THREAD_PRIORITY_MAX 8
// <o>OS tick per second
// <i>Default: 1000 (1ms)
#define RT_TICK_PER_SECOND 1000
// <o>Alignment size for CPU architecture data access
// <i>Default: 4
#define RT_ALIGN_SIZE 4
// <o>the max length of object name<2-16>
// <i>Default: 8
#define RT_NAME_MAX 8
// <c1>Using RT-Thread components initialization
// <i>Using RT-Thread components initialization
#define RT_USING_COMPONENTS_INIT
// </c>
#define RT_USING_USER_MAIN
// <o>the stack size of main thread<1-4086>
// <i>Default: 512
#define RT_MAIN_THREAD_STACK_SIZE 1024
// </h>
// <h>Debug Configuration
// <c1>enable kernel debug configuration
// <i>Default: enable kernel debug configuration
//#define RT_DEBUG
// </c>
// <o>enable components initialization debug configuration<0-1>
// <i>Default: 0
#define RT_DEBUG_INIT 0
// <c1>thread stack over flow detect
// <i> Diable Thread stack over flow detect
//#define RT_USING_OVERFLOW_CHECK
// </c>
// </h>
// <h>Hook Configuration
// <c1>using hook
// <i>using hook
//#define RT_USING_HOOK
// </c>
// <c1>using idle hook
// <i>using idle hook
//#define RT_USING_IDLE_HOOK
// </c>
// </h>
// <e>Software timers Configuration
// <i> Enables user timers
#define RT_USING_TIMER_SOFT 0
#if RT_USING_TIMER_SOFT == 0
#undef RT_USING_TIMER_SOFT
#endif
// <o>The priority level of timer thread <0-31>
// <i>Default: 4
#define RT_TIMER_THREAD_PRIO 4
// <o>The stack size of timer thread <0-8192>
// <i>Default: 512
#define RT_TIMER_THREAD_STACK_SIZE 512
// </e>
// <h>IPC(Inter-process communication) Configuration
// <c1>Using Semaphore
// <i>Using Semaphore
#define RT_USING_SEMAPHORE
// </c>
// <c1>Using Mutex
// <i>Using Mutex
//#define RT_USING_MUTEX
// </c>
// <c1>Using Event
// <i>Using Event
//#define RT_USING_EVENT
// </c>
// <c1>Using MailBox
// <i>Using MailBox
#define RT_USING_MAILBOX
// </c>
// <c1>Using Message Queue
// <i>Using Message Queue
//#define RT_USING_MESSAGEQUEUE
// </c>
// </h>
// <h>Memory Management Configuration
// <c1>Dynamic Heap Management
// <i>Dynamic Heap Management
//#define RT_USING_HEAP
// </c>
// <c1>using small memory
// <i>using small memory
#define RT_USING_SMALL_MEM
// </c>
// <c1>using tiny size of memory
// <i>using tiny size of memory
//#define RT_USING_TINY_SIZE
// </c>
// </h>
// <h>Console Configuration
// <c1>Using console
// <i>Using console
#define RT_USING_CONSOLE
// </c>
// <o>the buffer size of console <1-1024>
// <i>the buffer size of console
// <i>Default: 128 (128Byte)
#define RT_CONSOLEBUF_SIZE 128
// </h>
#if defined(RT_USING_FINSH)
#define FINSH_USING_MSH
#define FINSH_USING_MSH_ONLY
// <h>Finsh Configuration
// <o>the priority of finsh thread <1-7>
// <i>the priority of finsh thread
// <i>Default: 6
#define __FINSH_THREAD_PRIORITY 5
#define FINSH_THREAD_PRIORITY (RT_THREAD_PRIORITY_MAX / 8 * __FINSH_THREAD_PRIORITY + 1)
// <o>the stack of finsh thread <1-4096>
// <i>the stack of finsh thread
// <i>Default: 4096 (4096Byte)
#define FINSH_THREAD_STACK_SIZE 512
// <o>the history lines of finsh thread <1-32>
// <i>the history lines of finsh thread
// <i>Default: 5
#define FINSH_HISTORY_LINES 1
#define FINSH_USING_SYMTAB
// </h>
#endif
// <<< end of configuration section >>>
#endif
board.c
/*
* Copyright (c) 2006-2019, RT-Thread Development Team
*
* SPDX-License-Identifier: Apache-2.0
*
* Change Logs:
* Date Author Notes
* 2017-07-24 Tanek the first version
* 2018-11-12 Ernest Chen modify copyright
*/
#include <stdint.h>
#include <rtthread.h>
#include "hc32_ddl.h"
static void sysTick_Init(void);
static void SysClkConfig(void);
static void SysClkConfig(void)
{
stc_clk_xtal_cfg_t stcXtalCfg;
stc_clk_mpll_cfg_t stcMpllCfg;
en_clk_sys_source_t enSysClkSrc;
stc_clk_sysclk_cfg_t stcSysClkCfg;
MEM_ZERO_STRUCT(enSysClkSrc);
MEM_ZERO_STRUCT(stcSysClkCfg);
MEM_ZERO_STRUCT(stcXtalCfg);
MEM_ZERO_STRUCT(stcMpllCfg);
/* Set bus clk div. */
stcSysClkCfg.enHclkDiv = ClkSysclkDiv1;
stcSysClkCfg.enExclkDiv = ClkSysclkDiv2;
stcSysClkCfg.enPclk0Div = ClkSysclkDiv1;
stcSysClkCfg.enPclk1Div = ClkSysclkDiv2;
stcSysClkCfg.enPclk2Div = ClkSysclkDiv4;
stcSysClkCfg.enPclk3Div = ClkSysclkDiv4;
stcSysClkCfg.enPclk4Div = ClkSysclkDiv2;
CLK_SysClkConfig(&stcSysClkCfg);
/* Switch system clock source to MPLL. */
/* Use Xtal as MPLL source. */
stcXtalCfg.enMode = ClkXtalModeOsc;
stcXtalCfg.enDrv = ClkXtalLowDrv;
stcXtalCfg.enFastStartup = Enable;
CLK_XtalConfig(&stcXtalCfg);
CLK_XtalCmd(Enable);
/* MPLL config. */
stcMpllCfg.pllmDiv = 1u; /* XTAL 8M / 1 */
stcMpllCfg.plln = 50u; /* 8M*50 = 400M */
stcMpllCfg.PllpDiv = 4u; /* MLLP = 100M */
stcMpllCfg.PllqDiv = 4u; /* MLLQ = 100M */
stcMpllCfg.PllrDiv = 4u; /* MLLR = 100M */
CLK_SetPllSource(ClkPllSrcXTAL);
CLK_MpllConfig(&stcMpllCfg);
/* flash read wait cycle setting */
EFM_Unlock();
EFM_SetLatency(EFM_LATENCY_4);
EFM_Lock();
/* Enable MPLL. */
CLK_MpllCmd(Enable);
/* Wait MPLL ready. */
while (Set != CLK_GetFlagStatus(ClkFlagMPLLRdy))
{
}
/* Switch system clock source to MPLL. */
CLK_SetSysClkSource(CLKSysSrcMPLL);
}
#if defined(RT_USING_USER_MAIN) && defined(RT_USING_HEAP)
#define RT_HEAP_SIZE 4096
static uint32_t rt_heap[RT_HEAP_SIZE]; // heap default size: 4K(1024 * 4)
RT_WEAK void *rt_heap_begin_get(void)
{
return rt_heap;
}
RT_WEAK void *rt_heap_end_get(void)
{
return rt_heap + RT_HEAP_SIZE;
}
#endif
/**
* This function will initial your board.
*/
void rt_hw_board_init()
{
/* Call components board initial (use INIT_BOARD_EXPORT()) */
SysClkConfig();
sysTick_Init();
/* Call components board initial (use INIT_BOARD_EXPORT()) */
#ifdef RT_USING_COMPONENTS_INIT
rt_components_board_init();
#endif
#if defined(RT_USING_USER_MAIN) && defined(RT_USING_HEAP)
rt_system_heap_init(rt_heap_begin_get(), rt_heap_end_get());
#endif
}
void SysTick_Handler(void)
{
/* enter interrupt */
rt_interrupt_enter();
rt_tick_increase();
/* leave interrupt */
rt_interrupt_leave();
}
static void sysTick_Init(void)
{
stc_clk_freq_t stcClkFreq;
/* configure structure initialization */
MEM_ZERO_STRUCT(stcClkFreq);
/* Config 1 sec trigger interrupt*/
CLK_GetClockFreq(&stcClkFreq);
SysTick_Config(stcClkFreq.sysclkFreq/RT_TICK_PER_SECOND);
}
更改完这两个文件后,我们可以尝试编译,会发现会报错(如下图),我们只需要在hc32f460_interrupts.c文件中删掉对应的函数,重新编译即可。
最后,编写代码简单测试下系统能否正常运行。
static struct rt_thread led_thread;
ALIGN(RT_ALIGN_SIZE)
static rt_uint8_t rt_led_thread_stack[1024];
static void led_thread_entry(void * para);
static rt_uint8_t led_thread_priority = 6;
/**
*******************************************************************************
** \brief Main function of template project
**
** \param None
**
** \retval int32_t return value, if needed
**
******************************************************************************/
int32_t main(void)
{
DDL_PrintfInit(DEBUG_USART_CH, DEBUG_USART_BAUDRATE, DebugUsartInit);
LedInit();
DDL_Printf("hc32 example. \r\n");
/* main thread */
rt_thread_init(&led_thread,
"led_thread",
led_thread_entry,
RT_NULL,
&rt_led_thread_stack,
sizeof(rt_led_thread_stack),
led_thread_priority,
1000);
rt_thread_startup(&led_thread);
}
static void led_thread_entry(void * para)
{
DDL_Printf("Entry %s. \r\n", __func__);
while(1)
{
LED0_TOGGLE();
LED1_TOGGLE();
rt_thread_delay(1000);
}
}
正常运行
4.finsh组件移植
最后我们来讲一下RT-Thread FinSh组件的移植。Finsh是RT-Thread提供的一套命令行组件,在命令行下通过各种指令的调用,我们可以很轻松的获取到当前系统的运行状态,在项目开发中可以方便我们的调试。
添加源码
配置头文件路径
更改配置文件
rtconfig.h
#define RTE_USING_FINSH
#if defined(RTE_USING_FINSH)
#define RT_USING_FINSH
#endif //RTE_USING_FINSH
board.c
/**
* This function will initial your board.
*/
void rt_hw_board_init()
{
/* Call components board initial (use INIT_BOARD_EXPORT()) */
SysClkConfig();
sysTick_Init();
/* Call components board initial (use INIT_BOARD_EXPORT()) */
#ifdef RT_USING_COMPONENTS_INIT
rt_components_board_init();
#endif
#if defined(RT_USING_USER_MAIN) && defined(RT_USING_HEAP)
rt_system_heap_init(rt_heap_begin_get(), rt_heap_end_get());
#endif
DDL_PrintfInit(DEBUG_USART_CH, DEBUG_USART_BAUDRATE, DebugUsartInit);
}
void rt_hw_console_output(const char *str)
{
rt_size_t i = 0, size = 0;
char a = '\r';
size = rt_strlen(str);
for (i = 0; i < size; i++)
{
if (*(str + i) == '\n')
{
DEBUG_USART_CH->DR = a;
while (0ul == DEBUG_USART_CH->SR_f.TC)
{
;
}
}
DEBUG_USART_CH->DR = *(str + i);
while (0ul == DEBUG_USART_CH->SR_f.TC)
{
;
}
}
}
char rt_hw_console_getchar(void)
{
int ch = -1;
if (Set == USART_GetStatus(DEBUG_USART_CH, UsartRxNoEmpty))
{
ch = DEBUG_USART_CH->DR_f.RDR;
}
else
{
if(Set == USART_GetStatus(DEBUG_USART_CH, UsartOverrunErr))
{
USART_ClearStatus(DEBUG_USART_CH, UsartOverrunErr);
}
rt_thread_mdelay(10);
}
return ch;
}
运行
最后附上源码链接:hc32f460petb_template.zip
总结
以上就是今天分享内容的全部,本文仅是简单的介绍了RT-Thread+FinSh组件的移植,若是有什么问题欢迎评论或私信提出。