嵌入式系统中串口传输的校验算法简介
在做嵌入式系统时,经常需要通过串口将数据传送给PC机,为了保证数据的正确性,往往需要进行校验,在传送一个字节的时候可以进行奇偶校验,传送一组数据的时候可以采用CRC校验等,但是在发送一组数据的时候采用CRC校验单片机往往太占资源,这就需要有一种比较简单的校验方法。
我们可以采用先学习下IP校验的方式:
当发送IP包时,IP报头的校验和为:
1、 把校验和字段置为0;
2、 对IP头部中的每16bit进行二进制求和;
3、 如果和的高16bit不为0,则将和的高16bit和低16bit反复相加,直到和的高16bit为0,从而获得一个16bit的值;
4、 将该16bit的值取反,存入校验和字段。
当接收IP包时,需要对报头进行确认,检查IP头是否有误,算法同上2、3步,然后判断取反的结果是否为0,是则正确,否则有错。
实例:
IP头:
45 00 00 31
89 F5 00 00
6E 06 00 00(校验字段)
DE B7 45 5D -> 222.183.69.93
C0 A8 00 DC -> 192.168.0.220
计算:
4500 + 0031 +89F5 + 0000 + 6e06+ 0000 + DEB7 + 455D + C0A8 + 00DC =3 22C4
0003 + 22C4 = 22C7
~22C7 = DD38 ->即为应填充的校验和
当接受到IP数据包时,要检查IP头是否正确,则对IP头进行检验,方法同上:
计算:
4500 + 0031 +89F5 + 0000 + 6E06+ DD38 + DEB7 + 455D + C0A8 + 00DC =3 FFFC
0003 + FFFC = FFFF
~FFFF = 00000 ->正确
TCP首部检验和与IP首部校验和的计算方法相同,在程序中使用同一个函数来计算。
在嵌入式系统中,如果是8位单片机,则校验变成了
1、 把校验和字段置为0;
2、 对要传输的数组每8bit进行二进制求和;
3、 如果和的高8bit不为0,则将和的高8bit和低8bit反复相加,直到和的高8bit为0,从而获得一个8bit的值;
4、 将该8bit的值取反,存入校验和字段。
当接收数据时,对校验和进行确认,检查是否有误,算法同上2、3步,然后判断取反的结果是否为0,是则正确,否则有错。
c程序为
unsigned char checksum(unsigned char *s,unsigned char n)
{
unsigned int check = 0;
unsigned char i, ch = 0;
for(i=0;i<n;i++)
{
check += *s++;
}
check = (check>>8) + (check&0xff); //高八位+低八位
check += (check>>8); //为防止进位,再加一次
ch = check&0x00ff;
return ~ch;
}
上面就是如何在嵌入式系统中如何进行串口传输的校验了,你学会了没有?
AOVX资产监测设备之华大单片机Boot软件升级
近期笔者在研究如何从软件方面提升资产监测设备的性能,从而提升用户的使用体验感。
资产监测设备的主要作用是帮助用户监测设备的实时位置、实时状态避免运动中的货物出现丢失等情况。AOVX环境监测设备在智慧物流方面发挥了重要的作用,例如在运输贵重货物过程中,只需要将该设备安装在货物中,用户即可远程了解货物的位置、货物是否出现暴力扔件、以及避免货物在运输过程中的丢失。
资产监测设备中的华大单片机,对该设备的工作发挥了重要的作用。笔者的软件团队对单片机进行了软件升级。
具体流程如下:
具体流程如下:/**
** \brief 上位机数据帧解析及处理**** \param [in] None**** \retval Ok APP程序升级完成,并接受到跳转至APP命令** \retval OperationInProgress 数据处理中** \retval Error 通讯错误********************************************************************************/en_result_t Modem_Process(void){uint8_t u8Cmd, u8FlashAddrValid, u8Cnt, u8Ret;uint16_t u16DataLength, u16PageNum, u16Ret;uint32_t u32FlashAddr, u32FlashLength, u32Temp;
if (enFrameRecvStatus == FRAME_RECV_PROC_STATUS) //有数据帧待处理, enFrameRecvStatus值在串口中断中调整
{
u8Cmd = u8FrameData[PACKET_CMD_INDEX]; //获取帧指令码
if (PACKET_CMD_TYPE_DATA == u8FrameData[PACKET_TYPE_INDEX]) //如果是数据指令
{
u8FlashAddrValid = 0u;
u32FlashAddr = u8FrameData[PACKET_ADDRESS_INDEX] + //读取地址值
(u8FrameData[PACKET_ADDRESS_INDEX + 1] << 8) +
(u8FrameData[PACKET_ADDRESS_INDEX + 2] << 16) +
(u8FrameData[PACKET_ADDRESS_INDEX + 3] << 24);
if ((u32FlashAddr >= (FLASH_BASE + BOOT_SIZE)) && (u32FlashAddr < (FLASH_BASE + FLASH_SIZE))) //如果地址值在有效范围内
{
u8FlashAddrValid = 1u; //标记地址有效
}
}
switch (u8Cmd) //根据指令码跳转执行
{
case PACKET_CMD_HANDSHAKE : //握手帧 指令码
u8FrameData[PACKET_RESULT_INDEX] = PACKET_ACK_OK; //返回状态为:正确
Modem_SendFrame(&u8FrameData[0], PACKET_INSTRUCT_SEGMENT_SIZE); //发送应答帧给上位机
break;
case PACKET_CMD_ERASE_FLASH : //擦除flash 指令码
if ((u32FlashAddr % FLASH_SECTOR_SIZE) != 0) //如果擦除地址不是页首地址
{
u8FlashAddrValid = 0u; //标记地址无效
}
if (1u == u8FlashAddrValid) //如果地址有效
{
u32Temp = u8FrameData[PACKET_DATA_INDEX] + //获取待擦除flash尺寸
(u8FrameData[PACKET_DATA_INDEX + 1] << 8) +
(u8FrameData[PACKET_DATA_INDEX + 2] << 16) +
(u8FrameData[PACKET_DATA_INDEX + 3] << 24);
u16PageNum = FLASH_PageNumber(u32Temp); //计算需擦除多少页
for (u8Cnt=0; u8Cnt<u16PageNum; u8Cnt++) //根据需要擦除指定数量的扇区
{
u8Ret = Flash_EraseSector(u32FlashAddr + (u8Cnt * FLASH_SECTOR_SIZE));
if (Ok != u8Ret) //如果擦除失败,反馈上位机错误代码
{
u8FrameData[PACKET_RESULT_INDEX] = PACKET_ACK_ERROR;
break;
}
}
if (Ok == u8Ret) //如果全部擦除成功,反馈上位机成功
{
u8FrameData[PACKET_RESULT_INDEX] = PACKET_ACK_OK;
}else //如果擦除失败,反馈上位机错误超时标志
{
u8FrameData[PACKET_RESULT_INDEX] = PACKET_ACK_TIMEOUT;
}
}
else //地址无效,反馈上位机地址错误
{
u8FrameData[PACKET_RESULT_INDEX] = PACKET_ACK_ADDR_ERROR;
}
Modem_SendFrame(&u8FrameData[0], PACKET_INSTRUCT_SEGMENT_SIZE); //发送应答帧到上位机
break;
case PACKET_CMD_APP_DOWNLOAD : //数据下载 指令码
if (1u == u8FlashAddrValid) //如果地址有效
{
u16DataLength = u8FrameData[FRAME_LENGTH_INDEX] + (u8FrameData[FRAME_LENGTH_INDEX + 1] << 8)
- PACKET_INSTRUCT_SEGMENT_SIZE; //获取数据包中的数据长度(不包含指令码指令类型等等)
if (u16DataLength > PACKET_DATA_SEGMENT_SIZE) //如果数据长度大于最大长度
{
u16DataLength = PACKET_DATA_SEGMENT_SIZE; //设置数据最大值
}
u8Ret = Flash_WriteBytes(u32FlashAddr, (uint8_t *)&u8FrameData[PACKET_DATA_INDEX], u16DataLength); //把所有数据写入flash
if (Ok != u8Ret) //如果写数据失败
{
u8FrameData[PACKET_RESULT_INDEX] = PACKET_ACK_ERROR; //反馈上位机错误 标志
}
else //如果写数据成功
{
u8FrameData[PACKET_RESULT_INDEX] = PACKET_ACK_OK; //反馈上位机成功 标志
}
}
else //如果地址无效
{
u8FrameData[PACKET_RESULT_INDEX] = PACKET_ACK_ADDR_ERROR; //反馈上位机地址错误
}
Modem_SendFrame(&u8FrameData[0], PACKET_INSTRUCT_SEGMENT_SIZE); //发送应答帧到上位机
break;
case PACKET_CMD_CRC_FLASH : //查询flash校验值 指令码
if (1u == u8FlashAddrValid) //如果地址有效
{
u32FlashLength = u8FrameData[PACKET_DATA_INDEX] +
(u8FrameData[PACKET_DATA_INDEX + 1] << 8) +
(u8FrameData[PACKET_DATA_INDEX + 2] << 16) +
(u8FrameData[PACKET_DATA_INDEX + 3] << 24); //获取待校验flash大小
if ((u32FlashLength + u32FlashAddr) > (FLASH_BASE + FLASH_SIZE)) //如果flash长度超出有效范围
{
u8FrameData[PACKET_RESULT_INDEX] = PACKET_ACK_FLASH_SIZE_ERROR; //反馈上位机flash尺寸错误
}else
{
u16Ret = Cal_CRC16(((unsigned char *)u32FlashAddr), u32FlashLength);//读取flash指定区域的值并计算crc值
u8FrameData[PACKET_FLASH_CRC_INDEX] = (uint8_t)u16Ret; //把crc值存储到应答帧
u8FrameData[PACKET_FLASH_CRC_INDEX+1] = (uint8_t)(u16Ret>>8);
u8FrameData[PACKET_RESULT_INDEX] = PACKET_ACK_OK; //反馈上位机成功 标志
}
}
else //如果地址无效
{
u8FrameData[PACKET_RESULT_INDEX] = PACKET_ACK_ADDR_ERROR; //反馈上位机地址错误
}
Modem_SendFrame(&u8FrameData[0], PACKET_INSTRUCT_SEGMENT_SIZE+2); //发送应答帧到上位机
break;
case PACKET_CMD_JUMP_TO_APP : //跳转至APP 指令码
Flash_EraseSector(BOOT_PARA_ADDRESS); //擦除BOOT parameter 扇区
u8FrameData[PACKET_RESULT_INDEX] = PACKET_ACK_OK; //反馈上位机成功
Modem_SendFrame(&u8FrameData[0], PACKET_INSTRUCT_SEGMENT_SIZE); //发送应答帧到上位机
return Ok; //APP更新完成,返回OK,接下来执行跳转函数,跳转至APP
case PACKET_CMD_APP_UPLOAD : //数据上传
if (1u == u8FlashAddrValid) //如果地址有效
{
u32Temp = u8FrameData[PACKET_DATA_INDEX] +
(u8FrameData[PACKET_DATA_INDEX + 1] << 8) +
(u8FrameData[PACKET_DATA_INDEX + 2] << 16) +
(u8FrameData[PACKET_DATA_INDEX + 3] << 24); //读取上传数据长度
if (u32Temp > PACKET_DATA_SEGMENT_SIZE) //如果数据长度大于最大值
{
u32Temp = PACKET_DATA_SEGMENT_SIZE; //设置数据长度为最大值
}
Flash_ReadBytes(u32FlashAddr, (uint8_t *)&u8FrameData[PACKET_DATA_INDEX], u32Temp); //读flash数据
u8FrameData[PACKET_RESULT_INDEX] = PACKET_ACK_OK; //反馈上位机成功 标志
Modem_SendFrame(&u8FrameData[0], PACKET_INSTRUCT_SEGMENT_SIZE + u32Temp);//发送应答帧到上位机
}
else //如果地址无效
{
u8FrameData[PACKET_RESULT_INDEX] = PACKET_ACK_ADDR_ERROR; //反馈上位机地址错误 标志
Modem_SendFrame(&u8FrameData[0], PACKET_INSTRUCT_SEGMENT_SIZE); //发送应答帧到上位机
}
break;
case PACKET_CMD_START_UPDATE : //启动APP更新(此指令正常在APP程序中调用)
u8FrameData[PACKET_RESULT_INDEX] = PACKET_ACK_OK; //反馈上位机成功 标志
Modem_SendFrame(&u8FrameData[0], PACKET_INSTRUCT_SEGMENT_SIZE); //发送应答帧到上位机
break;
}
enFrameRecvStatus = FRAME_RECV_IDLE_STATUS; //帧数据处理完成,帧接收状态恢复到空闲状态
}
return OperationInProgress; //返回,APP更新中。。。
}
同样从事软件工作相关的用户,对资产监测设备感兴趣的话可以参考该文档。
相关问答
单片机 中 ADC 是如何进行采集的?摘要:本文设计并实现了基于2.4GHzISM频段射频收发芯片nRF2401的计算机短距离无线数据采集系统。该系统采用PC作为系统控制中心,以C8051F021单片机...
51 单片机 和stm 单片机 的区别?两者的主要区别如下:1、内核:51单片机采用的是51Core,8Bit@2MHzMax(分频后),0.06DMIPS;STM32采用的是ARMCortex-M3,32Bit@72MHz,1....