EDN首页   博客首页 用户登陆  |  注册
aaa
发表于 2009/6/12 15:27:59

2

关于投票

MODBUS协议在TI DSP TMS320F22335上的实现(一)

唉,做的仪表越来越复杂了,需要使用DSP了,开始一点一点将单片机的东西移植到DSP上面了。
先说TMS320F28335的串口吧。
有三个串口,SCIA,SCIB和SCIC,GPIO的管脚对应如下
SCIA对应GPIO28/29和GPIO35/36两组可选,SCIB有四组管脚可以选择,分别是GPIO9/11,GPIO14/15,GPIO18/19,GPIO22/23,SCIC对应的是GPIO62/63。
首先需要定义管脚。
void InitSciaGpio()    //初始化GPIO管脚
{
   EALLOW;
//根据硬件设计决定采用GPIO28/29和GPIO35/36中的哪一组。这里以35/36为例
//定义管脚为上拉
    GpioCtrlRegs.GPBPUD.bit.GPIO36 = 0;     
    GpioCtrlRegs.GPBPUD.bit.GPIO35 = 0;     
//定义管脚为异步输入
    GpioCtrlRegs.GPBQSEL1.bit.GPIO36 = 3;
//配置管脚为SCI功能管脚
    GpioCtrlRegs.GPBMUX1.bit.GPIO36 = 1; 
    GpioCtrlRegs.GPBMUX1.bit.GPIO35 = 1;  
    EDIS;
}
SCIB和SCIC的配置差不多。
下来就要定义波特率等等了。例子是9600bps,1,8,n,1.
void scia_init()
{
    SciaRegs.SCICCR.all =0x0007;   // 1 stop bit,  No loopback
                                   // No parity,8 char bits,
                                   // async mode, idle-line protocol
    SciaRegs.SCICTL1.all =0x0003;  // enable TX, RX, internal SCICLK,
                                   // Disable RX ERR, SLEEP, TXWAKE
    SciaRegs.SCICTL2.bit.TXINTENA =1; //发送中断使能
    SciaRegs.SCICTL2.bit.RXBKINTENA =1;//接收中断使能
    SciaRegs.SCIHBAUD    =0x0001;  // 9600 baud @LSPCLK = 37.5MHz.
    SciaRegs.SCILBAUD    =0x00E7;
    SciaRegs.SCICTL1.all =0x0023;  // Relinquish SCI from Reset
}
接着进行中断的配置
   EALLOW;    // This is needed to write to EALLOW protected registers
   PieVectTable.SCIRXINTA = &sciaRxIsr;
   PieVectTable.SCITXINTA = &sciaTxIsr;
   PieVectTable.SCIRXINTB = &scibRxIsr;
   PieVectTable.SCITXINTB = &scibTxIsr;
   EDIS;   // This is needed to disable write to EALLOW protected registers
上面是将SCIA和SCIB的中断服务程序连到PIE的中断表中,发生中断就会跑到你的ISR去了
下面就是开中断了。
   PieCtrlRegs.PIECTRL.bit.ENPIE = 1;   // Enable the PIE block
   PieCtrlRegs.PIEIER9.bit.INTx1=1;     // PIE Group 9, int1
   PieCtrlRegs.PIEIER9.bit.INTx2=1;     // PIE Group 9, INT2
   PieCtrlRegs.PIEIER9.bit.INTx3=1;     // PIE Group 9, INT3
   PieCtrlRegs.PIEIER9.bit.INTx4=1;     // PIE Group 9, INT4
   IER = 0x100;    // Enable CPU INT
   EINT;

哈哈,串口OK了,下面就该进行MODBUS的软件处理了。
。。。。待续

系统分类: DSP  |  用户分类: 仪表-软件-通讯  |  标签: MODBUS DSP SCI 串口  |  来源: 原创  | 

点击查看原文

发表评论 阅读全文(803) | 回复(1)

发表于 2008/9/8 12:37:01

2

关于投票

PC端程序初始化参数的处理。

PC端的许多初始化参数需要保存,我对PC机的编程是很怵的。
还是在编WM6.0下的通讯软件。使用VS2005下的C#开发。
初始化参数我见到的有这么几种。
1、使用ini文件:好像说是.net不推荐使用,就没有深入学习了。
2、使用注册表。这是MS推荐使用的,但俺怕麻烦。觉得还是绿色软件好一些。
3、使用XML。.net对XML文件的支持力度很强。俺决心就用它了。

我的XML文件是这样的。
<?xml version="1.0" encoding="utf-8"?>
<Modbus Version="1.00.00">
  <CommPort>COM1</CommPort>
  <MassUnit>T</MassUnit>
