MFC学习之下载hex文件到单片机程序代码(串口通信)


前言

因为平时需要配合硬件开发的做程序升级,提到了一些问题,之前每次用串口升级的时候,都需要安装MSComm控件,在新的设备工控机上安装比较繁琐,另外对之前的下载升级程序效率上要有所提升。
下面写这篇文章也是总结下遇到的问题,希望对看到的朋友有帮助。MFC程序实现


一、MFC串口通信问题

相信看到的朋友,应该都用过MSComm控件写串口通信,也能找到很多的例子程序。还要一种方式用windows api的方式,完成串口通信操作。具体细节往下看。

二、MSComm控件的串口通信形式

1.添加active控件

在项目菜单栏中选择“工具”菜单,在弹出的窗口中选择“选择工具箱选项”在“COM组件”中选择,然后点击“确定”按钮将MSComm添加到工具箱。
如下图所示是已经添加完成MSComm控件后的工具箱,工具箱中会出现一个电话式样的图标,该图标就是MSComm控件。
在这里插入图片描述
在这里插入图片描述
拖进去就能添加控件,右键添加控件变量后就能使用了。

2.添加Combo box控件,获取当前主机可用串口

获取当前主机可用串口号,添加到Combo box控件变量中:

// 初始化端口号
void CSerialMSCommDlg::GetCom()
{
	//程序启动时获取全部可用串口
	HANDLE hCom;
	int i, k;
	CString str;
	BOOL flag;
 
	//((CComboBox *)GetDlgItem(IDC_COMBO1))->ResetContent();
	m_ComboSerial.ResetContent();
	flag = FALSE;
	num = 0;
	for (i = 1; i <= 16; i++)
	{//此程序支持16个串口
		str.Format(L"\\\\.\\COM%d", i);	
		hCom = CreateFile(str, 0, 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
		if (INVALID_HANDLE_VALUE != hCom)
		{//能打开该串口,则添加该串口
			CloseHandle(hCom);
			str = str.Mid(4);
			m_ComboSerial.AddString(str);
			//((CComboBox *)GetDlgItem(IDC_COMBO1))->AddString(str);
			if (flag == FALSE)
			{
				flag = TRUE;
				num = i;
			}
		}
	}
	i = m_ComboSerial.GetCount();
	
	if (i == 0)
	{//若找不到可用串口则禁用“打开串口”功能
		m_ComboSerial.EnableWindow(FALSE);
	}
	else
	{
		k = m_ComboSerial.GetCount();
		m_ComboSerial.SetCurSel(k-1);
	}
}

3.打开串口、关闭串口、发送和接收串口数据代码实现

打开串口代码实现:

if (!m_mscom.get_PortOpen())			
	{	
		try 
		{
			m_ComboSerial.GetLBText(m_ComboSerial.GetCurSel(), strCBText);
			int iabc = _ttoi(strCBText.Right(1));
			m_mscom.put_CommPort(_ttoi(strCBText.Right(1)));	//选择串口
		}
		catch (CException* e)
		{
			m_mscom.put_OutBufferCount(0);

			AfxMessageBox(L"打开串口 失败");
			return;
		}
		m_mscom.put_InputMode(1);			//设置输入方式为二进制方式
		m_mscom.put_Settings(_T("9600,n,8,1"));		//设置串口参数,波特率,无奇偶校验,位停止位,位数据位
		m_mscom.put_InputLen(1024);		//设置当前接收区数据长度为1024
		m_mscom.put_RThreshold(1);			//接收缓冲区有1个及1个以上字符时,触发OnComm事件
		m_mscom.put_RTSEnable(1);			//设置RT允许


		m_mscom.put_PortOpen(true);		//打开串口

		if (m_mscom.get_PortOpen())
		{
			bIsloop = true;
			str = _T("关闭串口");
			UpdateData(true);
			h1->SetWindowText(str);			//改变按钮名称为‘’关闭串口”
		}
	}

关闭串口代码:

if (m_MSCommControl.get_PortOpen())
	{
		m_mscom.put_PortOpen(false);		//关闭串口
		if (str != _T("打开串口"))
			{
				bIsloop = false;
				str = _T("打开串口");
				UpdateData(true);				//将控件的状态传给其关联的变量
				h1->SetWindowText(str);			//改变按钮名称为打开串口
			}
	}	

接收串口返回数据时,可以添加【串口事件】
为MSComm控件增加OnComm处理函数:在类向导中选择“命令”、选择ID为IDC_MSCOMMPORT的控件为其增加OnComm消息处理函数。具体在函数中怎么处理,大家可以自己搜一下,或者看下面的例子(我没测试哦!)
在这里插入图片描述

void CMFCApplicationMSCommPortDlg::OnOncommMscommport()
{
	VARIANT        m_Variant_Rec;
	COleSafeArray  m_SafeArray_Rec;
	LONG           m_DataLenth, nCount;
	const int      m_RecByteLenth = 2048;
	BYTE           m_RecDataByte[m_RecByteLenth];
	CString        m_TemDataStr, strRevBuff, strSendBuff,strRevByte,strSendByte,strRxCount;
 
	if (m_MSCommControl.get_CommEvent() == 1)
	{
		//strSendByte.Format(_T("%d"), m_MSCommControl.get_OutBufferCount());
		//printf("get_OutBufferCount=%d\n", m_MSCommControl.get_OutBufferCount());
		//m_SendByteCount.SetWindowText(_T("get_OutBufferCount:") + strSendByte);
		m_SendMsg = TRUE;
		PostMessage(WM_SENDMSG, WPARAM(m_SendMsg), 0);
		printf("get_CommEvent=%d\n", m_MSCommControl.get_CommEvent());	
	}
	else if (m_MSCommControl.get_CommEvent() == 2)
	{
		m_SendMsg = FALSE;
		PostMessage(WM_SENDMSG, WPARAM(m_SendMsg), 0);
	}
	if (m_MSCommControl.get_CommEvent()==2)
	{
 
		strRevBuff.Format(_T("%d"), m_MSCommControl.get_InBufferSize());
		printf("get_InBufferSize=%d\n", m_MSCommControl.get_InBufferSize());
		strSendBuff.Format(_T("%d"), m_MSCommControl.get_OutBufferSize());
		printf("get_OutBufferSize=%d\n", m_MSCommControl.get_OutBufferSize());
 
		strRevByte.Format(_T("%d"), m_MSCommControl.get_InBufferCount());
		printf("get_InBufferCount=%d\n", m_MSCommControl.get_InBufferCount());
		
		
		InCount= InCount+m_MSCommControl.get_InBufferCount();
		printf("InCount=%I64u\n", InCount);
		strRxCount.Format(_T("%I64u"), InCount);
	}
}

自己的代码实例中要求一直循环读取气象设备数据,所以写了个死循环的线程函数,

while (true)
		{
			CByteArray HexDataBuf;
			int i = 0;
			BYTE SendBuf[128]={0};
			BYTE GetData[256]={0};
			int SendLen = 0;
			int GetLen = 0;
			CString csEdit;

			HexDataBuf.RemoveAll();               //清空数组
			// 气象
			//01 03 00 00 00 05 85 C9
			pMainDlg->SetDlgItemTextW(IDC_EDIT1,_T("01 03 00 00 00 05 85 C9"));
			//pMainDlg->UpdateData(TRUE);//获取编辑框内容
			pMainDlg->GetDlgItemTextW(IDC_EDIT1,csEdit);
			GetLen = csEdit.GetLength();
			for(i=0; i<GetLen; i++)
			{
				GetData[i] = (BYTE)csEdit.GetBuffer()[i];
			}
			StringtoHex(GetData, GetLen, SendBuf, &SendLen);//将字符串转化为字节数据

			HexDataBuf.SetSize(SendLen);            //设置数组大小为帧长度 
			for(i=0; i<SendLen; i++)
			{
				HexDataBuf.SetAt(i,SendBuf[i]);
			}

			pMainDlg->m_mscom.put_Output(COleVariant(HexDataBuf)); //发送十六进制数据

			// 空气
			// 01 03 00 00 00 06 C5 C8
			// 01 03 00 00 00 07 04 08
			Sleep(100);		//延时毫秒

			//------------------------------------
			VARIANT variant_inp;
			COleSafeArray safearray_inp;
			long len, k;
			byte rxdata[1024]; //设置 BYTE 数组

			CString strtemp, buffer;
			variant_inp = pMainDlg->m_mscom.get_Input(); //读缓冲区消息
			safearray_inp = variant_inp; ///变量转换
			len = safearray_inp.GetOneDimSize(); //得到有效的数据长度
			//将数组转换为 CString 型变量
			for (k = 0; k < len; k++) 
			{safearray_inp.GetElement(&k, rxdata + k);
			strtemp.Format(_T("%02X"), *(rxdata + k)); buffer += strtemp;}
			CStringA cstrNum, cstrNum1, cstrNum2, cstrNum3, cstrNum4;
			// 温度
			cstrNum2 = buffer.Mid(6,4);
			// 湿度
			cstrNum3 = buffer.Mid(10,4);
			// 气压
			cstrNum4 = buffer.Mid(14,4);
			// 风速
			cstrNum = buffer.Mid(18,4);
			// 风向
			cstrNum1 = buffer.Mid(22,4);
			UINT sum4 = strtoul(cstrNum4.GetBuffer(), 0, 16);

			CString cstFengsu, cstFengxiang, cstShidu, cstWendu, cstQiya;
			cstFengsu.Format(_T("风速 m/s: %d.%d"),strtoul(cstrNum.GetBuffer(), 0, 16) / 100,strtoul(cstrNum.GetBuffer(), 0, 16) % 100);
			cstFengxiang.Format(_T("  风向 °: %d"),strtoul(cstrNum1.GetBuffer(), 0, 16));
			if (strtoul(cstrNum2.GetBuffer(), 0, 16) > 1000)
			{
				cstWendu.Format(_T("  温度 °C: -%d.%d"),(65535-strtoul(cstrNum2.GetBuffer(), 0, 16))/10, (65535-strtoul(cstrNum2.GetBuffer(), 0, 16))%10);
			}
			else
			{
				cstWendu.Format(_T("  温度 °C: %d.%d"),strtoul(cstrNum2.GetBuffer(), 0, 16)/10, strtoul(cstrNum2.GetBuffer(), 0, 16)%10);
			}
			cstShidu.Format(_T("  湿度 %%RH: %d.%d"),strtoul(cstrNum3.GetBuffer(), 0, 16)/10, strtoul(cstrNum3.GetBuffer(), 0, 16)%10);
			cstQiya.Format(_T("  气压 hPa: %d.%d"),strtoul(cstrNum4.GetBuffer(), 0, 16)/10, strtoul(cstrNum4.GetBuffer(), 0, 16)%10);
			pMainDlg->GetDlgItem(IDC_QIYA)->SetWindowTextW(cstFengsu+cstFengxiang+cstWendu+cstShidu+cstQiya);
			pMainDlg->GetDlgItem(IDC_QIYA)->UpdateData(false); //更新到控件
			//------------------------------------			
			Sleep(2000); //滞后2秒
		}

完整代码参见:MFC串口通信发送和接收+MSCOMM注册组件控件.rar

三、Windows API方式串口通信

1.获取当前主机可用串口信息,打开串口(采用异步的方式)

部分代码可参见上面的获取串口信息
闭坑指南:注意串口号如果大于COM9应该在前面加上\.\,比如COM10表示为"\\.\COM10",这里都用“ \\.\%d ”

BOOL CDlg::OpenCom()
{
	CString csCOMMnum;
	m_ComboSerial.GetLBText(m_ComboSerial.GetCurSel(), csCOMMnum);	// 获取Combo box控件选中的串口号
	csCOMMnum = "\\\\.\\" + csCOMMnum;
	hCom = CreateFile(csCOMMnum, //COM1口 注意串口号如果大于COM9应该在前面加上\\.\,比如COM10表示为"\\\\.\\COM10"
		GENERIC_READ | GENERIC_WRITE, //允许读和写
		0, //独占方式
		NULL,
		OPEN_EXISTING, //打开而不是创建
		FILE_ATTRIBUTE_NORMAL|FILE_FLAG_OVERLAPPED, //0:同步方式,FILE_FLAG_OVERLAPPED:异步方式
		NULL);

	if (hCom == (HANDLE)-1)
	{
		return FALSE;
	}
	return TRUE;
}

2.配置串口参数信息

void CDlg::InitCom()
{
	SetupComm(hCom, 1024, 1024); //输入缓冲区和输出缓冲区的大小都是1024
	COMMTIMEOUTS TimeOuts; //设定读超时
	TimeOuts.ReadIntervalTimeout = 1000;
	TimeOuts.ReadTotalTimeoutMultiplier = 500;
	TimeOuts.ReadTotalTimeoutConstant = 5000; //设定写超时
	TimeOuts.WriteTotalTimeoutMultiplier = 500;
	TimeOuts.WriteTotalTimeoutConstant = 2000;
	SetCommTimeouts(hCom, &TimeOuts); //设置超时
	DCB dcb;
	GetCommState(hCom, &dcb);
	dcb.BaudRate = 9600; //波特率为9600
	dcb.ByteSize = 8; //每个字节有8位
	dcb.Parity = NOPARITY; //无奇偶校验位
	dcb.StopBits = TWOSTOPBITS; //2个停止位  TWOSTOPBITS  ONE5STOPBITS
	SetCommState(hCom, &dcb);
	PurgeComm(hCom, PURGE_TXCLEAR | PURGE_RXCLEAR); // PurgeComm()函数清空缓冲区
}

3.发送串口信息

闭坑指南:WriteFile发送串口数据时,总返回错误码 6, 查了好多资料,才知道是参数没初始化的问题。

OVERLAPPED m_OsWrite;
memset(&m_OsWrite, 0, sizeof(OVERLAPPED)); // 不初始化,发送结果返回错误码 6
WriteFile(hCom, m_Sendinfo.GetBuffer(), m_Sendinfo.GetLength(), &dwBytesWritten, &m_OsWrite);

BOOL CDlg::Sendcomminfo()
{

	//char buffer[1024] = ;
	DWORD dwBytesWritten = 1024;
	DWORD dwErrorFlags;
	COMSTAT ComStat;
	OVERLAPPED m_OsWrite;
	BOOL bWriteStat;
	memset(&m_OsWrite, 0, sizeof(OVERLAPPED)); // 不初始化,发送结果返回错误码 6
	memset(&m_odWrite, 0, sizeof(OVERLAPPED));
	m_odWrite.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
	PurgeComm(hCom, PURGE_RXABORT | PURGE_TXABORT | PURGE_RXCLEAR | PURGE_TXCLEAR);
	if (m_checkHEX.GetCheck())	// 添加复选框,如果是HEX数据,则转换格式
	{
		char csSendBuff[2048];
		//memset(csSendBuff, 0xFF, 2048);
		int ilen = String2Hex(m_Sendinfo, csSendBuff);
		bWriteStat = WriteFile(hCom, csSendBuff, ilen, &dwBytesWritten, &m_OsWrite);
	}
	else
	{
		bWriteStat = WriteFile(hCom, m_Sendinfo.GetBuffer(), m_Sendinfo.GetLength(), &dwBytesWritten, &m_OsWrite);
	}
	
	if (!bWriteStat)
	{
		if (GetLastError() == ERROR_IO_PENDING)
		{
			WaitForSingleObject(m_OsWrite.hEvent, 1000);
			CString csline;
			csline.Format("发送串口数据:%s", m_Sendinfo);
			EditShowData(csline);
			return TRUE;
		}
		CString cserror;
		cserror.Format("err = %d\n ", GetLastError());
		EditShowData(cserror);
		return FALSE;
	}
	CString csline;
	csline.Format("发送串口数据:%s", m_Sendinfo);
	EditShowData(csline);
	return TRUE;
}

4.接收串口信息

这里// 添加复选框,如果是HEX数据,则转换格式,具体实现函数可以参见完整程序代码,放文末了。

BOOL CDlg::Receivedatayibu()
{
	char lpInBuffer[1024] = {0};
	DWORD dwBytesRead = 1024;
	BOOL bReadStatus;
	DWORD dwErrorFlags;
	COMSTAT ComStat;
	OVERLAPPED m_osRead;
	memset(&m_osRead, 0, sizeof(OVERLAPPED));//注意每次读取串口时都要初始化OVERLAPPED
	m_osRead.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
	ClearCommError(hCom, &dwErrorFlags, &ComStat);
	if (!ComStat.cbInQue) return 0; //读缓冲区没有字节
	dwBytesRead = min(dwBytesRead, (DWORD)ComStat.cbInQue); //防止读取字节数超过数组最大值
	bReadStatus = ReadFile(hCom, lpInBuffer, dwBytesRead, &dwBytesRead, &m_osRead);
	if (!bReadStatus) 
	{  //如果ReadFile函数返回FALSE 
		if (GetLastError() == ERROR_IO_PENDING) 
		{
			GetOverlappedResult(hCom, &m_osRead, &dwBytesRead, TRUE); // GetOverlappedResult函数的最后一个参数设为TRUE,函数会一直
																	  //等待,直到读操作完成或由于错误而返回。 
		}
		return FALSE;
	}
	else
	{
		if (m_checkHEX.GetCheck())	// 添加复选框,如果是HEX数据,则转换格式
		{
			CString csbuffer, strtemp;
			for (int k = 0; k < dwBytesRead; k++)
			{
				strtemp.Format(_T("%02X"), *(lpInBuffer + k));
				csbuffer += strtemp.Mid(0, 2);
				csbuffer += " ";
			}
			EditShowData(csbuffer);
			if (csbuffer == "12 FF 00 30 ") 	// 这里设置了一个标志,HEX文件下发定义了1024个字节下发,等待下位机返回参数后,再继续下发
			{
				m_bNextLine = TRUE;
			}
			
		}
		else
		{
			EditShowData(lpInBuffer);
		}		
	}
	
	return TRUE;
}

5.关闭串口通信

void CDlg::OnBnClickedClosecom()
{
	// TODO: 在此添加控件通知处理程序代码
	//::WaitForSingleObject(m_MyThreadComm->m_hThread, INFINITE);
	m_bOpencomm = FALSE;
	GetDlgItem(BTN_UPDATECOMM)->EnableWindow(TRUE);
	GetDlgItem(BEN_OPENCOM)->EnableWindow(TRUE);
	m_ComboSerial.EnableWindow(TRUE);
	if (CloseHandle(hCom))
	{
		EditShowData("关闭串口连接成功...");
		if (m_MyThreadDownHEX != NULL)
		{
			TerminateThread(m_MyThreadDownHEX->m_hThread, 0);	// 这里选择强制关闭线程
		}
	}
	else
	{
		EditShowData("关闭串口连接失败...");
	}

}

四、下载HEX文件到单片机

if (strDlg->m_csFilePath.Right(4) == ".hex")
	{
		// 按1024字节大小读取
		CStdioFile file;
		CString csLineAll, csline;

		if (file.Open((LPCTSTR)strDlg->m_csFilePath, CStdioFile::modeReadWrite))
		{
			strDlg->EditShowData("开始下载HEX文件...");
			int iLineNum = 0;
			CString csStartID;	// 保存hex文件 第一行标志
			// 获取文件总自己长度,每小包1024字节,计算总包数//
			while (file.ReadString(csline))
			{
				iLineNum += 1;
				if (iLineNum == 1)
				{
					csStartID = csline.Mid(9, csline.GetLength() - 11);	// 保存hex文件 第一行标志
					continue;
				}
				//csline = csline.Mid(9);
				csline = csline.Mid(9, csline.GetLength() - 11);
				if (csStartID == csline.Mid(0, 4))	// 判断hex第一行标志,是否到文件结尾
				{
					break;
				}
				else
				{
					csLineAll += csline;	// 把每行截取到的数据段都存入 csLineAll 中	
				}

			}
			file.Close();	// 读取hex文件结束,释放文件句柄
		//----------------------------------------------------------------------------------
			int iCount = csLineAll.GetLength() / 2048 + 1;	// 计算文件总包数  每包1024字节长度
			for (size_t i = 0; i < iCount; i++)	// 拆分小包下载到单片机
			{
				while (1)
				{
					if (strDlg->m_bNextLine == FALSE)
					{
						Sleep(100);
					}
					else
					{
						break;
					}
				}
				
				char cBuffLine[2048];
				memset(cBuffLine, 0xFF, 2048);

				// 添加包头 包尾
				char cBuffPage[1034] = { 0xFE, 0xFE };
				cBuffPage[2] = (uint16_t)iCount & 0xff;
				cBuffPage[3] = (uint16_t)iCount >> 8;
				cBuffPage[4] = (uint16_t)i & 0xff;
				cBuffPage[5] = (uint16_t)i >> 8;
				cBuffPage[1032] = 0xEF;
				cBuffPage[1033] = 0xEF;
				//将字符串转化为字节数据
				csline = csLineAll.Mid(0, 2048);
				int istrlen = strDlg->String2Hex(csline, cBuffLine);
				for (size_t i = 0; i < 1024; i++)
				{
					cBuffPage[6 + i] = cBuffLine[i];
				}
				// 添加crc32 校验
				uint16_t crc16;
				crc16 = crc16_modbus((uint8_t*)cBuffPage, 1030);
				cBuffPage[1030] = crc16 & 0xff;
				cBuffPage[1031] = crc16 >> 8;
				//------------------------------------------------------------

				DWORD dwBytesWritten = 1034;
				DWORD dwErrorFlags;
				COMSTAT ComStat;
				OVERLAPPED m_OsWrite;
				BOOL bWriteStat;
				memset(&m_OsWrite, 0, sizeof(OVERLAPPED)); // 不初始化,发送结果返回错误码 6
				memset(&strDlg->m_odWrite, 0, sizeof(OVERLAPPED));
				strDlg->m_odWrite.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
				PurgeComm(strDlg->hCom, PURGE_RXABORT | PURGE_TXABORT | PURGE_RXCLEAR | PURGE_TXCLEAR);
				bWriteStat = WriteFile(strDlg->hCom, cBuffPage, 1034, &dwBytesWritten, &m_OsWrite);
				if (!bWriteStat)
				{
					if (GetLastError() == ERROR_IO_PENDING)
					{
						WaitForSingleObject(m_OsWrite.hEvent, 1000);
						CString cslineshow;
						cslineshow.Format("发送串口数据:%s", csline);
						csLineAll = csLineAll.Mid(2048);
						strDlg->EditShowData(cslineshow);
						strDlg->m_bNextLine = FALSE;
					}
					else
					{
						CString cserror;
						cserror.Format("err = %d\n ", GetLastError());
						strDlg->EditShowData(cserror);
					}

				}
				else
				{
					CString cslineshow;
					cslineshow.Format("发送串口数据:%s", csline);
					strDlg->EditShowData(cslineshow);
					csLineAll = csLineAll.Mid(2048);
					strDlg->m_bNextLine = FALSE;
				}
			}
			return 1;

		}

五、HEX文件格式

hex文件格式是可以烧写到单片机中,被单片机执行的一种文件格式,生成Hex文件的方式有很多种,可以通过不同的编译器将C程序或者汇编程序编译生成hex。

Hex文件格式解析
Hex文件如果用特殊的程序来查看(一般记事本就可以实现)。打开后可发现,整个文件以行为单位,每行以冒号开头,内容全部为16进制码(以ASCII码形式显示)。Hex文件可以按照如下的方式进行拆分来分析其中的内容:
例如:
:020000040000FA , 我把它看做 0x02 0x00 0x00 0x04 0x00 0x00 0xFA
第一个 0x02 为数据长度。
紧跟着后面的0x00 0x00 为地址。
再后面的0x04为数据类型,类型共分以下几类:
‘00’ Data Record//数据记录
‘01’ End of File Record//文件结束记录
‘02’ Extended Segment Address Record//扩展段地址记录
‘03’ Start Segment Address Record//开始段地址记录
‘04’ Extended Linear Address Record//扩展线性地址记录
‘05’ Start Linear Address Record//开始线性地址记录
然后,接着0x04后面的两个 0x00 0x00就是数据。最后一个0xFA是校验码。

第一行,是Extended Linear Address Record,里面的数据,也就是基地址是0x0004,第二行是Data Record,里面的地址值是0x0000。那么数据18F09FE518F09FE518F09FE518F09FE5要写入FLASH中的地址为 (0x0004 << 16) | 0x0000,也就是写入FLASH的0x40000这个地址。同样,第三行的数据的写入地址为0x40010。当一个HEX文件的数据超过7k的时候,文件中就会出现多个Extended Linear Address Record。
End of File Record 行是每一个HEX文件的最后一行。例如:
:00000001FF
这样的一行数据内容是固定的,数据长度为0,地址为0。
校验值:每一行的最后一个值为此行数据的校验和。例如:
:1000000018F09FE518F09FE518F09FE518F09FE5C0 这行中的 0xC0
:1000100018F09FE5805F20B9F0FF1FE518F09FE51D 这行中的 0x1D
校验和的算法为:计算从0x3A 以后(不包括0x3A)的所有各字节的和模256的余。即各字节二进制算术和,不计超过256的溢出值,然后用0x100减去这个算数累加和,得出得值就是此行得校验和。

在这里插入图片描述
在这里插入图片描述

工程代码参见:
Windows API串口异步通信实例代码:需要的自己下载吧,有问题可以一起交流。
有部分TCP通信的代码实现,也可以参考下。
在这里插入图片描述

总结

以上就是今天要讲的内容了,希望本文能够帮助您更好地学习技术知识,并且让它实现更多的价值。如果您想要获得更多关于技术文章信息,欢迎关注我或者一起探讨相关问题,感谢支持。

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

逃逸的卡路里

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值