历史上的今天
返回首页

历史上的今天

今天是:2025年12月07日(星期日)

正在发生

2022年12月07日 | STM32F103+RT-Thread从零开始(二)——RTT系统中点亮LED

2022-12-07 来源:zhihu

上次的的推文简单说了下如何使用Keil创建STM32F103的工程,并且完成了LED点亮,及让LED等闪烁的功能,那是诸多同学学习单片机的起手式。本篇推文是继续上一篇推文的内容,依旧是点亮LED,不同的是,这次点亮LED等,是在RT-Thread操作系统中进行的。


创建工程


创建一个Keil工程,芯片依旧选择STM32F103C8T6,然后在Manage Run-Time Environment对话框中选择需要用的的软件组件,与上文不同的是,我们需要把RTT一起勾上。如下图:



上图中,红线框中即为RTT操作系统的组件,分别为设备驱动,系统内核以及shell。蓝线框中为Keil的RTX操作系统。我们现在要用的是RTT,所以勾选RTT的组件即可,其中Kernel为必选项,device drivers依赖kernel,shell又依赖device drivers。


shell也提一下,shell强翻成中文就是命令行外壳,如同linux操作系统一样,RTT也提供了一套共用户在命令行操作的操作接口。RTT提供的这套接口叫做finsh,主要用于调试、查看系统信息。finsh支持两种模式:


1. C语言解释器模式, 为行文方便称之为c-style;

2. 传统命令行模式,此模式又称为msh(module shell)。


在大部分嵌入式系统中,一般开发调试都使用硬件调试器和printf日志打印,在有些情况下,这两种方式并不是那么好用。比如对于RT-Thread这个多线程系统,我们想知道某个时刻系统中的线程运行状态、手动控制系统状态。如果有一个shell,就可以输入命令,直接相应的函数执行获得需要的信息,或者控制程序的行为。这无疑会非常方便。finsh就是基于此而设计,它运行于开发板,可以使用串口/以太网/USB等与PC机进行通信。


创建工程后,相对上一篇推文创建的工程,项目中会多出了RTT,如下图。至于各个文件及其作用,后续使用的时候再逐步理解。我们当前最需要关注的是board.c和rtthread.h两个文件。从图中可以看出,只有这两个文件上没有标注钥匙,有钥匙标注的是不允许更改,也就是我们能更改就是这两个文件。后面再分析这两个文件。且走下一步。





编写点灯程序


创建好工程后,开始编写点灯程序了,与上篇推文一样,直接贴上代码:


#include "rtthread.h"

#include "stm32f10x.h"

#include "stm32f10x_gpio.h"

int main(){

GPIO_InitTypeDef gpioInit;

//打开GPIOB的时钟


RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);



//LED上拉连接GPIOB 12引脚,所以设置如下,推挽输出,Pin12,2MHz输出速度



gpioInit.GPIO_Mode=GPIO_Mode_Out_PP;


gpioInit.GPIO_Pin=GPIO_Pin_12;


gpioInit.GPIO_Speed=GPIO_Speed_2MHz;


GPIO_Init(GPIOB,&gpioInit);

while(1){


//点亮LED


GPIO_ResetBits(GPIOB,GPIO_Pin_12);


//延时0.5s


rt_thread_delay(RT_TICK_PER_SECOND/2);


//关闭LED


GPIO_SetBits(GPIOB,GPIO_Pin_12);


//延时0.5s


rt_thread_delay(RT_TICK_PER_SECOND/2);

}

}


这样编写程序后,编译通过,烧写后却发现LED根本无法按照预期进行工作,这是因为我们还缺少工作没有做。
打开board.c,可以看到它上面有几句注释,根据注释,修改如下:


#include

#include

#include "stm32f10x_rcc.h"

// rtthread tick configuration


// 1. include header files


// 2. configure rtos tick and interrupt


// 3. add tick interrupt handler


// rtthread tick configuration


// 1. include some header file as need


//#include

