STM32L476应用开发之八:便携式气体分析仪项目总结
在本次项目中,我们实现的实际上是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的操作以及数据获取与转化,并对采集到的数据做滤波处理,滤波的目的是在数据稳定时,避免数据小数点后的微小变化。
/*获取采集的物理量值,并作平滑处理*/
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应用开发之八:便携式气体分析仪项目总结的更多相关文章
- MVC5 网站开发之八 栏目功能 添加、修改和删除
本次实现栏目的浏览.添加.修改和删除. 栏目一共有三种类型. 常规栏目-可以添加子栏目,也可以添加内容模型.当不选择内容模型时,不能添加内容. 单页栏目-栏目只有一个页面,可以设置视图. 链接栏目-栏 ...
- STM32L476应用开发之五:数据保存与SD卡操作
便携式气体分析仪的特点就是离线运行.尽管是离线运行,但测试数据还是需要的,所以采取方式保存数据就是必须的.在本次项目中我们计划采用SD卡来保存数据. 1.硬件设计 该读卡器整合 SD 卡规范和 FAT ...
- STM32L476应用开发之一:初次使用
今天终于收到了期待已久的NUCLEO-F412ZG,感谢电子发烧友论坛!多年以来基本都是在STM32平台上做一些设计开发工作.但是低功耗的基本没用过,这次要做便携式设备才对这方面有所接触,正好这时电子 ...
- MVC5 网站开发之二 创建项目
昨天对项目的思路大致理了一下,今天先把解决方案建立起来.整个解决包含Ninesky.Web.Ninesky.Core,Ninesky.DataLibrary等3个项目.Ninesky.Web是web应 ...
- ASP.NET 5 入门(1) - 建立和开发ASP.NET 5 项目
ASP.NET入门(1) - 建立和开发ASP.NET 5 项目 ASP.NET 5 理解和入门 使用自定义配置文件 建立项目 首先,目前只有VS 2015支持开发最新的ASP.NET 5 程序,所以 ...
- 实战使用Axure设计App,使用WebStorm开发(2) – 创建 Ionic 项目
系列文章 实战使用Axure设计App,使用WebStorm开发(1) – 用Axure描述需求 实战使用Axure设计App,使用WebStorm开发(2) – 创建 Ionic 项目 实战使 ...
- 在iOS开发中,给项目添加新的.framework
首先需要了解一下iOS中静态库和动态库.framework的概念 静态库与动态库的区别 首先来看什么是库,库(Library)说白了就是一段编译好的二进制代码,加上头文件就可以供别人使用. 什么时候我 ...
- XAF应用开发教程(一) 创建项目
XAF是DevExpress公司的快速开发框架,全称eXpress Application Framework,是企业信息系统的开发利器,快速开发效果显著,在.net框架中,笔者至今没有找到一款可以与 ...
- 一年开发ASP.NET MVC4项目经验总结
一年开发ASP.NET MVC4项目所用所学技术经验总结 阅读目录 文章背景 前端所用技术摘要 后端所用技术摘要 1. 文章背景 本人2014年从Java转行到C#从事BS项目的开发,刚开始接触的是A ...
随机推荐
- 【解题报告】SRM-08
A Description 给一个 01 串设为其 S,询问是否存在只出现两次的 01 串 T. 这里的出现定义为存在一串下标 ,满足 且 . Input 一行,一个 01 串 Output 一行, ...
- Docker 创建 mongo 容器
获取 docker 认证 mongo 镜像: docker pull mongo 创建运行 mongo 容器: docker run -d -it -p : --name mongo3 -m 512M ...
- new和delete动态分配和撤销内存
视频:C++引用及new和delete的使用 一.new用法 使用new运算符时必须已知数据类型,new运算符会向系统堆区申请足够的存储空间,如果申请成功,就返回该内存块的首地址,动态分配失败,则返回 ...
- HashMap的源码,实现原理,底层结构
转载一遍不错的文章,让你深入了解HashMap http://www.cnblogs.com/ITtangtang/p/3948406.html
- wx小程序-起航!
手动创建 入口配置文件 app 页面文件新创建一个文件夹 然后在里面分类 1.文件夹名可以不一样,但是里面的wxml,wxss,json 等文件名必须保持一致 2.app.json 入口文件, ...
- SpringCloud Zuul(路由网关)
⒈Zuul是什么? Zuul包含了两个最主要的功能,对请求的路由和过滤.其中路由功能负责将外部请求转发到具体的微服务实例上,是实现外部访问统一入口的基础.过滤器功能则负责对请求的处理过程进行干预,是实 ...
- 【php】随缘php企业网站管理系统V2.0 shownews.php注入漏洞
程序名称:随缘网络php企业网站管理系统2.0免费版 以下为系统的功能简介: 1.采用div+css布局经测试兼容IE及firefox主流浏览器,其他浏览器暂未测试. 2.产品新闻三级无限分类. 3. ...
- 如何操控DevExpress的 SpreadSheet 控件 并与 XAF 结合应用
DevExpress的XAF 框架通常使用 GridControl 控件来操作数据库表中的数据,但导入导出.非结构化数据的管理可以使用SpreadSheet 控件. SpreadSheet 控件模拟微 ...
- mac使用pytesseract
import locale locale.setlocale(locale.LC_ALL, 'C') import pytesseract import pathlib import tracebac ...
- RPC与实践(thrift在windows的编译/安装--c++版)
------------------------------------------------------------------------ RPC 功能目标 RPC 的主要功能目标是让构建分布式 ...