基于STM32嵌入式的水体水质水位监测系统

1.项目设计内容

水质监测项目旨在检测和评估特定水体的质量,以确保水体的可持续管理和保护。通过测量水体的TDS来反映水体的质量,并同时可以测量水位(水深)。

这里需要给大家介绍一下TDS(Total Dissolved Solids),中文名总溶解固体,又称溶解性固体总量,表明1升水中溶有多少毫克溶解性固体。一般来说,TDS值越高,表示水中含有的溶解物越多,水就越不洁净。虽然在特定情况下TDS并不能有效反映水质的情况,但作为一种可快速检测的参数,TDS目前还是可以作为有效的水质情况反映参数来作为参考。水位测量可以直观的看到水的深度。

2.系统功能设计

根据项目设计的需求,该系统需要具备以下功能:

一、数据采集:系统能够连接到要测的水体。

TDS探针能够捕获水体的TDS数据,并把数据传输到嵌入式系统。水位传感器可以把数据传输到嵌入式系统。

二、数据处理:采集到TDS数据和水位在嵌入式系统中实时处理。

三、数据显示:要把测得TDS数据和水位数据实时显示在LCD屏幕上。

系统总体设计的框图如下:

水质监测系统总体框图

3.硬件选择 

3.1 硬件选型

3.1.1 水位测量模块

水位测量传感器本质上是一个压力测量传感器。压力的值和传感器产生的电压值是线性关系,压力的值和水深也是线性关系。根据这个原理,我们不需要知道具体的电压,就可以测量出来水位。假设水位是x,从ADC读取的值y。(y不必转成具体的电压)。则有下面的等式成立:y=ax+b芯片的接线如图所示:SCK接PA2,是时钟信号,OUT接PA3,是数据输出信号。

知道了硬件的连线后,还需要根据其时序图来读取数据,如下所示:

读取时序图

3.1.2 TDS采集模块 

TDS采集的原理就是当水中的导电粒子多时,导电性好,采集到的电压高;导电粒子少时导电性差,采集到的电压低。可以简单的认为水中杂质多时,导电粒子多,杂质少时导电粒子少。所以可以通过采集的电压高低来计算TDS的值。测出电压值,根据公式可以计算出来TDS的值。 

TDS-电压关系图

 模块的硬件接线如下图中所示

接线连接示意图

 3.1.3 外置​​​​ADC模块(ADS1115)

STM32中内置ADC精度只有12位,本工程项目选用的外置ADC模块中的ADS1115是16位精度,支持4路电压转换,而且支持单端和差分输入。与STM32通讯使用I2C通用协议,物理接线如下图所示。

ADS1115接线图

根据ADCS1115模块的ADDR引脚的不同接法决定模块I2C的地址(7位地址)。

4.软件程序设计

介绍完了硬件的选型和基本的工作原理,接下来就是根据系统实现的功能编写具体的代码程序了,为使项目尽可能的规范、易于移植,采取对软件架构的分层设计。

软件分层架构

 注意这里的驱动层由HAL库自动生成的代码,不需要做任何变动

4.1 LCD显示程序设计

Inf_LCD.h

#ifndef __INF_LCD_H
#define __INF_LCD_H

#include "fsmc.h"


#define SRAM_BANK4 0x6C000000
#define LCD_AX 10                                                           // Inf_LCD的地址线,我们连接的时A10
#define LCD_ADDR_CMD ((__IO uint16_t *)SRAM_BANK4)                          // 写命令地址
#define LCD_ADDR_DATA ((__IO uint16_t *)(SRAM_BANK4 + (1 << (LCD_AX + 1)))) // 数据地址(计算地址的时候要左移一位)