#ifdef __CC_ARM


extern int Image$$RW_IRAM1$$ZI$$Limit;


#define HEAP_BEGIN (&Image$$RW_IRAM1$$ZI$$Limit)


#elif __ICCARM__


#pragma section="HEAP"


#define HEAP_BEGIN (__segment_end("HEAP"))


#else


extern int __bss_end;


#define HEAP_BEGIN (&__bss_end)


#endif

#define SRAM_SIZE 8


#define SRAM_END (0x20000000 + SRAM_SIZE * 1024)

/**

* This function will initial STM32 board.

*/

void rt_hw_board_init()

{

// rtthread tick configuration


// 2. Configure rtos tick and interrupt



//SysTick_Config(SystemCoreClock / RT_TICK_PER_SECOND);



SysTick_Config(SystemCoreClock / RT_TICK_PER_SECOND);



/* Call components board initial (use INIT_BOARD_EXPORT()) */



#ifdef RT_USING_COMPONENTS_INIT


rt_components_board_init();


#endif

#if defined(RT_USING_CONSOLE) && defined(RT_USING_DEVICE)


rt_console_set_device(RT_CONSOLE_DEVICE_NAME);


#endif

#if defined(RT_USING_USER_MAIN) && defined(RT_USING_HEAP)


rt_system_heap_init((void*)HEAP_BEGIN, (void*)SRAM_END);



#endif

}

// rtthread tick configuration

// 3. add tick interrupt handler

// tickvoid SysTick_Handler(void)

// {

// /* enter interrupt */

// rt_interrupt_enter();

//

// rt_tick_increase();

//

// /* leave interrupt */

// rt_interrupt_leave();

// }

void SysTick_Handler(void)

{

// /* enter interrupt */

rt_interrupt_enter();

//

rt_tick_increase();

//

// /* leave interrupt */

rt_interrupt_leave();

}


再次编译,烧写程序,LED开始闪烁。



RTT第一次分析



board.c修改后,程序就正常工作了。可是为什么呢?根据经验来说,C程序不是从main开始的么,board中的程序又是何时执行的呢?在main中我们有死循环,如果是从main开始执行的,那么board.c的函数就绝对不可能执行了。


为什么不是从main开始执行的


Ctrl+F搜索rt_hw_board_init函数。发现他在components.c中的rtthread_startup调用,再搜索rtthread_startup结构发现其调用如下:


#if defined (__CC_ARM)


extern int $Super$$main(void);


/* re-define main function */


int $Sub$$main(void)


{


rt_hw_interrupt_disable();


rtthread_startup();


return 0;

}

#elif defined(__ICCARM__)


extern int main(void);


/* __low_level_init will auto called by IAR cstartup */


extern void __iar_data_init3( void );


int __low_level_init(void)


{


// call IAR table copy function.


__iar_data_init3();


rt_hw_interrupt_disable();


rtthread_startup();


return 0;


}


#elif defined(__GNUC__)


extern int main(void);


/* Add -eentry to arm-none-eabi-gcc argument */


int entry(void)


{

rt_hw_interrupt_disable();


rtthread_startup();


return 0;

}


#endif



在上面预处理指令有三段,分别判断三个宏是否被定义——__CC_ARM、__ICCARM__、__GNUC__,这三个是什么呢?如果全局搜索,会发现在core_cm3.h中它们出现很多次了。
ARM 系列目前支持三大主流的工具链,即ARM RealView (armcc), IAR EWARM (iccarm), and GNU Compler Collection (gcc),这三个就是用来指示当前使用的是哪个工具链。因为我们使用的就是RealView(Keil)了。
可以看到$Super$$main和$Sub$$main,这又是什么呢?
在某些情况下,无法修改现有符号,例如,由于符号位于外部库或 ROM 代码中。为了解决这个问题,Keil提供了使用 $Super$$ 和 $Sub$$ 模式来修补现有符号的方法。 $Super$$标识的是原函数,$Sub$$标识的是新函数。上面的代码就是它们用法的最好示例了。


