stm8s_atomthread

发布者:huanli最新更新时间:2020-01-16 来源: eefocus关键字:stm8s  atomthread 手机看文章 扫描二维码
随时随地手机看文章

STM8S Atomthread 实时操作系统移植

介绍


1.嵌入式操作系统基本知识

嵌入性、专用性与计算机系统是嵌入式系统的基本元素;跟通用计算机系统(如windows、linux等)相比,嵌入式系统具备专用性强、可剪裁性好、实时性好和功耗低的特点。

实时操作系统满足条件: 

必须是多任务(任务调度或调度器,最核心功能)

任务的切换时间与系统当前任务数无关(调度器对任务切换时间)

中断延时的时间可预知并尽可能短(任务实时性要求,即为CPU对任务响应速度)

目前,实时系统主要类型: 

抢占式(剥夺式)

非抢占式(非剥夺式)


2.实时操作系统介绍:Atomthread

Atomthread完全开源、轻量、便捷,针对于嵌入式操作系统的实时调度。

具备特性:抢占式、无限的线程(在RAM允许的条件下)、255个优先级、相同优先级的时间片轮、任务同步与互斥、队列、定时器、具备元素的阻塞与非阻塞、线程的堆栈分析等系统基本元素。

对于初步学习操作系统的非计算机类学生来说是简便的,尤其是电子类学生或从事嵌入式设计的人是有效的系统学习对象。官方原生支持STM8系列CPU,配套电协第5代开发板,相应编写本教程,有助于大家进一步理解单片机系统。

系统文件组成:

文件名 文件作用 备注

atom.h 系统API 任务控制块、系统错误宏、系统API等

atomkernel.c 系统内核源码 内核功能:上下文切换、中断、TCB控制、信号量等

atommutex.c 互斥源码 **

atomport-template.h 系统宏、类型声明 时间片轮设定

atomqueue.c 队列源码 **

atomsem.c 信号量源码 **

atomtimer.c 定时器源码 系统滴答时钟、应用性定时器


3.注意:

Atomthread官方网站:http://atomthreads.com/

以下编写当中,涉及到的变量、宏及函数等,通过()提供文件位置,请注意。

Atomthread(STM8版本)使用指南


1.STM8S版本特点

移植唯一需要修改的汇编文件。

编译器的特点: IAR编译器的虚拟寄存器、变量空间选择关键字等特点。

Atom官方原生支持使用ST公司的官方库文件,应用代码都将使用库函数而不再是直接操作寄存器。学习需要注意数据结构体的设计和指针的灵活使用。


2.使用Atom基本流程:

系统初始化:设置空闲堆栈大小

启动系统时钟 

根据实际调节系统时间片轮

根据实际调节滴答定时器

创建线程 

设置任务堆栈大小

创建任务区(任务函数)

启动系统


3.电协

电协为广大的学习者,将ST库版本从1.1修改为V2.1(目前最新版本),库函数更完整。同时独自移植了一套直接操作寄存器的Atom版本,提供多种选择。本文,基于库函数版本编写。

实验平台: 

IAR V6.3

电协第五代开发板-B版

软件工具:source insight 3.5

电协修改后的源代码文件结构: 

kernel:Atom系统内核

ports:特定平台(根据不同的芯片选择) 

lib:库函数 

inc:库函数头文件

src:库函数源文件

ports-stm8s:主程序和系统的端口代码,涵盖编译器的选择

project:工程文件

tests:Atom官方测试代码

user:应用程序代码

学习建议:凡是讲述代码部分,建议正确打开工程,一步步跟着讲述,查看每一个提及到的变量、函数等,这样对系统加深理解。我们是学习实时系统的原理,而不单是使用它,而且要有信心优化系统。


原理:Atomthread系统的整体架构解析

1.程序控制块(atom.h)

操作系统是一种管理软件,负责管理对象的信息之余还按照某种规则对这些对象进行分配、调度,而实现这些操作的前提是需要有一份关于对象的详细信息,我们称之为程序控制块。从代码上看,程序控制块就是一个结构体。在Atom系统里面,程序控制块的组成简化如下图。 

