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

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

1、功能概述

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

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

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

具体结构如下图所示:

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

16 进制数为 0AH0A+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 *cmduint16_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(&aiDevaddrAiBusSendByte);

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

AIbusObjectType aiDev[4]

uint8_t addr[4]={0x010x020x030x04};

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

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

{

AIbusInitialization(aiDev+iaddr[i]AiBusSendByte);

}

3.2、发送操作命令

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

ReadAiBusDeviceParameter(&aiDevparaAddr);

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

WriteAiBusDeviceParameter(&aiDevparaAddrdata);

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

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(receiveDatareturnData&aiDevdeviceNum);

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

4、应用总结

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

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

欢迎关注:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/499328.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

步进电机驱动技术3:基于ULN2003的步进电机驱动

在我们的项目中&#xff0c;经常使用到低电压小功率的步进电机&#xff0c;此类步进电机若采用驱动器控制不断成本高也过于复杂&#xff0c;我们可以直接使用场效应管或者达林顿管来实现对其的驱动。在本篇中&#xff0c;我们就来讨论一下基于ULN2003A达林顿管实现对步进电机的…

通讯接口应用笔记2:MAX3160实现多协议通讯

在一些应用需求中&#xff0c;我们需要对外部提供串行通讯端口&#xff0c;但这些端口所通讯的目标设备各有不同&#xff0c;接口协议也有RS232以及RS485和RS422多种。面对这种情况&#xff0c;我们当然可以同时设计多个串口以适应不同需要&#xff0c;但无疑对硬件资源是一种浪…

电机速度曲线规划1:梯形速度曲线设计与实现

电机驱动是很常见的应用&#xff0c;在很多系统中我们都会碰到需要改变电机的速度以实现相应的控制功能&#xff0c;这就涉及到电机速度曲线规划的问题。在这篇中我们就来简单讨论一下电机的梯形曲线规划的问题。 1、基本原理 梯形速度曲线控制算法是工业控制领域应用最为广泛…

文件系统应用笔记之一:FatFS在STM32F4上的移植

在实现如U盘文件读写&#xff0c;SD卡的文件读写等工作时&#xff0c;我们往往需要一个文件系统来支持我们的工作。特别在一些MCU应用中&#xff0c;文件系统的加入能明显改善系统交互的友好性。在这一篇中&#xff0c;我们就来讨论FatFS文件系统在STM32F4上的移植和应用。 1、…

通讯接口应用笔记3:使用W5500实现Modbus TCP服务器

前面我们设计实现了W5500的驱动程序&#xff0c;也讲解了驱动的使用方式。在最近一次的项目应用中&#xff0c;正好有一个使用W5500实现TCP通讯的需求&#xff0c;所以我们就使用该驱动程序轻松实现。这一篇中我们就来说一说基于我们W5500通讯驱动程序实现TCP通讯的过程。 1、…

电机速度曲线规划2:S形速度曲线设计与实现

电机驱动是很常见的应用&#xff0c;在很多系统中我们都会碰到需要改变电机的速度以实现相应的控制功能&#xff0c;这就涉及到电机速度曲线规划的问题。在这篇中我们就来简单讨论一下电机的S型曲线规划的问题。 1、基本原理 S型速度曲线控制算法是工业控制领域另一种常用的加…

USB应用开发笔记之一:STM32上实现USB主机读写U盘

在项目应用中&#xff0c;经常会有对外交换数据的需求。USB接口读写U盘无疑是一种颇为方便的选择。在这一篇中&#xff0c;我们就来讨论如何在STM32上实现USB主机读写U盘文件的方法。 1、应用概述 在我们的产品上有这样一个需求&#xff0c;希望通过大容量的U盘存取数据。我们…

Modbus协议栈综合实例设计

自我们开源了我们的Modbus协议栈之后&#xff0c;就一直有朋友来信说希望提供示例。这次我们整理了几个例子以供参考。 1、应用实例规划 在这次的实例中&#xff0c;我们使用的目标板拥有一个以太网接口、一个RS232串行接口和一个RS485串行接口&#xff0c;所以我们规划实现&a…

ThreadX应用开发笔记之二:移植ThreadX到STM32H7平台

前面我们将ThreadX成功移植到了STM32F4平台&#xff0c;但这只是我们的部分应用。我们希望将ThreadX的优势发挥到我们的更多应用中&#xff0c;所以在这一篇中我们就来实现将ThreadX移植到STM32H7平台中。 1、前期准备 在开始将ThreadX移植到STM32H7平台之前&#xff0c;我们需…

外设驱动库开发笔记31:S-Modlue远红外气体传感器驱动

