zoukankan      html  css  js  c++  java
  • STM32L476应用开发之八:便携式气体分析仪项目总结

    在本次项目中,我们实现的实际上是2套设备:便携式氧气分析仪以及便携式甲烷分析仪。但这两台仪器实际使用的主控板我们是设计了一套,所以主控板是适合于这两个设备的。

    1、硬件设计

    便携式气体分析仪的功能比较专一,主要涉及数据采集,输出控制、数据交互与显示、数据持久化等,在完成测试的过程中我们的设计也就基本形成了。

    1)模拟量电路

    对于模拟量采集前面已经描述过,这次我们需要精度较高的采集有2路,其他的采用单片机自带的ADC就可以了,所以在这里我们只考虑AD7705的电路设计。具体如下:

    2)开关量控制电路

    开关量主要用于气路中的电磁阀和气泵的控制,采用继电器输出控制,可以选择输出干触点或者24VDC的湿触点。具体电路如下图:

     

    3)串口通讯电路

    串行通讯前面已经有所描述。串行通讯一个采用RS232接口,另一个直接采用TTL方式。采用RS232通讯的电路如下:

     

    使用TTL通讯的电路如下:

     

    4PWM输出电路

    PWM是用来控制气路采样气体的流量的,比例调节阀采用24V电源,所以我们采用L6207D驱动电路来实现,如下图。

     

    5SD卡读写电路

    为了设计简便和安装方便,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公司和电子发烧友网站提供的试用服务。对我们项目的进展有非常大的帮助。

  • 相关阅读:
    架构笔记七
    架构笔记六
    架构笔记五
    架构笔记四
    python2与python3的区别
    萌新VRTK学习(四)攀爬系统
    萌新VRTK学习(三)物体的抓取
    萌新VRTK学习(二)移动
    萌新VRTK学习(一)VRTK的配置
    C#委托事件随笔
  • 原文地址:https://www.cnblogs.com/foxclever/p/7751094.html
Copyright © 2011-2022 走看看