在本次项目中,我们实现的实际上是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的操作以及数据获取与转化,并对采集到的数据做滤波处理,滤波的目的是在数据稳定时,避免数据小数点后的微小变化。

 /*获取采集的物理量值,并作平滑处理*/
void GetMeasuredValue(void)
{
float currentValue[]={-1.0,-1.0};
CalcMeasuredValue(currentValue);
if(smoothIndex>=SmoothCount)
{
smoothIndex=;
} aPara.phyPara.o2Concentration=SmoothingFilter(currentValue[],AD1Value,smoothIndex,SmoothCount,(O2RANGE-O2ZERO),2.0,0.2);
aPara.phyPara.h2Concentration=SmoothingFilter(currentValue[],AD2Value,smoothIndex,SmoothCount,(H2RANGE-H2ZERO),2.0,0.2); smoothIndex++;
} /*计算测量值,将AD转换的值转为物理量的对应值*/
static void CalcMeasuredValue(float *newValue)
{
uint16_t measuredValue=; /*转化通道1的值*/
ADDA_AD7705_ENABLE();//使能器件
Delayus();
measuredValue=GetAD7705ChannelValue(Channel1,SPIReadWriteByte,CheckDataIsReady);
ADDA_AD7705_DISABLE();//片选取消
newValue[]=PowerNPolyfit(((float)(measuredValue-AD1Zero)/(float)(AD1Scale-AD1Zero)),ADFactor[],)*(O2RANGE-O2ZERO)+O2ZERO;
Delayms(); /*转化通道2的值*/
ADDA_AD7705_ENABLE();//使能器件
Delayus();
measuredValue=GetAD7705ChannelValue(Channel2,SPIReadWriteByte,CheckDataIsReady);
ADDA_AD7705_DISABLE();//片选取消
newValue[]=PowerNPolyfit(((float)(measuredValue-AD2Zero)/(float)(AD2Scale-AD2Zero)),ADFactor[],)*(H2RANGE-H2ZERO)+H2ZERO;
Delayms();
}

2)开关量控制操作

开关量的操作就更为通用一点,我们定义了DI、DO的一般性操作,然后需要操作哪一个DI和DO直接调用就好了。具体的实现如下,使用了HAL库。

 /*获取全部DI量状态输入值*/
