大家好,欢迎来到IT知识分享网。
文章目录
在学习HART协议前 请先了解基础的协议 如串口 SPI I2C等
HART协议
HART(Highway Addressable Remote Transducer),可寻址远程传感器高速通道的开放通信协议,Hart协议比较大的特点在于支持模拟信号和数字信号同时传递。
参考:
blog.csdn.net/weixin_/article/details/ blog.csdn.net/lixiaojie/article/details/
download.csdn.net/download/weixin_/
- 数模转换测量数据
- 基于频率数据传输
HART协议类似于早期坐机电话协议 早期的电话传输的是声音模拟量,包括了电压、频率,以此来反应不同的声音。而后加入了“来电显示”等数字量,就要采用HART协议类似的方式来传输,即在模拟量的基础上加上数字量。
依次排列下来即可得到数据
HART协议的数字量空闲时为1,即频率恒定1200Hz,而当表示8位数据时,则需要一个起始位0 一个校验位和一个停止位0 中间8位才是数据位
一般而言 HART的调制解调器芯片的HART输入输出端是无法控制数据格式的 是严格按照HART标准来执行的 也就是说 无法更改HART传输部分的校验位、数据长度等 也无法更改MSB或LSB 所以这点不用担心 只需要处理好MCU与调制解调器芯片之间的通信即可
HART协议的数据链路层与UART协议相似 所以一般的调制解调器芯片与MCU的通信协议都采用串口协议 并且波特率也是1200(只要保证数据实时性即可 所以只要传输的时间小于等于HART协议传输时间即可) 但千万不要将HART协议与串口协议相混淆
HART协议的模拟量部分就很好理解了 就是数模转换而已 不用再过多介绍
在使用HART器件时 通常都是用MCU连接一个HART协议的调制解析器 然后再连接到HART器件
HART协议的调制解析器有很多种 比如可以采用AD5700+AD5421构成的HART组合 但都大同小异 都是采用UART的方式进行与MCU的通信(如果用AD5421组合则是SPI)
在HART模块中,通常是通过UART发送一个字节,且需要11位二进制数据,第一位起始位,第二到九位共八位是要发送的数据,第十位是校验位<奇校验>,第十一位是停止位。
AD5700与MCU的串口通信中 是没有校验位的 但是AD5700的HART协议部分是严格按照HART协议规范来的 所以有校验位 (别杠 不信可以去买个AD5700打一下示波器)
- PREAMBLE 前导字符,作为通信同步的需要,是5到20个字节的0XFF,通常采用5个字节; 也就是5个0xFF
- START 起始字节符,将告之使用的结构为“长”还是“短”,消息源是否是“突发”模式消息。主机到从机为短结构时,起始位为 0X02,长帧时为 0X82。从机到主机的短结构值为 0X06,长结构值为0X86。而为“突发”模式的短结构值为 0X01,长结构为 0X81。一般设备进行通讯接收到 2 个0XFF 字节后,就将侦听起始位。
- ADDR 地址字符,包含了主机地址和从机地址,短结构中占 1 字节,长结构中占 5 字节。
主机到从机为短结构时,起始位为02,长帧时为82。
从机到主机的短结构值为06,长结构值为86
“突发”模式的短结构值为01,长结构为81
短指令时:地址码由一个字节表示(如80),其结构为
一般我们不用到突发模式,这里的80表示的就是(主机1—-设备地址(0))
长指令时:地址码由5个字节表示,其结构为
另外,长结构的低 38 位如果都是 0 的话表示的是广播地址,即消息发送给所有的设备。
- COM 命令字节,范围为 253 个,用 HEX 的 0~FD 表示。31,127,254,255 为预留值。PS:一开始对此处提到的命令很困惑,就在心里发问,每个命令都什么含义?所有的厂家都支持么?终于,在一篇名为HCF_SPEC 99官方文档中找到
- BCNT 数据总长度,它的值表示的是 BCNT 下一个字节到最后(不包括校验字节)的字节数,其实说了这么多,无非是因为从机回传的数据中多了一个状态码,明确也是要算在数据长度的。接收设备用他可以鉴别出校验字节,也可以知道消息的结束。因为规定数据最多为 25 字节,所以它的值是从 0~27。
- STATUS 状态字节,也叫做“响应码”,顾名思义,只存在于从机响应主机消息的时候,用 2 字节表示。他将报告通讯中的错误、接收命令的状态(如:设备忙、无法识别命令等)和从机的操作状态。 如果我们在通讯过程中发现了错误,首字节的最高位(第 7 位)将置 1,其余的 7 位将汇报出错误的细节,而第 2 个字节全为 0。否则,当首字节的最高位为 0 时,表示通讯正常,其余的 7 位表示命令响应情况,第 2 个字节表示场设备状态的信息。UART 发现的通讯错误一般有:奇偶校验、溢出和结构错误等。命令响应码可以有 128 个,表示错误和警告,他们可以是单一的意义,也可以有多种意义,我们通过特殊命令进行定义、规定。现场设备状态信息用来表示故障和非正常操作模式。此处依然是一个专门的文档来叙述 HCF_SPEC_307 。
- DATA 数据字节,首先需说明的是并非所有的命令和响应都包含数据字节,它最多不超过 25 字节(随着通讯速度的提高,正在要求放宽这一标准)。数据的形式可以是无符号的整数(可以是8,16,24,32 b),浮点数(用 IEEE754 单精浮点格式)或 ASCII 字符串,还有预先制定的单位数据列表。具体的数据个数根据不同的命令而定!此处查阅官方文档 HCF_SPEC_183<数据格式表> 此中数据量相当大 HCF_SPEC 99 HCF_SPEC 127 HCF_SPEC-151;
- CHK 奇偶校验,方式是纵向奇偶校验,从起始字节开始到奇偶校验前一个字节为止。
在这些数据中,除了数据长度对应的数据位不同,起始字节符对应的地址字符也不同,在做命令解析的时候 要根据起始字节和数据大小来判断数据位的位置
上面是突发模式发出的一条消息。第 1 个字节 81 表示突发的长结构模式,与前例中相似的地方不再介绍。注意到状态字节“00 60”后的字节“41 3F A0 00”,他表示的是当前的电流值,是IEEE754格式的浮点数,计算后是 11.9766;后面的 27 表示单位 mA,像后面的 39 表示“%”一样。数据字节中的”42 47 60 00″,“BF 06 60 00”,“41 95 00 00”分别表示”SV”,“TV”,”FV”表示方法与 PV 相同。经过解释后的消息可以表示为:“LBTXS/RdAllPv/026/0060/11.9766/mA/11.9766/%/49.8438/psi/-0./%/18.625”。 所有的数据格式在文档资料Common Tables HCF_SPEC 183中。附命令3——属于通用命令,可在HCF_SPEC_127中查找
以下代码为串口信号解析:
#include<stdio.h> #include<stdint.h> #include <string.h> typedef struct {
uint8_t PREAMBLE[5]; uint8_t START; uint8_t ADDR[5]; uint8_t COM; uint8_t BCNT; uint8_t STATUS[2]; uint8_t DATA[25]; uint8_t CHK; }HART_Struct; HART_Struct trans_HART_to_Struct(uint8_t * buf) {
HART_Struct HART_Stu; uint8_t i=0; uint8_t START_Num=0; uint8_t COM_Num=0; uint8_t CHK_Num=0; uint8_t Check_Sum=0; if(buf[0]==0xFF) {
for(i=0;i<6;i++) {
if(buf[i]!=0xFF) {
HART_Stu.START=buf[i]; START_Num=i; break; } START_Num=i; } if(START_Num<2) {
return HART_Stu; } for(i=0;i<START_Num;i++) {
HART_Stu.PREAMBLE[i]=buf[i]; } if(HART_Stu.START<0x10) {
COM_Num=START_Num+2; } else {
COM_Num=START_Num+6; } for(i=0;i<COM_Num-START_Num-1;i++) {
HART_Stu.ADDR[i]=buf[START_Num+1+i]; } HART_Stu.COM=buf[COM_Num]; HART_Stu.BCNT=buf[COM_Num+1]; CHK_Num=COM_Num+HART_Stu.BCNT+2; HART_Stu.CHK=buf[CHK_Num]; for(i=START_Num;i<CHK_Num;i++) {
Check_Sum=Check_Sum^buf[i]; } if(HART_Stu.CHK==Check_Sum) {
if(HART_Stu.BCNT>0) {
if(HART_Stu.BCNT>1 && (HART_Stu.START&0x0F)==0x06) {
for(i=0;i<2;i++) {
HART_Stu.STATUS[i]=buf[COM_Num+2+i]; } for(i=0;i<HART_Stu.BCNT-2;i++) {
HART_Stu.DATA[i]=buf[COM_Num+4+i]; } } else {
for(i=0;i<HART_Stu.BCNT;i++) {
HART_Stu.DATA[i]=buf[COM_Num+2+i]; } } } else {
return HART_Stu; } } else {
return HART_Stu; } } else {
return HART_Stu; } return HART_Stu; } int8_t trans_HART_to_Buf(HART_Struct HART_Stu,uint8_t * buf) {
uint8_t i=0; uint8_t START_Num=0; uint8_t COM_Num=0; uint8_t CHK_Num=0; uint8_t Check_Sum=0; if(HART_Stu.PREAMBLE[0]==0xFF) {
for(i=0;i<6;i++) {
if(HART_Stu.PREAMBLE[i]!=0xFF) {
buf[i]=HART_Stu.START; START_Num=i; break; } START_Num=i; } if(START_Num<2) {
return -1; } for(i=0;i<START_Num;i++) {
buf[i]=HART_Stu.PREAMBLE[i]; } if(HART_Stu.START<0x10) {
COM_Num=START_Num+2; } else {
COM_Num=START_Num+6; } CHK_Num=COM_Num+HART_Stu.BCNT+2; buf[START_Num]=HART_Stu.START; for(i=0;i<COM_Num-START_Num-1;i++) {
buf[START_Num+1+i]=HART_Stu.ADDR[i]; } buf[COM_Num]=HART_Stu.COM; buf[COM_Num+1]=HART_Stu.BCNT; buf[CHK_Num]=HART_Stu.CHK; if(HART_Stu.BCNT>1 && (HART_Stu.START&0x0F)==0x06) {
for(i=0;i<2;i++) {
buf[COM_Num+2+i]=HART_Stu.STATUS[i]; } for(i=0;i<HART_Stu.BCNT-2;i++) {
buf[COM_Num+4+i]=HART_Stu.DATA[i]; } } else {
for(i=0;i<HART_Stu.BCNT;i++) {
buf[COM_Num+2+i]=HART_Stu.DATA[i]; } } for(i=START_Num;i<CHK_Num;i++) {
Check_Sum=Check_Sum^buf[i]; } if(HART_Stu.CHK==Check_Sum) {
return 1; } else {
return -2; } } else {
return -1; } return 0; } int main(void) {
unsigned char list[41]={
0xFF,0xFF,0xFF,0xFF,0xFF,0x86,0xA6,0x06,0xBC,0x61,0x4E,0x01,0x07,0x00,0x00,0x06,0x40,0xB0,0x00,0x00,0x45}; HART_Struct a; uint8_t buf[41]; memset(buf,0,sizeof(buf)/sizeof(buf[0])); a=trans_HART_to_Struct(list); printf("%02X\n",a.CHK); printf("%d\n",trans_HART_to_Buf(a,buf)); printf("%02X\n",buf[8]); for(uint8_t i=0;i<41;i++) {
if(buf[i]==list[i]) {
} else {
printf("ERROR\n"); break; } } }
代码经检验后顺利通过不报错
附录:压缩字符串、大小端格式转换
压缩字符串
浮点数
浮点数里面 如 0x40 80 00 00表示4.0f
在HART协议里面 浮点数是按大端格式发送的 就是高位先发送 低位后发送
发送出来的数组为:40,80,00,00
大小端转换:
void swap32(void * p) {
uint32_t *ptr=p; uint32_t x = *ptr; x = (x << 16) | (x >> 16); x = ((x & 0x00FF00FF) << 8) | ((x >> 8) & 0x00FF00FF); *ptr=x; }
压缩Packed-ASCII字符串
压缩/解压函数后面再写:
//传入的字符串和数字必须提前声明 且字符串大小至少为str_len 数组大小至少为str_len%4*3 str_len必须为4的倍数 uint8_t Trans_ASCII_to_Pack(uint8_t * str,uint8_t * buf,const uint8_t str_len) {
if(str_len%4) {
return 0; } uint8_t i=0; memset(buf,0,str_len/4*3); for(i=0;i<str_len;i++) {
if(str[i]==0x00) {
str[i]=0x20; } } for(i=0;i<str_len/4;i++) {
buf[3*i]=(str[4*i]<<2)|((str[4*i+1]>>4)&0x03); buf[3*i+1]=(str[4*i+1]<<4)|((str[4*i+2]>>2)&0x0F); buf[3*i+2]=(str[4*i+2]<<6)|(str[4*i+3]&0x3F); } return 1; } //传入的字符串和数字必须提前声明 且字符串大小至少为str_len 数组大小至少为str_len%4*3 str_len必须为4的倍数 uint8_t Trans_Pack_to_ASCII(uint8_t * str,uint8_t * buf,const uint8_t str_len) {
if(str_len%4) {
return 0; } uint8_t i=0; memset(str,0,str_len); for(i=0;i<str_len/4;i++) {
str[4*i]=(buf[3*i]>>2)&0x3F; str[4*i+1]=((buf[3*i]<<4)&0x30)|(buf[3*i+1]>>4); str[4*i+2]=((buf[3*i+1]<<2)&0x3C)|(buf[3*i+2]>>6); str[4*i+3]=buf[3*i+2]&0x3F; } return 1; }
大小端转换
在串口等数据解析中 难免遇到大小端格式问题
什么是大端和小端
所谓的大端模式,就是高位字节排放在内存的低地址端,低位字节排放在内存的高地址端。
所谓的小端模式,就是低位字节排放在内存的低地址端,高位字节排放在内存的高地址端。
简单来说:大端——高尾端,小端——低尾端
举个例子,比如数字 0x12 34 56 78在内存中的表示形式为:
1)大端模式:
低地址 —————–> 高地址
0x12 | 0x34 | 0x56 | 0x78
2)小端模式:
低地址 ——————> 高地址
0x78 | 0x56 | 0x34 | 0x12
可见,大端模式和字符串的存储模式类似。
数据传输中的大小端
以大端存储来说 发送出来的buf就是依次发送 40 80 00 00
以小端存储来说 则发送 00 00 80 40
uint32_t dat=0; uint8_t buf[]={
0x00,0x00,0x80,0x40}; memcpy(&dat,buf,4); float f=0.0f; f=*((float*)&dat); //地址强转 printf("%f",f);
或更优解:
uint8_t buf[]={
0x00,0x00,0x80,0x40}; float f=0.0f; memcpy(&f,buf,4);
uint32_t dat=0; uint8_t buf[]={
0x40,0x80,0x00,0x00}; memcpy(&dat,buf,4); float f=0.0f; swap32(&dat); //大小端转换 f=*((float*)&dat); //地址强转 printf("%f",f);
或:
uint8_t buf[]={
0x40,0x80,0x00,0x00}; memcpy(&dat,buf,4); float f=0.0f; swap32(&f); //大小端转换 printf("%f",f);
或更优解:
uint32_t dat=0; uint8_t buf[]={
0x40,0x80,0x00,0x00}; float f=0.0f; dat=(buf[0]<<24)|(buf[0]<<16)|(buf[0]<<8)|(buf[0]<<0) f=*((float*)&dat);
总结
固 若数据为小端格式 则可以直接用memcpy函数进行转换 否则通过移位的方式再进行地址强转
对于多位数据 比如同时传两个浮点数 则可以定义结构体之后进行memcpy复制(数据为小端格式)
对于小端数据 直接用memcpy写入即可 若是浮点数 也不用再进行强转
对于大端数据 如果不嫌麻烦 或想使代码更加简洁(但执行效率会降低) 也可以先用memcpy写入结构体之后再调用大小端转换函数 但这里需要注意的是 结构体必须全为无符号整型 浮点型只能在大小端转换写入之后再次强转 若结构体内采用浮点型 则需要强转两次
所以对于大端数据 推荐通过移位的方式来进行赋值 然后再进行个别数的强转 再往通用结构体进行写入
大小端转换函数
void swap16(void * p) {
uint16_t *ptr=p; uint16_t x = *ptr; x = (x << 8) | (x >> 8); *ptr=x; } void swap32(void * p) {
uint32_t *ptr=p; uint32_t x = *ptr; x = (x << 16) | (x >> 16); x = ((x & 0x00FF00FF) << 8) | ((x >> 8) & 0x00FF00FF); *ptr=x; } void swap64(void * p) {
uint64_t *ptr=p; uint64_t x = *ptr; x = (x << 32) | (x >> 32); x = ((x & 0x0000FFFF0000FFFF) << 16) | ((x >> 16) & 0x0000FFFF0000FFFF); x = ((x & 0x00FF00FF00FF00FF) << 8) | ((x >> 8) & 0x00FF00FF00FF00FF); *ptr=x; }
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/125978.html