/* 常见颜色 */
#define WHITE 0xFFFF
#define BLACK 0x0000
#define BLUE 0x001F
#define BRED 0XF81F
#define GRED 0XFFE0
#define GBLUE 0X07FF
#define RED 0xF800
#define MAGENTA 0xF81F
#define GREEN 0x07E0
#define CYAN 0x7FFF
#define YELLOW 0xFFE0
#define BROWN 0XBC40 // 棕色
#define BRRED 0XFC07 // 棕红色
#define GRAY 0X8430  // 灰色

void Inf_LCD_Init(void);
uint32_t Inf_LCD_ReadId(void);
void Inf_LCD_RegConfig(void);
void Inf_LCD_Reset(void);
void Inf_LCD_BKOpen(void);
void Inf_LCD_BKClose(void);
void Inf_LCD_WriteCmd(__IO uint16_t cmd);
void Inf_LCD_WriteData(__IO uint16_t data);
uint16_t Inf_LCD_ReadData(void);
void Inf_LCD_DisplayAsciiChar(uint16_t x, uint16_t y, uint8_t c, uint16_t fgColor, uint16_t bgColor, uint8_t fontHeight);
void Inf_LCD_DisplayAsciiString(uint16_t x, uint16_t y, uint8_t *str, uint16_t fgColor, uint16_t bgColor, uint8_t fontHeight);
void Inf_LCD_DisplayChinesChar(uint16_t x, uint16_t y, uint8_t chinesCharPosition, uint16_t fgColor, uint16_t bgColor);
void Inf_LCD_SetAddr(uint16_t x, uint16_t y, uint16_t w, uint16_t h);
uint16_t Inf_LCD_ReadDisplayPixelFormat(void);
void Inf_LCD_Clear(uint16_t color);
void Inf_LCD_FillColor(uint32_t num, uint16_t color);
#endif

 Inf_LCD.c

#include "Inf_LCD.h"
#include "Inf_LCD_Font.h"

/* 初始化LCD */
void Inf_LCD_Init(void)
{
    /* 0. 初始化FSMC */
    MX_FSMC_Init();
    // 1. 重置LCD
    Inf_LCD_Reset();
    // 2. 开启背光
    Inf_LCD_BKOpen();
    // 3. 初始化寄存器
    Inf_LCD_RegConfig();
}

/* 读取lcd的id,用来测试通讯是否正常 */
uint32_t Inf_LCD_ReadId(void)
{
    uint32_t id = 0x0;
    Inf_LCD_WriteCmd(0x04);
    Inf_LCD_ReadData();             // 无用的数据,扔掉即可
    id |= Inf_LCD_ReadData() << 16; // 制造商id
    id |= Inf_LCD_ReadData() << 8;  // 模块/驱动版本号id
    id |= Inf_LCD_ReadData();       // 模块/驱动id
    return id;
}

