文章目录
- 一、ModBus协议
- 1.1 0x15(写文件)
- 请求
- 响应
- 例程
- 二、上位机
- 2.1 预定义
- 2.2 主要流程图
- 2.3 界面
- 2.4 主要程序
- 读取信息按钮-点击事件
- 升级固件按钮-点击事件
一、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 | …… | …… |
CRC | 2个字节 |
响应
说明 | 长度 | 值 |
地址域 | 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 | …… | …… |
CRC | 2个字节 |
例程
请求 | 十六进制 | 响应 | 十六进制 |
域名 | 0x09 | 域名 | 0x09 |
功能码 | 0x15 | 功能码 | 0x15 |
请求数据长度 | 0x0d | 请求数据长度 | 0x0d |
子请求1,参考类型 | 0x06 | 子请求1,参考类型 | 0x06 |
子请求1,文件号H | 0x00 | 子请求1,文件号H | 0x00 |
子请求1,文件号L | 0x04 | 子请求1,文件号L | 0x04 |
子请求1,记录号H | 0x00 | 子请求1,记录号H | 0x00 |
子请求1,记录号L | 0x07 | 子请求1,记录号L | 0x07 |
子请求1,记录长度H | 0x00 | 子请求1,记录长度H | 0x00 |
子请求1,记录长度L | 0x03 | 子请求1,记录长度L | 0x03 |
子请求1,记录数据H | 0x22 | 子请求1,记录数据H | 0x22 |
子请求1,记录数据L | 0x11 | 子请求1,记录数据L | 0x11 |
子请求1,记录数据H | 0x22 | 子请求1,记录数据H | 0x22 |
子请求1,记录数据L | 0x11 | 子请求1,记录数据L | 0x11 |
子请求1,记录数据H | 0x22 | 子请求1,记录数据H | 0x22 |
子请求1,记录数据L | 0x11 | 子请求1,记录数据L | 0x11 |
CRC L | crc L | ||
CRC H | crc H |
总结:请求和响应一样
二、上位机
上位机使用C#,在Visual Studio环境下开发。
2.1 预定义
地址 | 功能 | 说明 |
0xFF00 | OTA状态 | 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;
}