STM32项目---水质水位检测

1 项目简介

1.1 项目需求

本项目通过测量水体的TDS来反映水体的质量。并同时可以测量水位(水深)。

1.2 系统总体设计

在这里插入图片描述

2 硬件模块

2.1 硬件选型

  • 水位测量模块

在这里插入图片描述

在这里插入图片描述

  • TDS采集模块

在这里插入图片描述
在这里插入图片描述

  • 外置ADC模块(ADS1115)
    在这里插入图片描述

2.2 水位测量模块使用方法

  • 测量原理

水位测量传感器本质上是一个压力测量传感器。压力的值和传感器产生的电压值是线性关系,压力的值和水深也是线性关系。

  • 物理接线

在这里插入图片描述

SCK接PB12,是时钟信号。

OUT接PB13,是数据输出信号。

  • 时序图

在这里插入图片描述

在这里插入图片描述

2.3 TDS采集模块使用方法

  • 测量原理

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

  • 物理接线

在这里插入图片描述

2.4 ADC模块ADS1115使用方法

内置ADC精度只有12位,我们这次选用的外置ADC模块中的ADS1115是16位精度,支持4路电压转换,而且支持单端和差分输入。

与STM32通讯使用I2C通用协议。

  • 物理接线

在这里插入图片描述

  • ADC模块的在I2C中的通讯地址

模块中的ADDR引脚的不同接法决定模块的地址(7位地址)。

在这里插入图片描述

具体操作读写时,需要在末尾再加1位表示读或写:0表示写,1表示读。

如果我们把ADDR接GND,则写地址是就是:0x48<<1,读地址就是(0x48<<1) + 1。

  • ADC中的寄存器

一共提供了5个寄存器:

Pointer Register(指针寄存器)、Conversion Register (转换寄存器)、Config Register(配置寄存器)、Lo_thresh and Hi_thresh Registers(高低阈值寄存器)

    • 指针寄存器

在这里插入图片描述

在这里插入图片描述

    • 转换寄存器(读出的寄存器)

在这里插入图片描述

    • 配置寄存器 (写入配置的寄存器)

在这里插入图片描述

将来我们会配置3个地方,其他的全部使用默认值。

(1)采集通道:配置为100,表示采集0通道。

在这里插入图片描述

(2)增益:配置为001,表示不增益。

在这里插入图片描述

(3)转换模式:配置为0,表示连续转换。

在这里插入图片描述

  • 读写时序

在这里插入图片描述

在这里插入图片描述

3 软件架构及代码实现

在这里插入图片描述

调试模块

Common
  • Debug.c
#include "Debug.h"void Debug_Init(void)
{Driver_USART1_Init();Driver_USART1_Start();
}
  • Debug.h

strrchr: 用于查找一个字符在字符串中最后一次出现的位置。

其中...##__VA_ARGS__表示可变参数,需要导入头文件stdarg.h

[%s:%d]:表示对应文件名和行号,对应后面的__FILE____LINE__

#ifndef __DEBUG_H
#define __DEBUG_H#include "Driver_USART1.h"
#include "stdarg.h"
#include "string.h"
/*** 使用条件定义来表示是否开启printf输出功能* 如果#define DEBUG  表名开启debug功能  后续统一使用宏定义debug_printf()来输出调试信息* 实际产品上线的时候  将#define DEBUG修改掉  else分支中会存在debug_printf()内容为空的宏定义* 之前调用的debug_printf()都会统一失效为空  */
#define DEBUG#ifdef DEBUG#define debug_init() Debug_Init()// 拆分文件的路径加文件名称  只保留文件名称即可
// User\main.c  =>  main.c
//加1是为了从 \ 后面开始打印,不要这个反斜杠
#define FILE_NAME   strrchr(__FILE__,'\\') ? strrchr(__FILE__,'\\') + 1 : __FILE__// 在打印debug信息的时候 先输出处于哪个文件的哪一行
// 拼接前缀字符串"[%s:%d]"   表示对应的文件名和行号
#define debug_printf(format,...) printf("[%s:%d]" format ,FILE_NAME,__LINE__,##__VA_ARGS__)#define debug_printfln(format,...) printf("[%s:%d]" format "\n",FILE_NAME,__LINE__,##__VA_ARGS__)#else
#define debug_init() // 相同的宏定义名称  内容留空
#define debug_printf(format,...)  // 相同的宏定义名称  内容留空
#define debug_printfln(format,...) // 相同的宏定义名称 内容留空
#endifvoid Debug_Init(void);#endif
  • Delay.c

LOAD:先装载

VAL:写任意值清空

CTRL:配置

在这里插入图片描述

#include "Delay.h"void Delay_us(uint16_t us)
{/* 1. 装载需要计数的值 */SysTick->LOAD = 72 * us;/* 2. 清空原本val的值 */SysTick->VAL = 0;/* 3. 配置时钟源  关闭中断  启动定时器 */SysTick->CTRL |= SysTick_CTRL_CLKSOURCE;SysTick->CTRL &= ~SysTick_CTRL_TICKINT;SysTick->CTRL |= SysTick_CTRL_ENABLE;/* 4. 等待定时器计数 */while ((SysTick->CTRL & SysTick_CTRL_COUNTFLAG) == 0);/* 5. 关闭定时器 */SysTick->CTRL &= ~SysTick_CTRL_ENABLE;
}void Delay_ms(uint16_t ms)
{while (ms--){Delay_us(1000);}
}void Delay_s(uint16_t s)
{while (s--){Delay_ms(1000);}
}
Driver
  • Driver_USART1.c
#include "Driver_USART1.h"/* 初始化串口通信 */
void Driver_USART1_Init(void)
{/* 1. 开时钟  开引脚对应的时钟  PA */RCC->APB2ENR |= RCC_APB2ENR_USART1EN;RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;/* 2. 配置引脚模式 */// PA9 TX = 复用推挽输出 1011 PA10 RX = 浮空输入 0100GPIOA->CRH |= (GPIO_CRH_CNF9_1 | GPIO_CRH_MODE9);GPIOA->CRH &= ~GPIO_CRH_CNF9_0;GPIOA->CRH |= GPIO_CRH_CNF10_0;GPIOA->CRH &= ~(GPIO_CRH_CNF10_1 | GPIO_CRH_MODE10);/* 3. 配置USART1的寄存器 */// 3.1 配置波特率USART1->BRR |= 0X271;// 3.2 字长   停止位长度   关闭校验USART1->CR1 &= ~USART_CR1_M;USART1->CR2 &= ~USART_CR2_STOP;USART1->CR1 &= ~USART_CR1_PCE;// 3.3 开启收发功能USART1->CR1 |= USART_CR1_TE | USART_CR1_RE;
}/* 启动串口通信 */
void Driver_USART1_Start(void)
{// 总开关开启USART1->CR1 |= USART_CR1_UE;
}/* 停止串口通信 */
void Driver_USART1_Stop(void)
{// 总开关关闭USART1->CR1 &= ~USART_CR1_UE;
}/* 发送字节 */
void Driver_USART1_SendChar(uint8_t ch)
{/* 1. 判断标志位 */while ((USART1->SR & USART_SR_TXE) == 0);/* 2. 将字节数据写入到DR寄存器即为发送 */USART1->DR = ch;
}/* 发送字符串 */
void Driver_USART1_SendString(uint8_t *str, uint16_t len)
{for (uint16_t i = 0; i < len; i++){Driver_USART1_SendChar(str[i]);}
}/* 使用fputc重新原先printf中的调用  重定向到USART1的串口通信  实现printf打印字符串的功能 */
int fputc(int ch,FILE *file)
{Driver_USART1_SendChar((int)ch);return ch;
}