extern int $Super$$main(void);


/* re-define main function */


int $Sub$$main(void)


{

rt_hw_interrupt_disable();


rtthread_startup();


return 0;

}


这样,程序的执行就不是从用户写的main方法开始了。而是从这个$Sub$$main(void)开始的了。


main是怎么执行的


已经知道了程序不是从main开始执行的是RTT系统作的怪,那么用户写的main方法是何时执行的呢?接着搜索$Super$$main,得到其调用如下:


/* the system main thread */


void main_thread_entry(void *parameter)


{


extern int main(void);


extern int $Super$$main(void);


/* RT-Thread components initialization */


rt_components_init();


/* invoke system main function */


#if defined (__CC_ARM)


$Super$$main(); /* for ARMCC. */


#elif defined(__ICCARM__) || defined(__GNUC__)


main();


#endif


}


接着搜索main_thread_entry得带代码如下:



void rt_application_init(void)

{

rt_thread_t tid;

#ifdef RT_USING_HEAP

tid = rt_thread_create("main", main_thread_entry, RT_NULL,


RT_MAIN_THREAD_STACK_SIZE, R


T_THREAD_PRIORITY_MAX / 3, 20);


RT_ASSERT(tid != RT_NULL);


#else


rt_err_t result;

tid = &main_thread;


result = rt_thread_init(tid, "main", main_thread_entry, RT_NULL,


main_stack, sizeof(main_st


ack), RT_THREAD_PRIORITY_MAX / 3, 20);


RT_ASSERT(result == RT_EOK);


#endif

rt_thread_startup(tid);

}


从名字就可以看得出来,这是在造线程啊,查阅下rtthread的官方文档果然如此。rt_application_init被rtthread_startup调用,然后它创建了一个线程,并在线程中调用了用户定义的main函数。至此就真相大白了。RTT利用工具链提供的方式,替换掉了用户的main,来启动操作系统,并创建了一条线程,在线程中调用了用户的main方法。


至此,RTT操作系统就已经在STM32C8T6核心板上跑起来了。后续使用RTT操作系统得先看下官方文档,然后在使用中实践,在实践中深入理解,以便更快更好的掌握RTT


推荐阅读

史海拾趣

H&D Wireless公司的发展小趣事

2017年,高创被美的集团全资收购,成为美的工业技术事业群的重要业务板块之一。这一转变标志着高创进入了一个全新的发展阶段。借助美的集团的平台力量,高创在技术研发、生产制造、供应链管理等方面得到了显著提升。同时,与美的集团旗下其他业务板块如库卡机器人等的协同合作,进一步拓宽了高创的市场应用领域。

DZUS公司的发展小趣事

为了提高产品质量和客户满意度,DZUS公司决定建立质量管理体系。公司引进了先进的生产设备和检测设备,并制定了严格的生产流程和检验标准。此外,公司还加强了员工培训和技能提升工作,确保每一个员工都能熟练掌握操作技能和产品质量要求。这些措施的实施使DZUS公司的产品质量得到了显著提升,并赢得了客户的广泛赞誉。

AUK Contractors Co Ltd公司的发展小趣事

AUK Contractors Co Ltd深知人才是企业发展的核心。因此,公司一直注重人才培养和团队建设。通过定期的培训、交流以及激励机制,公司吸引并留住了一批批优秀的电子工程师和技术人才。这些人才为公司的发展提供了源源不断的动力。

DUCATI公司的发展小趣事

AUK Contractors Co Ltd成立之初,电子市场正处于技术革新的热潮中。公司凭借对新技术敏锐的洞察力,成功研发出一款具有颠覆性的电路板设计,显著提高了电子设备的性能和稳定性。这一创新不仅赢得了客户的广泛认可,还为公司赢得了多个重要合同,从而奠定了在电子行业中的坚实地位。

Digitron公司的发展小趣事