</Modbus>
编程思路是首先读取ModbusIni.xml,如果文件不存在则创建这个文件,如果文件存在则读取文件中的参数,最后在程序关闭时保存设置好的参数到ModbusIni.xml。
首先定义两个变量,mDocument代表XML文件,mCurrentNode表示选择好的节点。
        private XmlDocument mDocument;
        private XmlNode mCurrentNode;
        public Form1()
        {
            InitializeComponent();
            foreach (string s in SerialPort.GetPortNames())
            {
                comboBox1.Items.Add(s);   //通讯端口的选择
            }
     
            bool bSave = false;
            mDocument = new XmlDocument();
            try
            {
                mDocument.Load(@"ModbusIni.xml");//判断文件Load正确否
            }
            catch
            {
                // 走到这里就失败了,文件不存在或其它错误
                bSave = true;
                CreatXmlDocument();//创建文件
            }
            if (!bSave)
            {
                ReadXmlDocument();
            }
        }

        private void CreatXmlDocument()               //建立新的文件          
        {
            //定义XML文档头文件
            XmlDeclaration xmlDeclaration = mDocument.CreateXmlDeclaration("1.0", "utf-8", null);
            //增加XML文档头
            mDocument.AppendChild(xmlDeclaration);
            //定义XML的根
            XmlElement xmlRoot = mDocument.CreateElement("Modbus");
            //添加XML的根
            mDocument.AppendChild(xmlRoot);
            //添加根的属性
          xmlRoot.SetAttribute("Version", "1.00.00");
          XmlElement newCommPort = mDocument.CreateElement("CommPort");
          XmlElement newMassUnit = mDocument.CreateElement("MassUnit");
     
            XmlText CommPort = mDocument.CreateTextNode("COM1");
            XmlText MassUnit = mDocument.CreateTextNode("T");

            newCommPort.AppendChild(CommPort);
            newMassUnit.AppendChild(MassUnit);

            //添加XML根的节点
            xmlRoot.AppendChild(newCommPort);
            xmlRoot.AppendChild(newMassUnit);

            // Save the document to a file.
            mDocument.Save("ModbusIni.xml");
        }
        private void ReadXmlDocument()               //读取Xml文件
        {
            XmlElement xmlRoot = mDocument.DocumentElement;
          //使用SelectSingleNode查找到想要的参数
            mCurrentNode = xmlRoot.SelectSingleNode("CommPort");
            comboBox1.Text = mCurrentNode.InnerText;

        }
        private void SaveXmlDocument()               //保存Xml文件
        {
            XmlElement xmlRoot = mDocument.DocumentElement;
//使用SelectSingleNode查找到想要的参数,
            mCurrentNode = xmlRoot.SelectSingleNode("CommPort");
            mCurrentNode.InnerText = comboBox1.Text ;

            mDocument.Save("ModbusIni.xml");
        }

        //程序退出时保存XML文件
        private void Form1_Closed(object sender, EventArgs e)
        {
            SaveXmlDocument();
        }

以上程序在VS2005下编译成功,在多普达P660上运行成功。

系统分类: 工业控制  |  用户分类: 仪表-软件-通讯  |  标签: 初始化参数  |  来源: 原创  | 

点击查看原文

发表评论 阅读全文(855) | 回复(0)

发表于 2008/8/4 12:52:15

2

关于投票

串行通讯中浮点数和整型数的处理办法

串行通讯是以字节为单位进行传送的,对于浮点数和整型数都需要进行转换才能进行通讯。

MCU和PC的浮点数都是基于IEEE754格式的。有4字节(float)、8字节(double)、10字节(有一些不支持)。这里以4字节(float)浮点数为例。


转化常见的方法有:
一、强制指针类型转换。
 //   转换Int数据到字节数组   