/* 初始化寄存器的值 */
void Inf_LCD_RegConfig(void)
{
    /* 1. 设置灰阶电压以调整TFT面板的伽马特性, 正校准。一般出厂就设置好了 */
    Inf_LCD_WriteCmd(0xE0);
    Inf_LCD_WriteData(0x00);
    Inf_LCD_WriteData(0x07);
    Inf_LCD_WriteData(0x10);
    Inf_LCD_WriteData(0x09);
    Inf_LCD_WriteData(0x17);
    Inf_LCD_WriteData(0x0B);
    Inf_LCD_WriteData(0x41);
    Inf_LCD_WriteData(0x89);
    Inf_LCD_WriteData(0x4B);
    Inf_LCD_WriteData(0x0A);
    Inf_LCD_WriteData(0x0C);
    Inf_LCD_WriteData(0x0E);
    Inf_LCD_WriteData(0x18);
    Inf_LCD_WriteData(0x1B);
    Inf_LCD_WriteData(0x0F);

    /* 2. 设置灰阶电压以调整TFT面板的伽马特性,负校准 */
    Inf_LCD_WriteCmd(0XE1);
    Inf_LCD_WriteData(0x00);
    Inf_LCD_WriteData(0x17);
    Inf_LCD_WriteData(0x1A);
    Inf_LCD_WriteData(0x04);
    Inf_LCD_WriteData(0x0E);
    Inf_LCD_WriteData(0x06);
    Inf_LCD_WriteData(0x2F);
    Inf_LCD_WriteData(0x45);
    Inf_LCD_WriteData(0x43);
    Inf_LCD_WriteData(0x02);
    Inf_LCD_WriteData(0x0A);
    Inf_LCD_WriteData(0x09);
    Inf_LCD_WriteData(0x32);
    Inf_LCD_WriteData(0x36);
    Inf_LCD_WriteData(0x0F);

    /* 3.  Adjust Control 3 (F7h)  */
    /*LCD_WriteCmd(0XF7);
    Inf_LCD_WriteData(0xA9);
    Inf_LCD_WriteData(0x51);
    Inf_LCD_WriteData(0x2C);
    Inf_LCD_WriteData(0x82);*/
    /* DSI write DCS command, use loose packet RGB 666 */

    /* 4. 电源控制1*/
    Inf_LCD_WriteCmd(0xC0);
    Inf_LCD_WriteData(0x11); /* 正伽马电压 */
    Inf_LCD_WriteData(0x09); /* 负伽马电压 */

    /* 5. 电源控制2 */
    Inf_LCD_WriteCmd(0xC1);
    Inf_LCD_WriteData(0x02);
    Inf_LCD_WriteData(0x03);

    /* 6. VCOM控制 */
    Inf_LCD_WriteCmd(0XC5);
    Inf_LCD_WriteData(0x00);
    Inf_LCD_WriteData(0x0A);
    Inf_LCD_WriteData(0x80);

    /* 7. Frame Rate Control (In Normal Mode/Full Colors) (B1h) */
    Inf_LCD_WriteCmd(0xB1);
    Inf_LCD_WriteData(0xB0);
    Inf_LCD_WriteData(0x11);

    /* 8.  Display Inversion Control (B4h) (正负电压反转,减少电磁干扰)*/
    Inf_LCD_WriteCmd(0xB4);
    Inf_LCD_WriteData(0x02);

    /* 9.  Display Function Control (B6h)  */
    Inf_LCD_WriteCmd(0xB6);
    Inf_LCD_WriteData(0x0A);
    Inf_LCD_WriteData(0xA2);

    /* 10. Entry Mode Set (B7h)  */
    Inf_LCD_WriteCmd(0xB7);
    Inf_LCD_WriteData(0xc6);

    /* 11. HS Lanes Control (BEh) */
    Inf_LCD_WriteCmd(0xBE);
    Inf_LCD_WriteData(0x00);
    Inf_LCD_WriteData(0x04);

    /* 12.  Interface Pixel Format (3Ah) */
    Inf_LCD_WriteCmd(0x3A);
    Inf_LCD_WriteData(0x55); /* 0x55 : 16 bits/pixel  */

    /* 13. Sleep Out (11h) 关闭休眠模式 */
    Inf_LCD_WriteCmd(0x11);

    /* 14. 设置屏幕方向和RGB */
    Inf_LCD_WriteCmd(0x36);
    Inf_LCD_WriteData(0x08);

    HAL_Delay(120);

    /* 14. display on */
    Inf_LCD_WriteCmd(0x29);
}

/* 重置LCD PG15*/
void Inf_LCD_Reset(void)
{

    HAL_GPIO_WritePin(GPIOG, GPIO_PIN_15, GPIO_PIN_RESET);
    HAL_Delay(50);

    HAL_GPIO_WritePin(GPIOG, GPIO_PIN_15, GPIO_PIN_SET);
    HAL_Delay(50);
}