这里写图片描述 

Atom系统里把上图所示结构体称之为ATOM_TCB(atom.h 4行),系统依赖于此结构来执行系统任务程序。 


*注意:任务堆栈使用记录,是由ATOM_STACK_CHECKING宏决定是否存在TCB当中,如果不需要使用堆栈检测功能,可以根据宏来设置。


2.任务(kernel.h)

生活中,我们习惯将大而复杂的事情“分而治之”,程序也会遇到类似的问题;也有时候,我们需要等待用户按下按键,同时也需要不断读取来自网络的数据。可见,多任务的好处就是用来解决并发性问题。一个问题,我们称之为任务,程序需要通过算法去将这个任务执行并解决。而多线程便解决多任务问题,所以,操作系统正是通过系统规则对任务进行分配资源、调度处理。Atom系统的任务及其内存结构如下图: 

这里写图片描述 

由此可得,如果多个任务,就存在一张链表保存着所有任务的信息,通过图上标号F链接前一个任务控制块指针,标号N链接后一个任务控制块指针。系统则可以通过任务控制块链表来找到任意一个存在的任务,从而执行多个任务的程序。 


Atom系统将此链表称为tcbReadyQ(kernel.h 172行) 

至此,系统运行的“地基”已经造好。


3.(系统任务)系统程序(kernel.h)

系统初始化函数:atomOSInit()(kernel.h 657行)


任务链表 tcbReadyQ

系统的当前任务指针 curr_tcb

系统开关变量 atomOSStarted

创建空闲任务 idle_tcb(堆栈设置、优先级)

编写的代码中,除了有用户任务(处理应用程序逻辑)还需要有系统任务(处理系统逻辑),我们需要给予系统足够的堆栈空间运行,才能处理系统任务。Atom系统初始化系统自身的同时,也为自己创建了一个名为“空闲任务”的线程。 截取该函数主要代码解析:


    /* 初始化系统全局变量 */

    curr_tcb = NULL;//系统运行期间,该指针一直指向正在执行的任务控制块

    tcbReadyQ = NULL;//系统运行期间,该指针指向了保存所有任务控制块信息的链表

    atomOSStarted = FALSE;//作为系统的总开关变量,FALSE为停止系统调度任务,

                          //TRUE为开启系统调度任务


    /* 创建空闲线程(idle thread) */

    status = atomThreadCreate(&idle_tcb,//系统任务:空闲线程的程序控制块

                 IDLE_THREAD_PRIORITY,//空闲线程优先级,最低优先级255

                 atomIdleThread,//任务函数,空闲程序入口,不作用户任务。

                 0,

                 idle_thread_stack_top,//堆栈顶

                 idle_thread_stack_size);//堆栈大小


读者亲自查看代码,容易发现初始化系统函数仅接受两个参数(stack_top和stack_size),这意味着我们初始化系统只需要设置这连个参数,而函数内其他都是固定设置的,包括创建的idle_tcb。可见,系统启动必须存在“空闲任务”线程,它在系统没有其他用户任务需要处理的时候执行。当然,它只是一个死循环。


4.时间片轮(atomport.h)

时间片轮初始化函数:archInitSystemTickTimer()(atomport.c 237行),该函数启动STM8内部时钟(任意一个),并设置合适的定时间隔作为系统的心跳时间。系统的心跳快慢决定着系统切换任务的速率,所以需要根据实际来调节定时间隔。编写该函数等同于以往初始化某一定时器,详情翻查定时器章节。


每秒系统心跳次数:SYSTEM_TICKS_PER_SEC(atomport.c 237行),一个宏保存了系统心跳速率,方便使用其余系统函数。假设,定时间隔为10ms,那么该宏的值为100,合计1S。