unsigned int intVariable,i;
unsigned char charArray[2];
(unsigned char) * pdata = ((unsigned char)*)&intVariable;  //进行指针的强制转换 
for(i=0;i<2;i++)
{
    charArray[i] = *pdata++;    
}    
//   转换float数据到字节数组
unsigned int i;
float floatVariable;
unsigned char charArray[4];
(unsigned char) * pdata = ((unsigned char)*)&floatVariable;  //进行指针的强制转换
for(i=0;i<4;i++)
{
    charArray[i] = *pdata++;    
}
//   转换字节数组到int数据
unsigned int   intVariable="0";
unsigned char  i;
void   *pf;    
pf   =&intVariable;
(unsigned char) * px = charArray; 
for(i=0;i<2;i++)
{
 *(((unsigned char)*)pf+i)=*(px+i);    

//   转换字节数组到float数据
float   floatVariable="0";
unsigned char  i;
void   *pf;    
pf   =&floatVariable;
(unsigned char) * px = charArray; 
for(i=0;i<4;i++)
{
 *(((unsigned char)*)pf+i)=*(px+i);    
}  
二、使用结构和联合,这是我最喜欢的方法
定义结构和联合如下
typedef union {struct {unsigned char low_byte;
           unsigned char mlow_byte;
           unsigned char mhigh_byte;
           unsigned char high_byte;
          }float_byte;
       struct {unsigned int low_word;
          unsigned int high_word;
          }float_word;
       float  value;
      }FLOAT;

typedef union   {
        struct {
        unsigned char low_byte;
        unsigned char high_byte;
        } d1;
    unsigned int value;
    } INT;


使用方法:
对于浮点数:
FLOAT floatVariable;在程序中直接使用floatVariable.float_byte.high_byte,floatVariable.float_byte.mhigh_byte,
floatVariable.float_byte.mlow_byte,floatVariable.float_byte.low_byte这四个字节就可以方便的进行转换了。
对于整数:
INT intVariable;在程序中直接使用intVariable.value.high_byte,intVariable.value.low_byte就OK了。
三、对整型数可以用数学运算的方法进行转换
unsigned int intVariable;
unsigned char low_byte = intVariable%256;
unsigned char high_byte = intVariable/256;


系统分类: 单片机  |  用户分类: 仪表-软件-通讯  |  标签: IEEE754 浮点转字节  |  来源: 原创  | 

点击查看原文

发表评论 阅读全文(996) | 回复(0)

发表于 2008/7/31 9:38:55

3

关于投票

串口接收二进制数的显示问题。

串口通讯由于下位机一般都是MCU,不支持Unicode编码。我们采用二进制数据进行通讯。接收的数据为byte[]格式的字节串。而PC机的显示都是Unicode编码的string字符串。我们需要进行转化。
下面代码ToCharString()是byte[]格式的字节串转化成HEX十六进制数的显示方式。例如MCU发送“12345”,接收将显示“3132333435”。
 ToCharString()是 byte[]格式的字节串转化成Unicode编码的string字符串的显示方式 。例如MCU发送“12345”,接收将显示“12345”。


      static char[] hexDigits = {
        '0', '1', '2', '3', '4', '5', '6', '7',
        '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};

        public static string ToHexString(byte[] bytes)
        {
            char[] chars = new char[bytes.Length * 2];
            for (int i = 0; i < bytes.Length; i++)
            {
                int b = bytes[i];
                chars[i * 2] = hexDigits[b >> 4];
                chars[i * 2 + 1] = hexDigits[b & 0xF];
            }
            return new string(chars);
        }
        public static string ToCharString(byte[] bytes)
        {
            char[] chars = new char[bytes.Length];
            for (int i = 0; i < bytes.Length; i++)
            {
                chars[i ] = (char)bytes[i];
               
            }
            return new string(chars);
        }

系统分类: 工业控制  |  用户分类: 仪表-软件-通讯  |  标签: 串口 通讯  |  来源: 原创  | 

点击查看原文

发表评论 阅读全文(1340) | 回复(0)

发表于 2008/7/30 20:10:09

3

关于投票

Windows Mobile 6下使用serialPort控件进行串口通讯。

在仪表的Modbus通讯中,上位机可以是PLC、PC、PDA或智能手机。本人使用Windows Mobile 6操作系统下的多普达P660进行了上位机的编程。现在将调试程序说一下。

使用VS2005进行开发。

控件有3个button,两个TextBox,两个Label和一个SerialPort等。button1打开串口,button2将txtSend的字符发出,接收是自动的。接收txtReceive显示的是字符ASCII码的十进制数字,例如发送“12345”,txtReceive显示为“4950515253”。

txtReceive显示使用了在线程上异步执行指定委托。使用BeginInvoke和delegate完成。

接收使用事件private void serialPort1_DataReceived(object sender, SerialDataReceivedEventArgs e)。

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.IO.Ports;
using System.Threading;

namespace modbuscomm
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }
        private  String strReceive ;
        public delegate void InvokeDelegate();
        public void Display()
        {
            txtReceive.Text = strReceive;
        }

        private void button1_Click(object sender, EventArgs e)
        {
            //更改参数
            serialPort1.PortName = "COM6";
            serialPort1.BaudRate = 9600;
           // serialPort1.Parity = Parity.Odd; 默认是none
            serialPort1.StopBits = StopBits.One;

            //打开串口(打开串口后不能修改端口名,波特率等参数,修改参数要在串口关闭后修改)
            serialPort1.Open();

        }

        private void button2_Click(object sender, EventArgs e)
        {
            //发送数据
            //SendStringData(serialPort1);
            //serialPort1.Write(txtSend.Text);
            //也可用字节的形式发送数据
            SendBytesData(serialPort1); 
        }
 

  //发送字符串数据
                private   void   SendStringData(SerialPort   serialPort)
                {
                        serialPort.Write(txtSend.Text);
                }


                //发送二进制数据
                private   void   SendBytesData(SerialPort   serialPort)
                {
                        byte[]   bytesSend="System".Text.Encoding.Default.GetBytes(txtSend.Text   );
                        serialPort.Write(bytesSend,   0,   bytesSend.Length);
                }

        private void serialPort1_DataReceived(object sender, SerialDataReceivedEventArgs e)
        {

                //   接收缓冲区中数据的字节数
                int int_Len = serialPort1.BytesToRead;

                //   接收数据
                byte[] bytes = new byte[int_Len];
                serialPort1.Read(bytes, 0, int_Len);


                //   将数据存入字符串缓冲区中
                for (int i = 0; i < bytes.Length; i++)
                {
                    strReceive += bytes[i];
                }
                strReceive += "";
           
            txtReceive.BeginInvoke(new InvokeDelegate(Display));
        }

       } 
}  

 

