嵌入式系统通常都会与外部设备进行通讯,这就涉及到通讯协议的问题。这些通讯协议有的是标准协议有的厂家自定义的协议,如宇电的AI-BUS。在本篇中,我们将讨论AI-BUS的驱动,以便于与宇电设备的通讯。

1、功能概述

宇电的设备使用基于RS-485的自定义协议,该协议称为AI-BUS。AI-BUS协议采用16进制数据格式来表示各种指令代码。数据协议本身比较简单,标准的通讯指令只有两条,一条为读指令,一条为写指令:

读:地址代号+52H82+要读的参数代号+0+0+校验码

写:地址代号+43H67+要写的参数代号+写入数低字节+写入数高字节+校验码

具体结构如下图所示:

地址代号:为了在一个通讯接口上连接多台 AI 仪表,需要给每台 AI 仪表编一个互不相同的通讯地址。 有效的地址为 0~80(部分型号为 0~100),所以一条通讯线路上最多可连接 81 台 AI 仪表,仪表的通讯地址 由参数 Addr 决定。仪表内部采用两个重复的 128~208(16 进制为 80H~D0H)之间数值来表示地址代号,由 于大于 128 的数较少用到(如 ASC 方式的协议通常只用 0-127 之间的数),因此可降低因数据与地址重复造 成冲突的可能性。 AI 仪表通讯协议规定,地址代号为两个相同的字节,数值为(仪表地址+80H)。例如:仪表参数 Addr=10

(16 进制数为 0AH,0A+80H=8AH),则该仪表的地址代号为:8AH  8AH

参数代号:仪表的参数用 1 个 8 位二进制数(一个字节,写为 16 进制数)的参数代号来表示。它在指 令中表示要读/写的参数名。

校验码:校验码采用16 位求和校验方式,其中读指令的校验码计算方法为:要读参数的代号×256+82+ADDR。写指令的校验码计算方法为以下公式做16位二进制加法计算得出的余数(溢出部分不处理):要写的参数代号×256+67+要写的参数值+ADDR

公式中的数字都为十进制;公式中 ADDR 为仪表地址参数值,范围是 0~80(注意不要加上 80H)。校验 码为以上公式做二进制 16 位整数加法后得到的余数,余数为 2 个字节,其低字节在前,高字节在后。要写 的参数值用 16 位二进制整数表示。

返回的数据格式更是固定的,无论是读还是写,仪表都返回以下10个字节数据:

测量值 PV+给定值 SV+输出值 MV 及报警状态+所读/写参数值+校验码。

其中 PV、 SV 及所读参数值均各占 2 个字节,代表一个 16 位二进制有符号补码整数,低位字节在前,高位字节在后,整数无法表示小数点,要求用户在上位机处理; MV 占一个字节,按 8 位有符号二进制数格式,数值范围-110~+110,状态位占一个字节,校验码占 2 个字节,共 10 个字节。