时间片轮中断服务函数:System tick ISR函数名字根据实际决定,电协Atom版本于stm8s_it.c 225行,编写TIM1_UPD中断函数。

  /* 中断入口函数,中断嵌套层数全局变量atomIntCnt加一 */

  atomIntEnter();


  /* 系统时钟处理函数,心跳次数加一,并启动定时器回调 */

  atomTimerTick();


  /* 清空标志位 */

  TIM1->SR1 = (uint8_t)(~(uint8_t)TIM1_IT_UPDATE);


  /* 中断退出函数, atomIntCnt减一,调用系统调度程序*/

  atomIntExit(TRUE);


该程序,完成了系统自动调度任务功能,至此实时系统最基本的调度功能完成。


5.(用户任务)线程创建(kernel.h)

线程创建函数:atomThreadCreate() 

- 创建一个新线程并根据函数输入参数设置初始化TCB参数(挂起状态、优先级、队列指针、任务入口、入口参数) 

- 调用archThreadContextInit(),入栈操作。将thread_shell()返回信息记录在线程的TCB主中,使得下次调度后执行正确的线程开始位置。 


- 该函数内调用thread_shell()函数,能够找到线程处理函数入口地址。 

- 将该线程放到任务队列tcbReadyQ 

- 调用系统调度函数根据优先级决定线程运行,如果系统没有启动,则不进行调度。


线程堆栈问题: 

1. 线程被创建,随后由系统调度挂起或执行,都需要足够的堆栈区,用以保存线程的资源。任务的大小决定着线程堆栈大小,实际调试根据任务的复杂度来设置堆栈大小。 

2. Atom为线程提供堆栈检测接口,除了直观地设置堆栈大小,在声明了ATOM_STACK_CHECKING的情况下,还可以调用atomThreadStackCheck()函数检测线程堆栈使用情况。


6.任务调度(kernel.h)

系统任务调度函数:atomSched(),下面提供函数伪代码解析:

if(系统关闭)

    return ;

进入临界区,关闭总中断;

if(当前任务被挂起)

    从任务队列tcbReadyQ取出第一个线程调用atomThreadSwitch()切换任务;

else

    从tcbReadyQ取出任务优先级大于或者等于当前任务优先级等待执行的线程,进行任务切换,将当前任务添加到tcbReadyQ中;

退出临界区,开启总中断;


临界区:像象棋中的楚河汉界一样,我们跨越边界,也就是切换任务的时候,为防止出错,我们直接把总中断关断,确保系统在切换任务的过程不会被中断,堆栈数据自然得到了保护,那么就保证了任务切换的正确进行。随后,切换任务完成,我们再次开总中断。涉及的两个函数分别为:进入临界区CRITICAL_START() 和退出临界区CRITICAL_END(),定义在atomport.h 67行。


任务调度:调度器把调度工作分为两部分: 

第一步获取待运行的TCB指针:主要是任务队列操作(atomkernel.c) 

出队tcbDequeueHead()

入队tcbEnqueuePriority()


第二步进行断点数据的切换: 

atomThreadSwitch()(atomkernel.c 333行),该函数设置当前任务TCB指针curr_tcb,然后调用(汇编程序)archContextSwitch()根据两任务TCB切换指针SP和IAR虚拟寄存器。这样就可以完成断点数据切换了,断点数据主要是被切换任务的当前运行的CPU数据。


调度器种类:通过void atomSched(uint8_t timer_tick)参数timer_tick控制 

任务级:被atomThreadCreate()函数调用时,timer_tick为FALSE。

中断级:被atomIntExit()函数调用时,timer_tick为TRUE。除了滴答定时器,也就是系统心跳中断服务程序中,timer_tick为TRUE,其余都是任务级调度器。


7.启动系统

启动系统函数:atomOSStart()(atomkernel.c 694行),该函数主要处理: 

1. atomOSStarted全局系统标识开关,设置为TURE,允许启动系统 

2. 从任务队列取出“空闲任务”作为当前TCB指针curr_tcb,然后调用(汇编程序)archFirstThreadRestore()使它作为系统的第一个线程开始执行。


实验

示例一:双线程(多任务LED)

1.初步分析

