MSP430中MODBUS-RTU的程序编写方式

发布者:幸福的时光最新更新时间:2022-09-13 来源: csdn关键字:MSP430  MODBUS-RTU  程序 手机看文章 扫描二维码
随时随地手机看文章

**MSP430中MODBUS-RTU的程序编写方式**

MODBUS RTU简单介绍

MODBUS 轮询程序,此函数持续在while中循环

定时器定时卡3.5字符时间,置标志位给轮询函数

03功能码的处理函数,此函数通过for语句持续将寄存器中数据打包发送,并添加CRC校验

06功能码解析函数,此函数用于将发送进来的数据解析后根据地址写入寄存器

通过串口发送一串数据,并在此数据后自动追加CRC校验码

此函数是正确应答函数,在03/06功能码解析函数中用到

写到这里依然没有出现对应寄存器读写的函数,其实读写的函数很简单,读取的函数只是输入寄存器的地址,即可将读取到的结果return给函数本身,在03功能码的解析函数中将数据读取出来并追加CRC通过串口发送出去,就达到了读取寄存器的效果

大家可以根据自己的要求在本函数中添加相应的地址和数据,自行增加case语句即可;如果要读取32位数据,需要把32位数据拆分成两个16位数据,需要占用两个寄存器。


下面为06功能码写保持寄存器的函数,可以根据自己要求自行添加内部函数,增加case语句即可


最后分享一个将2字节的数组(小端Little Endian,低字节在前)转换为16位整数的程序

注明:以上的函数适用于MSP430F5438A单片机,程序来源是参考安富莱的MODBUS例程,本人将程序重新优化和改写,目前MODBUS响应速度小于20MS即可响应


MODBUS RTU简单介绍

Modbus 一个工业上常用的通讯协议、一种通讯约定。Modbus协议包括RTU、ASCII、TCP。其中MODBUS-RTU最常用,比较简单,在单片机上很容易实现。虽然RTU比较简单,但是看协议资料、手册说得太专业了,起初很多内容都很难理解。


首先第一步是串口的初始化以及定时器,MODBUS是传输协议,依赖于串口,本质就是通过串口对数据以规定的格式进行收发并判断,RTU为16进制传输。所以首先要对串口进行初始化,初始化串口波特率、数据位。校验位以及停止位,在编写MODBUS协议之前首先保证自己串口已经调通,定时器已经开启并初始化,这些是前提。


当上位机发来请求命令,比方说要读我们设备1个寄存器,地址为1,那么上位机就会发送01 03 00 00 00 01 xx xx


01 表示从机地址

03 表示modbus03功能码

00 第一组表示寄存器起始地址高8位

00 第二组表示寄存器起始地址低8位

00 第三组表示读取的寄存器数量高8位

01 表示读取的寄存器数量低8位,即这条数据帧代表从第0个地址开始,读取一个寄存器

XX 第一组表示CRC校验高8位

XX 第二组表示CRC校验低8位


MODBUS 轮询程序,此函数持续在while中循环

///*超过3.5个字符时间后执行函数,通知主函数开始解码*/

        if(gtModbusTime.u8ModbusTimeOutFlag == ENUM_FALSE)       

        {

            return;                                 

        }  

        u16Addr = gtModbusData.u8RxBuf[0];

        if(u16Addr != gtFramData.u8FramDeviceAddr)

        {

            gtModbusData.u8RxCount = 0;

            return; 

        } 

        gtModbusTime.u8ModbusTimeOutFlag = ENUM_FALSE;   

        /*接收的数据小于4个字节就认为是错误的*/

        if(gtModbusData.u8RxCount < 4)     

        {

            gtModbusData.u8RxCount = 0;

            return; 

        }    

        /*计算CRC校验和*/

        u16Crc = Crc16(gtModbusData.u8RxBuf,gtModbusData.u8RxCount);

        if(u16Crc != 0)

        {

            gtModbusData.u8RxCount = 0;

            return; 

        }    

        /*如果标志位不是0,则说明是有效数据,进入分析应用层协议*/

        if(gtModbusData.u8RxCount != 0)

        {

            /*进入应用解析代码*/

            ModBusApplication();          

        }