/* 给LCD开启背光 */
void Inf_LCD_BKOpen(void)
{
    // 高电平开启背光
    HAL_GPIO_WritePin(LCDK_GPIO_Port, LCDK_Pin, GPIO_PIN_SET);
}
/* 给LCD关闭背光 */
void Inf_LCD_BKClose(void)
{
    // 低电平关闭背光(最暗)
    HAL_GPIO_WritePin(LCDK_GPIO_Port, LCDK_Pin, GPIO_PIN_RESET);
}

/* 发送命令 */
void Inf_LCD_WriteCmd(__IO uint16_t cmd)
{
    *LCD_ADDR_CMD = cmd;
}

/* 发送数据 */
void Inf_LCD_WriteData(__IO uint16_t data)
{
    *LCD_ADDR_DATA = data;
}

/* 读取数据 */
uint16_t Inf_LCD_ReadData(void)
{
    return *LCD_ADDR_DATA;
}

/**
 * @description: 在LCD上显示一个Ascii字符
 * @param {uint16_t} x 起始x坐标
 * @param {uint16_t} y 起始y坐标
 * @param {uint8_t} c 要显示的字符
 * @param {uint16_t} fgColor 前景色:字符轮廓的颜色
 * @param {uint16_t} bgColor 背景色:非字符轮廓的颜色  比如白纸黑字:黑色是前景色 白色就是背景色
 * @param {uint8_t} fontHeight 字体高度 12|16|24|32
 */
void Inf_LCD_DisplayAsciiChar(uint16_t x,
                              uint16_t y,
                              uint8_t c,
                              uint16_t fgColor,
                              uint16_t bgColor,
                              uint8_t fontHeight)
{
    /* 1. 找到字符在字库中的位置 */
    uint8_t cPosition = c - ' ';
    /* 2. 设置要写入的LCD区域 */
    Inf_LCD_SetAddr(x, y, fontHeight >> 1, fontHeight);
    /* 3. 把字库数据写入到GRAM内存中 */
    Inf_LCD_WriteCmd(0x2C);
    uint8_t i, j, tmp, fontWidth = fontHeight >> 1;
    if (fontHeight == 12 || fontHeight == 16)
    {
        for (i = 0; i < fontHeight; i++)
        {
            // 一个字节控制着一行的8个像素。先低位再高位
            tmp = fontHeight == 12 ? ascii_1206[cPosition][i] : ascii_1608[cPosition][i];
            for (j = 0; j < fontWidth; j++)
            {
                if (tmp & 0x01) // 如果是 1,则发送前景色
                {
                    Inf_LCD_WriteData(fgColor);
                }
                else // 是0发送背景色
                {
                    Inf_LCD_WriteData(bgColor);
                }
                tmp >>= 1; // 右移一位
            }
        }
    }
    else if (fontHeight == 24)
    {
        for (i = 0; i < fontHeight << 1; i++)
        {
            tmp = ascii_2412[cPosition][i];
            uint8_t jCount = i % 2 ? 4 : 8;
            for (j = 0; j < jCount; j++)
            {
                if (tmp & 0x01) // 如果是 1,则发送前景色
                {
                    Inf_LCD_WriteData(fgColor);
                }
                else // 是0发送背景色
                {
                    Inf_LCD_WriteData(bgColor);
                }
                tmp >>= 1; // 右移一位
            }
        }
    }
    else if (fontHeight == 32)
    {
        for (i = 0; i < fontHeight << 1; i++)
        {
            tmp = ascii_3216[cPosition][i];

            for (j = 0; j < 8; j++)
            {
                if (tmp & 0x01) // 如果是 1,则发送前景色
                {
                    Inf_LCD_WriteData(fgColor);
                }
                else // 是0发送背景色
                {
                    Inf_LCD_WriteData(bgColor);
                }
                tmp >>= 1; // 右移一位
            }
        }
    }
}

/**
 * @description: 显示一行英文字符串
 * @param {uint16_t} x
 * @param {uint16_t} y
 * @param {uint8_t} *str
 * @param {uint16_t} fgColor
 * @param {uint16_t} bgColor
 * @param {uint8_t} fontHeight
 * @return {*}
 */