返回校验码的计算公式:PV+SV+(报警状态*256+MV+参数值+ADDR

2、驱动设计与实现

我们已经清楚了AI-BUS协议的的基本规则,对于通讯协议的驱动开发,只需要按照通讯协议来实现代码就可以了。

2.1、对象定义

同样的我们在操作AI-BUS设备之前,我们先需要定义AI-BUS设备对象。然后针对AI-BUS设备的操作就是针对该对象的操作。

2.1.1、对象的抽象

根据AI-BUS设备对象的特点,我们抽象对象类型。该对象各类型包括设备地址和状态2个属性和发送命令一个操作。而对于消息的接收我们一般采用串口中断方式。对象类型定义如下:

/* 定义AI-BUS设备对象 */
typedef struct AIbusObject {uint8_t deviceAddr;uint8_t status;void (*SendBytes)(uint8_t *cmd,uint16_t size);
}AIbusObjectType;

2.1.2、对象初始化

定义了AI-BUS对象类型后,我们就可以使用该类型声明不同的对象,但是声明的对象仅为一个对象变量,在使用之前必须对其进行初始化,初始化函数如下:

/* AI-BUS对象初始化 */
void AIbusInitialization(AIbusObjectType *aibus,uint8_t addr,AiBusSendBytes send)
{if((aibus==NULL)||(send==NULL)){return;}aibus->deviceAddr=addr;aibus->SendBytes=send;
}

2.2、对象操作

完成了对象的初始化后就可以实现对对象的操作了。根据前面对AI-BUS协议的了解,我们所需要完成的操作实际上就是3个方面。一是对目标设备参数的读操作;二是对目标设备参数的写操作;三是对接收到的消息进行解析。

2.2.1、读对象操作

对AI-BUS对象的读就是将读命令按一定格式下发就好了。读命令的格式为:地址代号+52H82+要读的参数代号+0+0+校验码。可以据此编写读操作如下:

/*读取目标设备的参数值*/
void ReadAiBusDeviceParameter(AIbusObjectType *aibus,uint8_t paraAddr)
{uint8_t readCommand[INSTRUCTION_LENGTH];uint16_t index=0;readCommand[index++]=0x80+aibus->deviceAddr;readCommand[index++]=0x80+aibus->deviceAddr;readCommand[index++]=READ_INSTRUCTION;readCommand[index++]=paraAddr;readCommand[index++]=0x0;readCommand[index++]=0x0;uint16_t checkSum=(uint16_t)paraAddr*256+READ_INSTRUCTION+(uint16_t)aibus->deviceAddr;readCommand[index++]=checkSum;readCommand[index++]=(checkSum>>8);aibus->SendBytes(readCommand,INSTRUCTION_LENGTH);
}

2.2.2、写对象操作

同样,对AI-BUS对象的写操作也是按照写命令的格式下发命令就可以了。写对象的命令格式为:地址代号+43H67+要写的参数代号+写入数低字节+写入数高字节+校验码。我们据此可以编写写操作函数:

/*设置目标设备的参数值*/
void WriteAiBusDeviceParameter(AIbusObjectType *aibus,uint8_t paraAddr,uint16_t data)
{uint8_t writeCommand[INSTRUCTION_LENGTH];uint16_t index=0;writeCommand[index++]=0x80+aibus->deviceAddr;writeCommand[index++]=0x80+aibus->deviceAddr;writeCommand[index++]=WRITE_INSTRUCTION;writeCommand[index++]=paraAddr;writeCommand[index++]=data;writeCommand[index++]=(data>>8);uint16_t checkSum=(uint16_t)paraAddr*256+WRITE_INSTRUCTION+(uint16_t)aibus->deviceAddr+data;writeCommand[index++]=checkSum;writeCommand[index++]=(checkSum>>8);aibus->SendBytes(writeCommand,INSTRUCTION_LENGTH);
}

2.2.3、消息解析

我们已经知道AI-BUS对象的返回消息是一个固定的的格式。即:测量值 PV+给定值 SV+输出值 MV 及报警状态+所读/写参数值+校验码。每一个字都是低字节在前,而校验码则是返回的数据和加上设备地址,我们据此编写解析函数:

/*解析返回数据,返回值为读或者写的参数值*/
int ParsingReturnData(uint8_t *receiveData,uint16_t *returnData,AIbusObjectType *aibus,uint16_t deviceNum)
{int status=-1;uint16_t pValue=0;uint16_t sValue=0;uint16_t mValue=0;uint16_t alarmStatus=0;uint16_t paraValue=0;uint16_t checkSum=0;pValue=receiveData[0]+receiveData[1]*256;sValue=receiveData[2]+receiveData[3]*256;mValue=(uint16_t)receiveData[4];alarmStatus=(uint16_t)receiveData[5];paraValue=receiveData[6]+receiveData[7]*256;checkSum=receiveData[8]+receiveData[9]*256;uint16_t chk=pValue+sValue+alarmStatus*256+mValue+paraValue;for(int i=0;i<deviceNum;i++){if(checkSum==chk+aibus[i].deviceAddr){status=i;returnData[0]=pValue;returnData[1]=sValue;returnData[2]=mValue;returnData[3]=alarmStatus;returnData[4]=paraValue;break;}}if(status>=0){aibus[status].status=alarmStatus;}return status;
}

3、驱动的使用

AI-BUS协议设备驱动的使用主要按照三个步骤来操作:声明并初始化对象;发送操作命令;接收并解析消息。接下来我们将据此完成驱动的使用。

3.1、声明并初始化对象

我们需要使用AIbusObjectType类型声明对象变量。同时我们要实现一个typedef void (*AiBusSendBytes)(uint8_t *cmd,uint16_t size)类型的操作函数。我们假设使用的USART1端口,则具体实现如下:

/*发送数据*/
void AiBusSendByte(uint8_t *instruction,uint16_t length)
{/*RS485设置为发送模式,准备发送*/TEMPCTL_TRANSMIT_ALLOW();aiBusRxLength=0;uint16_t i;for(i=0;i<length;i++){/*传送寄存器不为空,等待传送结束*/while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET){}/*写一个字节到对应的串口传送数据寄存器*/USART_SendData(USART1, instruction[i]);}Delayms(3);/*发送完毕,将RS485改为接收模式准备接收*/TEMPCTL_RECIEVE_ALLOW();Delayms(20);
}

