FreeRTOS记录(十、FreeRTOS实现带 I2C 通讯的 ModbusRTU 协议从机实例)

简介: 还是一个FreeRTOS的例子,这次不是裸机工程转的,没有大部分复制的代码,所以会把步骤会记录详细一点,这应该也是博文中 FreeRTOS 最后一个例子了平台: STM32L051C8T6 欧姆龙 D6T 红外测温传感器 I2C 协议 设备作为485从机
还是一个FreeRTOS的例子,这次不是裸机工程转的,没有大部分复制的代码,
所以会把步骤会记录详细一点,这应该也是博文中 FreeRTOS 最后一个例子了
平台: STM32L051C8T6   
       欧姆龙 D6T 红外测温传感器 I2C 协议 
       设备作为485从机 
本 FreeRTOS 专栏记录的开发环境:
FreeRTOS记录(一、熟悉开发环境以及CubeMX下FreeRTOS配置)
FreeRTOS记录(二、FreeRTOS任务API认识和源码简析)
FreeRTOS记录(三、RTOS任务调度原理解析_Systick、PendSV、SVC)
FreeRTOS记录(四、FreeRTOS任务堆栈溢出问题和临界区)
FreeRTOS记录(五、FreeRTOS任务通知)
FreeRTOS记录(六、FreeRTOS消息队列—Enocean模块串口通讯、RAM空间不足问题分析)
FreeRTOS记录(七、FreeRTOS信号量、事件标志组、邮箱和消息队列、任务通知的关系)
FreeRTOS记录(八、用软件定时器?还是硬件定时器?)

本产品的功能就是通过红外测温传感器定时测量温度保存,设备通过RS484接口,使用ModbusRTU协议进行传输,设备作为从机接收主机的查询上报温度等其他数据。

具体的理论分析请参考博文 MODBUS RTU 485 部分:总线协议记录(I2C,SPI, Modbus 485, CAN...)

一、STM32CubeMX 创建工程

第一步创建工程,这个以前博文中就提到过很多次了,实在不会去看一下我FreeRTOS记录(九、一个裸机工程转FreeRTOS的实例)中的说明,里面的 2.1 基本框架搭建 有所有相关的博文地址。

这里我就简单放一个步骤,说明一下我的基本设置:

1.1 芯片基本设置

  1. GPIO口,按键,LED,一个I2C传感器;

在这里插入图片描述

  1. 系统时钟源,使用外部高速时钟:

在这里插入图片描述

  1. 基础时钟源和调试方式,不勾选 Debug Serial Wire 烧录一次就会锁死,不能正常烧录,需要手动操作:

在这里插入图片描述

  1. 系统时钟频率,使用的STM32L051 ,最大支持32MHz,这里就选32MHz:

在这里插入图片描述

  1. 定时器设置,根据自己的使用习惯,两个定时器,一个定时器做基本逻辑时间管理,一个用于按键驱动的定时器:

在这里插入图片描述在这里插入图片描述

  1. 串口设置,2个串口,一个用于打印的调试串口1,一个用于485通讯的串口:

在这里插入图片描述在这里插入图片描述

1.2 FreeRTOS基本设置

  1. 任务和消息队列的创建,要考虑到设备的主要功能有哪些,传感器读取,485通讯,按键用来做一些观察测试(按键其实可以不用,但是考虑也可以通过按键来设置485的地址,这个想法再看):

在这里插入图片描述

  1. 打开任务运行情况查询功能,确保实施观察任务运行状态,这个东西熟悉了以后或者等系统稳定以后可以去掉:

在这里插入图片描述在这里插入图片描述

  1. 然后就可以生成代码了,用什么环境选什么,我用的是 gcc 环境,这里选用的是 Makefile ,这里最后我还有个小操作,把堆空间改小了一点:

在这里插入图片描述

二、基本框架代码添加

2.1 一些习惯的typedef 和 宏定义

根据个人习惯,把数据类型的名称命名头文件加入工程,在main.h文件中包含,这样在写代码的时候一些就可以根据个人习惯使用数据类型了:
在这里插入图片描述

接下来对LED灯,和I2C引脚进行需要的宏定义:
在这里插入图片描述

2.2 各外设相关代码

2.2.1 定时器(逻辑处理和按键驱动计时)

