STM32
直播中

surround

12年用户 553经验值
私信 关注
[问答]

如何通过ModbusRTU协议读取电能表的数据?

如何通过ModbusRTU协议读取电能表的数据?

回帖(1)

张鑫

2021-12-8 10:35:53
1、内容
使用 开发板 串口4进行通信,开发板做ModbusRTU主机,读取电能表参数。


2、所需硬件
1)开发板一块:STM32f072RBT6、JLlink下载线
2)IM1281B 电能计量模块一块 连接线若干


3、实验步骤
1)对使用的端口(包括串口,普通GPIO口)进行初始化,其中串口初始化的内容主要包括时钟配置、使能。
主要通过三个结构体来实现USART_InitTypeDef,NVIC_InitTypeDef, GPIO_InitTypeDef。
这三个结构体的主要作用如下:


USART_InitTypeDef:
  对ModbusRTU协议所需要的的通讯参数,比如波特率,校验位,停止位等进行配置,开启串口中断等,我觉得一般都要开启串口中断,因为要使用串口中断函数来接收从机的发来的数据
  配置代码如下:
/* USARTx configuration ----------------------------------------------------*/
USART_InitTypeDef USART_InitStruct;       
        USART_InitStruct.USART_BaudRate = Frequency;
        USART_InitStruct.USART_WordLength = USART_WordLength_8b;
        USART_InitStruct.USART_StopBits = USART_StopBits_1;
        USART_InitStruct.USART_Parity = USART_Parity_No;
        USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
        USART_InitStruct.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;
        USART_Init(USART4, &USART_InitStruct);
        USART_ITConfig(USART4, USART_IT_RXNE, ENABLE);//使能USART中断
        USART_Cmd(USART4, ENABLE);/* Enable USART */
        /* Enable USART */
  USART_Cmd(USART4, ENABLE);


NVIC_InitTypeDef:
配置中断名称和中断等级等
  配置代码如下:
        /* NVIC configuration: Enable the USARTx Interrupt---------------------------------------------------- */
        NVIC_InitStructure.NVIC_IRQChannel = USART3_4_IRQn;
        NVIC_InitStructure.NVIC_IRQChannelPriority = 1;
        NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;//使能中断


GPIO_InitTypeDef:
配置GPIO端口的模式,比如使用到哪些端口定义,输入还是输出,如果是输出的话,最大速度是多少,是推挽输出还是上拉输出等
本例中使用的是 PA.0和PA.1,
/* Configure USART Tx and Rx as alternate function push-pull */
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;               
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;  
GPIO_Init(GPIOA, &GPIO_InitStructure);  
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
GPIO_Init(GPIOA, &GPIO_InitStructure);


整体USART.C代码


#include "stm32f0xx.h"
#include "USART.h"
u16  ch_tx_SendIndex,ch_tx_SendLength,ch_rx_RecvIndex,ch_rx_RecvLength,ch_RXFLAG;
u8 ch_tx_buf[60];用于发送主机发送RTU指令,即先把要发送的字节放在这个缓冲数组内,再通过串口发送函数USART_SendData发送                                                    给从机
u8 ch_rx_buf[60];用于存放从机发来的数据,
void USART3_Init(uint32_t Frequency)
{
        GPIO_InitTypeDef GPIO_InitStructure;
        NVIC_InitTypeDef NVIC_InitStructure;
        USART_InitTypeDef USART_InitStruct;
       
        /* config USART3 clock */
        RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3 , ENABLE);
        RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOC, ENABLE);
       
        /* USART3 GPIO config */
        /* Configure USART3 Tx  as alternate function push-pull */
        GPIO_PinAFConfig(GPIOC,GPIO_PinSource10,GPIO_AF_1);
  GPIO_PinAFConfig(GPIOC,GPIO_PinSource11,GPIO_AF_1);
        GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10|GPIO_Pin_11;                 
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
        GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
        GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
        GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
        GPIO_Init(GPIOC, &GPIO_InitStructure);  


       
        NVIC_InitStructure.NVIC_IRQChannel = USART3_4_IRQn;
        NVIC_InitStructure.NVIC_IRQChannelPriority = 3;
        NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
        NVIC_Init(&NVIC_InitStructure);
       
        /* USART3 mode config */
        USART_InitStruct.USART_BaudRate = Frequency;
        USART_InitStruct.USART_WordLength = USART_WordLength_8b;
        USART_InitStruct.USART_StopBits = USART_StopBits_1;
        USART_InitStruct.USART_Parity = USART_Parity_No;
        USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
        USART_InitStruct.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;
        USART_Init(USART3, &USART_InitStruct);
        USART_ITConfig(USART3, USART_IT_RXNE, ENABLE);
        USART_Cmd(USART3, ENABLE);


}