void Inf_LCD_DisplayAsciiString(uint16_t x,
                                uint16_t y,
                                uint8_t *str,
                                uint16_t fgColor,
                                uint16_t bgColor,
                                uint8_t fontHeight)
{
    // 字体宽度
    uint8_t fontWidth = fontHeight >> 1;
    uint16_t i = 0;
    while (str[i] != '\0')
    {
        if (str[i] != '\n') // 如果不是换行符
        {
            if (x + fontWidth > 320) // 水平方向要显示的字符超出了屏幕, 则需要换行显示
            {
                y += fontHeight;
                x = 0;
            }
            Inf_LCD_DisplayAsciiChar(x, y, str[i], fgColor, bgColor, fontHeight);

            // 定义下一个字符的x坐标
            x += fontWidth;
        }
        else // 如果碰到换行符
        {
            y += fontHeight;
            x = 0;
        }
        i++;
    }
}

/**
 * @description: 显示中文字符
 * @param {uint16_t} x
 * @param {uint16_t} y
 * @param {uint8_t} chinesCharPosition
 * @param {uint16_t} fgColor
 * @param {uint16_t} bgColor
 * @return {*}
 */
void Inf_LCD_DisplayChinesChar(uint16_t x,
                               uint16_t y,
                               uint8_t chinesCharPosition,
                               uint16_t fgColor,
                               uint16_t bgColor)
{
    /* 1. 设置要写入的LCD区域 中午字符一般宽高一样*/
    Inf_LCD_SetAddr(x, y, 32, 32);
    /* 3. 把字库数据写入到GRAM内存中 */
    Inf_LCD_WriteCmd(0x2C);
    uint8_t i, j, tmp;

    for (i = 0; i < 128; i++)
    {
        tmp = chinese[chinesCharPosition][i];
        for (j = 0; j < 8; j++)
        {
            if (tmp & 0x01) // 如果是 1,则发送前景色
            {
                Inf_LCD_WriteData(fgColor);
            }
            else // 是0发送背景色
            {
                Inf_LCD_WriteData(bgColor);
            }
            tmp >>= 1; // 右移一位
        }
    }
}

/**
 * @description: 设置要写入的GRAM的起始地址和结束地址。 默认坐标原点是 左上角
 * @param {uint16_t} x 起始x坐标
 * @param {uint16_t} y 起始y坐标
 * @param {uint16_t} w 区域宽
 * @param {uint16_t} h 区域高
 */
void Inf_LCD_SetAddr(uint16_t x,
                     uint16_t y,
                     uint16_t w,
                     uint16_t h)
{

    /* 设置x坐标(第几列) */
    Inf_LCD_WriteCmd(0x2A);
    /* 1. 起始列 */
    Inf_LCD_WriteData(x >> 8);   // 高8位
    Inf_LCD_WriteData(x & 0xFF); // 低8位
    /* 2. 结束列 */
    Inf_LCD_WriteData((x + w - 1) >> 8);
    Inf_LCD_WriteData((x + w - 1) & 0xFF);

    /* 设置y坐标(第行列) */
    Inf_LCD_WriteCmd(0x2B);
    /* 1. 起始行 */
    Inf_LCD_WriteData(y >> 8);   /* 高8位 */
    Inf_LCD_WriteData(y & 0xFF); /* 低8位 */
    /* 2. 结束行 */
    Inf_LCD_WriteData((y + h - 1) >> 8);   /* 高8位 */
    Inf_LCD_WriteData((y + h - 1) & 0xFF); /* 低8位 */
}

/**
 * @description: 读取显示像素格式
 */
uint16_t Inf_LCD_ReadDisplayPixelFormat(void)
{
    Inf_LCD_WriteCmd(0x0C);
    Inf_LCD_ReadData();
    return Inf_LCD_ReadData();
}

/**
 * @description: 给LCD设置纯背景色
 * @return {*}
 */