定义两个全局变量,分别计算 定时器2 和 定时器 21的次数:
在这里插入图片描述
对于定时器21,直接计数就可以,后面在按键驱动中使用Timer21_count的值:
在这里插入图片描述
对于定时器2,在stm32l0xx_it.c文件中,进入定时器中断就处理,因为这里可能需要在中断中发送FreeRTOS任务通知或者消息等:
在这里插入图片描述

2.2.2 串口(调试串口和485通讯串口)

首先打印串口使用USART1,printf重定义,然后就能直接使用printf了:
在这里插入图片描述
接下来485通讯串口 USRAT2,我们需要使用 FreeRTOS 消息队列来发送通讯消息,需要在中断处理函数中操作:
在这里插入图片描述
上面是把串口接收到的消息放入消息队列,我们在 freertos.c文件中,把对应的接收消息队列的部分框架搭建好,同时自己要建立一个缓存用来保存消息队列的数据:
在这里插入图片描述
StartModbusTask任务中:
在这里插入图片描述

完成上面的框架,别忘了打开串口接收中断,但是要注意,因为使用了消息队列接收,打开中断需要等FreeRTOS 初始化完了以后,否者会出现问题!

如下图,我们在MX_FREERTOS_Init函数中结尾处,打开串口2 接收中断:
在这里插入图片描述

2.2.3 按键

按键的话,我还是使用以前的那个驱动代码,就直接复制过来,修改一下定时器源:

几个实用的按键驱动

在这里插入图片描述
增加了.c 文件记得在Makefile中也添加一下:
在这里插入图片描述

三、各外设函数代码

3.1 485从机驱动(ModbusRTU协议)

具体的理论分析请参考博文 MODBUS RTU 485 部分:总线协议记录(I2C,SPI, Modbus 485, CAN...)

先把CRC16校验的文件包含进来,这个网上查一下就有,这里是我一直用的:ModBus-check.zip
在这里插入图片描述
然后写一下命令处理函数:
在这里插入图片描述
最后实现一下Modbus_03_ack函数。

3.1.1 寄存器地址的疑问

在写驱动代码的时候,忽然想到一个问题,寄存器地址该如何定义?

假如我们就从 0x0000 开始,然后依次+1 的定义寄存器,通过一个数组 test[n]是可以一一对应的,这个没问题。

寄存器地址 数组
0000H test[0]
0001H test[1]
0002H test[2]
...... ......
00XY test[XY]

但是看各厂家有些设备,地址不是连续的,而且有小的有大的,比如:
在这里插入图片描述
上面这种情况总不能定义一个这么大的数组 test[0x0101]

(后续补充说明后来想想这个好像也不大,257,作为一款产品,肯定是厂家会保证产品地址在一定的范围内,然后可以用一个数组顺序对应寄存器地址,即便有多个通道,也是会用不同的数组表示,在一定的范围内可以离散,但是差距太大的话就得分不同数组)

当然,即使这样,我们依旧可以按照如下对应:

寄存器地址 数组
0000H test[0]
0001H test[1]
0006H test[2]
0100H test[3]
0101H test[4]

但是问题是,看到设备的说明里面,从地址0000H 开始读取数据,读取7个长度的数据,0001H 到 0006H 的数据都会返回 0,这样子的话,单单用一个数组就不好处理了:
在这里插入图片描述

暂时想不出来 一个数组如何才能知道 他是否是连续的寄存器地址呢?不确定是不是还有其他的设定?(答案应该是上面红色部分)

当然,我考虑过使用 结构体,因为结构体的话,可以先针对的判断寄存器的ID,然后再处理数据,比如:
在这里插入图片描述
但是使用结构体数据处理相对来说 就没有数组那么简单,其实最主要的,关键还是在于产品定义的寄存区是否连续,如果连续的话,处理起来都没问题。

所以目前这个示例我还是把地址设置为连续的,通过一个数组,人为的定义好对应关系。

这里就直接上源码,把Modbus_rtu.c的源码都放上来:

#include "Modbus_rtu.h"
#include "stdio.h"

