一、ModBus协议

ModBus是一个应用层的通信协议,广泛应用于工业控制等领域。
主要功能码有0x03(读多个寄存器),0x10(写多个寄存器),0x15(写文件)。
这里主要说明0x15(写文件)功能码

1.1 0x15(写文件)

请求

说明长度
地址域1个字节
功能码1个字节0x15
请求数据长度1个字节0x07到0xF5
子请求x,参考类型1个字节0x06
子请求x,文件号2个字节0x0000到0xFFFF
子请求x,记录号2个字节0x0000到0x270F
子请求x,记录长度2个字节N
子请求x,记录数据N*2个字节
子请求x+1…………
CRC2个字节

响应

说明长度
地址域1个字节
功能码1个字节0x15
请求数据长度1个字节0x07到0xF5
子请求x,参考类型1个字节0x06
子请求x,文件号2个字节0x0000到0xFFFF
子请求x,记录号2个字节0x0000到0x270F
子请求x,记录长度2个字节N
子请求x,记录数据N*2个字节
子请求x+1…………
CRC2个字节

例程

请求十六进制响应十六进制
域名0x09域名0x09
功能码0x15功能码0x15
请求数据长度0x0d请求数据长度0x0d
子请求1,参考类型0x06子请求1,参考类型0x06
子请求1,文件号H0x00子请求1,文件号H0x00
子请求1,文件号L0x04子请求1,文件号L0x04
子请求1,记录号H0x00子请求1,记录号H0x00
子请求1,记录号L0x07子请求1,记录号L0x07
子请求1,记录长度H0x00子请求1,记录长度H0x00
子请求1,记录长度L0x03子请求1,记录长度L0x03
子请求1,记录数据H0x22子请求1,记录数据H0x22
子请求1,记录数据L0x11子请求1,记录数据L0x11
子请求1,记录数据H0x22子请求1,记录数据H0x22
子请求1,记录数据L0x11子请求1,记录数据L0x11
子请求1,记录数据H0x22子请求1,记录数据H0x22
子请求1,记录数据L0x11子请求1,记录数据L0x11
CRC Lcrc L
CRC Hcrc H

总结:请求和响应一样

二、上位机

上位机使用C#,在Visual Studio环境下开发。

2.1 预定义

地址功能说明
0xFF00OTA状态0:APP状态,正常运行中;1:BootLoader状态,等待固件升级
0xFF02固件版本1个寄存器
0xFF04固件长度2个寄存器

2.2 主要流程图

Created with Raphaël 2.3.0 Start 写OTA_STATUS=1(ModBus写寄存器) OTA_STATUS==1?(ModBus读寄存器) 写SoftwareLength=xx(ModBus写寄存器) SoftwareLength==xx?(ModBus读寄存器) 写固件(ModBus写文件) OTA_STATUS==0?(ModBus读寄存器) End 错误,返回 yes no yes no yes no

2.3 界面

这里写图片描述

2.4 主要程序

读取信息按钮-点击事件