void USART4_Init(uint32_t Frequency)  //PA.0 Tx , PA.1Rx
{
        USART_InitTypeDef USART_InitStruct;       
        NVIC_InitTypeDef NVIC_InitStructure;
  GPIO_InitTypeDef GPIO_InitStructure;
       
        /* Enable GPIO clock */
        RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE);
       
        /* Enable USART clock */
        RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART4 , ENABLE);


        /* Connect PXx to USARTx_Tx */
        GPIO_PinAFConfig(GPIOA,GPIO_PinSource0,GPIO_AF_4);   


        /* Connect PXx to USARTx_Rx */
  GPIO_PinAFConfig(GPIOA,GPIO_PinSource1,GPIO_AF_4);
       
        /* Configure USART Tx and Rx as alternate function push-pull */
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;               
        GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
        GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
        GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;  
        GPIO_Init(GPIOA, &GPIO_InitStructure);  


  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
  GPIO_Init(GPIOA, &GPIO_InitStructure);  
       
/* USARTx configuration ----------------------------------------------------
  - USARTx configured as follow:
  - BaudRate = Frequency
  - Word Length = 8 Bits
  - One Stop Bit
  - No parity
  - Hardware flow control disabled (RTS and CTS signals)
  - Receive and transmit enabled   
*/


        USART_InitStruct.USART_BaudRate = Frequency;
        USART_InitStruct.USART_WordLength = USART_WordLength_8b;
        USART_InitStruct.USART_StopBits = USART_StopBits_1;
        USART_InitStruct.USART_Parity = USART_Parity_No;
        //USART_InitStruct.USART_Parity = USART_Parity_Even ;
        USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
        USART_InitStruct.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;
        USART_Init(USART4, &USART_InitStruct);
        USART_ITConfig(USART4, USART_IT_RXNE, ENABLE);//使能USART中断
        USART_Cmd(USART4, ENABLE);/* Enable USART */


        /* NVIC configuration: Enable the USARTx Interrupt */
        NVIC_InitStructure.NVIC_IRQChannel = USART3_4_IRQn;
        NVIC_InitStructure.NVIC_IRQChannelPriority = 1;
        NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;//使能中断
        NVIC_Init(&NVIC_InitStructure);
       
/* Enable USART */
  USART_Cmd(USART4, ENABLE);
}


2)在MODBUS.C中组织主机发宋给从机的数据(参考正点原子资料)


void mp_send_data(u8* buf, u16 len)               
{
        u16 i=0;
        for(i=0;i         {
                while((USART4->ISR&0X40)==0);        //等待上一次串口数据发送完成  
                USART4->TDR=buf;     //发送数据将待发送的数据放入串口的发送数据寄存器中即可,硬件会自动发送
        }       
}
struct m_frame *fx;//定义一个主机发送到从机的通讯帧结构
//打包一帧数据,并发送
//fx:指向需要打包的帧  
//发送   01 03 00 48 00 08 C4  1A
//接收   01 03 20 Data01_Data08  1B C2
void mb_packsend_frame(struct m_frame *fx)
{  
        u16 framelen=0;                                //打包后的帧长度
//  u8 sendbuf[8];                                //发送缓冲区,待发送的数据先处理好放在这块区内,然后确定好长度framelen和起始地址&sendbuf[0]后,利用senddata从串口中循环发送。
        u8* sendbuf;
        fx->address=0x01;       
        fx->function=0x03;
        fx->Start_address_Hi=0x00;
        fx->Start_address_Li=0x48;
        fx->Quantity_Registers_Hi=0x00;
        fx->Quantity_Registers_Li=0x08;
        fx->CRC16_Hi=0xC4;
        fx->CRC16_Li=0x1A;               
        fx->Framelen=0x08;
        framelen=fx->Framelen;


  sendbuf=(u8*)malloc(framelen);        //申请发送数据缓冲区内存
        sendbuf[0]=fx->address;
        sendbuf[1]=fx->function;
        sendbuf[2]=fx->Start_address_Hi;
        sendbuf[3]=fx->Start_address_Li;
        sendbuf[4]=fx->Quantity_Registers_Hi;
        sendbuf[5]=fx->Quantity_Registers_Li;                        
        sendbuf[6]=fx->CRC16_Hi;        
        sendbuf[7]=fx->CRC16_Li;                       
        mp_send_data(sendbuf,framelen);        //发送这一帧数据
free(sendbuf);//释放发送数据缓冲区内存,如果不释放吗,就会出现运行一段时间死机的现象
}


3)在串口中断中把从机返回的数据放到接收缓冲区的数组里面