void Modbus_check()
{
    u16 crc;
    u16 receivecrc1;
    u16 receivecrc2;
    u8 sendbuff[5];

    crc = Checksum_CRC16(USART2_BUF,USART2_Data - 2);
    printf("crc is :0x%x\r\n",crc);
    /*No matter the high bits before or the low bits before*/
    receivecrc1 = (USART2_BUF[USART2_Data - 2]<<8) + USART2_BUF[USART2_Data - 1];
    receivecrc2 = (USART2_BUF[USART2_Data - 1]<<8) + USART2_BUF[USART2_Data - 2];
    // if((lrc == receivelrc2)||(lrc == receivelrc1)){
    //     if(USART2_BUF[0] == mymodbus_add){
    //这里说明一下,先判断地址,然后返回错误,如果先判断校验,如果出错了,那么总线上所有都同时返回就有问题了
    if(USART2_BUF[0] == mymodbus_add){        
        if((crc == receivecrc2)||(crc == receivecrc1)){
            switch (USART2_BUF[1]){
            case 3:
                Modbus_03_ack();
                break;
            case 6:
                Modbus_06_ack();
                break;           
            default:
                printf("An unsupported command!\r\n");//for test
                break;
            }
        }
        else{ //校验错误,返回异常
            sendbuff[0] = mymodbus_add;
            sendbuff[1] = 0x80 | USART2_BUF[1];
            sendbuff[2] = 0;
            crc = Checksum_CRC16(sendbuff,3);
            sendbuff[3] = (u8)(crc >> 8);
            sendbuff[4] = (u8)crc;
            Uart2_sendBuffer(sendbuff,5);
        }
    }   
}

void Modbus_03_ack(){
  u16   Register_add;  // 2,3
  u16   Register_len;  // 4,5
  u16   crc;
  u8    i;
  u8    j;

  Register_add = (USART2_BUF[2]<<8) + USART2_BUF[3]; //get add;
  Register_len = (USART2_BUF[4]<<8) + USART2_BUF[5]; //get len;

  u8 sendbuff[Register_len*2 + 5];
  /*
  如果读取的地址写错了,或者读取长度超过规定的长度
  返回错误
  */
  if(( 0x0010 <= Register_add)&&( Register_add <= 0x0014 )&&(Register_len < 6)){  
    i = 0;
    sendbuff[i++] = mymodbus_add;
    sendbuff[i++] = 0x03;
    sendbuff[i++] = Register_len<<1;
    switch(Register_add){
    case 0x0010:
        for(j=0;j<Register_len;j++){
            sendbuff[i++]= (u8)(Register_value[0+j]>>8);    //发送读取数据字节数的高位 
            sendbuff[i++]= (u8)Register_value[0+j];      //发送读取数据字节数的低位 
        }
        break;
    case 0x0011:
        for(j=0;j<Register_len;j++){
            sendbuff[i++]= (u8)(Register_value[1+j]>>8);     
            sendbuff[i++]= (u8)Register_value[1+j];       
        }
        break;           
    case 0x0012:
        for(j=0;j<Register_len;j++){
            sendbuff[i++]= (u8)(Register_value[2+j]>>8);     
            sendbuff[i++]= (u8)Register_value[2+j];           
        }
        break;
    case 0x0013:
        for(j=0;j<Register_len;j++){
            sendbuff[i++]= (u8)(Register_value[3+j]>>8);    
            sendbuff[i++]= (u8)Register_value[3+j];       
        }
        break; 
    case 0x0014:
        for(j=0;j<Register_len;j++){
            sendbuff[i++]= (u8)(Register_value[4+j]>>8);     
            sendbuff[i++]= (u8)Register_value[4+j];           
        }
        break;
    default:break;  
    }
    crc = Checksum_CRC16(sendbuff,i);
    sendbuff[i++] = (u8)(crc >> 8);
    sendbuff[i++] = (u8)crc;
    Uart2_sendBuffer(sendbuff,i);
  }
  else{//地址不在规定返回或者长度太长,返回错误
    sendbuff[0] = mymodbus_add;
    sendbuff[1] = 0x80 | USART2_BUF[1];
    sendbuff[2] = 0;
    crc = Checksum_CRC16(sendbuff,3);
    sendbuff[3] = (u8)(crc >> 8);
    sendbuff[4] = (u8)crc;
    Uart2_sendBuffer(sendbuff,5);
  }
}

void Modbus_06_ack(){   
}

3.1.2 485 驱动测试

485接收部分驱动完成,这里需要测试一下,测试过程确实发现了问题,但是都是小问题,整体框架逻辑是正常的,在处理校验位的时候,数据位数处理有点大意了(上面贴出的程序是已经修改过的):
在这里插入图片描述
把第一个寄存器设置一个值:
在这里插入图片描述
测试结果,一切正常:
在这里插入图片描述

测完了还是把 06 写单个寄存器的函数给补充一下,目前只开放一个数据,就是设备ID的写入,直接上代码:

void Modbus_06_ack(){
  u16   Register_add;  // 
  u16   val;  // 
  u16   crc;
  u8    i;
  u8    sendbuff[8] = {0};
  if(USART2_Data < 9){     
    Register_add = (USART2_BUF[2]<<8) + USART2_BUF[3]; //get add;
    val = (USART2_BUF[4]<<8) + USART2_BUF[5]; //

    if(Register_add == 0x0013){
        mymodbus_add = val;
        Register_value[3] = mymodbus_add;
        i = 0;
        sendbuff[i++] = mymodbus_add;
        sendbuff[i++] = 0x06;
        sendbuff[i++] = (u8)(Register_add>>8);     
        sendbuff[i++] = (u8)Register_add;    
        sendbuff[i++] = (u8)(val>>8);     
        sendbuff[i++] = (u8)val;
        crc = Checksum_CRC16(sendbuff,i);
        sendbuff[i++] = (u8)(crc >> 8);
        sendbuff[i++] = (u8)crc;
        Uart2_sendBuffer(sendbuff,i);     
    }
    else{//写地址不在规定范围
        sendbuff[0] = mymodbus_add;
        sendbuff[1] = 0x80 | USART2_BUF[1];
        sendbuff[2] = 3;
        crc = Checksum_CRC16(sendbuff,3);
        sendbuff[3] = (u8)(crc >> 8);
        sendbuff[4] = (u8)crc;
        Uart2_sendBuffer(sendbuff,5);
    }
  }
  else{//写地址不在规定范围
    sendbuff[0] = mymodbus_add;
    sendbuff[1] = 0x80 | USART2_BUF[1];
    sendbuff[2] = 1;
    crc = Checksum_CRC16(sendbuff,3);
    sendbuff[3] = (u8)(crc >> 8);
    sendbuff[4] = (u8)crc;
    Uart2_sendBuffer(sendbuff,5);
  }    
}

测试一下效果,如下图,看上去OK:
在这里插入图片描述
想了一下,不对啊?

修改地址应该有个限制,从机的地址应该在: 1~ 247 (0XF7)

所以以代码里面还得加一个限制(按理来说应该是先判断地址,如果是写ID的寄存器,才需要限制从机地址大小,其他寄存器,是可以写其他数值的,因为示例中只开放ID寄存器的写操作,所以这里问题不大,但是在完善自己的从机代码的时候这里得注意一下逻辑!!!)
在这里插入图片描述

3.2 D6T传感器驱动(I2C协议)

I2C的基础知识这里就不多介绍了,网上很多,我那个总线协议记录里面也介绍了。

3.2.1 通用I2C驱动

I2C协议需要用到 us 延时,HAL库里面没有, FreeRTOS里面也没,这里我们还是用以前常用的自己写一个:
在这里插入图片描述

我们使用的是软件的I2C,所以要把通用的 I2C 驱动写一下,新建一个i2c.c和一个i2c.h文件作为软件 I2C的通用驱动,这里直接上源码:

#include "i2c.h"

// ------------------------------------------------------------------
void i2c_init(void)  {
  
// the SDA and SCL pins are defined as input with pull up enabled
  // pins are initialized as inputs, ext. pull => SDA and SCL = high

}
// ------------------------------------------------------------------
// send start sequence (S)

void i2c_start(void)
{
   sda_high;
   delay_us(5);
   scl_high;
   delay_us(10);
   sda_low;
   delay_us(10);
   scl_low; //使SCL置低,准备发送或者接受数据
   delay_us(10);
}