在气体分析类产品中&#xff0c;我们经常会用到远红外气体传感器。我们就在碳氢类气体成分分析中使用了S-Modlue远红外气体传感器。接下来&#xff0c;我们将讨论S-Modlue远红外气体传感器驱动的设计与实现。 1、功能概述 S-MODULE EVO 使用非分散红外检测技术NDIR&#xff0c…

外设驱动库开发笔记32:HLPM025K3 PM2.5传感器驱动

现在人们对大气环境及室内环境都比较关注。PM2.5在生活中也是常见的词汇。在有些产品中就要求检测PM2.5的数值。检测PM2.5的手段多种多样&#xff0c;在要求不高时我们通常可以采用激光模块。在这一篇中&#xff0c;我们将讨论HLPM025K3 PM2.5传感器驱动的设计与实现。 1、功能…

外设驱动库开发笔记33:LCD1602液晶显示屏驱动

LCD1602是一种工业字符型液晶&#xff0c;能够同时显示16x02即32个字符。LCD1602液晶显示的原理是利用液晶的物理特性&#xff0c;通过电压对其显示区域进行控制&#xff0c;即可以显示出图形。在这一章我们就来讨论LCD1602液晶显示屏驱动的设计与实现。 1、功能概述 LCD1602液…

滤波器开发之四:基于算术平均的中值滤波器

在信号采集系统中&#xff0c;除了我们感兴趣的数据外&#xff0c;难免会有一些来自于环境的干扰信号。但我们总希望我们得到的数据是纯净而真实的&#xff0c;为了达到这个目标&#xff0c;我们不得不想办法去除这些干扰信号&#xff0c;于是滤波器就成为我们必不可少的帮手。…

LwIP应用开发笔记之十一:LwIP带操作系统UDP服务器

我们已经实现了在FreeRTOS系统上的LwIP的移植工作&#xff0c;但只是简单的在系统平台上跑了起来。我们还希望能做更多的事情&#xff0c;这一节我们就在FreeRTOS系统上实现基于LwIP的UDP服务器。 1、UDP协议简述 UDP协议全称是用户数据报协议&#xff0c;在网络中它与TCP协议…

滤波器开发之五:基于算术平均的限幅滤波器

通过AD采集数据时&#xff0c;我们总是希望采集到的数据是纯净而真实的&#xff0c;而实际上环境中存在太多的干扰信号&#xff0c;为了让我们得到的数据尽可能地接近实际值&#xff0c;我们需要降低这些干扰信号的影响。所以软件实现的数字滤波器应运而生&#xff0c;这一篇我…

外设驱动库开发笔记34:OLED显示屏驱动

现在OLED显示屏在嵌入式系统中应用的越来越多。对于一些显示信息不太复杂&#xff0c;以显示信息为主的需求&#xff0c;我们一般会选择OLED显示屏。在这一篇中&#xff0c;我们将讨论OLED显示屏驱动的设计与实现。 1、功能概述 从使用的情况来说&#xff0c;较为常用的是0.96…

外设驱动库开发笔记35:迪文触摸屏驱动

有些时候嵌入式系统也需要显示更为复杂的图形&#xff0c;需要更丰富的数据展示。为此&#xff0c;我们需要更大&#xff0c;色彩更丰富&#xff0c;带触屏的显示屏&#xff0c;当然性价比更高就最好了。在我们的项目中遇到此类需求&#xff0c;我们有时会选择DWIN触摸屏。在本…

快速实现一个室内空气质量检测仪

冬天我们大多会关闭门窗&#xff0c;而依靠暖通空调设备来维持室内温度。而在保证居室温度的同时&#xff0c;我们也希望保持居室内大气环境的健康度。鉴于此&#xff0c;我们设计了一个简单的室内空气质量检测器。 1、系统概述 我们依靠暖通空调设备来维持室内温度、湿度和通…

外设驱动库开发笔记36:NTC负温度系数热电阻测温驱动

在嵌入式产品中&#xff0c;温度检测非常常见。在成本比较敏感而精度要求较低时&#xff0c;NTC电阻是个不错的选择。在这一篇中&#xff0c;我们将讨论如何和设计并实现一个通用的NTC驱动&#xff0c;以便在后续的项目中更方便的复用。 1、功能概述 NTC是指随温度上升电阻呈指…

外设驱动库开发笔记37:S1336-5BQ光敏二极管作为光度计驱动

光敏二极管能够实现很多应用&#xff0c;用于光度检测即是其一。我们在一些产品中就曾使用S1336-5BQ光敏二极管进行光度值检测。所以在本篇中&#xff0c;我们将讨论如何设计并实现S1336-5BQ光敏二极管用于光度检测的驱动。 1、功能概述 根据相关的资料&#xff0c;光电二极管…