基于梁山派-手持环境检测仪 - 嘉立创EDA开源硬件平台

编辑器版本 ×
标准版 Standard

1、简单易用,可快速上手

2、流畅支持300个器件或1000个焊盘以下的设计规模

3、支持简单的电路仿真

4、面向学生、老师、创客

专业版 professional

1、全新的交互和界面

2、流畅支持超过3w器件或10w焊盘的设计规模,支持面板和外壳设计

3、更严谨的设计约束,更规范的流程

4、面向企业、更专业的用户

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

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

开源协议: Public Domain

(未经作者授权,禁止转载)

已参加:立创·开发板电子设计训练营

创建时间: 2023-08-16 09:29:05
更新时间: 2023-12-07 14:40:52
描述
# 项目说明 本项目主控采用梁山派,实现手持环境检测仪。 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](//image.lceda.cn/pullimage/K9Pncf6uuST8JI1uifeMBHRI1Nl4bRMHZ4G46xfL.png) ## 电源设计 采用TP5400升压芯片,为电池充电,同时电池也可以给整个电路供电。TP5400 为一款移动电源专用的单节锂离子电池充电器和恒定 5V 升压控制器,充电部分集高精度电压和充电电流调节器、预充、充电状态指示和充电截止等功能于一体, 可以输出最大 1A 充电电流。而升压电路采 用CMOS 工艺制造的空载电流极低的 VFM 开关型 DC/DC 升压转换器。其具有极低的空载功耗(小于 10uA),且升压输出驱动电流能力能达到 1A。无需外部按键,可以即插即用。电路设计如下: ![image.png](//image.lceda.cn/pullimage/1PgOEUa1wTQRZzRa30lgRnqKMBs7NWiA6YKVN3Io.png) ## 温湿度传感器 采用的是AHT21温湿度传感器。AHT21作为新一代温湿度传感器,在尺寸与性能方面建立了新的标准:它嵌入了适于回流焊的双列扁平无引脚SMD封装,底面3x3mm,高度0。8 mm。传感器输出经过标定的数字信号,标准 I2C 格式。电路设计如下: ![image.png](//image.lceda.cn/pullimage/n8j4pXT1RwKdtpk0gIatQmqAQx2SCBGzS7gHtm3O.png) ## 气压传感器 采用的是数字防水气压传感器 WF183D。WF183D是一颗经济型数字压力温度传感器内部包含一个MEMS压力传感器和一个高分辨率 24位△∑ADC及DSP。WF183D通过UART提供高精度已校准压力和温度数字输出,通讯连接非常简单。电路设计如下: ![image.png](//image.lceda.cn/pullimage/sIBWrvDybKetKekyJiddvWxAUXK118NDikUmS5bA.png) ## 有害气体传感器 采用的是AGS10TVOC传感器,AGS10是一款采用数字信号输出的MEMS TVOC传感器。配置了专用的数字模块采集技术和气体感应传感技术,确保了产品具有极高的可靠性与卓越的长期稳定性,同时具有低功耗、高灵敏度、快速响应、成本低、驱动电路简单等特点。AGS10主要适用于侦测各类有机挥发性气体,如乙醇、氨气、硫化物、苯系蒸汽和其它有害气体,可应用在空气净化器、家用电器、新风机等设备。电路设计如下: ![image.png](//image.lceda.cn/pullimage/MspJy5yByyFooihShuJjrW78IvD88JOXB0k2Kf6b.png) ## 电池电量监测电路 采用的主板上的ADC,选取PA1管脚进行检测。电路设计如下: ![image.png](//image.lceda.cn/pullimage/0oT8SBdeEXmQzVkToE3QuQ6Idi9NCtTqhWGyER8n.png) ## 屏幕显示 屏幕采用了SPI通讯的240*280像素的1.69寸IPS高清圆角屏幕。 1.69寸屏幕是一种常见的小尺寸显示屏,它指的是屏幕的对角线尺寸为1.69英寸(约4.29厘米)。尽管它相对较小,但在某些应用领域中仍然具有广泛的用途和功能。该屏幕因为单位像素密度更高(总像素/尺寸),所以显示画质更精细,并且控制IO少,外围电路也相对简单。接口使用开发板的SPI3进行通信。电路设计如下: ![image.png](//image.lceda.cn/pullimage/TtcZzvhke4DbLdg4aup7hYO1f8qDhZTtEwgKUw93.png) ![image.png](//image.lceda.cn/pullimage/QDMACnclgIQzpFD1qN8FhnN1WZDOtgaY20WMaxRX.png) ## 无线通信模块 采用的是由安信可科技设计的Ai-WB2- 01S无线模块。Ai-WB2-01S是一款无线收发一体的2.4G 模块,2.4G模块是一种用于无线通信的模块,它能够在2.4GHz频段进行无线数据传输和接收。该模块通常由无线收发器和相关的控制电路组成,为用户提供方便的无线通信解决方案。他支持wifi和BLE,小巧且功能强大。设计原理图如下: ![image.png](//image.lceda.cn/pullimage/GCHq7WfnbnRsL5ZOI3c9QXpa70Uy9kFVgNBkvx47.png) ![image.png](//image.lceda.cn/pullimage/rrlhRRscYNNhWL1HWrdxJ3k9VQMs0WyqhULw66La.png) ## 按键设计 采用按键作为输入接口,进行控制输入。设计如下: ![image.png](//image.lceda.cn/pullimage/GXYgby520BWcZOcFiEYzFQhOEKYzcSkS9aPbdvqM.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](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](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<Num; byte++) { crc ^= (dat[byte]); for( i = 0; i < 8; i++ ) { if(crc & 0x80) crc = ( crc << 1 ) ^ 0x31; else crc = ( crc << 1 ); } } return crc; } uint32_t ags10_read(void) { uint8_t timeout = 0; uint8_t data[5] = {0}; uint32_t TVOC_data = 0; AGS10_IIC_Start(); AGS10_IIC_Send_Byte(0X34); if( AGS10_I2C_WaitAck() == 1 ) return 1; AGS10_IIC_Send_Byte(0X00); if( AGS10_I2C_WaitAck() == 1 ) return 2; AGS10_IIC_Stop(); do{ delay_1ms(1); timeout++; AGS10_IIC_Start(); AGS10_IIC_Send_Byte(0X35); }while( (AGS10_I2C_WaitAck() == 1) && (timeout >= 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](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](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/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](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://www.bilibili.com/video/BV1jw411c7Yh/?spm_id_from=333.999.0.0) ## 蓝牙模块驱动实现 具体实现方式参考如下开源移植的模块,连接如下: 等具体审核完了,可以立创模块移植里面看到,这里不能贴代码了,因为文档长度受限。 飞书链接:[https://h1idyj5839p.feishu.cn/docx/QhGrdzkxto0yxLx2OZncDveanOc](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](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](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)

[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)



设计图
原理图
1 /
PCB
1 /
未生成预览图,请在编辑器重新保存一次
工程视频/附件
序号 文件名称 下载次数
1

MyProject_Sensor_1010.zip

30
工程成员
侵权投诉
相关工程
换一批
加载中...
添加到专辑 ×

加载中...

温馨提示 ×

是否需要添加此工程到专辑?

温馨提示
动态内容涉嫌违规
内容:
  • 153 6159 2675

服务时间

周一至周五 9:00~18:00
  • 技术支持

support
  • 开源平台公众号

MP