void Inf_LCD_Clear(uint16_t color)
{
    Inf_LCD_SetAddr(0, 0, 320, 480);
    Inf_LCD_FillColor(320 * 480, color);
}

/**
 * @description: 填充颜色
 * @param {uint32_t} num 填充的像素数
 * @param {uint16_t} color 填充的具体颜色
 * @return {*}
 */
void Inf_LCD_FillColor(uint32_t num, uint16_t color)
{
    Inf_LCD_WriteCmd(0x2C);
    while (num--)
    {
        Inf_LCD_WriteData(color);
    }
}

4.2 TM7711(水位测量)软件设计 

Inf_TM7711.h

#ifndef __INF_TIM7711_H
#define __INF_TIM7711_H

#include "gpio.h"

void Inf_TM7711_Init(void);

uint32_t Inf_TM7711_ReadValue(void);
#endif

Inf_TM7711.c

#include "Inf_TM7711.h"
#include "Debug.h"

/* 实现微秒级延时 */
void delay_us(uint32_t us)
{
    uint32_t delay = (HAL_RCC_GetHCLKFreq() / 4000000 * us);
    while (delay--)
        ;
}
/**
 * @description: TM7711模块的初始化
 * @return {*}
 */
void Inf_TM7711_Init(void)
{
    /* 1. 初始化IO引脚 */
    //MX_GPIO_Init();
}

/**
 * @description: 从TM771读取转换后的值
 * @return {*}
 */
uint32_t Inf_TM7711_ReadValue(void)
{
    uint32_t data = 0;
    uint8_t i;
    /* 拉低时钟信号 */
    HAL_GPIO_WritePin(SCK_GPIO_Port, SCK_Pin, GPIO_PIN_RESET);
    /* 一直等到 DOUT 为低电平开始。一轮完整的数据从低电平开始 */
    while (HAL_GPIO_ReadPin(OUT_GPIO_Port, OUT_Pin))
    {
        // debug_printf("x\r\n");
    }

    for (i = 0; i < 24; i++)
    {
        /* 拉高时钟信号 */
        HAL_GPIO_WritePin(SCK_GPIO_Port, SCK_Pin, GPIO_PIN_SET);
        data <<= 1;
        delay_us(5);

        /* 时钟低电平期间 查看 OUT引脚电平 */
        HAL_GPIO_WritePin(SCK_GPIO_Port, SCK_Pin, GPIO_PIN_RESET);
        if (HAL_GPIO_ReadPin(OUT_GPIO_Port, OUT_Pin))
        {
            data |= 0x001;
        }
        delay_us(5);
    }

    /* 24位读完之后,拉高时钟信号. 第25个时钟信号 */
    HAL_GPIO_WritePin(SCK_GPIO_Port, SCK_Pin, GPIO_PIN_SET);
    delay_us(5);
    HAL_GPIO_WritePin(SCK_GPIO_Port, SCK_Pin, GPIO_PIN_RESET);
    delay_us(5);
    return data ^ 0x800000;
}

4.3 水质检测TDS模块软件设计

Inf_ADS1115.c

#include "Inf_ADS1115.h"

/**
 * @description: 初始化 ADS1115 模块。 对ADS1115进行一些配置
 *  1. 连续转换模式                           bit8 = 0     (默认1)
 *  2. 增益改位1倍 满量程范围是: +/- 4.096V   bit[11-9] = 001 (默认010)
 *  3. 输入复用:AINP = AIN0 and AINN = GND    bit[14-12] = 100 默认(000)
 *     其他全部用默认值
 *  为了效率,这些更改要一次性写入
 *  另外配置寄存器启动后的默认值是:0x8583
 */
