在本次项目中,我们实现的实际上是2套设备:便携式氧气分析仪以及便携式甲烷分析仪。但这两台仪器实际使用的主控板我们是设计了一套,所以主控板是适合于这两个设备的。
1、硬件设计
便携式气体分析仪的功能比较专一,主要涉及数据采集,输出控制、数据交互与显示、数据持久化等,在完成测试的过程中我们的设计也就基本形成了。
(1)模拟量电路
对于模拟量采集前面已经描述过,这次我们需要精度较高的采集有2路,其他的采用单片机自带的ADC就可以了,所以在这里我们只考虑AD7705的电路设计。具体如下:
(2)开关量控制电路
开关量主要用于气路中的电磁阀和气泵的控制,采用继电器输出控制,可以选择输出干触点或者24VDC的湿触点。具体电路如下图:
(3)串口通讯电路
串行通讯前面已经有所描述。串行通讯一个采用RS232接口,另一个直接采用TTL方式。采用RS232通讯的电路如下:
使用TTL通讯的电路如下:
(4)PWM输出电路
PWM是用来控制气路采样气体的流量的,比例调节阀采用24V电源,所以我们采用L6207D驱动电路来实现,如下图。
(5)SD卡读写电路
为了设计简便和安装方便,SD卡读卡器我们选择独立的小板。该读卡器与主控板之间的接口采用10Pin的DC3简易牛角座,所以设计连接图如下:
2、软件设计
硬件确定后,根据功能需求编写为软件就是一件顺理成章的事情了。软件我们也是按不同的功能模块来设计,在这里我们主要说一说如下几个方面:
(1)模拟量采集操作
模拟量的采集相对简单,就是通过SPI总线操作ADC采样并获取相应的值。硬件配置等再次不用多说,我们主要看一看ADC的操作以及数据获取与转化,并对采集到的数据做滤波处理,滤波的目的是在数据稳定时,避免数据小数点后的微小变化。
1 /*获取采集的物理量值,并作平滑处理*/ 2 void GetMeasuredValue(void) 3 { 4 float currentValue[2]={-1.0,-1.0}; 5 CalcMeasuredValue(currentValue); 6 if(smoothIndex>=SmoothCount) 7 { 8 smoothIndex=0; 9 } 10 11 aPara.phyPara.o2Concentration=SmoothingFilter(currentValue[0],AD1Value,smoothIndex,SmoothCount,(O2RANGE-O2ZERO),2.0,0.2); 12 aPara.phyPara.h2Concentration=SmoothingFilter(currentValue[1],AD2Value,smoothIndex,SmoothCount,(H2RANGE-H2ZERO),2.0,0.2); 13 14 smoothIndex++; 15 } 16 17 /*计算测量值,将AD转换的值转为物理量的对应值*/ 18 static void CalcMeasuredValue(float *newValue) 19 { 20 uint16_t measuredValue=0; 21 22 /*转化通道1的值*/ 23 ADDA_AD7705_ENABLE();//使能器件 24 Delayus(200); 25 measuredValue=GetAD7705ChannelValue(Channel1,SPIReadWriteByte,CheckDataIsReady); 26 ADDA_AD7705_DISABLE();//片选取消 27 newValue[0]=PowerNPolyfit(((float)(measuredValue-AD1Zero)/(float)(AD1Scale-AD1Zero)),ADFactor[0],3)*(O2RANGE-O2ZERO)+O2ZERO; 28 Delayms(1); 29 30 /*转化通道2的值*/ 31 ADDA_AD7705_ENABLE();//使能器件 32 Delayus(200); 33 measuredValue=GetAD7705ChannelValue(Channel2,SPIReadWriteByte,CheckDataIsReady); 34 ADDA_AD7705_DISABLE();//片选取消 35 newValue[1]=PowerNPolyfit(((float)(measuredValue-AD2Zero)/(float)(AD2Scale-AD2Zero)),ADFactor[1],3)*(H2RANGE-H2ZERO)+H2ZERO; 36 Delayms(1); 37 }
(2)开关量控制操作
开关量的操作就更为通用一点,我们定义了DI、DO的一般性操作,然后需要操作哪一个DI和DO直接调用就好了。具体的实现如下,使用了HAL库。
1 /*获取全部DI量状态输入值*/ 2 /*输入参数TargetPin *diPin为需要读取的DI通道列表*/ 3 /*输入参数BOOL *result为读取的通道值返回列表*/ 4 void GetAllDIStatusInput(TargetPin *diPin,bool *result) 5 { 6 DigitalInput DIChannel; 7 for(DIChannel=DIChannel1;DIChannel<DIChannelNum;DIChannel++) 8 { 9 result[DIChannel]=GetSingleDigitalInput(diPin[DIChannel]); 10 } 11 } 12 13 /*操作全部继电器DO通道*/ 14 /*输入参数TargetPin *doPin为要操作的DO通道列表*/ 15 /*输入参数BOOL *commands欲写给DO通道的值列表*/ 16 void OperationAllDOChannel(TargetPin *doPin,bool *commands) 17 { 18 DigitalOutput DOChannel; 19 for(DOChannel=DOChannel1;DOChannel<DOChannelNum;DOChannel++) 20 { 21 OperationSingleDigitalOutput(doPin[DOChannel],commands[DOChannel]); 22 } 23 }
(3)数据通讯操作
正如前面硬件设计中提到了,本次需要的串行通讯有2个方面,显示屏和甲烷传感器,我们先来看看如何通过串口向屏发送数据:
/*写数据变量存储器,一次最多允许写47个字,即length<=94*/ void WriteFlashDataToDwinLCD(uint16_t startAddress,uint8_t *txData,uint16_t length,SendDataForDwinType SendData) { /*命令的长度由帧头(2个字节)+数据长度(1个字节)+指令(1个字节)+起始地址(2个字节)+数据(长度为length)*/ uint16_t cmd_Length=length+6; uint8_t cmd_VAR_Write[100]; cmd_VAR_Write[0]=0x5A; cmd_VAR_Write[1]=0xA5; cmd_VAR_Write[2]=(uint8_t)(length+3); cmd_VAR_Write[3]= FC_VAR_Write; cmd_VAR_Write[4]=(uint8_t)(startAddress>>8);//起始地址 cmd_VAR_Write[5]=(uint8_t)startAddress;//起始地址 for(int dataIndex=0;dataIndex<length;dataIndex++) { cmd_VAR_Write[dataIndex+6]=txData[dataIndex]; } SendData(cmd_VAR_Write,cmd_Length); }
甲烷传感器通讯只在便携式甲烷分析仪中才会用到。它是一种收发单总线的方式,我们前面已经设计了它的通讯电路。这里我们讨论一下它的软件实现,该传感器采用了一种类式于Modbus ASCII的编码格式,所以我们按照其要求编写代码就好了。
1 /*从非分光红外气体检测模块读取浓度值*/ 2 float ReadConcentrationData(uint8_t moduleAddress,SendByteToNdirType SendByteToNdir,uint8_t * receiveDataBuffer) 3 { 4 uint8_t txData[6]; 5 txData[0]=moduleAddress; 6 txData[1]=ReadRegisterFC; 7 txData[2]=0x00;//起始地址高位 8 txData[3]=0x0A;//起始地址低位 9 txData[4]=0x00;//寄存器数量高位 10 txData[5]=0x01;//寄存器数量低位 11 12 NDIR_SendData(txData,6,SendByteToNdir); 13 // Delayms(100); //延时100毫秒等待处理响应 14 uint8_t result[2]={0xFF,0xFF}; 15 ParseReceiveData(receiveDataBuffer,result); 16 uint16_t conc=result[0]; 17 conc=(conc<<8)+result[1]; 18 return ((float)conc*0.01); /*浓度包含2位小数*/
(4)流量控制操作
流量的控制操作其实就是采集流量的实时值,与设定值一起作为输入送给PID控制器。PID控制器的具体实现我们在前面已经测试好了。对于PID控制器的输出,用于计算PWM的占空比,以此来调节阀门开度。具体实现如下:
1 void PMWControl(void) 2 { 3 uint16_t TimerPeriod = 0; 4 uint16_t PWM1Pulse = 0; 5 uint16_t PWM2Pulse = 0; 6 7 8 //PID设定值赋值,系统运行时,设定值由屏幕设定,系统不运行时,设定值为0 9 if(runningStatus==1) 10 { 11 vPID1.setpoint=stdSetVolume(FlowRateA,presProcessValue,tempProcessValue); 12 13 vPID2.setpoint=stdSetVolume(FlowRateB,presProcessValue,tempProcessValue); 14 } 15 else 16 { 17 vPID1.setpoint=0.0; 18 vPID2.setpoint=0.0; 19 } 20 21 //PID调节 22 PIDRegulation(&vPID1, flowProcessValue1); 23 PIDRegulation(&vPID2, flowProcessValue2); 24 25 dutyfactor1=vPID1.result/flowScale1; 26 dutyfactor2=vPID2.result/flowScale2; 27 //计算初始化的频率和占空比 28 TimerPeriod = PWMTimePeriod;//计算用于设置ARR寄存器的值使产生信号的频率为17.57 Khz 29 PWM1Pulse = (uint16_t) ((TimerPeriod - 1)*dutyfactor1);//计算CCR1寄存器的值在通道1和1N产生50%占空比,用于TIM1 30 TIM_SetCompare1 (TIM1,PWM1Pulse);//修改TIM1 PWM占空比 31 PWM2Pulse = (uint16_t) ((TimerPeriod - 1)*dutyfactor2);//计算CCR1寄存器的值在通道1和1N产生50%占空比,用于TIM8 32 TIM_SetCompare1 (TIM8,PWM2Pulse);//修改TIM8 PWM占空比 33 }
对于PID控制器前面已经有详细的叙述再次不多说了,该PID控制器的输出既有物理量值也有百分比,可根据需要选择。
(5)数据存取操作
数据存储到SD卡的操作,我们已经在前面封装过SD卡的操作命令,所以在这里我们只需要按照SD卡的数据读写过程要求调用相关命令就能完成,据提的实现代码如下:
1 //向SD卡中写数据 2 uint8_t SDCardFileOperation(void) 3 { 4 uint8_t busy; 5 int8_t status=0x00; 6 //读取SD卡的状态 7 uint8_t num=0; 8 status=GetSDCardStatus();//获取SD卡的状态 9 if((status & 0xC0)!=0x00)//如果命令执行不成功则返回 10 { 11 sderror=status; 12 return 1; 13 } 14 Delayms(50);//延时50ms以便进入下一步操作 15 16 //创建文件 17 busy=GPIO_ReadInputDataBit(GPIOD, GPIO_Pin_7); 18 num=0; 19 while(busy&&(num<RepeatCount))//系统忙则等待 20 { 21 Delayms(10);//延时10ms等待系统空闲 22 num++; 23 busy=GPIO_ReadInputDataBit(GPIOD, GPIO_Pin_7); 24 } 25 GenerateFileName();//生成文件名 26 status=CreateFile(fileName);//创建文件 27 if((status & 0xEF)!=0x00)//创建文件故障,退出 28 { 29 if((status & 0x04)==0x04)//文件处于打开状态 30 { 31 CloseFile(); 32 } 33 else 34 { 35 sderror=status; 36 return 2; 37 } 38 } 39 Delayms(50);//延时50ms以便进入下一步操作 40 41 //打开文件 42 busy=GPIO_ReadInputDataBit(GPIOD, GPIO_Pin_7); 43 num=0; 44 while(busy&&(num<RepeatCount))//系统忙则等待 45 { 46 Delayms(10);//延时10ms等待系统空闲 47 num++; 48 busy=GPIO_ReadInputDataBit(GPIOD, GPIO_Pin_7); 49 } 50 status=OpenFile(fileName);//打开文件 51 52 if((status & 0x80)!=0x00) 53 { 54 sderror=status; 55 return 4; 56 } 57 Delayms(50);//延时50ms以便进入下一步操作 58 59 //获取文件信息 60 busy=GPIO_ReadInputDataBit(GPIOD, GPIO_Pin_7); 61 num=0; 62 while(busy&&(num<RepeatCount))//系统忙则等待 63 { 64 Delayms(10);//延时10ms等待系统空闲 65 num++; 66 busy=GPIO_ReadInputDataBit(GPIOD, GPIO_Pin_7); 67 } 68 uint8_t rxData[9]; 69 GetFileStatus(rxData);//获取文件信息 70 status=rxData[0]; 71 if((status & 0x80)!=0x00) 72 { 73 sderror=status; 74 return 8; 75 } 76 Delayms(50);//延时50ms以便进入下一步操作 77 78 //写文件 79 busy=GPIO_ReadInputDataBit(GPIOD, GPIO_Pin_7); 80 num=0; 81 while(busy&&(num<RepeatCount))//系统忙则等待 82 { 83 Delayms(10);//延时10ms等待系统空闲 84 num++; 85 busy=GPIO_ReadInputDataBit(GPIOD, GPIO_Pin_7); 86 } 87 88 uint8_t address[4]; 89 address[0]=rxData[1]; 90 address[1]=rxData[2]; 91 address[2]=rxData[3]; 92 address[3]=rxData[4]; 93 uint8_t result[120]; 94 uint8_t datalength=DataProcess(saveData,result);//格式化将要写入的数据 95 status=WriteToFile(address,result,datalength); //写文件 96 if((status & 0xFF)!=0x00) 97 { 98 sderror=status; 99 return 16; 100 } 101 Delayms(50);//延时50ms以便进入下一步操作 102 103 //保存文件 104 busy=GPIO_ReadInputDataBit(GPIOD, GPIO_Pin_7); 105 num=0; 106 while(busy&&(num<RepeatCount))//系统忙则等待 107 { 108 Delayms(10);//延时10ms等待系统空闲 109 num++; 110 busy=GPIO_ReadInputDataBit(GPIOD, GPIO_Pin_7); 111 } 112 status=SaveFile();//保存文件 113 114 if((status & 0xFF)!=0x00) 115 { 116 sderror=status; 117 return 32; 118 } 119 Delayms(50);//延时50ms以便进入下一步操作 120 121 //关闭文件 122 busy=GPIO_ReadInputDataBit(GPIOD, GPIO_Pin_7); 123 num=0; 124 while(busy&&(num<RepeatCount))//系统忙则等待 125 { 126 Delayms(10);//延时10ms等待系统空闲 127 num++; 128 busy=GPIO_ReadInputDataBit(GPIOD, GPIO_Pin_7); 129 } 130 status=CloseFile(); 131 if((status & 0xFF)!=0x00) 132 { 133 sderror=status; 134 return 64; 135 } 136 Delayms(50);//延时50ms以便进入下一步操作 137 return 0; 138 }
3、测试结果
本次项目我们实际上是2个设备,其一是便携式氧气分析仪,其二是便携式甲烷分析仪。不过他们仅是内部传感器不一样,外观和主控板是一样的。完成后的外观如下:
运行界面如下:
最后再次感谢ST公司和电子发烧友网站提供的试用服务。对我们项目的进展有非常大的帮助。