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

创建工程

创建一个Keil工程,芯片依旧选择STM32F103C8T6,然后在Manage Run-Time Environment对话框中选择需要用的的软件组件,与上文不同的是,我们需要把RTT一起勾上,这里的RTOS入口,可以通过RTT的官网搜索pack进行安装。如下图:

上图中,红线框中即为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){//点亮LEDGPIO_ResetBits(GPIOB,GPIO_Pin_12);//延时0.5srt_thread_delay(RT_TICK_PER_SECOND/2);//关闭LEDGPIO_SetBits(GPIOB,GPIO_Pin_12);//延时0.5srt_thread_delay(RT_TICK_PER_SECOND/2);}
}

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

#include <rthw.h>
#include <rtthread.h>
#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 <your_header_file.h>#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_INITrt_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_HEAPtid = rt_thread_create("main", main_thread_entry, RT_NULL,RT_MAIN_THREAD_STACK_SIZE, RT_THREAD_PRIORITY_MAX / 3, 20);RT_ASSERT(tid != RT_NULL);
#elsert_err_t result;tid = &main_thread;result = rt_thread_init(tid, "main", main_thread_entry, RT_NULL,main_stack, sizeof(main_stack), RT_THREAD_PRIORITY_MAX / 3, 20);RT_ASSERT(result == RT_EOK);
#endifrt_thread_startup(tid);
}

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

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

STM32F103+RTT从零开始(二)——RTT系统中点亮LED相关推荐

  1. STM32F103+RTT从零开始(一)——点亮LED

    最近心血来潮,打算重新捡起老本行,结果发现很多都忘记了.干脆重新开始学习,并做个从零开始的笔记了. STM32系列MCU STM32系列芯片包括F0/F1/F2/F3/F4/F7/L0/L1/L4/H ...

  2. STM32f103+protues仿真(一) 点亮led

    STM32f103+protues仿真(一) 点亮led 文章目录 STM32f103+protues仿真(一) 点亮led 前言 一.程序编写 1.led.c 2.led.h 3.main.c 4. ...

  3. 一步步点亮LED3_从零开始手写汇编点亮LED

    1.GPxCON.GPxDAT寄存器分析 GPJ0端口一共有8个引脚,分别记作:GPJ0_0 ~ GPJ0_7,相关重要寄存器就是GPJ0CON和GPJ0DAT GPJ0CON寄存器中设置8个引脚的工 ...

  4. Linux系统点亮LED

    目录 应用层操控硬件的两种方式 sysfs 文件系统 sysfs 与/sys 总结 标准接口与非标准接口 LED 硬件控制方式 编写LED 应用程序 在开发板上测试 对于一款学习型开发板来说,永远都绕 ...

  5. 最详细蓝桥杯嵌入式教程(二):点亮LED(不用Delay)

    在上一篇文章里面,我们在点亮LED时,使用了Delay.但是其实Delay有非常明显的缺点:阻塞程序,使其他程序实时性下降!所以在我们比赛的是尽量不要使用Delay.接下来我将介绍另一种方法来点亮LE ...

  6. 【篇二】控制寄存器点亮LED

    本章总结控制LED的开发流程    寄存器 -> GPIO -> 引脚 -> 编程  -> LED灯亮 第一步:了解LED物理特性     1:一正一负俩个接线点,功能是亮和不 ...

  7. STM32从零开始(三) 点亮led灯并且配置时钟为72mhz

    ** 这部分感觉有点乱可以先看后边的总结部分,代码的上边那里 ** stm32的时钟就是这个图.时钟她是一级一级整上去的.通过pll锁相环,把外部晶振的频率给他顶上去.再需要多少就分频多少,得到需要的 ...

  8. 51单片机(二)成功点亮LED

    ❤️ 专栏简介:本专栏记录了从零学习单片机的过程,其中包括51单片机和STM32单片机两部分:建议先学习51单片机,其是STM32等高级单片机的基础:这样再学习STM32时才能融会贯通. ☀️ 专栏适 ...

  9. 从零开始的51单片机——(1)点亮LED

    从零开始的51单片机--点亮LED 原理 对于开发板上的8个LED分别对应一个八位二进制数. 当对应位为1的时候LED灯灭,为0的时候灯亮 例如1111 1110则是第一个LED灯亮 在C语言中,我们 ...

  10. STM32F103(库函数)——点亮LED并且使用软件延时实现led闪烁

    当我们学过51单片机之后就知道在51单片机中点亮一个LED很简单一句代码就可以实现.只需要将LED所连接的IO口拉低就可以点亮LED. 但是在STM32中,点亮LED确实比51要麻烦很多. 一样,在S ...

最新文章

  1. “云办公”:重新定义工作手机
  2. 再次发布SQL Prompt 3.8的新的patch,解决了不能格式化T-SQL的问题
  3. DEV express 对Gridview某行的元素赋值
  4. 1.4 通过时间的反向传播-深度学习第五课《序列模型》-Stanford吴恩达教授
  5. linux 下查看应用版本信息,Linux下查看版本信息
  6. Java将日期拆分成按周或月时间段统计
  7. C#重写WebBrowser组件,禁止跳转到IE新窗口、脚本错误
  8. 【ASP.NET 问题】IIS发布网站后出现 “处理程序“PageHandlerFactory-Integrated”在其模块列表中有一个错误“的解决办法
  9. 一个html代码太多,如何为一个部分呈现多次的一个html代码
  10. 浅谈JSON数据解析方法
  11. 深入浅出数据库索引原理
  12. oracle rac实现,炼数成金Oracle 12C RAC集群原理与管理实战 16课
  13. Linux网络——部署yum仓库
  14. Python绘制节点是饼状图的社交网络图(Plot network with pie chart)
  15. 主链增幅最高飚至152%,主流币却惊现回落;以太坊发币速度持续放缓
  16. Mac环境Terminal安装Python3
  17. 连续时间傅里叶变换的共轭与共轭对称性(详细推导)
  18. php网站模块修改,织梦wap手机模块网站修改方法
  19. linux怎么自动装驱动程序,linux中如何安装设备驱动程序
  20. goahead 2.5 碰到的问题

热门文章

  1. Android Startup实现分析
  2. 电脑断网的解决办法(方法不会对电脑或网络造成负面影响)
  3. POJ 3376 Finding Palindromes
  4. logback springProperty 标签不起作用
  5. java创建文件的路径_JAVA如何指定路径创建文件?
  6. G.703接口和E1接口区别
  7. SSRF深度解析Gopher协议
  8. 引流工具GoReplay简介和在猪齿鱼效能平台中的应用
  9. 【LOJ6038】【雅礼集训 2017 Day5】远行(LCT,树的直径)
  10. [转载]探索J2ME:用GCF通信