void Inf_ADS1115_Init(void)
{
    /* 1. 初始化 I2C 通讯 */
    MX_I2C2_Init();

    /* 2. 向控制寄存器写入数据 */
    uint16_t tmp = 0x8583;

    tmp &= ~ADS1115_REG_CONFIG_MODE_MASK;  // 对应的位置设置为0
    tmp |= ADS1115_REG_CONFIG_MODE_CONTIN; // 连续转换模式

    tmp &= ~ADS1115_REG_CONFIG_PGA_MASK;
    tmp |= ADS1115_REG_CONFIG_PGA_4_096V; // 设置增益位1倍

    tmp &= ~ADS1115_REG_CONFIG_MUX_MASK;
    tmp |= ADS1115_REG_CONFIG_MUX_SINGLE_0; // 设置 A0 为模拟输入信号(相对于GND)

    uint8_t data[3] = {
        0x01,
        (tmp >> 8) & 0xff,
        tmp & 0xff};

    HAL_I2C_Master_Transmit(&hi2c2, ADS1115_ADDRESS_W, data, 3, 1000);
}

/**
 * @description: 从转换寄存器读取电压值
 * @return {*}
 */
double Inf_ADS1115_ReadVoltage(void)
{
    /* 1. 向指针寄存器写入0x00表示要操作转换寄存器 */
    uint8_t data1 = 0x00;
    HAL_I2C_Master_Transmit(&hi2c2, ADS1115_ADDRESS_W, &data1, 1, 1000);

    /* 2. 读取电压值 */
    uint8_t rData[2] = {0};
    HAL_I2C_Master_Receive(&hi2c2, ADS1115_ADDRESS_R, rData, 2, 1000);
    int16_t tmp = 0;
    tmp |= (rData[0] << 8);
    tmp |= rData[1];
    /* 7. 转成电压值返回。 得到的值是用补码来表示. 2^15-1= 32767*/
    return tmp * 4.096 / 32767;
}

4.4 应用层APP软件设计

App_WaterLevel.c

#include "App_WaterLevel.h"

/**
 * @description: 初始化水位检测
 */
void APP_WaterLevel_Init(void)
{
    /* 1. 初始化 TM7711 模块 */
    Inf_TM7711_Init();

    /* 2. 初始化 W25Q32 */
    Inf_W25Q32_Init();

    /* 3. 对水位进行校准 */
    APP_WaterLevel_Calibrate();
}

/**
 * @description: 对水位进行校准
 *  通过按键提示用户校准流程
 *  直接用轮询的方式检测按键是否按下,不利用中断。
 *  y=ax+b。   x 表示水位 y 是对应从AD读出来的值
 *  当 x=0    时得y1
 *  当 x=10cm 时得y2
 *  计算出来 a 和 b的值
 *      b=y1
 *      a=(y2-y1)/10
 *  然后 x = (y - b)/a
 */
