FreeRTOS

前言:

函数指针:本质意义上是一个指针,指向某一个函数。

#include "stdio.h"  //包含输入输出头文件
void (*funcp)( );  //声明函数指针
void FileFunc( ),EditFunc( );  //声明函数
 
void main(void)
{
  funcp=FileFunc;  //FileFunc函数的地址赋给funcp
  (*funcp)( );
  funcp=EditFunc;  //EditFunc函数的地址赋给funcp
  (*funcp)( );
}
 
void FileFunc( )  //函数
{
  printf("File\n");
}
 
void EditFunc( )  //函数
{
  printf("Edit\n");
}

指针函数:本质上是一个函数,返回值是一个指针。

类型标识符    *函数名(参数表)

int *f(x,y);

首先它是一个函数,只不过这个函数的返回值是一个地址值。函数返回值必须用同类型的指针变量来接受,也就是说,指针函数一定有函数返回值,而且,在主调函数中,函数返回值必须赋给同类型的指针变量。

表示:

    float *fun( );
    float *p;
    p = fun(a);

一、堆

一块内存空间,可以从中分配出一个小buffer,用完后再把它放回去。

char heap_buf[1024];
int pos = 0;

void *my_malloc(int size)
{
	int old_pos = pos;
	pos += size;
	return &heap_buf[old_pos];
}

void my_free(void *buf)
{
	/* err */
}


int main(void)
{
	char ch = 65; // char ch = 'A';
	int i;
	char *buf = my_malloc(100);
	
	unsigned char uch = 200;
	
	for (i = 0; i < 26; i++)
		buf[i] = 'A' + i;
	
	return 0;
}

我现在要实现my_free()函数,我并不知道前面malloc分配了多少内存。但是malloc分配的时候有个头部里面保存了字节大小,指针减去读取里面的值就行。

二、栈

栈:也是一块内存空间,CPU的SP寄存器指向它,它可以用于函数调用、局部变量、多任务系统里保存现场

五、任务

任务的三要素:函数、栈、优先级

5.1创建任务

TaskHandle_t xTaskCreateStatic ( 
    TaskFunction_t pxTaskCode,   // 函数指针, 任务函数
    const char * const pcName,   // 任务的名字
    const uint32_t ulStackDepth, // 栈大小,单位为word,10表示40字节
    void * const pvParameters,   // 调用任务函数时传入的参数
    UBaseType_t uxPriority,      // 优先级
    StackType_t * const puxStackBuffer, // 静态分配的栈,就是一个buffer
    StaticTask_t * const pxTaskBuffer // 静态分配的任务结构体的指针,用它来操作这个任务
);

这里使用静态创建任务,参数意思如下:

pvTaskCode函数指针,可以简单地认为任务就是一个C函数。 它稍微特殊一点:永远不退出,或者退出时要调用"vTaskDelete(NULL)"
pcName任务的名字,FreeRTOS内部不使用它,仅仅起调试作用。 长度为:configMAX_TASK_NAME_LEN
usStackDepth每个任务都有自己的栈,这里指定栈大小。 单位是word,比如传入100,表示栈大小为100 word,也就是400字节。 最大值为uint16_t的最大值。 怎么确定栈的大小,并不容易,很多时候是估计。 精确的办法是看反汇编码。
pvParameters调用pvTaskCode函数指针时用到:pvTaskCode(pvParameters)
uxPriority优先级范围:0~(configMAX_PRIORITIES – 1) 数值越小优先级越低, 如果传入过大的值,xTaskCreate会把它调整为(configMAX_PRIORITIES – 1)
puxStackBuffer静态分配的栈内存,比如可以传入一个数组, 它的大小是usStackDepth*4。
pxTaskBuffer静态分配的StaticTask_t结构体的指针
返回值成功:返回任务句柄; 失败:NULL

第一个是默认任务,

后面两个分别是光和色的静态创建任务。

5.1.1 估算栈的大小

栈中有:返回地址,其他寄存器,局部变量,可以保存现场(16个Reg,为64Byte)

粗略估计栈的大小(根据代码):

每一级调用估计会使用9个寄存器,每个寄存器4个字节,

以上述代码为例,4层调用,第二层调用时有2个局部变量(4Byte)。

5.1.2 创建任务 --使用任务参数(不同的任务里面使用同一个函数)

例子:

没有加延时函数时,只能够在屏幕上看到task3内容,其他2个内容看不到,因为task3执行完后很有可能在LCDprintf那里被task1或者task2切换,此时g_LCDCanUse=0,然后这2个任务不能够打印,加了延时函数后就可能在延时函数里面被切换就可以打印了。

5.1.3 删除任务

while (1)
    {
        /* 读取红外遥控器 */
		if (0 == IRReceiver_Read(&dev, &data))
		{		
			if (data == 0xa8) /* play */
			{
				/* 创建播放音乐的任务 */
			  extern void PlayMusic(void *params);
			  if (xSoundTaskHandle == NULL)
			  {
					LCD_ClearLine(0, 0);
					LCD_PrintString(0, 0, "Create Task");
					ret = xTaskCreate(PlayMusic, "SoundTask", 128, NULL, osPriorityNormal, &xSoundTaskHandle);
			  }
			}
			
			else if (data == 0xa2) /* power */
			{
				/* 删除播放音乐的任务 */
				if (xSoundTaskHandle != NULL)
				{
					LCD_ClearLine(0, 0);
					LCD_PrintString(0, 0, "Delete Task");
					vTaskDelete(xSoundTaskHandle);
					PassiveBuzzer_Control(0); /* 停止蜂鸣器 */
					xSoundTaskHandle = NULL;
				}
			}
		}
    }

缺陷:

频繁动态创建任务、删除任务很容易造成内存碎片,多次操作后可能会分配不到内存了。

5.5.1任务的挂起与恢复(任务状态----改进播放的控制)

任务状态转换图如下分为4个状态(就绪、执行、等待、暂停):

其中暂停状态可以由自己调用函数或者其他任务调用函数。

任务刚创建出来是就绪状态(ready),然后马上就能运行,变成运行状态,当调用vTaskDelay()时,变成阻塞状态。按下按键变成暂停状态。

5.5.2任务的管理与调度

A、高优先级的任务未执行完,低优先级的任务无法运行;

B、一旦高优先级的任务就绪,会立刻马上运行;

C、最高优先级的任务有多个,会轮流运行。

5.5.3 空闲任务

(创建的任务必须是死循环,不然会进入一个错误的函数,会关闭所有中断,并且死循环,所有的任务都无法使用)

一个任务想要退出的话必须清掉,要么自杀要么他杀。

收尸:释放栈和TCB。

以上图可以看到空闲任务的优先级最低,如果某些任务自杀的话,空闲任务需要释放其栈和TCB,其他任务中delay全都改成vTaskDelay进入等待状态。

5.6 两个delay函数

编写任务函数建议使用事件来驱动。(比如说按键按下就触发)

  • 使用vTaskDelay(n)时,进入、退出vTaskDelay的时间间隔至少是n个Tick中断
  • 使用xTaskDelayUntil(&Pre, n)时,前后两次退出xTaskDelayUntil的时间至少是n个Tick中断
    • 退出xTaskDelayUntil时任务就进入的就绪状态,一般都能得到执行机会
    • 所以可以使用xTaskDelayUntil来让任务周期性地运行

执行某一个过程时间相同的话就调用第二个函数

6 同步与互斥

同步实例:

现在任务A计算一个比较大的数值,任务B来打印数值和消耗的时间。最后屏幕上面显示时间为2.5s,实际上B任务一直在死等,消耗CPU的资源。现在在B任务添加vTaskDelay(3000),让B直接进入阻塞状态,等A执行完后直接唤醒B任务。

8-1-1 数据传输方法-----全局变量、环形缓冲区、队列

使用全局变量的时候可能会导致传递错误的数据,比如说A任务修改某一结构体数据时,在A任务还没有修改完,就被B任务切换出去了,此时结构体里面的数据肯定是错误的数据;并且没有阻塞和唤醒机制,效率会很低下。

环形缓存区具体实现:

int w=0;
int r=0;
int buf[8]={0};
//空:r==w
//满:next_w==r

为了区分空和满,设定最后一个位置不能写数据。

写数据:

//写数据
while(1)
{
int next_w=r+1;
if(next_w==8)
        next_w=0;
        if(next_w!=r)//如果未满
        {
                buf[w]=val;
                w++;
                w=w%8;
        }
}

读数据:

//读数据
while(1)
{
        if(w!=r)
        {
                val=buf[r];
                r++;
                r=r%8;
        }
}

8-1-2 队列的本质

例子:A放产品,B拿出产品

A相对于写队列,B相当于读队列;

对于B来说:

当流水线中没有产品时,B就眯一会(会设定闹钟,即超时时间)

同事A放入产品后会叫醒B(任务A写队列,并唤醒任务B)

闹钟时间到后,会唤醒B

当流水线上面没有产品时,B会阻塞,任务B就会从就绪链表里面删除,放到接收队列链表和超时时间链表里面。当这两个条件(A放物品到流水线或者超时时间达到)任意一个条件到达时B任务会被唤醒,会转到就绪链表里面。

对于挡球板游戏:

将环形缓存区改为队列传输,

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值