代码链接:https://pan.baidu.com/s/15nGPyVFHL1WLsjqCmg_pZg?pwd=jyhh
提取码:jyhh
1.项目内容
本项目实现了水质和水位的测量,并将其显示在LCD屏上。
2.实验思路
本实验通过测量水中TDS的数量反应水质,整体思路是:首先水位传感器把数据传给单片机,计算出水位和压力的线性关系后,通过SPI协议将数据存储到FLASH中最终根据公式得出的结果在LCD屏上显示;其次水质的测量:通过TDS采集模块将数据发送给外部ADC,转换为数字信号后经过IIC协议将数据传给单片机,再根据官方给的计算的公式计算出TDS,最终在LCD上显示。
将实验分为三个层次:硬件驱动层:串口驱动、FMSC驱动、IIC驱动、SPI驱动、GPIO驱动、硬件接口层:LCD、ADS1115(TDS采集模块)、水位采集模块、W25Q32(FLASH芯片)应用层:水位数据获取展示、水质数据获取展示。
3.实验遇到的问题
1.检测不到数据,一直在循环中,出不来;原因:引脚冲突了;解决办法:换合适的引脚。
2.读到的电压值为0,原因:(1)读取的时序配置错了(2)配置时ADDR地址配错了
4.实验过程(寄存器开发)
4.1硬件驱动层
这些驱动层都是拷贝的以前的代码,就不详述了,如有需要可下载代码查看,哈哈哈哈哈~
4.2硬件接口层
4.2.1TM711(水位采集模块数据)
因为水位采集模块由水位传感器和ADC组成,所以会自动完成水位的采集和信号的转换,所以只需要完成硬件连接,根据TM711模数转换器读时序图来编写代码即可完成水位数据的收集,读时序图如下4-1所示:
图4-1 TM711数据输出时序图
图4-2 官方对时序图的补充说明
时序图详解:拉低时钟,等待DOUT变低(变低代表数据准备好了),开始读取数值:拉高时钟后要延时(因为要保证PD_SCK脉冲上升沿到DOUT数据有效以及其为正脉冲电平时间合理)再拉低时钟,在低时钟处读取输出的值,如此往复24次(因为TM711是24位的转换器芯片);如上图可知我们选择的是第一条PD_SCK线,所以要多读一次,总共读25次,代表我们的下一次转换仍是差分输入,增益128,10HZ,至此读取ADC的数值结束。
最后将数据按官方的模式进行处理,给数据异或0x800000,具体代码实现如下:
uint32_t Inf_HX710_ReadValue(void)
{
uint32_t data = 0x0;
SCK_L;
//等待out变低,表示数据准备好了
while(OUT_READ)
{
;
}
for(uint8_t i = 0; i < 24; i++)
{
SCK_H;
Delay_us(5);
data <<= 1;
SCK_L;
Delay_us(5);
if(OUT_READ)
{
data |= 1;
}
}
SCK_H;
Delay_us(5);
SCK_L;
return data ^ 0x800000;
}
水位采集模块硬件图如下4-3所示:
图4-3 水位检测硬件图
4.2.2ADS1115(TDS采集模块数据)
TDS采集的原理就是当水中的导电粒子多时,导电性好,采集到的电压高;导电粒子少时导电性差,采集到的电压低。可以简单的认为水中杂质多时,导电粒子多,杂质少时导电粒子少。所以可以通过采集的电压高低来计算TDS的值从而反映水质。ADS1115是将TDS传感器获取到的数据转换成电压传给单片机。采集水质数据的总体思路:首先配置ADS1115,根据时序图获取ADS1115的数据,经过处理后即可获得电压值。
本次采集过程需要用到ADS1115的三个寄存器:Pointer Register Byte、Conversion Register、Config Register,通过Pointer Register Byte来选择用转换寄存器还是配置寄存器,转换寄存器用来读取数据,配置寄存器需要做的操作有配置模拟信号通道、增益、模式(连续转换),配置完成后将配置的结果按下面的写时序图写入配置寄存器,
图4-4 ADS1115写时序图
写时序图详解:首先发送地址,再发送指针寄存器的值选择对哪一个寄存器进行操作,接下来写入数据,先写高8位再写低8位,最后停止信号即可完成写操作。
图4-5 ADS1115读时序图
读时序详解:首先仍是发送地址和发送指针寄存器的值选择对哪一个寄存器进行操作,停止信号;重新开始信号,再发送地址,开始接收数据,先接高8位,等待应答后再接低8位最后停止信号即可完成读操作。
根据上述的读写时序步骤写入配置寄存器的值,以及获取电压值即可,具体代码实现如下:
void Inf_ADS1115_Config(void)
{
uint16_t conf = 0x8583;
//1.配置模拟信号通道为通道0
conf &= ~ADS1115_REG_CONFIG_MUX_MASK;
conf |= ADS1115_REG_CONFIG_MUX_SINGLE_0;
//2.配置增益
conf &= ~ADS1115_REG_CONFIG_PGA_MASK;
conf |= ADS1115_REG_CONFIG_PGA_4_096V;
//3.配置模式 连续转换
conf &= ~ADS1115_REG_CONFIG_MODE_MASK;
conf |= ADS1115_REG_CONFIG_MODE_CONTIN;
//配置写出
Driver_IIC2_Start();
Driver_IIC2_SendAddr(ADS1115_ADDRESS_W);
Driver_IIC2_SendByte(ADS1115_REG_POINTER_CONFIG);
Driver_IIC2_SendByte(conf >> 8);
Driver_IIC2_SendByte(conf & 0xff);
Driver_IIC2_Stop();
}
/**
* 创建时间:2024/08/29 22:12:53
* @description:读取具体的电压值
* @return{}
* @author:zlh
*/
double Inf_ADS1115_ReadV(void)
{
Driver_IIC2_Start();
Driver_IIC2_SendAddr(ADS1115_ADDRESS_W);
Driver_IIC2_SendByte(ADS1115_REG_POINTER_CONVERT);
Driver_IIC2_Stop();
Driver_IIC2_Start();
Driver_IIC2_SendAddr(ADS1115_ADDRESS_R);
uint16_t tmp = 0;
tmp |= (Driver_IIC2_ReadByte() << 8);
Driver_IIC2_Ack();
tmp |= Driver_IIC2_ReadByte();
Driver_IIC2_Ack();
Driver_IIC2_Stop();
return tmp * 4.096 / 32767;
}
水质检测硬件图如下4-6所示:
图4-6 水质检测硬件图
4.3应用层
4.3.1获取水位值
我们在硬件接口层已经获取到ADC传的数据(电压值),接下来我们要对该值进行处理算出具体水位,也就是算出电压值和水位之间的线性关系y=ax+b(y是电压值 x是水位)。思路是获取x=0时的y,再获取x=10时的y,两式联立求出a b,将a b的值通过SPI协议存入FLASH中,得出线性关系后即可测得水位,具体实现如下所示:
uint32_t y1, y2;
double a, b;
uint8_t buff[50] = {0}; //存储到写入flash中的a b值
uint8_t len; //写入到flash的长度:字节数
void APP_WaterLevel_Calibrate(void)
{
Inf_W25Q32_EraseSector(0, 0);
//1.先从flash中读取a和b的值
Inf_W25Q32_Read(0, 0, 0, &len, 1);
if(len > 0 && len < 255) //表示曾经校准过
{
Inf_W25Q32_Read(0, 0, 1, buff, len);
a = strtod(strtok((char*)buff,"#"), NULL);
b = strtod(strtok(NULL,"#"), NULL);
debug_printfln("a=%.2f b=%.2f", a, b);
return;
}
//y=ax+b x表示水位 y表示读到的电压值
//x=0 y1=b
//x=10cm y2=10a+b
//a=(y2-y1)/10
//b=y1
Inf_LCD_WriteAsciiString(64,290,24,"Start Calibrate",BLUE,GREEN);
Inf_LCD_WriteAsciiString(0,320,24,"Do not put into water,then press key3",BLUE,GREEN);
//第一次按下按键
Driver_GPIO_Key3();
y1 = Inf_HX710_ReadValue();
debug_printfln("%d",y1);
b = y1;
Inf_LCD_WriteAsciiString(0,320,24,"Please put into water 10cm,then press key3",BLUE,GREEN);
//第二次按下按键
Driver_GPIO_Key3();
y2 = Inf_HX710_ReadValue();
a = (y2 - y1) / 10.0;
debug_printfln("a=%.2f b=%.2f", a, b);
//2.把a b的值存储到Flash
sprintf((char*)buff, "%.2f#%.2f", a, b);
len = strlen((char*)buff);
debug_printfln("%s len=%d", buff, len);
Inf_W25Q32_EraseSector(0, 0);
//2.1先把数据的长度写入
Inf_W25Q32_WritePage(0, 0, 0, &len, 1);
//2.2把a b的值写入
Inf_W25Q32_WritePage(0, 0, 1, buff, len);
}
double APP_WaterLevel_Read(void)
{
//y=ax+b
uint32_t y = Inf_HX710_ReadValue();
return (y - b) / a;
}
4.3.2获取水质值
根据硬件接口层获取到的TDS对应的电压值以及官方提供的电压与水质之间的关系式:计算出水质值,具体实现如下所示:
double APP_TDS_CalculateTDS(void)
{
//y=66.71x^3-127.93x^2+428.7x
double v = Inf_ADS1115_ReadV();
double vv = v * v;
double vvv = vv * v;
return (66.71 * vvv - 127.93 * vv + 428.7 * v);
}
4.3.3LCD显示水位水质值
根据获取到的水位水质值,在屏幕上指定位置显示即可,具体代码实现如下:
uint8_t d_buff[50];
void APP_Display_ShowWaterLevel(double wl)
{
memset((char*)d_buff,0,sizeof(d_buff));
sprintf((char*)d_buff,"water levle=%.2f ",wl);
Inf_LCD_WriteAsciiString(10,370,24,d_buff,BLUE,GREEN);
}
void APP_Display_ShowTDS(double tds)
{
memset((char*)d_buff,0,sizeof(d_buff));
sprintf((char*)d_buff,"tds=%.2f ",tds);
Inf_LCD_WriteAsciiString(10,400,24,d_buff,BLUE,GREEN);
}
5.实验总结
通过本次项目我更加熟悉如何看时序图和数据手册,更加清晰分层次编写代码数理逻辑的好处,针对本次实验遇到的问题,我也谨记看原理图的重要性,如上述有误和待优化的地方欢迎指正~~~~
注:本项目我是跟着b站尚硅谷老师做的