double a, b;
uint8_t abBuff[30] = {0};
uint8_t tmp[1] = {0};
void APP_WaterLevel_Calibrate(void)
{
    /* 1. 查看Flash中是否有存储a和b,如果有就无需做校准 */
    /* 1.1 读取第1个字节 */
    Inf_W25Q32_ReadData(0x00, tmp, 1);
    printf("%d\r\n", tmp[0]);
    if (tmp[0] > 0 && tmp[0] < 255) /* 曾经校准过 */
    {
        /* 1.2 读取a和b的值 */
        Inf_W25Q32_ReadData(0x01, abBuff, tmp[0]);
        /* 1.3 解析出a和b的值    123.55#124.66 */
        a = strtod(strtok((char *)abBuff, "#"), NULL);
        b = strtod(strtok(NULL, "#"), NULL);
        /* 1.4 结束后面的校准过程 */
        return;
    }

    Inf_LCD_DisplayAsciiString(10, 150,
                               "Start to calibrate water level... ",
                               RED, BLUE, 24);
    Inf_LCD_Clear(WHITE);
    Inf_LCD_DisplayAsciiString(10, 200,
                               "1. Don't put into water, then press the key.... ",
                               RED, BLUE, 24);
    /* 2. 等待按键第1次按下 */
    while (Driver_GPIO_IsKey3Pressed() == 0)
        ;
    /* 按下了按键, 读出电压值。 水位为0时的电压值 */
    uint32_t y1 = Inf_TM7711_ReadValue();
    Inf_LCD_Clear(WHITE);
    Inf_LCD_DisplayAsciiString(10, 200,
                               "2. Put into water 10cm, then press the key....   ",
                               RED, BLUE, 24);
    /* 3. 等待按键第2次按下 */
    while (Driver_GPIO_IsKey3Pressed() == 0)
        ;
    /* 按下了按键, 读出电压值。水位是10cm时的电压值 */
    uint32_t y2 = Inf_TM7711_ReadValue();

    b = y1;
    a = (y2 - y1) / 10.0;

    /* 在屏幕显示校准完成信息.... */
    Inf_LCD_Clear(WHITE);
    Inf_LCD_DisplayAsciiString(10, 200,
                               "Calibration is done!",
                               RED, BLUE, 24);
    /*
        4. 把 a和b的值存入到flash中,下次启动就无需校准
        写入格式:第一个字节表示后面存储的字节数
                后面就是存储的具体数据

                比如: 11 123.45#234.69
                    11表示后面一共存储了11个字节的数据。

    */
    sprintf((char *)abBuff, "%.2f#%.2f", a, b);
    tmp[0] = strlen((char *)abBuff);
    /* 4.1 擦除扇区 */
    Inf_W25Q32_SectorErase(0x0);
    /* 4.2 写入第一个字节。 从0x00开始写 */
    Inf_W25Q32_WritePage(0x00, tmp, 1);
    /* 4.3 接着写入a和b的值. 从0x01开始写 tmp[0]个字节 */
    Inf_W25Q32_WritePage(0x01, abBuff, tmp[0]);
}

/**
 * @description: 读取水位值
 * @return {uint32_t} 得到的水位值,单位是cm
 */
double APP_WaterLevle_ReadWaterLevle(void)
{
    uint32_t y = Inf_TM7711_ReadValue();
    return (y - b) / a;
}

最后是我们的main函数

#include "main.h"
#include "i2c.h"
#include "spi.h"
#include "usart.h"
#include "gpio.h"
#include "fsmc.h"
void SystemClock_Config(void);
#include "Debug.h"
#include "APP_Display.h"
#include "APP_TDS.h"
#include "App_WaterLevel.h"
uint8_t buff[30] = {0};
int main(void)
{
    /* HAL的一些初始化代码 */
    HAL_Init();
    SystemClock_Config();
    MX_GPIO_Init();

    debug_init();
    debug_printf("尚硅谷水质水位检测项目.....\r\n");

    /* 1. 初始化显示模块 */
    APP_Display_Init();

    /* 3. 初始化 水位检测模块 */
    APP_WaterLevel_Init();
    /* 4. 显示Logo*/
    APP_Display_DisplayLogo();

    /* 5. 启动 TDS 监测 */
    APP_TDS_Start();

    while (1)
    {
        /* 7. 显示水位 */
        double waterLevel = APP_WaterLevle_ReadWaterLevle();
        sprintf((char *)buff, "water_level=%.2fcm     ", waterLevel);
        APP_Display_DisplayString(10, 60, buff);

        /* 8. 显示水质 */
        double tds = APP_TDS_GetTDS();
        memset(buff, 0, sizeof(buff));
        sprintf((char *)buff, "TDS=%.2f               ", tds);
        APP_Display_DisplayString(10, 120, buff);
        HAL_Delay(1000);
    }
}

5.项目系统测试

根据硬件架构连接好线路,烧录好程序的工程文件,完成测试,效果展示如下。

水位水质监测系统测试

好了,以上就是关于STM32下水位水质监测系统的全部设计流程了,需要项目资料的可以私我,欢迎大家交流讨论 。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值