那么我们的串口接受到这样的请求数据就需要对数据进行处理,处理方式如下:

首先对定时器的标志位进行判断,如果检测到接受数据完成标志位没有被清零,那么证明数据还在接受中,所以直接break,判定数据有没有接受完成是计算一帧数据接受完成的时间,加入波特率为9600,那么证明一秒发送9600个位,9600除以8就是多少个字节,发送请求的命令是8个字节,MODBUS定义3.5个字符间隔就可以确定数据是否接收完成,3.5个字符不是3.5个字节,而是完整的一帧数据的3.5倍时间,9600意味着一秒发送9600个位,那么也就是一秒发送1200字节,这样就可以算出8个字节需要多少时间,并且计算8个字节的3.5倍大约是多少时间从而控制定时器去定这么长时间。


定时器定时卡3.5字符时间,置标志位给轮询函数

switch(TA0IV)

    {

        case 0x02:

        {

            /*关闭定时器中断,目的防止不接收数据时定时器一直在工作*/ 

            TA0CCTL1 &= ~CCIE;      

            /*接收一个字节置位*/

            gtModbusTime.u8ModbusTimeOutFlag = ENUM_TRUE;     

            if(gtModbusTime.u8ModbusFlag == ENUM_FALSE)

            {

                gtModbusTime.u8ModbusFlag = ENUM_TRUE;      

            }   

            break;

        }

在代码解析中就可以获取到MODBUS的命令位,根据命令就可以进入不同的协议解析:


static void ModBusApplication(void)

{

    switch (gtModbusData.u8RxBuf[1])       

    {

        /*01功能读取线圈输出状态,本程序未用*/ 

        case 0x01:         

        {

            break; 

        }  

        /*02功能读取线圈输入状态,本程序未用*/

        case 0x02:         

        {

            break; 

        }  

        /*03功能读取保持寄存器*/         

        case 0x03:         

        {

            Modbus03H();    

            break;

        }  

        /*04功能读取输入寄存器,本程序未用*/

        case 0x04:         

        {

            break;   

        } 

        /*05功能写开关量输出*/

        case 0x05:         

        {

            break; 

        }  

        /*06功能写单路寄存器*/          

        case 0x06:         

        {

            Modbus06H();

            break;

        } 

        /*10功能写多个寄存器*/

        case 0x10:          

        {

            Modbus10H();

            break;

        }           

        default:     

        {

            break;

        }                               

    }

}


在不同的协议解析中,根据MODBUS的要求当传输回来是03指令时,需要将寄存器的数据按照要求的长度和CRC校验后发送出去,如果是06指令,那么就返回响应成功的指令。


03功能码的处理函数,此函数通过for语句持续将寄存器中数据打包发送,并添加CRC校验

static void Modbus03H(void)

{

    uint16_t u16Register = 0;

    uint16_t u16Number = 0;

    uint16_t u16LoopData = 0;

    /*64大小计算出来最多32个寄存器*/    

    uint8_t  u8RegisterValue[128];   

    /*查询错误标志正确*/

    gtModbusData.u8RspCode = RSP_OK;               

    if(gtModbusData.u8RxCount != 8)          

    {

        /*数值域错误,数据位数不对就直接返回*/

        gtModbusData.u8RspCode = RSP_ERR_VALUE;      

        return;

    }

    /*取寄存器的首地址*/

    u16Register = u16BeBufToUint16(& gtModbusData.u8RxBuf[2]);    

    /*取多少位的长度*/

    u16Number = u16BeBufToUint16(& gtModbusData.u8RxBuf[4]);   

    /*如果读到数据长度不对*/

    if(u16Number > sizeof(u8RegisterValue) / 2)                  

    {

        /*则数值域错误*/

        gtModbusData.u8RspCode = RSP_ERR_VALUE;                  

        return;

    }   

    for(u16LoopData = 0; u16LoopData < u16Number; u16LoopData++)

    {

        if(ModBusReadRegisterValue(u16Register,& u8RegisterValue[2 * u16LoopData]) == 0)

        {

            /*如果读到的数据长度为0,则寄存器地址错误,查询错误标志为寄存器地址错误*/

            gtModbusData.u8RspCode = RSP_ERR_REG_ADDR;  

            break;          

        }

        u16Register++;

    }

    /*如果查询标志正确*/

    if(gtModbusData.u8RspCode == RSP_OK)               

    {

        gtModbusData.u8TxCount = 0;

        /*之前获取到的地址码*/

        gtModbusData.u8TxBuf[gtModbusData.u8TxCount++] = gtModbusData.u8RxBuf[0];  

        /*功能码*/

        gtModbusData.u8TxBuf[gtModbusData.u8TxCount++] = gtModbusData.u8RxBuf[1];   

        gtModbusData.u8TxBuf[gtModbusData.u8TxCount++] = u16Number * 2;             

        for(u16LoopData = 0; u16LoopData < u16Number; u16LoopData++)

        {

            gtModbusData.u8TxBuf[gtModbusData.u8TxCount++] = u8RegisterValue[2 * u16LoopData];      

            gtModbusData.u8TxBuf[gtModbusData.u8TxCount++] = u8RegisterValue[2 * u16LoopData + 1];  

        }    

        /*计算要发送的CRC*/     

        gtUnionUword.u16word = Crc16(gtModbusData.u8TxBuf,gtModbusData.u8TxCount); 

        /*将原有数据右移8位在尾部添加CRC*/

        gtModbusData.u8TxBuf[gtModbusData.u8TxCount++] = gtUnionUword.u8byte[1];   

        gtModbusData.u8TxBuf[gtModbusData.u8TxCount++] = gtUnionUword.u8byte[0];   

[1] [2]
关键字:MSP430  MODBUS-RTU  程序 引用地址:MSP430中MODBUS-RTU的程序编写方式

上一篇:MSP430F5529学习笔记(一)——点灯|IO输出
下一篇:MSP430f2619学习—串口通信

推荐阅读最新更新时间:2024-11-04 16:42

MSP430中的SD16模块
Datesheet中的英文看着不爽,突然发现了利尔达430演讲资料中的SD16模块介绍,还有MSP430FE42X中文参考手册,总结下方便学习,先来结构图。 SD16结构图 SD16CTL,ADC控制寄存器 ReservedBit 0 保留,读出总是0。 OVIE Bit 1溢出中断使能,溢出中断矢量独立使能。 若一个转换结果写进ADC存储器SD16MEMx,但前一结果还未读,发生溢出。 REFONBit 2接通内部参考电压,如果不用参考应该关闭以节约能源。 0:内部参考关闭。 如果使用ADC,参考电压必须有外部供给,否则转换结果不可预知。 1:内部参考电压接通。 注意VREF引脚需要连接一个
[单片机]
<font color='red'>MSP430</font>中的SD16模块
串口通信_MSP430串口通信(入门级)
MSP430F5529实现双板间串口通信 年轻人,不要一上来就急着敲代码,串口通信用到的的13个寄存器,快来看看你都会了吗? 哈哈哈~~,可千万不要被这些牛鬼蛇神吓住啊,这次我们讲的是入门级的,不会设置这么多寄存器的(但也不少呦)。 准备好了吗?下面我们开讲了! 1.数据格式 (1)ST:起始位(低电平启动串口) 因为串口待机时处于长期高电平状态,当检测到有低电平时,就会启动准备接收数据。 (2)D0~D7:数据位(可以7位,也可以8位) 1对应高定平,0对应低电平(这应该都清楚吧) (3)AD:地址位:(双板通信用不到) 当多机通信时(例如一个设备发送,多个设备接收) 需要添加地址位
[单片机]
串口通信_<font color='red'>MSP430</font>串口通信(入门级)
STM8S208R8的第九个程序---占空比
#include iostm8s208r8.h #define PB /*************************************** 利用定时器1的两个PWM通道看看不同占空比下LED亮暗情况 LED是经上拉电阻接入MCU,占空比小的反而更亮 ***************************************/ void PWM_INIT() { /************不进行预分频*************/ TIM1_PSCRH=0; TIM1_PSCRL=0; /**********自动重装载值0-99***********/ TIM1_ARRH=0; TIM1_ARRL=
[单片机]
基于低成本单芯片微处理器的高稳健性破损玻璃检测器解决方案
引言 破损玻璃检测器 (GBD) 主要用来检测家庭住宅或商业楼宇门窗玻璃的破损情况。GBD 也可归类为一种监控设备,用以提高家庭或商业环境的安全性,避免非法进入。GBD 既可独立工作,也可与其它防盗设备协同工作,形成一套完整的安全系统。GBD 的基本工作原理就是捕获各种声音,并对其进行分析,然后报告玻璃是否破碎。基于这种工作模式,GBD的性能很大程度上取决于声源音质,这对设计人员提出了诸多挑战。此外,GBD 必须排除各种非真正玻璃破碎发出的声音,这种可能触发虚假玻璃破碎警报的声音事件就是误报警。本文将介绍一种采用低成本微处理器 (MCU) 的高效、稳健可靠的 GBD 设计方案。 MCU 属于低端处理器,广泛用于简单
[单片机]
基于低成本单芯片微处理器的高稳健性破损玻璃检测器解决方案
基于MSP430F2012和nRF24L01低功耗RFID定位设计
  射频识别(RFID)技术是采用无线射频的方式实现双向数据交换并识别身份,RFID定位正是利用了这一识别特性,利用阅读器和标签之间的通信信号强度等参数进行空间的定位。   RFID标签按供电方式分为有源和无源2种 ,无源标签通过捕获阅读器发射的电磁波获取能量,具有成本低、尺寸小的优势;有源标签通常采用电池供电,具有通信距离远、读取速度快、可靠性好等优点 ,但为了满足煤矿井下定位,需要考虑低功耗设计以增强电池的续航能力。本文从有源标签的设计理念出发,针对小范围空间RFID定位的需求,根据低功耗、高效率的原则进行RFID标签的设计,并阐述了其硬件组成、软件流程和防冲突能力。   2.系统硬件设计   2.1 系统结构   有
[单片机]
基于<font color='red'>MSP430</font>F2012和nRF24L01低功耗RFID定位设计
矩阵键盘扫描程序
#include reg52.h #define uint unsigned int #define uchar unsigned char uchar temp,key=16; sbit duan=P2^6; sbit wei=P2^7; sbit led0=P1^0; sbit led1=P1^1; uchar code table = {0x3f,0x06,0x5b,0x4f, 0x66,0x6d,0x7d,0x07, 0x7f,0x6f,0x77,0x7c, 0x39,0x5e,0x79,0x71,0x00}; void display(uchar); uchar scan_key(); void delay(u
[单片机]
矩阵键盘扫描<font color='red'>程序</font>
LCD1602四线编程方法程序设计实例
第一次调4线的 LCD 1602,比较困难,或者因为延迟不对,或者因为命令错误,我足足用了一天时间,搞定了4线的1602编程实现。这里提供51 单片机 和LPC23XX系列单片机的程序。51的程序是我上网找的,可以参考里面的实现步骤,可以直接仿真,但是不知道烧到板子里是否可以。LPC23XX系列单片机的程序可以直接烧写板子里运行,注意里面的管脚定义,因为在proteus里没找到对应的芯片,这里不知是否可以仿真。 51程序 #include reg51.h sbit LCM_RW=P2^0; //定义引脚 sbit LCM_RS=P2^1; sbit LCM_E =P2^2; #define LCM_Data P1 #defin
[单片机]
MSP430的软硬件C延时程序设计
MSP430是超低功耗16位单片机,越来越受到电子工程师亲睐并得到广泛应用。C程序直观,可读性好,易于移植和维护,已被很多单片机编程人员所采用。MSP430集成开发环境(如IAR Embedded Workbench和AQ430)都集成了C编译器和C语言级调试器C SPY。但是C语言难以实现精确延时,这一直困扰着很多MSP430单片机程序员。笔者在实际项目开发过程中,遇到很多需要严格时序控制的接口器件,如单总线数字温度传感器DSl8820、实时时钟芯片PCF8563(需要用普通]/o模拟12C总线时序)、三线制数字电位器AD8402、CF卡(Compact Flash Card)等都需要 s级甚至纳ns级精确延时;而一些慢速设备只需
[单片机]
小广播
设计资源 培训 开发板 精华推荐

最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

换一换 更多 相关热搜器件

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved