尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

FreeRTOS + LwIP + TM4C1294XL 多线程数据采集网关开发实战

FreeRTOS + LwIP + TM4C1294XL 多线程数据采集网关开发实战 简介本资源是面向嵌入式开发工程师与TI单片机进阶学习者的FreeRTOSLwIP双栈移植实践工程聚焦TM4C1294XL微控制器平台解决RTOS与TCP/IP协议栈协同运行的核心技术难点。压缩包含502个文件以203个头文件h和149个源文件c为主体涵盖FreeRTOS内核配置、LwIP网络驱动如enet_s2e.c、emac.c、USB与以太网外设适配代码另有32个IAR工程配置文件xcl、24个编译目标文件o及多个调试脚本bat/ps1和工程文件ewp/uvproj整体9.89MB结构完整、可直接构建调试。已有271人学习下载资源提供可运行的完整工程框架包含driverlib.a库支持、多任务调度示例、LwIP与FreeRTOS同步机制信号量/队列、EMAC硬件抽象层实现及详细browse/pbd调试信息显著降低网络化嵌入式系统开发门槛。 几个月前我拿到一块新的 EK-TM4C1294XL LaunchPad准备做一个基于 FreeRTOS 多线程架构的采集网关。应用场景很直接现场 CAN 总线的数据、模拟量传感器信号统一采集之后通过 10/100M 以太网接入后台。工程代号我起了一个比较随意的名字repeatxdn。整套软件栈选型也很常规——RTOS 用 FreeRTOS网络协议栈用 LwIP芯片用 TM4C1294XL。真正折腾人的不是单个组件怎么跑而是三个东西组合在一起之后线程、中断、协议栈、驱动之间的资源争用和时序问题。这个组合在嵌入式圈子里非常经典TM4C1294 自带以太网 MAC 和内部 PHY省掉外挂 PHY 的物料成本和布线麻烦FreeRTOS 提供抢占式多任务调度适合把采集、处理、网络上报拆成多个职责独立的线程LwIP 则是嵌入式中最常用的轻量级 TCP/IP 协议栈。对于做工业数据采集、边缘网关、物联网设备的开发者来说这套方案完全可以当成一个可复用的参考模板。下面我把从零搭建到稳定性调优的完整过程写出来包括移植配置、任务划分、LwIP 对接以及我在线程死锁和堆栈溢出上踩过的具体坑。无论你是刚接触 FreeRTOS 的学生还是在评估 TM4C1294 方案的工程师这篇文章应该都能提供一些实在的参考。1. 项目背景与方案选型为什么是这三样组合1.1 项目原型和需求边界项目原型是一台工业数据采集网关。现场设备通过 CAN 总线对外发送状态信息规划波特率 500kbps峰值报文量大约 1000 帧/秒另外一路模拟量采样模块负责采集温度和电压采样周期 100ms板子还保留了一路 UART 作为本地调试配置口。所有数据最终通过以太网上报给上位机要求 TCP 长连接可配置上报周期最低到 10ms。这些需求合在一起光靠一个裸机大循环已经很难写。原因很简单CAN 接收是中断驱动的中断来了必须及时读走 FIFO否则会丢帧模拟量采样需要周期性触发TCP 数据发送又是另一套时序底层网卡还会连续产生收发中断。如果全部挤在同一个循环里任何一个环节处理慢了其他环节都会跟着抖。所以这个项目从一开始就需要一个 RTOS 做任务调度。这也是标题里基于线程这个说法的来源把不同工作拆成独立线程用优先级解决实时性用队列和信号量解决协作。1.2 芯片选型TM4C1294XL 能扛住什么TM4C1294XL 这个名字实际上包含两层意思如果买的是原厂 LaunchPad全称是 EK-TM4C1294XL板上那颗主控芯片的完整型号是 TM4C1294NCPDT。它基于 ARM Cortex-M4F 内核主频 120MHz带硬件浮点单元内置 256KB SRAM 和 1MB Flash。对于这个量级的采集网关来说CPU 算力足够内存也不算紧张。真正让它在我这儿胜出的原因是网络接口。TM4C1294 的以太网控制器在芯片内部集成了 MAC 和 PHY意味着 MCU 可以直接用 RMII/MII 接口信号外接一个带隔离变压器的 RJ45 座子或者使用板载 PHY 方案。而当时对比过的其他 M4 方案大多只集成 MAC需要外挂 PHY 芯片BOM 里多一颗物料PCB 布线也多一块成本其实没有优势。TivaWare 驱动库对这块芯片的覆盖也比较完整外设驱动、例程、甚至 LwIP 的移植参考都能在官方包里找到。这对快速起步帮助很大很多底层寄存器细节不用自己去啃数据手册。1.3 RTOS 和协议栈选型FreeRTOS 是我这边唯一考虑过的 RTOS。它开源免费商业使用没有授权费用在工业设备里不会有合规风险内核本身很小占用几 KB Flash 就够社区资料非常多碰到问题基本都能搜到。任务调度、队列、互斥量、信号量、任务通知这些基础件都提供文档也写得很清楚。LwIP 是嵌入式领域用得最广的 TCP/IP 协议栈之一。它最大的特点是对资源要求控制得比较好支持无操作系统模式和带操作系统模式。在带操作系统模式下LwIP 依赖外部提供的互斥量、信号量和邮箱机制来做线程同步这正好能和 FreeRTOS 对应上——互斥量对应 mutex信号量对应 semaphore邮箱对应队列。后面第 4 章我会详细讲这条对接链路。选型阶段也考虑过直接跑 uIP但那个协议栈功能上比 LwIP 弱不少TCP 会话管理也不够灵活项目要做 TCP server、UDP 组播设备发现还是 LwIP 更合适。2. 内核移植配置先把 FreeRTOS 在 TM4C1294XL 上跑稳2.1 源码和工程结构FreeRTOS 当前的版本可以从官方 GitHub 仓库获取核心源码集中在 FreeRTOS/Source 目录下。一个最小可用的内核工程至少需要以下文件FreeRTOS/Source/ ├── tasks.c ├── list.c ├── queue.c ├── timers.c ├── event_groups.c ├── croutine.c ├── stream_buffer.c └── portable/ ├── GCC/ARM_CM4F/ # GCC 工具链时用 ├── RVDS/ARM_CM4F/ # Keil MDK 时用 └── MemMang/heap_4.c我用的 IDE 是 Keil MDKportable 层选的是 RVDS/ARM_CM4F后来为了调试方便也切过 GCC 工具链。heap 实现我推荐 heap_4.c它支持合并相邻空闲块可以比较有效地减少内存碎片。当然如果你们团队对内存碎片有更严格的把控要求heap_5.c 可以把堆分散到多个不连续内存区域TM4C1294 的大 SRAM 也能这么玩。最简单的验证方式其实是用 TivaWare 自带的 FreeRTOS 例程起步。TivaWare 安装目录下有 examples/boards/ek-tm4c1294xl/freertos_demo 这个工程它已经把启动文件、中断处理函数和 FreeRTOSConfig.h 都配置好了在这个基础上改成自己的工程框架要快很多。但如果你不想用 TivaWare 的旧版 FreeRTOS而是想换成新版内核那就需要自己把 portable 层文件替换上去并核对中断向量表。2.2 FreeRTOSConfig.h 关键配置FreeRTOSConfig.h 是整个内核行为的开关集中地。我给 TM4C1294XL 做配置的时候主要关注下面这些宏配置宏取值说明configCPU_CLOCK_HZ120000000必须和实际 PLL 输出主频一致configTICK_RATE_HZ10001ms 一个系统节拍任务调度和延时更精细configMAX_PRIORITIES8最多 8 个任务优先级configTOTAL_HEAP_SIZE32 * 1024FreeRTOS 内核堆大小单位字节configUSE_MUTEXES1启用互斥量带优先级继承configUSE_COUNTING_SEMAPHORES1启用计数信号量configCHECK_FOR_STACK_OVERFLOW2使能栈溢出检测方式二configUSE_IDLE_HOOK1空闲任务钩子用来做低功耗或监控configUSE_TICK_HOOK0系统节拍钩子需要时再开configMINIMAL_STACK_SIZE128空闲任务栈大小单位是字WordconfigMAX_SYSCALL_INTERRUPT_PRIORITY5允许调用 FromISR 系列 API 的最大中断优先级这里有两个特别容易踩的坑。第一configCPU_CLOCK_HZ 必须和 SysCtlClockFreqSet 得到的系统时钟一致否则 vTaskDelay 的时间会按错误的时钟频率计算系统节拍就漂了。第二TM4C1294 的 NVIC 中断优先级只有高 3 位有效优先级实际范围是 0 到 7数值越大优先级越低。FreeRTOS 要求内核使用的 SysTick 和 PendSV 中断优先级必须是最低的所以 configKERNEL_INTERRUPT_PRIORITY 可以配成 255而 configMAX_SYSCALL_INTERRUPT_PRIORITY 配成 5意思是优先级数值大于等于 5 的中断不能直接调用 FreeRTOS 的 FromISR 接口。这个规则直接关系到后面中断和线程的数据交互需要仔细核对。2.3 中断向量和启动文件Cortex-M4 内核有两个中断和 FreeRTOS 强相关PendSV 负责上下文切换SysTick 负责系统节拍还有一个 SVC 用于第一任务启动。这三个中断处理函数必须由 FreeRTOS 的 port 层提供不能被启动文件里的同名弱定义占用。具体做法是如果使用 Keil需要在 startup_keil.s 或者对应的启动文件里把 SVC_Handler、PendSV_Handler、SysTick_Handler 这三个名字注释掉或者确认它们没有被定义。FreeRTOS 源码里的 port 层会重新定义这几个函数名如果两边重名链接时会产生 multiple definition 错误。TivaWare 的 freertos_demo 工程里这一点已经处理好了可以直接参考它的中断向量表写法。2.4 时钟和外设初始化TM4C1294 板载晶体是 25MHz和 TM4C123 那类的 16MHz 晶体不一样初始化 PLL 的时候要特别注意。我使用的是 TivaWare 的 APISysCtlClockFreqSet(SYSCTL_USE_PLL | SYSCTL_OSC_MAIN | SYSCTL_XTAL_25MHZ | SYSCTL_CFG_VCO_480, 120000000);这样配置之后系统主频、外设总线时钟都会以 120MHz 为基准后续配置 UART 波特率、PWM 频率、定时器周期时都要基于这个值计算。2.5 第一个线程验证内核工程搭建好之后我习惯先写一个最小任务验证调度器是否正常工作。最简单的方式是让板载 LED 以 500ms 周期翻转void vTaskLed(void *pvParameters) { for (;;) { GPIOPinWrite(GPIO_PORTN_BASE, GPIO_PIN_0, 0); vTaskDelay(pdMS_TO_TICKS(500)); GPIOPinWrite(GPIO_PORTN_BASE, GPIO_PIN_0, GPIO_PIN_0); vTaskDelay(pdMS_TO_TICKS(500)); } }如果 LED 以稳定节奏闪烁说明 SysTick 调度、上下文切换、PendSV 中断路径都正常工作了。这个时候再继续往上叠加外设驱动和协议栈排查问题的范围就会小很多。3. 基于线程的应用层设计任务划分与优先级调度3.1 任务清单标题里的基于线程不是单纯指系统用了 FreeRTOS而是应用层真的按并行思路去拆任务。我在 repeatxdn 工程里的任务划分如下任务名优先级触发方式建议栈字职责can_collect_task4CAN 接收中断 周期查询512CAN 报文接收、解析、入队sensor_task350ms 周期256模拟量采样、滤波、本地数据更新data_proc_task3消息队列触发512融合 CAN 与传感器数据封装上报帧tcp_server_task2阻塞于 netconn_accept1024TCP 长连接管理、命令接收、数据发送udp_notify_task2500ms 周期512UDP 组播设备发现与心跳广播led_task1500ms 周期128运行状态指示灯monitor_task01s 周期256统计任务栈余量、CPU 占用率我当时特意把 can_collect_task 放在最高优先级因为 CAN 报文实时性最强晚一点处理就可能被 FIFO 溢出冲掉。tcp_server_task 的栈给到了 1024 字因为 netconn API 调用路径的栈深度加上 LwIP 内部调用链确实比普通采集任务深不少如果给太小网络任务容易在调用 send 的时候莫名崩溃。3.2 优先级分配的核心逻辑任务优先级不是随意定的。我的分配思路是三条线第一离数据源头越近优先级越高。CAN 中断只是把报文从 FIFO 搬到队列真正的解析工作由 can_collect_task 做这个任务直接决定数据采集的完整性所以优先级最高。第二网络上报任务放在中优先级。tcpip_thread 和 tcp_server_task 不能开太高否则高频网络中断和数据发送会挤压 CAN 的采集时间但也不能太低否则上位机指令响应延迟会明显变大。我把它放在 2 级和 UDP 组播任务同级靠队列和信号量保证先后顺序。第三监控类任务永远放最低优先级。monitor_task 只做统计慢一点没有任何影响。空闲任务钩子里也做了一个轻量级的 CPU 空闲计数值结合运行时间统计功能可以算出 CPU 占用率这个在后面第 6 章会提到。3.3 任务间通信的选型FreeRTOS 里任务间通信方式有好几种我根据自己的场景做了简单分类队列适合数据流传递。CAN 原始报文从采集任务发给数据处理任务我用的是一个长度为 64 的队列上报数据从 data_proc_task 发往 tcp_server_task用的是另一个队列。二值信号量适合中断唤醒任务。ETH 收包中断里只做一个信号量 give网络线程等待这个信号量然后调用 LwIP 的收包接口。互斥量适合保护共享资源。比如调试串口、Flash 读写这类一个时刻只能一个任务使用的资源我用互斥量保护还带优先级继承特性。任务通知适合简单的唤醒场景。任务通知比信号量更轻量不占用独立的内核对象在只唤醒单个任务并且不需要计数的情况下我用任务通知替代信号量。一个比较实用的选择经验是如果传递的是连续数据流优先队列如果只是某个事件发生了这种标志优先任务通知或二值信号量如果多个任务都要访问同一份资源再考虑互斥量。不要什么场景都用队列硬套。3.4 中断与线程的数据接力中断服务程序里绝对不做复杂业务处理。这是多线程系统设计里最基础也最重要的一条原则。以 CAN0 接收中断为例。我在中断里做的最多的事情就是读 FIFO、构造消息结构体、调用 FromISR 后缀的队列发送函数把数据交给 can_collect_task然后清理中断标志void CAN0IntHandler(void) { uint32_t ui32Status; tCANMsgObject sMsg; uint8_t pui8Data[8]; BaseType_t xHigherPriorityTaskWoken pdFALSE; ui32Status CANIntStatus(CAN0_BASE, CAN_INT_STS_CAUSE); if (ui32Status CAN_INT_INTID_STATUS) { CANIntClear(CAN0_BASE, CAN_INT_INTID_STATUS); return; } sMsg.pui8MsgData pui8Data; CANMessageGet(CAN0_BASE, ui32Status, sMsg, 0); if (sMsg.ui32MsgID 0x100u) { xQueueSendFromISR(xCanQueue, sMsg, xHigherPriorityTaskWoken); } portYIELD_FROM_ISR(xHigherPriorityTaskWoken); }注意最后一行如果 xHigherPriorityTaskWoken 被置为 pdTRUE表示队列发送唤醒了一个高优先级任务这时需要主动触发一次上下文切换让唤醒后的任务立刻执行。这个细节很多人会漏掉导致中断退出了但任务没有及时调度。3.5 端到端数据链路整个数据流从物理信号到网络报文我梳理成了一条清晰的链路CAN 总线 - CAN 控制器 FIFO - CAN0 中断 - can_collect_task - CAN 报文队列 - data_proc_task 数据融合 - 上报队列 - tcp_server_task - LwIP netconn_write - EMAC 驱动 - 内部 PHY - RJ45 以太网每条数据通路都对应一组队列和信号量。实际调试时如果某个环节丢了数据我只需要在队列的出入口打点就能快速定位是采集慢、处理慢还是发送慢。这个数据流可视化的思路对排查线程问题帮助很大。4. LwIP 移植照这条路把网络栈接进 FreeRTOS4.1 移植前必须明白的分层网上很多资料把 LwIP 移植讲得像玄学其实看透分层之后就清晰了。LwIP 在你需要关注的层面可以分为五层驱动层操作具体的 MAC 控制器负责收发以太网帧。网卡接口层通过 struct netif 描述一个网口LwIP 通过 netif 的 input/output/linkoutput 回调函数驱动网卡。协议核心层IP、TCP、UDP、ICMP 等协议实现这部分是协议栈本身不需要改。OS 封装层LwIP 设计为可以在裸机或 RTOS 上跑所以需要一个 sys_arch 层把互斥量、信号量、邮箱、任务创建等能力映射到 FreeRTOS 上。API 层给用户提供 netconn API 或 socket API。对 TM4C1294 来说驱动层 TI 官方已经提供了 EMAC 驱动代码我要做的主要是 netif 初始化、注册回调以及补全 sys_arch。协议核心层和 API 层直接编译进工程即可。4.2 EMAC 与内部 PHY 初始化要点TM4C1294 的以太网 MAC 很特别它支持内部 PHY默认 PHY 地址是 0。使用内部 PHY 时不需要提供外部 MDIO 引脚连接。初始化过程大概包括使能以太网外设时钟、设置内部 PHY 模式、配置 MAC、打开 DMA 中断、初始化收发描述符。TivaWare 的 third_party/lwip-1.4.1 目录下有一个现成的移植例程其中 tivaif.c 和 tivaif.h 把 EMAC 初始化和收包处理都封装好了。我一开始是直接照搬它的后来为了适配新版 LwIP把里面几个过时的函数调用做了调整核心逻辑没动。读取 PHY 状态也是必须的通过 MDIO 接口读 PHY 的 BMSR 和 PHYSTS 寄存器判断连接是否建立、当前速率是 10M 还是 100M、双工模式是全双工还是半双工。我在项目里做了一个 link 状态监控任务每 500ms 读一次检测到网线断开或恢复时立即向应用层上报事件。4.3 netif 注册与底层收发回调LwIP 的主入口配置通常是这样的struct netif gNetIf; struct ip4_addr xIpAddr, xNetMask, xGateway; IP4_ADDR(xIpAddr, 192, 168, 1, 100); IP4_ADDR(xNetMask, 255, 255, 255, 0); IP4_ADDR(xGateway, 192, 168, 1, 1); netif_add(gNetIf, xIpAddr, xNetMask, xGateway, NULL, tivaif_init, tcpip_input); netif_set_default(gNetIf); netif_set_up(gNetIf);这里最关键的是 netif_add 的第二个函数指针参数 tivaif_init 和第三个参数 tcpip_input。tivaif_init 负责初始化硬件并填充 netif 的 output/linkoutput 回调tcpip_input 是 LwIP 在带操作系统模式下推荐的收包入口它会把收到的以太网帧放入一个消息队列由 tcpip_thread 统一处理。这种设计保证协议栈核心是单线程访问的避免多线程并发调用协议函数造成的数据竞争。4.4 sys_arch 层用 FreeRTOS 实现如果手动写 sys_arch需要实现的东西很少但每一样都得对应到 FreeRTOS 的机制LwIP 抽象FreeRTOS 实现说明sys_mutex_new/lock/unlock/freexSemaphoreCreateMutex / xSemaphoreTake / xSemaphoreGive保护共享数据sys_sem_new/signal/waitxSemaphoreCreateBinary / xSemaphoreGive / xSemaphoreTake事件同步sys_mbox_new/post/fetchxQueueCreate / xQueueSend / xQueueReceive传递指针消息sys_thread_newxTaskCreate创建 tcpip_thread 等sys_nowxTaskGetTickCount返回当前时间戳我建议直接使用社区里成熟的 sys_arch.c 文件做适量修改。一个容易忽视的细节是邮箱mbox队列长度。如果队列长度太小网络收包高峰期 sys_mbox_trypost 会失败直接导致丢包。我把 mbox 队列长度配置成 32配合 PBUF_POOL_SIZE 32实测小包高速收发时丢包率明显下降。4.5 两种 API 的取舍LwIP 用户态 API 有两种raw API 和 netconn API也包含基于 netconn 的 socket API。raw API 性能最高所有协议处理都在回调函数里完成但代码逻辑分散多线程之间要格外小心共享数据。netconn API 是面向连接的 API编程方式接近 socket每个连接可以独占一个线程阻塞等待数据这在多线程系统里写起来非常顺手。我这边的 tcp_server_task 就是标准 netconn 编程。主循环里 netconn_accept 等待客户端连接收到连接后进入一个子状态循环由任务栈上的局部变量保存连接句柄。每个客户端连接对应一个独立任务实例的话会更清晰但会增加线程数量我在这个项目里用的还是单任务管理多个连接轮询的方式。4.6 lwipopts.h 中的关键调校lwipopts.h 决定了 LwIP 的内存占用和行为特征。我在 repeatxdn 工程里做的关键配置如下宏取值说明NO_SYS0带操作系统模式LWIP_NETCONN1启用 netconn APILWIP_SOCKET0不使用 POSIX socket节省 ROMMEM_SIZE30 * 1024协议栈堆大小单位字节MEMP_NUM_NETCONN4同时打开的 netconn 数量PBUF_POOL_SIZE32PBUF 池数量影响收包吞吐TCP_MSS1460以太网最大报文段长度TCP_WND8 * TCP_MSSTCP 接收窗口提高吞吐LWIP_DHCP1启用 DHCP 客户端LWIP_IGMP1启用 IGMPUDP 组播需要TCP 接收窗口直接决定单连接吞吐上限。如果 TCP_WND 太小发送方的窗口受限吞吐就上不去。但窗口调大也会占用更多内存项目里 SRAM 有 256KB我给它留的余量是够的。实际使用中如果吞吐一直上不来可以优先检查 TCP_WND 和 PBUF_POOL_SIZE 这两个值。4.7 收发链路实测效果调试完协议栈之后我用两个方式做了验证。一是 UDP 高速小包测试通过上位机向开发板发送 200 字节的 UDP 包统计接收端收到的包数和校验错误数1000 包/秒的速率下基本无丢失。二是 TCP 单向吞吐测试用 iperf 工具跑板卡做 TCP server上位机做 client 发送 TCP 流实测吞吐稳定在 27Mbps 附近CPU 占用约 45%这个数值对 M4 内核跑 LwIP 来说是一个比较合理的水平。此时如果还想进一步提高吞吐通常的方向是加大 TCP_MSS、TCP_WND、PBUF 池数量同时降低中断处理延迟但实时性采集任务会受影响需要做平衡。5. 线程踩坑实录死锁、优先级反转、堆栈溢出5.1 案例一互斥量保护区内调用 netconn_write 导致死锁这个坑我印象很深。系统刚跑通网络功能时我总喜欢在任务里用一个共享互斥量保护一个全局缓冲区用来临时拼接多帧数据然后再调用 netconn_write 发送。结果板子运行几十秒后 TCP 连接必然断流tcp_server_task 卡死在发送路径上。后面用调试器挂上去看任务状态发现 tcp_server_task 阻塞在 netconn_write 调用里而 tcpip_thread 一直拿不到某个信号量。再结合栈回溯根因是典型的交叉等待我的任务拿着互斥量 A同时请求 LwIP 内部核心锁 Btcpip_thread 拿到了核心锁 B又需要访问由互斥量 A 保护的共享缓冲区。谁都不让谁死锁就出现了。解决方法很简单把所有需要拼接的临时数据在互斥量保护范围内拷贝到私有的发送缓冲区先释放互斥量 A再调用 netconn_write。原则就是不要在持有自己锁的时候去调用可能阻塞的协议栈 API。5.2 案例二两个任务互相等待的经典死锁另一个死锁发生在两个任务各持有一把互斥量后又去等待对方持有的资源。任务 A 拿 mutexA 后等待队列 queueB任务 B 拿 mutexB 后等待队列 queueA。两个队列都有数据但都同时被对方阻塞系统整体进入僵死状态。这种死锁排查起来并不轻松。我养成的一个习惯是所有 xSemaphoreTake 和 xQueueReceive 的地方一律加超时时间绝不使用无限等待。比如if (xQueueReceive(xCmdQueue, cmd, pdMS_TO_TICKS(100)) pdTRUE) { // 处理指令 }超时返回之后即使逻辑上没有完全处理完也好过整个任务卡死。系统设计上保证主流程在一个超时周期内还能继续跑监控任务就能发现问题。5.3 优先级反转低优先级任务拖慢高优先级任务优先级反转是嵌入式多线程系统里一个绕不开的话题。现象是高优先级任务在等一把被低优先级任务持有的互斥量而中等优先级任务抢占了低优先级任务的 CPU导致低优先级任务迟迟释放不了锁高优先级任务实际等待时间远大于预期。FreeRTOS 互斥量默认实现了优先级继承机制当高优先级任务等待一把互斥量时当前持有该互斥量的任务会被临时提升到高优先级从而加快释放。这个机制能解决大部分场景。但这里有个使用提醒共享临界区要尽量短不能把一个耗时很长的操作放在互斥量里面。否则即使有优先级继承等待锁的任务依然会被长时间拖住。5.4 堆栈溢出检测让问题在上线前现形任务栈配小是嵌入式开发最常见的内存事故来源之一。症状千奇百怪可能是某个变量的值突然被改写可能是函数返回地址被覆盖后跑飞也可能是在不同优化级别下表现完全不同。FreeRTOS 提供了两级栈溢出检测。configCHECK_FOR_STACK_OVERFLOW 设为 1只在任务切换时检查任务栈指针本文还有配套的精品资源点击获取
返回列表