系统分类: 工业控制  |  用户分类: 仪表-软件-通讯  |  标签: serialPort 通讯 Modbus Windows Mobile  |  来源: 原创  | 

点击查看原文

发表评论 阅读全文(2420) | 回复(2)

发表于 2008/6/5 13:21:47

3

关于投票

modbus协议-PC端的实现

我使用C++ Builder 6.0调用Win32 API实现的。
首先开一个线程。
void __fastcall TForm1::FormCreate(TObject *Sender)
{
           ReadComm =new TRead232(true);
        ReadComm->FreeOnTerminate = true;    
}
通过一个按钮实现打开/关闭串口
void __fastcall TForm1::Button1Click(TObject *Sender)
{
    if(Button1->Caption == "打开通讯端口")  //按钮名称是"打开通讯端口"?
    {
        char *ComNo;
        DCB dcb;
        String Temp;
        Temp = "COM"+IntToStr(rdCom->ItemIndex+1);
//        TRadioGroup *rdCom; 定义要选择的串口
        ComNo = Temp.c_str();
        hComm =CreateFile(ComNo,GENERIC_READ|GENERIC_WRITE,
                0,NULL,OPEN_EXISTING,0,0);
        if(hComm == INVALID_HANDLE_VALUE)
        {
                MessageBox(0,"打开串口失败!!","Set Error",MB_OK);
                return;
        }
        GetCommState(hComm,&dcb);
        dcb.BaudRate = CBR_9600;
        dcb.ByteSize = 8;
        dcb.Parity = NOPARITY;
        dcb.StopBits = ONESTOPBIT;
        if(!SetCommState(hComm,&dcb))
        {
                MessageBox(0,"打通讯端口设置错误!!","Set Error",MB_OK);
                CloseHandle(hComm);
                return;
        }
        Timer1->Enabled = True;
        Button1->Caption = "关闭串口";
        Shape1->Brush->Color = clRed;
        //启动接收线程

        ReadComm->Resume();
    }
    else
    {
        Button1->Caption = "打开通讯端口";
        CloseHandle(hComm);
        Shape1->Brush->Color = clBlack;
        Timer1->Enabled = False;
        ReadComm->Terminate();

    }
}
接收由线程TRead232完成
void __fastcall TRead232::Execute()
{
        SetName();
        //---- Place thread code here ----
        if(!Terminated) Synchronize(ReadData);
}
//---------------------------------------------------------------------------
void __fastcall TRead232::ReadData()
{
         Form1->Caption = "Updated in a thread";

        String Temp;
        char inbuff[1024];
        DWORD nBytesRead,dwEvent,dwError;
        COMSTAT cs;
        if(hComm == INVALID_HANDLE_VALUE)
        {
             MessageBox(0,"串口失败!!","Set Error",MB_OK);

                return;
        }
        ClearCommError(hComm,&dwError,&cs);
        Form1->Edit1->Text = cs.cbInQue;
        if(cs.cbInQue > sizeof(inbuff))
        {
                PurgeComm(hComm,PURGE_RXCLEAR);
                return;
        }
        ReadFile(hComm,inbuff,cs.cbInQue,&nBytesRead,NULL);
        inbuff[cs.cbInQue]='\0';
        Form1->Memo1->Text =Form1->Memo1->Text+inbuff;
}
发送是定时器TIMER1触发,定时发送的。
void __fastcall TForm1::Timer1Timer(TObject *Sender)
{
        char SendData[20];
        byte ch;
        int i;
        unsigned long lrc,BS;
        if (hComm==0) return;
        SendData[0] = 0x41;
        SendData[1] = 0x03;
        SendData[2] = 0x00;
        SendData[3] = 0x01;
        SendData[4] = 0x00;
        SendData[5] = 0x04;
        SendData[6] = 0x1b;
        SendData[7] = 0x09;
        SendData[8] = '\0';
//        BS = 8;           // StrLen(SendData);
        for(i=0;i<=7;i++)
        {
                ch = SendData[i];
                WriteFile(hComm,&ch,1,&lrc,NULL);
        }
}
原来想一起发的,发现不成功,还是一个一个发就没有问题了。