/*输入参数TargetPin *diPin为需要读取的DI通道列表*/
/*输入参数BOOL *result为读取的通道值返回列表*/
void GetAllDIStatusInput(TargetPin *diPin,bool *result)
{
DigitalInput DIChannel;
for(DIChannel=DIChannel1;DIChannel<DIChannelNum;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<DOChannelNum;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+;
uint8_t cmd_VAR_Write[];
cmd_VAR_Write[]=0x5A;
cmd_VAR_Write[]=0xA5;
cmd_VAR_Write[]=(uint8_t)(length+);
cmd_VAR_Write[]= FC_VAR_Write;
cmd_VAR_Write[]=(uint8_t)(startAddress>>);//起始地址
cmd_VAR_Write[]=(uint8_t)startAddress;//起始地址
for(int dataIndex=;dataIndex<length;dataIndex++)
{
cmd_VAR_Write[dataIndex+]=txData[dataIndex];
} SendData(cmd_VAR_Write,cmd_Length);
}

甲烷传感器通讯只在便携式甲烷分析仪中才会用到。它是一种收发单总线的方式,我们前面已经设计了它的通讯电路。这里我们讨论一下它的软件实现,该传感器采用了一种类式于Modbus ASCII的编码格式,所以我们按照其要求编写代码就好了。

 /*从非分光红外气体检测模块读取浓度值*/
float ReadConcentrationData(uint8_t moduleAddress,SendByteToNdirType SendByteToNdir,uint8_t * receiveDataBuffer)
{
uint8_t txData[];
txData[]=moduleAddress;
txData[]=ReadRegisterFC;
txData[]=0x00;//起始地址高位
txData[]=0x0A;//起始地址低位
txData[]=0x00;//寄存器数量高位
txData[]=0x01;//寄存器数量低位 NDIR_SendData(txData,,SendByteToNdir);
// Delayms(100); //延时100毫秒等待处理响应
uint8_t result[]={0xFF,0xFF};
ParseReceiveData(receiveDataBuffer,result);
uint16_t conc=result[];
conc=(conc<<)+result[];
return ((float)conc*0.01); /*浓度包含2位小数*/

4)流量控制操作

流量的控制操作其实就是采集流量的实时值,与设定值一起作为输入送给PID控制器。PID控制器的具体实现我们在前面已经测试好了。对于PID控制器的输出,用于计算PWM的占空比,以此来调节阀门开度。具体实现如下:

 void PMWControl(void)
{
uint16_t TimerPeriod = ;
uint16_t PWM1Pulse = ;
uint16_t PWM2Pulse = ; //PID设定值赋值,系统运行时,设定值由屏幕设定,系统不运行时,设定值为0
if(runningStatus==)
{
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 - )*dutyfactor1);//计算CCR1寄存器的值在通道1和1N产生50%占空比,用于TIM1
TIM_SetCompare1 (TIM1,PWM1Pulse);//修改TIM1 PWM占空比
PWM2Pulse = (uint16_t) ((TimerPeriod - )*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=;
status=GetSDCardStatus();//获取SD卡的状态
if((status & 0xC0)!=0x00)//如果命令执行不成功则返回
{
sderror=status;
return ;
}
Delayms();//延时50ms以便进入下一步操作 //创建文件
busy=GPIO_ReadInputDataBit(GPIOD, GPIO_Pin_7);
num=;
while(busy&&(num<RepeatCount))//系统忙则等待
{
Delayms();//延时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 ;
}
}
Delayms();//延时50ms以便进入下一步操作 //打开文件
busy=GPIO_ReadInputDataBit(GPIOD, GPIO_Pin_7);
num=;
while(busy&&(num<RepeatCount))//系统忙则等待
{
Delayms();//延时10ms等待系统空闲
num++;
busy=GPIO_ReadInputDataBit(GPIOD, GPIO_Pin_7);
}
status=OpenFile(fileName);//打开文件 if((status & 0x80)!=0x00)
{
sderror=status;
return ;
}
Delayms();//延时50ms以便进入下一步操作 //获取文件信息
busy=GPIO_ReadInputDataBit(GPIOD, GPIO_Pin_7);
num=;
while(busy&&(num<RepeatCount))//系统忙则等待
{
Delayms();//延时10ms等待系统空闲
num++;
busy=GPIO_ReadInputDataBit(GPIOD, GPIO_Pin_7);
}
uint8_t rxData[];
GetFileStatus(rxData);//获取文件信息
status=rxData[];
if((status & 0x80)!=0x00)
{
sderror=status;
return ;
}
Delayms();//延时50ms以便进入下一步操作 //写文件
busy=GPIO_ReadInputDataBit(GPIOD, GPIO_Pin_7);
num=;
while(busy&&(num<RepeatCount))//系统忙则等待
{
Delayms();//延时10ms等待系统空闲
num++;
busy=GPIO_ReadInputDataBit(GPIOD, GPIO_Pin_7);
} uint8_t address[];
address[]=rxData[];
address[]=rxData[];
address[]=rxData[];
address[]=rxData[];
uint8_t result[];
uint8_t datalength=DataProcess(saveData,result);//格式化将要写入的数据
status=WriteToFile(address,result,datalength); //写文件
if((status & 0xFF)!=0x00)
{
sderror=status;
return ;
}
Delayms();//延时50ms以便进入下一步操作 //保存文件
busy=GPIO_ReadInputDataBit(GPIOD, GPIO_Pin_7);
num=;
while(busy&&(num<RepeatCount))//系统忙则等待
{
Delayms();//延时10ms等待系统空闲
num++;
busy=GPIO_ReadInputDataBit(GPIOD, GPIO_Pin_7);
}
status=SaveFile();//保存文件 if((status & 0xFF)!=0x00)
{
sderror=status;
return ;
}
Delayms();//延时50ms以便进入下一步操作 //关闭文件
busy=GPIO_ReadInputDataBit(GPIOD, GPIO_Pin_7);
num=;
while(busy&&(num<RepeatCount))//系统忙则等待
{
Delayms();//延时10ms等待系统空闲
num++;
busy=GPIO_ReadInputDataBit(GPIOD, GPIO_Pin_7);
}
status=CloseFile();
if((status & 0xFF)!=0x00)
{
sderror=status;
return ;
}
Delayms();//延时50ms以便进入下一步操作
return ;
}

3、测试结果

本次项目我们实际上是2个设备,其一是便携式氧气分析仪,其二是便携式甲烷分析仪。不过他们仅是内部传感器不一样,外观和主控板是一样的。完成后的外观如下:

运行界面如下:

最后再次感谢ST公司和电子发烧友网站提供的试用服务。对我们项目的进展有非常大的帮助。