// ------------------------------------------------------------------
// send stop sequence (P)
void i2c_stop(void)
{ 
   sda_low;
   delay_us(5);
   scl_low;
   delay_us(10);
   scl_high;
   delay_us(5);
   sda_high;
   delay_us(10); 
}
// ------------------------------------------------------------------
// returns the ACK or NACK
uint8 i2c_write(uint8 u8Data) 
{
    uint8 u8Bit;
    uint8 u8AckBit;
    // write 8 data bits
    u8Bit = 0x80;  //msb first  
    while(u8Bit) {
        if(u8Data&u8Bit) { 
            sda_high;
            delay_us(20);
        } 
        //& compare every bit
        else{ 
            sda_low;
            delay_us(20);
        }            
        scl_high;
        delay_us(30);
        u8Bit >>= 1; 
        //next bit
        scl_low;
        delay_us(30);
    }
    // read acknowledge (9th bit) 
    sda_high;                                               
    delay_us(10);        
    scl_high;
    delay_us(10);
    u8AckBit= sda_read;    //#define sda_read()  (sda_port & sda_pin)? 1 :0  ack on bus is low -> u8AckBit = 1   sda_port gpio0   sda_pin SCSEDIO0
    delay_us(10);
    scl_low;                                              
    delay_us(10);
    return u8AckBit;
}

//读1个字节,ack=1时,发送ACK,ack=0,发送nACK   
u8 i2c_read_byte(unsigned char ack)
{
    unsigned char i,receive=0;
    // MYSDA_IN;//SDA设置为输入
   for(i=0;i<8;i++ )
    {
        scl_low;    //SCL为由低变高,在SCL高的时候去读 SDA的数据
        delay_us(10);
        scl_high;
        receive<<=1;  //第一次这里还是0,第二次开始每次接收的数据做移动一位,从高位开始接收
        if(sda_read)receive++;   //如果数据为1,++以后就是1,数据为0,不执行就是0; 
          delay_us(10); 
   }                     
   if (!ack)
        IIC_NAck();//发送nACK
   else
        IIC_Ack(); //发送ACK   
   return receive;
}

// ------------------------------------------------------------------
// pass the ack/nack 
// returns the read data 
uint8 i2c_read(uint8 u8Ack)  
{
    uint8 u8Bit;
    uint8 u8Data;
    u8Bit = 0x80;       // msb first
    u8Data = 0;
    while(u8Bit){
        scl_high;
        
        delay_us(20);
    
        u8Bit >>= 1;    //next bit
        u8Data <<= 1;
    
        u8Data |= sda_read;         //(sda_port & sda_pin)? 1 :0      sda_port gpio0   sda_pin SCSEDIO0
        delay_us(20);
    
        scl_low;
        delay_us(50);
    }
    // 9th bit acknowledge
    if(u8Ack==I2C_ACK) {
        sda_low;
        delay_us(20);
    }     
    //I2C_ACK=0
    else {
        sda_high;
        delay_us(20);
    }
      scl_high;
    delay_us(20);
    scl_low;
    delay_us(20);
    sda_high;
    delay_us(20);                                    
    return u8Data;
}

u8 i2c_wait_ack(void)
{
    u8 ucErrTime=0;
    delay_us(5);
    sda_high;delay_us(5);       //MCU DATA 置高,外面高就是高,外面低就是低
    scl_high; delay_us(5);       //CLK 高电平期间数据有效
    while(sda_read)             //低电平为有应答,高电平无应答    
    {
      ucErrTime++;
        if(ucErrTime>250)
        {
            i2c_stop();
            return 1;
        }  
    }
        delay_us(10);
    scl_low;
    return 0;
}

void IIC_Ack(void)
{
    scl_low;    //SCL为低,SDA为低,SCL为高,SDA为低,应答低电平有效,SCL为低,产生应答信号
    // MYSDA_OUT;
    sda_low;
    delay_us(10);
    scl_high;
    delay_us(10);
    scl_low;
    delay_us(10);
    sda_high;
}

void IIC_NAck(void)
{
    scl_low;     //SCL为低,SDA为高,SCL为高,SCL为低
    // MYSDA_OUT;
    sda_high;
    delay_us(10);
    scl_high;
    delay_us(10);
    scl_low;
}