系统分类: 工业控制  |  用户分类: 仪表-软件-通讯  |  标签: MODBUS 通讯协议  |  来源: 原创  | 

点击查看原文

发表评论 阅读全文(996) | 回复(0)

发表于 2008/6/5 12:14:27

7

关于投票

modbus协议--51端程序的实现

RTU需要一个定时器来判断3.5个流逝时间。
#define ENABLE    1
#define DISABLE    0
#define TRUE    1
#define FAULT    0
#define RECEIVE_EN    0
#define TRANSFER_EN    1
#define MAX_RXBUF  0x20

extern unsigned char emissivity;
extern unsigned char tx_count,txbuf[15];
extern unsigned char rx_count,rxbuf[15];
extern unsigned char tx_number,rx_number;
extern bit rx_ok;
unsigned char rx_temp;
void InitTimer1()            //针对标准8051
{
    TMOD=(TMOD|0xf0)&0x1f;    //将T1设为16位定时器
    TF1=0;
    TH1=0x62; //设T1位3.5位的接收时间35bit/9600bit/s=3.646ms
    TL1=0x80;//晶振为11.0592MHz,T=65535-3.646ms*11.0592MHz/12=0xf2df
            //0x6280是22.1184M下LPC9XX下的值。
    ET1=1;                                    //允许T1中断
    TR1=1;                                    //T1开始计数
}

void timer1() interrupt 3 using 2 //定时器中断
{
    TH1=0x62;    //3.646ms interrupt
    TL1=0x80;
    if(rx_count>=5)    //超时后,若接收缓冲区有数则判断为收到一帧
    {
        rx_ok=TRUE;
    }
}