/*
         * 读取信息按钮-点击事件
         * 读取固件信息
         * 
         */
        private void ReadSoftwareBtn_Click(object sender, EventArgs e)
        {
            ReadSoftware();
        }

        /*
         * 读取RTU固件
         * 
         */
        private void ReadSoftware()
        {
            if (!isSerialOpen)
            {
                MessageBox.Show("请先打开串口");
                return;
            }
            if (workerStatus.isBackgroundRun)
            {
                MessageBox.Show("请稍后,后台任务运行中");
                return;
            }
            StatusLabel.Text = "connecting...";
            readSoftwareWorker.RunWorkerAsync();
        } 

        /*
         * backgroundWorker-DoWork
         * 读取RTU固件
         * 
         */
        private void readSoftwareWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            workerStatus.isBackgroundRun = true;   //后台任务-运行中
            workerStatus.isRunError = false;       //后台任务-运行正常 

            //1. 读取固件版本
            if (!ModBus_Function_3(readSoftwareVersionBuff, readSoftwareVersionBuff.Length))
            { 
                workerStatus.isRunError = true;    //后台任务-运行出错
                return;
            }
            byte high = RxBuff[3];
            byte low = RxBuff[4];
            if (high != 0xff || low != 0xff)
            {
                softwareVersion = high + low * 1.0 / 10.0;
            }

            //2. 读取固件长度
            if (!ModBus_Function_3(readSoftwareLengthBuff, readSoftwareLengthBuff.Length))
            {
                workerStatus.isRunError = true;    //后台任务-运行出错
                return;
            }
            fileLength = RxBuff[3] + ((UInt32)RxBuff[4] << 8) + ((UInt32)RxBuff[5] << 16) + ((UInt32)RxBuff[6] << 24);

            //3. 读取OTA状态
            if (!ModBus_Function_3(readOTAStatusBuff, readOTAStatusBuff.Length))
            {
                workerStatus.isRunError = true;    //后台任务-运行出错
                return;
            }
            otaStatus = (UInt16)((RxBuff[3] << 8) + RxBuff[4]);

        } 

        /*
         * backgroundWork-RunWorkerCompleted
         * 读取RTU固件
         * 
         */
        private void readSoftwareWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            workerStatus.isBackgroundRun = false;  //后台任务-运行结束
            if (workerStatus.isRunError)
            {
                StatusLabel.Text = "Connect failed!";
                return;
            } 
            StatusLabel.Text = "Connected!";
            softwareVersionLable.Text = "V "+softwareVersion.ToString("f1");
            softwareLengthLabel.Text = fileLength.ToString()+" byte";
            if(otaStatus==0)
            {
                otaStatusLabel.Text = "APP";
            }
            else
            {
                otaStatusLabel.Text = "砖头(请升级固件)";
            }
        }

升级固件按钮-点击事件