对于单个的对象我们直接使用其声明就可:AIbusObjectType aiDev;假设其设备地址为uint8_t addr=0x01。然后调用初始化函数初始化。对于单台设备则可直接调用:

AIbusInitialization(&aiDev,addr,AiBusSendByte);

而如果是在同一总线上有多个设备,我们也可以将其定义为数组形式。假设有4台设备,地址我分别为:0x01,0x02,0x03,0x04,则可定义为:

AIbusObjectType aiDev[4];

uint8_t addr[4]={0x01,0x02,0x03,0x04};

然后同样调用初始化函数初始化:

for(int i=0;i<4;i++)

{

AIbusInitialization(aiDev+i,addr[i],AiBusSendByte);

}

3.2、发送操作命令

初始化完成后就可以对其进行真正的操作:读取或者写某个参数的值。对于读参数的值操作则只需要调用读操作函数来完成:

ReadAiBusDeviceParameter(&aiDev,paraAddr);

而对于写参数值的操作也只是简单的调用写操作函数来完成:

WriteAiBusDeviceParameter(&aiDev,paraAddr,data);

如果是多个对象,与前面一样操作数组的方式来操作就可以了,再次就不赘述。

3.3、接收并解析消息

接收消息我们采用串口中断接收。具体实现如下:

void USART1_ReceiveDataHandle(void)
{if(aiBusRxLength>=RETURNING_DATA_LENGTH){aiBusRxLength=0;}/*接收寄存器为空,等待字节被对应的串口完全接收*/if(USART_GetFlagStatus(USART1, USART_IT_RXNE) != RESET){  /*获取接收到的字节数*/aiBusRxBuffer[aiBusRxLength++] = USART_ReceiveData(USART1);}
}

我们知道接受的消息格式是固定的,我们调用消息解析函数来完成解析:

ParsingReturnData(receiveData,returnData,&aiDev,deviceNum);

其中receiveData是长度为10的uint8_t类型数组。returnData是长度为5的uint16_t类型数组。

4、应用总结

我们完成了AI-BUS驱动的编写及应用。我们使用其同时操作4台温度控制器。我们操作数组的方式简化函数的调用过程。当然结果与我们的预期是相符的。

使用本驱动程序操作AI-BUS设备,有一点需要注意:对于不懂的设备类型,参数的具体地址是用所不同的,需要查看厂家的参数定义来操作。

欢迎关注:

外设驱动库开发笔记30:宇电AI-BUS通讯驱动相关推荐

  1. 外设驱动库开发笔记3:AD527x系列数字电位器驱动

    在一些时候我们需要使用精度更高的数字电位器来实现我们的应用.我们经常使用AD527x系列数字电位器来实现这类应用.在通常情况下,AD527x系列数字电位器完全能够满足要求.为了减少重复工作,在这里我们 ...

  2. 外设驱动库开发笔记2:AD8400系列数字电位器驱动

    一些时候我们需要在系统使用过程中改变某些电路电阻值以达到改变设定的目的,这时候我们就会使用电位器.在我们使用数字控制电路时多选择数字电位器.在这一篇我们就来设计AD8400系列数字电位器的驱动. 1. ...

  3. 外设驱动库开发笔记24:FM24xxx系列FRAM存储器驱动

    虽然说使用EEPROM保存参数很有效,但操作及使用次数均有一下限制.当我们的一些参数需要不定时修改或存储时,使用FRAM就更为方便一点.这一节我们就来设计并实现FM24xxx系列FRAM的驱动. 1. ...

  4. 外设驱动库开发笔记48:MCP4725单通道DAC驱动

      在产品设计过程中,我们经常会遇到数模转换的应用需求.在本篇种我们就来讨论一下MCP4725单通道数模转换器的驱动设计与实现. 1.功能概述   MCP4725是一个低功耗,高精度,单通道,12位缓 ...

  5. 外设驱动库开发笔记26:nRF24L01无线通讯驱动

    现在无线在我们的生活中无处不在.而我们开发的物联网产品也大量使用无线通讯.在这一篇文章中,我们将讨论nRF24L01无线通讯模块驱动程序的开发与实现. 1.功能概述 nRF24L01是一款工作在2.4 ...

  6. 外设驱动库开发笔记0:EPD总体设计

    在产品开发过程中,不可避免需要使用很多外部的元件及传感器,这些元器件也许是板载的,也许是板外的,但不管怎样,为其开发驱动程序都是必须的.每次都需要为这些元器件编写驱动程序.但每次重复编写调试很麻烦,于 ...

  7. 外设驱动库开发笔记40:AT25xxx外部存储器驱动

      我们在前面开发过AT24CXX系列EEPROM存储器,它使用的是I2C接口.不过有时候我们也会使用SPI接口的EEPROM存储器.在这一篇我们将来讨论AT25XXX系列EEPROM存储器的驱动设计 ...

  8. 外设驱动库开发笔记13:MLX90614红外温度传感器驱动

    红外温度传感器一般用于非接触式的温度检测.在我们的系统中经常会有这样的需求.所以我们将其设计为通用的驱动库以备复用.这一篇我们将讲述MLX90614红外温度传感器驱动的设计与实现. 1.功能概述 ML ...

  9. 外设驱动库开发笔记6:AD719x系列ADC驱动

    前面我们讨论了AD7705这种ADC器件的驱动开发,在实际中我们使用更多的是AD719x系列的ADC芯片.包括有AD7191.AD7192和AD7193等.接下来我们就来设计并开发AD719x的驱动程 ...

最新文章

  1. 微信小游戏开发教程-游戏实现3
  2. PYTHON线程知识再研习F---队列同步Queue
  3. 服务器主板开机无显维修,电脑主板开机无显示的维修方法-1
  4. 运维自动化之ansible playbook安装apache
  5. 用go语言制作读取excel模板批量生成表格工具
  6. 判断均匀平面波的极化形式_测瑞通|怎样判断电波暗室的性能?
  7. SQLServer之分离数据库
  8. GridView分页后RowCommand出错:索引超出范围
  9. 用Gridview和ObjectDataSource轻松实现自定义分页
  10. 02_常用正则表达式
  11. mysql zerofill设置方法_在MySQL中使用ZEROFILL设置自定义自动增量
  12. 再次遇到golang乱码问题,用simplifiedchinese解决
  13. CentOS6.5使用rsync远程同步
  14. 心理学推荐书籍——《九型人格》
  15. javaScript常用案例
  16. 此图片来自微信公众平台 未经允许不可引用
  17. linux检查网络是否通畅_linux下怎么检测网络的连通性
  18. 微信公众号、微信号、微信文章爬虫(搜狗搜索)
  19. 维特比译码算法(Viterbi decoding algorithm)
  20. Fedora和Red Hat Enterprise Linux实用指南(第6版)(上、下册)( 入行必读的Linux圣经)

热门文章

  1. 当知识图谱遇上推荐系统之PippleNet模型(论文笔记二)
  2. 小程序基础能力~网络
  3. MicroPython (一)点亮我的Led
  4. iOS一次定位解决方案(基于高德iOS SDK)
  5. ubuntu 上已经安装libxml2还提示需要reinstall的解决方法
  6. nagios 整合 ganglia 设置邮件、短信报警
  7. 解决FileUpload控件上传大文件被拒问题时
  8. vue-day01-vue模板语法
  9. spring学习笔记02-spring-bean创建的细节问题
  10. 用python设计学生管理系统_基于python和tkinter实现的一个简单的学生信息管理系统...