一般,我们单片机的控制对象都是一个装置或设备(以下统称对象),任务的功能是相对固定的,典型的任务是一个无限循环结构,这样便可以不断处理事件。


void function ( void )

{

    while(1)

    {

        user_code;//用户代码

    }

}


那么,一般我们直接操作寄存器实现CPU片上设备的特定功能,然后响应被激活的中断,来实现对多个对象的控制。但这是单线程的处理,也是单片机常用的控制模式。随着功能愈复杂(对象量愈多),那么我们单线程的控制代码就会显得臃肿,难以维护,不利于产品迭代进化。 


假设,现在产生两个或以上的对象的控制矛盾,我们单线程处理就有点显得力不从心了。所以,使用多线程处理,能够更大限度地使用CPU资源,并行地运行多个任务(同时操作多个对象)。典型的多线程如下:


void thread_first ( void )

{

    while(1)

    {

        first_user_code;//第一个用户代码

    }

}


void thread_second ( void )

{

    while(1)

    {

        second_user_code;//第二个用户代码

    }

}


这时候有同学可能会问:while(1)不是一个死循环吗?对的,确实是。但Atomthread系统通过任务调度,实现了两个while(1)之间的切换,也就是我们所说的两个线程之间的调度了。恰好,我们通过堆栈保存断点数据实现任务切换,若不明白回头看6.任务调度章节。所以,我们在具有操作系统的情况下,只需要简单而便捷地创建一个线程,就可以实现对新增的对象进行控制,而不是设定一个又一个的标志位或状态机去轮询控制。


2.多任务——再玩闪烁LED

让我们通过Atom系统,实现入门单片机实验的LED闪烁功能。 

现在,先请读者回头翻看,使用指南 2.使用Atom基本流程;


步骤1:

除了包含必要的头文件外。首先,我们得对我们创建的线程声明必要的变量,为了方便修改。


/* 空闲(起始)任务:堆栈大小 */

#define IDLE_STACK_SIZE_BYTES       104

NEAR static uint8_t idle_thread_stack[IDLE_STACK_SIZE_BYTES];


/* 任务一:优先级、堆栈大小、堆栈区、任务块、任务函数体 */

#define FIRST_THREAD_PRIO 1

#define FIRST_STACK_SIZE_BYTES      208

NEAR static uint8_t first_thread_stack[FIRST_STACK_SIZE_BYTES];

static ATOM_TCB first_tcb;

static void first_thread_func (uint32_t param);


/* 任务二:优先级、堆栈大小、堆栈区、任务块、任务函数体 */

#define SECOND_THREAD_PRIO 2

#define SECOND_STACK_SIZE_BYTES      156

NEAR static uint8_t second_thread_stack[SECOND_STACK_SIZE_BYTES];

static ATOM_TCB second_tcb;

static void second_thread_func (uint32_t param);


解析点: 

1. 空闲任务的堆栈不易过小,用户任务的堆栈根据实际分配。 

2. NEAR为IAR编译器对STM8的变量内存分配空间管理的关键字,实际为IAR提供的编译模式,这里是微模式,简单理解为内存中放置程序代码及数据的方式。


步骤2:

接下来就是用户任务(应用线程)的编写,这里放置实际功能的代码。我们分别让开发板的LED0(PB0)和LED1(PB1)先后以一定时间间隔闪烁。


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

 函 数 名  : first_thread_func

 功能描述  : 任务一:led0闪烁

 输入参数  : param  

 输出参数  : 无

 返 回 值  : 

 调用函数  : 

 被调函数  : main

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

static void first_thread_func (uint32_t param)

{

    GPIO_Init(GPIOB, GPIO_PIN_0, GPIO_MODE_OUT_PP_LOW_FAST);


    while (1)

    {

        GPIO_WriteReverse(GPIOB, GPIO_PIN_0);

        atomTimerDelay(50);

    }

}

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

 函 数 名  : second_thread_func

 功能描述  : 任务二:led1闪烁

 输入参数  : param  

 输出参数  : 无

 返 回 值  : 

 调用函数  : 

 被调函数  : main

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

static void second_thread_func (uint32_t param)

{

    GPIO_Init(GPIOB, GPIO_PIN_1, GPIO_MODE_OUT_PP_LOW_FAST);


    while (1)

    {

        atomTimerDelay(50);

        GPIO_WriteReverse(GPIOB, GPIO_PIN_1);

    }

}


解析点: 

1. 这里调用了Atom系统的定时器延时函数atomTimerDelay()(atomtimer.c 328行),延时时间时基依赖于系统当前的心跳频率。


步骤3:

最后,编写主函数。


NO_REG_SAVE 

void main ( void )

{

    int8_t status;


    /* 系统初始化,赋予系统空闲运行堆栈 */

    status = atomOSInit(&idle_thread_stack[IDLE_STACK_SIZE_BYTES - 1], IDLE_STACK_SIZE_BYTES);


    if (status == ATOM_OK)

    {

        archInitSystemTickTimer();/* 启动系统滴答时钟 */


        atomThreadCreate(&first_tcb,

                         FIRST_THREAD_PRIO, first_thread_func, 0,

                         &first_thread_stack[FIRST_STACK_SIZE_BYTES - 1],

                         FIRST_STACK_SIZE_BYTES);


        atomThreadCreate(&second_tcb,

                         SECOND_THREAD_PRIO, second_thread_func, 0,

                         &second_thread_stack[SECOND_STACK_SIZE_BYTES - 1],

[1] [2]
关键字:stm8s  atomthread 引用地址:stm8s_atomthread

上一篇:stm8使用atomthreads项目
下一篇:stm8L 触摸库使用教程

推荐阅读最新更新时间:2024-11-08 15:33

STM8S 与 STM32F IO口输出速率测试
今天查看STM32资料时,对输出速度2M 10M 50M不是很了解,再加上移植ARF2496K程序到STM32时出现意外情况。 一、STM8S端作为接收端和发送端时接收到的数据都正常。 二、发送端(STM8S),接收端(STM32)这时也正常。 三、发送端(STM32),接收端(STM8)时,接收到的就是错误数据,数据每次都相同,但是是错的。 于是,便考虑是不是STM32 I/O输出速度太快导致的,今天便拿示波器测试,果然,豁然开朗。 网上很多全他妈瞎说,STM32的I/O输出配置成2MHz 10MHz 50MHz根本就不是所谓的输出速度,仅仅是翻转速度。 测试示波器为 RIGOL DS1062CA,探头为10X,通道二进
[单片机]
<font color='red'>STM8S</font> 与 STM32F IO口输出速率测试
STM8S单片机入门(前言)
STM8S单片机是一款广泛使用的8位低功耗单片机,具备系统成本低、功能强大等特点。功能强大也带来了学习入门相对较难的问题。本入门教程面向无任何单片机基础的人,从开发环境的搭建开始,通过详细讲解一个典型单片机应用系统最基本设计和实现过程,帮助大家快速入门。 单片机应用系统的形态很多,但基本模式类似。以智能硬件的应用为例,各种智能硬件区别大多是输入电路(各种传感器及外部信号输入)和输出电路(控制电路、动作电路)的不同,基础部分单片机、电池和充电管理、无线数据模块都是类似的。 所以本入门教程选取包含开关机电路、锂电池充电及电源管理、蓝牙无线接口的一个单片机应用实例,通过讲解这些功能的实现过程,把 STM8S单片机的G
[单片机]
<font color='red'>STM8S</font>单片机入门(前言)
STM8S 外部中断一直进解决方法
用到官方的函数库操作,也看了例程,设计方法基本一样: GPIO_Init(GPIOA, (GPIO_Pin_TypeDef)(GPIO_PIN_4|GPIO_PIN_5|GPIO_PIN_6), GPIO_MODE_IN_PU_IT); //设置外部IO中断模式 EXTI_SetExtIntSensitivity(EXTI_PORT_GPIOA, EXTI_SENSITIVITY_FALL_ONLY); //设置中断触发方式 然后外部中断就可以进了,但是发现中断服务函数没有中断源的情况下一直进。 解决方法: 在初始化外部中断的时候,总中断一定要关。 初始化完外部中断,在开总中断,上述情况得到解决,只有触发源来的时候
[单片机]
stm8s 时钟库函数选择内部RC初始化
//本文选择16M内部RC震荡,分频为1 即系统时钟为16M void CLK_HSICmd(FunctionalState NewState) { /* Check the parameters */ assert_param(IS_FUNCTIONALSTATE_OK(NewState)); if (NewState != DISABLE) { /* Set HSIEN bit */ CLK- ICKR |= CLK_ICKR_HSIEN; } else { /* Reset HSIEN bit */
[单片机]
关于STM8S IAR 无法下载程序,以及无法进去中断的问题
之前内有用过STM8 一会再用STM32 做一个小项目时 觉得STM8也有库应该很方便就直接选了它,结果调试的时候各种心酸,本想着一天解决的任务,结果拖了三天。接下来介绍一下遇到的主要问题 以及解决方法。 1、IAR无法下载程序问题 首先关于芯片型号设置就不说了 网上一大堆,主要是Vcap 引脚对地电容的取值,网上普遍说1uF, 由于我做了两个板子 一个PCB一个覆铜板 PCB没问题 ,但是覆铜板1uF 就下不进去程序,后来改成0.1uF才可以。如果软件配置对了,现在不进去很有可能是这个电容的问题。 2、定时器进不去中断问题 尝试了TIM2 TIM4 都进不去,我是用的是风驰 分享的STM8s207寄存器版本的例程,其他都
[单片机]
STM8S自学笔记-006 GPIO输入:按键输入 与 按键滤波
GPIO输入 在 《STM8S自学笔记-003 GPIO输出:点亮LED灯 and 跑马灯特效》中,我们曾经把LED的GPIO设置为推挽输出模式,而它只是GPIO输出功能中的一种。同样,GPIO的输入功能也不止有一种。 浮空输入,无中断 上拉输入,无中断 浮空输入,有中断 上拉输入,有中断 今天不讨论带有中断的的输入功能。 我的STM8S开发板上有3个按键,位置分别与3个LED对应,这些按键都有外部的上拉电阻。所以在初始化按键GPIO时,我们可以设置为浮空输入,也可以设置为上拉输入,两种设置方式对按键检测来说没有差别;如果没有外部上拉电阻,那就建议设置为内部上拉,以保证GPIO输入端口电压的稳定。 按键检测 目标功能 今天
[单片机]
stm8s内部时钟配置详解及配置步骤
  STM8S的4种时钟源可用做主时钟:   ● 1-24MHz高速外部晶体振荡器(HSE)   ● 最大24MHz高速外部时钟信号(HSE user-ext)   ● 16MHz高速内部RC振荡器(HSI)   ● 128KHz低速内部RC(LSI)   各个时钟源可单独打开或关闭,从而优化功耗。   系统的启动   为使系统快速启动,复位后时钟控制器自动使用HSI的8分频(HSI/8)做为主时钟。其原因为HSI的稳定时间短,而8分频可保证系统在较差的VDD条件下安全启动。   另外,stm8s还可以切换时钟源,有自动和手动两种方法。   时钟是单片机的灵魂,所有的东西都和时钟有关。相比AVR反人类的熔丝位设置时
[单片机]
<font color='red'>stm8s</font>内部时钟配置详解及配置步骤
STM8S-Discovery第三个程序 - DS18B20
/******** STM8S-Discovery DS18B20 Test ******** * 版本.........: 1.0 * 目标.........: STM8S105C6 * 文件名.......: main.c * 编译器.......: IAR for STM8 V1.1 **********************************************/ #include iostm8s105c6.h #include stdio.h #define UART_BAUD 9600 //波特率 #define F_MASTER 16000000 //主频率 #define D
[单片机]
小广播
设计资源 培训 开发板 精华推荐

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

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

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

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

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