/*
         * 升级固件按钮-点击事件
         * 
         */
        private void UpgradeSoftwareBtn_Click(object sender, EventArgs e)
        { 
            if (!isSerialOpen)
            {
                MessageBox.Show("请先打开串口");
                return;
            }
            if (workerStatus.isBackgroundRun)
            {
                MessageBox.Show("请稍后,后台任务运行中");
                return;
            } 

            openFileDialog.Filter = "程序文件(*.bin)|*.bin";
            openFileDialog.FilterIndex = 1;
            if (openFileDialog.ShowDialog() == DialogResult.OK)
            {
                filePathName = openFileDialog.FileName;
                int indexOf = filePathName.IndexOf("RTU_V"); 
                if (indexOf!=-1)
                {
                    fileName = filePathName.Substring(indexOf);
                    if(fileName.Length!=12)
                    {
                        MessageBox.Show("固件名错误!");
                        return; 
                    }
                }  

                StatusLabel.Text = "downloading...";
                upgradeSoftwareWorker.RunWorkerAsync();
                toolStripProgressBar.Visible = true;
                toolStripProgressBar.Value = 0;
            }
        } 


        private void upgradeSoftwareWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            workerStatus.isBackgroundRun = true;    //后台任务-开始运行
            workerStatus.isRunError = false;        //后台任务-运行正常 

            //写固件
            byte[] writeSoftwareBuff = new byte[0xfc];

            //1. 写OTA状态,0:无/成功/跳转 ; 1:准备升级
            if(!ModBus_Funtion_16(writeOTAStatusBuff,writeOTAStatusBuff.Length))
            {   
                workerStatus.isRunError = true;
                return;
            }
            //2. 读OTA状态
            if(!ModBus_Function_3(readOTAStatusBuff,readOTAStatusBuff.Length))
            {
                workerStatus.isRunError = true;
                return;
            }
            else
            {
                if(RxBuff[4]!=0x01)
                {
                    workerStatus.isRunError = true;
                    return;
                }
            }
            //3. 写固件长度 
            file = new FileStream(filePathName, FileMode.Open);
            file.Seek(0, SeekOrigin.Begin);
            fileLength = file.Length;
            writeSoftwareLengthBuff[7] = (byte)fileLength;
            writeSoftwareLengthBuff[8] = (byte)(fileLength>>8);
            writeSoftwareLengthBuff[9] = (byte)(fileLength>>16);
            writeSoftwareLengthBuff[10] = (byte)(fileLength>>24);
            Console.WriteLine("fileLength=" + fileLength);
            UInt16 crc = 0;
            crc = CRC16(writeSoftwareLengthBuff,writeSoftwareLengthBuff.Length-2);
            writeSoftwareLengthBuff[11] = (byte)crc;
            writeSoftwareLengthBuff[12] = (byte)(crc>>8); 
            if (!ModBus_Funtion_16(writeSoftwareLengthBuff,writeSoftwareLengthBuff.Length))
            {
                workerStatus.isRunError = true;
                return;
            }
            //4. 读固件长度  
            if (!ModBus_Function_3(readSoftwareLengthBuff,readSoftwareLengthBuff.Length))
            {
                workerStatus.isRunError = true;
                return;
            }
            else
            {
                long temp_fileLength = RxBuff[3] + ((UInt32)RxBuff[4] << 8) + ((UInt32)RxBuff[5] << 16) + ((UInt32)RxBuff[6] << 24);
                if(temp_fileLength!=fileLength)
                {
                    workerStatus.isRunError = true;
                    return;
                }
            }
            //5. 写文件 
            UInt16 recordNumber = 0;
            writeSoftwareBuff[0] = DEFAULT_ADDR;                //地址域
            writeSoftwareBuff[1] = 0x15;                        //功能码
            writeSoftwareBuff[2] = 0xFC;                        //请求数据长度
            writeSoftwareBuff[3] = 0x06;                        //参考类型
            writeSoftwareBuff[4] = (byte)(fileName[5] - '0');   //文件号
            writeSoftwareBuff[5] = (byte)(fileName[7] - '0');

            writeSoftwareBuff[8] = 0x00;                        //记录长度
            writeSoftwareBuff[9] = 0xf0;

            for (; recordNumber < fileLength/0xf0; recordNumber++)
            { 
                writeSoftwareBuff[6] = (byte)(recordNumber >> 8);   //记录号
                writeSoftwareBuff[7] = (byte)recordNumber;
                file.Read(writeSoftwareBuff, 10, 0xf0);             //记录数据 
                crc = CRC16(writeSoftwareBuff, writeSoftwareBuff.Length - 2);
                writeSoftwareBuff[0xfa] = (byte)crc;
                writeSoftwareBuff[0xfb] = (byte)(crc >> 8);
                if(!ModBus_Function_15(writeSoftwareBuff,writeSoftwareBuff.Length))
                {   
                    workerStatus.isRunError = true;
                    return;
                } 
                upgradeSoftwareWorker.ReportProgress((int)(recordNumber*0xf0/((int)fileLength*1.0)*100));//报告进度
            }
            int leftLength = (int)fileLength - recordNumber * 0xf0;
            if(leftLength!=0)
            { 
                writeSoftwareBuff[6] = (byte)(recordNumber >> 8);   //记录号
                writeSoftwareBuff[7] = (byte)recordNumber;
                file.Read(writeSoftwareBuff, 10, leftLength);             //记录数据 
                crc = CRC16(writeSoftwareBuff, 10 + leftLength);
                writeSoftwareBuff[10 + leftLength] = (byte)crc;
                writeSoftwareBuff[11 + leftLength] = (byte)(crc >> 8); 
                if (!ModBus_Function_15(writeSoftwareBuff, leftLength+12))
                {
                    workerStatus.isRunError = true;
                    return;
                }
            }

            Thread.Sleep(100);

            //6. 读取OTA状态
            if (!ModBus_Function_3(readOTAStatusBuff, readOTAStatusBuff.Length))
            {
                workerStatus.isRunError = true;
                return;
            }
            else
            {
                if (RxBuff[4] != 0x00)
                {
                    workerStatus.isRunError = true;
                    return;
                }
            }
        }

        private void upgradeSoftwareWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            workerStatus.isBackgroundRun = false;
            toolStripProgressBar.Visible = false;
            file.Close();
            if (workerStatus.isRunError)
            {
                MessageBox.Show("升级失败");
                StatusLabel.Text = "OTA fail";
            }
            else
            {
                MessageBox.Show("升级成功");
                StatusLabel.Text = "OTA success";
            }
            workerStatus.isRunError = false;
        }

        private void upgradeSoftwareWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            toolStripProgressBar.Value = e.ProgressPercentage;
        }

这里写图片描述

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