STM32F103/F407的FreeRTOS移植
目录
FreeRTOS简介
官方网站
源码
移植步骤(以STM32F103为例)
添加FreeRTOS源码至项目文件
-
官网下载源码,并解压。Source文件夹中的文件即为我们需要移植的文件,其中portable文件夹里都是需要根据平台修改或删减的文件。
-
打开一个工程的源码目录,新建文件夹FreeRTOS,将源码中的文件复制到此文件夹中。
-
在portable文件夹中只保留GCC/ARM_CM3(使用的STM32CubeIDE开发环境)与MemMang两个文件夹。如果是使用的keil开发环境,那么需要保留RVDS与MemMang两个文件夹。
-
根据需要在MemMang文件夹中选择合适的堆分配算法文件。(一般STM32F103选择heap_4.c文件)。如果使用Makefile文件组织编译过程,那么需要排除其他的算法文件。
SRC := $(filter-out Source/FreeRTOS/portable/MemMang/heap_1.c, $(SRC))
SRC := $(filter-out Source/FreeRTOS/portable/MemMang/heap_2.c, $(SRC))
SRC := $(filter-out Source/FreeRTOS/portable/MemMang/heap_3.c, $(SRC))
SRC := $(filter-out Source/FreeRTOS/portable/MemMang/heap_5.c, $(SRC))
- 添加在工程中添加FreeRTOS头文件。
#include directory of FreeRTOS
INCLUDE_DIR += -ISource/FreeRTOS/include
INCLUDE_DIR += -ISource/FreeRTOS/portable/GCC/ARM_CM3
- 从下载源码的Demo文件夹中选择一个适合的工程,并从工程的源码中复制FreeRTOSConfig.h到FreeRTOS/include文件夹。由于我们采用的是STM32CubeIDE,因此选择CORTEX_STM32F100_Atollic工程中的配置文件。
修改FreeRTOSConfig.h文件
根据项目工程需要修改FreeRTOSConfig.h文件,这是移植过程中最为重要的一步,需要根据需要对配置文件进行修改。以下提供了一个修改好的模板:
/*
* FreeRTOS Kernel V10.4.1
* Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
* http://www.FreeRTOS.org
* http://aws.amazon.com/freertos
*
* 1 tab == 4 spaces!
*/
/* The following #error directive is to remind users that a batch file must be
* executed prior to this project being built. The batch file *cannot* be
* executed from within CCS4! Once it has been executed, re-open or refresh
* the CCS4 project and remove the #error line below.
*/
//#error Ensure CreateProjectDirectoryStructure.bat has been executed before building. See comment immediately above.
#ifndef FREERTOS_CONFIG_H
#define FREERTOS_CONFIG_H
#include "stm32f10x_conf.h"
#include "stm32f10x.h"
/*-----------------------------------------------------------
* Application specific definitions.
*
* These definitions should be adjusted for your particular hardware and
* application requirements.
*
* THESE PARAMETERS ARE DESCRIBED WITHIN THE 'CONFIGURATION' SECTION OF THE
* FreeRTOS API DOCUMENTATION AVAILABLE ON THE FreeRTOS.org WEB SITE.
*
* See http://www.freertos.org/a00110.html
*----------------------------------------------------------*/
/*
* FreeRTOS 支持的调度方式FreeRTOS 操作系统支持三种调度方式:
* 抢占式调度,时间片调度和合作式调度。
* 实际应用主要是抢占式调度和时间片调度,合作式调度用到的很少。
*
* 抢占式调度:
* 每个任务都有不同的优先级,任务会一直运行直到被高优先级任务抢占或者遇到阻塞式的 API 函数,比如 vTaskDelay。
*
* 时间片调度:
* 每个任务都有相同的优先级,任务会运行固定的时间片个数或者遇到阻塞式的 API 函数,比如vTaskDelay,才会执行同优先级任务之间的任务切换。
* 在 FreeRTOS 操作系统中只有同优先级任务才会使用时间片调度
*/
/**
* 配置为 1, 使能抢占式调度器
* 配置为 0, 使能合作式调度器
*/
#define configUSE_PREEMPTION 1
/**
* 只能使用编译器提供的RAM创建RTOS对象
*/
#define configSUPPORT_STATIC_ALLOCATION 0
/**
* 使用从FreeRTOS堆自动分配的RAM创建RTOS对象
*/
#define configSUPPORT_DYNAMIC_ALLOCATION 1
/**
* 钩子函数的主要功能是用于函数的扩展,用户可以根据自己的需要往里面添加相关的测试函数。
*/
//使能空闲任务的钩子函数
#define configUSE_IDLE_HOOK 0
//使能滴答定时器中断里面执行的钩子函数
#define configUSE_TICK_HOOK 0
/**
* 此参数用于定义 CPU 的主频,单位 Hz
*/
#define configCPU_CLOCK_HZ ( SystemCoreClock )
/**
* 此参数用于定义系统时钟节拍数,单位 Hz,一般取 1000Hz 即可
*/
#define configTICK_RATE_HZ ((TickType_t)1000)
/**
* 此参数用于定义可供用户使用的最大优先级数,
* 如果这个定义的是 5,那么用户可以使用的优先级号是 0,1,2,3,4,不包含 5
*/
#define configMAX_PRIORITIES ( 32 )
/**
* 此参数用于定义空闲任务的栈空间大小,单位字,即 4 字节
*/
#define configMINIMAL_STACK_SIZE ((uint16_t)128)
/**
* 定义堆大小,FreeRTOS 内核,用户动态内存申请,任务栈,任务创建,信号量创建,消息队列创建等都需要用这个空间
*/
#define configTOTAL_HEAP_SIZE ((size_t)10*1024)
/**
* 定义任务名最大的字符数,末尾的结束符 '\0'也要计算在内
*/
#define configMAX_TASK_NAME_LEN ( 16 )
/**
* 系统时钟节拍计数使用 TickType_t 数据类型定义的,
* 如果用户使能了宏定义 configUSE_16_BIT_TICKS,那么 TickType_t 定义的就是 16 位无符号数,
* 如果没有使能,那么 TickType_t 定义的就是 32 位无符号数。
* 对于 32 位架构的处理器,一定要禁止此宏定义,即设置此宏定义数值为 0 即可。
* 而 16 位无符号数类型主要用于 8 位和 16 位架构的处理器。
*/
#define configUSE_16_BIT_TICKS 0
/**
* 使能互斥信号量
*/
#define configUSE_MUTEXES 1
/**
* 通过此定义来设置可以注册的信号量和消息队列个数。
*/
#define configQUEUE_REGISTRY_SIZE 8
/* 某些运行FreeRTOS的硬件有两种方法选择下一个要执行的任务:
* 通用方法和特定于硬件的方法(以下简称“特殊方法”)。
*
* 通用方法:
* 1.configUSE_PORT_OPTIMISED_TASK_SELECTION 为 0 或者硬件不支持这种特殊方法。
* 2.可以用于所有FreeRTOS支持的硬件
* 3.完全用C实现,效率略低于特殊方法。
* 4.不强制要求限制最大可用优先级数目
* 特殊方法:
* 1.必须将configUSE_PORT_OPTIMISED_TASK_SELECTION设置为1
* 2.依赖一个或多个特定架构的汇编指令(一般是类似计算前导零[CLZ]指令)
* 3.比通用方法更高效
* 4.一般强制限定最大可用优先级数目为32
* 一般是硬件计算前导零指令,如果所使用的,MCU没有这些硬件指令的话此宏应该设置为0
*/
/**
* 此配置用于优化优先级列表中要执行的最高优先级任务的算法。对 CM 内核的移植文件,默认已经在文件 portmacro.h 文件中使能。
*/
#define configUSE_PORT_OPTIMISED_TASK_SELECTION 1
/* Co-routine definitions. */
/**
* 使能合作式调度相关函数
*/
#define configUSE_CO_ROUTINES 0
/**
* 此参数用于定义可供用户使用的最大的合作式任务优先级数
*/
#define configMAX_CO_ROUTINE_PRIORITIES ( 2 )
/* Software timer definitions. */
//禁能软件定时器
#define configUSE_TIMERS 0
//配置软件定时器任务的优先级
#define configTIMER_TASK_PRIORITY ( 3 )
//配置软件定时器命令队列的长度
#define configTIMER_QUEUE_LENGTH 5
//配置软件定时器任务的栈空间大小
#define configTIMER_TASK_STACK_DEPTH (configMINIMAL_STACK_SIZE)
/* Set the following definitions to 1 to include the API function, or zero
to exclude the API function. */
#define INCLUDE_vTaskPrioritySet 1
#define INCLUDE_uxTaskPriorityGet 1
#define INCLUDE_vTaskDelete 1
#define INCLUDE_vTaskCleanUpResources 1
#define INCLUDE_vTaskSuspend 1
#define INCLUDE_vTaskDelayUntil 1
#define INCLUDE_vTaskDelay 1
/*
* Cortex-M内核使用8bit来配置优先级,但是STM32只使用了高4bit,数值越小,优先级越高。
* 在往寄存器里面写数值配置的时候,是按照8bit来写的,所以真正写的时候需要经过转换,公式为:
* ((priority << (8 - __NVIC_PRIO_BITS)) & 0xff),其中的priority就是我们配置的真正的优先级
*/
//Use the system definition, if there is one
#ifdef __NVIC_PRIO_BITS
#define configPRIO_BITS __NVIC_PRIO_BITS
#else
#define configPRIO_BITS 4 /* 15 priority levels */
#endif
#define configLIBRARY_LOWEST_INTERRUPT_PRIORITY 15
/**
* SysTick和PendSV 都是配置为了最低优先级,即0xf 。这样可以提高系统的实时响应能力,即其他的外部中断可以及时的得到响应。
*
*/
/* The lowest priority. */
#define configKERNEL_INTERRUPT_PRIORITY ( configLIBRARY_LOWEST_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )
/*
* 用于配置STM32的特殊寄存器basepri寄存器的值,用于屏蔽中断,当大于basepri值的优先级的中断将被全部屏蔽。basepri只有4bit有效,
* 默认只为0,即全部中断都没有被屏蔽。configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY配置为:5,意思就是中断优先级大于5的中断都被屏蔽。
* 当把配置好的优先级写到寄存器的时候,是按照8bit来写的,所以真正写的时候需要经过转换,公式为:
* ((priority << (8 - __NVIC_PRIO_BITS)) & 0xff),其中的priority就是我们配置的真正的优先级。经过这个公式之后得到的是下面的这个宏:
* configMAX_SYSCALL_INTERRUPT_PRIORITY
*
* 在FreeRTOS中,关中断是通过配置basepri寄存器来实现的,关掉的中断由配置的basepri的值决定,
* 小于basepri值的中断FreeRTOS是关不掉的,这样做的好处是可以系统设计者可以人为的控制那些非常重要的中断不能被关闭,在紧要的关头必须被响应。
* 而在UCOS中,关中断是通过控制PRIMASK来实现的,PRIMASK是一个单1的二进制位,写1则除能除了NMI和硬 fault的所有中断。
* 当UCOS关闭中断之后,即使是你在系统中设计的非常紧急的中断来了都不能马上响应,这加大了中断延迟的时间,如果是性命攸关的场合,那后果估计挺严重。
* 相比UCOS的关中断的设计,FreeRTOS的设计则显得人性化很多。
*/
#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 5
/* Priority 5, or 95 as only the top four bits are implemented. */
/* !!!! configMAX_SYSCALL_INTERRUPT_PRIORITY must not be set to zero !!!!
See http://www.FreeRTOS.org/RTOS-Cortex-M3-M4.html. */
#define configMAX_SYSCALL_INTERRUPT_PRIORITY ( configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )
#define configASSERT( x ) if( ( x ) == 0 ) { taskDISABLE_INTERRUPTS(); for( ;; ); }
/**
* 在 FreeRTOS 的移植文件 port.c 中有用到 SVC 中断的 0 号系统服务,即 SVC 0。此中断在 FreeRTOS中仅执行一次, 用于启动第一个要执行的任务。
* 另外, 由于 FreeRTOS 没有配置 SVC 的中断优先级,默认没有配置的情况下, SVC 中断的优先级就是最高的 0。
*/
#define vPortSVCHandler SVC_Handler
/**
* SysTick和PendSV 都是配置为了最低优先级
*/
#define xPortPendSVHandler PendSV_Handler
#define xPortSysTickHandler SysTick_Handler
#endif /* FREERTOS_CONFIG_H */
- 修改stm32f10x_it.c文件,注释void SVC_Handler(void), void PendSV_Handler(void), void SysTick_Handler(void)三个函数。因为这三个函数已经在port.c中实现了,并且在配置文件中通过宏定义的方式对应起来(也可以在启动文件中修改相应的中断向量名)。
/**
* 在 FreeRTOS 的移植文件 port.c 中有用到 SVC 中断的 0 号系统服务,即 SVC 0。此中断在 FreeRTOS中仅执行一次, 用于启动第一个要执行的任务。
* 另外, 由于 FreeRTOS 没有配置 SVC 的中断优先级,默认没有配置的情况下, SVC 中断的优先级就是最高的 0。
*/
#define vPortSVCHandler SVC_Handler
/**
* SysTick和PendSV 都是配置为了最低优先级
*/
#define xPortPendSVHandler PendSV_Handler
#define xPortSysTickHandler SysTick_Handler
- 配置MCU的中断优先级。移植了FreeRTOS的MCU一般采用第四中断优先级分组,即全部的四位都用来配置抢占优先级(共16个抢占式中断优先级)。
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
修改延时函数
延时函数在STM32单片机开发的项目中有广泛的应用,微秒级延时在一些时间要求严格的场景下(例如软件模拟I2C通讯)是必不可少的。由于FreeRTOS默认采用了system tick作为时间片分配的时基定时器,可能与利用system tick设计的延时函数出现冲突。再加上FreeRTOS提供的延时函数void vTaskDelay( const TickType_t xTicksToDelay )最小的延时时间等于FreeRTOS的tick时间(一般设置为1ms),因此需要重新设计一套不基于system tick的微秒级延时函数。利用CM3/4内核中的数据观察点与跟踪(DWT)寄存器,可以在不占用硬件外设定时器的情况下实现微秒级的精准延时。延时函数的设计与实现可以参考笔者的《STM32开发项目:微秒级的精准延时》这篇博文,以下给出核心代码实现:
// 0xE000EDFC DEMCR RW Debug Exception and Monitor Control Register.
#define DEMCR ( *(__IO uint32_t *)0xE000EDFC )
#define TRCENA ( 0x01 << 24) // DEMCR的DWT使能位
#define DBGMCU_CR *(__IO uint32_t *)0xE0042004 //MCU调试模块控制寄存器,详细内容参考《stm32中文参考手册》调试支持(DBG)章节,747页
// 0xE0001000 DWT_CTRL RW The Debug Watchpoint and Trace (DWT) unit
#define DWT_CTRL ( *(__IO uint32_t *)0xE0001000 )
#define DWT_CTRL_CYCCNTENA ( 0x01 << 0 ) // DWT的SYCCNT使能位
// 0xE0001004 DWT_CYCCNT RW Cycle Count register,
#define DWT_CYCCNT ( *(__IO uint32_t *)0xE0001004) // 显示或设置处理器的周期计数值
void delay_init()
{
//使能DWT外设
DEMCR |= (uint32_t)TRCENA;
//DWT CYCCNT寄存器计数清0
DWT_CYCCNT = (uint32_t)0u;
//使能Cortex-M3 DWT CYCCNT寄存器
DWT_CTRL |= (uint32_t)DWT_CTRL_CYCCNTENA;
}
// 微秒延时
void delay_us(uint32_t uSec)
{
uint32_t ticks_start, ticks_end, ticks_delay;
ticks_start = DWT_CYCCNT;
ticks_delay = ( uSec * ( SystemCoreClock / (1000000) ) ); // 将微秒数换算成滴答数
ticks_end = ticks_start + ticks_delay;
if ( ticks_end > ticks_start )
{
while( DWT_CYCCNT < ticks_end );
}
else // 计数溢出,翻转
{
while( DWT_CYCCNT >= ticks_end ); // 翻转后的值不会比ticks_end小
while( DWT_CYCCNT < ticks_end );
}
}
void delay_ms(uint16_t ms)
{
delay_us(ms*1000);
}
移植到STM32F407
移植到STM32F407的步骤与上面介绍的步骤基本一致,但有几个不同的地方需要注意:
- ARM_CM3文件夹替换为ARM_CM4F文件夹:在portable文件夹中只保留GCC/ARM_CM4F(使用的STM32CubeIDE开发环境);在Makefile中添加FreeRTOS头文件:
INCLUDE_DIR += -ISource/FreeRTOS/portable/GCC/ARM_CM4F
。 - FreeRTOSConfig.h文件中进行适当的修改。例如可以将分配的堆大小调大
#define configTOTAL_HEAP_SIZE ((size_t)50*1024)
。
应用指南
运行FreeRTOS的系统与裸机系统主要区别在于执行业务逻辑的方式发生了改变。裸机系统一般会采用主循环轮询+定时器中断轮询+其他中断抢占的方式来处理复杂的多任务。而FreeRTOS则采用根据功能的不同创建不同优先级的任务,然后借助任务调度器、任务通知等一系列的机制自动分配任务执行的顺序与时间。
一般流程
以下列举了运行FreeRTOS的系统的一般流程:
- 采用DWT寄存器实现系统的延时函数(项目中可能会用到微秒级的精确延时)。
- 正常初始化RCC时钟(可直接调用"system_stm32f10x.h"中的void SystemInit(void)函数)。
- 正常配置NVIC,需要设置NVIC分组为第四分组。
- 创建开始任务与各个业务任务,并开启任务调度器。
任务创建模板
为了代码移植和管理的方便,可以在用户项目文件夹User中创建"user_task.h"与"user_task.c"两个文件用以集中管理任务。以下列举了创建FreeRTOS的任务的一般流程:
- 创建开始任务,用于启动其他任务,并随即启动任务调度器(在主函数中直接调用void FreeRTOS_Exec( ))。
/*************************头文件内容****************************/
//任务优先级
#define START_TASK_PRIO 1
//任务堆栈大小
#define START_STK_SIZE 200
//任务句柄
extern TaskHandle_t StartTask_Handler;
/*************************源文件内容****************************/
TaskHandle_t StartTask_Handler;
void start_task(void *pvParameters)
{
taskENTER_CRITICAL(); //进入临界区
//创建LED0任务
xTaskCreate((TaskFunction_t )led0_task,
(const char* )"led0_task",
(uint16_t )LED0_STK_SIZE,
(void* )NULL,
(UBaseType_t )LED0_TASK_PRIO,
(TaskHandle_t* )&LED0Task_Handler);
//创建modbus poll任务
xTaskCreate((TaskFunction_t )ModbusPoll_task,
(const char* )"ModbusPoll_task",
(uint16_t )MODBUS_POLL_STK_SIZE,
(void* )NULL,
(UBaseType_t )MODBUS_POLL_TASK_PRIO,
(TaskHandle_t* )&ModbusPollTask_Handler);
//创建ads1115 DAQ任务
xTaskCreate((TaskFunction_t )ADS1115Daq_task,
(const char* )"ADS1115Daq_task",
(uint16_t )ADS1115_DAQ_STK_SIZE,
(void* )NULL,
(UBaseType_t )ADS1115_DAQ_TASK_PRIO,
(TaskHandle_t* )&ADS1115DaqTask_Handler);
//创建刷新modbus寄存器执行动作的任务
xTaskCreate((TaskFunction_t )MBRefresh_task,
(const char* )"MBRefresh_task",
(uint16_t )MBREFRESHTASK_STK_SIZE,
(void* )NULL,
(UBaseType_t )MBREFRESHTASK_TASK_PRIO,
(TaskHandle_t* )&MBRefreshTask_Handler);
vTaskDelete(StartTask_Handler); //删除开始任务
taskEXIT_CRITICAL(); //退出临界区
}
void FreeRTOS_Exec()
{
//创建开始任务
xTaskCreate((TaskFunction_t )start_task, //任务函数
(const char* )"start_task", //任务名称
(uint16_t )START_STK_SIZE, //任务堆栈大小
(void* )NULL, //传递给任务函数的参数
(UBaseType_t )START_TASK_PRIO, //任务优先级
(TaskHandle_t* )&StartTask_Handler); //任务句柄
vTaskStartScheduler(); //开启任务调度
}
- 根据项目的不同,设计并创建业务任务的执行函数(由开始任务创建并启动)。以下列举几个常用的任务执行函数:
/*************************头文件内容****************************/
//任务优先级
#define LED0_TASK_PRIO 0
//任务堆栈大小
#define LED0_STK_SIZE 50
//任务句柄
extern TaskHandle_t LED0Task_Handler;
//任务优先级
#define MODBUS_POLL_TASK_PRIO 1
//任务堆栈大小
#define MODBUS_POLL_STK_SIZE 100
//任务句柄
extern TaskHandle_t ModbusPollTask_Handler;
//任务优先级
#define ADS1115_DAQ_TASK_PRIO 2
//任务堆栈大小
#define ADS1115_DAQ_STK_SIZE 100
//任务句柄
extern TaskHandle_t ADS1115DaqTask_Handler;
//任务优先级
#define MBREFRESHTASK_TASK_PRIO 0
//任务堆栈大小
#define MBREFRESHTASK_STK_SIZE 100
//任务句柄
extern TaskHandle_t MBRefreshTask_Handler;
/*************************源文件内容****************************/
TaskHandle_t LED0Task_Handler;
TaskHandle_t ModbusPollTask_Handler;
TaskHandle_t ADS1115DaqTask_Handler;
TaskHandle_t MBRefreshTask_Handler;
//LED任务函数
void led0_task(void *pvParameters)
{
while(1)
{
MCU_LED=~MCU_LED;
vTaskDelay(200);
}
}
//modbus功能任务
void ModbusPoll_task(void *pvParameters)
{
TickType_t ticks = xTaskGetTickCount();
// User_MB_InitPortParam(3, 115200, MB_PAR_NONE);
User_MB_InitRegs();
eMBInit( MB_RTU, FREEMODBUS_DEV_ADDR, FREEMODBUS_PORT_NUM, FREEMODBUS_PORT_BAUDRATE, FREEMODBUS_PORT_PARITY);
eMBEnable( );
while (1)
{
eMBPoll();
// modbus_user_variate_limit();
// daemon_refresh();
//1ms 一个处理周期
vTaskDelayUntil( &ticks, 1 );
}
}
//ADS1115的采集任务
void ADS1115Daq_task(void *pvParameters)
{
int16_t adcDataTemp[4] = {0};
TickType_t ticks = xTaskGetTickCount();
I2C_Virtual_ConfigPort(ADS1115_SDA_PORT, ADS1115_SDA_PIN, ADS1115_SCL_PORT, ADS1115_SCL_PIN);
I2C_Virtual_SwitchBus(ADS1115_SDA_PORT, ADS1115_SDA_PIN, ADS1115_SCL_PORT, ADS1115_SCL_PIN);
ADS1115_UserConfig2();
while (1)
{
for (uint8_t chan = 0; chan < 2; chan++)
{
ADS1115_ScanChannel(chan);
vTaskDelay(10);
if(ADS1115_ReadRawData(&adcDataTemp[chan])!=0)
{
//保留小数点后三位精度
SensorData[chan].value = (float)(ADS1115_RawDataToVoltage(adcDataTemp[chan])*1000)/1000.0;
}
}
//100ms 一个处理周期
vTaskDelayUntil( &ticks, 100);
}
}
//其他数据的刷新任务
void MBRefresh_task(void *pvParameters)
{
TickType_t ticks = xTaskGetTickCount();
while (1)
{
User_RefreshData();
User_RefreshAction();
//500ms 一个处理周期
vTaskDelayUntil( &ticks, 500);
}
}