显示模块

Driver
  • Driver_FSMC.c
#include "Driver_FSMC.h"/*** @description: 对FSMC用到的各个 IO 端口进行配置*/
static void Driver_FSMC_GPIO_Init(void)
{/* 1 配置 A10 (PG0) 复用推挽输出CNY:10 50MHz速度 MODE:11*//* =============MODE=============== */GPIOG->CRL |= GPIO_CRL_MODE0;GPIOG->CRL |= GPIO_CRL_CNF0_1;GPIOG->CRL &= ~GPIO_CRL_CNF0_0;/*2 数据端口 复用推挽输出在实际应用中,即使数据线被配置为输出模式,FSMC控制器仍然能够管理数据线的方向,使其在需要时成为输入线。这种自动切换是由FSMC控制器硬件管理的,不需要软件干预。因此,即使GPIO配置为复用推挽输出,FSMC依然可以实现读取操作。*//* =============MODE=============== */GPIOD->CRL |= (GPIO_CRL_MODE0 |GPIO_CRL_MODE1);GPIOD->CRH |= (GPIO_CRH_MODE8 |GPIO_CRH_MODE9 |GPIO_CRH_MODE10 |GPIO_CRH_MODE14 |GPIO_CRH_MODE15);GPIOE->CRL |= (GPIO_CRL_MODE7);GPIOE->CRH |= (GPIO_CRH_MODE8 |GPIO_CRH_MODE9 |GPIO_CRH_MODE10 |GPIO_CRH_MODE11 |GPIO_CRH_MODE12 |GPIO_CRH_MODE13 |GPIO_CRH_MODE14 |GPIO_CRH_MODE15);/* =============CNF=============== */GPIOD->CRL |= (GPIO_CRL_CNF0_1 |GPIO_CRL_CNF1_1);GPIOD->CRL &= ~(GPIO_CRL_CNF0_0 |GPIO_CRL_CNF1_0);GPIOD->CRH |= (GPIO_CRH_CNF8_1 |GPIO_CRH_CNF9_1 |GPIO_CRH_CNF10_1 |GPIO_CRH_CNF14_1 |GPIO_CRH_CNF15_1);GPIOD->CRH &= ~(GPIO_CRH_CNF8_0 |GPIO_CRH_CNF9_0 |GPIO_CRH_CNF10_0 |GPIO_CRH_CNF14_0 |GPIO_CRH_CNF15_0);GPIOE->CRL |= (GPIO_CRL_CNF7_1);GPIOE->CRL &= ~(GPIO_CRL_CNF7_0);GPIOE->CRH |= (GPIO_CRH_CNF8_1 |GPIO_CRH_CNF9_1 |GPIO_CRH_CNF10_1 |GPIO_CRH_CNF11_1 |GPIO_CRH_CNF12_1 |GPIO_CRH_CNF13_1 |GPIO_CRH_CNF14_1 |GPIO_CRH_CNF15_1);GPIOE->CRH &= ~(GPIO_CRH_CNF8_0 |GPIO_CRH_CNF9_0 |GPIO_CRH_CNF10_0 |GPIO_CRH_CNF11_0 |GPIO_CRH_CNF12_0 |GPIO_CRH_CNF13_0 |GPIO_CRH_CNF14_0 |GPIO_CRH_CNF15_0);/* 3 其他控制端口  复用推挽输出 *//* 3.1 PD4: 读控制引脚  PD5: 写控制引脚 */GPIOD->CRL |= (GPIO_CRL_MODE4 |GPIO_CRL_MODE5);GPIOD->CRL |= (GPIO_CRL_CNF4_1 |GPIO_CRL_CNF5_1);GPIOD->CRL &= ~(GPIO_CRL_CNF4_0 |GPIO_CRL_CNF5_0);/* 3.2  PG12:NE4*/GPIOG->CRH |= (GPIO_CRH_MODE12);GPIOG->CRH |= (GPIO_CRH_CNF12_1);GPIOG->CRH &= ~(GPIO_CRH_CNF12_0);/* 3.3  背光引脚PB0:通用推挽输出*/GPIOB->CRL |= GPIO_CRL_MODE0;GPIOB->CRL &= ~GPIO_CRL_CNF0;/* 3.4  重置引脚PG15:通用推挽输出*/GPIOG->CRH |= GPIO_CRH_MODE15;GPIOG->CRH &= ~GPIO_CRH_CNF15;
}/*** @description: 初始化* @return {*}*/
void Driver_FSMC_Init(void)
{/* 1 开启时钟 *//* 1.1 开启FSMC时钟 */RCC->AHBENR |= RCC_AHBENR_FSMCEN;/* 1.2 开启用到的 GPIO 时钟:PD PE PF PG */RCC->APB2ENR |= (RCC_APB2ENR_IOPDEN |RCC_APB2ENR_IOPBEN |RCC_APB2ENR_IOPEEN |RCC_APB2ENR_IOPFEN |RCC_APB2ENR_IOPGEN);/* 1.3 AFIO 时钟 */RCC->APB2ENR |= RCC_APB2ENR_AFIOEN;/* 3 配置 FSMC 各个端口的输入输出模式 */Driver_FSMC_GPIO_Init();/* 4. 配置FSMC参数  *//* 4.1 存储器块使能 根据接线图可知是 Bank1的 3区   1区对应0 2区对应2 3区对应4 4区对应6  (BCR寄存器)*/FSMC_Bank1->BTCR[6] |= FSMC_BCR4_MBKEN;/* 4.2 设置存储器类型 00: SRAM ROM 01:PSRAM 10:NOR闪存*/FSMC_Bank1->BTCR[6] &= ~FSMC_BCR4_MTYP;/* 4.3 闪存访问使能: 禁止访问闪存 */FSMC_Bank1->BTCR[6] &= ~FSMC_BCR4_FACCEN;/* 4.4 地址和数据总线复用: 不复用 */FSMC_Bank1->BTCR[6] &= ~FSMC_BCR4_MUXEN;/* 4.5 数据总线宽度 00:8位 01:16位*/FSMC_Bank1->BTCR[6] &= ~FSMC_BCR4_MWID_1;FSMC_Bank1->BTCR[6] |= FSMC_BCR4_MWID_0;/* 4.6 写使能 */FSMC_Bank1->BTCR[6] |= FSMC_BCR4_WREN;/* 5. 配置SRAM的时序参数 BTCR[7] 访问bank1 4区对应的BTR寄存器 *//* 5.1 地址建立时间(时钟周期) 0:表示一个时钟周期。 对同步读写来说无效,永远是1个时钟周期*/FSMC_Bank1->BTCR[7] &= ~FSMC_BTR4_ADDSET;/* 5.2 地址保持时间(时钟周期) 对同步读写来说无效,永远是1个时钟周期*/FSMC_Bank1->BTCR[7] &= ~FSMC_BTR4_ADDHLD;/* 5.3 数据保持时间(时钟周期)数据在总线上的停留时间 */FSMC_Bank1->BTCR[7] &= ~FSMC_BTR4_DATAST; /* 对应的位置位0 */FSMC_Bank1->BTCR[7] |= (30 << 8);         /* 设置为1个us. 72个时钟周期 *//* 5.4 设置访问模式为A */FSMC_Bank1->BTCR[7] &= ~FSMC_BTR4_ACCMOD;
}
Inf
  • Inf_LCD.h
#ifndef __INF_LCD_H__
#define __INF_LCD_H__#include "Driver_FSMC.h"
#include "Delay.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_ClearAll(uint16_t color);
void Inf_LCD_SetArea(uint16_t x, uint16_t y, uint16_t w, uint16_t h);
void Inf_LCD_FillColor(uint32_t nums, uint16_t color);
void Inf_LCD_DisplayAsciiChar(uint16_t x,uint16_t y,uint16_t h,uint8_t ch,uint16_t color,uint16_t bcolor);
void Inf_LCD_DisplayString(uint16_t x,uint16_t y,uint8_t * str,uint16_t color,uint16_t bcolor);
void Inf_LCD_DisplayChinese(uint16_t x,uint16_t y,uint8_t index,uint16_t color,uint16_t bcolor);
void Inf_LCD_DisplayImage(uint16_t x,uint16_t y);#endif /* __INF_LCD_H__ */
  • Inf_LCD.c
#include "Inf_LCD.h"
#include "Inf_LCD_Font.h"/* 发送命令 */
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;
}/* 初始化寄存器的值 */
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);Delay_ms(120);/* 14. display on */Inf_LCD_WriteCmd(0x29);
}/* 重置LCD PG15*/
void Inf_LCD_Reset(void)
{GPIOG->ODR &= ~GPIO_ODR_ODR15; // 低电平重置Delay_ms(50);GPIOG->ODR |= GPIO_ODR_ODR15; // 高电平正常Delay_ms(50);
}/* 给LCD开启背光 */
void Inf_LCD_BKOpen(void)
{GPIOB->ODR |= GPIO_ODR_ODR0; // 高电平开启背光
}
/* 给LCD关闭背光 */
void Inf_LCD_BKClose(void)
{GPIOB->ODR &= ~GPIO_ODR_ODR0; // 低电平关闭背光(最暗)
}/* 初始化LCD */
void Inf_LCD_Init(void)
{/* 0. 初始化FSMC */Driver_FSMC_Init();// 1. 重置LCDInf_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; // 制造商idid |= Inf_LCD_ReadData() << 8;  // 模块/驱动版本号idid |= Inf_LCD_ReadData();       // 模块/驱动idreturn id;
}void Inf_LCD_SetArea(uint16_t x, uint16_t y, uint16_t w, uint16_t h)
{/*1 设置X的范围*/Inf_LCD_WriteCmd(0x2a);// 起始位置Inf_LCD_WriteData(x >> 8);Inf_LCD_WriteData(x & 0xff);// 结束位置Inf_LCD_WriteData((x + w - 1) >> 8);Inf_LCD_WriteData((x + w - 1) & 0xff);/*2 设置y的范围*/Inf_LCD_WriteCmd(0x2b);// 起始位置Inf_LCD_WriteData(y >> 8);Inf_LCD_WriteData(y & 0xff);// 结束位置Inf_LCD_WriteData((y + h - 1) >> 8);Inf_LCD_WriteData((y + h - 1) & 0xff);
}void Inf_LCD_FillColor(uint32_t nums, uint16_t color)
{/*1 发送填充数据的命令*/Inf_LCD_WriteCmd(0X2C);/*2 循环写入*/for (uint32_t i = 0; i < nums; i++){Inf_LCD_WriteData(color);}
}void Inf_LCD_ClearAll(uint16_t color)
{// 1 设置涂色区域Inf_LCD_SetArea(0, 0, 320, 480);// 2 填充颜色Inf_LCD_FillColor(320 * 480, color);
}void Inf_LCD_DisplayAsciiChar(uint16_t x, uint16_t y, uint16_t h, uint8_t ch, uint16_t color, uint16_t bcolor)
{// 统一使用32高度的字符/*1. 设置涂色区域*/Inf_LCD_SetArea(x, y, h / 2, h);/*2. 根据字模 填充颜色*/Inf_LCD_WriteCmd(0x2c);// 找到要展示的对应的字符uint8_t index = ch - ' ';for (uint32_t i = 0; i < 64; i++){uint8_t tmp = ascii_3216[index][i];for (uint8_t j = 0; j < 8; j++){if (tmp & 0x01){Inf_LCD_WriteData(color);}else{Inf_LCD_WriteData(bcolor);}tmp >>= 1;}}
}void Inf_LCD_DisplayString(uint16_t x, uint16_t y, uint8_t *str, uint16_t color, uint16_t bcolor)
{uint8_t i = 0;uint8_t h = 32;while (str[i] != '\0'){// 可以展示的字符if (str[i] == '\n'){x = 0;y += h;}else{// 展示真正的字符// 如果展示不下也要换行if ((x + h / 2) > 320){x = 0;y += h;}// 写字符Inf_LCD_DisplayAsciiChar(x, y, h, str[i], color, bcolor);x += h / 2;}i++;}
}void Inf_LCD_DisplayChinese(uint16_t x, uint16_t y, uint8_t index, uint16_t color, uint16_t bcolor)
{// 设置区域 32 * 32Inf_LCD_SetArea(x, y, 32, 32);Inf_LCD_WriteCmd(0x2c);for (uint16_t i = 0; i < 32 * 4; i++){uint8_t tmp = chinese[index][i];for (uint8_t j = 0; j < 8; j++){if (tmp & 0x01){Inf_LCD_WriteData(color);}else{Inf_LCD_WriteData(bcolor);}tmp >>= 1;}}
}void Inf_LCD_DisplayImage(uint16_t x,uint16_t y)
{//图片分辨率 206 54Inf_LCD_SetArea(x,y,206,54);//写入数据Inf_LCD_WriteCmd(0x2c);for (uint32_t i = 0; i < 206 * 54 * 2; i+= 2){uint16_t color = (gImage_atguigu[i] << 8) | gImage_atguigu[i + 1];Inf_LCD_WriteData(color);}}
  • Inf_LCD_Font.h

展示图像的时候要确定好分辨率

App
  • App_display.c
#include "App_display.h"void App_Display_Init(void)
{Inf_LCD_Init();
}
void App_Display_Back(void)
{//最后背景为白色Inf_LCD_ClearAll(WHITE);//最上方中间位置展示Logo 206 54Inf_LCD_DisplayImage((320 - 206)/2,10);//在logo下方展示标题for (uint8_t i = 0; i < 9; i++){Inf_LCD_DisplayChinese(32*i + 16,70,i,GREEN,WHITE);}}
void App_Display_String(uint16_t x,uint16_t y,uint8_t * str)
{Inf_LCD_DisplayString(x, y, str, BLUE, WHITE);}
void App_Display_ClearString(void)
{// 只清空110行往下的内容Inf_LCD_SetArea(0, 110, 320, 370);// 涂色白色Inf_LCD_FillColor(320 * 370, WHITE);
}

水位测量模块

Driver
  • Driver_GPIO.h
#ifndef __DRIVER_GPIO_H__
#define __DRIVER_GPIO_H__#include "stm32f10x.h"
#include "Delay.h"//时钟变换和读取out数据
#define SCK_HIGH (GPIOB->ODR |= GPIO_ODR_ODR12)
#define SCK_LOW (GPIOB->ODR &= ~GPIO_ODR_ODR12)
#define READ_OUT (GPIOB->IDR & GPIO_IDR_IDR13)void Driver_GPIO_TM7711_Init(void);
/* 返回1表示被按下,返回0表示没有被按下*/
uint8_t Driver_GPIO_isKeyPressed(void);#endif /* __DRIVER_GPIO_H__ */
  • Driver_GPIO.c
#include "Driver_GPIO.h"void Driver_GPIO_TM7711_Init(void)
{/*1. PB12 PB13 PF8 打开时钟*/RCC->APB2ENR |= RCC_APB2ENR_IOPBEN;RCC->APB2ENR |= RCC_APB2ENR_IOPFEN;/*2. 配置引脚模式*///PB12 sck 通用推挽 0011GPIOB->CRH |= GPIO_CRH_MODE12;GPIOB->CRH &= ~GPIO_CRH_CNF12;//PB13 OUT 浮空输入 0100GPIOB->CRH |= GPIO_CRH_CNF13_0;GPIOB->CRH &= ~(GPIO_CRH_CNF13_1 | GPIO_CRH_MODE13);//PF8 按键 上拉输入  1000GPIOF->CRH |= GPIO_CRH_CNF8_1;GPIOF->CRH &= ~(GPIO_CRH_CNF8_0 | GPIO_CRH_MODE8);//ODR中的值决定上/下拉GPIOF->ODR |= GPIO_ODR_ODR8;}uint8_t Driver_GPIO_isKeyPressed(void)
{while (GPIOF->IDR & GPIO_IDR_IDR8);//一直等到被按下Delay_ms(100);if ((GPIOF->IDR & GPIO_IDR_IDR8) == 0){//消抖完成,真的被按下//等待抬起while ((GPIOF->IDR & GPIO_IDR_IDR8) == 0);return 1;}//消抖之后 确认没有被按下return 0;}
  • Driver_SPI.h
#ifndef __DRIVER_SPI_H__
#define __DRIVER_SPI_H__#include "stm32f10x.h"#define CS_HIGH (GPIOC->ODR |= GPIO_ODR_ODR13)
#define CS_LOW (GPIOC->ODR &= ~GPIO_ODR_ODR13)void Driver_SPI_Init(void);void Driver_SPI_Start(void);void Driver_SPI_Stop(void);uint8_t Driver_SPI_SwapByte(uint8_t data);#endif /* __DRIVER_SPI_H__ */
  • Driver_SPI.c
#include "Driver_SPI.h"void Driver_SPI_Init(void)
{/* 1. PA PC SPI1 */RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;RCC->APB2ENR |= RCC_APB2ENR_IOPCEN;RCC->APB2ENR |= RCC_APB2ENR_SPI1EN;/* 2. 配置引脚模式 */// PA5 复用推挽输出  PA7 复用推挽输出 1011GPIOA->CRL |= (GPIO_CRL_CNF5_1 | GPIO_CRL_CNF7_1 | GPIO_CRL_MODE5 | GPIO_CRL_MODE7);GPIOA->CRL &= ~(GPIO_CRL_CNF5_0 | GPIO_CRL_CNF7_0);// PA6 浮空输入 0100GPIOA->CRL &= ~(GPIO_CRL_CNF6_1 | GPIO_CRL_MODE6);GPIOA->CRL |= (GPIO_CRL_CNF6_0);// PC13通用推挽输出  0011GPIOC->CRH |= GPIO_CRH_MODE13;GPIOC->CRH &= ~GPIO_CRH_CNF13;/* 3. 配置SPI的寄存器 */// 3.1 主设备选择SPI1->CR1 |= SPI_CR1_MSTR;// 3.2 配置模式选择 0模式 00SPI1->CR1 &= ~SPI_CR1_CPHA;SPI1->CR1 &= ~SPI_CR1_CPOL;// 3.3 波特率选择  4分频 001SPI1->CR1 &= ~SPI_CR1_BR;SPI1->CR1 |= SPI_CR1_BR_0;// 3.4 先发送高位SPI1->CR1 &= ~SPI_CR1_LSBFIRST;// 3.5 软件从设备管理SPI1->CR1 |= SPI_CR1_SSM;SPI1->CR1 |= SPI_CR1_SSI;// 3.6 选择8位SPI1->CR1 &= ~SPI_CR1_DFF;// 3.7 使能SPI1->CR1 |= SPI_CR1_SPE;
}void Driver_SPI_Start(void)
{// 片选CS_LOW;
}void Driver_SPI_Stop(void)
{CS_HIGH;
}uint8_t Driver_SPI_SwapByte(uint8_t data)
{// 硬件模式 => 有状态位  直接状态位判断while ((SPI1->SR & SPI_SR_TXE) == 0);// 发送数据SPI1->DR = data;// 判断读取数据存在while ((SPI1->SR & SPI_SR_RXNE) == 0);return SPI1->DR;
}
Inf
  • Inf_W25q32.c ---- (Flash)
#include "Inf_W25q32.h"void Inf_W25Q32_Init(void)
{Driver_SPI_Init();
}/*测试*/
void Inf_W25Q32_ReadID(uint8_t *id1,uint16_t *id2)
{//1. 启动Driver_SPI_Start();//2. 输入读取ID的指令Driver_SPI_SwapByte(0x9f);//3. 接收数据*id1 = Driver_SPI_SwapByte(0xff);//高位优先*id2 |= Driver_SPI_SwapByte(0xff) << 8;*id2 |= Driver_SPI_SwapByte(0xff);//4. 停止Driver_SPI_Stop();
}/* 等待忙状态*/
void Inf_W25Q32_WaitNoBusy(void)
{//1. 启动Driver_SPI_Start();//2. 发送查看状态的指令Driver_SPI_SwapByte(0x05);//3. 等待不忙 => 返回的字节最后一位为0 表示不忙while (Driver_SPI_SwapByte(0xff) & 0x01);//4. 停止Driver_SPI_Stop();
}/*等待写使能*/
void Inf_W25Q32_WriteEnable(void)
{//1. 启动Driver_SPI_Start();//2. 发送写使能的指令Driver_SPI_SwapByte(0x06);//3. 停止Driver_SPI_Stop();
}/*擦除扇区*/
void Inf_W25Q32_Sector_Erase(uint32_t addr)
{//1. 等待忙状态Inf_W25Q32_WaitNoBusy();//2. 开启写使能Inf_W25Q32_WriteEnable();//3. 擦除操作//3.1 启动Driver_SPI_Start();//3.2 发送擦除指令Driver_SPI_SwapByte(0x20);//3.3 写入擦除地址Driver_SPI_SwapByte(addr >> 16);Driver_SPI_SwapByte((addr >> 8) & 0xff);Driver_SPI_SwapByte((addr >> 0) & 0xff);//3.4 关闭Driver_SPI_Stop();}/*写入数据*/
void Inf_W25Q32_PageProgram(uint32_t addr,uint8_t data[],uint8_t len)
{//1. 等待忙状态Inf_W25Q32_WaitNoBusy();//2. 开启写使能Inf_W25Q32_WriteEnable();//3. 开启Driver_SPI_Start();//4. 发送写指令Driver_SPI_SwapByte(0x02);//5. 写24位地址Driver_SPI_SwapByte(addr >> 16);Driver_SPI_SwapByte((addr >> 8) & 0xff);Driver_SPI_SwapByte((addr >> 0) & 0xff);//6. 依次写入数据for (uint8_t i = 0; i < len; i++){Driver_SPI_SwapByte(data[i]);}//7. 关闭Driver_SPI_Stop();
}/*读取数据*/
void Inf_W25Q32_ReadData(uint32_t addr,uint8_t * buff,uint8_t len)
{//1. 等待忙状态Inf_W25Q32_WaitNoBusy();//2. 启动Driver_SPI_Start();//3. 发送读指令Driver_SPI_SwapByte(0x03);//4. 发送24位地址Driver_SPI_SwapByte(addr >> 16);Driver_SPI_SwapByte((addr >> 8) & 0xff);Driver_SPI_SwapByte((addr >> 0) & 0xff);//5. 读取数据for (uint8_t i = 0; i < len; i++){buff[i] = Driver_SPI_SwapByte(0xff);}//6. 停止Driver_SPI_Stop();
}
  • Inf_TM7711.c ---- (水位测量)
#include "Inf_TM7711.h"void Inf_TM7711_Init(void)
{Driver_GPIO_TM7711_Init();}//读取水位传感器的值
uint32_t Inf_TM7711_ReadV(void)
{uint32_t data = 0;/*1. 等待空闲信号*/SCK_LOW;Delay_us(5);while (READ_OUT );/*2. 循环24次读取电压值*/for (uint8_t i = 0; i < 24; i++){SCK_HIGH;Delay_us(5);SCK_LOW;//每次的下降沿读取数据data <<= 1;if (READ_OUT){data |= 0x01;}Delay_us(5);   }/*3. 使用第25个空时钟信号*/SCK_HIGH;Delay_us(5);SCK_LOW;Delay_us(5);//注意:这里data存的数据是用补码存储的  -> 转化为正数存储return data ^ 0x800000;}
App
  • App_water_level.c
#include "App_water_level.h"double a = 0;
double b = 0;
uint8_t ABbuff[50] = {0};
uint8_t ab_len[1] = {0};
/* 启动水位测量系统并完成校验*/
void App_Water_Level_Start(void)
{//先初始化Inf_TM7711_Init();//初始化w25q32Inf_W25Q32_Init();//再校验App_Water_Level_Calibrate();}void App_Water_Level_Calibrate(void)
{//程序第一次启动的时候 肯定要校验// Inf_W25Q32_Sector_Erase(0);//程序第二次启动的时候 根据FLUSH中是否存在数据uint8_t tmp = 0;Inf_W25Q32_ReadData(0,&tmp,1);if (tmp > 0 && tmp < 255){//说明已经校验过了 不需要再次校验//将AB的值从FLUSH中读取出来 赋值给变量//strtod 将字符串 转化为double //strtok 使用分隔符拆分字符串 第一次调用取前面的,再次调用取后面的Inf_W25Q32_ReadData(1,ABbuff,tmp);a = strtod(strtok((char *)ABbuff,"#"),NULL);b = strtod(strtok(NULL,"#"),NULL);return;}//校验水位传感器,计算 有= a * x + b 中的两个常量值
//(1)第一次测量 水位为0的电压值 y1 b = y1
//(2)第二次测量 水位为10的电压值 y2 y2 = a * 10 - b
//根据两次测量结果,能够算出 b = y1 a = (y2 - y1) / 10App_Display_String(0,110,"Start Calibrate");Delay_s(2);App_Display_ClearString();App_Display_String(0,110,"1.Please Don't put into water, then press the key1....");while (Driver_GPIO_isKeyPressed() == 0);//第一次测量校验uint32_t y1 = Inf_TM7711_ReadV();App_Display_ClearString();App_Display_String(0,110,"2.Please put into water 10cm, then press the key1....");while (Driver_GPIO_isKeyPressed() == 0);//第二次测量校验uint32_t y2 = Inf_TM7711_ReadV();b = y1;a = (y2 - y1) / 10;App_Display_ClearString();App_Display_String(0,110,"Calibrate is done!");//将校验得到的ab值永久存储sprintf((char *)ABbuff,"%.2f#%.2f",a,b);ab_len[0] = strlen((char *)ABbuff);//写入到W25Q32// 在地址0的位置写入一个字节 => ab拼接的长度//在位置1的位置开始写入 => ABbuff 写的就是ab_lenInf_W25Q32_Sector_Erase(0);Inf_W25Q32_PageProgram(0,ab_len,1);Inf_W25Q32_PageProgram(1,ABbuff,ab_len[0]);Delay_s(2);}/* 直接读取水位测量结果*/
double App_Read_Water_Level(void)
{//y = a * x + buint32_t y = Inf_TM7711_ReadV();return (y - b)/a;}

水质检测模块

Driver
  • Driver_I2C.c
#include "Driver_I2C.h"void Driver_I2C_Init(void)
{/*1. 打开时钟 PB I2C2*/RCC->APB2ENR |= RCC_APB2ENR_IOPBEN;RCC->APB1ENR |= RCC_APB1ENR_I2C2EN;/*2. 配置引脚模式 PB10 PB11*/GPIOB->CRH |= (GPIO_CRH_MODE10 | GPIO_CRH_CNF10);GPIOB->CRH |= (GPIO_CRH_MODE11 | GPIO_CRH_CNF11);/*3. 配置I2C*///3.1 选择使用I2C模式I2C2->CR1 &= ~I2C_CR1_SMBUS;//3.2 选择i2c时钟I2C2->CR2 &= ~I2C_CR2_FREQ;I2C2->CR2 |= 36;//3.3 选择i2c标准模式I2C2->CCR &= ~I2C_CCR_FS;//3.4 标准模式分频系数 1 / 36us 8 ccr = 5usI2C2->CCR |= 180;//3.5 最大上升时间I2C2->TRISE |= 37;//使能I2C2->CR1 |= I2C_CR1_PE;}void Driver_I2C_Start(void)
{//置位启动信号 等待发送成功I2C2->CR1 |= I2C_CR1_START;while ((I2C2->SR1 & I2C_SR1_SB) == 0);}void Driver_I2C_Stop(void)
{//置位停止信号I2C2->CR1 |= I2C_CR1_STOP;
}void Driver_I2C_ACK(void)
{// 置位ACKI2C2->CR1 |= I2C_CR1_ACK;
}void Driver_I2C_NACK(void)
{// 置位NACKI2C2->CR1 &= ~I2C_CR1_ACK;
}void Driver_I2C_SendADDR(uint8_t addr)
{/*1. 等待发送寄存器为空 (可选操作)*/// while ((I2C2->SR1 & I2C_SR1_TXE) == 0)// ;/*2. 发送地址*/I2C2->DR = addr;/*3. 等待地址发送成功*/while ((I2C2->SR1 & I2C_SR1_ADDR) == 0);/* 4. 清除标志位*/I2C2->SR2;
}void Driver_I2C_SendData(uint8_t data)
{/*1. 等待发送寄存器为空 (必须操作)*/while ((I2C2->SR1 & I2C_SR1_TXE) == 0);/*2. 发送数据*/I2C2->DR = data;/*3. 等待数据发送成功*/while ((I2C2->SR1 & I2C_SR1_BTF) == 0);
}uint8_t Driver_I2C_ReadData(void)
{/*1. 等待接收数据寄存器非空*/while ((I2C2->SR1 & I2C_SR1_RXNE) == 0);/*2. 读取数据*/return I2C2->DR;}
Inf
  • Inf_ADS1115.h ---- (这些宏定义可以直接复制,不用再一个个手敲)
#ifndef __INF_ADS1115_H__
#define __INF_ADS1115_H__#include "Driver_I2C.h"#define ADS1115_ADDR_W (0x48 << 1)
#define ADS1115_ADDR_R ((0x48 << 1) + 1)/* ADS1115的I2C地址 */
#define ADS1115_ADDRESS (0x48)   //  1001 000 (ADDR = GND)
#define ADS1115_ADDRESS_W (0x90) // (0x48 << 1)
#define ADS1115_ADDRESS_R (0x91) // ((0x48 << 1) | 1)#define ADS1115_CONFIG_DEFULT 0x8583/* ADS1115指针寄存器  向低2位写入不同的值表示操作不同的其他寄存器 */
#define ADS1115_REG_POINTER_MASK (0x03)      /* 指针寄存器掩码 */
#define ADS1115_REG_POINTER_CONVERT (0x00)   /* 读取转换寄存器(ADC转换后的值) */
#define ADS1115_REG_POINTER_CONFIG (0x01)    /* 读写配置寄存器  */
#define ADS1115_REG_POINTER_LOWTHRESH (0x02) /* Lo_thresh 寄存器 */
#define ADS1115_REG_POINTER_HITHRESH (0x03)  /* Hi_thresh  寄存器 *//* 配置寄存器 */
#define ADS1115_REG_CONFIG_OS_MASK (0x8000)
#define ADS1115_REG_CONFIG_OS_SINGLE (0x8000)  // Write: Set to start a single-conversion
#define ADS1115_REG_CONFIG_OS_BUSY (0x0000)    // Read: Bit = 0 when conversion is in progress
#define ADS1115_REG_CONFIG_OS_NOTBUSY (0x8000) // Read: Bit = 1 when device is not performing a conversion#define ADS1115_REG_CONFIG_MUX_MASK (0x7000)
#define ADS1115_REG_CONFIG_MUX_DIFF_0_1 (0x0000) // Differential P = AIN0, N = AIN1 (default)
#define ADS1115_REG_CONFIG_MUX_DIFF_0_3 (0x1000) // Differential P = AIN0, N = AIN3
#define ADS1115_REG_CONFIG_MUX_DIFF_1_3 (0x2000) // Differential P = AIN1, N = AIN3
#define ADS1115_REG_CONFIG_MUX_DIFF_2_3 (0x3000) // Differential P = AIN2, N = AIN3
#define ADS1115_REG_CONFIG_MUX_SINGLE_0 (0x4000) // Single-ended AIN0
#define ADS1115_REG_CONFIG_MUX_SINGLE_1 (0x5000) // Single-ended AIN1
#define ADS1115_REG_CONFIG_MUX_SINGLE_2 (0x6000) // Single-ended AIN2
#define ADS1115_REG_CONFIG_MUX_SINGLE_3 (0x7000) // Single-ended AIN3#define ADS1115_REG_CONFIG_PGA_MASK (0x0E00)
#define ADS1115_REG_CONFIG_PGA_6_144V (0x0000) // +/-6.144V range = Gain 2/3
#define ADS1115_REG_CONFIG_PGA_4_096V (0x0200) // +/-4.096V range = Gain 1
#define ADS1115_REG_CONFIG_PGA_2_048V (0x0400) // +/-2.048V range = Gain 2 (default)
#define ADS1115_REG_CONFIG_PGA_1_024V (0x0600) // +/-1.024V range = Gain 4
#define ADS1115_REG_CONFIG_PGA_0_512V (0x0800) // +/-0.512V range = Gain 8
#define ADS1115_REG_CONFIG_PGA_0_256V (0x0A00) // +/-0.256V range = Gain 16#define ADS1115_REG_CONFIG_MODE_MASK (0x0100)
#define ADS1115_REG_CONFIG_MODE_CONTIN (0x0000) // Continuous conversion mode
#define ADS1115_REG_CONFIG_MODE_SINGLE (0x0100) // Power-down single-shot mode (default)#define ADS1115_REG_CONFIG_DR_MASK (0x00E0)
#define ADS1115_REG_CONFIG_DR_128SPS (0x0000)  // 128 samples per second
#define ADS1115_REG_CONFIG_DR_250SPS (0x0020)  // 250 samples per second
#define ADS1115_REG_CONFIG_DR_490SPS (0x0040)  // 490 samples per second
#define ADS1115_REG_CONFIG_DR_920SPS (0x0060)  // 920 samples per second
#define ADS1115_REG_CONFIG_DR_1600SPS (0x0080) // 1600 samples per second (default)
#define ADS1115_REG_CONFIG_DR_2400SPS (0x00A0) // 2400 samples per second
#define ADS1115_REG_CONFIG_DR_3300SPS (0x00C0) // 3300 samples per second#define ADS1115_REG_CONFIG_CMODE_MASK (0x0010)
#define ADS1115_REG_CONFIG_CMODE_TRAD (0x0000)   // Traditional comparator with hysteresis (default)
#define ADS1115_REG_CONFIG_CMODE_WINDOW (0x0010) // Window comparator#define ADS1115_REG_CONFIG_CPOL_MASK (0x0008)
#define ADS1115_REG_CONFIG_CPOL_ACTVLOW (0x0000) // ALERT/RDY pin is low when active (default)
#define ADS1115_REG_CONFIG_CPOL_ACTVHI (0x0008)  // ALERT/RDY pin is high when active#define ADS1115_REG_CONFIG_CLAT_MASK (0x0004)   // Determines if ALERT/RDY pin latches once asserted
#define ADS1115_REG_CONFIG_CLAT_NONLAT (0x0000) // Non-latching comparator (default)
#define ADS1115_REG_CONFIG_CLAT_LATCH (0x0004)  // Latching comparator#define ADS1115_REG_CONFIG_CQUE_MASK (0x0003)
#define ADS1115_REG_CONFIG_CQUE_1CONV (0x0000) // Assert ALERT/RDY after one conversions
#define ADS1115_REG_CONFIG_CQUE_2CONV (0x0001) // Assert ALERT/RDY after two conversions
#define ADS1115_REG_CONFIG_CQUE_4CONV (0x0002) // Assert ALERT/RDY after four conversions
#define ADS1115_REG_CONFIG_CQUE_NONE (0x0003)  // Disable the comparator and put ALERT/RDY in high state (default)/*初始化*/
void Inf_ADS1115_Init(void);/*读取电压*/
double Inf_ADS1115_ReadV(void);#endif /* __INF_ADS1115_H__ */
  • Inf_ADS1115.c
#include "Inf_ADS1115.h"/*初始化*/
void Inf_ADS1115_Init(void)
{/*1.初始化底层驱动I2C*/Driver_I2C_Init();/*2. 配置ADS1115寄存器*///使用I2C驱动配置 需要 设备地址 和 字节地址 指针寄存器 -> 01 配置寄存器//I2C写入数据流程Driver_I2C_Start();Driver_I2C_SendADDR(ADS1115_ADDR_W);Driver_I2C_SendData(ADS1115_REG_POINTER_CONFIG);//写入数据一次一个字节uint16_t tmp = ADS1115_CONFIG_DEFULT;//选择通道0,设置 A0 为模拟输入信号tmp &= ~ ADS1115_REG_CONFIG_MUX_MASK;tmp |= ADS1115_REG_CONFIG_MUX_SINGLE_0;//4. 选择 4.096vtmp &= ~ADS1115_REG_CONFIG_PGA_MASK;tmp |= ADS1115_REG_CONFIG_PGA_4_096V;//选择连续转换tmp &= ~ADS1115_REG_CONFIG_MODE_MASK;tmp |= ADS1115_REG_CONFIG_MODE_CONTIN;Driver_I2C_SendData((tmp >> 8) & 0xff);Driver_I2C_SendData((tmp >> 0) & 0xff);Driver_I2C_Stop();
}/*读取电压*/
double Inf_ADS1115_ReadV(void)
{//使用I2C的流程读取电压Driver_I2C_Start();//发送设备地址Driver_I2C_SendADDR(ADS1115_ADDR_W);//发送字节地址 -> 指针寄存器Driver_I2C_SendData(ADS1115_REG_POINTER_CONVERT);//假写真读Driver_I2C_Stop();Driver_I2C_Start();//发送设备地址Driver_I2C_SendADDR(ADS1115_ADDR_R);uint16_t tmp = 0;Driver_I2C_ACK();tmp |= Driver_I2C_ReadData() << 8;tmp |= Driver_I2C_ReadData();Driver_I2C_Stop();//将二进制的值转换为测量电压return (tmp * 4.096) / 32767;
}
App
  • App_tds.c
#include "App_tds.h"void App_TDS_Start(void)
{Inf_ADS1115_Init();
}double App_TDS_ReadTds(void)
{double v = Inf_ADS1115_ReadV();double v2 = v * v;return 66.71 * v2 - 127.93 * v2 + 428.7 * v;
}

4 成果展示

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/466149.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

从神经元到神经网络:深度学习的进化之旅

神经元、神经网络 神经元 Neuron )&#xff0c;又名感知机( Perceptron )&#xff0c;在模型结构上与 逻辑回归 一致&#xff0c;这里以一个二维输入量的例子对其进行进一步 的解释&#xff1a; 假设模型的输 入向 量是一 维特征向 (x1,x2). 则单神 经元的模型结构 如下…

银行信贷风控专题:Python、R 语言机器学习数据挖掘应用实例合集:xgboost、决策树、随机森林、贝叶斯等

银行信贷风控专题&#xff1a;Python、R 语言机器学习数据挖掘应用实例合集&#xff1a;xgboost、决策树、随机森林、贝叶斯等 原创 拓端研究室 全文链接&#xff1a;https://tecdat.cn/?p38026 在当今金融领域&#xff0c;风险管控至关重要。无论是汽车贷款违约预测、银行挖掘…

某华迪加现场大屏互动系统mobile.do.php任意文件上传

免责声明 本文章仅供学习与交流&#xff0c;请勿用于非法用途&#xff0c;均由使用者本人负责&#xff0c;文章作者不为此承担任何责任 漏洞描述 该系统是实现现场大屏互动&#xff0c;里面功能众多&#xff0c;但在mobile.do.php接口处存在任意文件上传漏洞 搜索语法 fof…

基于Python的学生宿舍管理系统

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、Vue项目源码、SSM项目源码、微信小程序源码 精品专栏&#xff1a;…

【AI视频换脸整合包及教程】AI换脸新星:Rope——让换脸变得如此简单

在数字技术迅猛发展的今天&#xff0c;人工智能&#xff08;AI&#xff09;的应用已经渗透到了我们生活的方方面面&#xff0c;从日常的语音助手到复杂的图像处理&#xff0c;无不体现着AI技术的魅力。特别是在娱乐和创意领域&#xff0c;AI技术更是展现出了惊人的潜力。其中&a…

A012-基于Spring Boot的私房菜定制上门服务系统的设计与实现

摘 要 如今社会上各行各业&#xff0c;都喜欢用自己行业的专属软件工作&#xff0c;互联网发展到这个时候&#xff0c;人们已经发现离不开了互联网。新技术的产生&#xff0c;往往能解决一些老技术的弊端问题。因为传统私房菜定制上门服务系统信息管理难度大&#xff0c;容错率…

EV录屏好用吗?盘点2024年10款专业好用的录屏软件。

EV录屏的方式有很多种&#xff0c;它设置了很多模式&#xff0c;并且录制高清&#xff0c;可以免费使用。但是现在很多的录屏工具都有着与这个软件相似的功能&#xff0c;在这里我可以给大家列举一些。 1、福昕电脑录屏 这个软件为用户提供了多种录制模式&#xff0c;让视频录…

【网易云插件】听首歌放松放松

先看效果&#xff1a; 网易云有两种类似的插件。 第一种 &#xff1a; iframe 优点&#xff1a;可以自己调整插件的高度、宽度 缺点&#xff1a;很多博客网站不支持嵌入iframe&#xff0c;请试一下您的网站是否支持 登录可直接复制代码。 也可以在我这里 <iframe fram…

Java的Object类常用的方法(详述版本)

文章目录 一、什么是Object类二、常用方法&#xff1a;toString&#xff08;&#xff09;三、常用方法&#xff1a;对象比较equals&#xff08;&#xff09;四、常用方法&#xff1a;hashcode&#xff08;&#xff09;五、总结 一、什么是Object类 顾名思义&#xff0c;Object类…

SQL--查询连续三天登录数据详解

问题&#xff1a; 现有用户登录记录表&#xff0c;请查询出用户连续三天登录的所有数据记录 id dt1 2024-04-25 1 2024-04-26 1 2024-04-27 1 2024-04-28 1 2024-04-30 1 2024-05-01 1 2024-05-02 1 2024-05-04 1 2024-05-05 2 20…

docker+mysql配置

拉取mysql docker pull mysqlmysql配置 创建存储文件夹 mkdir -p /home/mysql/{date,conf}在conf文件中配置my.cnf sudo vim my.cnfmy.cnf具体配置 [mysqld] #Mysql服务的唯一编号 每个mysql服务Id需唯一 server-id1#服务端口号 默认3306 port3306#mysql安装根目录&#x…

qt QHeaderView详解

1、概述 QHeaderView 是 Qt 框架中的一个类&#xff0c;它通常作为 QTableView、QTreeView 等视图类的一部分&#xff0c;用于显示和管理列的标题&#xff08;对于水平头&#xff09;或行的标题&#xff08;对于垂直头&#xff09;。QHeaderView 提供了对这些标题的排序、筛选…

删除 需要来自XXXX的权限才能对此文件夹进行更改 文件的解决办法

如果你也是&#xff1a; 如果你也有类似上面的问题&#xff0c;这篇文章算是你看对了&#xff0c;哦哟&#xff01; 我的牙齿现在是怨灵的牙齿&#xff0c;可以啃下一头牛。 翻遍千山万水&#xff0c;咱们也是终于取到真经了家人们。 首先下一个everything好吗 甩一个官网链…

题目练习之二叉树那些事儿(续集)

♥♥♥~~~~~~欢迎光临知星小度博客空间~~~~~~♥♥♥ ♥♥♥零星地变得优秀~也能拼凑出星河~♥♥♥ ♥♥♥我们一起努力成为更好的自己~♥♥♥ ♥♥♥如果这一篇博客对你有帮助~别忘了点赞分享哦~♥♥♥ ♥♥♥如果有什么问题可以评论区留言或者私信我哦~♥♥♥ 这一篇博客我们继…

本地Docker部署ZFile网盘打造个人云存储,告别公共网盘让你数据安全感爆棚

文章目录 前言1.关于ZFile2.本地部署ZFile3.ZFile本地访问测试4.ZFile的配置5.cpolar内网穿透工具安装6.创建远程连接公网地址7.固定ZFile公网地址 前言 本文主要介绍如何在Linux Ubuntu系统使用Docker本地部署ZFile文件管理系统&#xff0c;并结合cpolar内网穿透工具实现远程…

职场逆袭!学会管理上司,你也能成为职场赢家

书友们&#xff0c;不要错过了&#xff01;我挖到了一本真正让我彻夜难眠的小说&#xff0c;情节跌宕起伏&#xff0c;角色鲜活得就像从书里跳出来陪你聊天。每一页都是新的惊喜&#xff0c;绝对让你欲罢不能。要是你也在寻找那种让人上瘾的阅读体验&#xff0c;这本书就是你的…

LangChain Ollama实战文献检索助手(三)思维链COT、思维树TOT和思维网NOT

大模型的思考方式有时候并不尽人意。我们可以在提示词中引导大模型如何拆分任务&#xff0c;按部就班地思考。 一、思维链 思维链是引导模型一步一步地思考&#xff0c;分为Zero-Shot CoT和Few-Shot CoT。Zero-Shot CoT就是著名的Let’s think step by step。Few-Shot CoT是对…

ASP页面改为UTF-8编码后,刷新页面不定时中文输出乱码终极解决方案

IIS7下的ASP页面&#xff0c;改为Utf-8编码后&#xff0c;Html部分的中文显示正常&#xff0c;但是由 Response.Write 输出的中文字符&#xff0c;在不特定的时间会变成乱码&#xff0c;一开始以为是浏览器问题&#xff0c;测试了多个浏览器故障依旧不定时出现&#xff1a; &l…

Spring底层源码(一)

Spring的入门代码&#xff1a; public class XmlTest {public static void main(String[] args) {//构造一个容器.ClassPathXmlApplicationContext context new ClassPathXmlApplicationContext("springTest.xml");//从容器中获取Bean对象UserService userService …

理解Web登录机制:会话管理与跟踪技术解析(二)-JWT令牌

JWT令牌是一种用于安全地在各方之间传递信息的开放标准&#xff0c;它不仅能够验证用户的身份&#xff0c;还可以安全地传递有用的信息。由于其结构简单且基于JSON&#xff0c;JWT可以在不同的系统、平台和语言间无缝传递&#xff0c;成为现代Web开发中不可或缺的一部分。 文章…