近期,我们开发了一款用于检定各种酒精测试仪的产品,该产品是一种检测设备,用于对警用或者民用就见检测仪进行检定。
1、项目概述
本项目是一套用于酒精检测仪的检定的设备,用于测试酒精检测仪器精确性的检定。如可用于检定呼吸式警用酒精检测仪等。系统要实现温湿度检测、输入输出压力检测、温度控制及高精度温度检测等,因为在测试过程中温度和湿度会对检定的精确性有重要影响。进气分为6路,除了空气和酒精气体外,还有CO和丙酮等干扰性气体,有一套复炸的气体配置方式。同时又有4路气体加热腔室对测试气体进行加热并进行精确的温度控制。另外对气体的流量要进行控制并能实现数据远传和操作。
2、硬件设计
根据项目需求,在气体流量控制方面选优MFC(气体质量流量控制器)通过ADC和DAC实现控制。ADC和DAC都使用片上自带的,采用运算放大器进行隔离和放大使其输入输出0-5VDC信号。
温度检测通过LTC2400实现热电阻高精度温度采集。由于对温度非常敏感所以这部分要求较高,使用了24位的ADC实现高进度采集,通过SPI总线获取。
试验气体的配置采用6个六个电磁阀控制配置不同成分的测试气体。没路输出根据需要可输出干触点或者24VDC湿触点。
温度控制器采用宇电AI-7804同时实现4路温度的检测与控制。采用RS-485的通讯方式和PWM输出控制加热功率。
上位通讯采用两种方式,以太网远传和串口触摸屏控制。以太网采用W5500和SPI接口方式实现:
而串口触摸屏的通讯采用RS485或者RS232方式,本次采用RS485的通讯方式,使用MAX3485芯片实现。
压力检测采用MS5803采集压力的变化,采用I2C通讯。湿度检测使用SHT15实现,采用类式I2C通讯方式,通过GPIO模拟。
3、软件设计
软件的设计采用IAREWARMV7.8和ST标准库V3.5实现。具体的软件设计包括上位远传通讯、模拟量输入输出控制、逻辑控制、热电阻温度采集、温湿度和压力数据采集以及加热和温度控制。
为了让软件更好地适应更换传感器和应用不同场合的功能增减要求,我们在设计软件时使用了一个配置文件来配置更能的使用和增减。这个配置文件就是一个头文件,定义了一些宏来控制条件编译,节选部分配置文件如下:
复制代码
/*定义以太网通讯功能的使能,1:启用;0:禁用*/
#ifndefEthernet_ENABLE
#defineEthernet_ENABLE(1)
#endif
/*定义串口上位通讯使能,1:启用;0:禁用*/
#ifndefUPPER_SERIAL_ENABLE
#defineUPPER_SERIAL_ENABLE(0)
#endif
/*定义片上Flash存取使能,1:启用;0:禁用*/
#ifndefSTORAGE_ENABLE
#defineSTORAGE_ENABLE(0)
#endif
/*定义模拟量输入是否启用,1:启用;0:禁用*/
#ifndefANALOG_INPUT_ENABLE
#defineANALOG_INPUT_ENABLE(1)
#endif
/*定义模拟量输出是否启用,1:启用;0:禁用*/
#ifndefANALOG_OUTPUT_ENABLE
#defineANALOG_OUTPUT_ENABLE(1)
#endif
/*定义数字量操作是否启用,1:启用;0:禁用*/
#ifndefDIGITAL_ENABLE
#defineDIGITAL_ENABLE(1)
#endif
/*定义温湿度计是否启用,1:启用;0:禁用*/
#ifndefHYGRO_THERMO_ENABLE
#defineHYGRO_THERMO_ENABLE(1)
#endif
/*压力变送器是否启用,1:启用;0:禁用*/
#ifndefPRESS_TRANS_ENABLE
#definePRESS_TRANS_ENABLE(1)
#endif
/*热电阻采集电路是否启用,1:启用;0:禁用*/
#ifndefRTD_COLLECT_ENABLE
#defineRTD_COLLECT_ENABLE(0)
#endif
/*备用串行设备是否启用,1:启用;0:禁用*/
#ifndefSERIAL_SPARE_ENABLE
#defineSERIAL_SPARE_ENABLE(0)
#endif
/*温控器是否启用,1:启用;0:禁用*/
#ifndefTHERMOSTAT_ENABLE
#defineTHERMOSTAT_ENABL(0)
#endif
/*定义串口上位通讯方式的选择,1:RS232;0:RS485*/
#ifndefUPUSART_COMM_TYPE
#defineUPUSART_COMM_TYPE(0)
#endif
/*定义压力变送器MS4515DO的通讯方式的选择,1:SPI;0:I2C*/
#ifndefMS4515DO_COMM_TYPE
#defineMS4515DO_COMM_TYPE(0)
#endif
/*定义启用的压力变送器类型,1:MS5837;0:MS5803*/
#ifndefPRESSURE_TRANSMITTER_TYPE
#definePRESSURE_TRANSMITTER_TYPE(0)
#endif
/*定义MS5803(MS5837)采集值是否启用滤波功能,1:启用;0:禁用*/
#ifndefMS5803_FILTER_ENABLE
#defineMS5803_FILTER_ENABLE(1)
#endif
复制代码
上位远传通讯包括有以太网通讯和串口通讯,以太网通讯用于连接远程计算机,出口通讯用于连接触摸屏。均使用Modbus协议,支持03、06、16等功能码。
复制代码
//解析接收到的数据
uint16_tReceivedDataParsing(uint8_t*rxBuffer,uint8_t*txBuffer)
{
uint16_tlength=0;
uint8_tfuctionCode=rxBuffer[7];
switch(fuctionCode)
{
caseREAD_HOLDING_REGISTERS:
{
uint16_tstartAddress=rxBuffer[8];
startAddress=(startAddress<<8)+(uint16_t)rxBuffer[9];
uint16_tRegisterNumber=rxBuffer[10];
RegisterNumber=(RegisterNumber<<8)+(uint16_t)rxBuffer[11];
//RegisterNumber=0;
//读取对应寄存器数值
uint8_treturnData[REGISTERAMOUNT*2+2];
GetRegisterValue(startAddress,RegisterNumber,returnData);
uint16_tbyteCount=0;
txBuffer[byteCount++]=rxBuffer[0];
txBuffer[byteCount++]=rxBuffer[1];
txBuffer[byteCount++]=rxBuffer[2];
txBuffer[byteCount++]=rxBuffer[3];
uint16_tbyteAmount=RegisterNumber*2+3;
txBuffer[byteCount++]=(byteAmount>>8);
txBuffer[byteCount++]=byteAmount;
txBuffer[byteCount++]=rxBuffer[6];
for(inti=0;i
{
txBuffer[byteCount++]=returnData[i];
}
length=byteCount;
break;
}
caseWRITE_SINGLE_REGISTER:
{
//txBuffer=rxBuffer;//将指针赋值,不能改变数组的值
uint16_tByteAmount=rxBuffer[4];
ByteAmount=(ByteAmount<<8)+(uint16_t)rxBuffer[5];
length=ByteAmount+6;
uint16_tobjectRegister=0;
objectRegister=rxBuffer[8];
objectRegister=(objectRegister<<8)+(uint16_t)rxBuffer[9];
uint16_tsetValue=rxBuffer[10];
setValue=(setValue<<8)+(uint16_t)rxBuffer[11];
//将设定值写到对应的寄存器
SetRegisterValue(objectRegister,setValue);
for(inti=0;i
{
txBuffer[i]=rxBuffer[i];
}
break;
}
caseWRITE_MULTI_REGISTER:
{
length=12;
SetMultiRegisterValue(rxBuffer+8);
for(inti=0;i
{
txBuffer[i]=rxBuffer[i];
}
txBuffer[5]=0x06;
break;
}
default:
{
break;
}
}
returnlength;
}
复制代码
模拟量输入输出控制的控制较为简单,直接使用DMA操作方式,实现简单方便,不用详述。数字量的控制方式以及温度的SPI操作与前面文章中的一致。
温湿度采集使用的SHT15模块,它的通讯协议类是于I2C方式,但是用STM32自带的I2C来操作却不方便,所以使用GPIO来实现通讯。为了方便在不同的系统使用所以对时钟和数据引脚的操作采用的回掉函数来实现。
先编写SHT15(实际上SHT1X均可用)的操作函数,并在此时用弱化的操作函数:
复制代码
/*读取DATA引脚位,弱化的函数,无操作,必须在应用中实现该函数,并自动调用*/
__weakuint8_tReadDataPinBit(void)
{
return0;
}
/*将DATA线设置为输入输出方向模式*/
__weakvoidSetDataPineDirection(IODirectiondirection)
{
}
复制代码
事实上是,什么也不做,然后在最终的应用中来实现这两个函数,以实现相应的操作,对于不同的系统只需在应用中实现不同操作。
复制代码
/*读取DATA引脚位*/
uint8_tReadDataPinBit(void)
{
returnGPIO_ReadInputDataBit(GPIOB,GPIO_Pin_7);
}
/*将DATA线设置为输入输出方向模式*/
voidSetDataPineDirection(IODirectiondirection)
{
GPIO_InitTypeDefGPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_7;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
if(direction)
{
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;
}
else
{
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IN_FLOATING;
}
GPIO_Init(GPIOB,&GPIO_InitStructure);
}
复制代码
同时使用函数执政数组的方式来简化SCK和DATA引脚的操作函数的编写:
复制代码
/*定义操作GPIO管脚的函数指针*/
void(*OperationSHT1xIO[])(GPIO_TypeDef*GPIOx,uint16_tGPIO_Pin)={GPIO_ResetBits,GPIO_SetBits};
/*定义SHT1X总线引脚操作函数指针*/
BusPinOperationSetBusPin[]={OperationSckPin,OperationDataPin};
/*操作SCK引脚,设置高低操作*/
voidOperationSckPin(BusPinValuevalue)
{
OperationSHT1xIO[value](GPIOB,GPIO_Pin_6);
}
/*操作DATA引脚,设置高低操作*/
voidOperationDataPin(BusPinValuevalue)
{
OperationSHT1xIO[value](GPIOB,GPIO_Pin_7);
}
同时定义几个枚举类型:
typedefenum{
Set=1,
Reset=(!Set)
}BusPinValue;
typedefenum{
Out=1,
In=(!Out)
}IODirection;
typedefenum{
SckPin=0,
DataPin=1
}SHT1XPin;
复制代码
这样要操作那个引脚就非常方便了:
复制代码
/*将data线设置为输出模式*/
SetDataPineDirection(Out);
/*将DATA引脚置位*/
SetBusPin[DataPin](Set);
复制代码
压力数据采集的采用I2C总线,对STM32的I2C通讯网上文章很多此处不再重复,需要提一下的是整个操作也是适用函数指针来实现毁掉的方式来简化编程过程。另外STM32的I2C好像比较容易出现死锁的情况,不知为何,有待解惑。
加热和温度控制采用RS485通讯,使用宇电的AI-BUS协议,这个比较简单,不再多说。
4、结果验证
经过差不多2周的调试,现在设备已经成型,效果还是比较理想的,首先来一张调试图片:
这是现实的DAC输出电压:
这是数据远传以Modscan读取数据的结果:
来一张组装后的图片:
最后来一组测试是的上位操作及显示画面:
最后说一下这次的一点体会,以往程序调试没问题就算好了,由于这个产品对时间非常敏感,有些测试过程必须在很短的时间(以毫秒为单位)完成。所以这次我再软解的整体结构上下了一些功夫,让速度提升了好几倍,所以我觉得规划好程序结构也是一很重要的方面。