//IIC发送一个字节
//返回从机有无应答
//1,有应答
//0,无应答              
void i2c_send_byte(u8 txd)
{                        
   u8 t;   
    // MYSDA_OUT;         
   scl_low;  //拉低时钟开始数据传输   ,SCL为低,SDA变高或者变低(数据位),SCL变高,SCL变低,期间SDA为1既1,为0既0
   for(t=0;t<8;t++)  //一个字节8位,一位一位发送
    {              
     scl_low;
        if((txd&0x80)>>7)   //从最高位开始发送,如果是1,发送高电平
            sda_high;
        else
            sda_low;
        txd<<=1;           //SDA处理完毕,此时可以将SCL拉高接受数据,拉高以后延时拉低
        delay_us(10);   //对TEA5767这三个延时都是必须的
        scl_high;
        delay_us(10); 
        scl_low;    
        delay_us(5);
    }     
}

3.2.2 I2C传感器数据读取

对于欧姆龙的这个传感器,主要的时序图如下:
在这里插入图片描述
根据时序图,设计代码,源码如下(当然要根据自己的传感器型号进行细节修改):

void D6T_Measure()
{
    u8 D6Tbuff[20];
    u8 D6T_Data=0;
    // u16 tPEC;

      i2c_start();
    i2c_send_byte(0X14); //地址,和读写指令
    i2c_wait_ack();
    delay_us(150);    //这里必须加

    i2c_send_byte(0X4C);
    i2c_wait_ack(); 

    delay_us(150);

    i2c_start();
    i2c_send_byte(0X15); //地址,读指令
    i2c_wait_ack();
    delay_us(120);

    // D6T44L_ReadLenByte(5);    //D6T-1A-02 只有5个数值
    u8 t;
      D6T_Data=0;
    for(t=0;t<(5-1);t++)
    {
        D6Tbuff[D6T_Data++] = i2c_read_byte(1);
        delay_us(120);
    }
     D6Tbuff[D6T_Data] = i2c_read_byte(0);
    delay_us(120);
    i2c_stop();

    //  tPTAT = 256 * D6Tbuff[1] + D6Tbuff[0];
    tP = 256 * D6Tbuff[3] + D6Tbuff[2];
}

然后在任务里面调用温度读取函数,做一个简单的测试:
在这里插入图片描述
驱动OK,测试结果(结果是温度 * 10):
在这里插入图片描述

3.3 任务调整、任务通讯

驱动都已经设计完成,那么就到了最后的地方,任务间的通讯了,什么时候读取数据,读取的数据放入对应的 485 的寄存器。

3.3.1 任务栈说明

当然,不要忘了各个任务的栈空间调整,还要考虑到使用的STM32L051 单片机只有8K的 RAM,要注意内存的使用情况:

不看不知道,一看吓一跳 = =!:
在这里插入图片描述

如果打开了堆栈溢出钩子函数,估计一直在报溢出,测试一下:
在这里插入图片描述

测试方式见博文:FreeRTOS记录(四、FreeRTOS任务堆栈溢出问题和临界区)

上面我们看到,按键任务和 RTU接收任务的 栈不够了,D6Tread 也不是很够,因为后期还需要做逻辑处理,也有调用关系,所以必须对各任务的大小进行一定的调整。

KeyTask

前面我们说到,按钮目前只是为了测试,所以按钮把里面定义的数组改小一点,其实只要能够满足完全打印任务状态的大小就行了,以前设置500并没有仔细的去计算。

任务大小:
在这里插入图片描述
任务运行:
在这里插入图片描述
在后期的完善过程中,考虑到一个问题,作为485设备,如果拿到一个设备,并不知道他的 ID ,怎么办,所以正好可以通过按钮操作,使得设备恢复默认ID。所以将按键任务加了一个长按操作:
在这里插入图片描述
在这里插入图片描述

RTUreceived

串口接收任务会溢出是开始没想打的,但是仔细想想还是不应该,看了一下,开始测试的时候加了一句 printf 函数,把printf后 RTUreceived 就正常不会溢出了:
在这里插入图片描述
加个printf就占用那么大空间?这个怎么理解呢,虽然知道使用 printf 会占用栈空间,但是这么大还是有点诧异的

任务大小:
在这里插入图片描述
任务运行:
在这里插入图片描述
D6TRead