void USART3_4_IRQHandler(void)
{
if(USART_GetITStatus(USART4, USART_IT_RXNE) != RESET)
{        
//    USART4->TDR=USART4->RDR;//调试代码 :自收自发,收到什么,发送什么
        if( ch_rx_RecvIndex<60)
        {
                ch_rx_buf[ch_rx_RecvIndex]=USART4->RDR;
          ch_rx_RecvIndex++;
        }
        else
                ch_rx_RecvIndex=0;
}                 
                          
}       


最后上一份实际图,代表这个实验暂时告一段落。这个实验我从5月份断断续续调到今天。
发送 01 03 00 48 00 08 C4 1A
接收 01 03 20 Data01~Data08 1B C2


  

  

  备注:
1)F0的固件库函数使用网上没有找到,我参考的的是F1的固件库手册。
2)F0外设参数初始化模板可以参考开发板历程,也可以在ST逛网下载外设库,里面有一个帮助手册也可以看到。
文件是以chm结尾的文件。具体名称是stm32f0xx_stdperiph_lib_um.chm。参考的是 STM32F0xx_StdPeriph_Lib_V1.5.0。
3)开发F0单片机必备的手册 ,根据我的老师们讲,手册在手,天下我有。
1)想知道管脚定义,时钟树,内存图:看数据手册(Datasheet-production data)

  

  

  2)想知道操作外设的库函数怎么用:看固件库手册

  

  

  3)至于编程手册:还有看过,感觉很有用,下次分解
  
  

  

  4)看具体外设寄存器知识,看参考手册

  

  

  实验中遇到的问题:
  

  • TTL 电平与485电平混接,还好同事发现得早,不然累死也调不出,第1次被鄙视
    IM1281B 电能计量模块通讯电平为TTL的
    2)使用malloc申请发送缓冲区时,没有释放,导致若干个周期后,程序死机,后来用free释放后正常。
    3)有些转态重要的状态可以通过点灯来判断,比如执行某段程序时灯亮,离开时灯灭,有时候还是挺有用的,至少可以
    判断程序是否死机,比如主程序中用亮–灭--亮来代表主程序一直在循环执行。
    4)中断名称写错,死活进不去中断,还是同事发现的,第2次被鄙视。一定要注意,不然即使中断条件满足了,也进不去中断。
    正确写法:void USART3_4_IRQHandler(void),我的写法void USART4_IRQHandler(void)
    5)这次工程时新建的,需要添加还多依赖文件,也是第一次完整地从0到1建工程,花费了很长时间,最后一吨的错误,还是要同事来帮忙,原因是官网上依赖文件不能直接用,需要修改,索性用同事已有的文件来替换,就把这个问题解决了。这已算是站在巨人的肩膀上吧。
    6)结构体使用时语法错误,编译未报错,使用了一个没有初始化的野指针,所以只要对结构体成员的赋值,即使用野指针的地址就会进入死机,单步跟踪时,直接进入硬件错误while(1)无限循环。还是同事帮助解决,一眼指出问题所在,牛牛牛。深刻理解一句话“嵌入式高手都是C语言高手”
    7)总之一句话 ,细节太多,需要自己动手去,才会有感觉。
举报

更多回帖

×
20
完善资料,
赚取积分