第二章 HART软件设计 — HART协议代码
一、HART协议帧
上文我们简单介绍了HART硬件设计,这章我再简单的介绍一下HART协议
HART协议的帧格式以8位为一个字节进行编码,对每个字节加上一个起始位、一个奇偶校验位和一个停止位以串行方式进行传输。通常采用UART(通用异步接收/发送器)来完成字节的传输。由于数据的有无和长短不恒定,所以HART数据的长度不能超过25个字节。
(1)PREAMBLE 导言字节,一般是5~20个FF十六进制字节。
(2)START 起始字节,他将告之使用的结构为“长”还是“短”、消息源、是否是“突发”模式消息。主机到从机为短结构时,起始位为02,长帧时为82。
(3)ADDR 地址字节,他包含了主机地址和从机地址,短结构中占1字节,长结构中占5字节。
(4)COM 命令字节,他的范围为253个,用HEX的0~FD表示。31,127,254,255为预留值。
(5)BCNT 数据总长度,他的值表示的是BCNT下一个字节到最后(不包括校验字节)的字节数。接收设备用他可以鉴别出校验字节,也可以知道消息的结束。因为规定数据最多为25字节,所以他的值是从0~27。
(6)STATUS 状态字节,他也叫做“响应码”,顾名思义,他只存在于从机响应主机消息的时候,用2字节表示。他将报告通讯中的错误、接收命令的状态(如:设备忙、无法识别命令等)和从机的操作状态。
(7)DATA 数据字节,首先我想说明的是并非所有的命令和响应都包含数据字节,他最多不超过25字节(随着通讯速度的提高,正在要求放宽这一标准)。
(8)CHK 奇偶校验,方式是纵向奇偶校验,从起始字节开始到奇偶校验前一个字节为止。另外,每一个字节都有1位的校验位,这两者的结合可以检测出3位的突发错误。
举例:
短帧
主机到从机
FF FF FF FF FF 02 80 00 00 82
FF FF FF FF FF 导言字节
02 代表短帧
80 地址
00 命令
00 数据长度
82 校验位
从机回复
FF FF FF FF FF 06 80 00 0E 00 00 FE 00 57 05 05 05 02 00 00 11 00 04 33
FF FF FF FF FF 导言字节
06 定界符
80 地址
00 命令
0E 数据长度
00 00 表示OK
FE 数据的头字节
00 制造商ID
57 制造商设备类型
05 请求的前导符数
05 通用命令文档版本号
05 变送器规范版本号
02 设备软件版本号
00 设备硬件版本号
00 设备标志
11 00 04 设备序号
33 校验位
详细指令含义要参要HART协议的命令手册,这里不一一举例
二、单片机串口配置
本文以MSP430F149示例
void UART1_Init(void)
{
U1CTL|= CHAR | PENA; //复位SWRST,8位数据模式
U1TCTL|=SSEL1; //SMCLK为串口时钟
U1BR1=baud_h; //BRCLK=4MHZ,Baud=BRCLK/N
U1BR0=baud_l; //N=UBR+(UxMCTL)/4
U1MCTL=0; //微调寄存器为0,波特率1200bps
ME2|=UTXE1; //UART1发送使能
ME2|=URXE1; //UART1接收使能
U1CTL&=~SWRST;
IE2|=URXIE1; //接收中断使能位
P3SEL|= BIT6 + BIT7; //设置IO口为第二功能模式,启用UART功能
P3DIR|= BIT6; //设置TXD1口方向为输出
}
HART协议帧是一个标准UART帧,该帧包含一个起始位、8位数据、一个奇偶校验和一个停止位。
要注意校验允许位。
波特率是1200b/s
三、主循环函数
等待接收数据
void HartOperationMain(void)
{
// 判断数据是否发送完成
if((HartTransferFlag & COMM_STATE_TX_COMPLETE) == COMM_STATE_TX_COMPLETE)
{
// 清除发送完成标志
HartTransferFlag &= (~COMM_STATE_TX_COMPLETE);
// 数据发送完成,进行数据接收
HartStartReceiveCommand();
}
// 判断数据是否接收完成
if((HartTransferFlag & COMM_STATE_RX_COMPLETE) == COMM_STATE_RX_COMPLETE)
{
// 清除接收完成标志
HartTransferFlag &= (~COMM_STATE_RX_COMPLETE);
// // 数据接收完成,进行数据解析处理,如有需要,生成新的命令
// HartFrameResolution();
// 如解析数据成功,发送生成的命令帧
if(HartFrameResolution() == TRUE)
{
// 侦听载波,确保传输路径上的数据已经传输完成
//while((Get_OCD & 0x01) == 0x01);
// 开始发送数据
HartStartSendCommand();
}
else // 如解析数据失败,继续接收数据
{
HartTransferFlag = COMM_STATE_READY;
HartStartReceiveCommand(); // 开始接收数据
}
}
return;
}
发送数据
void HartStartSendCommand(void)
{
// 判断通讯接口是否为空闲状态
if((HartTransferFlag | COMM_STATE_READY) == COMM_STATE_READY)
{
// 设置计数差异为0,开始计时,用于判断通讯是否超时
TimerOutSoftCountus = TimerSoftCountus;
// 置为调制状态
INRTS_Low;
delay_ms(5);
// 设置为调制状态后,延时5ms,等待信号主机HART达到解调要求
//for(nTime = TimerSoftCountus; TimerSoftCountus - nTime < 5*1e3;);
// 清除空闲标志,设置发送忙标志
HartTransferFlag &= (~COMM_STATE_READY);
HartTransferFlag |= COMM_STATE_BUSY_TX;
// 设置通讯标志,即发送的数据结构
CommunicationFlag = BllWethFlag | DataOutFlag | DataCouFlag;
// 发送数据
sendData(HartSenBuffer,HartSenCount);
//sendData(testRead,0x16);
delay_ms(20);
//sendData(HartSenBuffer,HartSenCount);
//HAL_UART_Transmit_IT(&HartUsart, HartSenBuffer, HartSenCount);//stm32如果用了HAL库 就用这个发送
// 数据发送完成,清除发送忙标志
HartTransferFlag &= (~COMM_STATE_BUSY_TX);
// 设置数据发送完成标志
HartTransferFlag |= COMM_STATE_TX_COMPLETE;
}
return ;
}
数据解析
uint32_t HartFrameResolution(void)
{
uint32_t nResult = TRUE;
uint8_t nRecDataSize = 0;
uint8_t nSenDataSize = 0;
HartGetAddrCommand(HartRecBuffer); // 得到Hart地址和命令号
ClearDataBuffer(HartSenBuffer, HARTDATASIZE, 0x00); // 清空发送缓冲区
HartSenCount = 0; // 清空发送字节数
nResult = HartCompAddrCommand(); // 进行地址匹配
if(nResult == FALSE) // 如果地址匹配失败,表示此命令不是发给本机的命令,无需应答
return nResult;
// 校验接收到的数据
if(HartRecBuffer[HartRecCount - 1] != CheckXorFunc((HartRecBuffer + RecBellWetherNum), (HartRecCount - RecBellWetherNum)))
{
HartSenBuffer[HartSenCount - 1] = 2; // 字节数:通信错误标志 + 从机状态标志 + 数据
HartData.nVFunction.DevCommErrFlg = ERRCHECKSUM; // 设置校验和错误
HartSenCount += 1;HartSenBuffer[HartSenCount - 1] = HartData.nVFunction.DevCommErrFlg; // 通信错误标志字节
HartSenCount += 1;HartSenBuffer[HartSenCount - 1] = HartData.nVFunction.DevStateFlg; // 从机状态标志字节
}
else
{
HartData.nVFunction.DevCommErrFlg = NOER; // 设置无错误
switch(HartCommand) // 根据命令号设置发送和接收的字节数
{
case 0:{ nRecDataSize = 0; nSenDataSize = 2 + 12;break;}
case 1:{ nRecDataSize = 0; nSenDataSize = 2 + 5; break;}
case 2:{ nRecDataSize = 0; nSenDataSize = 2 + 8; break;}
case 3:{ nRecDataSize = 0; nSenDataSize = 2 + 24;break;}
case 6:{ nRecDataSize = 1; nSenDataSize = 2 + 1; break;}
case 11:{ nRecDataSize = 6; nSenDataSize = 2 + 12;break;}
case 12:{ nRecDataSize = 0; nSenDataSize = 2 + 24;break;}
case 13:{ nRecDataSize = 0; nSenDataSize = 2 + 21;break;}
case 14:{ nRecDataSize = 0; nSenDataSize = 2 + 16;break;}
case 15:{ nRecDataSize = 0; nSenDataSize = 2 + 17;break;}
case 16:{ nRecDataSize = 0; nSenDataSize = 2 + 3; break;}
case 17:{ nRecDataSize = 24;nSenDataSize = 2 + 24;break;}
case 18:{ nRecDataSize = 21;nSenDataSize = 2 + 21;break;}
case 19:{ nRecDataSize = 3; nSenDataSize = 2 + 3; break;}
case 33:{ nRecDataSize = 4; nSenDataSize = 2 + 24;break;}
case 34:{ nRecDataSize = 4; nSenDataSize = 2 + 4; break;}
case 35:{ nRecDataSize = 9; nSenDataSize = 2 + 9; break;}
case 36:{ nRecDataSize = 0; nSenDataSize = 2 + 0; break;}
case 37:{ nRecDataSize = 0; nSenDataSize = 2 + 0; break;}
case 38:{ nRecDataSize = 0; nSenDataSize = 2 + 0; break;}
case 39:{ nRecDataSize = 1; nSenDataSize = 2 + 1; break;}
case 40:{ nRecDataSize = 4; nSenDataSize = 2 + 4; break;}
case 41:{ nRecDataSize = 0; nSenDataSize = 2 + 0; break;}
case 42:{ nRecDataSize = 0; nSenDataSize = 2 + 0; break;}
case 43:{ nRecDataSize = 0; nSenDataSize = 2 + 0; break;}
case 44:{ nRecDataSize = 1; nSenDataSize = 2 + 1; break;}
case 45:{ nRecDataSize = 4; nSenDataSize = 2 + 4; break;}
case 46:{ nRecDataSize = 4; nSenDataSize = 2 + 4; break;}
case 210:{ nRecDataSize =42; nSenDataSize = 2 + 42;break;}
case 211:{ nRecDataSize = 2; nSenDataSize = 2 + 42;break;}
case 230:{ nRecDataSize =42; nSenDataSize = 2 + 42;break;}
case 231:{ nRecDataSize = 2; nSenDataSize = 2 + 42;break;}
case 240:{/*nRecDataSize = 0;nSenDataSize = 0;*/break;}
default:{/*nRecDataSize = 0;nSenDataSize = 0;*/break;}
}
if((HartRecCount - RecBellWetherNum - HartDeviceAddrSize - 4) < nRecDataSize) // 判断是否接收到与该命令相称的字节数
{
HartSenBuffer[HartSenCount - 1] = 2; // 字节数:通信错误标志 + 从机状态标志 + 数据
HartData.nVFunction.DevCommErrFlg = RECFEWDAT; // 设置 接收数据字节太少
HartSenCount += 1;HartSenBuffer[HartSenCount - 1] = HartData.nVFunction.DevCommErrFlg; // 通信错误标志字节
HartSenCount += 1;HartSenBuffer[HartSenCount - 1] = HartData.nVFunction.DevStateFlg; // 从机状态标志字节
}
// 根据命令号选择解析函数
else
{
BellWetherCount = HartData.nVFunction.DevSenBell;
ClearDataBuffer(HartSenBuffer, BellWetherCount, 0xFF); // 填充发送的导前符数
// 导前符 + 定界符 + 地址 + 命令 + 字节长度
// 导前符 :BellWetherCount
// 定界符 :1
// 地址 :(((uint8_t)(HartSenBuffer[BellWetherCount]/0x80))*4 + 1)
// 命令 :1
// 字节长度 :1
HartSenCount = BellWetherCount + (1 + ((HartSenBuffer[BellWetherCount] & 0x80)/0x80*4 + 1) + 1 + 1);
HartSenBuffer[HartSenCount - 1] = nSenDataSize; // 字节数:通信错误标志 + 从机状态标志 + 数据
HartSenCount += 1;HartSenBuffer[HartSenCount - 1] = HartData.nVFunction.DevCommErrFlg; // 通信错误标志字节
HartSenCount += 1;HartSenBuffer[HartSenCount - 1] = HartData.nVFunction.DevStateFlg; // 从机状态标志字节
switch(HartCommand)
{
case 0:{HartCommand_000();break;}
case 1:{HartCommand_001();break;}
case 2:{HartCommand_002();break;}
case 3:{HartCommand_003();break;}
case 6:{HartCommand_006();break;}
case 11:{HartCommand_011();break;}
case 12:{HartCommand_012();break;}
case 13:{HartCommand_013();break;}
case 14:{HartCommand_014();break;}
case 15:{HartCommand_015();break;}
case 16:{HartCommand_016();break;}
case 17:{HartCommand_017();break;}
case 18:{HartCommand_018();break;}
case 19:{HartCommand_019();break;}
case 33:{HartCommand_033();break;}
case 34:{HartCommand_034();break;}
case 35:{HartCommand_035();break;}
case 36:{HartCommand_036();break;}
case 37:{HartCommand_037();break;}
case 38:{HartCommand_038();break;}
case 39:{HartCommand_039();break;}
case 40:{HartCommand_040();break;}
case 41:{HartCommand_041();break;}
case 42:{HartCommand_042();break;}
case 43:{HartCommand_043();break;}
case 44:{HartCommand_044();break;}
case 45:{HartCommand_045();break;}
case 46:{HartCommand_046();break;}
case 210:{HartCommand_210();break;}
case 211:{HartCommand_211();break;}
case 230:{HartCommand_230();break;}
case 231:{HartCommand_231();break;}
case 240:{HartCommand_240();break;}
default:
{ClearDataBuffer(HartSenBuffer, 50, 0x00);break;}
}
}
}
// 计算校验值
HartSenCount = HartCountCommDataBytes(HartSenBuffer); // 得到数据字节数
HartSenBuffer[HartSenCount - 1] = CheckXorFunc((HartSenBuffer + BellWetherCount), (HartSenCount - BellWetherCount));// 根据数据字节数计算校验
return TRUE;
}
四、总结
HART协议多数应用在智能变送器和智能阀门定位器等产品上,需具备4-20mA的电流环路上的产品,所以不是每个硬件工程师都需要掌握,它和modbus很相似,按照协议编辑就好,难度不大,但代码量不小,如果有需要可以用来参考,依旧是有问题联系我。
五、下期计划
第三章 HART软件设计 — HART上位机QT