void scomm() interrupt 4 using 3    //modbus RTU模式
{
    if(TI)
    {
        TI = 0;
        if(tx_count < tx_number)    //是否发送结束
        {
            SBUF = txbuf[tx_count];
        }
        tx_count++;
    }
    if(RI)
    {
        rx_temp=SBUF;
        if(rx_ok==FAULT)    //已接收到一帧数据,在未处理之前收到的数舍弃
        {
            if(rx_count
                rxbuf[rx_count]=rx_temp;
            rx_count++;
        }
        TH1=0x62;        //timer1 reset,count again
        TL1=0x80;
        RI=0;
    }
}
在主循环中判断标志rx_ok来执行帧处理。
if(rx_ok)
        {
            ParseFrame();
            KB0=1;
            REN=0;
            tx_count=0;
            TI=1;       //启动发送响应帧
            rx_count=0;
            rx_ok=0;
        }
WORD MAKEWORD(a, b)
{
    int_byte itemp;
    itemp.items.high=a;   
    itemp.items.low=b;
    return (itemp.item);   
}
// 解析帧并发送响应帧 (在帧完整的前提下调用)
bit ParseFrame()
{
    unsigned char byAddr ;    // 地址
    unsigned char byFunCode ;    // 功能代码
    int_byte wCRC;

   
    wCRC.item = MAKEWORD(rxbuf[rx_count-1], rxbuf[rx_count-2]);
    if(wCRC.item != CRC(rxbuf, rx_count-2))    // 判断校验是否正确
    return FALSE;

    // 正式解析
    byAddr = rxbuf[0];    // 地址
    byFunCode = rxbuf[1];    // 功能代码

    // 如果地址不对
    if( (byAddr != m_byAddress) && (byAddr != 0) )
        return FALSE;

    if(byAddr == m_byAddress)
    {
        AddSendByte(m_byAddress) ;    // 地址
        switch( byFunCode )
        {
        case 3:            // 读保持寄存器
            Fun3(3);
            break;
        ....// 添加命令散转
        ......
        default:
            ErroRespond(1);
            return FALSE;
            break;
        }
    }   

    wCRC.item = CRC(txbuf,tx_number);

    AddSendByte(wCRC.items.low);
    AddSendByte(wCRC.items.high);
    return TRUE;
}
// 根据接收帧模式发送相应,模式的数据
BOOL AddSendByte(const BYTE byData)
{
    txbuf[tx_number]=byData;
    tx_number++;
    if(tx_number>30)return FALSE;
    return TRUE;
}

// 异常响应            描述        响应解释
//   01              无效功能    变送器不允许执行收到的功能
//   02              无效地址    数据栏中的地址是不允许的
//   03              无效数据    数据栏中的数据是不允许的
//   06              忙        收到的消息没错,但从机正在执行一个长的程序命令
bit ErroRespond(const unsigned char byErroCode)
{
//    printf("\nErroRespond%02X \n", byErroCode);
    if( !AddSendByte(rxbuf[1] | 0x80) )
        return FALSE;
    return AddSendByte(byErroCode);   
}
//***CRC Calculation for MODBUS Protocol for VC++***//
//数组snd为地址等传输字节,num为字节数//
unsigned int CRC(unsigned char *snd, unsigned char num)
{
    unsigned char i, j;
    unsigned int c,crc=0xFFFF;
    for(i = 0; i < num; i ++)
    {
        c = snd[i] & 0x00FF;
        crc ^= c;
        for(j = 0;j < 8; j ++)
        {
            if (crc & 0x0001)
            {
                crc>>=1;
                crc^=0xA001;
            }
            else crc>>=1;
        }
    }   
    return(crc);
}

系统分类: 工业控制  |  用户分类: 仪表-软件-通讯  |  标签: modbus 8051 源程序  |  来源: 原创  | 

点击查看原文

发表评论 阅读全文(4513) | 回复(30)

发表于 2008/6/5 11:17:25

2

关于投票

仪表的现场总线通讯--modbus(4)

1       数据传送模式

MODBUS允许两种传送模式:ASCII模式和RTU模式。传送模式由主机决定。在ASCII模式,消息由7ASCII字符组成。在RTU模式,消息由8位二进制字符组成。MODBUS拥有几种不同的错误校验,ASCII模式使用LRC校验,RTU模式使用CRC校验。

下表给出ASCII模式和RTU模式的比较:

 

ASCII模式(7bits

RTU模式(8bits

代码系统

十六进制(ASCII字符09AF

8位二进制

位个数

起始位

1

1

数据位

7

8

校验位

1

1

停止位

12

12

错误校验

LRC

CRC

1.1    ASCII模式消息帧

ASCII模式,每个消息帧都包括一个起始符、一个地址栏、一个功能栏、一个数据栏、一个校验栏和一个结束符。

冒号符(:)作为起始符,回车符(CR)和换行符(LF)是结束符。

ASCII模式允许在两个字符之间有最长1秒的时间间隔。

下图举例说明一个ASCII模式消息帧:

帧起始 地址栏 功能栏 数据栏 校验栏 帧结束 准备就绪
2字符模式
2字符 2字符 N4字符 2字符 回车 换行
16位模式
16 16 N16 16 回车 换行

1.2    RTU模式消息帧

RTU模式,每个消息帧都包括一个地址栏、一个功能栏、一个数据栏和一个校验栏。

一帧消息传送完毕后,必须等待可发送3?个字符的时间,这一时间间隔用来同步MODBUS RTU通讯。

下图举例说明一个RTU模式消息帧:

流逝时间 地址栏 功能栏 数据栏 校验栏 流逝时间
大于发送3?个字符的时间 8位)  1字符 8位)  1字符 8位)  1字符 8位)  1字符 大于发送3?个字符的时间
           
           
           
           
           
           
           
         

1.3    错误检查

错误检查包括硬件奇偶校验、ASCII模式的LRC校验和RTU模式的CRC校验.

1.3.1   硬件奇偶校验

偶校验

    数据位与校验位的和为偶数。

奇校验

数据位与校验位的和为奇数。

1.3.2   ASCII模式的LRC校验

LRC校验数与ASCII模式消息帧的地址栏、功能栏、数据栏的总和为零。

在计算时,不包括起始符冒号(:)和结束符回车(CR)与换行(LF)。

下面给出LRC的计算程序:

static unsigned char LRC(auchMsg, usDataLen)

unsigned char *auchMsg ; /* message to calculate LRC upon */