通过上文可以看到 D6TRead 只剩下 21 的栈空间,在该任务中,我们调用了 I2C 的驱动,读取传感器数据,考虑到后面还可能进行数据计算操作,此任务估计得放大一点空间(为什么数据保存不能新建一个任务,因为内存空间不够,给FreeRTOS分配的总栈空间已经不够我再新建一个任务了,当然,实际应用,可以对keyTask任务进行缩减来换取其他任务更多的空间)。

当然,我还是改大了一点:
在这里插入图片描述

任务大小:
在这里插入图片描述

任务运行:
在这里插入图片描述

3.3.2 任务间通信

其实示例做到上面,整体上已经没什么大问题了,最主要的还是示例还是比较简单的,没有太多的任务,所以到这里其实基本上大问题就没有了。余下的就是细节处理。

最初构思的时候,对于任务之间,什么时候读取传感器的数据,是想使用 TIM2 定时 ,在定时器中断中给D6TRead 任务通知来实现,但是因为整体结构简单,这里直接使用延时就可以达到效果= =!这里就不再折腾了,因为具体的使用可以参考博文,也是任务中读取传感器:

FreeRTOS记录(五、FreeRTOS任务通知)

然后针对实际应用,可能需要对采集的温度进行一定的数据处理,直接根据需求修改即可。

最后整体测试一下,没有问题:
在这里插入图片描述

相关实践学习
通过Ingress进行灰度发布
本场景您将运行一个简单的应用,部署一个新的应用用于新的发布,并通过Ingress能力实现灰度发布。
容器应用与集群管理
欢迎来到《容器应用与集群管理》课程,本课程是“云原生容器Clouder认证“系列中的第二阶段。课程将向您介绍与容器集群相关的概念和技术,这些概念和技术可以帮助您了解阿里云容器服务ACK/ACK Serverless的使用。同时,本课程也会向您介绍可以采取的工具、方法和可操作步骤,以帮助您了解如何基于容器服务ACK Serverless构建和管理企业级应用。 学习完本课程后,您将能够: 掌握容器集群、容器编排的基本概念 掌握Kubernetes的基础概念及核心思想 掌握阿里云容器服务ACK/ACK Serverless概念及使用方法 基于容器服务ACK Serverless搭建和管理企业级网站应用
相关文章
|
6月前
【FreeRTOS】中断管理(二)
【FreeRTOS】中断管理
126 0
|
6月前
【FreeRTOS】中断管理(三)
【FreeRTOS】中断管理
|
6月前
|
算法 Linux 调度
xenomai内核解析--xenomai与普通linux进程之间通讯XDDP(一)--实时端socket创建流程
xenomai与普通linux进程之间通讯XDDP(一)--实时端socket创建流程
465 1
xenomai内核解析--xenomai与普通linux进程之间通讯XDDP(一)--实时端socket创建流程
|
6月前
|
存储 缓存 Linux
xenomai内核解析--xenomai与普通linux进程之间通讯XDDP(三)--实时与非实时数据交互
本文介绍了Xenomai中的XDDP(Xenomai Distributed Data Protocol)通信机制,XDDP用于实时和非实时进程之间的数据交换。XDDP在Xenomai内核中涉及的数据结构和管理方式,以及创建XDDP通道后的实时端和非实时端连接过程。
316 0
xenomai内核解析--xenomai与普通linux进程之间通讯XDDP(三)--实时与非实时数据交互
|
6月前
|
Linux 调度 数据库
|
6月前
I/O设备与主机信息传送的方式(程序查询方式,程序中断方式,DMA方式)
I/O设备与主机信息传送的方式(程序查询方式,程序中断方式,DMA方式
350 0
|
6月前
|
API C语言
【FreeRTOS】中断管理(一)
【FreeRTOS】中断管理
121 0
|
6月前
|
API
FreeRTOS软件定时器的原理以及使用实例
FreeRTOS软件定时器的原理以及使用实例
147 0
|
6月前
|
编解码 计算机视觉 Python
IPC机制在jetson中实现硬解码视频流数据通信的逻辑解析
IPC机制在jetson中实现硬解码视频流数据通信的逻辑解析
190 0
|
6月前
|
API 调度
FreeRTOS深入教程(中断管理)
FreeRTOS深入教程(中断管理)
312 0