创建第一个FreeRTOS程序
1、官网源码下载
(1)进入FreeRTOS官网FreeRTOS professional services for application and RTOS development and consulting. FreeRTOS is an Open Source Code RTOS
(2)点击下载FreeRTOS
2、处理目录
(1)下载后解压FreeRTOS文件
(2)删除多余文件(红框里的)
(3)删除"FreeRTOSv202212.01\FreeRTOS\Demo"目录下用不到的示例工程,留下common这里放了一些公共文件
(4)"FreeRTOSv202212.01\FreeRTOS\Source\portable"目录下只保留如下两个文件夹,其他全部删掉。(5)"FreeRTOSv202212.01\FreeRTOS\Source\portable\RVDS"目录下只保留如下一个文件夹,其他全部删掉
3、打开编译工程
(1)删除后文件后,进入如下图打开工程
(2)弹出如下对话框,说明该工程是用KeilMDK4创建的。点击“Migrate to Device Pack”更新为KeilMDK5。
(3)弹出对话框,点击“确定”。
- 更新后,关闭工程再重新打开。编译
- 工程目录介绍(System里还有一个LCD也删掉)
-
4、去掉无关代码
(1)Demo Files文件下只保留“serial.c和main.c”文件,其他都删掉(删完之后main里去掉一些头文件)。
(2)编译
5、删除未定义报错内容
(1)在文件STM32F10x.s中,删除如下内容。
(2)删除其他未定义的相关内容,再次编译。报错的内容均删除或者注释,直到没错为止。
- 验证
在原有任务的基础上加个i++验证
- 配置串口
int fputc( int ch, FILE *f )//重定向 修改数据传输方向
{
while(!(USART1->SR & (1<<7))){}
USART1->DR =ch;
return ch;
}
初始化删除多余的东西,只保留串口一的配置(这里的函数就是个串口的初始化,有效程序只有串口配置和GPIO配置,按照我下面写的程序弄就可以了。写完记得再main里调用,参数可以不填写。。或者删掉重新写个这个函数也可以)
xComPortHandle xSerialPortInitMinimal( unsigned long ulWantedBaud, unsigned portBASE_TYPE uxQueueLength )
{
xComPortHandle xReturn;
USART_InitTypeDef USART_InitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
/* Enable USART1 clock */
RCC_APB2PeriphClockCmd( RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE );
/* Configure USART1 Rx (PA10) as input floating */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init( GPIOA, &GPIO_InitStructure );
/* Configure USART1 Tx (PA9) as alternate function push-pull */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init( GPIOA, &GPIO_InitStructure );
USART_InitStructure.USART_BaudRate = 115200;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
USART_InitStructure.USART_StopBits = USART_StopBits_1;
USART_InitStructure.USART_Parity = USART_Parity_No ;
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
USART_InitStructure.USART_Clock = USART_Clock_Disable;
USART_InitStructure.USART_CPOL = USART_CPOL_Low;
USART_InitStructure.USART_CPHA = USART_CPHA_2Edge;
USART_InitStructure.USART_LastBit = USART_LastBit_Disable;
USART_Init( USART1, &USART_InitStructure );
USART_Cmd( USART1, ENABLE );
return xReturn;
}
使用printf
仿真里看串口打印消息
- 命名规范
三、FreeRTOS命名规范_freertos命名规则-CSDN博客
3、 验证
在原有任务的基础上加个i++验证
4、 配置串口
int fputc( int ch, FILE *f )//重定向 修改数据传输方向
{
while(!(USART1->SR & (1<<7))){}
USART1->DR =ch;
return ch;
}
初始化删除多余的东西,只保留串口一的配置(这里的函数就是个串口的初始化,有效程序只有串口配置和GPIO配置,按照我下面写的程序弄就可以了。写完记得再main里调用,参数可以不填写。。或者删掉重新写个这个函数也可以)
xComPortHandle xSerialPortInitMinimal( unsigned long ulWantedBaud, unsigned portBASE_TYPE uxQueueLength )
{
xComPortHandle xReturn;
USART_InitTypeDef USART_InitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
/* Enable USART1 clock */
RCC_APB2PeriphClockCmd( RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE );
/* Configure USART1 Rx (PA10) as input floating */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init( GPIOA, &GPIO_InitStructure );
/* Configure USART1 Tx (PA9) as alternate function push-pull */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init( GPIOA, &GPIO_InitStructure );
USART_InitStructure.USART_BaudRate = 115200;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
USART_InitStructure.USART_StopBits = USART_StopBits_1;
USART_InitStructure.USART_Parity = USART_Parity_No ;
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
USART_InitStructure.USART_Clock = USART_Clock_Disable;
USART_InitStructure.USART_CPOL = USART_CPOL_Low;
USART_InitStructure.USART_CPHA = USART_CPHA_2Edge;
USART_InitStructure.USART_LastBit = USART_LastBit_Disable;
USART_Init( USART1, &USART_InitStructure );
USART_Cmd( USART1, ENABLE );
return xReturn;
}
使用printf
仿真里看串口打印消息
5、 命名规范
三、FreeRTOS命名规范_freertos命名规则-CSDN博客
动态任务的创建
1、 任务是什么
任务的外观:一个永远不返回的函数
说明:使用void *类型形参,确保可以传入任意类型的参数
2、 任务实验
实现
创建任务函数xTaskCreate:
任务也不是很复杂的东西,任务也就是一个函数xTaskCreate。简单得说,创建一个任务,你得提供它的执行函数,你得提供它的栈的大小,函数的执行空间,函数的优先级等重要的条件。因为任务在运行中,任务函数有调用关系,有局部变量,这些都保存在任务的栈里面;任务有可能被切换,有可能被暂停,这时候CPU寄存器中断现场数据都保存在栈里面。
函数原型
BaseType_t xTaskCreate( TaskFunction_t pxTaskCode,
const char * const pcName, const configSTACK_DEPTH_TYPE usStackDepth,
void * const pvParameters,
UBaseType_t uxPriority,
TaskHandle_t * const pxCreatedTask )
参数说明
pvTaskCode:指向任务函数的指针。该函数表示任务要执行的代码。
pcName:任务名称字符串。用于调试和跟踪,不影响任务功能。
usStackDepth:任务栈大小(以单词为单位)。根据任务需求设定,过小可能导致栈溢出。
pvParameters:传递给任务函数的参数。可以是任意类型的指针。
uxPriority:任务优先级。数值越大,优先级越高。
pxCreatedTask:任务句柄指针。用于存储创建任务后的任务句柄,可选参数。
返回值
如果任务创建成功,返回pdPASS。
如果任务创建失败(例如内存不足),返回错误码。
任务创建成功后,系统会自动将其加入到调度队列。调度器会根据任务优先级选择合适的任务执行。
实验
xTaskCreate(test1,"demo1",100,NULL,1,NULL);
xTaskCreate(test2,"demo2",100,NULL,1,NULL);
int a,b;
void test1(void *param)
{
while(1)
{
a++;
printf("test1\n");
}
}
void test2(void *param)
{
while(1)
{
b++;
printf("test2\n");
}
}
3、 任务的内部
① 代码段和数据区由编译器在编译代码时自动分配与控制
② 堆的分配和使用由程序员控制
③ C代码中一般不会显式使用栈,将由编译器完成;在汇编代码中,程序员可以设置栈的位置并使用
④ C代码也不会显示使用寄存器,也是由编译器完成
个人:代码区 + 数据区 + 栈 + 堆可以理解为任务的实体 + 运行环境
任务切换的本质:保存前一任务(prev)的当前运行状态,恢复后一任务(next)之前的运行状态,并切换到该任务运行
4、 任务控制块
TCB_t的全称为Task Control Block,也就是任务控制块,这个结构体包含了一个任务所有的信息,但是源代码中存在大量的条件配置选项,以下屏蔽掉的都是可以通过条件来配置的选项,通过条件来决定哪些定义使用或者不用,暂时不需要用到这些,对条件配置项进行屏蔽,TCB最主要的参数在上面它的定义以及相关变量的解释如下
5、任务状态
就绪态(Ready):任务已经具备了运行条件(没有被挂起或阻塞),但是有更高优先级或同优先级的任务正在运行,所以需要等待。
运行态(Running):当任务正在运行时,此时的状态被称为运行态,即CPU的使用权被这个任务占用。
阻塞态(Blocked):任务在等待信号量、消息队列、事件标准组、系统延时时,被称为阻塞态,如果等待的事件到了,就会自动退出阻塞态,准备运行。
挂起态(Suspended):任务被暂时停止,通过调用挂起函数(vTaskSuspend())可以把指定任务挂起,任务挂起后暂时不会运行,只有调用恢复函数(xTaskResume())才可以退出挂起状态。
静态任务创建
xTaskCreateStatic():
xTaskCreateStatic() 用于在系统中创建静态任务。与xTaskCreate()动态分配任务内存不同,xTaskCreateStaitc()需要开发者为任务栈和任务控制块(TCB)提供预先分配的内存空间。这对于内存受限或需要更精准控制任务内存的系统来说非常有用。
函数原型
TaskHandle_t xTaskCreateStatic( TaskFunction_t pxTaskCode,
const char * const pcName, const uint32_t ulStackDepth,
void * const pvParameters,
UBaseType_t uxPriority,
StackType_t * const puxStackBuffer,
StaticTask_t * const pxTaskBuffer )
参数解释:
pvTaskCode:指向任务函数的指针。
pcName:任务的名称,通常用于调试目的。
ulStackDepth:任务栈大小,以堆栈类型(StackType_t)为单位计算。
pvParameters:传递给任务函数的参数指针。
uxPriority:任务优先级,数值越大,优先级越高。
puxStackBuffer:指向已分配的任务栈内存缓冲区的指针。
pxTaskBuffer:指向已分配的任务控制块 (TCB) 内存结构的指针
函数返回值:
成功创建任务时,返回任务句柄 TaskHandle_t。
若任务创建失败,返回 NULL。
挂起任务、恢复任务
挂起任务函数原型
vTaskSuspend( TaskHandle_t xTaskToSuspend )
参数
xTaskToSuspend:需要挂起任务的句柄
实验
创建任务时加上句柄
句柄声明
在任务1里挂起任务2
void vTask1( void *pvParameters )
{
while(1)
{
printf("vTask1\n");
demo1=1;
demo2=0;
vTaskDelay(4);
vTaskSuspend(CreatedTask);
}
}
恢复任务函数原型
void vTaskResume( TaskHandle_t xTaskToResume )
参数
TaskToResumex:需要恢复任务的句柄
实验
//挂起后又恢复了实验中如果正常执行任务2就验证了
//也可以再创建个任务3,进入延时一段时间恢复任务2
void vTask1( void *pvParameters )
{
while(1)
{
printf("vTask1\n");
demo1=1;
demo2=0;
vTaskDelay(4);
vTaskSuspend(CreatedTask);
vTaskDelay(4);
vTaskResume(CreatedTask); }
}
任务删除
vTaskDelete()函数用于删除任务。在使用这个函数时,需要提供一个任务句柄作为参数,以便通知内核删除哪个任务。
函数原型
void vTaskDelete( TaskHandle_t xTaskToDelete )
参数:
xTaskToDelete:需要删除任务的句柄
空闲任务与钩子函数
1、空闲任务
创建的任务大部份时间都处于阻塞态。这种状态下所有的任务都不可运行,所以也不能被调度器选中。但处理器总是需要代码来执行——所以至少要有一个任务处于运行态。为了保证这一点,当调用 vTaskStartScheduler()时,调度器会自动创建一个空闲任务。空闲任务是一个非常短小的循环——和最早的示例任务十分相似,总是可以运行。空闲任务拥有最低优先级(优先级 0)以保证其不会妨碍具有更高优先级的应用任务进入运行态——当然,没有任何限制说是不能把应用任务创建在与空闲任务相同的优先级上;如果需要的话,你一样可以和空闲任务一起共享优先级。运行在最低优先级可以保证一旦有更高优先级的任务进入就绪态,空闲任务就会立即切出运行态。
2、 钩子函数
通过空闲任务钩子函数(或称回调,hook, or call-back),可以直接在空闲任务中添加应用程序相关的功能。空闲任务钩子函数会被空闲任务每循环一次就自动调用一次。通常空闲任务钩子函数被用于:
● 执行低优先级,后台或需要不停处理的功能代码。
● 测试系统处理(空闲任务只会在所有其它任务都不运行时才有机会执行,所以测量出空闲任务占用的处理时间就可以清楚的知道系统有多少富余的处理时间)。
● 将处理器配置到低功耗模式——提供一种自动省电方法,使得在没有任何应用功能
● 需要处理的时候,系统自动进入省电模式。
3、 空闲任务钩子函数的实现限制
1. 绝不能阻或挂起。空闲任务只会在其它任务都不运行时才会被执行(除非有应用任务共享空闲任务优先级)。以任何方式阻塞空闲任务都可能导致没有任务能够进入运行态!
2. 如果应用程序用到了 vTaskDelete() AP 函数,则空闲钩子函数必须能够尽快返回。因为在任务被删除后,空闲任务负责回收内核资源。如果空闲任务一直运行在钩子函数中,则无法进行回收工作。
4、 钩子函数的使用
● main函数中找到vTaskStartScheduler()并跳转
● vTaskStartScheduler()内可以找到如图的函数
● 跳转空闲任务函数找到了vApplicationIdleHook()函数就是钩子函数,但是需要configUSE_IDLE_HOOK==1
● 右键跳转configUSE_IDLE_HOOK并将configUSE_IDLE_HOOK等于1
● 编译发现报错,内容为vApplicationIdleHook未定义
● 接下来我们声明写一个vApplicationIdleHook函数并在里面写自己的任务程序就可以了
FreeRTOS的延时函数
vTaskDelay()延时函数,参数xTicksToDelay表示,延时xTicksToDelay*Tick的时间,填入1表示延时1个Tick的时间
vTaskDelayUntil()函数,参数pxPreviousWakeTime是一个起始的Tick时间,xTimeIncrement是填入所需要延时的时间。当需要我们的任务在精确时间开始执行时可以使用该函数达到准确延时。
xTaskGetTickCount()获取当前时间节点
void vTask1( void *pvParameters )
{
int i=0;
int j=0;
int BUF[6]={4,16,12,2};
while(1)
{
TickType_t tStart = xTaskGetTickCount();//获取时间节拍
for(i=0;i<BUF[j];i++){printf("vTask1\n");}
j++;
if(j>4){j=0;}
demo1=1;
demo2=0;
// vTaskDelay(4);//固定延时
xTaskDelayUntil(&tStart,4);//动态调节的延时
}
}
Cubemx使用
1、 打开cubemx
2、 新建工程,选择自己芯片的型号
3 、配置LED灯以后,选择FreeRTOS,并配置版本V2
3、改下时钟
4、 生成工程
5、 程序编写
//初始化
void MX_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
/* GPIO Ports Clock Enable */
__HAL_RCC_GPIOB_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0|GPIO_PIN_1, GPIO_PIN_SET);
/*Configure GPIO pin : PB0 */
GPIO_InitStruct.Pin = GPIO_PIN_0|GPIO_PIN_1;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
}
void StartDefaultTask(void *argument)
{
/* USER CODE BEGIN StartDefaultTask */
/* Infinite loop */
for(;;)
{
HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_1);
osDelay(500);
}
/* USER CODE END StartDefaultTask */
}
void StartTask02(void *argument)
{
/* USER CODE BEGIN StartTask02 */
/* Infinite loop */
for(;;)
{
HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_0);
osDelay(500);
}
/* USER CODE END StartTask02 */
}
5、烧录验证两个灯闪烁了