unsigned short usDataLen ; /* quantity of bytes in message */

 {

unsigned char uchLRC = 0 ; /* LRC char initialized */

while (usDataLen––) /* pass through message buffer */

uchLRC += *auchMsg++ ; /* add buffer byte without carry */

return ((unsigned char)(–((char)uchLRC))) ; /* return twos complement */

 }

1.3.3   RTU模式的CRC校验

产生CRC校验值的过程

1、  调用一个16位寄存器,写入十六进制数FFFF。我们称这一寄存器为CRC寄存器。

2、  异或消息帧的第一个8位数,结果存在CRC寄存器。

3、  CRC寄存器右移一位,最高位(MSB)填零,然后检查移出位。

4、  如果移出位为0,则重复第3步(继续右移);

   如果移出位为1CRC寄存器异或十六进制数A0001

5、  重复步骤34直到8次右移结束。

6、  异或消息帧的下一个8位数,结果存在CRC寄存器。

7、  重复步骤36直到消息帧的所有数据被处理。

8、  最后得到的2字节数(16位)就是CRC校验值。

下面给出CRC的计算程序:

//***CRC Calculation for MODBUS Protocol for VC++***//
//
数组snd为地址等传输字节,num为字节数//
unsigned int mb_crc(BYTE *snd,int num)
{ int i,j;
unsigned int c,crc=0xFFFF
for (i=0;i
{ c=str[i] & 0x00FF;
crc^=c;
for(j=0,j<8,j++)
{ if (crc & 0x0001)
{crc>>=1;crc^=0xA001;}
else crc>>=1
}
}
return(crc);
}

系统分类: 工业控制  |  用户分类: 仪表-软件-通讯  |  标签: 无标签  |  来源: 无分类  | 

点击查看原文

发表评论 阅读全文(930) | 回复(0)

发表于 2008/6/5 11:14:31

2

关于投票

仪表的现场总线通讯--modbus(3)

1       MODBUS消息帧

1.1    查询帧和广播帧

主控制器可以发出查询帧和广播帧。查询帧对应一个来自网络设备的响应帧。广播帧通知所有设备,不需要响应。每一帧均有一个地址栏、一个功能栏、一个数据栏和一个校验栏。

 

地址栏

功能栏

数据栏

校验栏

 

1.2    广播帧和地址0

任何一个使用从机地址0的查询帧就是广播帧。在广播帧中只有MODBUS功能5, 6, 8, 15,16才是有效的。

1.3    地址栏

在查询帧中地址栏为变送器的轮询地址。在响应帧中地址栏为响应设备的轮询地址。在广播帧中地址栏为一个0,它告诉网络上的设备不需要回答。

1.4    功能栏

查询帧或广播帧中,功能栏位含有一个功能码,它表示对在数据拦中映射地址的阅读指令,书写指令或诊断的指令。在一个响应帧中,功能栏位含有功能码用来验证装置对指令的响应。 如果在功能栏位中的最高有效位元被设定,数据拦含有异常响应用来说明在处理命令中遇到的任何错误。

变送器使用了MODBUS功能码的一部分。包括读指令、写指令和诊断指令。

        读指令:包括功能01(读卷状态),02(读输入状态),03(读保持寄存器),04(读输入寄存器),17(读设备识别码)。

        写指令:包括功能05(写卷),06(写寄存器),15(写多卷),16(写多寄存器)。

        诊断指令:包括功能07(读异常状态),08(循环诊断)。

下表给出变送器支持的功能。

功能码

类型

描述

功能解释

01

读卷状态

读一个或连续卷的开关状态

02

读输入状态

读一个或连续离散量的开关状态

03

读保持寄存器

读一个或连续保持寄存器的二进制值

04

读输入寄存器

读一个或连续输入寄存器的二进制值

05

写卷

设置单个卷的开关状态

06

写寄存器

写二进制值到保持寄存器

07

诊断

读异常状态

读输入寄存器30125的状态位

08

诊断

循环诊断

发送一条测试消息给变送器用于评估通讯过程

15

写多卷

设置连续卷的开关状态

16

写多寄存器

写二进制值到连续的保持寄存器

17

读设备识别码

返回设备类型和操作状态


 



例如请求读17号从机卷2056的状态

查询帧

区域名

数值(十六进制)

从机地址

11

功能码

01

起始地址高

00

起始地址低

13

卷个数高

00

卷个数低

25

校验(LRCCRC

响应帧

区域名

数值(十六进制)

从机地址

11

功能码

01

字节数

05

数据(卷2720

CD

数据(卷3528

6B

数据(卷4336

B2

数据(卷5144

0E

数据(卷5652

1B

校验(LRCCRC

       依照惯例,高位在左,低位在右。没有用的位添0


例如请求读17号从机寄存器4010840110的状态

查询帧

区域名

数值(十六进制)

从机地址

11

功能码

03

起始地址高

00

起始地址低

6B

卷个数高

00

卷个数低

03

校验(LRCCRC

响应帧

区域名

数值(十六进制)

从机地址

11

功能码

03

字节数

06

数据高(寄存器40108

02

数据低(寄存器40108

2B

数据高(寄存器40109

00

数据低(寄存器40109

00

数据高(寄存器40110

00

数据低(寄存器40110

64

校验(LRCCRC


 


1.5    数据栏

在查询帧和广播帧中,数据栏包含需要从机去执行的信息。在响应帧中,数据栏包含从机执行的结果或者异常响应。数据拦可以包含数值、位址基准、限度或异常响应。

如果从机在响应主机命令时出现错误,那么响应帧的功能栏的最高位将被置高,数据栏包含为什么从机不能执行命令的代码。

异常响应代码如下:

异常响应

描述

响应解释

01

无效功能

变送器不允许执行收到的功能

02

无效地址

数据栏中的地址是不允许的

03

无效数据

数据栏中的数据是不允许的

06

收到的消息没错,但从机正在执行一个长的程序命令

1.6    校验栏

校验栏用于检查主机与网络设备之间传送的信息是否有错。

系统分类: 工业控制  |  用户分类: 仪表-软件-通讯  |  标签: 无标签  |  来源: 原创  | 

点击查看原文

发表评论 阅读全文(841) | 回复(3)

发表于 2008/6/5 11:08:44

2

关于投票

仪表的现场总线通讯--modbus(2)

1       轮询地址和映象地址

在一个多站网络中,变送器模拟一台可编程逻辑控制器与兼容Modbus协议的主控制器进行通讯。每一台变送器都拥有1247中的一个唯一的轮询地址。主控制器使用轮询地址开始与网络中的一个设备进行通讯或者使用地址0对网络中的所有设备广播一条消息。

同样变送器拥有映象地址来模拟PLC卷、离散输入、输入寄存器和保持寄存器。这样的地址在变送器的微处理机中对应特定的存储区。变送器同样拥有映射到与输入寄存器和保持寄存器同样的存储区的浮点数和字符串寄存器。主控制器通过读写一个或一串存储器来进行通讯。

变送器支持兼容Modbus协议的主控制器所使用的标准数据类型。变送器支持以下数据类型:

        无符号16位整数,065535。寄存器地址为3XXXX4XXXX

      单字节和3字节整数,用来表示设备鉴别码或序列号。寄存器地址为3XXXX4XXXX

        8ASCII字符串,每个16位寄存器存储两个。寄存器地址为5XXXX

        浮点数,四字节IEEE754格式。寄存器地址为2XXXX

下表给出变送器支持的数据类型:

映射地址

可使用的功能

地址类型

存取方式

描述

0XXXX

01,05,15

读写

每个卷表示单个开关位

1XXXX

02

离散输入

只读

每个卷表示单个开关位

2XXXX

03,04,06,16

浮点寄存器

只读/读写

两个连续16位寄存器表示一个浮点数

3XXXX

04

输入寄存器

只读

每个寄存器表示一个16位无符号整数

4XXXX

03,06,16

保持寄存器

读写

每个寄存器表示一个16位无符号整数

5XXXX

03,04,06,16

ASCII字符

读写

每个寄存器表示两个ASCII字符

系统分类: 工业控制  |  用户分类: 仪表-软件-通讯  |  标签: 无标签  |  来源: 无分类  | 

点击查看原文

发表评论 阅读全文(784) | 回复(0)

发表于 2008/6/5 11:06:15

1

关于投票

仪表的现场总线通讯--modbus(1)

现在的仪表已经不是单独的仪表,而是自动化系统网络的一部分。仪表到网络的连接通常使用现场总线(fieldbus)。
仪表行业用的最多的是HART、FF和PROFIBUS。
但受知识产权保护的影响,国内厂家正式成为这几家基金会成员的很少。
而基于RS485总线的MODBUS协议在国内外是广泛使用。
MODBUS是起源于PLC的一种标准通讯协议。
使用这种方式可以这样理解。
物理层采用RS485。数据链路层规定了帧格式。相当于完成主机RAM和从机RAM的对应关系。应用层是需要自己定义的,定义RAM每个单元的对应物理量。例如地址0 0002代表  “开始/停止总累积”。

系统分类: 工业控制  |  用户分类: 仪表-软件-通讯  |  标签: 无标签  |  来源: 原创  | 

点击查看原文

发表评论 阅读全文(634) | 回复(0)

Total , Page /