Day03_水质水位监测项目

01_水质水位监测项目_水质模块_思路梳理和硬件连线

水质模块概述

在水质水位监测项目中,水质模块是核心功能之一,主要用于检测水中的总溶解固体量(TDS)。TDS 是衡量水质的重要指标,它反映了水中溶解的钙盐、镁盐等矿物质的含量。通过检测 TDS 值,我们可以了解水的硬度和洁净程度,从而判断水质的好坏.

测量原理与数据处理

测量原理

TDS 传感器模块通过检测水中的导电粒子(离子)数量来测量 TDS 值。水中溶解的离子越多,导电性越好,传感器采集到的电压值也就越高。传感器采集到的电压值是一个模拟电压,需要通过外置 ADC(如 ADS1115)转换为数字电压,再传输给 STM32 进行处理.

数据处理

  • 电压与 TDS 关系:TDS 与电压之间的关系不是简单的线性关系,而是一个复杂的三次曲线关系。幸运的是,传感器手册中已经给出了这个曲线的方程,我们可以直接使用这个方程来计算 TDS 值.
  • 模拟电压转换:STM32 收到的数字电压值是通过 ADC 转换得到的,我们需要将其转换回原始的模拟电压值,然后再代入 TDS 计算公式中求得 TDS 值.
  • 计算公式:根据手册提供的三次曲线方程,将模拟电压值代入公式计算出 TDS 值.公式中包含多个系数,有整数和小数,计算时要注意数据类型和精度.

硬件连线

TDS 模块连线

  • 探针连接:TDS 探针的接口是一个两针接口,直接插入水质模块板子上的白色接口中即可.
  • 电源连接:水质模块需要接电源和地,将 VCC 和 GND 分别接到开发板上的电源和地引脚.
  • 模拟输出连接:将水质模块的 AO(模拟输出)引脚连接到外置 ADC 模块的 A0 引脚,作为 ADC 的一路输入.

ADC 模块连线

  • 输入连接:外置 ADC 模块的 A0 引脚接收来自水质模块的模拟电压信号.
  • 通信连接:ADC 模块通过 I2C 与 STM32 通信,将 SCL(时钟线)和 SDA(数据线)分别连接到 STM32 的 I2C2 引脚(如 PB10 和 PB11).
  • 电源连接:ADC 模块的 VDD 和 GND 分别接电源和地.
  • 地址线连接:ADC 模块的 ADDR 引脚决定设备地址,将其接地即可设置为默认地址 0x48.

注意事项

  • 接线准确性:在进行硬件连线时,要确保接线准确无误,避免接错引脚导致模块无法正常工作.
  • 电源稳定性:水质模块和 ADC 模块都需要稳定的电源供电,确保电源电压和电流符合模块的要求.
  • 通信配置:在 STM32 中配置 I2C 通信时,要设置正确的设备地址和通信参数,确保能够正确读取 ADC 模块的数据.

通过以上步骤,我们完成了水质模块的思路梳理和硬件连线,为后续的代码编写和功能实现奠定了基础.

02_水质水位监测项目_水质模块_工程配置和驱动层

工程配置

在开始编写代码之前,我们需要对项目进行工程配置,以确保各个模块能够正确地组织和编译。以下是具体的配置步骤:

添加文件到工程

  1. 驱动层:将之前实现的 I2C 驱动层代码复制到 driver 文件夹下。由于我们之前已经为其他模块实现过 I2C 驱动,这里可以直接复用,不需要做太多修改.
  2. 接口层:在 interface 文件夹下创建一个新的文件夹 ADC,用于存放与外置 ADC(ADS1115)相关的接口代码。创建 ADS1115.cADS1115.h 文件,用于定义与 ADS1115 芯片通信的接口函数.
  3. 应用层:在 APP 文件夹下创建一个新的文件夹 waterTDS,用于存放水质监测模块的代码。创建 TDS.cTDS.h 文件,用于实现水质监测的逻辑.

配置工程文件组

在 Keil 工程中,将上述文件添加到对应的文件组中:

  • driver:添加 I2C.c 文件.
  • interface:添加 ADS1115.c 文件.
  • APP:添加 TDS.c 文件.

配置包含路径

在工程的 C/C++ 选项中,为每个文件组配置相应的包含路径,以便编译器能够找到所需的头文件:

  • driver:配置 I2C 头文件的路径.
  • interface:配置 ADS1115 头文件的路径.
  • APP:配置 TDS 头文件的路径.

驱动层实现

I2C 驱动层

由于我们之前已经为其他模块实现过 I2C 驱动,这里可以直接复用。I2C 驱动层主要负责与外置 ADC 模块进行通信,实现数据的读写操作。以下是 I2C 驱动层的关键函数:

  • I2C 初始化:配置 I2C 时钟、工作模式等参数,使其能够与 ADS1115 正常通信.
  • 发送起始信号:产生 I2C 通信的起始信号,开始一次数据传输.
  • 发送停止信号:产生 I2C 通信的停止信号,结束一次数据传输.
  • 发送设备地址:发送 ADS1115 的设备地址,并等待其应答,确保通信成功.
  • 发送数据:向 ADS1115 发送数据,如配置寄存器的值.
  • 读取数据:从 ADS1115 读取数据,如转换结果.

I2C 通信流程

I2C 通信的基本流程如下:

  1. 发送起始信号:通过 I2C_Start() 函数产生 I2C 通信的起始信号.
  2. 发送设备地址:通过 I2C_SendAddr() 函数发送 ADS1115 的设备地址,并等待其应答.设备地址由芯片的固定部分和可配置部分组成,可配置部分通过连接不同的引脚来设置.
  3. 发送指针寄存器值:如果需要读写特定的寄存器,先发送指针寄存器的值,指定要操作的寄存器地址.
  4. 发送/读取数据:根据需要发送或读取数据.发送数据时,通过 I2C_SendByte() 函数发送一个字节的数据;读取数据时,通过 I2C_ReadByte() 函数读取一个字节的数据.
  5. 发送停止信号:数据传输完成后,通过 I2C_Stop() 函数产生 I2C 通信的停止信号,结束本次通信.

