2、软件设计
硬件确定后,根据功能需求编写为软件就是一件顺理成章的事情了。软件我们也是按不同的功能模块来设计,在这里我们主要说一说如下几个方面:
(1)模拟量采集操作
模拟量的采集相对简单,就是通过SPI总线操作ADC采样并获取相应的值。硬件配置等再次不用多说,我们主要看一看ADC的操作以及数据获取与转化,并对采集到的数据做滤波处理,滤波的目的是在数据稳定时,避免数据小数点后的微小变化。
/*获取采集的物理量值,并作平滑处理*/
voidGetMeasuredValue(void)
{
float currentValue[2]={-1.0,-1.0};
CalcMeasuredValue(currentValue);
if(smoothIndex>=SmoothCount)
{
smoothIndex=0;
}
aPara.phyPara.o2Concentra
tion=SmoothingFilter(currentValue[0],AD1Value,smoothIndex,SmoothCount,(O2RANGE-O2ZERO),2.0,0.2);
aPara.phyPara.h2Concentration=SmoothingFilter(currentValue[1],AD2Value,smoothIndex,SmoothCount,(H2RANGE-H2ZERO),2.0,0.2);
smoothIndex++;
}
/*计算测量值,将AD转换的值转为物理量的对应值*/
static voidCalcMeasuredValue(float *newValue)
{
uint16_t measuredValue=0;
/*转化通道1的值*/
ADDA_AD7705_ENABLE();//使能器件
Delayus(200);
measuredValue=GetAD7705ChannelValue(Channel1,SPIReadWriteByte,CheckDataIsReady);
ADDA_AD7705_DISABLE();//片选取消
newValue[0]=PowerNPolyfit(((float)(measuredValue-AD1Zero)/(float)(AD1Scale-AD1Zero)),ADFactor[0],3)*(O2RANGE-O2ZERO)+O2ZERO;
Delayms(1);
/*转化通道2的值*/
ADDA_AD7705_ENABLE();//使能器件
Delayus(200);
measuredValue=GetAD7705ChannelValue(Channel2,SPIReadWriteByte,CheckDataIsReady);
ADDA_AD7705_DISABLE();//片选取消
newValue[1]=PowerNPolyfit(((float)(measuredValue-AD2Zero)/(float)(AD2Scale-AD2Zero)),ADFactor[1],3)*(H2RANGE-H2ZERO)+H2ZERO;
Delayms(1);
}
(2)开关量控制操作
开关量的操作就更为通用一点,我们定义了DI、DO的一般性操作,然后需要操作哪一个DI和DO直接调用就好了。具体的实现如下,使用了HAL库。
/*获取全部DI量状态输入值*/
/*输入参数TargetPin*diPin为需要读取的DI通道列表*/
/*输入参数BOOL *result为读取的通道值返回列表*/
void GetAllDIStatusInput(TargetPin *diPin,bool *result)
{
DigitalInput DIChannel;
for(DIChannel=DIChannel1;DIChannel
{
result[DIChannel]=GetSingleDigitalInput(diPin[DIChannel]);
}
}
/*操作全部继电器DO通道*/
/*输入参数TargetPin*doPin为要操作的DO通道列表*/
/*输入参数BOOL *commands欲写给DO通道的值列表*/
void OperationAllDOChannel(TargetPin *doPin,bool *commands)
{
DigitalOutput DOChannel;
for(DOChannel=DOChannel1;DOChannel
{
OperationSingleDigitalOutput(doPin[DOChannel],commands[DOChannel]);
}
}
(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(intdataIndex=0;dataIndex
{
cmd_VAR_Write[dataIndex+6]=txData[dataIndex];
}
SendData(cmd_VAR_Write,cmd_Length);
}
甲烷传感器通讯只在便携式甲烷分析仪中才会用到。它是一种收发单总线的方式,我们前面已经设计了它的通讯电路。这里我们讨论一下它的软件实现,该传感器采用了一种类式于Modbus ASCII的编码格式,所以我们按照其要求编写代码就好了。
/*从非分光红外气体检测模块读取浓度值*/
float ReadConcentrationData(uint8_t moduleAddress,SendByteToNdirTypeSendByteToNdir,uint8_t * receiveDataBuffer)
{
uint8_t txData[6];
txData[0]=moduleAddress;
txData[1]=ReadRegisterFC;
txData[2]=0x00;//起始地址高位
txData[3]=0x0A;//起始地址低位
txData[4]=0x00;//寄存器数量高位
txData[5]=0x01;//寄存器数量低位
NDIR_SendData(txData,6,SendByteToNdir);
// Delayms(100); //延时100毫秒等待处理响应
uint8_t result[2]={0xFF,0xFF};
ParseReceiveData(receiveDataBuffer,result);
uint16_t conc=result[0];
conc=(conc<<8)+result[1];
return ((float)conc*0.01); /*浓度包含2位小数*/
}
(4)流量控制操作
流量的控制操作其实就是采集流量的实时值,与设定值一起作为输入送给PID控制器。PID控制器的具体实现我们在前面已经测试好了。对于PID控制器的输出,用于计算PWM的占空比,以此来调节阀门开度。具体实现如下:
void PMWControl(void)
{
uint16_t TimerPeriod = 0;
uint16_t PWM1Pulse = 0;
uint16_t PWM2Pulse = 0;
//PID设定值赋值,系统运行时,设定值由屏幕设定,系统不运行时,设定值为0
if(runningStatus==1)
{
vPID1.setpoint=stdSetVolume(FlowRateA,presProcessValue,tempProcessValue);
vPID2.setpoint=stdSetVolume(FlowRateB,presProcessValue,tempProcessValue);
}
else
{
vPID1.setpoint=0.0;
vPID2.setpoint=0.0;
}
//PID调节
PIDRegulation(&vPID1,flowProcessValue1);
PIDRegulation(&vPID2,flowProcessValue2);
dutyfactor1=vPID1.result/flowScale1;
dutyfactor2=vPID2.result/flowScale2;
//计算初始化的频率和占空比
TimerPeriod = PWMTimePeriod;//计算用于设置ARR寄存器的值使产生信号的频率为17.57 Khz
PWM1Pulse = (uint16_t)((TimerPeriod - 1)*dutyfactor1);//计算CCR1寄存器的值在通道1和1N产生50%占空比,用于TIM1
TIM_SetCompare1(TIM1,PWM1Pulse);//修改TIM1 PWM占空比
PWM2Pulse = (uint16_t)((TimerPeriod - 1)*dutyfactor2);//计算CCR1寄存器的值在通道1和1N产生50%占空比,用于TIM8
TIM_SetCompare1(TIM8,PWM2Pulse);//修改TIM8 PWM占空比
}
对于PID控制器前面已经有详细的叙述再次不多说了,该PID控制器的输出既有物理量值也有百分比,可根据需要选择。
(5)数据存取操作
数据存储到SD卡的操作,我们已经在前面封装过SD卡的操作命令,所以在这里我们只需要按照SD卡的数据读写过程要求调用相关命令就能完成,据提的实现代码如下:
//向SD卡中写数据
uint8_t SDCardFileOperation(void)
{
uint8_t busy;
int8_t status=0x00;
//读取SD卡的状态
uint8_t num=0;
status=GetSDCardStatus();//获取SD卡的状态
if((status & 0xC0)!=0x00)//如果命令执行不成功则返回
{
sderror=status;
return 1;
}
Delayms(50);//延时50ms以便进入下一步操作
//创建文件
busy=GPIO_ReadInputDataBit(GPIOD,GPIO_Pin_7);
num=0;
while(busy&&(num系统忙则等待
{
Delayms(10);//延时10ms等待系统空闲
num++;
busy=GPIO_ReadInputDataBit(GPIOD,GPIO_Pin_7);
}
GenerateFileName();//生成文件名
status=CreateFile(fileName);//创建文件
if((status & 0xEF)!=0x00)//创建文件故障,退出
{
if((status & 0x04)==0x04)//文件处于打开状态
{
CloseFile();
}
else
{
sderror=status;
return 2;
}
}
Delayms(50);//延时50ms以便进入下一步操作
//打开文件
busy=GPIO_ReadInputDataBit(GPIOD,GPIO_Pin_7);
num=0;
while(busy&&(num系统忙则等待
{
Delayms(10);//延时10ms等待系统空闲
num++;
busy=GPIO_ReadInputDataBit(GPIOD, GPIO_Pin_7);
}
status=OpenFile(fileName);//打开文件
if((status & 0x80)!=0x00)
{
sderror=status;
return 4;
}
Delayms(50);//延时50ms以便进入下一步操作
//获取文件信息
busy=GPIO_ReadInputDataBit(GPIOD,GPIO_Pin_7);
num=0;
while(busy&&(num系统忙则等待
{
Delayms(10);//延时10ms等待系统空闲
num++;
busy=GPIO_ReadInputDataBit(GPIOD, GPIO_Pin_7);
}
uint8_t rxData[9];
GetFileStatus(rxData);//获取文件信息
status=rxData[0];
if((status & 0x80)!=0x00)
{
sderror=status;
return 8;
}
Delayms(50);//延时50ms以便进入下一步操作
//写文件
busy=GPIO_ReadInputDataBit(GPIOD,GPIO_Pin_7);
num=0;
while(busy&&(num系统忙则等待
{
Delayms(10);//延时10ms等待系统空闲
num++;
busy=GPIO_ReadInputDataBit(GPIOD, GPIO_Pin_7);
}
uint8_t address[4];
address[0]=rxData[1];
address[1]=rxData[2];
address[2]=rxData[3];
address[3]=rxData[4];
uint8_t result[120];
uint8_tdatalength=DataProcess(saveData,result);//格式化将要写入的数据
status=WriteToFile(address,result,datalength); //写文件
if((status & 0xFF)!=0x00)
{
sderror=status;
return 16;
}
Delayms(50);//延时50ms以便进入下一步操作
//保存文件
busy=GPIO_ReadInputDataBit(GPIOD,GPIO_Pin_7);
num=0;
while(busy&&(num系统忙则等待
{
Delayms(10);//延时10ms等待系统空闲
num++;
busy=GPIO_ReadInputDataBit(GPIOD, GPIO_Pin_7);
}
status=SaveFile();//保存文件
if((status & 0xFF)!=0x00)
{
sderror=status;
return 32;
}
Delayms(50);//延时50ms以便进入下一步操作
//关闭文件
busy=GPIO_ReadInputDataBit(GPIOD,GPIO_Pin_7);
num=0;
while(busy&&(num系统忙则等待
{
Delayms(10);//延时10ms等待系统空闲
num++;
busy=GPIO_ReadInputDataBit(GPIOD, GPIO_Pin_7);
}
status=CloseFile();
if((status & 0xFF)!=0x00)
{
sderror=status;
return 64;
}
Delayms(50);//延时50ms以便进入下一步操作
return 0;
}
3、测试结果
本次项目我们实际上是2个设备,其一是便携式氧气分析仪,其二是便携式甲烷分析仪。不过他们仅是内部传感器不一样,外观和主控板是一样的。完成后的外观如下:
运行界面如下:
最后再次感谢ST公司和
电子发烧友网站提供的试用服务。对我们项目的进展有非常大的帮助。