【环境仪扩展板】资料
一、项目与要求
基于梁山派的无线手持环境检测仪的设计
1.背景
我国目前的产业地区分布地域辽阔,地形复杂,导致工矿企业和乡镇企业分布很广,这给环境监测人员的监测工作带来很多的不便,环境监测人员不可能将大型的实验室检测设备运送至各处。尤其相当量的乡镇企业已经蓬勃兴起,但许多乡镇还没有具备检测的能力,在预防和治理的过程中有着很大的不便和患。便携式检测仪器的使用不仅可以减少环境式样在传输过程中的污染问题,减少样品固定和保存的繁杂手续,而且可以大大减少检测人员的工作量,实时掌握环境等动态变化趋势,从而尽可能地将潜在的风险降至低。因此需要设计一款基于梁山派的无线手持环境检测仪。
2.设计要求与指标
技术要求
- 温湿度传感器:用于检测温湿度数据;
- 气压传感器:用于检测大气压数据;
- 有害气体传感器:用于检测有害气体等 数据;
- 屏幕:用于实时显示采集到的设备;
- 无线通信:用于将数据发送至接收设备使用;
- 手持:小巧、使用电池,脱离供电线的困扰;
技术指标
- 测量温湿度、气压、有害气体等数据;
- 屏幕通过LVGL开源GUI库实时显示测量结果;
- 使用电池可以放电充电;
- 无线通信方式二选一:1.实现NBIOT将数据传输到云端;2.将数据通过无线模块发送出去给接收设备使用;
二、硬件设计
1. 充电升压电路设计
1.1 充电升压芯片介绍
根据项目需求,我们需要设计一个小巧、使用电池的环境仪。而使用电池就要考虑到充电与放电。本案例采用的是TP5400 锂电池充电和升压控制芯片。
TP5400 为一款移动电源专用的单节锂离子电池充电器和恒定 5V 升压控制器,充电部分集高精度电压和充电电流调节器、预充、充电状态指示和充电截止等功能于一体, 可以输出最大 1A 充电电流。而升压电路采用CMOS 工艺制造的空载电流极低的 VFM 开关型DC/DC 升压转换器。其具有极低的空载功耗(小于10uA),且升压输出驱动电流能力能达到 1A。无需外部按键,可以即插即用。
文件下载
1.2 应用电路设计
TP5400的数据手册中的参考电路如下:
各引脚说明
- VOUT(引脚 1):输出电压检测引脚。 连接升压 5V 输出端。
- CHRG(引脚 2):充电中漏极开路输出的充电状态指示端。当充电器向电池充电时,CHRG 管脚被内部开关拉到低电平,表示充电正在进行;否则 CHRG 管脚处于高阻态。
- PROG(引脚 3):充电电流设定、充电电流监控和停机引脚。在该引脚与地之间连接一 个精度为 1%的电阻器 Rprog可以设定充电电流(典型电路中的1.1K则设置为1000mA的充电电流)。当在恒定电流模式下进行充电时,引 脚的电压被维持在1V。PROG 引脚还可用来关断充电器。将设定电阻器与地断接,内部一个 2.5μA 电流将 PROG 引脚拉至高电平。当该引脚的电压达到 2.7V 的停机门限电压时,充电器进入停机模式,充电停止且输入电源电流降至 40μA。重新将Rprog与地相连将使充电器恢复正常操作状态。
- TDBY (引脚 4):电池充电完成指示端。 当电池充电完成时 STDBY 被内部开关拉到低电平,表示充电完成。除此之外, STDBY 管脚将处于高阻态。
- VCC(引脚 5):充电器输入电源电压。充电输入电源引脚。典型值 5V,并应通过至少 一个 10μF 电容器进行旁路。当 VCC 降至 BAT 引脚电压的 30mV 以内,TP5400 充电部分进入停机模式。
- BAT(引脚 6):充电电流输出。该引脚向电池提供充电电流并将最终浮充电压调节至4.2V。该引脚的一个精准内部电阻分压器设定浮充电压,在停机模式中,该内部电阻分压器断开,升压模式下内部工作电源。
- GND(引脚 7):地
- LX(引脚 8):升压电路内部功率管输出端。
根据参考电路,得出以下应用电路:
电池电压为 BAT+,Type-C充电电压为VCCIN,VOUT为5V输出电压。
该电路是我们整个设备的供电部分,所以该用电容的地方,建议都不要省。实际应用电路中,STDBY引脚未使用,该引脚为充电完成指示引脚,充电的时候我们亮一个灯(CHRG),表示正在充电;充电完成(STDBY)我们可以什么都不做,毕竟充电灯都不亮了,代表已经充满电了。
2. 温湿度传感器设计
2.1 温湿度传感器介绍
温湿度传感器是用于测量环境温度和相对湿度的设备。它通常由传感元件、信号处理电路和输出接口组成。
传感元件常用的技术包括热敏电阻、电容式湿度传感器、半导体传感器等。热敏电阻是基于材料的温度敏感性来测量温度的,而电容式湿度传感器则利用湿度与电容之间的关系进行测量。半导体传感器既可以测量温度,又可以测量湿度,是一种多功能传感器。
信号处理电路用于将传感元件获取的信号进行放大、滤波、线性化等处理,确保传感器输出的准确和稳定性。
输出接口可以是模拟信号接口或数字信号接口,用于将传感器测量的温湿度数据传输给其他设备进行进一步处理或显示。
本案例采用的是AHT21温湿度传感器。AHT21作为新一代温湿度传感器,在尺寸与性能方面建立了新的标准:它嵌入了适于回流焊的双列扁平无引脚SMD封装,底面3x3mm ,高度0 . 8 mm。传感器输出经过标定的数字信号,标准 I2C 格式。
2.2 应用电路设计
我们要设计其电路,需要了解其电气特性。见下图:
输入电压为2.0~5.5V,通信采用的是IIC接口。
数据手册中,提供了两种典型应用电路。
参照数据手册中的典型电路,得出以下我们实际使用的应用电路。使用开发板的PD3作为IIC接口的SCL接口,PD6作为IIC接口的SDA接口。
3. 气压传感器设计
3.1 气压传感器介绍
气压传感器是一种用于测量大气压力的设备。它通过传感元件将大气压力转化为电信号,并经过信号处理电路进行放大、滤波和线性化处理,最终输出与气压相关的数字或模拟信号。
传感元件常用的技术包括压阻式传感器(如压电传感器和电阻式传感器)、半导体式传感器和电容式传感器等。
- 压阻式传感器:压电传感器基于压电效应,当外加压力变化时会产生电荷或电压变化;电阻式传感器则基于金属材料的电阻值与受到的压力之间的关系。这两种传感器通过测量电荷或电阻值的变化来间接测量气压。
- 半导体式传感器:半导体式传感器利用半导体材料的电阻与温度和压力之间的关系。当外界气压变化时,半导体材料的电阻值会发生变化,通过测量电阻的变化来判断气压。
- 电容式传感器:电容式传感器利用电容与间隙大小和介质介电常数之间的关系,通过测量电容的变化来推断气压的变化。它可以直接测量气压,精度较高。
气压传感器广泛应用于气象观测、气候研究、空气质量监测、飞行器导航和高度测量、气压控制系统等领域。 它们能够提供实时和精确的气压数据,帮助人们了解和预测气象变化以及实现精确的高度测量和控制。
本案例采用的是数字防水气压传感器 WF183D。WF183D是一颗经济型数字压力温度传感器内部包含一个MEMS压力传感器和一个高分辨率 24位△∑ADC及DSP。WF183D通过UART提供高精度已校准压力和温度数字输出,通讯连接非常简单。
主要特点
- 数字压力温度直接读取
- 工作电压: 2.4V~3.6V
- 压力量程: 0~180kPa(绝压)
- 工作电流: 1.5mA
- 待机电功耗: < 2uA
其他说明
- 产品出厂前已完成压力温度校准,可以即插即用,无需客户再生产校准。
- 并且采用UART通信,对MCU要求更低,降低客户整机成本。
- WF183D 防水等级达到I P65 ,满足大部分防水产品要求。
3.2 应用电路设计
数据手册中,提供了该传感器的典型电路:
因传感器采用的是串口通信方式,我们需要确定使用开发板的哪一个串口与之通信。查看GD32F470数据手册,最终本案例采用的是串口1(TX=PA2,RX=PA3)。
使用应用电路如下:
4. 有害气体传感器设计
4.1 有害气体传感器介绍
有害气体传感器是一种用于检测和监测环境中存在的有害气体浓度的设备。它们广泛应用于工业安全、室内空气质量监测、环境污染监测等领域。
有害气体传感器可以检测和测量多种常见的有害气体,包括但不限于一氧化碳 (CO)、二氧化碳 (CO2)、甲醛(HCHO)、氨气 (NH3)、氢气 (H2)、硫化氢 (H2S)、苯 (C6H6)、氮氧化物 (NOx)、臭氧 (O3) 等。
传感器的工作原理因传感器类型和所检测的有害气体而异。常见的传感器技术包括化学传感器、电化学传感器、红外传感器和半导体传感器等。
- 化学传感器:化学传感器使用特定的化学反应来检测有害气体。传感器中通常含有能与目标气体发生特定反应的感敏层,当目标气体存在时,感敏层的电阻、电容、颜色或光学特性等会发生变化,通过测量这种变化来确定气体浓度。
- 电化学传感器:电化学传感器基于气体与电极之间的电化学反应来测量气体浓度。传感器中通常含有与目标气体相互作用的电极,当目标气体进入传感器时,会产生电化学反应,并产生特定的电流或电势变化,从而实现测量。
- 红外传感器:红外传感器利用气体特定的红外吸收特性来检测气体浓度。传感器发射红外辐射进入气体样本,通过测量透射或吸收的红外光来获得目标气体的浓度信息。
- 半导体传感器:半导体传感器利用半导体材料在目标气体存在时其电学性质发生变化的原理来检测气体浓度。当目标气体进入传感器时,半导体材料表面的电阻或电导率会发生变化,从而测量气体浓度。
有害气体传感器的准确性、响应时间、灵敏度和稳定性是选择传感器时需要考虑的重要因素。根据具体应用需求,传感器可以单独使用或与监测系统相结合,用于及时警报、数据记录和远程监测等。
本案例采用的是AGS10TVOC传感器,AGS10是一款采用数字信号输出的MEMS TVOC传感器。配置了专用的数字模块采集技术和气体感应传感技术,确保了产品具有极高的可靠性与卓越的长期稳定性,同时具有低功耗、高灵敏度、快速响应、成本低、驱动电路简单等特点。
AGS10主要适用于侦测各类有机挥发性气体,如乙醇、氨气、硫化物、苯系蒸汽和其它有害气体,可应用在空气净化器、家用电器、新风机等设备。
传感器特性
传感器采用标准IIC通信协议,适应多种设备。IIC的物理接口包含串行数据信号(SDA)与串行时钟信号(SCL)两个接口。设计时两个接口需通过1kΩ~10kΩ电阻上拉至VDD。
4.2 应用电路设计
传感器的工作电压在3V,而我们的电压只有5V与3.3V,可以在传感器的VCC引脚处串联一个肖特基二极管。
普通二极管的电压压降在0.6V-1.7V之间,而肖特基二极管的电压降通常在0.15V-0.45V之间。
气体传感器采用的也是IIC接口,我们知道IIC协议是允许多个设备共用一条IIC总线的,只是需要多个设备的设备地址不同。这里的IIC接口,连接的是温湿度传感器的IIC接口,这两者传感器的器件地址并不相同,因此可以直接使用同一个IIC接口。
实际应用电路如下:
5. 电量测量设计
电池的最大电压为4.2V,最小工作电压3.2V(要根据具体情况确定)。
最小工作电压要根据具体情况确定,可能3.4V就无法工作了
因为GD32的IO口最大可以兼容5V,超过5V就会把IO口烧坏;而测量电压的外设ADC,它的参考电压在立创·梁山派开发板上是3.3V。所以如果使用ADC直接测量电池的电压,那么它无法测量3.3V以上的电压。
电量测量的电路原理图如下。
为了解决这个问题,我们可以通过电阻分压的形式测量电池电压。电阻的大小可以根据IO口电平计算,分压后不要超过IO口容忍的电压的即可,选取一个合适的值。这里我们选择最常用的10K电阻进行分压。我们可以根据电阻分压公式计算分压后的电压:
其中 【R】 是电量测量接口图中的R7,10K;【R总】 是R6+R7为20K; 【U源】 是 BAT+,即电池电压。那么当电池满电为4.2V时,两个电阻分压的结果U为:
2.1V是完全符合我们的ADC测量范围。我们再计算当电池要没有电为3.2V时,两个电阻分压的结果U为:
6. 屏幕接口设计
6.1 屏幕介绍
屏幕采用了SPI通讯的240*280像素的1.69寸IPS高清圆角屏幕。 1.69寸屏幕是一种常见的小尺寸显示屏,它指的是屏幕的对角线尺寸为1.69英寸(约4.29厘米)。尽管它相对较小,但在某些应用领域中仍然具有广泛的用途和功能。该屏幕因为单位像素密度更高(总像素/尺寸),所以显示画质更精细,并且控制IO少,外围电路也相对简单。
6.2 应用电路设计
该屏幕使用的是SPI通信方式,为了达到更快的刷屏效果,我们在选择连接开发板的引脚时,可以连接到硬件SPI引脚上。本案例连接的是PB3(SPI0_SCK)PB5(SPI0_MOSI)。
7. 2.4G无线通信接口设计
7.1 无线通信模块介绍
本案例采用的是由安信可科技设计的NF-03无线模块。NF-03 是一款 5mW 功率的无线收发一体的 2.4G 模块,2.4G模块是一种用于无线通信的模块,它能够在2.4GHz频段进行无线数据传输和接收。该模块通常由无线收发器和相关的控制电路组成,为用户提供方便的无线通信解决方案。
购买链接
文件下载
这种模块通常具有以下特点和功能:
- 2.4GHz频段:2.4GHz频段是一种常用的无线通信频段,具有较好的穿透能力和较远的传输距离,适用于各种无线通信应用。
- 收发一体设计:收发一体设计使得模块能够同时实现数据的发送和接收功能,提供了更方便的无线通信解决方案。
- 高速数据传输:2.4G无线收发一体模块通常具有较高的数据传输速率,可以满足大多数应用的需求,如传输音频、视频、图像等。
- 低功耗设计:为了延长电池使用寿命或减少电源消耗,这种模块通常采用低功耗的设计,以提供更长的使用时间。
- 简单易用:2.4G无线收发一体模块通常提供简单易用的接口和协议,方便用户进行配置和控制。
- 多种应用领域:这种模块可以广泛应用于无线遥控、无线数据传输、无线传感器网络等领域,为各种设备和系统提供稳定可靠的无线通信能力。
7.2 应用电路设计
模块引脚如下:
说明:
- CE 可以长期接高电平,但是模块写寄存器的时候必须设置为掉电模式,建议 CE 脚连接单片机的 GPIO 口;
- IRQ 可不接,可采用SPI 查询方式获取 STATUS 寄存器的中断状态。但建议使用单片机的硬件外部中断,让 IRQ 接单片机外部触发引脚,触发单片机中断;
- 注意接地良好,有大面积的铺地,电源纹波小,应增加滤波电容并尽量靠近模块 VCC 与 GND;
数据手册中的参考电路如下:
实际应用电路如下:
该模块使用的是SPI通信方式,为了使通信速度更快,我们在选择连接开发板的引脚时,可以连接到硬件SPI引脚上。本案例连接的是PG11(SPI3_SCK)、PG12(SPI3_MISO)、PG13(MOSI)。
8. NBIOT模块接口设计
8.1 NBIOT模块介绍
NBIoT(Narrowband Internet of Things)模块是一种专门用于窄带物联网通信的硬件模块,它提供了一种省电、低成本、远距离通信的解决方案,适用于物联网(IoT)设备和应用。
以下是NBIoT模块的一些主要特点和功能:
- 窄带通信:NBIoT模块采用窄带通信技术,其优势是具有较低的功耗和较长的通信距离。窄带通信技术可以有效地解决物联网设备面临的能耗和覆盖范围限制的问题。
- 高覆盖能力:NBIoT模块能够在复杂的环境中提供广泛的覆盖能力,包括室内、室外和地下等。它可以穿过墙壁和障碍物,实现远距离通信。
- 低功耗设计:NBIoT模块的设计注重功耗的优化,以延长设备的电池寿命。它采用了低功耗模式,只在数据传输时才启动无线模块,其他时候处于休眠状态。
- 数据传输安全性:NBIoT模块提供了数据传输的安全性保证。它支持加密和身份验证等安全机制,确保设备和通信数据的安全性。
- 广泛应用:NBIoT模块可以应用于各种物联网设备,如智能城市、智能农业、智能家居、智能能源管理、智能交通等领域。它为这些应用提供了长距离、低功耗和低成本的通信解决方案。
- 兼容性:NBIoT模块通常具有良好的兼容性,可以与其他设备和系统进行集成。它支持标准的NBIoT通信协议,与现有的网络和平台进行连接和交互。
需要注意的是使用NBIOT模块,需要使用到物联网卡作为联网的网络来源。
本案例使用的是EC-01F。EC-01F 是安信可科技开发的一款 NBIOT 模组。其中 NB 部分采用的主芯片方案为 EC616S。该 芯片具备超高集成度的 NB-IoT SoC、支持超低功耗、完全支持 3GPP Rel14 NB-IoT 标准, 是一款超高性价比的 NB-IoT 芯片。
安信可科技NBIOT模块资料中心:模块资料中心
模块购买链接:https://detail.tmall.com/item.htm?_u=o2t4uge5816e&id=659492575663&spm=a1z09.2.0.0.1f6f2e8dwxojm9
8.2 应用电路设计
在模块的规格书中,并没有完整的模块应用电路,但是在资料中心,官方有设计了一款基于EC-01F的开发板,并将原理图开源。
我们舍弃一些我们不需要的部分,重新绘制。我们只需要EC-01F、SIM卡槽、天线座即可。关于模块的电源部分要求有至少500mA的电流。我们立创·梁山派开发板上的3.3V是由DC-DC电路得来,完全符合电源要求。
以下为实际应用电路设计:
说明
USIM卡部分的22Ω 与 33pF为必须需要,不然无法识别! 其余请参考规格书说明。
三、软件设计
1. 屏幕显示驱动
一般我们拿到一个屏幕首先需要移植厂商提供的官方代码进行亮屏测试。该款屏幕的硬件SPI与软件SPI移植步骤,已在立创开发板的模块移植手册中。欢迎大家查看1.4章节 的1.69寸彩屏。
移植好代码后要仔细观察颜色刷新是否正确(色差),显示方法是否正确(横屏竖屏),颜色数据刷新是否正常(错,漏,闪烁)。设计连线符合器件要求无误的情况下如果存在问题,就需要查看手册,通过手册发现 GD32F470的SPI存在FIFO,所以发送数据的结尾需要等待数据发送完成再释放片选。否则容易导致屏幕显示出现问题(一般都是以缓冲区是否为空当作传输的判断标准,以此提高发送效率)。
1.1 使用DMA进行SPI刷屏
使用DMA的通用流程都是先配置外设对应的DMA及通道,再配置自动或者软件触发DMA搬运方向。
不过,我们需要根据屏幕的像素确定要传输的数据量,DMA的最大数据传输量为65535。而案例使用的是1.69寸屏幕,像素为240x280,即全部像素为240 * 280 = 67200,已经超过了最大DMA传输量。所以将全部像素数据分两次进行传输,即67200 / 2 = 33600。
但是,我将DMA的数据宽度设置为了8位,即一次传输8位的数据。而我使用的1.69寸屏,是TFT彩屏,一个像素点需要16位的彩色数据。DMA设置为8位传输,而屏幕一个像素是16位的数据,故实际的传输数据量为全部像素大小*2!即67200 * 2=134400。所以我们想要显示一帧图像,需要传输4次!
DMA通道的说明
案例中的硬件SPI使用的是SPI0(需要根据使用引脚确定的),根据数据手册中关于DMA的请求表可以知道,要想使用DMA搬运硬件SPI0的数据,只可以通过DMA1的通道3,或者通道5。案例中选择的是DMA1的通道3。
void dma_spi_init(void)
{
dma_single_data_parameter_struct dma_init_struct;
/* 使能DMA1时钟 */
rcu_periph_clock_enable(RCU_DMA1);
/* 初始化DMA1的通道3 */
dma_deinit(DMA1, DMA_CH3);
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_CH3, &dma_init_struct); //将以上参数填入DMA1的通道3
// 禁用DMA循环模式
dma_circulation_disable(DMA1, DMA_CH3);
// DMA通道外设选择 011
dma_channel_subperipheral_select(DMA1, DMA_CH3, DMA_SUBPERI3);
// 启用DMA1传输完成中断
dma_interrupt_enable(DMA1, DMA_CH3, DMA_CHXCTL_FTFIE);
// 配置中断优先级(必须为最高)
nvic_irq_enable(DMA1_Channel3_IRQn, 0, 0);
// 失能DMA1的通道3
dma_channel_disable(DMA1, DMA_CH3);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
要使用硬件SPI的DMA功能,需要开启DMA使能。其中又分为发送使能与接收使能。这里我们只需要将屏幕数据搬运到屏幕里,使用使能的是硬件SPI的DMA发送使能。(具体其他参数,请见实物验证章节的代码工程文件)
void lcd_gpio_config(void)
{
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_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);
/* 配置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();
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
DMA和SPI初始化配置后,需要再次传输的快捷操作为:清除标志位,设置传输量,设置传输地址,开始传输。
关于清除全部中断标志位的解释
因为我使用到的硬件SPI的是DMA1的通道3,实际上只需要往DMA_INTC0寄存器写入( 0x2f << 22)即可。我为了方便,直接将整个寄存器可以设置的地方全部置1了。
关于传输地址的说明
一个全屏缓存需要240 * 280=67200字节,我们可以先将缓存内的数据修改为需要显示的内容,在统一更新在屏幕里面了。最简单的方法是定义一个67200大小的数组,将我们要更新的数据写入,然后一次性发送给屏幕,相当于图片刷新;
uint16_t Show_Gram[ 67200];
但是我们的梁山派内部RAM有限,需要留给其他需要高速访问的RAM变量使用。所以这里我们使用梁山派上外置的SDRAM(32MB)。
在main.c中,初始化时,写入以下代码:(记得导入头文件)
//初始化SDRAM设备0内存(具体初始化代码请见文章下面的工程)
exmc_synchronous_dynamic_ram_init(EXMC_SDRAM_DEVICE0); //sram初始化
2
定义一个使用SDRAM内存的LCD显示缓冲数组:
为了方便后续的DMA传输,这里定义的SRAM地址一定要4字节对齐( __align(32) )
//LCD显示缓冲区
__align(32) uint16_t Show_Gram[LCD_RAM_NUMBER] __attribute__((at(SDRAM_DEVICE0_ADDR)));
2
完整的配置请看以下代码:
//开始显示
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_CH3) = 33600;
//设置传输地址
DMA_CH3M0ADDR(DMA1) = (uint32_t)Show_Gram;
//开始传输
DMA_CHCTL(DMA1, DMA_CH3) |= DMA_CHXCTL_CHEN;
}
//屏幕数据DMA搬运完成中断
void DMA1_Channel3_IRQHandler(void)
{
//搬运次数 (必须设置为静态)
static uint8_t Show_Number=0;
//全屏幕需要搬运4次
if((++Show_Number) < 4)
{
//清除全部DMA1中断标志位
DMA_INTC0(DMA1) = 0xfffffff;
//重新填充要搬运的数据量
DMA_CHCNT(DMA1, DMA_CH3) = 33600;
//内存搬运地址
DMA_CH3M0ADDR(DMA1) = (uint32_t)Show_Gram+33600*Show_Number;
//开启搬运
DMA_CHCTL(DMA1, DMA_CH3) |= DMA_CHXCTL_CHEN;
}
else //如果4次全部搬运完成
{
//清除DMA搬运完成中断标志位
dma_interrupt_flag_clear(DMA1, DMA_CH3, DMA_INT_FLAG_FTF);
//等待SPI发送完毕
while(SPI_STAT(PORT_SPI) & SPI_STAT_TRANS);
//SPI片选拉高
LCD_CS_Set();
//搬运次数清零
Show_Number=0;
//一帧搬运完成标志位
show_over_flag=0;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
理论上将SPI的刷屏交给DMA已经做到了有效的提高CPU利用率,但是在不同的循环里面进行刷屏会有不稳定刷屏撕裂现象(MCU屏带显存,数据传输给屏幕主控,屏幕主控也需要处理后再更新显示。本身和SPI的通信时长也 有原因。RGB屏使用的单片机RAM作为显存,可以保证同步)。这种情况下我们使用定时器周期的查询是否更新屏幕,保证屏幕刷屏符合固定周期,减少不同时差刷新导致画面撕裂不同步现象。(不严重,也可以不使用定时器)
//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);
}
//显示帧刷新中断 50ms
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();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
在定时器的中断函数里面,需要保证刷新RAM和操作RAM分离,这样才能保证显示的画面和实际画面的帧同步。而这个读写标志Show_AB也是判断当前操作的唯一指标。
1.2 修改显示函数
目前我们工程中采用的LCD画点函数,是通过硬件SPI一个一个像素的去控制颜色显示。(lcdgui.c)
我们要将其修改为要填充的BUFF,达到显示目的。
/******************************************************************
* 函 数 名 称:LCD_Fill
* 函 数 说 明:在指定区域填充颜色
* 函 数 形 参:xsta,ysta 起始坐标
xend,yend 终止坐标
color 要填充的颜色
* 函 数 返 回:无
* 作 者: LC
* 备 注:无
******************************************************************/
void LCD_Fill(uint16_t xsta,uint16_t ysta,uint16_t xend,uint16_t yend,uint16_t color)
{
uint16_t i,j;
for( i = ysta; i < yend; i++ )
{
for(j=xsta;j<xend;j++)
{
Show_Gram[((LCD_W * i) + j)] = color;
}
}
}
/******************************************************************
* 函 数 名 称:LCD_DrawPoint
* 函 数 说 明:在指定位置画点
* 函 数 形 参:x,y 画点坐标
color 点的颜色
* 函 数 返 回:无
* 作 者:LC
* 备 注:无
******************************************************************/
void LCD_DrawPoint(uint16_t x,uint16_t y,uint16_t color)
{
Show_Gram[((LCD_W * y) + x)] = color;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
写汉字、写字符、写图片等函数中,使用到了SPI画点的函数 LCD_WR_DATA
,都要全部改为我们修改之后的画点函数。(其余字体大小的汉字显示函数修改同下)
字符显示函数修改如下:
图片显示函数修改如下:
1.3 实物验证
/********************************************************************************
* 测试硬件:立创·梁山派开发板GD32F470ZGT6 使用主频200Mhz 晶振25Mhz
* 版 本 号: V1.0
* 修改作者: LC
* 修改日期: 2023年06月12日
* 功能介绍: 增加GD32的DMA/SDRAM/EXMC/TIMER标准库
******************************************************************************
* 梁山派软硬件资料与相关扩展板软硬件资料官网全部开源
* 开发板官网:www.lckfb.com
* 技术支持常驻论坛,任何技术问题欢迎随时交流学习
* 立创论坛:club.szlcsc.com
* 其余模块移植手册:【立创·梁山派开发板】模块移植手册
* 关注bilibili账号:【立创开发板】,掌握我们的最新动态!
* 不靠卖板赚钱,以培养中国工程师为己任
*********************************************************************************/
#include "gd32f4xx.h"
#include "systick.h"
#include "bsp_led.h"
#include "lcdinit.h"
#include "lcdgui.h"
#include "bsp_usart.h"
#include "stdio.h"
#include "string.h"
uint16_t color_buff[7] = {WHITE,BLACK,BLUE,RED,GREEN,YELLOW,GRAY};
/******************************************************************
* 函 数 名 称:main
* 函 数 说 明:主函数
* 函 数 形 参:无
* 函 数 返 回:无
* 作 者: LC
* 备 注:无
******************************************************************/
int main(void)
{
int LCD_Fill_Xend=20;
int rand_x = 0, rand_y = 0;
int circle_r = 0;
nvic_priority_group_set(NVIC_PRIGROUP_PRE2_SUB2); // 优先级分组
//滴答定时器初始化 1us
systick_config();
//sram初始化
exmc_synchronous_dynamic_ram_init(EXMC_SDRAM_DEVICE0);
//lcd初始化
LCD_Init();
//清除全屏为黑色
LCD_Fill(0,0,280,240,BLACK);
//开启显示
LCD_Show_Gram();
//等待一帧数据搬运完成
while(get_show_over_flag());
//开启定时器固定刷屏
Lcd_Show_Time_config();
//串口0初始化(调试)
usart_gpio_config( 115200U );
printf("start\r\n");
while(1)
{
//画矩形
LCD_Fill(0,0,LCD_Fill_Xend,20,RED);
LCD_Fill_Xend = ( LCD_Fill_Xend + 20 ) % 300;
//全屏随机画点
rand_x = rand() % 280;
rand_y = rand() % 240;
LCD_DrawPoint(rand_x, rand_y, WHITE);
//画矩形
LCD_DrawRectangle(30,30,280-30,240-30,WHITE);
//画中心圆
Draw_Circle(280/2,240/2,circle_r,YELLOW);
circle_r = ( circle_r + 2 ) % 50;
//显示中文汉字
LCD_ShowChinese12x12(40,35*1,"中",WHITE, BLACK, 12, 0);
LCD_ShowChinese16x16(40,35*2,"梁",WHITE, BLACK, 16, 0);
LCD_ShowChinese24x24(40,35*3,"电",WHITE, BLACK, 24, 0);
LCD_ShowChinese32x32(40,35*4,"山",WHITE, BLACK, 32, 0);
//显示字符串
LCD_ShowString(40,35*5,"welcome home",WHITE,BLACK,16,0);
//定时器循环固定数据刷屏
while(get_show_over_flag());
set_show_update_flag(1);
while(get_show_update_flag());
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
实物验证:
视频
文件下载
通过百度网盘分享的文件:1.硬件SPI+DMA屏幕驱动.zip
链接:https://pan.baidu.com/s/1EBJ8gLN3r3yfwQ_a3IiLvw?pwd=LCKF
提取码:LCKF
2. 温湿度传感器驱动
2.1 AHT21软件驱动
案例采用的是AHT21温湿度传感器,该传感器采用的是IIC接口进行通信,因此我们需要准备一套IIC通信的模板。
我们已在模块移植手册中进行移植,欢迎大家查看2.51章节 AHT21温湿度传感器。关于原理部分已在模块移植手册中进行说明,我们讲解如何移植。
将模块移植手册上的代码工程文件下载并且打开。AHT21的驱动代码,主要在bsp_aht21.c处。
引脚选择说明
因AHT20温湿度传感器采用的是标准的IIC接口,我们可以使用软件IIC或硬件IIC的方式与其通信。只要是通用输入输出(GPIO)的引脚就可以使用软件IIC,而硬件IIC需要使用到指定引脚才可使用。本案例采用的是软件IIC的方式,可以使用任意 可以用的引脚作为IIC接口。
在bsp_aht21.h
文件里,有对于IIC接口的宏定义,我们将其引脚修改为我们的原理图接入的引脚PD3/PD6。
2.2 实物验证
将我们修改引脚的工程下载到我们的开发板上验证。
采集失败说明:如果采集不到数据,可能是传感器没有焊接好,需要重新焊接。
接下来我们将温湿度数据,显示到我们扩展板的LCD上,LCD工程请参考上一章节的屏幕显示驱动。
- 将AHT21的代码文件复制到我们LCD工程的Hardware文件夹里,后面我们所有自己写的代码文件,都放在该文件夹下。
打开我们LCD的工程,添加温湿度的文件与路径。
添加文件:
添加文件路径:
main.c中编写如下:
/********************************************************************************
* 测试硬件:立创·梁山派开发板GD32F470ZGT6 使用主频200Mhz 晶振25Mhz
* 版 本 号: V1.0
* 修改作者: LC
* 修改日期: 2023年06月12日
* 功能介绍: 增加GD32的DMA/SDRAM/EXMC/TIMER标准库
******************************************************************************
* 梁山派软硬件资料与相关扩展板软硬件资料官网全部开源
* 开发板官网:www.lckfb.com
* 技术支持常驻论坛,任何技术问题欢迎随时交流学习
* 立创论坛:club.szlcsc.com
* 其余模块移植手册:【立创·梁山派开发板】模块移植手册
* 关注bilibili账号:【立创开发板】,掌握我们的最新动态!
* 不靠卖板赚钱,以培养中国工程师为己任
*********************************************************************************/
#include "gd32f4xx.h"
#include "systick.h"
#include "bsp_led.h"
#include "lcdinit.h"
#include "lcdgui.h"
#include "bsp_usart.h"
#include "stdio.h"
#include "string.h"
#include "bsp_aht21.h"
uint16_t color_buff[7] = {WHITE,BLACK,BLUE,RED,GREEN,YELLOW,GRAY};
/******************************************************************
* 函 数 名 称:main
* 函 数 说 明:主函数
* 函 数 形 参:无
* 函 数 返 回:无
* 作 者: LC
* 备 注:无
******************************************************************/
int main(void)
{
char disp_buf[200] = {0};
nvic_priority_group_set(NVIC_PRIGROUP_PRE2_SUB2); // 优先级分组
//滴答定时器初始化 1us
systick_config();
//sram初始化
exmc_synchronous_dynamic_ram_init(EXMC_SDRAM_DEVICE0);
//AHT21温湿度传感器初始化
aht21_gpio_init();
//串口0初始化(调试)
usart_gpio_config( 115200U );
printf("start\r\n");
//lcd初始化
LCD_Init();
//清除全屏为黑色
LCD_Fill(0,0,280,240,BLACK);
//开启显示
LCD_Show_Gram();
//等待一帧数据搬运完成
while(get_show_over_flag());
//开启定时器固定刷屏
Lcd_Show_Time_config();
while(1)
{
//读取温湿度数据
aht21_read_data();
//画矩形
LCD_DrawRectangle(30,30,280-30,240-30,WHITE);
//显示温度
sprintf(disp_buf, "TEMP = %.2f C ",get_temperature() );
LCD_ShowString(60, 240/2-16, (const uint8_t*)disp_buf, WHITE, BLACK, 16, 0);
//显示湿度
sprintf(disp_buf, "HUMI = %.2f %%RH ",get_humidity() );
LCD_ShowString(60, 240/2+16, (const uint8_t*)disp_buf, WHITE, BLACK, 16, 0);
//定时器循环固定刷屏
while(get_show_over_flag());
set_show_update_flag(1);
while(get_show_update_flag());
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
LCD显示验证:
视频
3. 有害气体传感器驱动
3.1 AGS10软件驱动
案例采用的是AGS10有害气体传感器,该传感器采用的是IIC接口进行通信,因此我们需要准备一套IIC通信的模板。
我们已在模块移植手册中进行移植,欢迎大家查看2.57章节 AGS10有害气体传感器。关于原理部分已在模块移植手册中进行说明,我们讲解如何移植。
将模块移植手册上的代码工程文件下载并且打开。AGS10的驱动代码,主要在bsp_ags10.c处。
引脚选择说明
因AGS10有害气体传感器采用的也是标准IIC接口,为了节省引脚,这里使用的IIC接口与上一章节的温湿度传感器一致。在bsp_ags10.h
文件里,有对于IIC接口的宏定义,我们将其引脚修改为我们的原理图接入的引脚PD3/PD6。
3.2 实物验证
将我们修改引脚的工程下载到我们的开发板上验证。
采集失败说明:
1.如果采集不到数据,可能是传感器没有焊接好,需要重新焊接。
2.不可频繁发送“数据采集”命令,否则会导致传感器无法正常采集数据,从而使得Status的状态位RDY位数值一直为1。“数据采集”命令间隔发送时间不可小于1.5s。
接下来我们将有害气体TVOC数据,显示到我们扩展板的LCD上,LCD工程请参考上一章节的温湿度采集驱动。
- 将AGS10的代码文件复制到我们LCD工程的Hardware文件夹里,后面我们所有自己写的代码文件,都放在该文件夹下。
打开我们LCD的工程,添加温湿度的文件与路径。
添加文件:
添加文件路径:
main.c中编写如下:
/********************************************************************************
* 测试硬件:立创·梁山派开发板GD32F470ZGT6 使用主频200Mhz 晶振25Mhz
* 版 本 号: V1.0
* 修改作者: LC
* 修改日期: 2023年06月12日
* 功能介绍: 增加GD32的DMA/SDRAM/EXMC/TIMER标准库
******************************************************************************
* 梁山派软硬件资料与相关扩展板软硬件资料官网全部开源
* 开发板官网:www.lckfb.com
* 技术支持常驻论坛,任何技术问题欢迎随时交流学习
* 立创论坛:club.szlcsc.com
* 其余模块移植手册:【立创·梁山派开发板】模块移植手册
* 关注bilibili账号:【立创开发板】,掌握我们的最新动态!
* 不靠卖板赚钱,以培养中国工程师为己任
*********************************************************************************/
#include "gd32f4xx.h"
#include "systick.h"
#include "bsp_led.h"
#include "lcdinit.h"
#include "lcdgui.h"
#include "bsp_usart.h"
#include "stdio.h"
#include "string.h"
#include "bsp_aht21.h"
#include "bsp_ags10.h"
uint16_t color_buff[7] = {WHITE,BLACK,BLUE,RED,GREEN,YELLOW,GRAY};
/******************************************************************
* 函 数 名 称:main
* 函 数 说 明:主函数
* 函 数 形 参:无
* 函 数 返 回:无
* 作 者: LC
* 备 注:无
******************************************************************/
int main(void)
{
char disp_buf[200] = {0};
int time_value = 0;
nvic_priority_group_set(NVIC_PRIGROUP_PRE2_SUB2); // 优先级分组
//滴答定时器初始化 1us
systick_config();
//sram初始化
exmc_synchronous_dynamic_ram_init(EXMC_SDRAM_DEVICE0);
//AHT21温湿度传感器初始化
aht21_gpio_init();
//有害气体传感器初始化
ags10_gpio_init();
//串口0初始化(调试)
usart_gpio_config( 115200U );
printf("start\r\n");
//lcd初始化
LCD_Init();
//清除全屏为黑色
LCD_Fill(0,0,280,240,BLACK);
//开启显示
LCD_Show_Gram();
//等待一帧数据搬运完成
while(get_show_over_flag());
//开启定时器固定刷屏
Lcd_Show_Time_config();
while(1)
{
//画矩形
LCD_DrawRectangle(30,30,280-30,240-30,WHITE);
//采集温湿度数据
aht21_read_data();
//显示温度
sprintf(disp_buf, "TEMP = %.2f C ",get_temperature() );
LCD_ShowString(60, 240/2-32, (const uint8_t*)disp_buf, WHITE, BLACK, 16, 0);
//显示湿度
sprintf(disp_buf, "HUMI = %.2f %%RH ",get_humidity() );
LCD_ShowString(60, 240/2, (const uint8_t*)disp_buf, WHITE, BLACK, 16, 0);
//因不可频繁采集数据,需要间隔一定时间
if( ( time_value++ ) >= 10 )
{
time_value = 0;
//显示有害气体浓度
sprintf(disp_buf, "TVOC = %d ppb ",ags10_read() );
LCD_ShowString(60, 240/2+32, (const uint8_t*)disp_buf, WHITE, BLACK, 16, 0);
}
//定时器循环固定刷屏
while(get_show_over_flag());
set_show_update_flag(1);
while(get_show_update_flag());
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
LCD显示验证:
视频
4. 气压传感器驱动
气压传感器采用的是数字防水型WF183D,它是一颗经济型数字压力温度传感器,内部包含一个MEMS压力传感器和一个高分辨率 24位△∑ADC及DSP。WF183D通过UART提供高精度已校准压力和温度数字输出,通讯连接非常简单。产品出厂前已完成压力温度校准,可以即插即用,无需客户再生产校准。采用UART通信,对MCU要求更低,降低客户整机成本。WF183D 防水等级达到IP65,满足大部分防水产品要求。
4.1 WF183D软件驱动
WF183D是通过串口进行通信,其默认的通信格式如下:
在硬件设计部分,我们将传感器的串口引脚接入了立创·梁山派开发板的PA2PA3(USART1)。所以在使用之前我们需要初始化引脚为串口通信,通信的配置根据WF183D数据手册的要求,配置如下:
#define BSP_WF183D_USART_TX_RCU RCU_GPIOA // 串口TX的端口时钟
#define BSP_WF183D_USART_RX_RCU RCU_GPIOA // 串口RX的端口时钟
#define BSP_WF183D_USART_RCU RCU_USART1 // 串口1的时钟
#define BSP_WF183D_USART_TX_PORT GPIOA // 串口TX的端口
#define BSP_WF183D_USART_RX_PORT GPIOA // 串口RX的端口
#define BSP_WF183D_USART_AF GPIO_AF_7 // 串口1的复用功能
#define BSP_WF183D_USART_TX_PIN GPIO_PIN_2 // 串口TX的引脚
#define BSP_WF183D_USART_RX_PIN GPIO_PIN_3 // 串口RX的引脚
#define BSP_WF183D_USART USART1 // 串口1
#define BSP_WF183D_USART_IRQ USART1_IRQn // 串口1中断
2
3
4
5
6
7
8
9
10
11
12
/**********************************************************
* 函 数 名 称:wf183d_gpio_config
* 函 数 功 能:wf183d气压传感器初始化
* 传 入 参 数:无
* 函 数 返 回:无
* 作 者:LCKFB
* 备 注:默认波特率9600
**********************************************************/
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); // 空闲检测中断
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
根据数据手册的说明,我们要读取气压数据之前,需要先让其获取温度,在获取气压。由于转换压力需要根据当前温度进行补偿,所以需要先进行采集转换温度。发送获取温度即可转换当前温度。
该传感器也是可以测量温度的
获取温度的步骤如下:
我们通过串口发送4位数据【0X55, 0X04, 0X0E, 0X6A】给传感器,即为获取温度值命令。传感器接收到命令之后就开始采集转换温度值。
/**********************************************************
* 函 数 名 称:wf183d_send_command
* 函 数 功 能:串口发送指定长度数据
* 传 入 参 数:ucstr:要发送的数据地址
* length:发送数据长度
* 函 数 返 回:无
* 作 者:LCKFB
* 备 注:无
**********************************************************/
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));
}
}
//温度采集命令
uint8_t get_TemperatureCmd_buff[4] = {0X55, 0X04, 0X0E, 0X6A};
//发送采集温度命令
wf183d_send_command( get_TemperatureCmd_buff, 4 );
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
数据的接收与解析,只需要判断串口是否有接收到数据,如果接收到数据,再判断第一位数据帧头是否为0XAA,如果不是则说明数据不正确,结束接收;如果是0XAA,则再判断CRC值是否正确。
/******************************************************************
* 函 数 名 称:FrameResolution
* 函 数 说 明:帧格式解析
* 函 数 形 参:buff:帧数据
* 函 数 返 回:1:帧头不对 2:校验不对 其他:气压数据
* 作 者:LC
* 备 注:无
******************************************************************/
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;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Cal_uart_buf_CRC
校验函数在数据手册中已经提供,代码如下:
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 ;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
完整的驱动代码如下:
bsp_wf183d.c
#include "bsp_wf183d.h"
//最大串口接收长度
#define DATA_LENGTH_MAX 200
uint8_t AirPressure_Data_Buff[DATA_LENGTH_MAX];
uint8_t AirPressure_data_length = 0;
uint8_t AirPressure_data_flag = 0;
/**********************************************************
* 函 数 名 称:wf183d_gpio_config
* 函 数 功 能:wf183d气压传感器初始化
* 传 入 参 数:无
* 函 数 返 回:无
* 作 者:LCKFB
* 备 注:默认波特率9600
**********************************************************/
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); // 空闲检测中断
}
/**********************************************************
* 函 数 名 称:wf183d_send_command
* 函 数 功 能:串口发送指定长度数据
* 传 入 参 数:ucstr:要发送的数据地址
* length:发送数据长度
* 函 数 返 回:无
* 作 者:LCKFB
* 备 注:无
**********************************************************/
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)); // 等待发送数据缓冲区标志置位
}
}
/**********************************************************
* 函 数 名 称:wf183d_data_clear
* 函 数 功 能:清除串口接收全部缓存
* 传 入 参 数:无
* 函 数 返 回:无
* 作 者:LCKFB
* 备 注:无
**********************************************************/
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 ;
}
/******************************************************************
* 函 数 名 称:FrameResolution
* 函 数 说 明:帧格式解析
* 函 数 形 参:buff:帧数据
* 函 数 返 回:1:帧头不对 2:校验不对 其他:气压数据
* 作 者:LCKFB
* 备 注:无
******************************************************************/
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;
}
/**********************************************************
* 函 数 名 称:USART1_IRQHandler
* 函 数 功 能:串口1中断服务函数
* 传 入 参 数:无
* 函 数 返 回:无
* 作 者:LCKFB
* 备 注:无
**********************************************************/
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;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
bsp_wf183d.h
/********************************************************************************
* 测试硬件:立创·梁山派开发板GD32F470ZGT6 使用主频200Mhz 晶振25Mhz
* 版 本 号: V1.0
* 修改作者: LCKFB
* 修改日期: 2023年08月04日
* 功能介绍:
******************************************************************************
* 梁山派软硬件资料与相关扩展板软硬件资料官网全部开源
* 开发板官网:www.lckfb.com
* 技术支持常驻论坛,任何技术问题欢迎随时交流学习
* 立创论坛:club.szlcsc.com
* 其余模块移植手册:https://dri8c0qdfb.feishu.cn/docx/EGRVdxunnohkrNxItYTcrwAnnHe
* 关注bilibili账号:【立创开发板】,掌握我们的最新动态!
* 不靠卖板赚钱,以培养中国工程师为己任
*********************************************************************************/
#ifndef _BSP_WF183D_H
#define _BSP_WF183D_H
#include "gd32f4xx.h"
#include "systick.h"
#define BSP_WF183D_USART_TX_RCU RCU_GPIOA // 串口TX的端口时钟
#define BSP_WF183D_USART_RX_RCU RCU_GPIOA // 串口RX的端口时钟
#define BSP_WF183D_USART_RCU RCU_USART1 // 串口1的时钟
#define BSP_WF183D_USART_TX_PORT GPIOA // 串口TX的端口
#define BSP_WF183D_USART_RX_PORT GPIOA // 串口RX的端口
#define BSP_WF183D_USART_AF GPIO_AF_7 // 串口1的复用功能
#define BSP_WF183D_USART_TX_PIN GPIO_PIN_2 // 串口TX的引脚
#define BSP_WF183D_USART_RX_PIN GPIO_PIN_3 // 串口RX的引脚
#define BSP_WF183D_USART USART1 // 串口1
#define BSP_WF183D_USART_IRQ USART1_IRQn // 串口1中断
void wf183d_gpio_config(void);
uint32_t GetAirPressureValue(void);
#endif
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
4.2 实物验证
我们将气压数据,显示到我们扩展板的LCD上,LCD工程请参考上一章节的有害气体传感器驱动。
将我们所编写的 bsp_wf183d.c
与 bsp_wf183d.h
,添加到我们LCD工程之中。
在main.c中编写代码如下:
/********************************************************************************
* 测试硬件:立创·梁山派开发板GD32F470ZGT6 使用主频200Mhz 晶振25Mhz
* 版 本 号: V1.0
* 修改作者: LC
* 修改日期: 2023年06月12日
* 功能介绍: 增加GD32的DMA/SDRAM/EXMC/TIMER标准库
******************************************************************************
* 梁山派软硬件资料与相关扩展板软硬件资料官网全部开源
* 开发板官网:www.lckfb.com
* 技术支持常驻论坛,任何技术问题欢迎随时交流学习
* 立创论坛:club.szlcsc.com
* 其余模块移植手册:【立创·梁山派开发板】模块移植手册
* 关注bilibili账号:【立创开发板】,掌握我们的最新动态!
* 不靠卖板赚钱,以培养中国工程师为己任
*********************************************************************************/
#include "gd32f4xx.h"
#include "systick.h"
#include "bsp_led.h"
#include "lcdinit.h"
#include "lcdgui.h"
#include "bsp_usart.h"
#include "stdio.h"
#include "string.h"
#include "bsp_aht21.h"
#include "bsp_ags10.h"
#include "bsp_wf183d.h"
uint16_t color_buff[7] = {WHITE,BLACK,BLUE,RED,GREEN,YELLOW,GRAY};
/******************************************************************
* 函 数 名 称:main
* 函 数 说 明:主函数
* 函 数 形 参:无
* 函 数 返 回:无
* 作 者: LC
* 备 注:无
******************************************************************/
int main(void)
{
char disp_buf[200] = {0};
int time_value = 0;
nvic_priority_group_set(NVIC_PRIGROUP_PRE2_SUB2); // 优先级分组
//滴答定时器初始化 1us
systick_config();
//sram初始化
exmc_synchronous_dynamic_ram_init(EXMC_SDRAM_DEVICE0);
//AHT21温湿度传感器初始化
aht21_gpio_init();
//有害气体传感器初始化
ags10_gpio_init();
//气压传感器初始化
wf183d_gpio_config();
//串口0初始化(调试)
usart_gpio_config( 115200U );
printf("start\r\n");
//lcd初始化
LCD_Init();
//清除全屏为黑色
LCD_Fill(0,0,280,240,BLACK);
//开启显示
LCD_Show_Gram();
//等待一帧数据搬运完成
while(get_show_over_flag());
//开启定时器固定刷屏
Lcd_Show_Time_config();
while(1)
{
//画矩形
LCD_DrawRectangle(30,30,280-30,240-30,WHITE);
//采集温湿度数据
aht21_read_data();
//显示温度
sprintf(disp_buf, "TEMP = %.2f C ",get_temperature() );
LCD_ShowString(60, 240/2-64, (const uint8_t*)disp_buf, WHITE, BLACK, 16, 0);
//显示湿度
sprintf(disp_buf, "HUMI = %.2f %%RH ",get_humidity() );
LCD_ShowString(60, 240/2-32, (const uint8_t*)disp_buf, WHITE, BLACK, 16, 0);
//因有害气体传感器不可频繁采集数据,需要间隔一定时间
if( ( time_value++ ) >= 10 )
{
time_value = 0;
//显示有害气体浓度
sprintf(disp_buf, "TVOC = %d ppb ",ags10_read() );
LCD_ShowString(60, 240/2, (const uint8_t*)disp_buf, WHITE, BLACK, 16, 0);
}
//显示气压
sprintf(disp_buf, "pressure = %d PA ",GetAirPressureValue() );
LCD_ShowString(60, 240/2+32, (const uint8_t*)disp_buf, WHITE, BLACK, 16, 0);
//定时器循环固定刷屏
while(get_show_over_flag());
set_show_update_flag(1);
while(get_show_update_flag());
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
视频验证
视频
5. 电源电量采集
在硬件设计部分时,关于电源电量有进行过说明。那么当电池满电为4.2V时,两个电阻分压的结果U为2.1V;
当电池要没有电为3.2V时,两个电阻分压的结果U为1.6V。
接下来通过计算给大家实际演示如何将ADC值与电压值进行转换。
-- 前提:假设ADC是12位的(采集到的最大值为4095),电池供电最高4.2V,最低3.2V; 引脚采集到的电压 = 采集到的值 / ( 2的12次方-1 )* 参考电压
2的12次方-1 = 4095 参考电压=3.3V
-------知道ADC值求电压------- 先采集ADC值,得到 【adc_value】;
adc_value = adc_regular_data_read(ADC0);
通过ADC值换算出引脚采集到的电压【voltage】;(引脚电压处于分压状态)
voltage = adc_value / 4095.0 * 3.3;
实际电池电压【virtual voltage】= 采集到的电压【voltage】/ 电压比例【0.5】
//换算实际电池电压 : 采集到的电压 / 0.5(电压比例)
printf("virtual voltage = %f\r\n",voltage / 0.5);
2
5.1 电量测量驱动
新建两个文件,bsp_voltage.c
与bsp_voltage.h
。将它们保存到工程之中,工程参考上一章节的代码工程文件 4-气压传感器驱动。 在bsp_voltage.c
中编写以下代码:
/********************************************************************************
* 测试硬件:立创·梁山派开发板GD32F470ZGT6 使用主频200Mhz 晶振25Mhz
* 版 本 号: V1.0
* 修改作者: LCKFB
* 修改日期: 2023年08月04日
* 功能介绍:
******************************************************************************
* 梁山派软硬件资料与相关扩展板软硬件资料官网全部开源
* 开发板官网:www.lckfb.com
* 技术支持常驻论坛,任何技术问题欢迎随时交流学习
* 立创论坛:club.szlcsc.com
* 其余模块移植手册:https://dri8c0qdfb.feishu.cn/docx/EGRVdxunnohkrNxItYTcrwAnnHe
* 关注bilibili账号:【立创开发板】,掌握我们的最新动态!
* 不靠卖板赚钱,以培养中国工程师为己任
*********************************************************************************/
#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_GPIOC);
//使能ADC时钟
rcu_periph_clock_enable(RCU_ADC0);
//配置ADC时钟
adc_clock_config(ADC_ADCCK_HCLK_DIV5);
//配置引脚为模拟输入模式
gpio_mode_set(GPIOC, GPIO_MODE_ANALOG, GPIO_PUPD_NONE, GPIO_PIN_3);
//配置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
* 备 注:PC3 = ADC_CHANNEL_13
**********************************************************/
float get_voltage_value(void)
{
unsigned int adc_value = 0;
float voltage = 0;
//设置采集通道
adc_regular_channel_config(ADC0, 0, ADC_CHANNEL_13, 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);
//电压百分比 = 当前值 / 总值 * 100
voltage = (voltage-1.6) / 0.5 * 100.0;
// printf("Percentage voltage = %f\r\n",voltage);
//返回电压百分比
return voltage;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
在bsp_voltage.h中编写以下代码:
/********************************************************************************
* 测试硬件:立创·梁山派开发板GD32F470ZGT6 使用主频200Mhz 晶振25Mhz
* 版 本 号: V1.0
* 修改作者: LCKFB
* 修改日期: 2023年08月04日
* 功能介绍:
******************************************************************************
* 梁山派软硬件资料与相关扩展板软硬件资料官网全部开源
* 开发板官网:www.lckfb.com
* 技术支持常驻论坛,任何技术问题欢迎随时交流学习
* 立创论坛:club.szlcsc.com
* 其余模块移植手册:https://dri8c0qdfb.feishu.cn/docx/EGRVdxunnohkrNxItYTcrwAnnHe
* 关注bilibili账号:【立创开发板】,掌握我们的最新动态!
* 不靠卖板赚钱,以培养中国工程师为己任
*********************************************************************************/
#ifndef _BSP_VOLTAGE_H__
#define _BSP_VOLTAGE_H__
#include "gd32f4xx.h"
void power_voltage_gpio_config(void);
float get_voltage_value(void);
#endif
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
5.2 实物验证
在main.c中编写以下代码:
/********************************************************************************
* 测试硬件:立创·梁山派开发板GD32F470ZGT6 使用主频200Mhz 晶振25Mhz
* 版 本 号: V1.0
* 修改作者: LC
* 修改日期: 2023年08月04日
* 功能介绍:
******************************************************************************
* 梁山派软硬件资料与相关扩展板软硬件资料官网全部开源
* 开发板官网:www.lckfb.com
* 技术支持常驻论坛,任何技术问题欢迎随时交流学习
* 立创论坛:club.szlcsc.com
* 其余模块移植手册:【立创·梁山派开发板】模块移植手册
* 关注bilibili账号:【立创开发板】,掌握我们的最新动态!
* 不靠卖板赚钱,以培养中国工程师为己任
*********************************************************************************/
#include "gd32f4xx.h"
#include "systick.h"
#include "bsp_led.h"
#include "lcdinit.h"
#include "lcdgui.h"
#include "bsp_usart.h"
#include "stdio.h"
#include "string.h"
#include "bsp_aht21.h"
#include "bsp_ags10.h"
#include "bsp_wf183d.h"
#include "bsp_voltage.h"
uint16_t color_buff[7] = {WHITE,BLACK,BLUE,RED,GREEN,YELLOW,GRAY};
/******************************************************************
* 函 数 名 称:main
* 函 数 说 明:主函数
* 函 数 形 参:无
* 函 数 返 回:无
* 作 者: LC
* 备 注:无
******************************************************************/
int main(void)
{
char disp_buf[200] = {0};
int time_value = 0;
// 优先级分组
nvic_priority_group_set(NVIC_PRIGROUP_PRE2_SUB2);
//滴答定时器初始化 1us
systick_config();
//sram初始化
exmc_synchronous_dynamic_ram_init(EXMC_SDRAM_DEVICE0);
//AHT21温湿度传感器初始化
aht21_gpio_init();
//有害气体传感器初始化
ags10_gpio_init();
//气压传感器初始化
wf183d_gpio_config();
//串口0初始化(调试)
usart_gpio_config( 115200U );
printf("start\r\n");
//lcd初始化
LCD_Init();
//清除全屏为黑色
LCD_Fill(0,0,280,240,BLACK);
//开启显示
LCD_Show_Gram();
//等待一帧数据搬运完成
while(get_show_over_flag());
//开启定时器固定刷屏
Lcd_Show_Time_config();
while(1)
{
//画矩形
LCD_DrawRectangle(30,30,280-30,240-30,WHITE);
//采集温湿度数据
aht21_read_data();
//显示温度
sprintf(disp_buf, "TEMP = %.2f C ",get_temperature() );
LCD_ShowString(60, 240/2-64, (const uint8_t*)disp_buf, WHITE, BLACK, 16, 0);
//显示湿度
sprintf(disp_buf, "HUMI = %.2f %%RH ",get_humidity() );
LCD_ShowString(60, 240/2-32, (const uint8_t*)disp_buf, WHITE, BLACK, 16, 0);
//因不可频繁采集数据,需要间隔一定时间
if( ( time_value++ ) >= 10 )
{
time_value = 0;
//显示有害气体浓度
sprintf(disp_buf, "TVOC = %d ppb ",ags10_read() );
LCD_ShowString(60, 240/2, (const uint8_t*)disp_buf, WHITE, BLACK, 16, 0);
}
//显示气压
sprintf(disp_buf, "pressure = %d PA ",GetAirPressureValue() );
LCD_ShowString(60, 240/2+32, (const uint8_t*)disp_buf, WHITE, BLACK, 16, 0);
//电量测量初始化(PC3与sram的控制线冲突,因此采用分时复用方法)
power_voltage_gpio_config();
//测量电量
get_voltage_value();
//sram重新初始化
exmc_synchronous_dynamic_ram_init(EXMC_SDRAM_DEVICE0);
//定时器循环固定刷屏
while(get_show_over_flag());
set_show_update_flag(1);
while(get_show_update_flag());
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
实物验证
6. NBIOT模块移植
NBIOT模块是一种专门用于物联网设备的通信模块,全称为Narrowband Internet of Things(窄带物联网)。NBIOT是一种低功耗、窄带宽的无线通信技术,它专门设计用于连接较小、低功耗、低速度的物联网设备。
NBIOT模块具有以下特点:
- 低功耗:NBIOT模块使用了节能的设计,能够使设备在长时间内使用一次电池充电,并具备长时间待机。
- 大范围覆盖:由于采用了低功耗的设计,NBIOT模块可以实现远距离通信,并具备较好的室内和室外覆盖能力,适用于各种环境。
- 较低成本:相对于其他无线通信技术,NBIOT模块的成本相对较低,适合大规模应用和广泛部署。
- 高可靠性:NBIOT模块具备较高的通信可靠性,可以在复杂的无线环境中稳定地传输数据。
- 低速率:NBIOT模块通信速率较低,适合传输小型数据,如传感器数据或简单命令。
NBIOT模块通常被广泛应用于智能城市、智能家居、智能农业、智能能源和工业自动化等领域,为物联网设备的连接和数据传输提供可靠的通信解决方案。
本案例采用的NBIOT模块是由安信可科技设计的EC-01F模块。其使用网络是通过物联网卡提供流量,物联网卡链接:电信NB卡 电信NB IoT卡 贴片/插拔 节能低功耗设备用水电气表门磁
接下来我们以阿里云生活物联网平台(飞燕平台)为例,通过EC-01F模组使用MQTT协议接入阿里云,并用阿里公版app云智能远程控制设备。
6.1 生活物联网平台配置
- 创建阿里云产品;百度搜索阿里云,进入阿里云官网。
- 登录账号,进入生活物联网平台。(没有账号则先注册)
- 进入控制台
- 新建一个新项目。
- 进入项目配置。
- 创建新产品。
- 新产品配置。
务必选择蜂窝的联网方式
- 创建完成之后,在弹出的界面选择继续开发。
- 添加自定义功能
- 配置自定义功能。(后面可以自己添加自己需要的自定义功能,这里仅作为展示)
这里仅作为展示
- 配置功能完成之后,进入人机交互模式
- 使用云智能APP进行控制。(云智能APP是阿里云提供的,无需我们去重新设计APP)
- 设置基础配置中的产品展示。(配置完成之后记得要保存)
- 设置基础配置中的绑定方式。(使用云智能APP固定只能使用授权式)
- 设置基础配置中的设备面板。
- 创建一个新的面板。
- 设计面板的界面UI。
注意功能选项是可以在手机上控制的;信息选项是只能在手机上查看,无法控制。最终我们的界面布局如下:
视频
内容
- 退出设计界面,回到产品管理界面,选择我们刚才设计的面板。
- 上传产品说明书(根据情况上传,这里我随便上传一个PDF文件)
- 进入设备调试,新增测试设备。
- 给测试设备命名。
- 保存设备证书。(大家实际使用时不要使用我们设备证书)
6.2 EC-01F模块软件驱动
EC-01F是通过串口进行通信,其默认的波特率为9600,如下:
在硬件设计部分,我们将模组的串口引脚接入了立创·梁山派开发板的PB10PB11(USART2)。所以在使用之前我们需要初始化引脚为串口通信,通信的配置根据EC-01F数据手册的要求,配置如下:
#define BSP_EC01F_USART_TX_RCU RCU_GPIOB // 串口TX的端口时钟
#define BSP_EC01F_USART_RX_RCU RCU_GPIOB // 串口RX的端口时钟
#define BSP_EC01F_USART_RCU RCU_USART2 // 串口2的时钟
#define BSP_EC01F_USART_TX_PORT GPIOB // 串口TX的端口
#define BSP_EC01F_USART_RX_PORT GPIOB // 串口RX的端口
#define BSP_EC01F_USART_AF GPIO_AF_7 // 串口1的复用功能
#define BSP_EC01F_USART_TX_PIN GPIO_PIN_10 // 串口TX的引脚
#define BSP_EC01F_USART_RX_PIN GPIO_PIN_11 // 串口RX的引脚
#define BSP_EC01F_USART USART2 // 串口2
#define BSP_EC01F_USART_IRQ USART2_IRQn // 串口2中断
#define EC01F_RX_LEN_MAX 2096 //串口接收最大长度
//是否开启串口调试显示 1=开始 0=关闭
#define DEBUG 1
//设备证书
#define ProductKey "a1RCMGzigPn"
#define DeviceName "LCKFB"
#define DeviceSecret "2bdf8b41dd6f4c549abe92b4ab73f2b9"
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**********************************************************
* 函 数 名 称:ec01f_gpio_config
* 函 数 功 能:EC-01F模组的初始化
* 传 入 参 数:无
* 函 数 返 回:无
* 作 者:LCKFB
* 备 注:默认波特率9600
**********************************************************/
void ec01f_gpio_config(void)
{
/* 开启时钟 */
rcu_periph_clock_enable(BSP_EC01F_USART_TX_RCU); // 开启串口时钟
rcu_periph_clock_enable(BSP_EC01F_USART_RX_RCU); // 开启端口时钟
rcu_periph_clock_enable(BSP_EC01F_USART_RCU); // 开启端口时钟
/* 配置GPIO复用功能 */
gpio_af_set(BSP_EC01F_USART_TX_PORT,BSP_EC01F_USART_AF,BSP_EC01F_USART_TX_PIN);
gpio_af_set(BSP_EC01F_USART_RX_PORT,BSP_EC01F_USART_AF,BSP_EC01F_USART_RX_PIN);
/* 配置GPIO的模式 */
/* 配置TX为复用模式 上拉模式 */
gpio_mode_set(BSP_EC01F_USART_TX_PORT,GPIO_MODE_AF,GPIO_PUPD_PULLUP,BSP_EC01F_USART_TX_PIN);
/* 配置RX为复用模式 上拉模式 */
gpio_mode_set(BSP_EC01F_USART_RX_PORT, GPIO_MODE_AF,GPIO_PUPD_PULLUP,BSP_EC01F_USART_RX_PIN);
/* 配置TX为推挽输出 50MHZ */
gpio_output_options_set(BSP_EC01F_USART_TX_PORT,GPIO_OTYPE_PP,GPIO_OSPEED_50MHZ,BSP_EC01F_USART_TX_PIN);
/* 配置RX为推挽输出 50MHZ */
gpio_output_options_set(BSP_EC01F_USART_RX_PORT,GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, BSP_EC01F_USART_RX_PIN);
/* 配置串口的参数 */
usart_deinit(BSP_EC01F_USART); // 复位串口
usart_baudrate_set(BSP_EC01F_USART,9600); // 设置波特率
usart_parity_config(BSP_EC01F_USART,USART_PM_NONE); // 没有校验位
usart_word_length_set(BSP_EC01F_USART,USART_WL_8BIT); // 8位数据位
usart_stop_bit_set(BSP_EC01F_USART,USART_STB_1BIT); // 1位停止位
/* 使能串口 */
usart_enable(BSP_EC01F_USART); // 使能串口
usart_transmit_config(BSP_EC01F_USART,USART_TRANSMIT_ENABLE); // 使能串口发送
usart_receive_config(BSP_EC01F_USART,USART_RECEIVE_ENABLE); // 使能串口接收
/* 中断配置 */
nvic_irq_enable(BSP_EC01F_USART_IRQ, 1, 1); // 配置中断优先级
usart_interrupt_enable(BSP_EC01F_USART,USART_INT_RBNE); // 读数据缓冲区非空中断和溢出错误中断
usart_interrupt_enable(BSP_EC01F_USART,USART_INT_IDLE); // 空闲检测中断
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
全部的代码如下:
bsp_ec01f.c
#include "bsp_ec01f.h"
#include "string.h"
#include "bsp_usart.h"
#include "stdio.h"
unsigned char ec01f_rx_buff[EC01F_RX_LEN_MAX];
unsigned char ec01f_rx_flag = 0;
unsigned int ec01f_rx_len = 0;
//默认波特率9600
void ec01f_gpio_config(void)
{
/* 开启时钟 */
rcu_periph_clock_enable(BSP_EC01F_USART_TX_RCU); // 开启串口时钟
rcu_periph_clock_enable(BSP_EC01F_USART_RX_RCU); // 开启端口时钟
rcu_periph_clock_enable(BSP_EC01F_USART_RCU); // 开启端口时钟
/* 配置GPIO复用功能 */
gpio_af_set(BSP_EC01F_USART_TX_PORT,BSP_EC01F_USART_AF,BSP_EC01F_USART_TX_PIN);
gpio_af_set(BSP_EC01F_USART_RX_PORT,BSP_EC01F_USART_AF,BSP_EC01F_USART_RX_PIN);
/* 配置GPIO的模式 */
/* 配置TX为复用模式 上拉模式 */
gpio_mode_set(BSP_EC01F_USART_TX_PORT,GPIO_MODE_AF,GPIO_PUPD_PULLUP,BSP_EC01F_USART_TX_PIN);
/* 配置RX为复用模式 上拉模式 */
gpio_mode_set(BSP_EC01F_USART_RX_PORT, GPIO_MODE_AF,GPIO_PUPD_PULLUP,BSP_EC01F_USART_RX_PIN);
/* 配置TX为推挽输出 50MHZ */
gpio_output_options_set(BSP_EC01F_USART_TX_PORT,GPIO_OTYPE_PP,GPIO_OSPEED_50MHZ,BSP_EC01F_USART_TX_PIN);
/* 配置RX为推挽输出 50MHZ */
gpio_output_options_set(BSP_EC01F_USART_RX_PORT,GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, BSP_EC01F_USART_RX_PIN);
/* 配置串口的参数 */
usart_deinit(BSP_EC01F_USART); // 复位串口
usart_baudrate_set(BSP_EC01F_USART,9600); // 设置波特率
usart_parity_config(BSP_EC01F_USART,USART_PM_NONE); // 没有校验位
usart_word_length_set(BSP_EC01F_USART,USART_WL_8BIT); // 8位数据位
usart_stop_bit_set(BSP_EC01F_USART,USART_STB_1BIT); // 1位停止位
/* 使能串口 */
usart_enable(BSP_EC01F_USART); // 使能串口
usart_transmit_config(BSP_EC01F_USART,USART_TRANSMIT_ENABLE); // 使能串口发送
usart_receive_config(BSP_EC01F_USART,USART_RECEIVE_ENABLE); // 使能串口接收
/* 中断配置 */
nvic_irq_enable(BSP_EC01F_USART_IRQ, 1, 1); // 配置中断优先级
usart_interrupt_enable(BSP_EC01F_USART,USART_INT_RBNE); // 读数据缓冲区非空中断和溢出错误中断
usart_interrupt_enable(BSP_EC01F_USART,USART_INT_IDLE); // 空闲检测中断
}
void ec01f_usart_send_char(unsigned char ch)
{
//发送字符
usart_data_transmit(BSP_EC01F_USART, (unsigned char)ch);
// 等待发送数据缓冲区标志自动置位
while(RESET == usart_flag_get(BSP_EC01F_USART, USART_FLAG_TBE) );
}
/******************************************************************
* 函 数 名 称:BSP_EC01F_USART_send_String
* 函 数 说 明:向EC01F模块发送字符串
* 函 数 形 参:str=发送的字符串
* 函 数 返 回:无
* 作 者:LC
* 备 注:无
******************************************************************/
void ec01f_usart_send_string(unsigned char *str)
{
while( str && *str ) // 地址为空或者值为空跳出
{
ec01f_usart_send_char(*str++);
}
}
/******************************************************************
* 函 数 名 称:Clear_EC01F_RX_BUFF
* 函 数 说 明:清除EC01F发过来的数据
* 函 数 形 参:无
* 函 数 返 回:无
* 作 者:LC
* 备 注:无
******************************************************************/
void ec01f_rx_buff_clear(void)
{
unsigned int i = EC01F_RX_LEN_MAX-1;
while(i)
{
ec01f_rx_buff[i--] = 0;
}
ec01f_rx_len = 0;
ec01f_rx_flag = 0;
}
/******************************************************************
* 函 数 名 称:EC01F_Send_Cmd
* 函 数 说 明:向EC01F模块发送指令,并查看EC01F模块是否返回想要的数据
* 函 数 形 参:cmd:发送的AT指令
ack:想要的应答
waitms:等待应答的时间
cnt:等待应答多少次
* 函 数 返 回:1=得到了想要的应答 0=没有得到想要的应答
* 作 者:LC
* 备 注:无
******************************************************************/
char ec01f_send_cmd(char *cmd,char *ack,unsigned int waitms,unsigned char cnt)
{
ec01f_usart_send_string((unsigned char*)cmd);//发送AT指令
while(cnt--)
{
//串口中断接收EC01F应答
if( ec01f_rx_flag == 1 )
{
ec01f_rx_flag = 0;
ec01f_rx_len = 0;
//查找是否有想要的数据
if( strstr((char*)ec01f_rx_buff, ack) != NULL )
{
//清除接收的数据
ec01f_rx_buff_clear();
return 1;
}
//清除接收的数据
ec01f_rx_buff_clear();
}
//时间间隔
delay_1ms(waitms);
}
ec01f_rx_flag = 0;
ec01f_rx_len = 0;
return 0;
}
/******************************************************************
* 函 数 名 称:ec01f_connect_aliyun
* 函 数 说 明:配置EC-01F模块连接阿里云物联网平台
* 函 数 形 参:无
* 函 数 返 回:无
* 作 者:LC
* 备 注:如返回错误码10,说明未识别到SIM卡
******************************************************************/
void ec01f_connect_aliyun(void)
{
char at_cmd[500]={0};
RST:
//测试指令AT
ec01f_send_cmd("AT\r\n","OK",100,3);
//关闭飞行模式
if( ec01f_send_cmd("AT+CFUN=1\r\n","OK",100,3) == 0 )
printf("AT+CFUN=1 fail\r\n");
//判断网络附着状态
if( ec01f_send_cmd("AT+CEREG?\r\n","+CEREG: 0,1",100,3) == 0 )
printf("AT+CEREG? fail\r\n");
delay_1ms(1000);
//配置平台为阿里物联网
if( ec01f_send_cmd("AT+ECMTCFG=\"cloud\",0,2,1\r\n","OK",100,3) == 0 )
printf("AT+ECMTCFG =\"cloud\",0,2,1 fail\r\n");
//连接设备证书
sprintf(at_cmd, "AT+ECMTCFG=\"aliauth\",0 ,\"%s\",\"%s\",\"%s\"\r\n", ProductKey, DeviceName, DeviceSecret);
if( ec01f_send_cmd(at_cmd,"OK",100,3) == 0 )
printf("%s fail\r\n",at_cmd);
//打开客户端连接
sprintf(at_cmd, "AT+ECMTOPEN=0, \"%s.iot-as-mqtt.cn-shanghai.aliyuncs.com\",1883\r\n", ProductKey);
if( ec01f_send_cmd(at_cmd,"OK",100,3) == 0 )
printf("%s fail\r\n",at_cmd);
//连接客户端到 MQTT 服务器。
if( ec01f_send_cmd("AT+ECMTCONN=0, \"0\"\r\n","OK",100,3) == 0 )
printf("AT+ECMTCONN=0, \"0\" fail\r\n");
//订阅
sprintf(at_cmd, "AT+ECMTSUB=0,1,\"/sys/%s/%s/thing/service/property/set\",1\r\n", ProductKey, DeviceName);
ec01f_send_cmd(at_cmd,"OK",100,3);
printf("连接aliyu成功\r\n");
ec01f_rx_buff_clear();//清除串口接收缓存
}
/******************************************************************
* 函 数 名 称:Publish_message
* 函 数 说 明:发布消息(上传数据至服务器)
* 函 数 形 参:identifier:发布的标识符 value:发布的值
* 函 数 返 回:无
* 作 者:LC
* 备 注:无
******************************************************************/
void Publish_message(char* identifier, char* value)
{
char at_cmd[250]={0};
unsigned char tailed[2] = {0x1a,0x00};
//发布主题消息
sprintf(at_cmd,"AT+ECMTPUB=0,0,0,0,\"/sys/%s/%s/thing/event/property/post\"\r\n", ProductKey, DeviceName);
ec01f_send_cmd(at_cmd,">",10,100);
//发送JSON数据
sprintf(at_cmd,"{\"params\":{\"%s\":%s},\"version\":\"1.0.0\"}\r\n%s", identifier, value,tailed);
ec01f_send_cmd(at_cmd,"OK",10,100);
// //发送数据结束符,需采用HEX形式发送
// ec01f_usart_send_char(0X1A);
}
/******************************************************************
* 函 数 名 称:Subscribe_message
* 函 数 说 明:订阅消息(接收服务器下发的数据)
* 函 数 形 参:revData:接收到的数据要保存到的结构体
* 函 数 返 回:0:解析成功 1:解析失败
* 作 者:LC
* 备 注:char *strtok(char s[], const char *delim)
分解字符串为一组字符串。s为要分解的字符串,delim为分隔符字符串。
假设接收到:+ECMTRECV: 0,0,"/sys/a1RCMGzigPn/LCKFB/thing/service/property/set",
{"method":"thing.service.property.set","id":"1510072399","params":{"powerstate":1},"version":"1.0.0"}
******************************************************************/
uint8_t Subscribe_message(Subscribe_message_struct *revData)
{
char buff[500]={0};
char *temp = buff;
if( ec01f_rx_flag == 1 )
{
ec01f_rx_flag = 0;
ec01f_rx_len = 0;
if( strstr( (const char*)ec01f_rx_buff, "params" ) != 0 )
{
//temp = params":{"powerstate":1},"version":"1.0.0"}
temp = strstr( (const char*)ec01f_rx_buff, "params" );
//获取功能名称
temp += strlen("params\":{\"");
strcpy(revData->keyName,strtok(temp,"\""));
printf("data->keyname = %s\r\n",revData->keyName);
//获取功能值
temp = strstr((char*)ec01f_rx_buff, "params" );
temp += strlen("params\":{\"")+strlen(revData->keyName)+2;
strcpy(revData->keyValue, strtok(temp,"}"));
printf("data->keyValue = %s\r\n",revData->keyValue);
return 1;
}
}
return 0;
}
/******************************************************************
* 函 数 名 称:usart0Debug
* 函 数 说 明:通过串口0调试EC-01F模组
* 函 数 形 参:无
* 函 数 返 回:无
* 作 者:LC
* 备 注:串口0接收电脑发送过来的数据,并将数据发送给EC-01F模组;
模组返回的数据,又通过串口0发送至电脑串口调试助手。
******************************************************************/
void usart0Debug(void)
{
//串口0收到数据
if( g_recv_complete_flag == 1 )
{
g_recv_complete_flag = 0;
ec01f_usart_send_string(g_recv_buff);
memset(g_recv_buff,0,g_recv_length);
g_recv_length = 0;
}
//收到EC-01F发送过来的数据
if( ec01f_rx_flag == 1)
{
ec01f_rx_flag = 0;
//通过串口0发送至电脑串口调试助手
printf("%s",ec01f_rx_buff);
ec01f_rx_len = 0;
}
}
void USART2_IRQHandler(void)
{
// 接收缓冲区不为空
if(usart_interrupt_flag_get(BSP_EC01F_USART,USART_INT_FLAG_RBNE) != RESET)
{
//接收数据
ec01f_rx_buff[ ec01f_rx_len ] = usart_data_receive(BSP_EC01F_USART);
#if DEBUG
//测试,查看接收到了什么数据
printf("%c", ec01f_rx_buff[ ec01f_rx_len ]);
#endif
//接收长度限制
ec01f_rx_len = ( ec01f_rx_len + 1 ) % EC01F_RX_LEN_MAX;
}
// 检测到空闲中断
if(usart_interrupt_flag_get(BSP_EC01F_USART,USART_INT_FLAG_IDLE) == SET)
{
usart_data_receive(BSP_EC01F_USART); // 必须要读,读出来的值不能要
ec01f_rx_buff[ec01f_rx_len] = '\0'; //字符串结尾补 '\0'
ec01f_rx_flag = 1; // 接收完成
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
bsp_ec01f.h
/********************************************************************************
* 测试硬件:立创·梁山派开发板GD32F470ZGT6 使用主频200Mhz 晶振25Mhz
* 版 本 号: V1.0
* 修改作者: LCKFB
* 修改日期: 2023年08月04日
* 功能介绍:
******************************************************************************
* 梁山派软硬件资料与相关扩展板软硬件资料官网全部开源
* 开发板官网:www.lckfb.com
* 技术支持常驻论坛,任何技术问题欢迎随时交流学习
* 立创论坛:club.szlcsc.com
* 其余模块移植手册:https://dri8c0qdfb.feishu.cn/docx/EGRVdxunnohkrNxItYTcrwAnnHe
* 关注bilibili账号:【立创开发板】,掌握我们的最新动态!
* 不靠卖板赚钱,以培养中国工程师为己任
*********************************************************************************/
#ifndef _BSP_EC01F_H
#define _BSP_EC01F_H
#include "gd32f4xx.h"
#include "systick.h"
#define BSP_EC01F_USART_TX_RCU RCU_GPIOB // 串口TX的端口时钟
#define BSP_EC01F_USART_RX_RCU RCU_GPIOB // 串口RX的端口时钟
#define BSP_EC01F_USART_RCU RCU_USART2 // 串口2的时钟
#define BSP_EC01F_USART_TX_PORT GPIOB // 串口TX的端口
#define BSP_EC01F_USART_RX_PORT GPIOB // 串口RX的端口
#define BSP_EC01F_USART_AF GPIO_AF_7 // 串口1的复用功能
#define BSP_EC01F_USART_TX_PIN GPIO_PIN_10 // 串口TX的引脚
#define BSP_EC01F_USART_RX_PIN GPIO_PIN_11 // 串口RX的引脚
#define BSP_EC01F_USART USART2 // 串口2
#define BSP_EC01F_USART_IRQ USART2_IRQn // 串口2中断
#define EC01F_RX_LEN_MAX 2096 //串口接收最大长度
//是否开启串口调试显示 1=开始 0=关闭
#define DEBUG 1
//设备证书
#define ProductKey "a1aon9jegv5"
#define DeviceName "LCKFB"
#define DeviceSecret "f93634ef8e74206801626fd4662aa950"
//接收服务器下发的数据
typedef struct{
char keyName[50];
char keyValue[20];
}Subscribe_message_struct;
void ec01f_gpio_config(void);
void usart0Debug(void);
void ec01f_connect_aliyun(void);
void Publish_message(char* identifier, char* value);
uint8_t Subscribe_message(Subscribe_message_struct *revData);
#endif
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
6.3 绑定设备
将bsp_ec01f.h
中宏定义的3个设备证书数据进行更改,更改为大家自己的设备证书三要素。
6.4 移植验证
在main.c中编写如下代码:
/********************************************************************************
* 测试硬件:立创·梁山派开发板GD32F470ZGT6 使用主频200Mhz 晶振25Mhz
* 版 本 号: V1.0
* 修改作者: LC
* 修改日期: 2023年08月04日
* 功能介绍:
******************************************************************************
* 梁山派软硬件资料与相关扩展板软硬件资料官网全部开源
* 开发板官网:www.lckfb.com
* 技术支持常驻论坛,任何技术问题欢迎随时交流学习
* 立创论坛:club.szlcsc.com
* 其余模块移植手册:【立创·梁山派开发板】模块移植手册
* 关注bilibili账号:【立创开发板】,掌握我们的最新动态!
* 不靠卖板赚钱,以培养中国工程师为己任
*********************************************************************************/
#include "gd32f4xx.h"
#include "systick.h"
#include "bsp_led.h"
#include "lcdinit.h"
#include "lcdgui.h"
#include "bsp_usart.h"
#include "stdio.h"
#include "string.h"
#include "bsp_aht21.h"
#include "bsp_ags10.h"
#include "bsp_wf183d.h"
#include "bsp_voltage.h"
#include "bsp_ec01f.h"
uint16_t color_buff[7] = {WHITE,BLACK,BLUE,RED,GREEN,YELLOW,GRAY};
/******************************************************************
* 函 数 名 称:main
* 函 数 说 明:主函数
* 函 数 形 参:无
* 函 数 返 回:无
* 作 者: LC
* 备 注:无
******************************************************************/
int main(void)
{
char disp_buf[200] = {0};
int time_value = 0;
uint32_t pressure_val = 0;
uint32_t tvoc_val = 0;
// 优先级分组
nvic_priority_group_set(NVIC_PRIGROUP_PRE2_SUB2);
//滴答定时器初始化 1us
systick_config();
//sram初始化
exmc_synchronous_dynamic_ram_init(EXMC_SDRAM_DEVICE0);
//AHT21温湿度传感器初始化
aht21_gpio_init();
//有害气体传感器初始化
ags10_gpio_init();
//气压传感器初始化
wf183d_gpio_config();
//串口0初始化(调试)
usart_gpio_config( 115200U );
printf("start\r\n");
//NBIOT模组EC-01F初始化
ec01f_gpio_config();
//NBIOT模组连接阿里云物联网平台
ec01f_connect_aliyun();
delay_1ms(500);
//lcd初始化
LCD_Init();
//清除全屏为黑色
LCD_Fill(0,0,280,240,BLACK);
//开启显示
LCD_Show_Gram();
//等待一帧数据搬运完成
while(get_show_over_flag());
//开启定时器固定刷屏
Lcd_Show_Time_config();
while(1)
{
//画矩形
LCD_DrawRectangle(30,30,280-30,240-30,WHITE);
//采集温湿度数据
aht21_read_data();
//显示温度
sprintf(disp_buf, "TEMP = %.2f C ",get_temperature() );
LCD_ShowString(60, 240/2-64, (const uint8_t*)disp_buf, WHITE, BLACK, 16, 0);
//显示湿度
sprintf(disp_buf, "HUMI = %.2f %%RH ",get_humidity() );
LCD_ShowString(60, 240/2-32, (const uint8_t*)disp_buf, WHITE, BLACK, 16, 0);
//因不可频繁采集数据,需要间隔一定时间
if( ( time_value++ ) >= 20 )
{
time_value = 0;
//显示有害气体浓度
tvoc_val = ags10_read();
sprintf(disp_buf, "TVOC = %d ppb ",tvoc_val );
LCD_ShowString(60, 240/2, (const uint8_t*)disp_buf, WHITE, BLACK, 16, 0);
//上传温度
sprintf(disp_buf, "%.2f",get_temperature());
Publish_message("temperature",disp_buf);
//上传湿度
sprintf(disp_buf, "%.2f",get_humidity());
Publish_message("Humidity",disp_buf);
//上传气压
sprintf(disp_buf, "%d",pressure_val);
Publish_message("Atmosphere",disp_buf);
//上传TVOC
sprintf(disp_buf, "%d",tvoc_val);
Publish_message("TVOC",disp_buf);
}
//显示气压
pressure_val = GetAirPressureValue();
sprintf(disp_buf, "pressure = %d PA ", pressure_val );
LCD_ShowString(60, 240/2+32, (const uint8_t*)disp_buf, WHITE, BLACK, 16, 0);
//电量测量初始化(PC3与sram的控制线冲突,因此采用分时复用方法)
power_voltage_gpio_config();
//测量电量
sprintf(disp_buf, "power = %.0f %% ",get_voltage_value() );
LCD_ShowString(60, 240/2+64, (const uint8_t*)disp_buf, WHITE, BLACK, 16, 0);
//sram重新初始化
exmc_synchronous_dynamic_ram_init(EXMC_SDRAM_DEVICE0);
//定时器循环固定刷屏
while(get_show_over_flag());
set_show_update_flag(1);
while(get_show_update_flag());
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
将以上代码烧录开发板。打开串口调试助手。如果发现串口调试助手有输出 fail 或者 +CME ERROR: 3 说明设备还在准备状态,我们需要复位。
可以根据返回的数据确定NBIOT模块的状态。其中
- 发送【AT+CFUN=1】 如果返回 +CME ERROR: 10 说明没有识别到SIM卡;
- 发送【AT+CEREG? 】如果返回+CEREG: 0,2 说明没有信号(没有接天线);
关于错误码的具体意义,可以查看NBIOT模块AT指令集V1.0文件的265页【Error Values 错误码】章节。
如果正常情况下通过了,那么刷新一下阿里云平台的网页,即可看见自己的设备已经在线。
6.5 手机绑定与数据显示
回到人机交互界面。进入产品说明书选项,下载配网二维码。
如果之前没有安装云智能软件APP,那么扫码之后会跳转到下载界面;如果已经下载有云智能APP则通过APP扫二维码,即可加载我们之前配置的设备面板界面。(输入设备证书三要素的Device Name,然后生成二维码)
7. LVGL8.2移植
7.1 LVGL介绍
LittlevGL又简称LVGL,它是一个免费的开源图形库,提供了创建嵌入式GUI所需的一切,具有易于使用的图形元素、漂亮的视觉效果和低内存占用。
LVGL是一个免费的开放源代码图形库,目前由来自世界各地的志愿者共同维护和开发。LVGL是用C语言编写的,遵循MIT协议,可以自由地使用和修改。LVGL支持多种操作系统,例如Linux、Windows、RTOS等,也可以在开发板上运行。LVGL还支持多种显示器驱动器和触摸屏驱动器,可以与不同大小和分辨率的显示器兼容。LVGL还提供了多种语言的绑定,例如Python、Micropython、JavaScript等,以及多种开发工具,例如模拟器、视觉化设计器、字体转换器等。
7.2 准备工作
硬件接口
我的工程是以梁山派GD32F470ZGT6作为主控,以1.69寸TFT彩色的240x280屏幕作为显示。主控与屏幕的连接如下:
其中PB3 与 PB5 分别是SPI0_SCK、SPI0_MOSI,已经满足了硬件SPI的通信方式
为了提高LVGL的刷新率,建议使用硬件SPI+DMA,最好是双BUFF方式。关于如何配置使用硬件SPI+DMA以及代码,可以看 三、软件设计 1.屏幕显示驱动
这一章的内容。
7.3 材料准备
移植过程中需要用到 LVGL 图形库文件,本案例使用的是 LVGL8.2.0 版本。LVGL 图形库官方的下载地址为:https://github.com/lvgl/lvgl
如果上面的官方下载地址很卡,可以使用我下载下来的:
文件下载
通过百度网盘分享的文件:lvgl-release-v8.2.zip
链接:https://pan.baidu.com/s/1e2WuDk1ymHi-lzO-Tdtr8g?pwd=LCKF
提取码:LCKF
7.4 文件移植
删减LVGL文件
- 在我们准备好的屏幕工程下,新建一个文件【LVGL】。
- 将下载的 lvgl 源码解压。
- 复制【 example】文件夹 和【 src】 文件夹。复制文件 “lv_conf_template.h” 和 “lvgl.h”
- 将复制的文件放到我们屏幕工程目录的LVGL 文件夹目录下。并将“lv_conf_template.h”名称修改为“lv_conf.h”;
- 在我们之前复制的【examples】文件夹里,将文件夹【porting】提取到【LVGL】文件夹下。
- 将【examples】文件夹删除。让我们工程LVGL目录里只剩下文件夹【porting】文件夹【src】文件"lv_conf.h" 文件"lvgl.h"。
- 在
LVGL\porting
文件夹目录下,将lv_port_disp_template.c
和lv_port_disp_template.h
文件名称修改为lv_port_disp.c
和lv_port_disp.h
,将lv_port_indev_template.c
和lv_port_indev_template.h
文 件 名 称 修 改 为lv_port_indev.c
和lv_port_indev.h
。
KEIL中添加LVGL相关文件与路径
- 添加.h路径
- 新建工程目录管理。具体如下:
- 往新建的目录文件夹添加文件。文件过多,我这里不在写明,只截图。
- lvgl_porting
- lvgl_core
将【LVGL】->【src】->【core】下的全部文件添加。
- lvgl_draw
添加.\src\draw下所有源码(不要添加文件夹!),以及sw文件夹下所有源码。
- lvgl_extra
添加\src\extra下所有源码和layouts、libs、others、themes、widgets下所有源文件。这里的文件很多很杂,只要是说到的文件夹,那么里面的所有文件夹里的.c文件都要全部添加! - lvgl_font
添加\src\font下所有源码。
- lvgl_gpu
添加\src\draw\sdl下所有源码,还有\src\draw\stm32_dma2d下的所有源码。
- lvgl_hal
添加\src\hal下所有源码.
- lvgl_misc
添加\src\misc下所有源码.
- lvgl_widgets
添加\src\widgets下所有源码.
修改报错
- 尝试编译,出现很多个错误,大多都是找不到lv_conf.h的路径,导致打开文件失败。
- 双击上图中画红色方框的地方跳转到对应报错的地方。将
../../lv_conf.h
修改为../lv_conf.h
- 继续编译,还是很多个错误。因为LVGL的源码需要C99的支持,否则编译无法通过。
- 将编译模式,修改为C99模式。
- 再编译,就没有错误,只有警告了。
- 如果大家编译还是有错误。参考如下:
如果编译还是有错,.\Objects\Application.axf: Error: L6218E: Undefined symbol **aeabi_assert (referred from qrcodegen.o).qrcodegen 中使用了 assert这个断言。断言里面用到了 **aeabi_assert,这个NDEBUG没有定义所以报错了
- 我们可以在外面定义__aeabi_assert这个函数如下,或者通过添加定义 NDEBUG宏来失效断言
void __aeabi_assert(const char *err, const char *file, int line)
{
/* 输出内容自己定 */
}
2
3
4
- 如果工程之中没有内存管理,则需要修改启动文件中的堆栈。根据官方推荐我们可以把堆栈修改为4K,假如使用的功能比较多,还需要再适当增大。
- 打开lv_conf.h文件中的条件编译。
现在移植成功了,但是还显示不了,我们还需要去实现显示的相关接口。
修改显示接口文件
- 默认【 lv_port_disp.c】 和 【lv_port_disp.h】 的条件编译是关闭的,我们需要把他打开并修改包含目录层级。
- 将【 lv_port_disp.c】里的头文件
#include “lv_port_disp_template.h”
修改为#include “lv_port_disp.h”
。并将#include “. ./. ./lvgl.h”
修改为#include “. ./lvgl.h”
- 更改【lv_port_disp.c 】文件中
lv_port_disp_init
驱动函数。此函数提供了三种写缓存方式,保留其中一种即可,本案例采用方式一。
方式一:单缓存显示(10行),主控内存较小时选用此方式。
方式二:双缓存显示(两个10行),此方式支持DMA交替传输,缓存区越大,显示效果越好(有条件两个满屏缓存)。
方式三:双满屏缓存显示,相当于有条件的方式二
注释方式二和方式三,更改第一种方式如下:
- 将报错的宏【MY_DISP_HOR_RES】定义为我们屏幕的宽,这里我的屏幕宽是280,所以我重新宏定义为 280。这里建议大家直接将屏幕的宽定义拿过来定义。拿过来的话,需要像我一样导入LCD的头文件。LCD_W = 280 LCD_H = 240
- 更改屏幕大小
- 在
static void disp_init(void)
中,添加你的LCD初始化函数。
- 修改disp_flush函数
static void disp_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p)
{
/*The most simple case (but also the slowest) to put all pixels to the screen one-by-one*/
int32_t x;
int32_t y;
for(y = area->y1; y <= area->y2; y++) {
for(x = area->x1; x <= area->x2; x++) {
/*Put a pixel to the display. For example:*/
/*put_px(x, y, *color_p)*/
LCD_DrawPoint(x,y,color_p->full);
color_p++;
}
}
// LCD_DrawLump(area->x1,area->y1,area->x2,area->y2,(uint32_t)color_p);
/*IMPORTANT!!!
*Inform the graphics library that you are ready with the flushing*/
lv_disp_flush_ready(disp_drv);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
- 其中,LCD_DrawPoint 函数 在lcdgui.中,具体代码如下:
Show_Gram为我们使用立创梁山派的SDRAM开辟的LCD缓存数组
/******************************************************************
* 函 数 名 称:LCD_DrawPoint
* 函 数 说 明:在指定位置画点
* 函 数 形 参:x,y 画点坐标
color 点的颜色
* 函 数 返 回:无
* 作 者:LC
* 备 注:无
******************************************************************/
void LCD_DrawPoint(uint16_t x,uint16_t y,uint16_t color)
{
Show_Gram[((LCD_W * y) + x)] = color;
}
2
3
4
5
6
7
8
9
10
11
12
13
- 去到【lv_port_disp.h】文件,将头文件路径 :
#include “lvgl/lvgl.h”
修改为#include “. ./lvgl.h”
修改LVGL配置
- 根据需求修改lv_conf.h中的宏定义
- LV_COLOR_DEPTH:屏幕的色彩深度,支持1bit、8bit、16bit、32bit。
- LV_COLOR_16_SWAP:字节交换,使用SPI或者DMA刷屏的时候需要置1。
- LV_MEM_SIZE:GUI可支配的内存空间,根据使用的功能调节。
- LV_USE_PERF_MONITOR:显示CPU使用率和FPS计数。
- LV_USE_MEM_MONITOR:显示已使用的内存和内存碎片。
- LV_FONT_MONTSERRAT:字体大小,太小会很模糊。
- 修改上面字体时,下面这个地方也要修改:例如改为16大小,则将14改为16即可。
- 这里需要注意,在设置里不能使用
Use MicroLIB
, 不然会卡死。将【Use MicroLIB】选项的打勾取消。
- 如果需要通过 printf 打印一些信息。则将以下代码,写入串口文件bsp_usart.c中。
#pragma import(__use_no_semihosting)
//标准库需要的支持函数
struct __FILE
{
int handle;
};
FILE __stdout;
//定义_sys_exit()以避免使用半主机模式
void _sys_exit(int x)
{
x = x;
}
/************************************************
函数名称 : fputc
功 能 : 串口重定向函数
参 数 :
返 回 值 :
作 者 : LC
*************************************************/
int fputc(int ch, FILE *f)
{
usart_send_data(ch);
// 等待发送数据缓冲区标志置位
return ch;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
这样就可以使用 printf 了。
- 要让LVGL运行还需要给LVGL周期的运行两个函数。【lv_tick_inc】【lv_task_handler】。其中【lv_tick_inc】的运行周期单位为ms。这里我将其放在滴答定时器中断中,每隔1ms调用一次。
7.5 LVGL移植验证
验证代码
在main.c中编写以下代码:
#include "gd32f4xx.h"
#include "systick.h"
#include "bsp_led.h"
#include "lcdinit.h"
#include "lcdgui.h"
#include "bsp_usart.h"
#include "stdio.h"
#include "string.h"
#include "lv_conf.h"
#include "lv_port_disp.h"
#include "lvgl.h"
void Lvgl_Lable_Demo(void)
{
lv_obj_t *scr = lv_scr_act();
lv_obj_t * label1 = lv_label_create(scr);
lv_label_set_long_mode(label1, LV_LABEL_LONG_WRAP);
lv_label_set_recolor(label1, true);
lv_label_set_text(label1, "#0000ff Re-color# #ff00ff words# #ff0000 of a# label, align the lines to the center "
"and wrap long text automatically.");
lv_obj_set_width(label1, 150);
lv_obj_set_style_text_align(label1, LV_TEXT_ALIGN_CENTER, 0);
lv_obj_align(label1, LV_ALIGN_CENTER, 0, -40);
lv_obj_t * label2 = lv_label_create(scr);
lv_label_set_long_mode(label2, LV_LABEL_LONG_SCROLL_CIRCULAR);
lv_obj_set_width(label2, 150);
lv_label_set_text(label2, "It is a circularly scrolling text. ");
lv_obj_align(label2, LV_ALIGN_CENTER, 0, 40);
}
/******************************************************************
* 函 数 名 称:main
* 函 数 说 明:主函数
* 函 数 形 参:无
* 函 数 返 回:无
* 作 者: LC
* 备 注:无
******************************************************************/
int main(void)
{
int i=0;
Subscribe_message_struct data;
nvic_priority_group_set(NVIC_PRIGROUP_PRE2_SUB2); // 优先级分组
//滴答定时器初始化 1us
systick_config();
//sram初始化
exmc_synchronous_dynamic_ram_init(EXMC_SDRAM_DEVICE0);
//串口0初始化(调试)
usart_gpio_config( 115200U );
printf("start\r\n");
//lcd初始化
LCD_Init();
lv_init(); // 初始化lvgl
lv_port_disp_init(); // 显示初始化
Lvgl_Lable_Demo(); //LVGL显示测试
//开启定时器固定刷屏
Lcd_Show_Time_config();
while(1)
{
//定时器循环固定数据刷屏
while(get_show_over_flag());
set_show_update_flag(1);
while(get_show_update_flag());
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
实物展示
7.6 关于其他
LVGL帧率限制
LVGL是有一个帧率刷新周期的宏定义,在lv_conf.h里。LVGL会通过LVGL内部的tick,定时去刷屏幕,也就是说该宏定义限定了LVGL刷屏帧率的上限,默认满帧33帧。这里的30即1000ms/30ms=33FPS,这里我们直接改成10ms刷新一次,满帧100帧。满帧是指达到显示器刷新率的上限。
DPI说明
在lv_conf.h里配置DPI:
/*
* LV_DPI_DEF 注意这里,虽然LVGL的作者说这个没这么重要,但他会严重影响到LVGL的动画效果
* 你应该进行DPI的手动计算,例如280*240分辨率1.69英寸的屏幕,
* 那么 DPI = (280*280+240*240)^0.5 / 1.69 = 218
*/
#define LV_DPI_DEF 218 /*[px/inch]*/
2
3
4
5
6
8. LVGL的UI设计
8.1 工具介绍
本案例使用的是由恩智浦开发的上位机GUI设计工具:GUI Guider。GUI Guider是恩智浦为LVGL开发了一个上位机GUI设计工具,可以通过拖放控件的方式设计LVGL GUI页面,加速GUI的设计。设计完成的GUI页面可以在PC上仿真运行,确认设计完毕之后可以生成C代码,再整合到MCU项目中。
GUI Guider(Version: 1.4.0-GA)的主要特征:
- 支持Windows 10和Ubuntu 20.04。
- 支持中文、英文。
- 兼容LVGL V7和LVGL V8版本。
- 支持拖放的所见即所得(WYSIWYG)用户界面设计。
- 多种字体支持及第三方字体导入。
- 可定制的中文字符范围。
- 小部件对齐方式:左、中、右。
- 自动产生LVGL 的C语言源代码。
- 支持默认样式和自定义样式。
- 演示应用程序集成。
- 实时日志显示。
- 集成上位机仿真器。
注意!!GUI-Guider会依赖javaJDK,因此需要安装jdk.
8.2 工具安装
安装Java-JDK
安装JDK,我使用的JDK见下图(如果你已经安装有JDK,则忽略这里。)
JDK安装我这里就不写了,外面很多关于JDK的安装教程非常详细。
安装GUI-Guider
下图为安装包。(我的电脑是64位 WIN10)
选择中文(简体)
选择同意协议
选择安装路径
等待安装完成
安装完成
8.3 创建UI工程
这里我以480*800分辨率的屏幕作为案例。
选择创建新工程
选择LVGL版本
这里使用的是V8版本的LVGL。
选择设备模板
选择应用模板
这里选择了一个空白模板,如果感兴趣可以试一试官方的模板。
工程配置
屏幕的像素是480x800,我这里选择的16位彩色,面板尺寸自定义为480x800。工程路径请务必放在英文路径。
创建完成
创建完成之后如下图。
8.4 常用组件的应用
标签
点击标签然后拖拽到我们的显示界面上。
标签属性设置
这里主要设置显示的文本内容,和字体。注意:你使用一个字体,它就会生成一个字库给你,所以请慎重考虑内存问题,或者将生成的字库保存至外部FLASH里。
按钮
点击按钮然后拖拽到我们的显示界面上。
按钮属性设置
按钮事件添加
事件是按钮的灵魂,涉及到我们后继代码如何判断按键是否点击。
事件配置里,如果不随便点一个动作,则软件不会生成按钮事件的C代码,所以我选择修改背景色。后面生成代码后再把这个动作的代码删除。
图片
点击图片组件然后拖拽到我们的显示界面上。
添加图片资源
点击我们拉出来的图片组件,在属性设置里,添加我们的图片资源。
在弹出的界面选择添加。
这里我选择了一张蓝牙的图标。
添加完成之后,选择这个图片。
这样就显示出来了。注意,每使用一张图片,都会生成C代码,注意内存问题,或保存至外部FLASH。
生成第二个界面
返回之后,点击+号,生成第二个界面。
点击生成的第二个界面,就可以进行编辑了。
图片按钮
这个图片按钮我们用来进行界面跳转,回到界面一。拉出图片按钮。
这里推荐一个图标网站,对大多数的图标都可以免费下载。
网站链接:iconfont 平台
我下载了返回图标和前进图标。
将图片加载到我们的图片按钮
设置图片按钮的事件为界面切换事件
去到第二个界面,设置返回键。
在返回键添加界面切换事件,让其可以切换到第一个界面
8.5 模拟运行
点击绿色按键,选择C。开始模拟运行。(会比较慢)
运行效果。效果见下方:(大家自行测试)
8.6 生成代码并移植
根据1.69寸屏幕最终配置的UI界面如下:
点击生成代码。
去到这个工程目录下,找到生成的代码。
我之前创建工程时的路径
代码就在工程目录的generated和custom文件夹下。将两个文件夹复制到我们的GD32LVGL工程文件夹里。(LVGL工程参考上一章节的LVGL8.2移植)在LVGL工程下的Hardware文件夹里新建一个文件夹,我们将UI界面文件放在此处。
打开LVGL工程,添加文件和路径。将 generated
和custom
文件夹下的所有 .c 文件添加。
添加完成之中,编译发现有错误,无法打开lv_font.h文件。
双击上图中的错误点,跳转到错误地。将 #include "lv_font.h"
修改为 #include "../lvgl.h"
如果涉及到显示中文,会有中文编码错误的现象。主要问题是编码错误导致将符号 “ ”
给乱码,导致语句错误。
将全部中文的前后都加一个空格。没有错误只有警告。
8.7 移植验证
在main.c中编写如下代码:
/********************************************************************************
* 测试硬件:立创·梁山派开发板GD32F470ZGT6 使用主频200Mhz 晶振25Mhz
* 版 本 号: V1.0
* 修改作者: LC
* 修改日期: 2023年06月12日
* 功能介绍: 增加GD32的DMA/SDRAM/EXMC/TIMER标准库
******************************************************************************
* 梁山派软硬件资料与相关扩展板软硬件资料官网全部开源
* 开发板官网:www.lckfb.com
* 技术支持常驻论坛,任何技术问题欢迎随时交流学习
* 立创论坛:club.szlcsc.com
* 其余模块移植手册:【立创·梁山派开发板】模块移植手册
* 关注bilibili账号:【立创开发板】,掌握我们的最新动态!
* 不靠卖板赚钱,以培养中国工程师为己任
*********************************************************************************/
#include "gd32f4xx.h"
#include "systick.h"
#include "bsp_led.h"
#include "lcdinit.h"
#include "lcdgui.h"
#include "bsp_usart.h"
#include "bsp_aht21.h"
#include "stdio.h"
#include "bsp_ags10.h"
#include "bsp_wf183d.h"
#include "bsp_ec01f.h"
#include "string.h"
#include "bsp_voltage.h"
#include "lv_conf.h"
#include "lv_port_disp.h"
#include "lvgl.h"
#include "gui_guider.h"
lv_ui ui;
/******************************************************************
* 函 数 名 称:main
* 函 数 说 明:主函数
* 函 数 形 参:无
* 函 数 返 回:无
* 作 者: LC
* 备 注:无
******************************************************************/
int main(void)
{
// 优先级分组
nvic_priority_group_set(NVIC_PRIGROUP_PRE2_SUB2);
//滴答定时器初始化 1us
systick_config();
//sram初始化
exmc_synchronous_dynamic_ram_init(EXMC_SDRAM_DEVICE0);
//串口0初始化(调试)
usart_gpio_config( 115200U );
printf("start\r\n");
//lcd初始化
LCD_Init();
// 初始化lvgl
lv_init();
// 显示初始化
lv_port_disp_init();
//设置UI界面显示
setup_ui(&ui);
// 开启定时器固定刷屏
Lcd_Show_Time_config();
while(1)
{
//定时器循环固定数据刷屏
while(get_show_over_flag());
set_show_update_flag(1);
while(get_show_update_flag());
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
效果:
9. 无线2.4G通信模块驱动
9.1 NF-03软件驱动
案例采用的是NF-032.4G通信模块,该模块采用的是SPI接口进行通信,因此我们配置好接口。该模块我们已在模块移植手册中进行移植,欢迎大家查看梁山派模块移植手册 3.15 NF-03无线2.4G通信模块。关于原理部分已在模块移植手册中进行说明,我们讲解如何移植并制作一个从机显示数据。
将模块移植手册上的代码工程文件下载,然后将其Hardware 文件夹下的【si24r1】文件夹复制到我们的LCD工程。
LCD工程参考之前章节NBIOT模块移植的代码工程文件
在LCD工程之中添加【si24r1】文件夹的路径与.c文件。
9.2 从机设计
硬件部分 打板一次发5片,在另一片上只焊接屏幕部分与2.4G通信模块部分即可。如下图,只需要焊接红色方框框住的部分。
软件部分
使用Keil下载【从机LVGL显示】代码。如果对UI不满意可以下载【slave】文件,使用GUI Guider软件打开重新修改即可。
9.3 移植验证
在LCD工程的main.c中编写以下代码:
/********************************************************************************
* 测试硬件:立创·梁山派开发板GD32F470ZGT6 使用主频200Mhz 晶振25Mhz
* 版 本 号: V1.0
* 修改作者: LC
* 修改日期: 2023年08月04日
* 功能介绍:
******************************************************************************
* 梁山派软硬件资料与相关扩展板软硬件资料官网全部开源
* 开发板官网:www.lckfb.com
* 技术支持常驻论坛,任何技术问题欢迎随时交流学习
* 立创论坛:club.szlcsc.com
* 其余模块移植手册:【立创·梁山派开发板】模块移植手册
* 关注bilibili账号:【立创开发板】,掌握我们的最新动态!
* 不靠卖板赚钱,以培养中国工程师为己任
*********************************************************************************/
#include "gd32f4xx.h"
#include "systick.h"
#include "bsp_led.h"
#include "lcdinit.h"
#include "lcdgui.h"
#include "bsp_usart.h"
#include "stdio.h"
#include "string.h"
#include "bsp_aht21.h"
#include "bsp_ags10.h"
#include "bsp_wf183d.h"
#include "bsp_voltage.h"
#include "bsp_ec01f.h"
#include "bsp_si24r1.h"
uint16_t color_buff[7] = {WHITE,BLACK,BLUE,RED,GREEN,YELLOW,GRAY};
/******************************************************************
* 函 数 名 称:main
* 函 数 说 明:主函数
* 函 数 形 参:无
* 函 数 返 回:无
* 作 者: LC
* 备 注:无
******************************************************************/
int main(void)
{
char disp_buf[200] = {0};
int time_value = 0;
uint32_t pressure_val = 0;
uint32_t tvoc_val = 0;
// 优先级分组
nvic_priority_group_set(NVIC_PRIGROUP_PRE2_SUB2);
//滴答定时器初始化 1us
systick_config();
//sram初始化
exmc_synchronous_dynamic_ram_init(EXMC_SDRAM_DEVICE0);
//AHT21温湿度传感器初始化
aht21_gpio_init();
//有害气体传感器初始化
ags10_gpio_init();
//气压传感器初始化
wf183d_gpio_config();
//串口0初始化(调试)
usart_gpio_config( 115200U );
printf("start\r\n");
//NF-03模块初始化
SI24R1_Init();
//设置为发送模式
SI24R1_TX_Mode();
// //NBIOT模组EC-01F初始化
// ec01f_gpio_config();
// //NBIOT模组连接阿里云物联网平台
// ec01f_connect_aliyun();
//lcd初始化
LCD_Init();
//清除全屏为黑色
LCD_Fill(0,0,280,240,BLACK);
//开启显示
LCD_Show_Gram();
//等待一帧数据搬运完成
while(get_show_over_flag());
//开启定时器固定刷屏
Lcd_Show_Time_config();
while(1)
{
//画矩形
LCD_DrawRectangle(30,30,280-30,240-30,WHITE);
//采集温湿度数据
aht21_read_data();
//显示温度
sprintf(disp_buf, "TEMP = %.2f C ",get_temperature() );
LCD_ShowString(60, 240/2-64, (const uint8_t*)disp_buf, WHITE, BLACK, 16, 0);
//显示湿度
sprintf(disp_buf, "HUMI = %.2f %%RH ",get_humidity() );
LCD_ShowString(60, 240/2-32, (const uint8_t*)disp_buf, WHITE, BLACK, 16, 0);
//因不可频繁采集数据,需要间隔一定时间
if( ( time_value++ ) >= 10 )
{
time_value = 0;
//显示有害气体浓度
tvoc_val = ags10_read();
sprintf(disp_buf, "TVOC = %d ppb ",tvoc_val );
LCD_ShowString(60, 240/2, (const uint8_t*)disp_buf, WHITE, BLACK, 16, 0);
}
//显示气压
pressure_val = GetAirPressureValue();
sprintf(disp_buf, "pressure = %d PA ", pressure_val );
LCD_ShowString(60, 240/2+32, (const uint8_t*)disp_buf, WHITE, BLACK, 16, 0);
//按照格式发送数据给从机
//格式:"C=%d-T=%.0f-H=%.0f-P=%d-"
sprintf(disp_buf, "C=%d-T=%.0f-H=%.0f-P=%d-",tvoc_val,get_temperature(),get_humidity(),pressure_val);
SI24R1_TxPacket((unsigned char*)disp_buf);
//电量测量初始化(PC3与sram的控制线冲突,因此采用分时复用方法)
power_voltage_gpio_config();
//测量电量
sprintf(disp_buf, "power = %.0f %% ",get_voltage_value() );
LCD_ShowString(60, 240/2+64, (const uint8_t*)disp_buf, WHITE, BLACK, 16, 0);
//sram重新初始化
exmc_synchronous_dynamic_ram_init(EXMC_SDRAM_DEVICE0);
//定时器循环固定刷屏
while(get_show_over_flag());
set_show_update_flag(1);
while(get_show_update_flag());
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
视频验证:
视频
文件下载
通过百度网盘分享的文件:9-无线2.4G通信模块驱动.zip
链接:https://pan.baidu.com/s/1Wb9qdRquy2lN5CYUrDnRvg?pwd=LCKF
提取码:LCKF
10. 完整案例设计
- 测量温湿度、气压、有害气体等数据;
- 屏幕通过LVGL开源GUI库实时显示测量结果;
- 使用电池可以放电充电;
- 无线通信方式二选一:1.实现NBIOT将数据传输到云端;2.将数据通过无线模块发送出去给接收设备使用;
10.1 设置内容离线存储
GD32F4系列具备备份域(Backup Domain) 功能。备份域是指芯片内部提供的一个独立的区域,它提供了用于存储数据的非易失性存储器(NVM),可以保存关键数据,如配置参数、校准值、状态信息等。用于存储关键数据永久性的保存,以防止意外断电或者其他异常情况下数据丢失。备份域这些数据在断电或重启后依然可以被读取。
备份域的初始化
/**********************************************************
* 函 数 名 称:BKP_init
* 函 数 功 能:开启备份域存储区
* 传 入 参 数:无
* 函 数 返 回:无
* 作 者:LCKFB
* 备 注:共有20个寄存器可以存储,RTC_BKP0~RTC_BKP19
**********************************************************/
void BKP_init(void)
{
//使能电源管理时钟
rcu_periph_clock_enable(RCU_PMU);
//启用对备份域中寄存器的写访问
pmu_backup_write_enable();
// RTC_BKP0
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
最常见的用法是判断是否是第一次开机。举个例子:
RTC_BKP0为备份域的第一个数据存储寄存器,没有操作过的情况下默认为0。
开机判断RTC_BKP0是否为0,如果不为0,说明不是第一次开机。
如果为0,说明是第一次开机,我们要往RTC_BKP0写入一个非0值,好让我们后面判断是否是第一次开机。
//如果为0,说明第一次开机
if( RTC_BKP0 == 0 )
{
//往寄存器RTC_BKP0 里写入非0值。
//让下一次开机判断为非0
RTC_BKP0 = 1;
}
else //如果不是0,说明不是第一次开机
{
}
2
3
4
5
6
7
8
9
10
11
10.2 LCD亮度控制
在硬件设计时,我们为屏幕的背光引脚BLK设置为了PB8。PB8可以设置为PWM的输出引脚,可以复用为定时器1的通道0,定时器3的通道2,定时器9的通道0。
我们通过输出PWM,控制其占空比实现LCD背光亮度的调整。本案例选择的是定时器1的通道0作为PWM输出。
LCD背光引脚PB8初始化如下:
/**********************************************************
* 函 数 名 称:lcd_backlight_init
* 函 数 功 能:LCD背光控制初始化
* 传 入 参 数:无
* 函 数 返 回:无
* 作 者:LCKFB
* 备 注:无
**********************************************************/
void lcd_backlight_init(void)
{
/* 关闭定时器 */
timer_deinit(TIMER1);
/* 开启对应引脚时钟 */
rcu_periph_clock_enable(RCU_LCD_BLK);
/* 开启定时器时钟 */
rcu_periph_clock_enable(RCU_TIMER1);
/* 配置定时器时钟 CK_TIMERx = 4 x CK_APB1 = 4x50M = 200MHZ */
rcu_timer_clock_prescaler_config(RCU_TIMER_PSC_MUL4);
/* 设置引脚模式为复用浮空输出 */
gpio_mode_set(PORT_LCD_BLK, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_LCD_BLK);
gpio_output_options_set(PORT_LCD_BLK, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_LCD_BLK);
/* 设置复用线,具体见数据手册46页开始 */
gpio_af_set(PORT_LCD_BLK, GPIO_AF_1, GPIO_LCD_BLK);
/* 定时器初始化参数结构体定义 */
timer_parameter_struct timer_initpara={0};
timer_initpara.prescaler = 10-1;
timer_initpara.clockdivision = TIMER_CKDIV_DIV1;
timer_initpara.alignedmode = TIMER_COUNTER_EDGE; //触发方式设置根据边沿决定
timer_initpara.counterdirection = TIMER_COUNTER_UP; //设置为向上计数
timer_initpara.period = 8000-1; //设置频率周期
timer_initpara.repetitioncounter = 0;
timer_init(TIMER1, &timer_initpara); //定时器1更新配置
/* 定时器PWM初始化参数结构体定义 */
timer_oc_parameter_struct timer_ocintpara={0};
timer_ocintpara.ocpolarity = TIMER_OC_POLARITY_HIGH; //通道输出极性为高电平有效
timer_ocintpara.outputstate = TIMER_CCX_ENABLE; //使能 PWM 输出到端口
timer_ocintpara.ocnpolarity = TIMER_OCN_POLARITY_HIGH; //通道互补输出极性为高电平
timer_ocintpara.outputnstate = TIMER_CCXN_DISABLE; //通道互补输出状态失能
timer_ocintpara.ocidlestate = TIMER_OC_IDLE_STATE_LOW; //信道输出的空闲状态为低
timer_ocintpara.ocnidlestate = TIMER_OCN_IDLE_STATE_LOW; //信道互补输出的空闲状态为低
/* 将修改的内容设置到定时器的通道上 */
timer_channel_output_config(TIMER1,TIMER_CH_0,&timer_ocintpara);
/*设置定时器1通道0输出占空比0 */
timer_channel_output_pulse_value_config(TIMER1, TIMER_CH_0, 0);
/* 设置定时器通道的输出模式为PWM0模式 */
timer_channel_output_mode_config(TIMER1, TIMER_CH_0, TIMER_OC_MODE_PWM0);
/* 设置定时器1所有通道禁止输出比较影子寄存器 */
timer_channel_output_shadow_config(TIMER1, TIMER_CH_0, TIMER_OC_SHADOW_DISABLE);
/* 使能TIMERx_CAR寄存器的影子寄存器 */
timer_auto_reload_shadow_enable(TIMER1);
/* 所有通道输出使能 */
timer_primary_output_config(TIMER1, ENABLE);
/* 使能定时器 */
timer_enable(TIMER1);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
LCD背光设置函数如下: 当传入的value为0时,屏幕熄灭。建议是不能由人为的控制屏幕熄灭。如果可以设置屏幕熄灭,下次打开发现屏幕一直不亮,以为是电路出了问题,结果是屏幕的亮度设置为了0。因此在代码中,我们需要限制屏幕亮度的范围10~100。
百分比公式: 百分比值 = 当前值 / 总值 _ 100;那么知道了百分比,就可以根据百分比公式换算出当前要设置的值。要设置的值 = 百分比值 _ 总值 / 100;
/**********************************************************
* 函 数 名 称:set_lcd_backlight
* 函 数 功 能:设置LCD背光亮度
* 传 入 参 数:value=亮度百分比 范围0~100 100最亮
* 函 数 返 回:无
* 作 者:LCKFB
* 备 注:无
**********************************************************/
void set_lcd_backlight(uint32_t value)
{
uint32_t timer_value = 0;
if( value < 10 ) value = 10;
if( value > 100 ) value = 100;
timer_value = value * ( 8000 - 1 ) / 100.0;
timer_channel_output_pulse_value_config(TIMER1, TIMER_CH_0, timer_value);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
10.3 常见控件的控制方法
10.3.1 Label(标签)
介绍 标签是用来显示文本的基本对象类型。
控制方法
可以在运行时使用 lv_label_set_text(label, "New text")
设置标签上的文本。
使用 lv_label_set_text_fmt(label, "Value: %d", 15)
printf 格式可用于设置文本。
使用示例:
//显示 "梁山派"
lv_label_set_text(gui_ui.screen_label_humi_val, "梁山派");
2
图标显示
LVGL中自带有一些常用的图标,通过字体的方式显示出来。使用前需要配置好内置字体的大小。
使用示例:
配置字体(图标)大小为24。
//设置标签对象my_label 显示图标 “√”
lv_label_set_text(my_label, LV_SYMBOL_OK);
2
按照以上步骤,就会显示出一个打勾的图标。
10.3.2 Switch(开关)
介绍
开关看起来像一个小滑块,开关的功能类似于按钮,也可以用来打开和关闭某些东西。
控制方法 当开关被打开时,开关的状态会变为 LV_STATE_CHECKED 。我们可以通过下面这个接口获取开关当前的状态:
lv_obj_has_state(switch, LV_STATE_CHECKED); // 返回 bool 类型, 开-true ; 关-false
示例:
//定义一个开关对象
lv_obj_t *screen_set_sw_slave;
//如果 开关 的状态为打开状态
if( lv_obj_has_state( screen_set_sw_slave, LV_STATE_CHECKED ) == true );
2
3
4
一般我们通过触摸或按键控制让 开关 打开/关闭,我们也可以通过下面这个接口来主动打开/关闭开关:
lv_obj_add_state(switch, LV_STATE_CHECKED); // 开
lv_obj_clear_state(switch, LV_STATE_CHECKED); // 关
2
示例:
typedef struct
{
lv_obj_t *screen_set_sw_slave;
}lv_ui;
//如果开关当前是打开状态
if( lv_obj_has_state(gui_ui->screen_set_sw_slave, LV_STATE_CHECKED) == true )
{
//设置开关为关闭状态
lv_obj_clear_state(gui_ui->screen_set_sw_slave, LV_STATE_CHECKED);
}
else//如果开关当前是关闭状态
{
//设置开关为选中状态
lv_obj_add_state(gui_ui->screen_set_sw_slave, LV_STATE_CHECKED);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
10.3.3 Bar(进度条)
介绍
进度条对象有一个背景和一个指示器。 指示器的宽度根据进度条的当前值自动设置。如果设置进度条的宽度小于其高度,就可以创建出垂直摆放的进度条。可以通过设置进度条的值,从而改变指标的开始位置。
控制方法
可以通过lv_bar_set_value(bar, new_value, LV_ANIM_ON/OFF)
设置新值。 该值在可以使用 lv_bar_set_range(bar, min, max)
修改的范围(最小值和最大值)内进行解释。 进度条默认的范围是 0..100。
使用示例:
//设置进度值为50,并开启移动动画
lv_bar_set_value(gui_ui->screen_set_bar_light, 50, LV_ANIM_ON);
2
10.4 内存不足优化方法
当大家使用的控件或图片过多时,会导致我们的内存不足,编译报错。
增加堆栈空间大小
图中Stack_Size
就是栈空间大小的配置,一般函数内的局部变量占用栈空间,并且函数执行完毕后会被自动回收,根据自己的芯片手册以及代码的编写使用,合理分配即可,例如图中的0x00004000,十进制是16384,即16KB空间的栈内存。
图中Heap_Size
就是堆空间大小的配置,堆空间主要是程序员自己申请的空间,例如malloc,new申请的内存占用堆空间,用完后需要使用free或delete手动释放,否则会造成内存泄漏、野指针的问题。根据自己的芯片手册以及代码的编写使用,合理分配即可。
降低显示缓冲区的大小
在LVGL的显示初始化中,选择方式1,单BUFF刷屏,一次刷10行。刷新行数可以调整。
也可以通过使用SDRAM作为缓冲区。
使用前需要初始化SDRAM
降低LVGL内存大小
减少 lv_conf.h
中的 LV_MEM_SIZE
。 当您创建按钮、标签等对象时会使用此内存。要使用较低的“LV_MEM_SIZE”,您可以仅在需要时创建对象并在不再需要时将其删除。
禁用多余功能
在 lv_conf.h 中禁用所有未使用的功能(例如动画、文件系统、GPU 等)和对象类型。
减少图片字体的使用
减少LVGL中图片、字体的使用。图标可以采用LVGL内部自带的常用图标,使用方法见10.3.1 Label(标签);字体尽量采用同一种风格,不要生成多种风格,多种大小的字体占用内存。
10.5 按键状态扫描
由于按键的物理特性以及电气接触的不稳定性会导致有抖动现象。虽然只按下按键一次,但是因为有抖动的原因,导致误判断按下很多次。
按键消抖是为了解决按键在物理接触时可能产生的抖动信号而采取的措施。当我们按下一个按键时,由于按键的机械性质和电气接触的不稳定性,可能会导致按键信号在短时间内多次变化,产生抖动现象。
以下是一些常见的按键消抖方法:
- 软件延时消抖:通过软件延时来忽略短时间内的按键抖动。在检测到按键按下时,通过延时一段时间后再次检测按键状态,只有当两次检测结果一致时才确认按键按下。这种方法简单易实现,但需要根据实际情况设置合适的延时时间。
- 硬件 RC 滤波:在按键与微控制器的连接处添加一个 RC 滤波电路,通过 RC 电路的低通滤波特性来消除按键信号的高频抖动。该方法需要在电路设计时加入滤波电路,并根据按键特性选择合适的 RC 参数。
- 硬件滤波芯片:使用专门的按键消抖芯片,例如倒扣稳压器(Debouncing IC)或滤波器 IC,它们内部集成了消抖电路,可以直接使用。
以上是一些常见的按键消抖方法,本案例采用的是软件消抖的方法。而使用软件消抖又有很多种方法。以下是一些常见的软件消抖方法:
- 状态机:使用状态机来对按键状态进行监测和处理。当检测到按键按下时,状态机会在一段时间内继续检测按键状态,只有在该时间内连续检测到按键状态稳定时才确认按键按下。
- 计时器:通过设置一个计时器,在按键按下时启动计时,在一段时间内持续检测按键状态。只有在该时间内连续检测到按键状态稳定时才确认按键按下。
- 中断触发:使用中断来监听按键的状态改变。当按键按下时触发中断,中断服务程序中可以进行进一步的按键状态判断和消抖处理。
- 去抖动延时函数:在按键按下时使用延时函数进行一段时间的延时,然后再次读取按键状态进行确认。只有在延时期间两次读取的按键状态一致时才确认按键按下。
- 滑动窗口平均滤波:通过维护一个固定大小的滑动窗口,将连续多次的按键读取值进行平均计算,从而滤除抖动信号。
本案例采用的是定时器扫描消抖法,使用定时器来定期触发按键扫描,然后在每次定时器中断或定时器周期到达时读取按键状态并进行消抖处理。通过定时器的定期触发,可以在一定的时间间隔内进行按键状态的采样和处理,从而消除按键的抖动效应。在实现定时器扫描消抖法时,需要合理设置定时器的触发频率,以确保能够检测到按键状态的变化,并进行消抖处理。
定时器初始化代码如下:
使用的定时器是LCD的刷屏定时器
//30ms周期
void Lcd_Show_Time_config(void)
{
timer_parameter_struct timer_initpara;
nvic_irq_enable(TIMER3_IRQn, 0, 1); // 设置中断优先级
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 = 30000 -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); // 中断使能
timer_enable(TIMER3);
}
/******************************************************************
* 函 数 名 称:TIMER3_IRQHandler
* 函 数 说 明:定时器3中断服务函数
* 函 数 形 参:无
* 函 数 返 回:无
* 作 者:LCKFB
* 备 注:LCD的显示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();
}
//定时器循环固定数据刷屏
if( get_show_update_flag() == 0 )
{
if(get_show_over_flag() == 0){
set_show_update_flag(1);
}
}
//有害气体采集时间判断
tvoc_measured();
//设置键扫描
set_key_scan();
//左键扫描
left_key_scan();
//右键扫描
right_key_scan();
//返回键扫描
return_key_scan();
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
具体的按键任务如下:只有在设置界面时才用到按键。
//全部按键扫描
void all_key_scan(lv_ui *gui_ui)
{
int light_val = RTC_BKP0;
//0=亮度调节选项
//1=上传云端功能开关选项
//2=从机显示功能开关选项
static char set_select_flag = 0;
char disp_buf[10]={0};
//当前样式
static lv_style_t style_screen_set_spangroup_new_main_main_default;
//之前样式
static lv_style_t style_screen_set_spangroup_again_main_main_default;
//左按键按下
if( get_left_button_state() == 1 )
{
set_left_button_state(0);
if( mode_select == SET_MODE )
{
//设置的选项向上
if( set_select_flag != 0 )
set_select_flag = set_select_flag - 1;
else
set_select_flag = 2;
//选中的设置选项背景色修改为蓝色
//设置背景色
lv_style_set_bg_color(&style_screen_set_spangroup_new_main_main_default, lv_color_make(0x21, 0x95, 0xf6));
//设置透明度
lv_style_set_bg_opa(&style_screen_set_spangroup_new_main_main_default, 127);
//更新样式
lv_obj_add_style((set_select_flag==0)?gui_ui->screen_set_spangroup_6:(set_select_flag==1)?gui_ui->screen_set_spangroup_1:gui_ui->screen_set_spangroup_2,\
&style_screen_set_spangroup_new_main_main_default, LV_PART_MAIN|LV_STATE_DEFAULT);
//之前被选中的设置选项背景色修改为原色
//设置透明度
lv_style_set_bg_opa(&style_screen_set_spangroup_again_main_main_default, 0);
//更新样式
lv_obj_add_style((set_select_flag==0)?gui_ui->screen_set_spangroup_1:(set_select_flag==1)?gui_ui->screen_set_spangroup_2:gui_ui->screen_set_spangroup_6,\
&style_screen_set_spangroup_again_main_main_default, LV_PART_MAIN|LV_STATE_DEFAULT);
}
}
//右按键按下
if( get_right_button_state() == 1 )
{
set_right_button_state(0);
if( mode_select == SET_MODE )
{
//设置的选项向下
set_select_flag = ( set_select_flag + 1 ) % 3;
//选中的设置选项背景色修改为蓝色
//设置背景色
lv_style_set_bg_color(&style_screen_set_spangroup_new_main_main_default, lv_color_make(0x21, 0x95, 0xf6));
//设置透明度
lv_style_set_bg_opa(&style_screen_set_spangroup_new_main_main_default, 127);
//更新样式
lv_obj_add_style((set_select_flag==0)?gui_ui->screen_set_spangroup_6:(set_select_flag==1)?gui_ui->screen_set_spangroup_1:gui_ui->screen_set_spangroup_2,\
&style_screen_set_spangroup_new_main_main_default, LV_PART_MAIN|LV_STATE_DEFAULT);
//之前被选中的设置选项背景色修改为原色
//设置透明度
lv_style_set_bg_opa(&style_screen_set_spangroup_again_main_main_default, 0);
//更新样式
lv_obj_add_style((set_select_flag==0)?gui_ui->screen_set_spangroup_2:(set_select_flag==1)?gui_ui->screen_set_spangroup_6:gui_ui->screen_set_spangroup_1,\
&style_screen_set_spangroup_again_main_main_default, LV_PART_MAIN|LV_STATE_DEFAULT);
}
}
//设置键按下
if( get_set_button_state() == 1 )
{
set_set_button_state(0);
lv_disp_t * d = lv_obj_get_disp(lv_scr_act());
if (d->prev_scr == NULL && d->scr_to_load == NULL)
{
//如果当前在主界面
if( mode_select == MAIN_MODE )
{
//切换到设置界面
//如果设置界面被删除
if (gui_ui->screen_set_del == true)
//设置界面重新初始化
setup_scr_screen_set(gui_ui);
//显示设置界面动画
lv_scr_load_anim(gui_ui->screen_set, LV_SCR_LOAD_ANIM_OVER_TOP, 0, 0, true);
//删除主界面
gui_ui->screen_del = true;
//当前模式修改为设置模式
mode_select = SET_MODE;
//设置界面显示内容初始化(根据保存在后备区的内容设置显示值)
set_interface_disp_init(gui_ui);
}
else//如果当前在设置界面
{
//切换到主界面
//如果主界面被删除
if (gui_ui->screen_del == true)
//主界面初始化
setup_scr_screen(gui_ui);
//显示主界面动画
lv_scr_load_anim(gui_ui->screen, LV_SCR_LOAD_ANIM_OVER_TOP, 0, 0, true);
//删除设置界面
gui_ui->screen_set_del = true;
//当前模式修改为主界面模式
mode_select = MAIN_MODE;
}
}
}
//返回键按下
if( get_return_button_state() == 1 )
{
set_return_button_state(0);
if( mode_select == SET_MODE )
{
switch( set_select_flag )
{
case 0://如果当前选择到亮度调整
//亮度值自增
light_val = ( light_val + 10 ) % 110;
//滑动条根据亮度值进行变化
lv_bar_set_value(gui_ui->screen_set_bar_light, light_val, LV_ANIM_ON);
//屏幕显示设置的亮度值
sprintf(disp_buf, "%d%%",light_val);
lv_label_set_text(gui_ui->screen_set_label_light, disp_buf);
//设置LCD亮度
set_lcd_backlight(light_val);
//将数据保存到备份域0
RTC_BKP0 = light_val;
break;
case 1://如果当前选择到云端上传开关
//如果当前是打开状态
if( lv_obj_has_state(gui_ui->screen_set_sw_server, LV_STATE_CHECKED) == true )
{
//设置云端上传开关为关闭状态
lv_obj_clear_state(gui_ui->screen_set_sw_server, LV_STATE_CHECKED);
//设置云端上传标志位 为 关闭状态
switch_server = SWITCH_CLOSE;
//将数据保存到备份域1
RTC_BKP1 = switch_server;
}
else//如果当前是关闭状态
{
//设置云端上传开关为选中状态
lv_obj_add_state(gui_ui->screen_set_sw_server, LV_STATE_CHECKED);
//设置云端上传标志位 为 打开状态
switch_server = SWITCH_OPEN;
//将数据保存到备份域1
RTC_BKP1 = switch_server;
}
break;
case 2://如果当前选择到从机显示开关
//如果当前是打开状态
if( lv_obj_has_state(gui_ui->screen_set_sw_slave, LV_STATE_CHECKED) == true )
{
//设置从机显示开关为关闭状态
lv_obj_clear_state(gui_ui->screen_set_sw_slave, LV_STATE_CHECKED);
//设置从机显示标志位 为 关闭状态
switch_slave = SWITCH_CLOSE;
//将数据保存到备份域2
RTC_BKP2 = switch_slave;
}
else//如果当前是关闭状态
{
//设置从机显示开关为选中状态
lv_obj_add_state(gui_ui->screen_set_sw_slave, LV_STATE_CHECKED);
//设置从机显示标志位 为 打开状态
switch_slave = SWITCH_OPEN;
//将数据保存到备份域2
RTC_BKP2 = switch_slave;
}
break;
}
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
文件下载10.完整案例.zip
通过百度网盘分享的文件:10.完整案例.zip
链接:https://pan.baidu.com/s/1RvYaGXQnSJNdWqkujUTWRw?pwd=LCKF
提取码:LCKF