为了进一步扩大市场份额,Digitron公司积极寻求与行业内外的企业建立战略合作关系。XXXX年,Digitron公司与一家全球知名的物流公司达成合作协议,为其提供定制化的温度监控解决方案。这一合作不仅为Digitron公司带来了可观的收入,还提高了其在物流行业的知名度。此外,Digitron公司还积极参加国际展会和论坛,与全球客户建立联系,拓展国际市场。

DAYLIGHT公司的发展小趣事

随着全球环保意识的提高,DAYLIGHT公司也开始注重环保和可持续发展。公司投入大量资金用于研发环保型电子产品和技术,并积极参与环保公益活动。此外,DAYLIGHT还制定了严格的环保标准和生产流程,确保其产品的生产和使用过程中对环境的影响最小化。

问答坊 | AI 解惑

弱问一个关于白光烙铁芯的问题~~~~

不知坛里有哪位用过白光烙铁芯,或有关这方面的资料。 我想问一下它这个芯子里用的是哪种热电偶来做温度检测??? 有人说是J型分度,我测了一下,有点像是E型分度的。 [ 本帖最后由 huchuan987 于 2008-12-29 22:08 编辑 ]…

查看全部问答>

74系列功能大全(中文)

74系列功能大全(中文)…

查看全部问答>

s3c2440的核心电源问题

s3c2440的datasheet上写的核心的电源和主频有关系,但是他只说了400m和300m该用什么核心电源,如果我用是320m主频应该的核心电压是多少呢?…

查看全部问答>

有谁知道 PQ DOS版 (DOS下的硬盘分区软件)的界面是用什么技术开发的?

有谁知道 PQ DOS版 (DOS下的硬盘分区软件)的界面是用什么技术开发的? 在DOS下它竟然有WINDOWS 95类似的界面,例如对话框、输入框、按钮、标题栏等等GUI元素,难道它是PQ 软件自己画出来的界面吗? 或者他使用了什么界面库?例如类似QT界面库? ...…

查看全部问答>

谁用过samsung6410+ddr+onenand(wince)

谁用过samsung6410+ddr+onenand(wince),samsung提供的bsp只支持nand.没有onenand。。是不是samsung还有相对应的bsp??…

查看全部问答>

vc005开发智能设备sdi 如何得到程序运行的跟目录, 和相对目录

vc005开发智能设备sdi 如何得到程序运行的跟目录, 和相对目录…

查看全部问答>

有用c8051f060作数据采集的吗?DMA怎么用?

我写的程序把AD转换后的数据直接存储后,察看片外sram内容发现数据存了两遍,请那位大侠指教以下,谢谢!运行结果察看片外sram内容现象如下: 34123 561 34123 561 42157 3186 42157 3186 ... ... 那位帮我解决以下。…

查看全部问答>

请教个小问题

内核为应用程序提供基本服务,为了能支持流接口驱动程序,内核重定向应用程序的文件I/0函数到适当的流接口驱动程序进入点。 wince内核为什么要一直运行着?也就是说为什么系统一开始加载就要运行nk.exe? 需要时才调用不行吗?…

查看全部问答>

哪位大侠研究或设计过智能限电系统?

哎,宿舍被智能限电了~~   只打开洗衣机可以洗涤,不能漂洗和甩干了,一到漂洗和甩干阶段就跳闸,洗衣机的额定功率是330W   打开电脑台式机(纯平)+日光灯+21寸彩色电视机之类的电器没有关系,电视机的功率应该在100瓦左右,电脑台 ...…

查看全部问答>

我是菜鸟,问个蠢蠢的问题

本帖最后由 dontium 于 2015-1-23 13:24 编辑 我用的是TMS320LF2407-C板,在做PWM实验时,程序里有这几条语句,请各位大侠帮帮我看是什么意思 CMPR1=0X1000;比较单元一设置,设置成什么了? CMPR2=0X3000;比较单元二设置成什么了? TIPER=0X6000;设置 ...…

查看全部问答>