最近在写哪个Tiny,我发现几乎和FreeRTOS一样的实现。
这里是IEEE,电气电子工程师学会,这里会指定许多的标准,我们的头文件就是这里定义的。
https://pubs.opengroup.org/onlinepubs/9699919799/stdint.h
标准的退出
1.在C++中,内存分成5个区,他们分别是堆、栈、自由存储区、全局/静态存储区和常量存储区。 2.栈,就是那些由编译器在需要的时候分配,在不需要的时候自动清楚的变量的存储区。里面的变量通常是局部变量、函数参数等。 3.堆,就是那些由new分配的内存块,他们的释放编译器不去管,由我们的应用程序去控制,一般一个new就要对应一个delete。如果程序员没有释放掉,那么在程序结束后,操作系统会自动回收。 4.自由存储区,就是那些由malloc等分配的内存块,他和堆是十分相似的,不过它是用free来结束自己的生命的。 5.全局/静态存储区,全局变量和静态变量被分配到同一块内存中,在以前的C语言中,全局变量又分为初始化的和未初始化的,在C++里面没有这个区分了,他们共同占用同一块内存区。 6.常量存储区,这是一块比较特殊的存储区,他们里面存放的是常量,不允许修改(当然,你要通过非正当手段也可以修改,而且方法很多)
https://en.cppreference.com/w/这里的话就是一个专业的CPP库参考网站
size_t类型,是一个与机器相关的unsigned类型,其大小足以保证存储内存中对象的大小。
这是C51
https://www.keil.com/support/man/docs/c51/c51_lib_size_t.htm以下这个概念会频繁出现.
什么是systick:
Systick 只是一个存在于基于 ARM 的微控制器中的计时器。基本目的是为不同的任务(RTOS)生成准确的中断提供帮助。
除此之外,它还有多种用途。例如,许多开发人员使用它来生成准确的延迟函数。其他好处是可移植性,可以轻松地将 RTOS 任务从一个微控制器转移到另一个微控制器,并且最终不会更改任务的调度时间和时间相关中断,因为新微控制器上可以使用不同的时钟源。这个东西也叫系统定时器。
SysTick 是一个24 位的倒计数定时器,当计到0 时,将从RELOAD 寄存器中自动重装载定时初值。
只要不把它在SysTick 控制及状态寄存器中的使能位清除,就永不停息。这样可以用systick来实现延时定时功能,不用再占用系统定时器。systick也多用做系统的时钟节拍,如freeRTOS等OS,再启动调度器的时候,就会将systick配置成其系统时钟,给系统提供心跳。
控制寄存器
加载寄存器
STM32 微控制器的定时器资源一般比较丰富,比如STM32存在8个定时器,为啥还要再提供一个SYSTICK?
原因就是所有基于ARM Cortex_M3内核的控制器都带有SysTick定时器,这样就方便了程序在不同的器件之间的移植。而使用RTOS的第一项工作往往就是将其移植到开发人员的硬件平台上,由于SYSTICK的存在无疑降低了移植的难度。
https://developer.arm.com/documentation/dai0179/b具体的我们可以看ARM的文档
void SysTick_Init(void)
{
if(SysTick_Config(SystemCoreClock/1000)) //1ms定时器
{
while(1);
}
//SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk; //若无法启动则关闭
}SysTick_Config的参数,其实就是一个时钟次数,叫systick重装定时器的值。意思就是我要多少个1/fosc 时间后中断一下。
根据学过的物理中的时间与频率的公式:
fosc=1/T T=1/fosc ,fosc为系统的频率
如果STM32时钟频率为:72MHz,每次的时间为:T=1/72MHz。
1秒钟为:1/(每次的时间)=1/(1/72MHz)=72 000 000次。
1MHz是:1000 000。
反过来讲。SysTick_Config(72000)代表:
72000*(1/72MHz)=1/1000=1(ms),即定时为1ms。
如果需要1S则,可以通一设置一个全局变量,然后定初值得为1000,这样,每个systick中断一次,这个全局变量减1,减到0,即systick中断1000次,时间为:1ms*1000=1S。从而实现1S的定时。
因为SysTick定时器是:24位的,最大定时时间为:2的24次方*(1/72MHz)的时间,这里系统频率为:72MHz的情况下。
如何使用这个Systick用于程序设计上的延时或是定时作用呢?
如下:
__IO uint32_t TimingDelay;定义一个全局变量,注意类型为 volatile的。
volatile的作用:作为指令关键字,确保本条指令不会因为编译器的优化而省略,且要求每次直接读值.然后定义一个延时或是定时函数:
/*
* 函数名:Delay_ms
* 描述 :ms延时程序,1ms为一个单位
* 输入 : - nTime
* 输出 :无
* 示例 : Delay_ms(1) 实现的延时为:1*ms=1ms
* 调用 :外部调用
*/
void Delay_ms(uint16_t nTime)
{
TimingDelay = nTime;
//使能系统滴答定时器
while(TimingDelay !=0);
}还要在系统的中断函数文件:stm32f10x_it.c/h里面,修改系统自带的systick函数。这个函数要么没有声明或是为空操作。这里加入定时延时里的处理。即中断后,全局变量做个处理即可。
在stm32f10x_it.c里修改如下:
添加外部的声明:
extern __IO uint32_t TimingDelay;修改这个函数:SysTick_Handler,这是系统的关于SysTick_Handler的中断服务程序名,
在启动文件里如:startup_stm32f10x_hd.s 有它的定义的名字。不要弄错了。否则无法中断处理。
/**
* @brief This function handles SysTick Handler.
* @param None
* @retval None
*/
void SysTick_Handler(void)
{
if (TimingDelay != 0x00)
{
TimingDelay--;
}
}以上,即定义配置好了Systick定时器。如何使用呢?很简单。
Delay_ms(500);即为延时500ms。
当然,使用前,请先初始化:
SysTick_Init();否则无法使用并影响后续的程序运行,这个很重要,就像打开了串口中断,你不清标志位,也同样在接收字符后,CPU中断在那里,而不能继续执行!。使用外设功能,需要初始化!
SysTick_Config(SystemFrequency / 10)函数的形参就是systick重装定时器的值。
systick计数频率为每秒72000000次,所以7200000次就是1/10秒,也就是100ms。
SysTick是1个24bit递减计数器,通过对SysTick控制与状态寄存器的设置,可选择HCLK时钟(72M)或HCLK的8分频(9M,缺省是这个)作为SysTick的时钟源。
SysTick的重装寄存器决定了定时器频率。
若SysTick的时钟源是72M, SystemFrequency = 72000000Hz
所以 SysTick_Config(SystemFrequency / 1000);就是1ms时基。
https://blog.csdn.net/tcjy1000/article/details/50554312部分参考此文内容。
一开始的单线编程模型
抢占式RTOS
/*********************************************************************************
* @ 函数名 :vTaskLed1
* @ 功能说明:LED1 任务,实现一个周期性的闪烁
* @ 参数 :pvParameters,当任务创建的时候传进来,可以没有
* @ 返回值 :无
********************************************************************************/
void vTaskLed1(void *pvParameters)
{
/* 任务都是一个无限,不能返回 */
while(1)
{
LED2( ON );
/* 阻塞延时,单位ms */
vTaskDelay( 500 );
LED2( OFF );
vTaskDelay( 500 );
}
}一个单任务,每个任务都是在自己权限范围内的一个小程序。其具有程序入口,通常会运行在一个死循环中,也不会退出。FreeRTOS 任务不允许以任何方式从实现函数中返回——它们绝不能有一条”return”语句,也不能执行到函数末尾。如果一个任务不再需要,可以显式地将其删除。一个任务函数可以用来创建若干个任务——创建出的任务均是独立的执行实例,拥有属于自己的栈空间,以及属于自己的自动变量(栈变量),即任务函数本身定义的变量。应用程序可以包含多个任务。如果运行应用程序的微控制器只有一个核(core),那么在任意给定时间,实际上只会有一个任务被执行。这就意味着一个任务可以有一个或两个状态,即运行状态和非运行状态。我们先考虑这种最简单的模型——但请牢记这其实是过于简单,我们稍后将会看到非运行状态实际上又可划分为若干个子状态当某个任务处于运行态时,处理器就正在执行它的代码。当一个任务处于非运行态时,该任务进行休眠,它的所有状态都被妥善保存,以便在下一次调试器决定让它进入运行态时可以恢复执行。当任务恢复执行时,其将精确地从离开运行态时正准备执行的那一条指令开始执行。任务从非运行态转移到运行态被称为”切换入或切入(switched in)”或”交换入
(swapped in)”。相反,任务从运行态转移到非运行态被称为”切换出或切出(switchedout)”或”交换出(swapped out)”。FreeRTOS 的调度器是能让任务切入切出的唯一实体。
创建的任务原型
usStackDepth :当任务创建时,内核会分为每个任务分配属于任务自己的唯一状态。usStackDepth 值用于告诉内核为它分配多大的栈空间。这个值指定的是栈空间可以保存多少个字(word),而不是多少个字节(byte)。比如说,如果是 32 位宽的栈空间,传入的usStackDepth值为 100,则将会分配 400 字节的栈空间(100 * 4bytes)。栈深度乘以栈宽度的结果千万不能超过一个 size_t 类型变量所能表达的最大值。
应用程序通过定义常量 configMINIMAL_STACK_SIZE 来决定空闲任务任用的栈空间大小。在 FreeRTOS 为微控制器架构提供的Demo 应用程序中,赋予此常量的值是对所有任务的最小建议值。如果你的任务会使用大量栈空间,那么你应当赋予一个更大的值。没有任何简单的方法可以决定一个任务到底需要多大的栈空间。计算出来虽然是可能的,但大多数用户会先简单地赋予一个自认为合理的值,然后利用 FreeRTOS 提供的特性来确证分配的空间既不欠 缺也不浪费。
剩下的参数意思
/*********************************************************************************
* @ 函数名 :AppTaskCreate
* @ 功能说明:任务创建,为了方便管理,所有的任务创建函数都可以放在这个函数里面
* @ 参数 :无
* @ 返回值 :无
********************************************************************************/
static void AppTaskCreate(void)
{
xTaskCreate(vTaskLed1, /* 任务函数名 */
"Task Led1", /* 任务名,字符串形式,方便调试 */
512, /* 栈大小,单位为字,即4个字节 */
NULL, /* 任务形参 */
1, /* 优先级,数值越大,优先级越高 */
&xHandleTaskLED1); /* 任务句柄 */
}任务具体是一个任务的管理器,使用任务名来区分各种任务。
int main(void)
{
NVIC_PriorityGroupConfig( NVIC_PriorityGroup_4 );
/* LED 端口初始化 */
LED_GPIO_Config();
/*创建任务*/
AppTaskCreate();
/*启动任务调度器,任务开始执行*/
vTaskStartScheduler();
while (1)
{
}
}设置好一个任务的各种注册信息,然后就是具体的实现了。
如果有相似功能的函数,我们可以不用使用多个任务函数实体,而在一个任务函数中多次创建任务。
使用的是任务参数:
void vTaskLed1(void *pvParameters)
{
/* 任务都是一个无限,不能返回 */
int *piParameters;
piParameters=(int *)pvParameters;
while(1)
{
if(*piParameters==1)
{
LED1( ON );
/* 阻塞延时,单位ms */
vTaskDelay( 500 );
LED1( OFF );
vTaskDelay( 500 );
}
else if(*piParameters==2)
{
LED2( ON );
/* 阻塞延时,单位ms */
vTaskDelay( 500 );
LED2( OFF );
vTaskDelay( 500 );
}
else if(*piParameters==3)
{
LED3( ON );
/* 阻塞延时,单位ms */
vTaskDelay( 500 );
LED3( OFF );
vTaskDelay( 500 );
}
}
全局区增加标志定义:
static const int task_led1=1;
static const int task_led2=2;
static const int task_led3=3;更改任务创建函数,注意任务参数处不再是NULL;
static void AppTaskCreate(void)
{
xTaskCreate(vTaskLed1, /* 任务函数名 */
"Task Led1", /* 任务名,字符串形式,方便调试 */
512, /* 栈大小,单位为字,即4个字节 */
(void *)&task_led3, // task_led1-task_led3可以切换 /* 任务形参 */
1, /* 优先级,数值越大,优先级越高 */
&xHandleTaskLED1); /* 任务句柄 */
}更改任务参数1-3,可以看到三个不同颜色的灯在闪烁。
任务参数的原型:void * const pvParameters;如果我们不强制转化成
void *将会报错。是不是觉得奇怪,void类型的指针不应该兼容吗?当然,你只用把我定义变量的标志的const去掉,就不用强制转化成void *了,那么是什么原因呢?const int a;对a取地址是const int *类型,是底层const了,不容忽略。这里和keil编译器有关系,在gcc中,虽然有警告,但是可以通过编译并运行。但是这样的类型不兼容情况我们应该避免。
以上为单任务:
static void AppTaskCreate(void)
{
xTaskCreate(vTaskLed1, /* 任务函数名 */
"Task Led1", /* 任务名,字符串形式,方便调试 */
512, /* 栈大小,单位为字,即4个字节 */
(void *)&task_led3, // task_led1-task_led3可以切换 /* 任务形参 */
1, /* 优先级,数值越大,优先级越高 */
&xHandleTaskLED1); /* 任务句柄 */
xTaskCreate(
vTaskBeep,
"Task Beep",
512,
NULL,
2,
&xHandleTaskBeep);
}多任务的创建。
任务优先级 xTaskCreate() API 函数的参数 uxPriority 为创建的任务赋予了一个初始优先级。这个侁先级可以在调度器启动后调用 vTaskPrioritySet() API 函数进行修改。应 用 程 序 在 文 件 FreeRTOSConfig.h 中 设 定 的 编 译 时 配 置 常 量configMAX_PRIORITIES 的值,即是最多可具有的优先级数目。 FreeRTOS 本身并没有限定这个常量的最大值,但这个值越大,则内核花销的内存空间就越多。所以总是建议将此常量设为能够用到的最小值。 对于如何为任务指定优先级, FreeRTOS 并没有强加任何限制。任意数量的任务可以共享同一个优先级——以保证最大设计弹性。当然,如果需要的话,你也可以为每个任务指定唯一的优先级(就如同某些调度算法的要求一样),但这不是强制要求的。低优先级号表示任务的优先级低,优先级号 0 表示最低优先级。有效的优先级号范围从 0 到(configMAX_PRIORITES – 1)。
调度器保证总是在所有可运行的任务中选择具有最高优先级的任务,并使其进入运行态。如果被选中的优先级上具有不止一个任务,调度器会让这些任务轮流执行。这种行为方式在之前的例子中可以明显看出来。两个测试任务被创建在同一个优先级上,并且一直是可运行的。所以每个任务都执行一个”时间片”,任务在时间片起始时刻进入运行态,在时间片结束时刻又退出运行态。 图 中 t1 与 t2 之间的时段就等于一个时间片。
执行图
我们下篇文章继续