网站营销网,没有客源怎么找客源,个人网站免费申请注册,哪家建设公司宣传搞得好目录 1. 简介2. RTOS设置#xff08;1#xff09;分配内存#xff08;2#xff09;查看任务剩余空间#xff08;3#xff09;使用osDelay 3. 队列的使用#xff08;1#xff09;创建队列#xff08;1#xff09;直接传值和指针传值#xff08;2#xff09;发送/接收… 目录 1. 简介2. RTOS设置1分配内存2查看任务剩余空间3使用osDelay 3. 队列的使用1创建队列1直接传值和指针传值2发送/接收等待时间3不要在硬件中断发送队列 4. 数据传递和共享1尽量用全局常量代替函数指针传参2同一资源需要被多个线程访问的两种方法 5. 开发调试1修改任务名称前备份代码否则都会被删除2keil的字体和编码vscode的使用3DMA串口日志4文档放在项目文件夹外面以免被cube删除 6. LCD乱码问题 1. 简介
之前都是用CubeMXKeil裸机开发STM32最近第一次启用了FreeRTOS用它可以实现多线程但是如果写代码不严谨单片机容易卡死非常头疼。
2. RTOS设置
1分配内存
config parameters选项卡里有个totoal heap size意思大概是freertos占用的总内存这个数值的默认值是比较小的后面线程和队列加多了可能会不够可以手动增加。我设置成的8kBSTM32f103rct6有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.statusosEventMessage){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.statusosEventMessage){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里面修改任务名称和入口函数前千万记得备份代码否则重新生成代码之后之前写的代码都会被擦除。
2keil的字体和编码vscode的使用
在菜单栏Edit最下面打开configuration窗口设置编码和字体 Editor选项卡里面编码设置有两个选择
①Courier字体方案字体易读编码改成UTF-8这是为了适配Courier字体。同时为了让cube适配UTF-8需要添加一个系统环境变量变量名称JAVA_TOOL_OPTIONS变量值-Dfile.encodingUTF-8。如果不加环境变量cube会把中文注释搞成乱码。 ②Keil默认字体方案较难阅读保持GB2312编码也不用设置全局变量了。
同时勾选右边的“Automatic reload of externally modified files”避免每次都提示要不要重新加载 如果选Courier字体方案还需要在Colors Fonts选项卡设置 开发过程中可以用vscode打开项目文件夹在里面写代码再在keil里面编译下载。VSC的代码辅助比Keil好多了而且深色主题更护眼。
3DMA串口日志
启用日志打印串口的发送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后问题就消失了。 应该是代码地址和参数写入地址冲突了。