STM32F103+RT-Thread从零开始(二)——RTT系统中点亮LED

发布者:WhisperingWave最新更新时间:2022-12-07 来源: zhihu关键字:STM32F103  RT-Thread  RTT系统  点亮LED 手机看文章 扫描二维码
随时随地手机看文章

上次的的推文简单说了下如何使用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


关键字:STM32F103  RT-Thread  RTT系统  点亮LED 引用地址:STM32F103+RT-Thread从零开始(二)——RTT系统中点亮LED

上一篇:STM32 之 HAL库、标准外设库、LL库
下一篇:STM32F103+RT-Thread从零开始(一)——点亮LED

推荐阅读最新更新时间:2024-11-04 08:55

STM32F103串口通讯注意事项
(1)通过串口不断发送数据 库函数中的 void USART_SendData(USART_TypeDef* USARTx, uint16_t Data); 函数不具备缓存作用,当我们需要连续不断发送一段数据时,我们需要等待前一次数据发送完成,放置数据丢失错误 void usart2_send_char(u8 _char) { /* 等待上一次发送完成*/ while( !(USART2- SR & (1 USART2_SR_TC) )) ; USART2- DR = _char ; } (2)通过中断接受数据 通过中断中断接受数据时我们可以通过读取数据寄存器中的值使硬件自动清除挂起的接收中断标志
[单片机]
STM32F103 Flash操作代码解析
/************************************************************************/ // !!!一定要记住!!!Flash寿命是有限的,别程序开着一直擦!!用到再擦 // !!!爱护Flash人人有责 // 必须先解锁Flash uint64_t data = 0; // 一页 1KB // stm32f103 Flash 建议使用页地址为:0x0807F000 // 寻址范围:0x0807F000 - 0x0807FFFF uint32_t *Address = (uint32_t*)0x08
[单片机]
基于STM32F103内部AD测量电池电压
STM32的ADC介绍: STM32 拥有 1~3 个 ADC ( STM32F101/102系列只有1个ADC),这些ADC可以独立使用,也可以使用双重模式(提高采样率)。STM32 的 ADC 是 12 位逐次逼近型的模拟数字转换器。它有18个通道,可测量16个外部和2个内部信号源。各通道的A/D转换可以单次、连续、扫描或间断模式执行。ADC的结果可以左对齐或右对齐方式存储在16位数据寄存器中。模拟看门狗特性允许应用程序检测输入电压是否超出用户定义的高/低阀值 STM32 的 ADC 最大的转换速率为 1Mhz,也就是转换时间为 1us(在 ADCCLK=14M,采样周期 1.5 个 ADC 时钟下得到),不要让
[单片机]
基于<font color='red'>STM32F103</font>内部AD测量电池电压
STM32学习笔记(1) GPIO初始化及点亮LED
一、led.h头文件分析 #ifndef __LED_H #define __LED_H #include sys.h #define LED0 PAout(8) // PA8 #define LED1 PDout(2) // PD2 void LED_Init(void);//初始化 #endif 问:ifndef,endif有什么用? 在这一段代码中,假设同时有A.h和B.h同时包含了这个头文件,在编译器编译A时,会先判断LED.H有没有被定义(这里肯定是未被定义的),那么就定义(define __LED_H),然后再(endif),在编译器编译B时,同样会判断LED.H有没有被定义(这里经过A的编译,肯定是被定义的),那么
[单片机]
STM32学习笔记(1) GPIO初始化及<font color='red'>点亮</font><font color='red'>LED</font>
基于STM32F103的贴片机控制系统的设计与实现
贴片机又称“表面贴装系统”(Surface Mount System),是一种通过移动、吸取、安放动作把表贴元件精准放置在指定位置的一种自动化设备。在实际生产线中,先由点胶机对PCB板进行点胶操作,然后由贴片机进行贴装操作,最后由回流焊机焊接,完成整个PCB板的焊接任务,是SMT流水线中不可或缺的一环。目前发达国家垄断了贴片机的主要领域,我国的贴片机产业完全靠进口。而且在实际生产中,国际上的自动贴片机虽然效率与精度最高,但大都造价昂贵,功能单一,适用于大型企业。而手动贴片机造价低廉,但效率极低,精度取决于操作者得水平,且无法解放双手。 本文以STM32F103RBT6为主控芯片,设计了一种适应于个体经营者、学校实验以及科研制板等
[单片机]
基于<font color='red'>STM32F103</font>的贴片机控制<font color='red'>系统</font>的设计与实现
stm32f103 adc采集 程序配置
这个例程是采集电压adc值 对于通道几是如何选择的: 这里在adc初始化函数中,设置通道几的采样率只是设置要使用的通道几,并不是选择用通道几,真正的选择用通道几是在Get_Adc()AD采样这个函数中,ADC1- SQR3|=ch;这里的配置,因为SQR3的0~4位 是设置在规则序列转换中要转换的通道,又因为只有一个通道需要转换,所以就用这里来设置。 对于选择IO口几对应的通道几具体要看原理图或者芯片手册: 例如 void Adc_Init(void) { //先初始化IO口 RCC- APB2ENR|=1 2; //使能PORTA口时钟 GPIOA- CRL&=0XFFF0FFFF;//PA4 anolo
[单片机]
<font color='red'>stm32f103</font> adc采集 程序配置
RT-Thread与多家主流芯片厂商签署战略合作协议
国内自主物联网操作系统平台RT-Thread暨上海睿赛德电子科技有限公司宣布:公司已与国内多家主流芯片厂商签署战略合作协议,RT-Thread将作为他们的原生操作系统之一,搭配其芯片推向市场。首批签约公司包括中天微系统、富瀚微电子(300613)、灵动微电子、北京君正(300223)、全志科技(300458)、爱普特微电子、华芯微特科技等企业。此举标志着RT-Thread获得了业界的广泛认同,同时其生态体系建设成效显著。 随着目前国内集成电路产业的快速发展,国内芯片设计企业不断推出MCU、无线SoC等全新的芯片产品。其中许多芯片产品针对相应的物联网应用,需要特别优化或者差异化的操作系统来对其应用软件提供支持,以最好地
[嵌入式]
<font color='red'>RT-Thread</font>与多家主流芯片厂商签署战略合作协议
STM32F103 CAN中断发送功能的再次讨论
我在之前的一篇博客日志中,写过关于CAN发送功能如何使用,但是当时由于时间匆忙,赶项目,按照对USART中断发送的理解,在数据成功发送出去的情况下,写了那篇误人子弟的日志,在这里向大家道歉,实在不好意思,现在我重新阐述下CAN中断发送原理。 1、USART发送中断与CAN发送中断的区别 USART发送中断,是因为发送缓冲区为空,CAN发送中断的中断源是成功(或者abort)发送一次,正是这种区别误导了我。 2、我之前的CAN中断发送的处理方法是,将数据填充到发送缓冲区,由CAN中断提取进行发送,为了启动CAN的发送,我写了一句话CAN- sTxMailBox .TIR |= 1;就是启动发送,我以为在这以后CA
[单片机]
小广播
设计资源 培训 开发板 精华推荐

最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

换一换 更多 相关热搜器件
随便看看

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved