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不必转成具体的电压)。则有下面的等式成立:芯片的接线如图所示:SCK接PA2,是时钟信号,OUT接PA3,是数据输出信号。
知道了硬件的连线后,还需要根据其时序图来读取数据,如下所示:
3.1.2 TDS采集模块
TDS采集的原理就是当水中的导电粒子多时,导电性好,采集到的电压高;导电粒子少时导电性差,采集到的电压低。可以简单的认为水中杂质多时,导电粒子多,杂质少时导电粒子少。所以可以通过采集的电压高低来计算TDS的值。测出电压值,根据公式可以计算出来TDS的值。
模块的硬件接线如下图中所示
3.1.3 外置ADC模块(ADS1115)
STM32中内置ADC精度只有12位,本工程项目选用的外置ADC模块中的ADS1115是16位精度,支持4路电压转换,而且支持单端和差分输入。与STM32通讯使用I2C通用协议,物理接线如下图所示。
根据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下水位水质监测系统的全部设计流程了,需要项目资料的可以私我,欢迎大家交流讨论 。