注意事项

  • 设备地址配置:确保 ADS1115 的设备地址与 I2C 驱动层中发送的地址一致,可以通过连接不同的引脚来设置设备地址的可配置部分.
  • 通信稳定性:在 I2C 通信过程中,要注意通信的稳定性,避免出现通信失败的情况.可以通过检查应答信号等方式来判断通信是否成功.
  • 数据格式:在发送和读取数据时,要注意数据的格式和大小,确保数据能够正确地传输和解析.

通过以上步骤,我们完成了水质模块的工程配置和驱动层实现,为后续的接口层和应用层开发奠定了基础.

03_水质水位监测项目_水质模块_接口层_寄存器宏定义

寄存器宏定义概述

在水质模块的接口层实现中,寄存器宏定义是关键的一步。它能够提高代码的可读性和可维护性,使我们在操作寄存器时更加直观和方便。通过宏定义,我们可以将复杂的寄存器地址和位操作转换为易于理解的符号名称,从而简化代码编写和调试过程.

宏定义实现步骤

设备地址宏定义

首先,我们需要定义 ADS1115 的设备地址。根据芯片的连接方式,我们可以确定其地址:

#define ADS1115_ADDR (0x48)  // 设备地址,ADDR引脚接地时为0x48
#define ADS1115_ADDR_W (ADS1115_ADDR << 1)  // 写地址,左移一位
#define ADS1115_ADDR_R (ADS1115_ADDR_W | 0x01)  // 读地址,在写地址基础上加1

指针寄存器宏定义

指针寄存器用于选择要操作的寄存器地址。我们定义其掩码和各种配置情况:

#define ADS1115_REG_POINTER_MASK (0x03)  // 指针寄存器掩码,后两位有效

// 指针寄存器配置情况
#define ADS1115_POINTER_CONVERSION (0x00)  // 转换寄存器
#define ADS1115_POINTER_CONFIG (0x01)  // 配置寄存器
#define ADS1115_POINTER_LOWTHRESH (0x02)  // 低阈值寄存器
#define ADS1115_POINTER_HITHRESH (0x03)  // 高阈值寄存器

配置寄存器宏定义

配置寄存器包含多个字段,我们需要定义每个字段的掩码和可能的配置值:

#define ADS1115_CONFIG_MUX_MASK (0xE000)  // MUX字段掩码
#define ADS1115_CONFIG_PGA_MASK (0x1C00)  // PGA字段掩码
#define ADS1115_CONFIG_MODE_MASK (0x0100)  // MODE字段掩码

// MUX字段配置
#define ADS1115_CONFIG_MUX_DIFF_P0_N1 (0x0000)  // 差分输入 P0-N1
#define ADS1115_CONFIG_MUX_DIFF_P0_N3 (0x2000)  // 差分输入 P0-N3
#define ADS1115_CONFIG_MUX_DIFF_P1_N3 (0x4000)  // 差分输入 P1-N3
#define ADS1115_CONFIG_MUX_DIFF_P2_N3 (0x6000)  // 差分输入 P2-N3
#define ADS1115_CONFIG_MUX_SINGLE_P0 (0x8000)  // 单端输入 P0
#define ADS1115_CONFIG_MUX_SINGLE_P1 (0xA000)  // 单端输入 P1
#define ADS1115_CONFIG_MUX_SINGLE_P2 (0xC000)  // 单端输入 P2
#define ADS1115_CONFIG_MUX_SINGLE_P3 (0xE000)  // 单端输入 P3

// PGA字段配置
#define ADS1115_CONFIG_PGA_6_144V (0x0000)  // 满量程 6.144V
#define ADS1115_CONFIG_PGA_4_096V (0x0200)  // 满量程 4.096V
#define ADS1115_CONFIG_PGA_2_048V (0x0400)  // 满量程 2.048V
#define ADS1115_CONFIG_PGA_1_024V (0x0600)  // 满量程 1.024V
#define ADS1115_CONFIG_PGA_0_512V (0x0800)  // 满量程 0.512V
#define ADS1115_CONFIG_PGA_0_256V (0x0A00)  // 满量程 0.256V

// MODE字段配置
#define ADS1115_CONFIG_MODE_CONTINUOUS (0x0000)  // 连续转换模式
#define ADS1115_CONFIG_MODE_SINGLE (0x0100)  // 单次转换模式

宏定义使用示例

在实际代码中,我们可以利用这些宏定义来简化寄存器配置操作。例如,要配置 ADS1115 为单端输入 P0,满量程 4.096V,并设置为连续转换模式,可以这样写:

uint16_t config = ADS1115_CONFIG_MUX_SINGLE_P0 | ADS1115_CONFIG_PGA_4_096V | ADS1115_CONFIG_MODE_CONTINUOUS;

这样,代码的可读性大大提高,我们一眼就能看出当前的配置情况,而不需要去查阅寄存器手册来分析每一位的具体含义.

注意事项

  • 宏定义一致性:在定义宏时,要确保其与寄存器手册中的描述一致,避免因定义错误导致配置失败.
  • 代码可读性:尽量使用有意义的宏名称,使代码易于理解和维护,避免使用过于复杂或难以理解的宏定义.
  • 灵活性与可扩展性:在定义宏时,可以考虑其灵活性和可扩展性,以便在后续开发中能够方便地进行修改和扩展.

通过以上步骤,我们完成了水质模块接口层的寄存器宏定义,为后续的寄存器操作和配置提供了便利,使代码更加清晰、简洁和易于维护.

04_水质水位监测项目_水质模块_接口层_代码实现

在之前的课程中,我们已经完成了水质水位监测项目中水质模块的头文件定义,主要涉及寄存器的掩码定义和相关操作。今天,我们将深入到代码实现部分,具体讲解如何在 C 文件中实现水质模块的接口层功能。希望通过本课程的学习,大家能够更好地理解水质模块的工作原理,并掌握接口层代码的编写技巧。

一、寄存器掩码定义的修正

在头文件定义阶段,我们对寄存器的掩码进行了定义。这里需要特别注意一个细节:指针寄存器的掩码定义。对于转换寄存器,其掩码定义为 0x00 是没有问题的;而对于配置寄存器,其掩码定义为 0x01 也符合要求,因为它的最后一位是 1。但是,对于高低阈值寄存器,虽然我们没有实际用到,但其掩码配置存在错误。正确的配置应该是最后两位分别为 0x020x03,因为十六进制数中的一位代表四位二进制位。在 C 语言中,我们只能将二进制数表示为十六进制数,并在前面加上 0x。所以,大家在编写代码时,一定要注意二进制和十六进制的转换,避免混淆。

二、初始化函数的实现

1. 初始化流程概述

初始化函数的实现是接口层代码编写的第一步。我们需要按照以下流程进行操作:

  • 首先,调用 I2C_Init 函数对 I2C 模块进行初始化。这个函数我们之前已经编写过,直接调用即可。
  • 接下来,设置配置寄存器。这一步涉及到读写时序的操作,我们需要按照写入时序图来进行。

2. 写入时序详解

写入时序的具体步骤如下:

  • 发送开始信号:调用 I2C_Start 函数,向 I2C 总线发送开始信号。
  • 发送写地址:调用 sendADDR 函数,发送设备的写地址。这个地址是我们在头文件中定义的 ADDRW
  • 发送指针寄存器的值:向指针寄存器写入要操作的寄存器地址。例如,如果要操作配置寄存器,就发送 0x01
  • 发送数据:向配置寄存器发送数据。这里需要注意的是,配置寄存器的数据是两个字节,我们需要按照定义的掩码进行配置。
  • 发送结束信号:调用 I2C_Stop 函数,发送结束信号,完成整个写入过程。

3. 配置寄存器数据的准备

在发送数据之前,我们需要先准备好要写入配置寄存器的数据。由于配置寄存器包含多个字段,如 MUX、PGA 和工作模式等,我们需要先分别配置这些字段,然后将它们拼接成一个完整的数据。

  • 定义临时变量:定义一个 uint16_t 类型的变量 temp,用于存储要写入的数据。
  • 配置 MUX 字段:根据需要选择的输入源,对 temp 进行配置。例如,如果选择 single_a0,则将 tempADS1115_MUX_SINGLE_A0 进行或操作。
  • 配置 PGA 字段:根据需要设置的满量程值,对 temp 进行配置。例如,如果设置为 4.096V,则将 tempADS1115_PGA_4_096V 进行或操作。
  • 配置工作模式字段:根据需要设置的工作模式,对 temp 进行配置。例如,如果设置为连续模式,则将 tempADS1115_MODE_CONT 进行或操作。

4. 发送数据

在准备好要写入的数据后,我们就可以按照写入时序发送数据了。首先发送高位字节,然后发送低位字节。

  • 发送高位字节:将 temp 右移 8 位,得到高位字节,然后调用 I2C_SendByte 函数发送。
  • 发送低位字节:将 temp0xFF 进行与操作,得到低位字节,然后调用 I2C_SendByte 函数发送。

三、读取转换结果函数的实现

1. 读取流程概述

读取转换结果函数的实现是接口层代码编写的核心部分。我们需要按照以下流程进行操作:

  • 首先,发出开始信号。
  • 然后,发送写地址,并发送转换寄存器的地址,进行假写操作。
  • 接着,发送停止信号,并再次发出开始信号。
  • 然后,发送读地址,开始真读操作。
  • 最后,读取转换寄存器的值,并将其转换为模拟电压值。

2. 假写真读时序详解

假写真读时序的具体步骤如下:

  • 发送开始信号:调用 I2C_Start 函数,向 I2C 总线发送开始信号。
  • 发送写地址:调用 sendADDR 函数,发送设备的写地址 ADDRW
  • 发送转换寄存器的地址:向指针寄存器写入转换寄存器的地址 0x00
  • 发送停止信号:调用 I2C_Stop 函数,发送停止信号。
  • 再次发送开始信号:调用 I2C_Start 函数,再次向 I2C 总线发送开始信号。
  • 发送读地址:调用 sendADDR 函数,发送设备的读地址 ADDRR

3. 读取转换寄存器的值

在完成假写真读后,我们就可以读取转换寄存器的值了。

  • 定义接收变量:定义两个 uint8_t 类型的变量 highlow,用于接收高位和低位字节。
  • 读取高位字节:调用 I2C_ReadByte 函数,读取高位字节,并将其存储在 high 中。
  • 发送应答信号:调用 I2C_ACK 函数,发送应答信号。
  • 读取低位字节:再次调用 I2C_ReadByte 函数,读取低位字节,并将其存储在 low 中。

4. 转换为模拟电压值

读取到的转换寄存器的值是一个数字电压值,我们需要将其转换为模拟电压值。

  • 拼接数据:将 high 左移 8 位,与 low 进行或操作,得到完整的 16 位数字电压值。
  • 计算模拟电压值:根据等比例关系,将数字电压值转换为模拟电压值。公式为:y = x * 4.096 / 32767,其中 x 为数字电压值,y 为模拟电压值。

四、注意事项

在编写接口层代码时,需要注意以下几点:

  • 二进制和十六进制的转换:在定义寄存器掩码时,要正确地进行二进制和十六进制的转换,避免混淆。
  • 数据类型的匹配:在处理数字电压值时,要注意数据类型的匹配。由于数字电压值可能是负数,所以接收变量应该定义为 int16_t 类型。
  • 读写时序的准确性:在进行 I2C 通信时,要严格按照读写时序图进行操作,确保每个步骤的准确性。
  • 代码的可读性:在编写代码时,要注意代码的可读性。可以使用宏定义和注释来提高代码的可读性,方便后续的维护和调试。

五、总结

通过本课程的学习,我们掌握了水质水位监测项目中水质模块接口层代码的实现方法。从寄存器掩码的定义修正,到初始化函数和读取转换结果函数的实现,我们详细讲解了每个步骤的操作和注意事项。希望大家在实际开发过程中,能够灵活运用所学知识,编写出高效、可靠的代码。同时,也希望大家在学习过程中,多思考、多实践,不断提高自己的编程能力和技术水平。

05_水质水位监测项目_水质模块_应用层实现和测试

在之前的课程中,我们已经完成了水质水位监测项目中水质模块的驱动层和接口层的实现。接下来,我们将进入应用层的具体实现阶段。应用层的实现相对简单,主要涉及到 TDS 值的计算和显示。通过本课程的学习,希望大家能够更好地理解应用层的功能,并掌握其实现方法。

一、应用层概述

应用层的主要任务是将底层获取到的模拟电压值转换为 TDS 值,并进行显示。与水位模块不同,TDS 模块不需要进行校准,因为其测量原理和计算公式已经确定。我们只需要将模拟电压值代入公式,计算出 TDS 值即可。

二、TDS 模块的头文件定义

1. 引入必要的头文件

在 TDS 模块的头文件 TDS.h 中,我们需要引入之前实现的 ADS1115.h 头文件,以便调用其相关函数。

#include "ADS1115.h"

2. 定义初始化函数和获取 TDS 值函数

在头文件中,我们定义两个函数:初始化函数 TDS_Init 和获取 TDS 值函数 getTDS

void TDS_Init(void);
double getTDS(void);

三、TDS 模块的实现

1. 初始化函数的实现

初始化函数 TDS_Init 的实现非常简单,因为我们已经在接口层完成了芯片的初始化和配置,所以这里不需要进行额外的操作。直接调用 ADS1115_Init 函数即可。

void TDS_Init(void)
{
    ADS1115_Init();
}

2. 获取 TDS 值函数的实现

获取 TDS 值函数 getTDS 的实现主要包括以下步骤:

(1) 获取模拟电压值

首先,我们需要获取当前的模拟电压值。定义一个 double 类型的变量 voltage,并调用 readVoltage 函数获取模拟电压值。

double voltage = readVoltage();
(2) 计算 TDS 值

接下来,根据 TDS 的测量原理和计算公式,计算 TDS 值。定义一个 double 类型的变量 TDS,并使用公式进行计算。

double TDS = 66.713 * pow(voltage, 3) - 127.93 * pow(voltage, 2) + 428.7 * voltage;
(3) 处理 TDS 值

最后,我们需要对计算得到的 TDS 值进行处理。如果 TDS 值足够小(例如小于 5),则直接返回 0;否则返回计算得到的 TDS 值。

if (TDS < 5)
{
    return 0;
}
else
{
    return TDS;
}

四、主函数中的应用层调用

1. 模块初始化

在主函数中,我们需要调用 TDS_Init 函数对 TDS 模块进行初始化。

TDS_Init();

2. 获取并显示 TDS 值

每隔一秒,调用 getTDS 函数获取 TDS 值,并将其显示在液晶屏上。为了保证显示的准确性,我们可以在显示之前清空缓冲区。

char info[20];
memset(info, 0, sizeof(info));
sprintf(info, "TDS: %.2f", getTDS());
LCD_Display(info, 220, 50);

五、测试与验证

1. 编译与烧写

首先,进行代码的编译,确保没有编译错误。然后将代码烧写到开发板中。

2. 测试显示效果

复位开发板,观察液晶屏的显示效果。确保水位值和 TDS 值能够正确显示,并且 TDS 值在没有插入探针时能够显示为 0。

3. 测试水质测量

将 TDS 探针插入水中,观察液晶屏上显示的 TDS 值。可以尝试使用不同水质的水进行测试,例如自来水、纯净水等,验证 TDS 值的测量准确性。

六、注意事项

  • 在计算 TDS 值时,确保使用正确的公式,并注意数据类型的匹配。
  • 在显示 TDS 值时,合理设置显示位置和格式,确保显示效果清晰。
  • 在测试过程中,注意探针的插入和取出,避免对探针造成损坏。

七、总结

通过本课程的学习,我们掌握了水质水位监测项目中水质模块应用层的实现方法。从 TDS 模块的头文件定义,到获取 TDS 值函数的实现,再到主函数中的应用层调用,我们详细讲解了每个步骤的操作和注意事项。希望大家在实际开发过程中,能够灵活运用所学知识,编写出高效、可靠的代码。同时,也希望大家在学习过程中,多思考、多实践,不断提高自己的编程能力和技术水平。

06_水质水位监测项目_HAL库_驱动层配置和工程架构搭建

在之前的课程中,我们已经完整地实现了水质水位监测项目,使用的是纯寄存器的方式。接下来,我们将使用 HAL 库来实现另一个版本。在实际工作中,HAL 库的使用更为普遍,因为它能够提高开发效率,方便团队协作。通过本课程的学习,希望大家能够掌握 HAL 库的使用方法,并理解其在项目开发中的优势。

一、HAL 库与寄存器方式的对比

1. 开发效率

  • HAL 库:提供了丰富的外设驱动函数,封装了底层硬件操作的细节,使得开发人员可以更专注于应用逻辑的实现,提高了开发效率。例如,在配置串口时,只需调用几个简单的函数即可完成波特率、数据位、停止位等参数的设置,无需像寄存器方式那样逐位操作寄存器。
  • 寄存器方式:需要直接操作硬件寄存器,对硬件的理解要求较高,且代码量较大。在处理复杂的外设配置时,如 FSMC 控制液晶屏,需要配置多个寄存器,代码可读性和可维护性较差。

2. 可读性与可维护性

  • HAL 库:代码结构清晰,函数命名规范,易于理解和维护。例如,使用 HAL_GPIO_Init 函数初始化 GPIO 引脚,函数名直观地表明了其功能,便于后期的代码阅读和修改。
  • 寄存器方式:代码中充斥着大量的寄存器地址和位操作,对于不熟悉硬件的开发人员来说,阅读和维护起来较为困难。

3. 适用场景

  • HAL 库:适用于项目较为复杂、多人协作开发的场景。在大型项目中,使用 HAL 库可以减少代码冗余,提高代码复用率,便于团队成员之间的沟通和协作。
  • 寄存器方式:适用于对硬件性能要求极高、需要精细控制硬件的场景。例如,在对实时性要求极高的嵌入式系统中,通过寄存器方式可以更精确地控制硬件操作的时序。

二、HAL 库工程架构搭建

1. 硬件选型与需求分析

在搭建工程架构之前,我们需要对项目需求进行详细分析,并根据需求选择合适的硬件。对于水质水位监测项目,我们需要以下硬件模块:

  • 主控芯片:STM32F103ZET6,具备丰富的外设接口和足够的处理能力,满足项目需求。
  • 水位检测模块:HX711 芯片,用于检测水位变化。
  • 水质检测模块:TDS 探针,用于测量水中的溶解固体总量。
  • 显示模块:液晶屏,用于显示水位和水质信息。
  • 调试模块:串口,用于调试和数据输出。

2. 软件架构设计

软件架构设计是项目开发的关键步骤,合理的架构设计能够提高项目的可扩展性和可维护性。对于水质水位监测项目,我们采用分层架构设计:

  • 驱动层:负责与硬件直接交互,提供底层的硬件驱动函数。例如,GPIO 驱动、SPI 驱动、I2C 驱动等。
  • 接口层:封装驱动层的函数,提供统一的接口供应用层调用。例如,水位检测接口、水质检测接口等。
  • 应用层:实现项目的具体功能逻辑,调用接口层提供的函数。例如,水位监测功能、水质监测功能、数据显示功能等。

3. HAL 库配置

使用 STM32CubeMX 工具进行 HAL 库配置,步骤如下:

  • 选择 MCU:在 STM32CubeMX 中选择 STM32F103ZET6 芯片。
  • 系统配置:配置系统时钟为 72MHz,选择外部 8MHz 晶振作为时钟源,通过 PLL 九倍频得到系统时钟。
  • 外设配置
    • 串口:配置 USART1 用于调试模块,设置波特率为 115200,数据位为 8 位,停止位为 1 位。
    • FSMC:配置 FSMC 控制液晶屏,选择 NE4 作为片选信号,A10 作为命令数据选择信号。
    • I2C:配置 I2C2 用于水质检测模块,设置为标准模式,速率为 100kHz。
    • SPI:配置 SPI1 用于水位检测模块的 Flash 存储,设置为主设备,数据帧格式为 Motorola,时钟极性和相位为模式 0。
  • GPIO 配置:配置相关的 GPIO 引脚,如水位检测模块的 SCK、OUT 引脚,液晶屏的 BG、RST 引脚等。

4. 工程生成与代码管理

  • 生成工程:在 STM32CubeMX 中生成工程代码,选择 MDK-ARM 作为工具链。
  • 代码管理:将生成的代码导入到 MDK-ARM 中,创建不同的文件夹对代码进行分类管理,如 DriversCoreCommonInterfaceAPP 等。

三、HAL 库驱动层实现

1. GPIO 驱动

使用 HAL 库提供的 HAL_GPIO_Init 函数初始化 GPIO 引脚,设置引脚模式、输出类型、速度等参数。例如,初始化水位检测模块的 SCK 引脚:

GPIO_InitTypeDef GPIO_InitStruct = {0};
GPIO_InitStruct.Pin = GPIO_PIN_12;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

2. SPI 驱动

使用 HAL_SPI_Init 函数初始化 SPI 接口,配置 SPI 参数,如数据帧格式、时钟极性和相位、波特率等。例如,初始化 SPI1:

SPI_HandleTypeDef hspi1;
hspi1.Instance = SPI1;
hspi1.Init.Mode = SPI_MODE_MASTER;
hspi1.Init.Direction = SPI_DIRECTION_2LINES;
hspi1.Init.DataSize = SPI_DATASIZE_8BIT;
hspi1.Init.CLKPolarity = SPI_POLARITY_LOW;
hspi1.Init.CLKPhase = SPI_PHASE_1EDGE;
hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_4;
HAL_SPI_Init(&hspi1);

3. I2C 驱动

使用 HAL_I2C_Init 函数初始化 I2C 接口,配置 I2C 参数,如时钟速度、地址模式等。例如,初始化 I2C2:

I2C_HandleTypeDef hi2c2;
hi2c2.Instance = I2C2;
hi2c2.Init.ClockSpeed = 100000;
hi2c2.Init.DutyCycle = I2C_DUTYCYCLE_2;
hi2c2.Init.OwnAddress1 = 0;
hi2c2.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;
HAL_I2C_Init(&hi2c2);

四、HAL 库接口层与应用层实现

1. 接口层实现

接口层封装驱动层的函数,提供统一的接口供应用层调用。例如,封装水位检测接口:

uint32_t getWaterLevel(void)
{
    // 调用 GPIO 驱动函数获取水位数据
    uint32_t data = HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_13);
    return data;
}

2. 应用层实现

应用层实现项目的具体功能逻辑,调用接口层提供的函数。例如,实现水位监测功能:

void waterLevelMonitor(void)
{
    uint32_t waterLevel = getWaterLevel();
    if (waterLevel > 0)
    {
        // 水位过高,进行报警处理
        // ...
    }
    else
    {
        // 水位正常,继续监测
        // ...
    }
}

五、HAL 库的优势与注意事项

1. 优势

  • 提高开发效率:减少了底层硬件操作的复杂性,使得开发人员可以更专注于应用逻辑的实现。
  • 提高代码可读性与可维护性:代码结构清晰,函数命名规范,便于后期的代码阅读和修改。
  • 便于团队协作:在多人协作开发的项目中,使用 HAL 库可以减少代码冗余,提高代码复用率,便于团队成员之间的沟通和协作。

2. 注意事项

  • 性能开销:由于 HAL 库封装了底层硬件操作,可能会带来一定的性能开销。在对实时性要求极高的场景中,需要权衡使用。
  • 学习成本:对于初学者来说,学习 HAL 库的使用方法需要一定的时间和精力,需要熟悉库函数的参数和使用场景。
  • 版本兼容性:不同版本的 HAL 库可能存在不兼容的情况,在升级库版本时需要注意兼容性问题,避免对项目造成影响。

六、总结

通过本课程的学习,我们掌握了使用 HAL 库实现水质水位监测项目的方法。从 HAL 库与寄存器方式的对比,到工程架构的搭建,再到驱动层、接口层和应用层的具体实现,我们详细讲解了每个步骤的操作和注意事项。希望大家在实际开发过程中,能够灵活运用 HAL 库,提高开发效率,编写出高效、可靠的代码。同时,也希望大家在学习过程中,多思考、多实践,不断提高自己的编程能力和技术水平。

07_水质水位监测项目_HAL库_调试模块和显示模块

在之前的课程中,我们已经使用 HAL 库搭建好了水质水位监测项目的工程架构,并完成了驱动层的配置。接下来,我们将重点实现调试模块和显示模块。这两个模块是项目中非常重要的组成部分,调试模块可以帮助我们更好地进行程序调试和问题排查,而显示模块则用于将监测到的水位和水质信息展示给用户。通过本课程的学习,希望大家能够掌握这两个模块的实现方法,并理解它们在项目中的作用。

一、调试模块的实现

1. 重写 fputc 函数

在使用 printf 函数进行调试输出时,我们需要重写 fputc 函数,以实现对 printf 的重定向。这个过程相对简单,但非常重要。

  • 函数定义int fputc(int ch, FILE *f)

  • 参数说明ch 是要传输的字符,以 ASCII 码形式表示,数据类型为 intf 是一个 FILE 类型的数据,表示文件。

  • 实现思路:调用 HAL 库中的 HAL_UART_Transmit 函数,将字符发送到串口。

  • 代码实现

    int fputc(int ch, FILE *f)
    {
        HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 1000);
        return ch;
    }
    
  • 注意事项:在调用 HAL_UART_Transmit 函数时,需要传入串口句柄的指针(如 &huart1)、指向字符数据的指针、数据长度(这里是 1,因为只传输一个字符)以及超时时间(这里设置为 1000 毫秒)。

2. 调试模块的作用

  • 程序调试:在开发过程中,通过 printf 函数输出调试信息,帮助我们了解程序的运行状态,快速定位问题。
  • 问题排查:当项目出现异常时,可以通过调试模块输出相关变量的值和程序的执行流程,辅助我们进行问题排查和修复。

二、显示模块的实现

1. FSMC 配置

显示模块的底层是 FSMC(Flexible Static Memory Controller),我们需要对其进行配置,以实现与液晶屏的通信。

  • 片选信号:选择 NE4 作为片选信号,将其与液晶屏的片选引脚相连。
  • 地址线:使用 A10 作为命令数据选择信号,控制液晶屏是接收命令还是数据。
  • 数据宽度:设置为 16 位数据宽度,与液晶屏的数据接口相匹配。
  • 时序参数:配置地址建立时间和数据建立时间等时序参数,确保数据传输的稳定性。

2. 液晶屏驱动函数实现

在接口层,我们需要实现一些基本的液晶屏驱动函数,如初始化、复位、写命令、写数据等。

  • 初始化函数:调用 FSMC 相关函数,初始化液晶屏的控制器寄存器,设置液晶屏的工作模式和显示参数。
  • 复位函数:通过控制液晶屏的复位引脚,实现液晶屏的复位操作。
  • 写命令函数:将命令数据发送到液晶屏,控制液晶屏执行相应的操作,如清屏、设置显示位置等。
  • 写数据函数:将显示数据发送到液晶屏,更新液晶屏上的显示内容。

3. 显示模块的应用

在应用层,我们可以调用接口层提供的函数,实现各种显示功能。

  • 显示英文字符:将英文字符的 ASCII 码转换为对应的点阵数据,通过写数据函数发送到液晶屏,实现英文字符的显示。
  • 显示中文字符:将中文字符的编码转换为对应的点阵数据,通过写数据函数发送到液晶屏,实现中文字符的显示。
  • 显示图片:将图片的像素数据按照液晶屏的显示格式进行处理,通过写数据函数发送到液晶屏,实现图片的显示。

三、调试模块和显示模块的协同工作

调试模块和显示模块在项目中协同工作,共同实现信息的输出和展示。

  • 信息输出:在程序运行过程中,通过调试模块输出调试信息,同时将监测到的水位和水质数据通过显示模块展示在液晶屏上,方便用户查看。
  • 状态监控:实时监控项目的运行状态,当出现异常时,通过调试模块输出错误信息,并在液晶屏上显示相应的提示信息,提醒用户进行处理。

四、总结

通过本课程的学习,我们掌握了调试模块和显示模块的实现方法。调试模块通过重写 fputc 函数,实现了对 printf 的重定向,方便了程序的调试和问题排查;显示模块通过 FSMC 配置和液晶屏驱动函数的实现,实现了水位和水质信息的展示。希望大家在实际开发过程中,能够灵活运用这两个模块,提高项目的开发效率和用户体验。同时,也希望大家在学习过程中,多思考、多实践,不断提高自己的编程能力和技术水平。

08_水质水位监测项目_HAL库_水位监测模块

在之前的课程中,我们已经使用 HAL 库搭建好了水质水位监测项目的工程架构,并完成了调试模块和显示模块的实现。接下来,我们将深入到项目的核心功能模块——水位监测模块。水位监测模块是整个项目中非常重要的部分,它负责检测水位的变化,并将检测到的数据传递给上层应用。通过本课程的学习,希望大家能够掌握水位监测模块的实现方法,并理解其在项目中的作用。

一、水位监测模块概述

水位监测模块的核心芯片是 HX711,它通过自定义的类似于串口的协议与主控芯片进行通信。为了实现水位监测功能,我们需要完成以下几个关键步骤:

  1. GPIO 配置:配置与 HX711 芯片相连的 GPIO 引脚,包括时钟线 SCK 和数据线 OUT。
  2. SPI 配置:虽然 HX711 芯片本身不使用 SPI 协议,但在项目中我们还需要使用 SPI 接口与 Flash 存储器进行通信,以保存水位校准参数。
  3. 水位校准:通过测量已知水位下的输出值,计算出校准参数,并将其保存到 Flash 中。
  4. 水位读取:根据校准参数,读取当前水位的输出值,并将其转换为实际水位高度。

二、GPIO 配置

1. 引脚定义

  • SCK:连接到 HX711 芯片的时钟线,用于控制数据传输的时序。
  • OUT:连接到 HX711 芯片的数据线,用于接收水位检测数据。

2. 配置步骤

  • 时钟线 SCK:配置为输出模式,初始状态为低电平。
  • 数据线 OUT:配置为输入模式,用于读取 HX711 芯片输出的数据。

3. 代码实现

GPIO_InitTypeDef GPIO_InitStruct = {0};
// 配置时钟线 SCK
GPIO_InitStruct.Pin = GPIO_PIN_12;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
// 配置数据线 OUT
GPIO_InitStruct.Pin = GPIO_PIN_13;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

三、SPI 配置

1. SPI 接口选择

选择 SPI1 作为与 Flash 存储器通信的接口。

2. 配置参数

  • 模式:全双工主设备模式。
  • 数据帧格式:8 位数据帧。
  • 时钟极性和相位:模式 0(CPOL=0,CPHA=0)。
  • 波特率:设置为系统时钟的四分频,即 18MHz。

3. 代码实现

SPI_HandleTypeDef hspi1;
hspi1.Instance = SPI1;
hspi1.Init.Mode = SPI_MODE_MASTER;
hspi1.Init.Direction = SPI_DIRECTION_2LINES;
hspi1.Init.DataSize = SPI_DATASIZE_8BIT;
hspi1.Init.CLKPolarity = SPI_POLARITY_LOW;
hspi1.Init.CLKPhase = SPI_PHASE_1EDGE;
hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_4;
HAL_SPI_Init(&hspi1);

四、水位校准

1. 校准原理

通过测量已知水位下的输出值,计算出校准参数。校准参数包括零点偏移量和量程比例系数。

2. 校准步骤

  • 零点校准:将水位传感器置于无水状态,读取输出值作为零点偏移量。
  • 量程校准:将水位传感器置于满水状态,读取输出值,并结合已知的满水高度,计算出量程比例系数。

3. 代码实现

uint32_t zeroOffset = 0; // 零点偏移量
uint32_t rangeCoefficient = 0; // 量程比例系数

// 零点校准
zeroOffset = readHX711Data();

// 量程校准
uint32_t fullScaleData = readHX711Data();
rangeCoefficient = (FULL_WATER_HEIGHT - ZERO_WATER_HEIGHT) / (fullScaleData - zeroOffset);

五、水位读取

1. 读取原理

根据校准参数,将 HX711 芯片输出的数字值转换为实际水位高度。

2. 读取步骤

  • 发送时钟信号:通过 SCK 引脚发送时钟信号,控制 HX711 芯片输出数据。
  • 读取数据:通过 OUT 引脚读取 HX711 芯片输出的 24 位数据。
  • 数据转换:将读取到的数字值减去零点偏移量,再乘以量程比例系数,得到实际水位高度。

3. 代码实现

uint32_t readWaterLevel(void)
{
    uint32_t data = 0;
    // 发送时钟信号,读取数据
    for (int i = 0; i < 24; i++)
    {
        HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_SET);
        data = (data << 1) | HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_13);
        HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_RESET);
    }
    // 数据转换
    uint32_t waterLevel = (data - zeroOffset) * rangeCoefficient;
    return waterLevel;
}

六、水位监测模块的协同工作

水位监测模块与调试模块和显示模块协同工作,共同实现水位信息的检测、输出和展示。

  • 信息检测:水位监测模块实时检测水位的变化,并将检测到的数据传递给上层应用。
  • 信息输出:调试模块通过串口输出水位监测模块检测到的数据,方便开发人员进行调试和问题排查。
  • 信息展示:显示模块将水位监测模块检测到的水位高度展示在液晶屏上,方便用户查看。

七、总结

通过本课程的学习,我们掌握了水位监测模块的实现方法。从 GPIO 配置和 SPI 配置,到水位校准和水位读取,我们详细讲解了每个步骤的操作和注意事项。希望大家在实际开发过程中,能够灵活运用水位监测模块,提高项目的开发效率和准确性。同时,也希望大家在学习过程中,多思考、多实践,不断提高自己的编程能力和技术水平。

09_水质水位监测项目_HAL库_水质监测模块和测试

在之前的课程中,我们已经完成了水质水位监测项目的多个模块的实现,包括调试模块、显示模块和水位监测模块。今天,我们将继续深入学习项目的核心功能模块——水质监测模块,并进行最终的测试。水质监测模块主要负责检测水质的 TDS 值,其核心是外置的 ADC 芯片 ADS1115,通过 I2C 与主控芯片进行通信。通过本课程的学习,希望大家能够掌握水质监测模块的实现方法,并理解其在项目中的作用。

一、水质监测模块概述

水质监测模块的核心芯片是 ADS1115,它是一款高精度的模数转换器,能够将模拟信号转换为数字信号,从而实现对水质 TDS 值的检测。模块的工作流程主要包括以下几个步骤:

  1. 初始化:配置 ADS1115 的相关参数,如采样率、增益等。
  2. 读取转换结果:通过 I2C 读取 ADS1115 转换得到的数字信号。
  3. 计算 TDS 值:根据读取到的数字信号,计算出对应的 TDS 值。

二、初始化

1. 初始化步骤

  • 配置 I2C 接口:设置 I2C 的时钟速度、地址模式等参数,确保与 ADS1115 的通信正常。
  • 配置 ADS1115:设置 ADS1115 的采样率、增益等参数,以满足水质监测的需求。

2. 代码实现

I2C_HandleTypeDef hi2c2;
hi2c2.Instance = I2C2;
hi2c2.Init.ClockSpeed = 100000;
hi2c2.Init.DutyCycle = I2C_DUTYCYCLE_2;
hi2c2.Init.OwnAddress1 = 0;
hi2c2.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;
HAL_I2C_Init(&hi2c2);

// 配置 ADS1115 的采样率和增益
uint8_t configData[3] = {0x01, 0x83, 0x80};
HAL_I2C_Master_Transmit(&hi2c2, ADS1115_ADDRESS, configData, 3, 1000);

三、读取转换结果

