news 2026/7/18 17:11:10

freertos随意记录

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
freertos随意记录

freertos是一个开源免费的嵌入式实时操作系统,非常好用,也比较简单,对freertos的学习过程做一些记录。

1、移植:

freertos的移植应该是比较简单的,下载官方最新版的LTS版本,然后拷贝一些文件和做一些设置就行了。

a、文件复制:

在目标工程下新建文件夹freertos,将freertos-kernel根目录下的C文件复制到前面的文件夹中

复制including文件夹

复制portable文件夹中的文件,我使用的是正点原子的STM32H750开发板,使用的keil ac6创建的工程,文件目录及文件如下:

另外,freertos还有内存管理,复制下列文件到工程目录

freertos还需要一个用于裁剪代码的配置文件,直接复制example里面的

至此,文件的拷贝已经完成了。完整目录结构如下:

b、工程配置

新建一个hal工程,把开发板上的led灯和串口初始化,然后配置工程

添加头文件

freertos的任务切换有几个非常重要的中断函数PendSV_Handler SysTick_Handler SVC_Handler,这是任务切换的关键,需要注释掉hal工程中stm32h7xx_it.c中的这几个函数,并在freertosconfig.h文件中添加下列代码,让stm32h750的中断和freertos内核的中断关联起来

freertos的框架已经搭建起来了,下面需要调用freertos的入口函数vTaskStartScheduler(),这个地方要注意,不要添加错了,我之前就添加成xPortStartScheduler(),找了好久才发现。vTaskStartScheduler里面会调用xPortStartScheduler,不过在这之前需要进行一些闲置任务这些初始化。

完成上面这些,freertos已经在运行了,为了观看效果,新建几个任务

这是几个简单的任务,led和printf任务,其中led是可以阻塞的,print则没有阻塞。非常不建议print这种写法,因为该任务不会离开RUN状态,会一直执行,导致优先级比他低的任务没有机会执行(包括IDLE任务),这里仅仅是为了测试,项目中千万不要这样写,每个任务一定需要离开RUN状态,要么延时等待,要么等待信号量这些。通过测试,print优先级18,几个任务都可以正常运行,print优先级高于20,print任务正常运行,led任务无法运行。

2、学习记录

任务切换原理:

某个任务正在执行,但是系统时钟也在运行,其中systick中断设置为1000HZ,每1ms进入一次systick中断,看下systickHandler干了啥:

#define xPortSysTickHandler SysTick_Handler; void xPortSysTickHandler( void ) { /* The SysTick runs at the lowest interrupt priority, so when this interrupt * executes all interrupts must be unmasked. There is therefore no need to * save and then restore the interrupt mask value as its value is already * known. */ // 屏蔽掉中断优先级低于configMAX_SYSCALL_INTERRUPT_PRIORITY的中断,我们认为优先级低于这个值的中断可以被屏蔽 portDISABLE_INTERRUPTS(); traceISR_ENTER(); { /* Increment the RTOS tick. */ // 系统tick累加 if( xTaskIncrementTick() != pdFALSE ) { traceISR_EXIT_TO_SCHEDULER(); /* A context switch is required. Context switching is performed in * the PendSV interrupt. Pend the PendSV interrupt. */ // 产生pendSV中断请求 // portmacro.h文件如下 // #define portNVIC_INT_CTRL_REG ( *( ( volatile uint32_t * ) 0xe000ed04 ) ) // #define portNVIC_PENDSVSET_BIT ( 1UL << 28UL ) portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT; } else { traceISR_EXIT(); } } portENABLE_INTERRUPTS(); }

其中,产生pendSV中断请求:

从上面的代码可以看出,systick中断就干了一件事:产生pendSV中断。下面看看pendSV中断又干了啥:

void xPortPendSVHandler( void ) { /* This is a naked function. */ __asm volatile ( " mrs r0, psp \n" " isb \n" " \n" " ldr r3, =pxCurrentTCB \n" /* Get the location of the current TCB. */ " ldr r2, [r3] \n" " \n" " tst r14, #0x10 \n" /* Is the task using the FPU context? If so, push high vfp registers. */ " it eq \n" " vstmdbeq r0!, {s16-s31} \n" " \n" " stmdb r0!, {r4-r11, r14} \n" /* Save the core registers. */ " str r0, [r2] \n" /* Save the new top of stack into the first member of the TCB. */ " \n" " stmdb sp!, {r0, r3} \n" " mov r0, %0 \n" " cpsid i \n" /* ARM Cortex-M7 r0p1 Errata 837070 workaround. */ " msr basepri, r0 \n" " dsb \n" " isb \n" " cpsie i \n" /* ARM Cortex-M7 r0p1 Errata 837070 workaround. */ " bl vTaskSwitchContext \n" " mov r0, #0 \n" " msr basepri, r0 \n" " ldmia sp!, {r0, r3} \n" " \n" " ldr r1, [r3] \n" /* The first item in pxCurrentTCB is the task top of stack. */ " ldr r0, [r1] \n" " \n" " ldmia r0!, {r4-r11, r14} \n" /* Pop the core registers. */ " \n" " tst r14, #0x10 \n" /* Is the task using the FPU context? If so, pop the high vfp registers too. */ " it eq \n" " vldmiaeq r0!, {s16-s31} \n" " \n" " msr psp, r0 \n" " isb \n" " \n" #ifdef WORKAROUND_PMU_CM001 /* XMC4000 specific errata workaround. */ #if WORKAROUND_PMU_CM001 == 1 " push { r14 } \n" " pop { pc } \n" #endif #endif " \n" " bx r14 \n" " \n" " .ltorg \n" ::"i" ( configMAX_SYSCALL_INTERRUPT_PRIORITY ) ); } void vTaskSwitchContext( void ) { /** 无关代码 ***/ // 查找当前ready的最高优先级,并把pxCurrentTCB赋值为最高优先级TCB中的下一个TCB taskSELECT_HIGHEST_PRIORITY_TASK(); portTASK_SWITCH_HOOK( pxCurrentTCB ); } #define taskSELECT_HIGHEST_PRIORITY_TASK() \ do { \ UBaseType_t uxTopPriority = uxTopReadyPriority; \ \ /* Find the highest priority queue that contains ready tasks. */ \ while( listLIST_IS_EMPTY( &( pxReadyTasksLists[ uxTopPriority ] ) ) != pdFALSE ) \ { \ configASSERT( uxTopPriority ); \ --uxTopPriority; \ } \ \ /* listGET_OWNER_OF_NEXT_ENTRY indexes through the list, so the tasks of \ * the same priority get an equal share of the processor time. */ \ listGET_OWNER_OF_NEXT_ENTRY( pxCurrentTCB, &( pxReadyTasksLists[ uxTopPriority ] ) ); \ uxTopReadyPriority = uxTopPriority; \ } while( 0 ) /* taskSELECT_HIGHEST_PRIORITY_TASK */

" ldr r3, =pxCurrentTCB \n" /* Get the location of the current TCB. */

获取到当前的任务的TCB;

" bl vTaskSwitchContext \n"

任务切换函数,寻找处于ready的最高优先级,并把pxCurrentTCB赋值为最高优先级中的下一个(如果有,如果没有,不变)

后面的代码看注释知道是加载pxCurrentTCB中的寄存器值,然后进行跳转,完成任务切换。

最后看一下SVCHandler

void vPortSVCHandler( void ) { __asm volatile ( " ldr r3, =pxCurrentTCB \n" /* Restore the context. */ " ldr r1, [r3] \n" /* Get the pxCurrentTCB address. */ " ldr r0, [r1] \n" /* The first item in pxCurrentTCB is the task top of stack. */ " ldmia r0!, {r4-r11, r14} \n" /* Pop the registers that are not automatically saved on exception entry and the critical nesting count. */ " msr psp, r0 \n" /* Restore the task stack pointer. */ " isb \n" " mov r0, #0 \n" " msr basepri, r0 \n" " bx r14 \n" " \n" " .ltorg \n" ); } static void prvPortStartFirstTask( void ) { /* Start the first task. This also clears the bit that indicates the FPU is * in use in case the FPU was used before the scheduler was started - which * would otherwise result in the unnecessary leaving of space in the SVC stack * for lazy saving of FPU registers. */ __asm volatile ( " ldr r0, =0xE000ED08 \n" /* Use the NVIC offset register to locate the stack. */ " ldr r0, [r0] \n" " ldr r0, [r0] \n" " msr msp, r0 \n" /* Set the msp back to the start of the stack. */ " mov r0, #0 \n" /* Clear the bit that indicates the FPU is in use, see comment above. */ " msr control, r0 \n" " cpsie i \n" /* Globally enable interrupts. */ " cpsie f \n" " dsb \n" " isb \n" " svc 0 \n" /* System call to start first task. */ " nop \n" " .ltorg \n" ); } BaseType_t xPortStartScheduler( void ) { prvPortStartFirstTask(); }

这个代码貌似只执行一次,就是在vTaskStartScheduler中,会执行一次,启动第一个任务。

configMAX_SYSCALL_INTERRUPT_PRIORITY,设置系统最高优先级,即如果代码中有优先级大于(优先级数值小于)configMAX_SYSCALL_INTERRUPT_PRIORITY 的中断,不受freertos控制,可以正常产生中断,如果代码中有优先级小于configMAX_SYSCALL_INTERRUPT_PRIORITY 的中断,则会因为freertos调用vPortEnterCritical()函数进入临界状态,无法进入中断。

比如:串口非常重要,不想让串口被系统打断,那么,如果configMAX_SYSCALL_INTERRUPT_PRIORITY设置为16,串口的中断优先级就需要设置为<16;

按键中断不是很重要,可能在发送数据的时候不需要响应按键,可以把按键的中断优先级设置为大于16,那么在进入临界状态时候,按键中断是无法响应的。

// 进入临界状态,禁止中断 void vPortEnterCritical( void ) { // 进入临界状态,禁止中断 portDISABLE_INTERRUPTS(); uxCriticalNesting++; /* This is not the interrupt safe version of the enter critical function so * assert() if it is being called from an interrupt context. Only API * functions that end in "FromISR" can be used in an interrupt. Only assert if * the critical nesting count is 1 to protect against recursive calls if the * assert function also uses a critical section. */ if( uxCriticalNesting == 1 ) { configASSERT( ( portNVIC_INT_CTRL_REG & portVECTACTIVE_MASK ) == 0 ); } } #define taskDISABLE_INTERRUPTS() portDISABLE_INTERRUPTS() #define portDISABLE_INTERRUPTS() vPortRaiseBASEPRI() // 将configMAX_SYSCALL_INTERRUPT_PRIORITY 的值写入basepri,屏蔽优先级小于configMAX_SYSCALL_INTERRUPT_PRIORITY 的中断(优先级数值大于configMAX_SYSCALL_INTERRUPT_PRIORITY ) portFORCE_INLINE static void vPortRaiseBASEPRI( void ) { uint32_t ulNewBASEPRI; __asm volatile ( " mov %0, %1 \n" \ " cpsid i \n" \ " msr basepri, %0 \n" \ " isb \n" \ " dsb \n" \ " cpsie i \n" \ : "=r" ( ulNewBASEPRI ) : "i" ( configMAX_SYSCALL_INTERRUPT_PRIORITY ) : "memory" ); }
在更精巧的设计中,需要对中断掩蔽进行更细腻的控制——只掩蔽优先级低于某一阈值 的中断——它们的优先级在数字上大于等于某个数。那么这个数存储在哪里?就存储在 BASEPRI中。不过,如果往BASEPRI中写0,则另当别论——BASEPRI将停止掩蔽任何中断。例 如,如果你需要掩蔽所有优先级不高于0x60的中断,则可以如下编程: MOV R0, #0x60 MSR BASEPRI, R0 如果需要取消BASEPRI 对中断的掩蔽,则示例代码如下: MOV R0, #0 MSR BASEPRI, R0 《CORTEX M3 权威指南》

退出临界状态:

void vPortExitCritical( void ) { configASSERT( uxCriticalNesting ); uxCriticalNesting--; if( uxCriticalNesting == 0 ) { portENABLE_INTERRUPTS(); } } #define taskENABLE_INTERRUPTS() portENABLE_INTERRUPTS() #define portENABLE_INTERRUPTS() vPortSetBASEPRI( 0 ) // 将0写入basepri,恢复所有中断 portFORCE_INLINE static void vPortSetBASEPRI( uint32_t ulNewMaskValue ) { __asm volatile ( " msr basepri, %0 " ::"r" ( ulNewMaskValue ) : "memory" ); }

待续...

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/18 17:09:26

一个人、一个App、700万营收:AI时代的小生意开始成立了

AI Coding把Demo做烂了&#xff0c;真正稀缺的是把产品跑起来"不会写代码&#xff0c;不应该等于没资格做产品。"现在做一个AI产品&#xff0c;最容易的是做出一个看起来很像产品的东西。输入一句话&#xff0c;几分钟生成页面&#xff1b;再接上大模型&#xff0c;补…

作者头像 李华
网站建设 2026/7/18 17:07:21

嵌入式系统内存映射与EDMA控制器:TI C674x DSP数据流优化实战

1. 项目概述&#xff1a;从地址空间到数据搬运的嵌入式系统基石在嵌入式系统开发&#xff0c;尤其是涉及复杂信号处理的应用中&#xff0c;比如毫米波雷达、电机控制或者高级音频处理&#xff0c;我们常常会面临一个核心矛盾&#xff1a;强大的处理器内核&#xff08;如DSP&…

作者头像 李华
网站建设 2026/7/18 17:02:23

AI编程助手选型指南:Claude Fable 5与Gemini 4 Flash功能对比

在 AI 编程助手领域&#xff0c;Claude Fable 5 的重新上线和 Gemini 4 Flash 的发布标志着工具链竞争进入新阶段。对于每天需要写代码、调试、重构和排查问题的开发者来说&#xff0c;选择适合自己的 AI 编程工具不再只是尝鲜&#xff0c;而是直接影响开发效率和代码质量的核心…

作者头像 李华
网站建设 2026/7/18 17:00:45

Three.js 灯罩教程

灯罩 Lampshade ▶ 在线运行案例 案例合集&#xff1a; 三维可视化功能案例&#xff08;threehub.cn&#xff09;开源仓库github地址&#xff1a; https://github.com/z2586300277/three-cesium-examples400个案例代码: 网盘链接 你将学到什么 ShaderMaterial 自定义着色器…

作者头像 李华
网站建设 2026/7/18 16:59:40

计算机仿真计算工作站选型全指南

2026年跑ANSYS/Fluent仿真要什么硬件配置&#xff1f;仿真计算工作站选型全指南 仿真计算的硬件配置没有"唯一解"。CPU主频决定隐式求解速度&#xff0c;核心数和内存通道决定CFD/显式动力学并行效率&#xff0c;GPU加速则需要软件支持且精度匹配。本文基于ANSYS、Ab…

作者头像 李华
网站建设 2026/7/18 16:59:17

Docker容器化部署实战:从原理到生产环境最佳实践

1. 项目概述&#xff1a;从“部署地狱”到“一键交付”如果你和我一样&#xff0c;在运维或者开发岗位上摸爬滚打了好些年&#xff0c;一定经历过那种“部署地狱”&#xff1a;新来的同事要花一整天配环境&#xff0c;测试环境和生产环境因为一个库的版本差异导致bug&#xff0…

作者头像 李华