
开源协议
:Public Domain
描述
项目说明
本项目主控采用梁山派,实现手持环境检测仪。
V2版本是完全集成各个模块,IO使用没有变化,demo程序可以直接使用,这一版主要是学习用。
V3版本是简化版本,控制了成本,调整了IO使用,驱动与V2版本不兼容。使用时需要主要区别,切不可盲目抄袭。
开源协议
Public Domain
项目相关功能
技术要求
- 温湿度传感器:用于检测温湿度数据;
- 气压传感器:用于检测大气压数据;
- 有害气体传感器:用于检测有害气体等 数据;
- 屏幕:用于实时显示采集到的设备;
- 无线通信:用于将数据发送至接收设备使用;
- 手持:小巧、使用电池,脱离供电线的困扰;
技术指标
- 测量温湿度、气压、有害气体等数据;
- 屏幕通过LVGL开源GUI库实时显示测量结果;
- 使用电池可以放电充电;
- 无线通信方式:将数据通过无线模块发送出去给接收设备使用。
项目属性
本项目基于立创梁山派开发板,参考相关开源模块整理,为本人首次公开的学习项目,项目未曾在别的比赛中获奖。
项目进度
8/16-9/1 确定方案
9/2-9/22 原理图及PCB绘制
9/23-9/29 开发环境搭建及驱动编写
9/30-10/8 焊接调试
10/8- 整理与总结
电路设计说明
整体框图设计如下:
电源设计
采用TP5400升压芯片,为电池充电,同时电池也可以给整个电路供电。TP5400 为一款移动电源专用的单节锂离子电池充电器和恒定 5V 升压控制器,充电部分集高精度电压和充电电流调节器、预充、充电状态指示和充电截止等功能于一体, 可以输出最大 1A 充电电流。而升压电路采
用CMOS 工艺制造的空载电流极低的 VFM 开关型 DC/DC 升压转换器。其具有极低的空载功耗(小于 10uA),且升压输出驱动电流能力能达到 1A。无需外部按键,可以即插即用。电路设计如下:
温湿度传感器
采用的是AHT21温湿度传感器。AHT21作为新一代温湿度传感器,在尺寸与性能方面建立了新的标准:它嵌入了适于回流焊的双列扁平无引脚SMD封装,底面3x3mm,高度0。8 mm。传感器输出经过标定的数字信号,标准 I2C 格式。电路设计如下:
气压传感器
采用的是数字防水气压传感器 WF183D。WF183D是一颗经济型数字压力温度传感器内部包含一个MEMS压力传感器和一个高分辨率 24位△∑ADC及DSP。WF183D通过UART提供高精度已校准压力和温度数字输出,通讯连接非常简单。电路设计如下:
有害气体传感器
采用的是AGS10TVOC传感器,AGS10是一款采用数字信号输出的MEMS TVOC传感器。配置了专用的数字模块采集技术和气体感应传感技术,确保了产品具有极高的可靠性与卓越的长期稳定性,同时具有低功耗、高灵敏度、快速响应、成本低、驱动电路简单等特点。AGS10主要适用于侦测各类有机挥发性气体,如乙醇、氨气、硫化物、苯系蒸汽和其它有害气体,可应用在空气净化器、家用电器、新风机等设备。电路设计如下:
电池电量监测电路
采用的主板上的ADC,选取PA1管脚进行检测。电路设计如下:
屏幕显示
屏幕采用了SPI通讯的240*280像素的1.69寸IPS高清圆角屏幕。 1.69寸屏幕是一种常见的小尺寸显示屏,它指的是屏幕的对角线尺寸为1.69英寸(约4.29厘米)。尽管它相对较小,但在某些应用领域中仍然具有广泛的用途和功能。该屏幕因为单位像素密度更高(总像素/尺寸),所以显示画质更精细,并且控制IO少,外围电路也相对简单。接口使用开发板的SPI3进行通信。电路设计如下:
无线通信模块
采用的是由安信可科技设计的Ai-WB2- 01S无线模块。Ai-WB2-01S是一款无线收发一体的2.4G 模块,2.4G模块是一种用于无线通信的模块,它能够在2.4GHz频段进行无线数据传输和接收。该模块通常由无线收发器和相关的控制电路组成,为用户提供方便的无线通信解决方案。他支持wifi和BLE,小巧且功能强大。设计原理图如下:
按键设计
采用按键作为输入接口,进行控制输入。设计如下:
程序编写与调试
显示屏驱动
1.69屏幕驱动编写,这里参考了模块代码,IO已经更改了,需要重新配置相关IO和DMA配置。
DMA配置这块坑太多了,少了一项配置就错误百出。这让我记忆深刻,以后必须一行行代码去撸。
每个参数都不能错过。
话不多说上代码,由于文档有长度限制,只贴一部分:
#include "lcdinit.h"
//LCD显示缓冲区
__align(32) uint16_t Show_Gram[LCD_RAM_NUMBER] __attribute__((at(SDRAM_DEVICE0_ADDR)));
uint8_t show_over_flag;//是否显示结束
uint8_t show_update_flag; //是否更新
void dma_spi_init(void)
{
dma_single_data_parameter_struct dma_init_struct;
/* 使能DMA1时钟 */
rcu_periph_clock_enable(RCU_DMA1);
/* 初始化DMA1的通道1 */
dma_deinit(DMA1, DMA_CH1);
dma_init_struct.direction = DMA_MEMORY_TO_PERIPH; //内存往外设
dma_init_struct.memory0_addr = (uint32_t)0; //内存地址
dma_init_struct.memory_inc = DMA_MEMORY_INCREASE_ENABLE; //开启内存地址增量
dma_init_struct.periph_memory_width = DMA_MEMORY_WIDTH_8BIT; //内存数据宽度
dma_init_struct.number = LCD_W * LCD_H / 2; //数据量 240*280/2 = 67200/2 = 33600
dma_init_struct.periph_addr = (uint32_t)&SPI_DATA(PORT_SPI) ; //外设地址
dma_init_struct.periph_inc = DMA_PERIPH_INCREASE_DISABLE; //关闭外设地址增量
dma_init_struct.priority = DMA_PRIORITY_ULTRA_HIGH; //优先级高
dma_single_data_mode_init(DMA1, DMA_CH1, &dma_init_struct); //将以上参数填入DMA1的通道1
// 禁用DMA循环模式
dma_circulation_disable(DMA1, DMA_CH1);
// DMA通道外设选择 100
dma_channel_subperipheral_select(DMA1, DMA_CH1, DMA_SUBPERI4);
// 启用DMA1传输完成中断
dma_interrupt_enable(DMA1, DMA_CH1, DMA_CHXCTL_FTFIE);
// 配置中断优先级(必须为最高)
nvic_irq_enable(DMA1_Channel1_IRQn, 0, 0);
// 失能DMA1的通道3
dma_channel_disable(DMA1, DMA_CH1);
}
void lcd_gpio_config(void)
{
#if USE_SOFTWARE
/* 使能时钟 */
rcu_periph_clock_enable(RCU_LCD_SCL);
rcu_periph_clock_enable(RCU_LCD_SDA);
rcu_periph_clock_enable(RCU_LCD_CS);
rcu_periph_clock_enable(RCU_LCD_DC);
rcu_periph_clock_enable(RCU_LCD_RES);
rcu_periph_clock_enable(RCU_LCD_BLK);
/* 配置SCL */
gpio_mode_set(PORT_LCD_SCL,GPIO_MODE_OUTPUT,GPIO_PUPD_PULLUP,GPIO_LCD_SCL);
gpio_output_options_set(PORT_LCD_SCL,GPIO_OTYPE_PP,GPIO_OSPEED_50MHZ,GPIO_LCD_SCL);
gpio_bit_write(PORT_LCD_SCL, GPIO_LCD_SCL, SET);
/* 配置SDA */
gpio_mode_set(PORT_LCD_SDA,GPIO_MODE_OUTPUT,GPIO_PUPD_PULLUP,GPIO_LCD_SDA);
gpio_output_options_set(PORT_LCD_SDA,GPIO_OTYPE_PP,GPIO_OSPEED_50MHZ,GPIO_LCD_SDA);
gpio_bit_write(PORT_LCD_SDA, GPIO_LCD_SDA, SET);
/* 配置DC */
gpio_mode_set(PORT_LCD_DC,GPIO_MODE_OUTPUT,GPIO_PUPD_PULLUP,GPIO_LCD_DC);
gpio_output_options_set(PORT_LCD_DC,GPIO_OTYPE_PP,GPIO_OSPEED_50MHZ,GPIO_LCD_DC);
gpio_bit_write(PORT_LCD_DC, GPIO_LCD_DC, SET);
/* 配置CS */
gpio_mode_set(PORT_LCD_CS,GPIO_MODE_OUTPUT,GPIO_PUPD_PULLUP,GPIO_LCD_CS);
gpio_output_options_set(PORT_LCD_CS,GPIO_OTYPE_PP,GPIO_OSPEED_50MHZ,GPIO_LCD_CS);
gpio_bit_write(PORT_LCD_CS, GPIO_LCD_CS, SET);
/* 配置RES */
gpio_mode_set(PORT_LCD_RES,GPIO_MODE_OUTPUT,GPIO_PUPD_PULLUP,GPIO_LCD_RES);
gpio_output_options_set(PORT_LCD_RES,GPIO_OTYPE_PP,GPIO_OSPEED_50MHZ,GPIO_LCD_RES);
gpio_bit_write(PORT_LCD_RES, GPIO_LCD_RES, SET);
/* 配置BLK */
gpio_mode_set(PORT_LCD_BLK, GPIO_MODE_OUTPUT, GPIO_PUPD_PULLUP, GPIO_LCD_BLK);
gpio_output_options_set(PORT_LCD_BLK, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_LCD_BLK);
gpio_bit_write(PORT_LCD_BLK, GPIO_LCD_BLK, SET);
#else
spi_parameter_struct spi_init_struct;
rcu_periph_clock_enable(RCU_LCD_SCL);
rcu_periph_clock_enable(RCU_LCD_SDA);
rcu_periph_clock_enable(RCU_LCD_CS);
rcu_periph_clock_enable(RCU_LCD_DC);
rcu_periph_clock_enable(RCU_LCD_RES);
rcu_periph_clock_enable(RCU_LCD_BLK);
rcu_periph_clock_enable(RCU_SPI_HARDWARE); // 使能SPI
/* 配置 SPI的SCK GPIO */
gpio_af_set(PORT_LCD_SCL, LINE_AF_SPI, GPIO_LCD_SCL);
gpio_mode_set(PORT_LCD_SCL, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_LCD_SCL);
gpio_output_options_set(PORT_LCD_SCL, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_LCD_SCL);
gpio_bit_set(PORT_LCD_SCL,GPIO_LCD_SCL);
/* 配置 SPI的MOSI GPIO */
gpio_af_set(PORT_LCD_SDA, LINE_AF_SPI, GPIO_LCD_SDA);
gpio_mode_set(PORT_LCD_SDA, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_LCD_SDA);
gpio_output_options_set(PORT_LCD_SDA, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_LCD_SDA);
gpio_bit_set(PORT_LCD_SDA, GPIO_LCD_SDA);
/* 配置DC */
gpio_mode_set(PORT_LCD_DC,GPIO_MODE_OUTPUT,GPIO_PUPD_PULLUP,GPIO_LCD_DC);
gpio_output_options_set(PORT_LCD_DC,GPIO_OTYPE_PP,GPIO_OSPEED_50MHZ,GPIO_LCD_DC);
gpio_bit_write(PORT_LCD_DC, GPIO_LCD_DC, SET);
/* 配置RES */
gpio_mode_set(PORT_LCD_RES,GPIO_MODE_OUTPUT,GPIO_PUPD_PULLUP,GPIO_LCD_RES);
gpio_output_options_set(PORT_LCD_RES,GPIO_OTYPE_PP,GPIO_OSPEED_50MHZ,GPIO_LCD_RES);
gpio_bit_write(PORT_LCD_RES, GPIO_LCD_RES, SET);
/* 配置BLK */
gpio_mode_set(PORT_LCD_BLK, GPIO_MODE_OUTPUT, GPIO_PUPD_PULLDOWN, GPIO_LCD_BLK);
gpio_output_options_set(PORT_LCD_BLK, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_LCD_BLK);
gpio_bit_write(PORT_LCD_BLK, GPIO_LCD_BLK, SET);
/* 配置CS */
gpio_mode_set(PORT_LCD_CS,GPIO_MODE_OUTPUT,GPIO_PUPD_NONE,GPIO_LCD_CS);
gpio_output_options_set(PORT_LCD_CS,GPIO_OTYPE_PP,GPIO_OSPEED_50MHZ,GPIO_LCD_CS);
gpio_bit_write(PORT_LCD_CS, GPIO_LCD_CS, SET);
// 配置 SPI 参数
spi_init_struct.trans_mode = SPI_TRANSMODE_FULLDUPLEX;// 传输模式全双工
spi_init_struct.device_mode = SPI_MASTER; // 配置为主机
spi_init_struct.frame_size = SPI_FRAMESIZE_8BIT; // 8位数据
spi_init_struct.clock_polarity_phase = SPI_CK_PL_HIGH_PH_2EDGE; // 极性高相位2
spi_init_struct.nss = SPI_NSS_SOFT; // 软件cs
spi_init_struct.prescale = SPI_PSC_2; // 2分频
spi_init_struct.endian = SPI_ENDIAN_MSB; // 高位在前
spi_init(PORT_SPI, &spi_init_struct);
//使能DMA发送
spi_dma_enable(PORT_SPI,SPI_DMA_TRANSMIT);
// 使能 SPI
spi_enable(PORT_SPI);
//初始化DMA
dma_spi_init();
#endif
}
void LCD_Writ_Bus(uint8_t dat)
{
#if USE_SOFTWARE
uint8_t i;
LCD_CS_Clr();
for(i=0;i<8;i++)
{
LCD_SCLK_Clr();
if(dat&0x80)
{
LCD_MOSI_Set();
}
else
{
LCD_MOSI_Clr();
}
LCD_SCLK_Set();
dat<<=1;
}
LCD_CS_Set();
#else
LCD_CS_Clr();
//等待发送完成
while(RESET == spi_i2s_flag_get(PORT_SPI, SPI_FLAG_TBE));
//发送数据
spi_i2s_data_transmit(PORT_SPI, dat);
//等待接收完成
while(RESET == spi_i2s_flag_get(PORT_SPI, SPI_FLAG_RBNE));
//等待缓冲区传输完成
while(SET == spi_i2s_flag_get(PORT_SPI,I2S_FLAG_TRANS));
//接收数据
spi_i2s_data_receive(PORT_SPI);
LCD_CS_Set();
#endif
}
//50ms周期
void Lcd_Show_Time_config(void)
{
timer_parameter_struct timer_initpara;
rcu_periph_clock_enable(RCU_TIMER3);
rcu_timer_clock_prescaler_config(RCU_TIMER_PSC_MUL4);
timer_deinit(TIMER3); // 定时器复位
timer_initpara.prescaler = 200 - 1; // 预分频
timer_initpara.alignedmode = TIMER_COUNTER_EDGE; // 对齐模式
timer_initpara.counterdirection = TIMER_COUNTER_UP; // 计数方向
timer_initpara.period = 50000 -1; // 周期
timer_initpara.clockdivision = TIMER_CKDIV_DIV1; // 时钟分频
timer_initpara.repetitioncounter = 0; // 重复计数器
timer_init(TIMER3,&timer_initpara);
timer_master_output_trigger_source_select(TIMER3,TIMER_TRI_OUT_SRC_UPDATE);
timer_interrupt_enable(TIMER3,TIMER_INT_UP); // 中断使能
nvic_irq_enable(TIMER3_IRQn, 0, 1); // 设置中断优先级
timer_enable(TIMER3);
}
//开始显示
void LCD_Show_Gram(void)
{
//设置标志位为未显示完成状态
show_over_flag=1;
//设置显示范围
LCD_Address_Set(0,0,LCD_W-1,LCD_H-1);
LCD_CS_Clr();
//清除全部中断标志位(至少清除通道3的全部中断标志位)
DMA_INTC0(DMA1) = 0xfffffff;
//设置传输数据大小
DMA_CHCNT(DMA1, DMA_CH1) = 33600;
//设置传输地址
DMA_CH1M0ADDR(DMA1) = (uint32_t)Show_Gram;
//开始传输
DMA_CHCTL(DMA1, DMA_CH1) |= DMA_CHXCTL_CHEN;
}
//获取 一帧是否显示完成标志位 =0完成 =1未完成
uint8_t get_show_over_flag(void)
{
return show_over_flag;
}
//设置 一帧是否显示完成标志位
void set_show_over_flag(uint8_t flag)
{
show_over_flag = flag;
}
//获取 是否需要更新标志位 =1需要更新 =0未需要更新
uint8_t get_show_update_flag(void)
{
return show_update_flag;
}
//设置 是否需要更新标志位
void set_show_update_flag(uint8_t flag)
{
show_update_flag = flag;
}
//屏幕搬运完成
void DMA1_Channel1_IRQHandler(void)
{
static uint8_t Show_Number=0;
//全屏幕需要搬运4次
if((++Show_Number) < 4)
{
//清除全部DMA1中断标志位
DMA_INTC0(DMA1) = 0xfffffff;
//重新填充要搬运的数据量
DMA_CHCNT(DMA1, DMA_CH1) = 33600;
//内存搬运地址
DMA_CH1M0ADDR(DMA1) = (uint32_t)Show_Gram+33600*Show_Number;
//开启搬运
DMA_CHCTL(DMA1, DMA_CH1) |= DMA_CHXCTL_CHEN;
}
else
{
//清除DMA搬运完成中断标志位
dma_interrupt_flag_clear(DMA1, DMA_CH1, DMA_INT_FLAG_FTF);
//等待SPI发送完毕
while(SPI_STAT(PORT_SPI) & SPI_STAT_TRANS);
//SPI片选拉高
LCD_CS_Set();
//搬运次数清零
Show_Number=0;
//一帧搬运完成标志位
show_over_flag=0;
}
}
//显示BUFF切换
void TIMER3_IRQHandler(void)
{
timer_interrupt_flag_clear(TIMER3, TIMER_INT_FLAG_UP);
//如果屏幕显示数据DMA搬运完成
if(show_update_flag)
{
//清除完成标志
show_update_flag=0;
//更新显示
LCD_Show_Gram();
}
}
温湿度驱动实现
采用PB6、PB7作为IIC管脚,和LCD的共用IIC,这里为了方便采用模拟IO方式实现,如下
#include "bsp_aht21.h"
#include "systick.h"
#include "bsp_usart.h"
#include "stdio.h"
static float temperature = 0;
static float humidity = 0;
/******************************************************************
* 函 数 名 称:aht21_gpio_init
* 函 数 说 明:对AHT21的IIC引脚初始化
* 函 数 形 参:无
* 函 数 返 回:无
* 作 者:LCKFB
* 备 注:
******************************************************************/
void aht21_gpio_init(void)
{
//打开SDA与SCL的引脚时钟
rcu_periph_clock_enable(RCU_AHT21_SCL);
rcu_periph_clock_enable(RCU_AHT21_SDA);
//设置SCL引脚模式为上拉输出
gpio_mode_set(PORT_AHT21_SCL, GPIO_MODE_OUTPUT, GPIO_PUPD_PULLUP, GPIO_AHT21_SCL);
//设置引脚为开漏模式,翻转速度2MHz
gpio_output_options_set(PORT_AHT21_SCL, GPIO_OTYPE_OD, GPIO_OSPEED_2MHZ, GPIO_AHT21_SCL);
//设置引脚输出高电平SCL等待信号
gpio_bit_write(PORT_AHT21_SCL, GPIO_AHT21_SCL, SET);
//设置SDA引脚
gpio_mode_set(PORT_AHT21_SDA, GPIO_MODE_OUTPUT, GPIO_PUPD_PULLUP, GPIO_AHT21_SDA);
gpio_output_options_set(PORT_AHT21_SDA, GPIO_OTYPE_OD, GPIO_OSPEED_2MHZ, GPIO_AHT21_SDA);
gpio_bit_write(PORT_AHT21_SDA, GPIO_AHT21_SDA, SET);
}
/******************************************************************
* 函 数 名 称:aht21_read_status
* 函 数 说 明:读取AHT21的状态寄存器
* 函 数 形 参:无
* 函 数 返 回:读取到的状态数据
* 作 者:LCKFB
* 备 注:串口如果发送"warning -1",说明无法与传感器通信,请确定是否焊接成功
******************************************************************/
uint8_t aht21_read_status(void)
{
uint8_t status_register_address = 0x71;
uint8_t status_byte;
IIC_Start();
IIC_Send_Byte( status_register_address );
if( I2C_WaitAck() == 1 ) printf("warning -1\r\n");
status_byte = IIC_Read_Byte();
IIC_Send_Nack();
IIC_Stop();
return status_byte;
}
/******************************************************************
* 函 数 名 称:aht21_send_gather_command
* 函 数 说 明:向AHT21发送采集命令
* 函 数 形 参:无
* 函 数 返 回:1:器件识别失败
* 2:发送采集命令失败
* 3:发送数据1失败
* 4:发送数据2失败
* 作 者:LCKFB
* 备 注:无
******************************************************************/
uint8_t aht21_send_gather_command(void)
{
uint8_t device_addr = 0x70;//器件地址
uint8_t gather_command = 0xac;//采集命令
uint8_t gather_command_parameter_1 = 0x33;//采集参数1
uint8_t gather_command_parameter_2 = 0x00;//采集参数2
IIC_Start();
IIC_Send_Byte(device_addr);//发送器件地址
if( I2C_WaitAck() == 1 ) return 1;
IIC_Send_Byte(gather_command);//发送采集命令
if( I2C_WaitAck() == 1 ) return 2;
IIC_Send_Byte(gather_command_parameter_1);//发送采集参数1
if( I2C_WaitAck() == 1 ) return 3;
IIC_Send_Byte(gather_command_parameter_2);//发送采集参数2
if( I2C_WaitAck() == 1 ) return 4;
IIC_Stop();
return 0;
}
/******************************************************************
* 函 数 名 称:aht21_device_init
* 函 数 说 明:通过命令字节初始化AHT21
* 函 数 形 参:无
* 函 数 返 回:无
* 作 者:LCKFB
* 备 注:串口如果发送"warning -x",说明无法与传感器通信,请确定是否焊接成功
******************************************************************/
void aht21_device_init(void)
{
uint8_t device_addr = 0x70;//器件地址
uint8_t init_command = 0xBE;//初始化命令
uint8_t init_command_parameter_1 = 0x08;//采集参数1
uint8_t init_command_parameter_2 = 0x00;//采集参数2
IIC_Start();
IIC_Send_Byte(device_addr);//发送器件地址
if( I2C_WaitAck() == 1 ) printf("warning -5\r\n");
IIC_Send_Byte(init_command);//发送初始化命令
if( I2C_WaitAck() == 1 ) printf("warning -6\r\n");
IIC_Send_Byte(init_command_parameter_1);//发送初始化参数1
if( I2C_WaitAck() == 1 ) printf("warning -7\r\n");
IIC_Send_Byte(init_command_parameter_2);//发送初始化参数2
if( I2C_WaitAck() == 1 ) printf("warning -8\r\n");
IIC_Stop();
}
/******************************************************************
* 函 数 名 称:aht21_read_data
* 函 数 说 明:读取温湿度
* 函 数 形 参:无
* 函 数 返 回:1:未校准 2:读取超时 0:读取成功
* 作 者:LCKFB
* 备 注:无
******************************************************************/
char aht21_read_data(void)
{
uint8_t data[6] = {0};
uint32_t temp = 0;
uint8_t aht21_status_byte = 0;
uint8_t timeout = 0;
//读取AHT21的状态
aht21_status_byte = aht21_read_status();
//如果未校准,则返回1
if( (aht21_status_byte & (1<<3)) == 0 )
{
aht21_device_init();
delay_1ms(50);
return 1;
}
//发送采集命令
aht21_send_gather_command();
do
{
delay_1ms(1);
timeout++;
//读取AHT21的状态
aht21_status_byte = aht21_read_status();
}while( ( ( aht21_status_byte & (1<<7) ) != 0 ) && ( timeout >= 80 ) );
//如果读取超时,则返回2
if( timeout >= 80 ) return 2;
IIC_Start();
IIC_Send_Byte(0x71);
if( I2C_WaitAck() == 1 ) printf("error -1\r\n");
IIC_Read_Byte();//读取状态,不需要保存
IIC_Send_Ack();
//读取6位数据
data[0] = IIC_Read_Byte();
IIC_Send_Ack();
data[1] = IIC_Read_Byte();
IIC_Send_Ack();
data[2] = IIC_Read_Byte();
IIC_Send_Ack();
data[3] = IIC_Read_Byte();
IIC_Send_Ack();
data[4] = IIC_Read_Byte();
IIC_Send_Ack();
data[5] = IIC_Read_Byte();
IIC_Send_Nack();
IIC_Stop();
//整合湿度数据
temp = (data[0]<<12) | (data[1]<<4);
temp = temp | (data[2]>>4);
//换算湿度数据
//2的20次方 = 1048576
humidity = temp / 1048576.0 * 100.0;
//整合湿度数据
temp = ( (data[2]&0x0f)<< 16 ) | (data[3]<<8);
temp = temp | data[4];
//换算湿度数据
//2的20次方 = 1048576
temperature = temp / 1048576.0 * 200.0 - 50;
return 0;
}
/******************************************************************
* 函 数 名 称:get_temperature
* 函 数 说 明:返回读取过的温度
* 函 数 形 参:无
* 函 数 返 回:温度值
* 作 者:LCKFB
* 备 注:使用前,请确保调用 aht21_read_data 采集过数据
******************************************************************/
float get_temperature(void)
{
return temperature;
}
/******************************************************************
* 函 数 名 称:get_humidity
* 函 数 说 明:返回读取过的湿度
* 函 数 形 参:无
* 函 数 返 回:湿度值
* 作 者:LCKFB
* 备 注:使用前,请确保调用 aht21_read_data 采集过数据
******************************************************************/
float get_humidity(void)
{
return humidity;
}
有害气体传感器驱动实现
同样采用IIC方式通讯,由于文档有长度限制,只贴一部分实现如下
#include "bsp_ags10.h"
#include "bsp_usart.h"
#include "stdio.h"
void ags10_gpio_init(void)
{
//打开SDA与SCL的引脚时钟
rcu_periph_clock_enable(RCU_AGS10_SCL);
rcu_periph_clock_enable(RCU_AGS10_SDA);
//设置SCL引脚模式为上拉输出
gpio_mode_set(PORT_AGS10_SCL, GPIO_MODE_OUTPUT, GPIO_PUPD_PULLUP, GPIO_AGS10_SCL);
//设置引脚为开漏模式,翻转速度2MHz
gpio_output_options_set(PORT_AGS10_SCL, GPIO_OTYPE_OD, GPIO_OSPEED_2MHZ, GPIO_AGS10_SCL);
//设置引脚输出高电平SCL等待信号
gpio_bit_write(PORT_AGS10_SCL, GPIO_AGS10_SCL, SET);
//设置SDA引脚
gpio_mode_set(PORT_AGS10_SDA, GPIO_MODE_OUTPUT, GPIO_PUPD_PULLUP, GPIO_AGS10_SDA);
gpio_output_options_set(PORT_AGS10_SDA, GPIO_OTYPE_OD, GPIO_OSPEED_2MHZ, GPIO_AGS10_SDA);
gpio_bit_write(PORT_AGS10_SDA, GPIO_AGS10_SDA, SET);
}
uint8_t Calc_CRC8(uint8_t *dat, uint8_t Num)
{
uint8_t i, byte, crc=0xFF;
for(byte=0; byte= 50) );
//如果超时
if( timeout >= 50 ) return 3;
data[0] = AGS10_IIC_Read_Byte();
AGS10_IIC_Send_Ack();
data[1] = AGS10_IIC_Read_Byte();
AGS10_IIC_Send_Ack();
data[2] = AGS10_IIC_Read_Byte();
AGS10_IIC_Send_Ack();
data[3] = AGS10_IIC_Read_Byte();
AGS10_IIC_Send_Ack();
data[4] = AGS10_IIC_Read_Byte();
AGS10_IIC_Send_Nack();
AGS10_IIC_Stop();
if( Calc_CRC8(data,4) != data[4] )
{
// printf("Check failed\r\n");
return 4;
}
TVOC_data = (data[1]<<16) | (data[2]<<8) | data[3] ;
return TVOC_data;
}
气压传感器驱动实现
采用串口方式实现,如下所示
#include "bsp_wf183d.h"
//最大串口接收长度
#define DATA_LENGTH_MAX 64
uint8_t AirPressure_Data_Buff[DATA_LENGTH_MAX];
uint8_t AirPressure_data_length = 0;
uint8_t AirPressure_data_flag = 0;
void wf183d_gpio_config(void)
{
/* 开启时钟 */
rcu_periph_clock_enable(BSP_WF183D_USART_TX_RCU); // 开启串口时钟
rcu_periph_clock_enable(BSP_WF183D_USART_RX_RCU); // 开启端口时钟
rcu_periph_clock_enable(BSP_WF183D_USART_RCU); // 开启端口时钟
/* 配置GPIO复用功能 */
gpio_af_set(BSP_WF183D_USART_TX_PORT,BSP_WF183D_USART_AF,BSP_WF183D_USART_TX_PIN);
gpio_af_set(BSP_WF183D_USART_RX_PORT,BSP_WF183D_USART_AF,BSP_WF183D_USART_RX_PIN);
/* 配置GPIO的模式 */
/* 配置TX为复用模式 上拉模式 */
gpio_mode_set(BSP_WF183D_USART_TX_PORT,GPIO_MODE_AF,GPIO_PUPD_PULLUP,BSP_WF183D_USART_TX_PIN);
/* 配置RX为复用模式 上拉模式 */
gpio_mode_set(BSP_WF183D_USART_RX_PORT, GPIO_MODE_AF,GPIO_PUPD_PULLUP,BSP_WF183D_USART_RX_PIN);
/* 配置TX为推挽输出 50MHZ */
gpio_output_options_set(BSP_WF183D_USART_TX_PORT,GPIO_OTYPE_PP,GPIO_OSPEED_50MHZ,BSP_WF183D_USART_TX_PIN);
/* 配置RX为推挽输出 50MHZ */
gpio_output_options_set(BSP_WF183D_USART_RX_PORT,GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, BSP_WF183D_USART_RX_PIN);
/* 配置串口的参数 */
usart_deinit(BSP_WF183D_USART); // 复位串口
usart_baudrate_set(BSP_WF183D_USART,9600); // 设置波特率
usart_parity_config(BSP_WF183D_USART,USART_PM_NONE); // 没有校验位
usart_word_length_set(BSP_WF183D_USART,USART_WL_8BIT); // 8位数据位
usart_stop_bit_set(BSP_WF183D_USART,USART_STB_1BIT); // 1位停止位
/* 使能串口 */
usart_enable(BSP_WF183D_USART); // 使能串口
usart_transmit_config(BSP_WF183D_USART,USART_TRANSMIT_ENABLE); // 使能串口发送
usart_receive_config(BSP_WF183D_USART,USART_RECEIVE_ENABLE); // 使能串口接收
/* 中断配置 */
nvic_irq_enable(BSP_WF183D_USART_IRQ, 1, 2); // 配置中断优先级
usart_interrupt_enable(BSP_WF183D_USART,USART_INT_RBNE); // 读数据缓冲区非空中断和溢出错误中断
usart_interrupt_enable(BSP_WF183D_USART,USART_INT_IDLE); // 空闲检测中断
}
void wf183d_send_command(uint8_t *ucstr, uint16_t length)
{
while(length--)
{
usart_data_transmit(BSP_WF183D_USART,*ucstr++); // 发送数据
while(RESET == usart_flag_get(BSP_WF183D_USART,USART_FLAG_TBE)); // 等待发送数据缓冲区标志置位
}
}
void wf183d_data_clear(void)
{
uint16_t i = DATA_LENGTH_MAX - 1;
while( i )
{
AirPressure_Data_Buff[ i-- ] = 0;
}
AirPressure_data_length = 0;
}
/**********************************************************
* 函 数 名 称:Cal_uart_buf_CRC
* 函 数 功 能:CRC校验
* 传 入 参 数:arr:要计算校验值的数据地址 len:校验长度
* 函 数 返 回:计算完成的校验值
* 作 者:LCKFB
* 备 注:无
**********************************************************/
uint8_t Cal_uart_buf_CRC (uint8_t *arr, uint8_t len)
{
uint8_t crc=0 ;
uint8_t i=0 ;
while(len--)
{
crc ^= *arr++;
for(i = 0 ;i < 8 ;i++)
{
if(crc & 0x01) crc = (crc >> 1) ^ 0x8c;
else crc >>= 1 ;
}
}
return crc ;
}
uint32_t FrameResolution(uint8_t *buff)
{
uint32_t data = 0;
//如果帧头不是0XAA
if( buff[0] != 0xAA ) return 1;
//如果CRC校验的值跟接收的校验值不一致
if( Cal_uart_buf_CRC( buff, 7 ) != buff[7] ) return 2;
//数据整合
data = buff[3] | (buff[4] << 8) | (buff[5] << 16) | (buff[6] << 24);
return data;
}
uint8_t get_TemperatureCmd_buff[4] = {0X55, 0X04, 0X0E, 0X6A};
uint8_t get_AirPressureCmd_buff[4] = {0X55, 0X04, 0X0D, 0X88};
/******************************************************************
* 函 数 名 称:GetAirPressureValue
* 函 数 说 明:获取大气压值
* 函 数 形 参:无
* 函 数 返 回:大气压值,单位PA
* 作 者:LCKFB
* 备 注:由于转换压力需要根据当前温度进行补偿,所以需要先进行采集转换温度
******************************************************************/
uint32_t GetAirPressureValue(void)
{
uint32_t AirPressureValue = 0;
//发送采集温度命令
wf183d_send_command( get_TemperatureCmd_buff, 4 );
//等待数据接收完成
while( AirPressure_data_flag != 1 );
//清除接收完成标准位
AirPressure_data_flag = 0;
//不读取温度值,清除当前接收到的温度数据帧
wf183d_data_clear();
//发送采集温度命令
wf183d_send_command( get_AirPressureCmd_buff, 4 );
//等待数据接收完成
while( AirPressure_data_flag != 1 );
//清除接收完成标准位
AirPressure_data_flag = 0;
//解析气压帧,并返回气压数据
AirPressureValue = FrameResolution(AirPressure_Data_Buff);
return AirPressureValue;
}
void USART1_IRQHandler(void)
{
if(usart_interrupt_flag_get(BSP_WF183D_USART,USART_INT_FLAG_RBNE) == SET) // 接收缓冲区不为空
{
AirPressure_Data_Buff[AirPressure_data_length++] = usart_data_receive(BSP_WF183D_USART);// 把接收到的数据放到缓冲区中
}
if(usart_interrupt_flag_get(BSP_WF183D_USART,USART_INT_FLAG_IDLE) == SET) // 检测到帧中断
{ usart_data_receive(BSP_WF183D_USART); // 必须要读,读出来的值不能要
AirPressure_Data_Buff[AirPressure_data_length] = '\0';// 数据接收完毕,数组结束标志
AirPressure_data_flag = 1;
}
}
电池电量采集实现
采用ADC进行采集实现,如下所示
#include "bsp_voltage.h"
#include "bsp_usart.h"
#include "stdio.h"
/**********************************************************
* 函 数 名 称:power_voltage_gpio_config
* 函 数 功 能:电源电压测量引脚初始化
* 传 入 参 数:无
* 函 数 返 回:无
* 作 者:LCKFB
* 备 注:无
**********************************************************/
void power_voltage_gpio_config(void)
{
//使能引脚时钟
rcu_periph_clock_enable(RCU_GPIOA);
//使能ADC时钟
rcu_periph_clock_enable(RCU_ADC0);
//配置ADC时钟
adc_clock_config(ADC_ADCCK_HCLK_DIV5);
//配置引脚为模拟输入模式
gpio_mode_set(GPIOA, GPIO_MODE_ANALOG, GPIO_PUPD_NONE, GPIO_PIN_1);
//配置ADC为独立模式
adc_sync_mode_config(ADC_SYNC_MODE_INDEPENDENT);
//使能扫描模式
adc_special_function_config(ADC0, ADC_SCAN_MODE, ENABLE);
//数据右对齐
adc_data_alignment_config(ADC0, ADC_DATAALIGN_RIGHT);
//ADC0设置为12位分辨率
adc_resolution_config(ADC0, ADC_RESOLUTION_12B);
//ADC0设置为规则组 一共使用 1 个通道
adc_channel_length_config(ADC0,ADC_REGULAR_CHANNEL, 1);
//ADC外部触发禁用, 即只能使用软件触发
adc_external_trigger_config(ADC0, ADC_REGULAR_CHANNEL, EXTERNAL_TRIGGER_DISABLE);
//ADC0使能
adc_enable(ADC0);
//开启ADC自校准
adc_calibration_enable(ADC0);
}
/**********************************************************
* 函 数 名 称:get_voltage_value
* 函 数 功 能:读取电压值
* 传 入 参 数:无
* 函 数 返 回:测量到的值
* 作 者:LC
* 备 注:PA1 = ADC_CHANNEL_1
**********************************************************/
float get_voltage_value(void)
{
unsigned int adc_value = 0;
float voltage = 0;
//设置采集通道
adc_regular_channel_config(ADC0, 0, ADC_CHANNEL_1, ADC_SAMPLETIME_112);
//开始软件转换
adc_software_trigger_enable(ADC0, ADC_REGULAR_CHANNEL);
//等待 ADC0 采样完成
while ( adc_flag_get(ADC0, ADC_FLAG_EOC) == RESET )
{ ;
}
//读取采样值
adc_value = adc_regular_data_read(ADC0);
//电阻分压公式:U = ( R / R总 ) * U源 (R1=7600 R2=8580 R1+R2=16180)
//ADC端占的电压比例:10K/20k=0.5 ( 7600/16180=0.47)
//电池电量最高时,ADC端分压得到的电压为:0.5*4.2V=2.1V (0.47*4.2=1.974)
//电池电量最低时,ADC端分压得到的电压为:0.5*3.2V=1.6V (0.47*3.2=1.504)
voltage = adc_value / 4095.0 * 3.3;
// printf("voltage = %f\r\n",voltage);
//换算实际电池电压 : 采集到的电压 / 0.5
// printf("virtual voltage = %f\r\n",voltage / 0.5);
if( (voltage-1.6) <= 0 ) voltage = 1.6;
//电压百分比 = 当前值 / 总值 * 100
voltage = (voltage-1.6) / 0.5 * 100.0;
// printf("Percentage voltage = %f\r\n",voltage);
//返回电压百分比
return voltage;
}
实现效果如下:
https://www.bilibili.com/video/BV1Uu4y1W7ka/?spm_id_from=333.999.0.0&vd_source=e36622a05269c0356d6cd566056a2488
https://www.bilibili.com/video/BV1S8411k7ea/?spm_id_from=333.999.0.0&vd_source=e36622a05269c0356d6cd566056a2488
LVGL GUI实现
由于文档长度有限,不能贴代码了,具体代码见整个工程文件吧。
实现效果如下:
https://www.bilibili.com/video/BV1jw411c7Yh/?spm_id_from=333.999.0.0
蓝牙模块驱动实现
具体实现方式参考如下开源移植的模块,连接如下:
等具体审核完了,可以立创模块移植里面看到,这里不能贴代码了,因为文档长度受限。
飞书链接:https://h1idyj5839p.feishu.cn/docx/QhGrdzkxto0yxLx2OZncDveanOc 密码:No59+q)0
通过手机蓝牙连接后,主机设置从机功能,手机可以接收信息,效果见附件--从机蓝牙数据接收。
按键驱动
具体代码详见附件工程文件,这里不细说
IO与案例不一样,Key1~4分别对应:PB3-4,PC4-5
效果如下:
整体功能实现
代码见工程,效果见附件-主机效果演示。
由于时间有限,关于WIFI上传的云服务后续有空实现吧。
WIFI&BLE模块功能强大,一个模块具有蓝牙和WIFI功能,节省不少资源。
本次参与,学到了不少知识,虽然累,但是还是成就感满满啊。
感谢平台给了我学习的机会。时间和水平均有限,欢迎各位提出宝贵意见,谢谢。
效果如下:
https://www.bilibili.com/video/BV16N4y1y7aC/?spm_id_from=333.999.0.0&vd_source=e36622a05269c0356d6cd566056a2488
https://www.bilibili.com/video/BV16N4y1y7aC/?spm_id_from=333.999.0.0&vd_source=e36622a05269c0356d6cd566056a2488
设计图

BOM


评论