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

还是一个FreeRTOS的例子,这次不是裸机工程转的,没有大部分复制的代码,所以会把步骤会记录详细一点,这应该也是博文中 FreeRTOS 最后一个例子了
平台: STM32L051C8T6   
	   欧姆龙 D6T 红外测温传感器 I2C 协议 
	   设备作为485从机 

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

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

一、STM32CubeMX 创建工程

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

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

1.1 芯片基本设置

  1. GPIO口,按键,LED,一个I2C传感器;
    在这里插入图片描述
  2. 系统时钟源,使用外部高速时钟:
    在这里插入图片描述
  3. 基础时钟源和调试方式,不勾选 Debug Serial Wire 烧录一次就会锁死,不能正常烧录,需要手动操作:
    在这里插入图片描述
  4. 系统时钟频率,使用的STM32L051 ,最大支持32MHz,这里就选32MHz:
    在这里插入图片描述
  5. 定时器设置,根据自己的使用习惯,两个定时器,一个定时器做基本逻辑时间管理,一个用于按键驱动的定时器:
    在这里插入图片描述在这里插入图片描述
  6. 串口设置,2个串口,一个用于打印的调试串口1,一个用于485通讯的串口:
    在这里插入图片描述在这里插入图片描述

1.2 FreeRTOS基本设置

  1. 任务和消息队列的创建,要考虑到设备的主要功能有哪些,传感器读取,485通讯,按键用来做一些观察测试(按键其实可以不用,但是考虑也可以通过按键来设置485的地址,这个想法再看):
    在这里插入图片描述
  2. 打开任务运行情况查询功能,确保实施观察任务运行状态,这个东西熟悉了以后或者等系统稳定以后可以去掉:
    在这里插入图片描述在这里插入图片描述
  3. 然后就可以生成代码了,用什么环境选什么,我用的是 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任务中:
在这里插入图片描述

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);
    /*No matter the high bits before or the low bits before*/
    receivecrc1 = (USART2_BUF[USART2_Data -1]<<8) + USART2_BUF[USART2_Data];
    receivecrc2 = (USART2_BUF[USART2_Data]<<8) + USART2_BUF[USART2_Data - 1];
    // 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(mymodbus_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.2 D6T传感器驱动(I2C协议)

3.3 逻辑设计(任务间通信)

猜你喜欢

转载自blog.csdn.net/weixin_42328389/article/details/122326743