IAP上位机开发
串口类型和串口名
由于使用到的串口类型和串口名都是系统自带的,我们所能做的只是将电脑中可用的串口搜索出来,并且在上位机上面显示出来供我们使用。因此,我们是没办法自己编辑串口名和串口类型来使用的。因此在设置这两个功能的combo Box的时候,需要修改一下他们的DropDownStyle
属性,将其修改为DropDownList
,就可以让这两个下拉列表只能够使用提供的选项,而不能由用户自定义编辑。
而串口的波特率有些时候由于预设的波特率不满足需求,需要用户自定义波特率,因此这个时候,下拉列表的DropDownStyle
属性就不需要修改,保持为DropDown
就行了。
传输文件的打开
我们需要一个button来打开所需要传输的文件,并且将打开了的文件路径显示在一个text box中。但是由于IAP串口升级固件不是所有的文件都能够传输的,因此需要进行判断,不能够传输的文件就无法打开。通过messagebox
报告错误信息.
控制编辑的数据内容
由于波特率和下载的初始地址是用户自定义编辑的,因此我们需要控制用户编辑的内容是符合要求的. 比方说波特率的输入只能是数字,不能有其他的内容. 否则波特率就是不合法的,会导致程序错误. 同样的,文件传输的开始地址也是如此. 而要要控制编辑的内容,需要添加keypress事件,在添加的keypress事件函数中添加我们所需要规定的内容就可以了. 具体的实现函数如下所示:
// 传输开始地址编辑内容控制
private void textBox2_KeyPress(object sender, KeyPressEventArgs e)
{
e.Handled = "0123456789ABCDEFabcdef\b".IndexOf(char.ToUpper(e.KeyChar)) < 0;
}
// 波特率编辑内容控制
private void comboBox3_KeyPress(object sender, KeyPressEventArgs e)
{
e.Handled = "0123456789\b".IndexOf(char.ToUpper(e.KeyChar)) < 0;
}
要想添加keypress事件可以通过属性边上的闪电标志进行添加.
串口RS232下载
这是整个IAP通过USART串口升级最重要的部分
这个部分可以分为几个部分,我分别进行介绍
获取所需传输的文件信息
要通过串口升级固件,首先就是要知道我们需要发送的文件是什么,内容如何.
这里我们通过一个class类将文件所需的一些基本信息包括进来
public class FilePartInfo
{
public uint PartStartAddress; //开始地址
public int PartEndAddress; //结束地址
public int PartSize; //大小
public int PartChecksum; //校验位
public byte[] PartData; //数据
}
由于一个文件太大,不可能一次性发送完成,因此我们的基本信息是一部分一部分的进行收集的.
这个class是将每个部分的信息保存起来,然后将所有部分的信心,统一保存在一个list<FilePartInfo>
中.
而要获取所需传输文件的信息,在串口下载的时候还需要进行判断获取信息的过程是否成功,如果失败了,则串口升级就失败了,就需要停止串口下载升级固件.因此,我们需要这个函数返回一个bool类型的值 . 但也需要将所需文件的信息也返回出来,在c#中有一个关键词可以让一个函数返回多个值,就是out
. 我们让整个函数返回bool类型的值,然后通过out修饰函数的参数,在函数中对out所修饰的值进行修改,就可以达到返回多个值的目的. 只是out修饰的参数并不是函数的返回值,但是已经在函数中被修改了.
获取所需文件的函数具体实现如下:
private bool GetFileInfo(out uint StartAddr, out List<FilePartInfo> FIleInfo, string filePath)
{
StartAddr = 0;
FIleInfo = null;
if (filePath == string.Empty)
{
return false;
}
string tempType = filePath.Substring(filePath.LastIndexOf(@"\")).ToUpper();
if (tempType.Substring(tempType.Length - 3, 3) == "BIN")
{
FIleInfo = AddValue(filePath);
string strAddr = textBox2.Text;
StartAddr = StringToUInt32(strAddr, 16);
}
else
{
MessageBox.Show("下载文件格式错误", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error,
MessageBoxDefaultButton.Button1, MessageBoxOptions.ServiceNotification);
return false;
}
if (FIleInfo == null)
{
MessageBox.Show("下载文件错误,file information为null", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error,
MessageBoxDefaultButton.Button1, MessageBoxOptions.ServiceNotification);
CloseProgress();
return false;
}
if (FIleInfo.Count == 0)
{
MessageBox.Show("下载文件错误,file information.count为0", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error,
MessageBoxDefaultButton.Button1, MessageBoxOptions.ServiceNotification);
CloseProgress();
return false;
}
return true;
}
首先是判断是否有文件打开,如果filePath为空,则返回false,文件的信息则还是为null. 如果filePath不为空,则继续判断格式是否复合要求, 不符合的报错,符合的再将文件的基本信息通过AddValue函数保存到事先声明好的列表中. AddValue后,再对列表进行判断,来最后决定是否成功的将所需的文件信息保存到了列表中.
其中AddValue函数的具体实现为:
public List<FilePartInfo> AddValue(string FilePath)
{
if (!File.Exists(FilePath))
{
return null;
}
List<FilePartInfo> partInfo = new List<FilePartInfo>();
FileStream fs = new FileStream(FilePath, FileMode.Open, FileAccess.Read);
BinaryReader br = new BinaryReader(fs);
int fullLength = (int)fs.Length;
if (fullLength == 0)
{
return null ;
}
int partLength = 0;
FilePartInfo tempInfo = new FilePartInfo();
tempInfo.PartStartAddress = 0;
tempInfo.PartEndAddress = 0;
tempInfo.PartSize = 2048;
tempInfo.PartData = new byte[2048];
for (int k = 0; k < 2048; k++)
tempInfo.PartData[k] = 0xFF;
partInfo.Add(tempInfo);
partLength = 0;
for(int i = 0; i < fullLength; i++)
{
partInfo[partInfo.Count - 1].PartData[partLength] = br.ReadByte();
partLength++;
if ((partLength == 0x0800) && (i + 1 < fullLength))
{
tempInfo = new FilePartInfo();
tempInfo.PartStartAddress = partInfo[partInfo.Count - 1].PartStartAddress + 0x0800;
tempInfo.PartEndAddress = 0;
tempInfo.PartSize = 2048;
tempInfo.PartData = new byte[2048];
for (int k = 0; k < 2048; k++)
tempInfo.PartData[k] = 0xFF;
partInfo.Add(tempInfo);
partLength = 0;
}
}
return partInfo;
}
同样的,首先是对filePath进行判断,只有文件路径存在,才说明有文件的信息需要保存.
当文件路径存在的时候,再通过文件流filestream对文件进行打开,并且使用binaryreader进行对文件进行读取.(因此文件我们规定只有二进制文件能够传输) 然后通过tempInfo将文件的信息一部分一部分的保存在声明的列表中. 由于串口通信协议规定不满足2k的数据用0xFF填充,因此我们先将所有的数据都填充为0xFF,然后再对实际的数据进行更改. 最后将所有的 数据都保存到列表后,返回列表.
串口通信
串口通信其实就是根据串口通信协议,对上位机面对不同情况的时候所需要做出的反应进行设置就可以了.
由于串口通信协议中对于超时的情况认定为通信失败,因此我们需要获取时间来判断是否超时. 而计算机中一个经典的获取时间的方法为:
private long GetCurrentTimeSeconds()
{
long currentTicks = System.DateTime.Now.Ticks;
System.DateTime dtFrom = new System.DateTime(1970, 1, 1, 0, 0, 0, 0);
long currentMillis = (currentTicks - dtFrom.Ticks) / 1000 / 1000 / 10;
return currentMillis;
}
1970年一月一日00:00:00是Unix纪元时间. 这个函数通过获取当前时间距离Unix纪元时间的时间来确定当前的时候. 两次调用这个函数得到的结果的差值就是我们所需要的时间间隔,当时间间隔大于我们通信协议中规定的时间时,就认为是超时了,则串口通信失败,停止串口升级.
串口通信函数有两个,一个是IAP固件刚开始升级的时候,用来判断0x5A 0xA5是否正常通信的,另一个是,进入IAP升级的时候,用来判断是否正常升级的函数.
两个函数之间的主要区别在于:
- 功能目的不同:
PortCommunicationRS232Begin
的主要目的是建立串口通信连接,并等待设备返回特定的应答字节序列(0xCC 0xDD),以确认连接已建立。PortCommunicationRS232
的主要目的是通过已建立的串口连接发送和接收数据。
- 接收数据处理逻辑不同:
PortCommunicationRS232Begin
在接收数据时,专门寻找特定的应答字节序列(0xCC 0xDD),并根据是否收到该序列来判断是否连接成功。PortCommunicationRS232
在接收数据时,只是简单地将接收到的字节存储到提供的接收缓冲区中,不做特殊处理。
- 超时处理逻辑不同:
PortCommunicationRS232Begin
在等待应答时,如果超过5秒钟没有收到期望的应答序列,就会退出循环并返回失败。PortCommunicationRS232
在接收数据时,如果连续5次读取操作失败,就会认为超时并返回失败。