1. 读取步骤

  • 发送写地址:向 ADS1115 发送写地址,准备读取转换结果。
  • 发送指针寄存器地址:发送指针寄存器地址,指定要读取的寄存器。
  • 读取数据:读取 ADS1115 转换得到的数字信号。

2. 代码实现

uint8_t pointer = ADS1115_POINTER_CONVERSION;
HAL_I2C_Master_Transmit(&hi2c2, ADS1115_ADDRESS, &pointer, 1, 1000);

uint8_t readData[2] = {0};
HAL_I2C_Master_Receive(&hi2c2, ADS1115_ADDRESS, readData, 2, 1000);

uint16_t conversionResult = (readData[0] << 8) | readData[1];

四、计算 TDS 值

1. 计算公式

根据水质监测的原理,TDS 值可以通过以下公式计算:

[ \text{TDS} = 66.713 \times V^3 - 127.93 \times V^2 + 428.7 \times V ]

其中,( V ) 是转换得到的数字信号对应的模拟电压值。

2. 代码实现

double voltage = conversionResult * (4.096 / 32767);
double TDS = 66.713 * pow(voltage, 3) - 127.93 * pow(voltage, 2) + 428.7 * voltage;

五、测试

1. 测试步骤

  • 编译代码:确保代码没有编译错误。
  • 烧写代码:将代码烧写到开发板中。
  • 连接探针:将 TDS 探针插入水中。
  • 观察结果:通过串口或液晶屏观察 TDS 值的变化。

2. 测试注意事项

  • 探针校准:在测试前,确保 TDS 探针已经校准。
  • 水质选择:可以使用不同水质的水进行测试,如自来水、纯净水等,以验证 TDS 值的测量准确性。
  • 温度影响:注意水温对 TDS 值的影响,尽量在标准温度下进行测试。

六、总结

通过本课程的学习,我们掌握了水质监测模块的实现方法。从初始化 ADS1115,到读取转换结果和计算 TDS 值,我们详细讲解了每个步骤的操作和注意事项。希望大家在实际开发过程中,能够灵活运用水质监测模块,提高项目的开发效率和准确性。同时,也希望大家在学习过程中,多思考、多实践,不断提高自己的编程能力和技术水平。

10_水质水位监测项目_HAL库_微秒级精确计时

在之前的课程中,我们已经完成了水质水位监测项目的多个模块的实现。然而,在项目中有一个比较棘手的问题点,那就是如何实现微秒级别的精确延时。在之前的寄存器方式实现中,我们通过经验估计,利用程序执行循环的次数来实现延时,虽然满足了项目的需求,但精确度并不高。在 HAL 库中,我们可以使用定时器来实现更精确的微秒级延时。通过本课程的学习,希望大家能够掌握如何在 HAL 库中实现微秒级精确计时,并理解其在项目中的应用。

一、微秒级延时的需求与挑战

1. 需求

在水质水位监测项目中,水位检测模块需要精确控制时序,以确保数据的准确读取。例如,在与 HX711 芯片通信时,需要严格控制时钟信号的发送和数据的读取时序,这就对延时的精确度提出了较高的要求。如果延时误差较大,可能会导致数据读取错误,进而影响整个项目的准确性。

2. 挑战

  • 精确度要求高:微秒级别的延时需要精确到几个微秒甚至更小的范围,这对硬件和软件的控制能力提出了较高的要求。
  • 与现有机制冲突:在 HAL 库中,系统滴答定时器(SysTick)已经用于实现毫秒级别的计时和延时功能,如果直接使用 SysTick 来实现微秒级延时,可能会与现有的计时机制冲突,导致系统时钟混乱。
  • 实现复杂度高:相比于简单的循环延时,使用定时器实现微秒级延时需要进行复杂的配置和编程,增加了项目的实现难度。

二、使用定时器实现微秒级延时

1. 定时器选择

在 STM32 中,有多个定时器可供选择,包括高级定时器、通用定时器和基本定时器。对于微秒级延时的需求,我们可以选择基本定时器 TIM6 或 TIM7,因为它们具有简单的计时功能,且不会与其他功能冲突。

2. 定时器配置

  • 预分频器(PSC):设置预分频器的值,以确定定时器的计数频率。例如,如果系统时钟为 72MHz,我们可以设置预分频器为 71,这样定时器的计数频率就是 1MHz,即每 1 微秒计数一次。
  • 自动重装载寄存器(ARR):设置自动重装载寄存器的值,以确定定时器溢出的时间。由于我们只需要实现几微秒的延时,所以可以将 ARR 设置为最大值 65535,避免溢出的处理。
  • 计数方向:设置定时器的计数方向为向上计数,从 0 开始计数。

3. 代码实现

#include "tim.h"

void delayUS(uint32_t us)
{
    // 清零计数器
    __HAL_TIM_SET_COUNTER(&htim6, 0);
    // 开启定时器
    HAL_TIM_Base_Start(&htim6);
    // 轮询等待计数值到达设定值
    while (__HAL_TIM_GET_COUNTER(&htim6) < us)
    {
    }
    // 关闭定时器
    HAL_TIM_Base_Stop(&htim6);
}

三、微秒级延时的应用

1. 水位检测模块

在水位检测模块中,我们需要精确控制与 HX711 芯片的通信时序。例如,在发送时钟信号和读取数据时,需要精确的延时来保证数据的正确读取。通过使用定时器实现的微秒级延时,我们可以确保时序的准确性,从而提高水位检测的精度。

2. 其他模块

除了水位检测模块,其他模块中也可能需要微秒级延时。例如,在与外部设备通信时,可能需要精确控制通信时序;在执行某些硬件操作时,可能需要等待硬件的响应时间。通过使用定时器实现的微秒级延时,可以满足这些模块的需求,提高整个项目的稳定性和可靠性。

四、总结

通过本课程的学习,我们掌握了在 HAL 库中使用定时器实现微秒级精确计时的方法。从定时器的选择和配置,到代码的实现和应用,我们详细讲解了每个步骤的操作和注意事项。希望大家在实际开发过程中,能够灵活运用微秒级延时,提高项目的开发效率和性能。同时,也希望大家在学习过程中,多思考、多实践,不断提高自己的编程能力和技术水平。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值