STM32L476应用开发之八:便携式气体分析仪项目总结的更多相关文章

  1. MVC5 网站开发之八 栏目功能 添加、修改和删除

    本次实现栏目的浏览.添加.修改和删除. 栏目一共有三种类型. 常规栏目-可以添加子栏目,也可以添加内容模型.当不选择内容模型时,不能添加内容. 单页栏目-栏目只有一个页面,可以设置视图. 链接栏目-栏 ...

  2. STM32L476应用开发之五:数据保存与SD卡操作

    便携式气体分析仪的特点就是离线运行.尽管是离线运行,但测试数据还是需要的,所以采取方式保存数据就是必须的.在本次项目中我们计划采用SD卡来保存数据. 1.硬件设计 该读卡器整合 SD 卡规范和 FAT ...

  3. STM32L476应用开发之一:初次使用

    今天终于收到了期待已久的NUCLEO-F412ZG,感谢电子发烧友论坛!多年以来基本都是在STM32平台上做一些设计开发工作.但是低功耗的基本没用过,这次要做便携式设备才对这方面有所接触,正好这时电子 ...

  4. MVC5 网站开发之二 创建项目

    昨天对项目的思路大致理了一下,今天先把解决方案建立起来.整个解决包含Ninesky.Web.Ninesky.Core,Ninesky.DataLibrary等3个项目.Ninesky.Web是web应 ...

  5. ASP.NET 5 入门(1) - 建立和开发ASP.NET 5 项目

    ASP.NET入门(1) - 建立和开发ASP.NET 5 项目 ASP.NET 5 理解和入门 使用自定义配置文件 建立项目 首先,目前只有VS 2015支持开发最新的ASP.NET 5 程序,所以 ...

  6. 实战使用Axure设计App,使用WebStorm开发(2) – 创建 Ionic 项目

    系列文章 实战使用Axure设计App,使用WebStorm开发(1) – 用Axure描述需求  实战使用Axure设计App,使用WebStorm开发(2) – 创建 Ionic 项目   实战使 ...

  7. 在iOS开发中,给项目添加新的.framework

    首先需要了解一下iOS中静态库和动态库.framework的概念 静态库与动态库的区别 首先来看什么是库,库(Library)说白了就是一段编译好的二进制代码,加上头文件就可以供别人使用. 什么时候我 ...

  8. XAF应用开发教程(一) 创建项目

    XAF是DevExpress公司的快速开发框架,全称eXpress Application Framework,是企业信息系统的开发利器,快速开发效果显著,在.net框架中,笔者至今没有找到一款可以与 ...

  9. 一年开发ASP.NET MVC4项目经验总结

    一年开发ASP.NET MVC4项目所用所学技术经验总结 阅读目录 文章背景 前端所用技术摘要 后端所用技术摘要 1. 文章背景 本人2014年从Java转行到C#从事BS项目的开发,刚开始接触的是A ...

随机推荐

  1. SQLMap用户手册【超详细】

    http://192.168.136.131/sqlmap/mysql/get_int.php?id=1 当给sqlmap这么一个url的时候,它会: 1.判断可注入的参数 2.判断可以用那种SQL注 ...

  2. Coursera, Deep Learning 4, Convolutional Neural Networks, week3, Object detection

    学习目标 Understand the challenges of Object Localization, Object Detection and Landmark Finding Underst ...

  3. Unsupervised Domain Adaptation Via Domain Adversarial Training For Speaker Recognition

    年域适应挑战(DAC)数据集的实验表明,所提出的方法不仅有效解决了数据集不匹配问题,而且还优于上述无监督域自适应方法.        

  4. IDEA对新建java线程池的建议

    1 代码片段 ExecutorService pool = Executors.newCachedThreadPool(); 2 建议的三种模板 A 第一种,采用Apache的common.lang3 ...

  5. lua 的元表与元方法

    1. 元表与元方法, 相当于C++的函数重载 参考链接: https://blog.csdn.net/yueya_shanhua/article/details/52233228

  6. Django 详解 信号Signal

    Django信号 Django中提供了“信号调度”,用于在框架执行操作时解耦.通俗来讲,就是一些动作发生的时候,信号允许特定的发送者去提醒一些接受者. Model signals pre_init # ...

  7. 【父元素parent】【子元素children】【同胞siblings】【过滤】

    1.父元素 $("span").parent()           //定位到span的父元素 $("span").parents()          // ...

  8. 《jQuery精品教程视频》视频目录

    \day01\03-视频\02-使用js的缺点.avi; \day01\03-视频\03-jQuery初体验.avi; \day01\03-视频\04-什么是jQuery.avi; \day01\03 ...

  9. 使用 JavaScript 将网站后台的数据变化实时更新到前端

    问:难道只能设置定时器每隔一秒通过 Ajax 向后台请求数据来实现吗? 答: 1. nodejs的 http://socket.io 支持上述 李宏训 所说的三种方式,另外还支持 Flash Sock ...

  10. Aizu - 2200 Mr. Rito Post Office

    题意:/*你是某个岛国(ACM-ICPC Japan)上的一个苦逼程序员,你有一个当邮递员的好基友利腾桑遇到麻烦了:全岛有一些镇子通过水路和旱路相连,走水路必须要用船,在X处下船了船就停在X处.而且岛 ...