发作品签到
专业版

基于梁山派-手持环境检测仪

工程标签

850
0
0
0

简介

基于梁山派开发板,开发手持环境检测仪。

简介:基于梁山派开发板,开发手持环境检测仪。
立创·开发板电子设计训练营

开源协议

Public Domain

创建时间:2023-08-16 01:29:05更新时间:2023-10-11 08:35:18

描述

项目说明

本项目主控采用梁山派,实现手持环境检测仪。
V2版本是完全集成各个模块,IO使用没有变化,demo程序可以直接使用,这一版主要是学习用。
V3版本是简化版本,控制了成本,调整了IO使用,驱动与V2版本不兼容。使用时需要主要区别,切不可盲目抄袭。

开源协议

Public Domain

项目相关功能

技术要求

  1. 温湿度传感器:用于检测温湿度数据;
  2. 气压传感器:用于检测大气压数据;
  3. 有害气体传感器:用于检测有害气体等 数据;
  4. 屏幕:用于实时显示采集到的设备;
  5. 无线通信:用于将数据发送至接收设备使用;
  6. 手持:小巧、使用电池,脱离供电线的困扰;

技术指标

  1. 测量温湿度、气压、有害气体等数据;
  2. 屏幕通过LVGL开源GUI库实时显示测量结果;
  3. 使用电池可以放电充电;
  4. 无线通信方式:将数据通过无线模块发送出去给接收设备使用。

项目属性

本项目基于立创梁山派开发板,参考相关开源模块整理,为本人首次公开的学习项目,项目未曾在别的比赛中获奖。

项目进度

8/16-9/1 确定方案
9/2-9/22 原理图及PCB绘制
9/23-9/29 开发环境搭建及驱动编写
9/30-10/8 焊接调试
10/8- 整理与总结

电路设计说明

整体框图设计如下:
image.png

电源设计

采用TP5400升压芯片,为电池充电,同时电池也可以给整个电路供电。TP5400 为一款移动电源专用的单节锂离子电池充电器和恒定 5V 升压控制器,充电部分集高精度电压和充电电流调节器、预充、充电状态指示和充电截止等功能于一体, 可以输出最大 1A 充电电流。而升压电路采
用CMOS 工艺制造的空载电流极低的 VFM 开关型 DC/DC 升压转换器。其具有极低的空载功耗(小于 10uA),且升压输出驱动电流能力能达到 1A。无需外部按键,可以即插即用。电路设计如下:
image.png

温湿度传感器

采用的是AHT21温湿度传感器。AHT21作为新一代温湿度传感器,在尺寸与性能方面建立了新的标准:它嵌入了适于回流焊的双列扁平无引脚SMD封装,底面3x3mm,高度0。8 mm。传感器输出经过标定的数字信号,标准 I2C 格式。电路设计如下:
image.png

气压传感器

采用的是数字防水气压传感器 WF183D。WF183D是一颗经济型数字压力温度传感器内部包含一个MEMS压力传感器和一个高分辨率 24位△∑ADC及DSP。WF183D通过UART提供高精度已校准压力和温度数字输出,通讯连接非常简单。电路设计如下:
image.png

有害气体传感器

采用的是AGS10TVOC传感器,AGS10是一款采用数字信号输出的MEMS TVOC传感器。配置了专用的数字模块采集技术和气体感应传感技术,确保了产品具有极高的可靠性与卓越的长期稳定性,同时具有低功耗、高灵敏度、快速响应、成本低、驱动电路简单等特点。AGS10主要适用于侦测各类有机挥发性气体,如乙醇、氨气、硫化物、苯系蒸汽和其它有害气体,可应用在空气净化器、家用电器、新风机等设备。电路设计如下:
image.png

电池电量监测电路

采用的主板上的ADC,选取PA1管脚进行检测。电路设计如下:
image.png

屏幕显示

屏幕采用了SPI通讯的240*280像素的1.69寸IPS高清圆角屏幕。 1.69寸屏幕是一种常见的小尺寸显示屏,它指的是屏幕的对角线尺寸为1.69英寸(约4.29厘米)。尽管它相对较小,但在某些应用领域中仍然具有广泛的用途和功能。该屏幕因为单位像素密度更高(总像素/尺寸),所以显示画质更精细,并且控制IO少,外围电路也相对简单。接口使用开发板的SPI3进行通信。电路设计如下:
image.png
image.png

无线通信模块

采用的是由安信可科技设计的Ai-WB2- 01S无线模块。Ai-WB2-01S是一款无线收发一体的2.4G 模块,2.4G模块是一种用于无线通信的模块,它能够在2.4GHz频段进行无线数据传输和接收。该模块通常由无线收发器和相关的控制电路组成,为用户提供方便的无线通信解决方案。他支持wifi和BLE,小巧且功能强大。设计原理图如下:
image.png
image.png

按键设计

采用按键作为输入接口,进行控制输入。设计如下:
image.png

程序编写与调试

显示屏驱动

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();
    }
}

调试效果:
https://www.bilibili.com/video/BV1yB4y1Z7HS/?spm_id_from=333.999.0.0&vd_source=e36622a05269c0356d6cd566056a2488


温湿度驱动实现

采用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;
}

实现效果:
https://www.bilibili.com/video/BV1Cw411A7Xy/?spm_id_from=333.999.0.0&vd_source=e36622a05269c0356d6cd566056a2488


有害气体传感器驱动实现

同样采用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;
}

调试效果:
https://www.bilibili.com/video/BV1pH4y1o7SG/?spm_id_from=333.999.0.0&vd_source=e36622a05269c0356d6cd566056a2488


气压传感器驱动实现

采用串口方式实现,如下所示


#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;
	}	
}

实现效果如下:
https://www.bilibili.com/video/BV1S8411k7ea/?spm_id_from=333.999.0.0&vd_source=e36622a05269c0356d6cd566056a2488


电池电量采集实现

采用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

通过手机蓝牙连接后,主机设置从机功能,手机可以接收信息,效果见附件--从机蓝牙数据接收。

效果如下:
https://www.bilibili.com/video/BV16N4y1y7aC/?spm_id_from=333.999.0.0&vd_source=e36622a05269c0356d6cd566056a2488


按键驱动

具体代码详见附件工程文件,这里不细说
IO与案例不一样,Key1~4分别对应:PB3-4,PC4-5

效果如下:

https://www.bilibili.com/video/BV16N4y1y7aC/?spm_id_from=333.999.0.0&vd_source=e36622a05269c0356d6cd566056a2488


整体功能实现

代码见工程,效果见附件-主机效果演示。
由于时间有限,关于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

暂无BOM

附件

序号文件名称下载次数
1
MyProject_Sensor_1010.zip
38
克隆工程
添加到专辑
0
0
分享
侵权投诉

工程成员

评论

全部评论(1)
按时间排序|按热度排序
粉丝0|获赞0
相关工程
暂无相关工程

底部导航