当前位置: 首页 > news >正文

STM32裸机开发转FreeRTOS教程

目录

  • 1. 简介
  • 2. RTOS设置
    • (1)分配内存
    • (2)查看任务剩余空间
    • (3)使用osDelay
  • 3. 队列的使用
    • (1)创建队列
    • (1)直接传值和指针传值
    • (2)发送/接收等待时间
    • (3)不要在硬件中断发送队列
  • 4. 数据传递和共享
    • (1)尽量用全局常量代替函数指针传参
    • (2)同一资源需要被多个线程访问的两种方法
  • 5. 开发调试
    • (1)修改任务名称前备份代码,否则都会被删除
    • (2)keil的字体和编码,vscode的使用
    • (3)DMA串口日志
    • (4)文档放在项目文件夹外面,以免被cube删除
  • 6. LCD乱码问题

1. 简介

之前都是用CubeMX+Keil裸机开发STM32,最近第一次启用了FreeRTOS,用它可以实现多线程,但是如果写代码不严谨,单片机容易卡死,非常头疼。

2. RTOS设置

(1)分配内存

config parameters选项卡里,有个totoal heap size,意思大概是freertos占用的总内存,这个数值的默认值是比较小的,后面线程和队列加多了可能会不够,可以手动增加。我设置成的8kB,STM32f103rct6有48kB的RAM,是很充足的:
在这里插入图片描述
可以在heap usage里面看到使用情况,"still available"和"used"加起来正好是上面设置的总大小:
在这里插入图片描述
还有个minimal stack size参数,这个相当于一个底线,分配给每个任务的空间大小不能小于这个值。注意这个是用Word(字)作单位,32位单片机的一个字占4字节。
在这里插入图片描述
下图是设置任务的界面,每个任务默认给了128个字(半个kB)这个大小是比较适中的, 足够大部分常规任务的应用,也不会太占用单片机内存。
在这里插入图片描述
如果想节省内存,可以把前面minimal stack size设为64 Words(不允许更小了),然后把那些变量比较少的线程空间大小设置为64 Word。调试期间可以用随后介绍的方法查看线程空间够不够。

(2)查看任务剩余空间

为了用uxTaskGetStackHighWaterMark()查看任务剩余空间,需要在cubemx中开启它对应的使能,如下图。
在FreeRTOSConfig.h里面改会和cubemx冲突。
在这里插入图片描述

(3)使用osDelay

所有线程(除了IDLE)的死循环里面都需要至少加个osDelay(1),否则容易卡死。
在cmsis_os.c里查看osDelay的函数体,可见它本质上就是vTaskDelay:
在这里插入图片描述

3. 队列的使用

(1)创建队列

在Cube的Tasks和Queues选项卡,添加队列:
在这里插入图片描述
Queue Size是队列长度,设置的别让队列溢出就行,可以用osMessageAvailableSpace()查询队列剩余长度。
Item Size是每个元素的长度,这个后面会讲。

生成代码之后,cube会在freertos.c里创建一个队列句柄:

osMessageQId ledQueHandle;

cube里面设置的item size,代表每个队列数据占用多少字节。但由于c语言属于初级语言,不能给函数传递不定长度的参数,添加队列元素的函数是:

osStatus osMessagePut (osMessageQId queue_id, uint32_t info, uint32_t millisec)

它的第二个参数info,始终是uint32_t类型的,占4个字节。那如何传递不同长度的数据呢?答案就是“指针传值”。

如果要传递的数据可以用4个字节表示,就用“直接传值”方法,item size设为4;如果单次数据量超过了4字节,可以把数据放在数组或结构体里面,用指针传值方法,item size为被传递的数组或结构体的大小。

(1)直接传值和指针传值

直接传值示例:

// 发送线程
void Task_Send(void const *arg)
{...int cmd;for(;;){...osMessagePut(ledQueHandle, (uint32_t)cmd, osWaitForever);// 参数是int或float等数值...}
}
// 接收线程:
void Task_Receive(void const * argument)
{/* USER CODE BEGIN Task_LED */osEvent evt;int cmd;for(;;)	{evt = osMessageGet(ledQueHandle,0); if(evt.status==osEventMessage){cmd=(int)evt.value.v; ...}osDelay(1);		}
}

指针传值示例:

// 发送线程
void Task_Send(void const *arg)
{...int cmd[4];//传递数组,队列的item size = 16// MyStructType cmd;//传递结构体,需要预先定义MyStructType类型,队列的item size = sizeof(MyStructType)for(;;){...osMessagePut(ledQueHandle, (uint32_t)cmd, osWaitForever);//传递数组指针
//		osMessagePut(ledQueHandle, (uint32_t)&cmd, osWaitForever);//传递结构体指针...}
}
// 接收线程:
void Task_Receive(void const * argument)
{/* USER CODE BEGIN Task_LED */osEvent evt;int* pcmd;//接收指针,需要和发送的指针类型一致
//	MyStructType * pcmd;for(;;)	{evt = osMessageGet(ledQueHandle,0); if(evt.status==osEventMessage){pcmd = (int*)evt.value.p;//需要强制转型
//			pcmd = (MyStructType*)evt.value.p;			...}osDelay(1);}
}

由以上可见,直接传值就是把要传送数据直接放到队列里,接收的时候用evt.value.v;指针传值是把被传递数据的指针放在队列里,接收的时候用evt.value.p。

(2)发送/接收等待时间

osMessagePut()和osMessageGet()的最后一个参数都是等待时间,发送函数的可以设置成osWaitForever,表示阻塞线程直到把数据放入队列;
接收函数的等待时间最好设置为0,同时在循环里加个osDelay()释放主控资源。设置成osWaitForever会卡死。

(3)不要在硬件中断发送队列

cmsis_os.h开头注释有:
在这里插入图片描述
意思是osMessagePut可以放中断,但是经过实测,在硬件中断中调用osMessagePut()函数会卡死。
所以,只能在操作系统函数(线程,定时器)操作队列,中断函数传值可以用全局变量。

4. 数据传递和共享

(1)尽量用全局常量代替函数指针传参

用指针传递维度高、数据量大的变量,容易导致各种错误。可以定义成全局变量,在函数里直接用。
如果全局变量需要被多个文件调用,可以先在.c文件定义,再在.h文件用 extern 声明一下,这样其他的C文件只要#include这个.h文件就能用全局变量了。

(2)同一资源需要被多个线程访问的两种方法

①互斥锁:在读写函数里面,先获取Mutex,操作之后再释放Mutex。
②队列:其他线程请求压入队列,再由资源访问线程接收处理。如果是读取操作,可以在队列元素里放个接收变量的指针(没验证过!)
经过测试,即便是4字节的变量,也要避免不同线程直接访问,不然会出错。

5. 开发调试

(1)修改任务名称前备份代码,否则都会被删除

在cube里面修改任务名称和入口函数前千万记得备份代码,否则重新生成代码之后,之前写的代码都会被擦除。
在这里插入图片描述

(2)keil的字体和编码,vscode的使用

在菜单栏Edit最下面打开configuration窗口,设置编码和字体:
在这里插入图片描述
在这里插入图片描述
Editor选项卡里面,编码设置有两个选择:

①Courier字体方案(字体易读):编码改成UTF-8,这是为了适配Courier字体。同时为了让cube适配UTF-8,需要添加一个系统环境变量,变量名称:JAVA_TOOL_OPTIONS,变量值:-Dfile.encoding=UTF-8。如果不加环境变量,cube会把中文注释搞成乱码。
②Keil默认字体方案(较难阅读):保持GB2312编码,也不用设置全局变量了。

同时勾选右边的“Automatic reload of externally modified files”,避免每次都提示要不要重新加载:
在这里插入图片描述
如果选Courier字体方案,还需要在Colors & Fonts选项卡设置:
在这里插入图片描述
开发过程中,可以用vscode打开项目文件夹,在里面写代码,再在keil里面编译下载。VSC的代码辅助比Keil好多了,而且深色主题更护眼。

(3)DMA串口日志

启用日志打印串口的发送DMA可以最小的干预主程序的运行。方法是在cube里面添加一个tx的dma通道,DMA参数默认
在这里插入图片描述
在NVIC页面里面,可以把DMA的中断关上,因为日志打印要求不高,不需要在DMA终端里面判断数据有没有发送完:
在这里插入图片描述
代码里面,可以先定义个全局数组作为发送缓冲区,在函数里用sprintf格式化字符串,先调用DMAStop,再发送,不然只能发送一次:

char uart_buf[50]; // 日志发送缓冲区
void Timer_Callback() // 要发送日志的函数,例如软件定时器
{sprintf(uart_buf,"%.2f %.2f %.2f %.2f\r\n",Mot.spd_sv, Mot.spd_pv, Mot.pos_sv, Mot.pos_pv);HAL_UART_DMAStop(&huart3);HAL_UART_Transmit_DMA(&huart3,uart_buf,strlen(uart_buf));
}

这个方法适用于周期循环发送日志的情况,发送周期基本上大于一次发送用时就行了,偶尔一次数据覆盖也没关系。如果日志量比较大,可以提高串口波特率。

(4)文档放在项目文件夹外面,以免被cube删除

如果要在项目里新建一个文件夹用来放文档,需要用全英文,避免特殊符号,以防被cube搞坏。或者把文档放项目文件夹外面。

6. LCD乱码问题

调试期间发现写入数据到芯片内部Flash之后,显示屏会出现字符错误。
解决方法是把把Flash写入地址往后移,从0x0800A000移到0x0800B000后,问题就消失了。
应该是代码地址和参数写入地址冲突了。

相关文章:

STM32裸机开发转FreeRTOS教程

目录 1. 简介2. RTOS设置(1)分配内存(2)查看任务剩余空间(3)使用osDelay 3. 队列的使用(1)创建队列(1)直接传值和指针传值(2)发送/接收…...

FreeSWITCH dialplan/default.xml 之释疑

准备花时间好好研究下,一直都是一知半解 sip_looped_call 通俗地说,就是自己呼叫自己 查文档,是这样讲的:如果调用已通过 ACL 以外的方式进行身份验证,并且当前请求 IP/port 与配置文件 IP/port 匹配,那…...

lambda用法及其原理

目录 lambda形式lambda用法1.sort降序2.swap3.捕捉列表 习题解题 lambda形式 [capture-list](parameters)->return type{function boby}[capture-list]:[捕捉列表]用于捕捉函数外的参数,可以为空,但不能省略;(parameters) &am…...

Go Ebiten随机迷宫生成示例

引言 迷宫生成是计算机科学中一个经典的问题,常用于算法教学和游戏开发。本文将介绍如何使用 Go 语言和 Ebiten 游戏引擎实现一个基于深度优先搜索(DFS)的随机迷宫生成算法,并通过可视化的方式展示迷宫的生成过程。 技术栈 Go …...

前端学习DAY31(子元素溢出父元素)

.box1{width: 200px;height: 200px;background-color: chocolate;} 子元素是在父元素的内容区中排列的,如果子元素的大小超过了父元素,则子元素会从 父元素中溢出,使用overflow属性设置父元素如何处理溢出的子元素 可选值:visible…...

『SQLite』表的创建、修改和删除

本节摘要:主要讲述SQLite中创建、删除、修改表等操作。 创建表 CREATE TABLE 语句来创建表。 修改表 ALTER TABLE 语句来修改表名称、已有表字段,或者新增字段。 删除表 DROP TABLE 语句用来删除表. 注意: 上述内容详细讲解见文章&#…...

可持久化数据结构-线段树(主席树)

可持久化数据结构-线段树(主席树) (与可持久化字典树差不多) 概念:可持久化线段树是基本线段树的一个简单拓展, 是使用函数式编程思想的线段树; 作用: 可以存下来数据结构的所有历史版本 特点: 拓扑结构…...

如何利用PHP爬虫按关键字搜索淘宝商品

在当今的电商时代,获取淘宝商品信息对于市场研究、价格监控和竞争分析等方面具有重要意义。手动搜索和整理大量商品信息不仅耗时耗力,而且容易出错。幸运的是,PHP爬虫技术为我们提供了一种高效、自动化的方式来按关键字搜索淘宝商品。本文将详…...

GitHub - riscv-software-src/riscv-isa-sim: Spike, a RISC-V ISA Simulator

GitHub - riscv-software-src/riscv-isa-sim: Spike, a RISC-V ISA Simulator 操作手册 $ apt-get install device-tree-compiler libboost-regex-dev libboost-system-dev $ mkdir build $ cd build $ ../configure --prefix$RISCV $ make $ [sudo] make install 具体安装 …...

ubuntu开机启动服务

需求背景: 需要监控日志,每次都是手动启动 nohup ./prometheus >/dev/null & nohub ./node_exporter >/dev/null & 需求目标: 重启后系统自动启动服务...

电子电气架构 --- 设计车载充电机的关键考虑因素

我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 简单,单纯,喜欢独处,独来独往,不易合同频过着接地气的生活,除了生存温饱问题之外,没有什么过多的欲望,表面看起来很高冷,内心热情,如果你身…...

2025_0105_生活记录

3号去内蒙看了流星雨。还记得上次看流星的时间是2018年,也是冬天,大家在雁栖湖校区的操场上仰望星空。那个时候幸运的看到了一颗流星,便迅速地在心里许愿。这次看到了三颗流星,我也许了愿,希望实现。 24年走过了十多个…...

电池管理系统(BMS)架构详细解析:原理与器件选型指南

BMS(电池管理系统)架构详细讲解 从你提供的BMS(Battery Management System)架构图来看,主要涉及到电池监控模块、通信模块、功率控制模块等部分。下面我将详细讲解该架构的各个功能模块及其工作原理。 1. 电池管理核…...

用JAVA编写一个简单的小游戏

用Java语言编写一个简单的小游戏。这里是一个非常基础的猜数字小游戏的代码示例。在这个游戏中,程序会随机选择一个1到100之间的整数,玩家需要猜测这个数字是什么。每次猜测后,程序会告诉玩家他们猜的数字是太高了、太低了还是正确。 impor…...

【SpringSecurity】二、自定义页面前后端分离

文章目录 1、用户认证流程AuthenticationSuccessHandler AuthenticationFailureHandlerSecurityFilterChain配置用户认证信息 2、会话并发处理2.1、实现处理器接口2.2、SecurityFilterChain配置 1、用户认证流程 AuthenticationSuccessHandler AuthenticationFailureHandler …...

小兔鲜儿:头部区域的logo,导航,搜索,购物车

头部:logo ,导航,搜索,购物车 头部总体布局: 设置好上下外边距以及总体高度, flex布局让总体一行排列 logo: logo考虑搜索引擎优化,所以要使用 h1中包裹 a 标签,a 里边写内容(到时候…...

什么是VLAN?

VLAN(Virtual Local Area Network,虚拟局域网)是一种将物理局域网划分成多个逻辑上独立的虚拟网络的技术。VLAN不依赖于设备的物理位置,而是通过逻辑划分,将局域网内的设备虚拟地组织到同一组。这种技术允许网络管理员…...

WPS计算机二级•数据查找分析

听说这里是目录哦 通配符🌌问号(?)星号(*)波形符(~) 排序🌠数字按大小排序以当前选定区域排序以扩展选定区域排序 文字按首字母排序 快速筛选分类数据☄️文字筛选数字筛选颜色筛选…...

计算机网络 (28)虚拟专用网VPN

前言 虚拟专用网络(VPN)是一种在公共网络上建立私有网络连接的技术,它允许远程用户通过加密通道访问内部网络资源,实现远程办公和安全通信。 一、基本概念 定义:VPN是一种通过公共网络(如互联网&#xff09…...

【Python学习(七)——序列、列表、元组、range、字符串、字典、集合、可变类型不可变类型】

Python学习(七)——序列、列表、元组、range、字符串、字典、集合、可变类型&不可变类型 本文介绍了序列、列表、元组、range、字符串、字典、集合、可变类型&不可变类型,仅作为本人学习时记录,感兴趣的初学者可以一起看…...

如何在5分钟内实现抖音批量下载?douyin-downloader开源工具完整指南

如何在5分钟内实现抖音批量下载?douyin-downloader开源工具完整指南 【免费下载链接】douyin-downloader A practical Douyin downloader for both single-item and profile batch downloads, with progress display, retries, SQLite deduplication, and browser f…...

告别手动大气校正!用Google Earth Engine直接调用Landsat C2 L2地表反射率数据的完整指南

告别手动大气校正!用Google Earth Engine直接调用Landsat C2 L2地表反射率数据的完整指南 如果你曾经为了计算NDVI或地表温度而花费数小时处理原始Landsat数据,那么这篇文章将彻底改变你的工作流程。想象一下:无需下载数十GB的原始数据&#…...

D3KeyHelper:暗黑破坏神3自动化战斗系统架构解析与实践指南

D3KeyHelper:暗黑破坏神3自动化战斗系统架构解析与实践指南 【免费下载链接】D3keyHelper D3KeyHelper是一个有图形界面,可自定义配置的暗黑3鼠标宏工具。 项目地址: https://gitcode.com/gh_mirrors/d3/D3keyHelper D3KeyHelper是一款基于AutoHo…...

学生强力去污洗衣液推荐:校服重污轻松洗,高性价比温和不伤衣品牌测评

搜索 “学生强力去污洗衣液” 的用户,核心是住校生、家长,主攻校服油渍、汗渍、泥渍、饭渍等顽固污渍,追求洗得干净、不伤衣、温和安全、性价比高。据中国洗涤用品工业协会 2026 数据,38.1% 洗衣液活性物不达标,62% 学…...

注意力机制模块:顶会 CVPR 2025 最新注意力:Focused Linear Attention 替换传统 Softmax 注意力

⚠️ 重要声明:本文部分核心理论内容(Focused Linear Attention的聚焦映射函数和秩恢复模块)源自清华大学黄高老师团队于ICCV 2023发表的论文 FLatten Transformer: Vision Transformer using Focused Linear Attention(论文链接:https://arxiv.org/pdf/2308.00442,代码:…...

Laravel 7.x 核心特性全解析

好的,Laravel 7.x 引入了多项重要的新特性和改进,主要集中在提升开发效率、增强路由功能、改进测试工具等方面。以下是其主要特性概述:路由模型绑定增强自定义键名:在路由定义中,除了 id,现在可以明确指定模…...

Cortex-A7 MPCore 架构

鉴于学习的硬件使用的是Cortex-A7架构,本章学习该架构的相关知识。了解了 Cortex-A7 架构以后有利于我们后面的学习,因为后面有很多例程涉及到 Cortex-A7 架构方面的知识,比如处理器模型、 Cortex-A7 寄存器组等等。Cortex-A7 MPCore 简介Cor…...

鸿蒙三方库实战指南:从安装到核心功能的深度解析

其实昨天那个没有写完,还有少部分没有进行分享,等有机会了我再将下文分享一下摘要​随着 HarmonyOS NEXT(API 12)的正式商用,原生三方库生态已成为提升鸿蒙应用开发效率的核心支撑。本文针对UI 组件、网络通信、数据库…...

保姆级教程:清音听真语音识别系统环境配置与API调用完整指南

保姆级教程:清音听真语音识别系统环境配置与API调用完整指南 1. 系统介绍与核心优势 清音听真语音识别系统搭载了Qwen3-ASR-1.7B旗舰引擎,相比前代0.6B版本有了质的飞跃。这个系统专为处理各种复杂语音场景而设计,无论是嘈杂环境录音、专业…...

Phi-4-mini-reasoning环境配置:CUDA版本兼容性检查与nvidia-smi验证

Phi-4-mini-reasoning环境配置:CUDA版本兼容性检查与nvidia-smi验证 1. 环境准备与CUDA兼容性检查 在部署Phi-4-mini-reasoning模型前,确保您的GPU环境满足基本要求是至关重要的第一步。这个轻量级开源模型虽然对硬件要求相对友好,但仍需要…...