LVGL+FreeRTOS实战项目:智能健康助手(xgzp6847a篇)
XGZP6847A 压力传感器简介
-
功能:用于测量气压或液压,输出模拟信号,与外部 ADC 结合使用以获取压力值。
-
接口:输出电压与压力成线性关系。
-
应用:广泛用于工业控制、环境监测、医疗设备等领域。
硬件连接
XGZP6847A 引脚 | 功能 | STM32 连接 |
---|---|---|
VCC | 电源输入 (+3.3V) | 3.3V |
GND | 地 | GND |
OUT | 模拟信号输出 | PA0 |
软件部分
XGZP6847A 输出模拟信号,因此无需通信协议,通过 STM32 内部 ADC 对信号进行采样即可实现数据读取,我们搭配DMA一起使用,以提高CPU利用率。
我们先看一下源文件:
#include "xgzp6847a.h"
__IO uint16_t ADC_ConvertedValue;
void XGZP_ADC_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
// 使能 GPIO 时钟
RCC_AHB1PeriphClockCmd(XGZP_ADC_GPIO_CLK, ENABLE);
// 配置 IO
GPIO_InitStructure.GPIO_Pin = XGZP_ADC_GPIO_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL ; //不上拉不下拉
GPIO_Init(XGZP_ADC_GPIO_PORT, &GPIO_InitStructure);
}
void XGZP_ADC_Mode_Init(void)
{
DMA_InitTypeDef DMA_InitStructure;
ADC_InitTypeDef ADC_InitStructure;
ADC_CommonInitTypeDef ADC_CommonInitStructure;
// 开启ADC时钟
RCC_APB2PeriphClockCmd(XGZP_ADC_CLK , ENABLE);
// ------------------DMA Init 结构体参数 初始化--------------------------
// ADC1使用DMA2,数据流0,通道0,这个是手册固定死的
// 开启DMA时钟
RCC_AHB1PeriphClockCmd(XGZP_ADC_DMA_CLK, ENABLE);
// 外设基址为:ADC 数据寄存器地址
DMA_InitStructure.DMA_PeripheralBaseAddr = XGZP_ADC_DR_ADDR;
// 存储器地址,实际上就是一个内部SRAM的变量
DMA_InitStructure.DMA_Memory0BaseAddr = (u32)&ADC_ConvertedValue;
// 数据传输方向为外设到存储器
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory;
// 缓冲区大小为1,缓冲区的大小应该等于存储器的大小
DMA_InitStructure.DMA_BufferSize = 1;
// 外设寄存器只有一个,地址不用递增
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
// 存储器地址固定
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Disable;
// // 外设数据大小为半字,即两个字节
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
// 存储器数据大小也为半字,跟外设数据大小相同
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
// 循环传输模式
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
// DMA 传输通道优先级为高,当使用一个DMA通道时,优先级设置不影响
DMA_InitStructure.DMA_Priority = DMA_Priority_High;
// 禁止DMA FIFO ,使用直连模式
DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;
// FIFO 大小,FIFO模式禁止时,这个不用配置
DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull;
DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;
DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
// 选择 DMA 通道,通道存在于流中
DMA_InitStructure.DMA_Channel = XGZP_ADC_DMA_CHANNEL;
//初始化DMA流,流相当于一个大的管道,管道里面有很多通道
DMA_Init(XGZP_ADC_DMA_STREAM, &DMA_InitStructure);
// 使能DMA流
DMA_Cmd(XGZP_ADC_DMA_STREAM, ENABLE);
// -------------------ADC Common 结构体 参数 初始化------------------------
// 独立ADC模式
ADC_CommonInitStructure.ADC_Mode = ADC_Mode_Independent;
// 时钟为fpclk x分频
ADC_CommonInitStructure.ADC_Prescaler = ADC_Prescaler_Div4;
// 禁止DMA直接访问模式
ADC_CommonInitStructure.ADC_DMAAccessMode = ADC_DMAAccessMode_Disabled;
// 采样时间间隔
ADC_CommonInitStructure.ADC_TwoSamplingDelay = ADC_TwoSamplingDelay_5Cycles;
ADC_CommonInit(&ADC_CommonInitStructure);
// -------------------ADC Init 结构体 参数 初始化--------------------------
ADC_StructInit(&ADC_InitStructure);
// ADC 分辨率
ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b;
// 禁止扫描模式,多通道采集才需要
ADC_InitStructure.ADC_ScanConvMode = DISABLE;
// 连续转换
ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;
//禁止外部边沿触发
ADC_InitStructure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_None;
//外部触发通道,本例子使用软件触发,此值随便赋值即可
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T1_CC1;
//数据右对齐
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
//转换通道 1个
ADC_InitStructure.ADC_NbrOfConversion = 1;
ADC_Init(XGZP_ADC, &ADC_InitStructure);
//---------------------------------------------------------------------------
// 配置 ADC 通道转换顺序为1,第一个转换,采样时间为3个时钟周期
ADC_RegularChannelConfig(XGZP_ADC, XGZP_ADC_CHANNEL, 1, ADC_SampleTime_3Cycles);
// 使能DMA请求 after last transfer (Single-ADC mode)
ADC_DMARequestAfterLastTransferCmd(XGZP_ADC, ENABLE);
// 使能ADC DMA
ADC_DMACmd(XGZP_ADC, ENABLE);
// 使能ADC
ADC_Cmd(XGZP_ADC, ENABLE);
//开始adc转换,软件触发
ADC_SoftwareStartConv(XGZP_ADC);
}
void XGZP_Init(void)
{
XGZP_ADC_GPIO_Init();
XGZP_ADC_Mode_Init();
}
u16 Get_Adc_Average(u8 times)
{
u32 temp_val=0;
u8 t;
for(t=0;t<times;t++)
{
temp_val+=ADC_ConvertedValue;
delay_ms(5);
}
return temp_val/times;
}
long map(long x, long in_min, long in_max, long out_min, long out_max)
{
return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}
int xgzp_adc;
const float VCC =3300; // ADC参考电压为mV
float Pressure_V=0.0;
long pressure=0;
const float Voltage_0 =160; // 零点电压值mV 校准时需修改
const float Voltage_40 =3750 ; // 满量程输出电压值mV 需修改
char P_disbuff[6]={0};
//void Get_XGZP_Data(void)
//{
// xgzp_adc = Get_Adc_Average(10);
// Pressure_V=(xgzp_adc*VCC)/4096;
// pressure = map(Pressure_V, Voltage_0, Voltage_40, 0, 40000.0);
// /*显示气压值*/
// if(pressure<=0){pressure=0;}
// if(pressure>=40000){pressure=40000;}
// P_disbuff[0]=(int)((pressure))/10000+'0';
// P_disbuff[1]=(int)((pressure))%10000/1000+'0';
// P_disbuff[2]=(int)((pressure))%1000/100+'0';
// P_disbuff[3]=(int)((pressure))%100/10+'0';
// P_disbuff[4]=(int)((pressure))%10+'0';
// //OLED_ShowStr(27,4,P_disbuff,2);
ADC_ConvertedValueLocal =(float) ADC_ConvertedValue/4096*(float)3.3; // 读取转换的AD值
// //LCD_Show_Info(0,80, "ADC: %s", P_disbuff);
//}
void Get_XGZP_Data(char *buffer)
{
xgzp_adc = Get_Adc_Average(10);
Pressure_V=(xgzp_adc*VCC)/4096;
pressure = map(Pressure_V, Voltage_0, Voltage_40, 0, 40000.0);
/*显示气压值*/
if(pressure<=0){pressure=0;}
if(pressure>=40000){pressure=40000;}
buffer[0]=(int)((pressure))/10000+'0';
buffer[1]=(int)((pressure))%10000/1000+'0';
buffer[2]=(int)((pressure))%1000/100+'0';
buffer[3]=(int)((pressure))%100/10+'0';
buffer[4]=(int)((pressure))%10+'0';
//OLED_ShowStr(27,4,P_disbuff,2);
// ADC_ConvertedValueLocal =(float) ADC_ConvertedValue/4096*(float)3.3; // 读取转换的AD值
//LCD_Show_Info(0,80, "ADC: %s", P_disbuff);
}
我们这里其实就是一套经典ADC+DMA的模板了,这里我给大家说一下大概思路,讲解注释里面几乎都写得非常清楚,如果有看不懂的,说明有关这两个外设的STM32知识没学明白,可以先去重新学习一下。
我们接的是PA0,通过查表可以知道,用的是ADC1的通道0,所以我们ADC配置的时候配置的是ADC1的通道0,如下所示:
然后就是DMA了,我们继续查看芯片手册:
可以看到对应的是DMA2的数据流0的通道2,所以我们按如图所下配置:
之后我们手动开启ADC转换,那么就可以实现ADC+DMA转换了,之后需要知道ADC值,只需要查看数据所转移到地址中的数据值,我们这里的是ADC_ConvertedValue 。
获取XGZP6847A传感器值
我们在获取传感器任务里面进行获取,我们先不用关注FreeRTOS,我们先去关注一下如何获取传感器数值。
Get_XGZP_Data(sens_data.P_disbuff);
这里就是把我们的ADC转换值进行一定的处理之后(均值处理以及误差消除),通过公式就可以算出我们当前的压力值了,代码非常简单,如下所示: