当前位置: 首页 > news >正文

基于32单片机的RS485综合土壤传感器检测土壤PH、氮磷钾的使用(超详细)

1-3为RS485综合土壤传感器的基本内容

4-5为基于STM32F103C8T6单片机使用RS485传感器检测土壤PH、氮磷钾并显示在OLED显示屏的相关配置内容

注意:本篇文件讲解使用的是PH、氮磷钾四合一RS485综合土壤传感器,但里面的讲解内容适配市面上的所有多合一的RS485综合土壤传感器

一、RS485综合土壤传感器概述

 1.1 产品概述

        本产品性能稳定灵敏度高,响应快,输出稳定,适用于各种土质。是观测和研究盐渍土的发生、演变、改良以及水盐动态的重要工具。通过测量土壤的介电常数,能直接稳定地反映各种土壤的真实水分含量。可测量土壤水分的体积百分比,是符合目前国际标准的土壤水分测量方法。可长期埋入土壤中,耐长期电解,耐腐蚀,抽真空灌封,完全防水。

        适用于土壤墒情监测、科学试验、节水灌溉、温室大棚、花卉蔬菜、草地牧场、土壤速测、植物培养、污水处理、精细农业等场合的温湿度、电导率、PH值测试。

1.2 功能特点

■ 门槛低,步骤少,测量快速,无需试剂,不限检测次数。

■ 电极采用特殊处理的合金材料,可承受较强的外力冲击,不易损坏。

■ 完全密封,耐酸碱腐蚀,可埋入土壤或直接投入水中进行长期动态检测。

■ 精度高,响应快,互换性好,探针插入式设计保证测量精确,性能可靠。

■ 也可用于水肥一体溶液、以及其他营养液与基质的电导率。

 二、硬件连接

      综合土壤传感器使用的RS485通信方式,而RS-485 是一种用于长距离通信的串行通信标准,常用于工业和商业应用。它在噪声干扰的环境中提供了良好的信号完整性,支持多点通信。

        在与单片机(如 Arduino、8051、PIC、STM32 等)进行通信时,通常需要将 RS-485 信号转换为 TTL信号。

        将 RS-485 信号转换为 TTL 信号是确保传感器和单片机之间能够顺利通信的关键步骤,这样可以解决电平匹配、通信协议、设计简化和电气保护等问题。通常使用专门的 RS-485 到 TTL 的转换器(例如 MAX485、SN75176 等)来实现这一转换。

                                                   单片机TTL转RS485模块

土壤综合传感器接线讲解

线色

说明

备注

棕色

电源正

4.5~30V DC

黑色

电源地

GND

黄色

485-A

485-A

蓝色

485-B

485-B

 传感器和转换器接线讲解

单片机TTL转RS485模块单片机和土壤综合传感器
VCC单片机的5V(建议)
TXDA2
RXDA3
GNDGND
GNDGND
B485-B
A485-A

三、单片机和RS485综合传感器的通信配置

        综合土壤传感器通过硬件连接后,32单片机和其的通信方式则为串口通信,单片机通过串口发送问询帧给综合土壤传感器,其接收到单片机的发送信息后,会反馈应答帧给单片机,单片机从应答帧接收到对的地址码后,则开始接收对应的信息,后通过转换数据得到我们想要的信息。

        要想知道具体的通信细节,我们还得查看商家给我们对应传感器的文档,里面有我们需要的对应通信协议。

四、通信协议 (重要)

4.1 通讯基本参数

编 码 

8位二进制

数据位 

8位

奇偶校验位

停止位 

1位

错误校验

CRC(冗余循环码)

波特率

2400bit/s、4800bit/s、9600 bit/s可设,出厂默认为4800bit/s

注意: 这里的波特率指的是串口通信中使用的波特率,而不同厂家生产的综合土壤传感器出厂默认的波特率也有可能不相同,有的可能为9600bit/s,具体到哪个需要找商家拿到对应的资料文档从而进行查看,本次使用的传感器出厂默认为4800bit/s。

4.2 数据帧格式定义

采用ModBus-RTU 通讯规约,格式如下:

初始结构 ≥4 字节的时间

地址码 = 1 字节

功能码 = 1 字节

数据区 = N 字节

错误校验 = 16 位CRC 码

结束结构 ≥4 字节的时间

地址码:为变送器的地址,在通讯网络中是唯一的(出厂默认0x01)。

功能码:本产品用到功能码0x03、0x06、0x10等。

数据区:数据区是具体通讯数据,注意16bits数据高字节在前!

CRC码:二字节的校验码。

        其中,主机问询帧结构就是单片机通过串口发送给传感器的信息,而从机应答帧结构就是土壤传感器反馈给单片机的信息

主机问询帧结构:

地址码

功能码

寄存器起始地址

寄存器长度

校验码低位

校验码高位

1字节

1字节

2字节

2字节

1字节

1字节

从机应答帧结构:

地址码

功能码

有效字节数

数据一区

第二数据区

第N数据区

校验码

1字节

1字节

1字节

2字节

2字节

2字节

2字节

4.3 寄存器地址

        不同厂家的土壤综合传感器的寄存器地址也可能会各不相同,无需理会,我们只需要读懂其使用方法就行。

4.4 通讯协议示例以及解释(重要)

以官方的文档为例

官方举例是电导率温度水分PH四合一设备的例子,那是不是所有的多合一的综合土壤综合传感器都一样呢?明显不是的,我们不同的综合土壤传感器所发的问询帧和返回的应答帧是不一样的,那具体该怎么发送,怎么修改呢? 

问询帧:

地址码、功能码:这两个出厂就有个默认值,而且资料文档也会给出,在4、2的截图有说明,而且其中一般都是默认地址码0x01,功能码0x03

起始地址:两个字节,具体字节是你检测的数据最前的第一个地址,例如上面举例的电导率温度水分PH四合一检测的数据地址最前面的则是000H,所有它的起始地址为0x00 0x00,而本次讲解使用的是PH、氮磷钾四合一,那根据官方给的文档查阅,地址最前的则是PH的地址0003H,则起始地址为0x00 0x3H。

数据长度:就是你发送地址数据的长度。检测多少个数据,看需要检测的数据是多少个以及它的地址字节数,但文档的地址的检测数据都是低字节,所以可以简单的总结为,四合一的数据长度为0x00 0x04,七合一的数据长度:0x00 0x07,只检测一个数据的长度为:0x00 0x01

效验码低字节、效验码高字节:这里的二字节的校验码指的是CRC码。那CRC码又是什么呢?CRC码是一种广泛使用的错误检测码,用于检测数据传输或存储过程中发生的错误。它通过在数据中添加冗余信息,使接收方能够验证数据的完整性。

根据前面的发送数据来计算CRC码,那怎样计算CRC码呢?计算CRC码比较复杂,而且难度高,那我们可以直接通过查阅数据来获取我们的CRC码,地址如下:16进制(CRC16)(MODBUS RTU通讯)校验码在线计算器

举例:

文档电导率温度水分PH四合一发送的地址码、功能码、起始地址、数据长度分为01 03 00 00 00 04,那它的CRC码(MSB-LSB)为:0944,也就是说效验码低字节:0x44,效验码高字节:0x09,这就和我上面列举的官方文档一致了。

那我们本次使用的PH、氮磷钾四合一,那其地址码、功能码、起始地址、数据长度分为01 03 00 03 00 04,那它的CRC码(MSB-LSB)为:09B4,也就是说效验码低字节:0xB4,效验码高字节:0x09。

那假如我四合一传感器只想读取其中一个数据呢,比如我只想读取磷的数据,那我的问询帧前面的格式则为,地址码:0x01 功能码:0x03,起始地址:0x00 0x05 ,数据长度:0x00 0x01,效验码低字节0x94,效验码高字节:0x0B。其中起始地址中的0x00 0x05是磷的寄存器地址。

当然你也可以全部读取所有数据,然后根据你数组存储的数据位置来获取单独的数据也是可以的。

应答帧:

以官方给出的电导率温度水分PH四合一例子讲解:

地址码、功能码:和上面问询帧讲解一样,出厂一般默认为0x01 0x03,具体看文档。

返回有效字节数:具体看从机应答帧结构。例如:四合一土壤综合传感器,那其有效字节数为为你检测的数据之和,也就是水分值+温度值+电导率+PH = 8,有效字节数则是0x08

后面的数据区就是根据你的地址数据从低到高排列的,我们用数组接受到返回的数据后,根据其位置分别读取就行

五、代码例子 (PH、氮磷钾四合一

        单片机通过串口发送问询帧给综合土壤传感器,其接收到单片机的发送信息后,会反馈应答帧给单片机,单片机从应答帧接收到对的地址码后,则开始接收对应的信息,后通过转换数据得到我们想要的信息。

 5.1 串口2定时发送问询帧给RS485综合土壤传感器

        通过配置串口2通信,并且波特率选择为4800(根据厂家的默认出厂波特率为准),通过1S定时给综合土壤传感器发送问询帧,等待发送完毕。(注意其中串口使用的串口2,定时器使用的定时器3,同学们可以自由选择自己的配置串口和定时器)

串口2配置和发送一字节函数:

void Usart2_Init(u32 bp)
{NVIC_InitTypeDef NVIC_InitStructure;GPIO_InitTypeDef GPIO_InitStructure;USART_InitTypeDef USART_InitStructure;RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);	// GPIOA时钟RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2,ENABLE); //串口2时钟使能USART_DeInit(USART2);  //复位串口2//USART2_TX   GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;	//复用推挽输出GPIO_Init(GPIOA, &GPIO_InitStructure); //USART2_RX	  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入GPIO_Init(GPIOA, &GPIO_InitStructure);  USART_InitStructure.USART_BaudRate = bp;USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长为8位数据格式USART_InitStructure.USART_StopBits = USART_StopBits_1;//一个停止位USART_InitStructure.USART_Parity = USART_Parity_No;//无奇偶校验位USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//无硬件数据流控制USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;	//收发模式USART_Init(USART2, &USART_InitStructure); //初始化串口2USART_Cmd(USART2, ENABLE);                    //使能串口 //使能接收中断USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);//开启中断   //设置中断优先级NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2 ;//抢占优先级3NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;		//子优先级3NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;			//IRQ通道使能NVIC_Init(&NVIC_InitStructure);	//根据指定的参数初始化VIC寄存器}//串口1发送一字节
void Send_Byte(uint8_t data)
{USART_SendData(USART2,data);while(!USART_GetFlagStatus(USART2,USART_FLAG_TXE));//等待发送数据完毕}

 定时器3配置:

通过定时一字节一字节的发送询问帧,其中PH、氮磷钾四合一的询问帧为

01 03 00 03 00 04 B4 09(为什么是这个,上面内容有解释,同学们可查看4.4内容)

void Tim3_Init(void)
{RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);//打开TIM3时钟//配置TIM3时基单元TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct={0};TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up;//向上计数TIM_TimeBaseInitStruct.TIM_Period = 10000;//重装载值TIM_TimeBaseInitStruct.TIM_Prescaler = 7200 - 1;//预分频数TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStruct);TIM_ITConfig(TIM3,TIM_IT_Update, ENABLE);//使能更新中断//TIM3中优先限配置NVIC_InitTypeDef NVIC_InitStruct;NVIC_InitStruct.NVIC_IRQChannel=TIM3_IRQn;//配置TIM3中断源NVIC_InitStruct.NVIC_IRQChannelCmd=ENABLE;//中断使能NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority=0;//抢断优先级NVIC_InitStruct.NVIC_IRQChannelSubPriority=2;	//子优先级NVIC_Init(&NVIC_InitStruct);//初始化配置NVIC(中断向量控制寄存器)TIM_Cmd(TIM3, ENABLE);//使能定时器
}u16 time[5]={0};
void TIM3_IRQHandler(void)
{if(TIM_GetITStatus(TIM3,TIM_IT_Update) != RESET){TIM_ClearITPendingBit(TIM3,TIM_IT_Update);time[0]++;if(time[0] == 1){time[0] = 0;delay_ms (1);//延时1msSend_Byte( 0x01);Send_Byte( 0x03);Send_Byte( 0x00);Send_Byte( 0x03);Send_Byte( 0x00);Send_Byte( 0x04);Send_Byte( 0xB4);Send_Byte( 0x09);}}
}

5.2 发送完询问帧后,通过串口2中断,接收返回的信息(应答帧)

其中我们根据应答帧第一个地址码为包头,从而接收信息。其中代码的13个字节数据计算方法是根据应答帧结构来的,例如四合一=地址码(1)+功能码(1)+有效字节数(1)+数据区(4x2)+效验码(2) = 13

5.3 完整代码

main.c 

#include "main.h"
extern char USART_data[20];//串口数据存储数组
float ph;
int N;
int P;
int K;
u8 bufss1[20];
u8 bufss2[20];
u8 bufss3[20];
u8 bufss4[20];
int main(void)
{NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//中断组的选择Usart2_Init(4800);//串口2初始化OLED_Init();//OLED初始化Tim3_Init();//定时器初始化
while(1)
{ph=(USART_data[3]*16+USART_data[4])/10;N=(USART_data[5]*16+USART_data[6]);P=(USART_data[7]*16+USART_data[8]);K=(USART_data[9]*16+USART_data[10]);sprintf((char *)bufss1,"PH:%.1f",ph);sprintf((char *)bufss2,"N:%d",N);sprintf((char *)bufss3,"P:%d",P);sprintf((char *)bufss4,"K:%d",K);Oled_ShowAll(0,0,bufss1);//显示中英字符串Oled_ShowAll(2,0,bufss2);//显示中英字符串Oled_ShowAll(4,0,bufss3);//显示中英字符串Oled_ShowAll(6,0,bufss4);//显示中英字符串}}

串口2配置

#include "usart.h"void Usart2_Init(u32 bp)
{NVIC_InitTypeDef NVIC_InitStructure;GPIO_InitTypeDef GPIO_InitStructure;USART_InitTypeDef USART_InitStructure;RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);	// GPIOA时钟RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2,ENABLE); //串口2时钟使能USART_DeInit(USART2);  //复位串口2//USART2_TX   GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;	//复用推挽输出GPIO_Init(GPIOA, &GPIO_InitStructure); //USART2_RX	  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入GPIO_Init(GPIOA, &GPIO_InitStructure);  USART_InitStructure.USART_BaudRate = bp;USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长为8位数据格式USART_InitStructure.USART_StopBits = USART_StopBits_1;//一个停止位USART_InitStructure.USART_Parity = USART_Parity_No;//无奇偶校验位USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//无硬件数据流控制USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;	//收发模式USART_Init(USART2, &USART_InitStructure); //初始化串口2USART_Cmd(USART2, ENABLE);                    //使能串口 //使能接收中断USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);//开启中断   //设置中断优先级NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2 ;//抢占优先级3NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;		//子优先级3NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;			//IRQ通道使能NVIC_Init(&NVIC_InitStructure);	//根据指定的参数初始化VIC寄存器}char USART_flag;char USART_data[20];int i=0;int USART_Ready=0;//数据接收完成标志
//串口中断
void USART2_IRQHandler(void)
{if(USART_GetITStatus(USART2,USART_IT_RXNE))//接收中断标志位{USART_flag = USART_ReceiveData(USART2);if(USART_flag==0x01)//检测包头{USART_Ready=1;}if(USART_Ready==1){USART_data[i]=USART_flag;i++;if(i==12)//捕捉完成18个字节数据{USART_Ready=0;i=0;	}}USART_ClearITPendingBit(USART2,USART_IT_RXNE);//清除中断标志位}
}
//串口2发送一字节
void Send_Byte(uint8_t data)
{USART_SendData(USART2,data);while(!USART_GetFlagStatus(USART2,USART_FLAG_TXE));//等待发送数据完毕}//串口2接收一节字u16 Rece_Byte(void)
{while(!USART_GetFlagStatus(USART2,USART_FLAG_RXNE))//等待接收数据完毕{}return USART_ReceiveData(USART2);
}//回显函数
void Data_Echo(void)
{uint16_t date=0;date=Rece_Byte();Send_Byte(date);
}//函数功能:printf重定向
int fputc(int c, FILE * stream)
{Send_Byte(c);return c;
}

定时器3配置:

#include "time.h"
/***********************
函数名:
函数功能:定时器3初始化
形参:
返回值:
函数说明:************************/
void Tim3_Init(void)
{RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);//打开TIM时钟//配置TIM3时基单元TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct={0};TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up;//向上计数TIM_TimeBaseInitStruct.TIM_Period = 10000;//重装载值TIM_TimeBaseInitStruct.TIM_Prescaler = 7200 - 1;//预分频数TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStruct);TIM_ITConfig(TIM3,TIM_IT_Update, ENABLE);//使能更新中断//TIM3中优先限配置NVIC_InitTypeDef NVIC_InitStruct;NVIC_InitStruct.NVIC_IRQChannel=TIM3_IRQn;//配置TIM3中断源NVIC_InitStruct.NVIC_IRQChannelCmd=ENABLE;//中断使能NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority=0;//抢断优先级NVIC_InitStruct.NVIC_IRQChannelSubPriority=2;	//子优先级NVIC_Init(&NVIC_InitStruct);//初始化配置NVIC(中断向量控制寄存器)TIM_Cmd(TIM3, ENABLE);//使能定时器
}u16 time[5]={0};
void TIM3_IRQHandler(void)
{if(TIM_GetITStatus(TIM3,TIM_IT_Update) != RESET){TIM_ClearITPendingBit(TIM3,TIM_IT_Update);time[0]++;if(time[0] == 1){time[0] = 0;delay_ms (1);//延时1msSend_Byte( 0x01);Send_Byte( 0x03);Send_Byte( 0x00);Send_Byte( 0x03);Send_Byte( 0x00);Send_Byte( 0x04);Send_Byte( 0xB4);Send_Byte( 0x09);}}
}

oled.c

#include "oled.h"
#include "oledfont.h" 
#include "pic.h"extern const unsigned char Aciss_8X16[];//字符库
extern const unsigned char indexs[][3];//寻找汉字位置
const unsigned char GB2312[];//汉字库/*@brief			初始化OLED与单片机的IO接口@param			无@retval			无*/
static void OLED_GPIO_Init(void)
{GPIO_InitTypeDef GPIO_InitStructure;	//定义一个GPIO_InitTypeDef类型的结构体RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB, ENABLE);	//打开GPIOC的外设时钟GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12 | GPIO_Pin_13;	//选择控制的引脚GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;	//设置为通用开漏输出GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;	//设置输出速率为50MHzGPIO_Init(GPIOB,&GPIO_InitStructure);	//调用库函数初始化GPIOAOLED_SCLK_Set();	//设PA5(SCL)为高电平OLED_SDIN_Set();	//设PA6(SDA)为高电平
}/*@brief			模拟IIC起始信号@param			无@retval			无*/
static void OLED_IIC_Start(void)
{OLED_SCLK_Set();	//时钟线置高OLED_SDIN_Set();	//信号线置高delay_us(1);	//延迟1usOLED_SDIN_Clr();	//信号线置低delay_us(1);	//延迟1usOLED_SCLK_Clr();	//时钟线置低delay_us(1);	//延迟1us
}/*@brief			模拟IIC停止信号@param			无@retval			无*/
static void OLED_IIC_Stop(void)
{OLED_SDIN_Clr();	//信号线置低delay_us(1);	//延迟1usOLED_SCLK_Set();	//时钟线置高delay_us(1);	//延迟1usOLED_SDIN_Set();	//信号线置高delay_us(1);	//延迟1us
}/*@brief			模拟IIC读取从机应答信号@param			无@retval			无*/
static unsigned char IIC_Wait_Ack(void)
{unsigned char ack;OLED_SCLK_Clr();	//时钟线置低delay_us(1);	//延迟1usOLED_SDIN_Set();	//信号线置高delay_us(1);	//延迟1usOLED_SCLK_Set();	//时钟线置高delay_us(1);	//延迟1usif(OLED_READ_SDIN())	//读取SDA的电平ack = IIC_NO_ACK;	//如果为1,则从机没有应答elseack = IIC_ACK;		//如果为0,则从机应答OLED_SCLK_Clr();//时钟线置低delay_us(1);	//延迟1usreturn ack;	//返回读取到的应答信息
}static void Write_IIC_Byte(unsigned char IIC_Byte)
{unsigned char i;  //定义变量for(i=0;i<8;i++) //for循环8次{OLED_SCLK_Clr();	//时钟线置低,为传输数据做准备delay_us(1);	//延迟1usif(IIC_Byte & 0x80)	//读取最高位OLED_SDIN_Set();//最高位为1elseOLED_SDIN_Clr();	//最高位为0IIC_Byte <<= 1;  //数据左移1位delay_us(1);	//延迟1usOLED_SCLK_Set(); //时钟线置高,产生上升沿,把数据发送出去delay_us(1);	//延迟1us}OLED_SCLK_Clr();	//时钟线置低delay_us(1);	//延迟1uswhile(IIC_Wait_Ack());	//从机应答
}/*@brief			IIC写入命令@param			IIC_Command:写入的命令@retval			无*/
static void Oled_Send_Cmd(unsigned char IIC_Command)
{OLED_IIC_Start();Write_IIC_Byte(0x78);//写入从机地址,SD0 = 0Write_IIC_Byte(0x00);//写入命令Write_IIC_Byte(IIC_Command);//数据OLED_IIC_Stop();  //发送停止信号
}/*@brief			IIC写入数据@param			IIC_Data:数据@retval			无*/
static void Oled_Send_Data(unsigned char IIC_Data)
{OLED_IIC_Start();Write_IIC_Byte(0x78);	//写入从机地址,SD0 = 0Write_IIC_Byte(0x40);	//写入数据Write_IIC_Byte(IIC_Data);//数据OLED_IIC_Stop();		//发送停止信号
}/*@brief			开显示@param			无@retval			无*/ 
void OLED_Display_On(void)
{Oled_Send_Cmd(0X8D);  //设置OLED电荷泵Oled_Send_Cmd(0X14);  //使能,开Oled_Send_Cmd(0XAF);  //开显示
}/*@brief			关显示@param			无@retval			无*/  
void OLED_Display_Off(void)
{Oled_Send_Cmd(0XAE);  //关显示Oled_Send_Cmd(0X8D);  //设置OLED电荷泵Oled_Send_Cmd(0X10);  //失能,关
}		   			 /*@brief			清屏@param			无@retval			无*/	  
void OLED_Clear(u8 data)  
{  u8 i,j;for(i=0;i<8;i++){Oled_Send_Cmd(0xB0+i);//发送页起始地址Oled_Send_Cmd(0x00);//发送列地址低四位Oled_Send_Cmd(0x10);//发送列地址高四位for(j=0;j<128;j++){Oled_Send_Data(data);}}
}void OLED_Init(void)
{OLED_GPIO_Init();	//GPIO口初始化delay_ms(200);	//延迟,由于单片机上电初始化比OLED快,所以必须加上延迟,等待OLED上复位完成Oled_Send_Cmd(0xAE);	//关闭显示Oled_Send_Cmd(0x00);	//设置低列地址Oled_Send_Cmd(0x10);	//设置高列地址Oled_Send_Cmd(0x40);	//设置起始行地址Oled_Send_Cmd(0xB0);	//设置页地址Oled_Send_Cmd(0x81); 	// 对比度设置,可设置亮度Oled_Send_Cmd(0xFF);	//  265  Oled_Send_Cmd(0xA1);	//设置段(SEG)的起始映射地址;column的127地址是SEG0的地址Oled_Send_Cmd(0xA6);	//正常显示;0xa7逆显示Oled_Send_Cmd(0xA8);	//设置驱动路数(16~64)Oled_Send_Cmd(0x3F);	//64dutyOled_Send_Cmd(0xC8);	//重映射模式,COM[N-1]~COM0扫描Oled_Send_Cmd(0xD3);	//设置显示偏移Oled_Send_Cmd(0x00);	//无偏移Oled_Send_Cmd(0xD5);	//设置震荡器分频Oled_Send_Cmd(0x80);	//使用默认值Oled_Send_Cmd(0xD9);	//设置 Pre-Charge PeriodOled_Send_Cmd(0xF1);	//使用官方推荐值Oled_Send_Cmd(0xDA);	//设置 com pin configuartionOled_Send_Cmd(0x12);	//使用默认值Oled_Send_Cmd(0xDB);	//设置 Vcomh,可调节亮度(默认)Oled_Send_Cmd(0x40);	使用官方推荐值Oled_Send_Cmd(0x8D);	//设置OLED电荷泵Oled_Send_Cmd(0x14);	//开显示Oled_Send_Cmd(0xAF);	//开启OLED面板显示OLED_Clear(0);        //清屏
//	Set_Postion(0,0); 	 //设置数据写入的起始行、列
//	OLED_Clear(0);
}  /***********************
函数名:Set_Postion
函数功能:
形参:
u8 page 页 
u8 col 列
返回值:void
函数说明:
页地址:0xb0
列低位地址:0x00
列高位地址:0x10
************************/
void Set_Postion(u8 page,u8 col)
{Oled_Send_Cmd(0xB0+page);//发送页起始地址Oled_Send_Cmd(0x00+(col&0x0f));//发送列地址低四位Oled_Send_Cmd(0x10+((col&0xf0)>>4));//发送列地址高四位
}  	     	  /***********************
函数名:Oled_ShowPic
函数功能:OLED显示图片
形参:
u8 page 页     
u8 col 列     
u8 height  图片高度
u8 width   图片宽度
u8 *pic    图片数据
返回值:void
64*64
************************/void Oled_ShowPic(u8 page,u8 col,u8 height,u8 width,u8 *pic)
{u8 i,j;for(i=0;i<height/8;i++)//活得页范围{Set_Postion(page+i,col);//定义框架for(j=0;j<width;j++){Oled_Send_Data(pic[j+i*width]);//往框架输入数据,以列方式}}}
//清空某字符
void Oled_clear(u8 page,u8 col,u8 height,u8 width)
{u8 i,j;for(i=0;i<height/8;i++)//活得页范围{Set_Postion(page+i,col);//定义框架for(j=0;j<width;j++){Oled_Send_Data(0x00);//往框架输入数据,以列方式}}}/***********************
函数名:Oled_ShowChar
函数功能:OLED显示字符
形参:
u8 page 页     
u8 col 列     u8 pic    字符数据
返回值:void
8*16
************************/void Oled_ShowChar(u8 page,u8 col,u8 eng)
{u8 i,j,Char;Char=eng-' ';//获取字符所在位置for(i=0;i<2;i++){Set_Postion(page+i,col);for(j=0;j<8;j++){Oled_Send_Data(Aciss_8X16[j+Char*16+i*8]);}}
}
/***********************
函数名:Oled_Showstring
函数功能:OLED显示字符串
形参:
u8 page 页  2
u8 col 列    8 u8 *str    字符串
返回值:void
8*16
************************/
void Oled_Showstring(u8 page,u8 col,u8*str)
{while(*str !='\0'){if(col>=128){col=0;page +=2;}if(page>=8){page=0;}Oled_ShowChar(page,col,*str);str++;//字符递加col +=8;//每显示完一个字符,列+8				
}}/***********************
函数名:Oled_ShowChi
函数功能:OLED显示汉字
形参:
u8 page 页     
u8 col 列     
u8 *chi    显示汉字
返回值:void16*16  = 32 byte
************************/void Oled_ShowChi(u8 page,u8 col,u8*chi)
{u8 i,j,z;for(i=0;i<sizeof(indexs)/sizeof(indexs[0]);i++){if(*chi == indexs[i][0]&&*(chi+1)==indexs[i][1])//比较汉字在第几个位置{break;}}for(j=0;j<2;j++){Set_Postion(page+j,col);for(z=0;z<16;z++){Oled_Send_Data(GB2312[z+i*32+j*16]);}}}/***********************
函数名:Oled_ShowAll
函数功能:OLED显示汉英字符串
形参:
u8 page 页     
u8 col 列     
u8 *str    显示汉字
返回值:void8*16;16*16  = 32 byte
************************/void Oled_ShowAll(u8 page ,u8 col ,u8*str)
{while(*str != '\0')
{if(*str>127){if(col>=120)//放不下字{page +=2;//col=0;}if(page>=8){page=0;}Oled_ShowChi(page,col,str);col +=16;str +=2;}else{if(col>=128)//放不下字{page +=2;//col=0;}if(page>=8){page=0;}Oled_ShowChar(page,col,*str);str++;col +=8;}}}/*@brief			OLED滚屏函数,范围0~1页,水平向左@param			无@retval			无*/	
void OLED_Scroll(void)
{Oled_Send_Cmd(0x2E);	//关闭滚动Oled_Send_Cmd(0x27);	//水平向左滚动Oled_Send_Cmd(0x00);	//虚拟字节Oled_Send_Cmd(0x00);	//起始页 0Oled_Send_Cmd(0x00);	//滚动时间间隔Oled_Send_Cmd(0x01);	//终止页 1Oled_Send_Cmd(0x00);	//虚拟字节Oled_Send_Cmd(0xFF);	//虚拟字节Oled_Send_Cmd(0x2F);	//开启滚动
}

六、结语

到这一步,基本已经配置成功了,是不是很简单,只需要按照程序来做,大家都能顺利成功读取到RS485的综合土壤传感器的数据。需要代码的可以去我的主页获取下载,只需5积分,最后附上代码获取网址:

https://download.csdn.net/download/weixin_52680858/90121983

看到这的同学,如果觉得对你有用,就麻烦大家点点赞和收藏,谢谢!!!!

---------以下内容为打广告-----------

本人承接各种单片机设计,价格实惠,有需要的同学可联系本人QQ1972218606

---------------------------------------------
————————————————

相关文章:

基于32单片机的RS485综合土壤传感器检测土壤PH、氮磷钾的使用(超详细)

1-3为RS485综合土壤传感器的基本内容 4-5为基于STM32F103C8T6单片机使用RS485传感器检测土壤PH、氮磷钾并显示在OLED显示屏的相关配置内容 注意&#xff1a;本篇文件讲解使用的是PH、氮磷钾四合一RS485综合土壤传感器&#xff0c;但里面的讲解内容适配市面上的所有多合一的RS…...

【从零开始入门unity游戏开发之——C#篇11】一个标准 C# 程序介绍、新的值类型——枚举

文章目录 一、一个标准 C# 程序1、文件名&#xff08;Program.cs&#xff09;&#xff1a;2、 using 语句&#xff1a;3、命名空间&#xff08;namespace&#xff09;4、类&#xff08;class&#xff09;&#xff1a;4、入口函数&#xff08;Main 方法&#xff09;5、程序运行流…...

vue 签名校验 md5 uuid

import CryptoJS from crypto-js import uuid from /utils/uuid import { SECRET_KEY } from /utils/config // 签名校验 const nonceStr uuid.uuid() const timestamp new Date().getTime() // const sign CryptoJS.MD5(nonceStr nonceStr &secretKey SECRET_KEY …...

CSS系列(16)-- 架构与模式详解

前端技术探索系列&#xff1a;CSS 架构与模式详解 &#x1f3d7;️ 致读者&#xff1a;探索 CSS 架构的艺术 &#x1f44b; 前端开发者们&#xff0c; 今天我们将深入探讨 CSS 架构与设计模式&#xff0c;学习如何构建可维护的样式系统。 CSS 架构方法论 &#x1f680; OO…...

【go语言】reflect包与类型推断

reflect 包的核心概念 Go 中的反射涉及两个核心概念&#xff1a; Type&#xff1a;表示一个类型的结构体&#xff0c;reflect.Type 是类型的描述。Value&#xff1a;表示一个值的结构体&#xff0c;reflect.Value 是一个具体值的包装。 反射让我们能够动态地访问对象的类型和…...

3.python运算符

Python 提供了多种运算符&#xff0c;用于执行算术、比较、逻辑等各种操作。以下是 Python 中常见的运算符类型及其用法&#xff1a; 文章目录 1. 算术运算符2. 比较运算符3. 逻辑运算符4. 赋值运算符5. 位运算符6. 成员运算符7. 身份运算符8. 运算符优先级 1. 算术运算符 算…...

【竞技宝】CS2-上海major:spirit力克MOUZ niko梦碎

北京时间2024年12月15日,CS2上海major正在如火如荼的进行中,昨日迎来两场半决赛MOUZ对阵spirit以及FAZE对阵G2。Spirit和MOUZ和各自赢下了自己的选图之后,spirit双子星在图三抗住压力帮助队伍杀入决赛。而G2和FAZE的比赛中,FAZE依然延续上一场的火热手感完全压制了G2,G2的明星选…...

【Leetcode 每日一题】3266. K 次乘运算后的最终数组 II

问题背景 给你一个整数数组 n u m s nums nums&#xff0c;一个整数 k k k 和一个整数 m u l t i p l i e r multiplier multiplier。 你需要对 n u m s nums nums 执行 k k k 次操作&#xff0c;每次操作中&#xff1a; 找到 n u m s nums nums 中的 最小 值 x x x&a…...

etcd集群常见日志

1、节点失去领导者 {"level":"info","ts":"2024-05-07T01:54:04.948Z","logger":"raft","caller":"etcdserver/zap_raft.go:77","msg":"raft.node: 9afce9447872453 lost le…...

【漫话机器学习系列】005.神经网络的结构(architecture on the neural network)

神经网络&#xff08;Neural Network&#xff09;是一种模拟人脑神经系统的计算模型&#xff0c;由大量相互连接的神经元&#xff08;节点&#xff09;组成&#xff0c;广泛应用于深度学习和机器学习领域。以下是神经网络的基本结构及关键组成部分。 1. 神经网络的基本组成 一…...

基于 Couchbase 数据仓库元数据管理的可行性方案

在大数据体系中&#xff0c;元数据管理是数据治理的关键一环。以下是一套元数据管理的可行性方案&#xff0c;适合你的当前架构设计&#xff08;基于 Couchbase 数据仓库&#xff09;并支持高效管理数据的分层与结构。 1. 元数据管理的目标 统一数据管理&#xff1a;清晰描述 …...

SpringBoot:快速构建微服务应用

一、SpringBoot简介 什么是SpringBoot 是由Pivotal团队提供的快速开发框架。它基于Spring框架&#xff0c;可以用于快速构建微服务应用程序。SpringBoot提供了一种快速、便捷的方式来启动和配置一个基于Spring的应用程序&#xff0c;它封装了很多常用的配置&#xff0c;简化了开…...

汽车嵌入式软件构建高效技术团队的全面思考

在汽车嵌入式软件开发领域&#xff0c;构建一支高效的通用技术团队至关重要。这类团队负责为各种项目提供可复用、标准化的技术基石&#xff0c;从而提高开发效率、降低成本并确保产品质量。构建这样的团队需要从技术能力、角色分工、标准化与复用、流程管理与质量保证、工具和…...

【跨库查询、多库查询】.NET开源 ORM 框架 SqlSugar 系列

文章目录 一、跨库方式1&#xff1a;跨库导航二、手动跨库查询三、同服务器&#xff1a;自动查询跨库查询3.1 Mysql和SqlServer自动3.2 自动: PgSql跨Scheme查询3.3 其他库同服务器 四、跨服务器&#xff1a;自动跨库查询4.1 配置SqlServer dblink4.2 配置 Oracle dblink4.3 配…...

智能人体安全防护:3D 视觉技术原理、系统架构与代码实现剖析

随着工业化程度的提高&#xff0c;生产安全已成为企业关注的重点。尤其是在一些存在禁区的工业厂区和车间&#xff0c;人员误入或违规进入将带来严重的安全隐患。为了解决这一问题&#xff0c;迈尔微视推出了智能人体安全检测解决方案&#xff0c;为企业提供全方位的人员安全监…...

第24周:文献阅读

目录 摘要 Abstract 一、现有问题 二、提出方法 三、创新点 模型结构创新 强化学习与GAN结合 属性特征与通顺性优化 四、方法论 生成对抗网络&#xff08;GAN&#xff09; 强化学习&#xff08;RL&#xff09; 模型组件 五、实验研究 数据集 数据预处理 评价指…...

yolov8 转华为昇腾om脚本

目录 yolov8 转华为昇腾 om脚本 测试ok 推理demo: yolov8 转华为昇腾 om脚本 测试ok import sys import osos.chdir(os.path.dirname(os.path.abspath(__file__)))import torchcurrent_dir = os.path.dirname(os.path.abspath(__file__))paths = [os.path.abspath(__file__)…...

分布式事物XA、BASE、TCC、SAGA、AT

分布式事务——Seata 一、Seata的架构&#xff1a; 1、什么是Seata&#xff1a; 它是一款分布式事务解决方案。官网查看&#xff1a;Seata 2.执行过程 在分布式事务中&#xff0c;会有一个入口方法去调用各个微服务&#xff0c;每一个微服务都有一个分支事务&#xff0c;因…...

域名信息收集(小迪网络安全笔记~

附&#xff1a;完整笔记目录~ ps&#xff1a;本人小白&#xff0c;笔记均在个人理解基础上整理&#xff0c;若有错误欢迎指正&#xff01; 2.1 域名信息收集 引子&#xff1a;上一章介绍了服务器的信息收集。本篇则介绍在面对存在Web资产企业时&#xff0c;其域名信息该如何收…...

力扣-图论-13【算法学习day.63】

前言 ###我做这类文章一个重要的目的还是给正在学习的大家提供方向和记录学习过程&#xff08;例如想要掌握基础用法&#xff0c;该刷哪些题&#xff1f;&#xff09;我的解析也不会做的非常详细&#xff0c;只会提供思路和一些关键点&#xff0c;力扣上的大佬们的题解质量是非…...

日语AI面试高效通关秘籍:专业解读与青柚面试智能助攻

在如今就业市场竞争日益激烈的背景下&#xff0c;越来越多的求职者将目光投向了日本及中日双语岗位。但是&#xff0c;一场日语面试往往让许多人感到步履维艰。你是否也曾因为面试官抛出的“刁钻问题”而心生畏惧&#xff1f;面对生疏的日语交流环境&#xff0c;即便提前恶补了…...

iOS 26 携众系统重磅更新,但“苹果智能”仍与国行无缘

美国西海岸的夏天&#xff0c;再次被苹果点燃。一年一度的全球开发者大会 WWDC25 如期而至&#xff0c;这不仅是开发者的盛宴&#xff0c;更是全球数亿苹果用户翘首以盼的科技春晚。今年&#xff0c;苹果依旧为我们带来了全家桶式的系统更新&#xff0c;包括 iOS 26、iPadOS 26…...

多模态商品数据接口:融合图像、语音与文字的下一代商品详情体验

一、多模态商品数据接口的技术架构 &#xff08;一&#xff09;多模态数据融合引擎 跨模态语义对齐 通过Transformer架构实现图像、语音、文字的语义关联。例如&#xff0c;当用户上传一张“蓝色连衣裙”的图片时&#xff0c;接口可自动提取图像中的颜色&#xff08;RGB值&…...

质量体系的重要

质量体系是为确保产品、服务或过程质量满足规定要求&#xff0c;由相互关联的要素构成的有机整体。其核心内容可归纳为以下五个方面&#xff1a; &#x1f3db;️ 一、组织架构与职责 质量体系明确组织内各部门、岗位的职责与权限&#xff0c;形成层级清晰的管理网络&#xf…...

unix/linux,sudo,其发展历程详细时间线、由来、历史背景

sudo 的诞生和演化,本身就是一部 Unix/Linux 系统管理哲学变迁的微缩史。来,让我们拨开时间的迷雾,一同探寻 sudo 那波澜壮阔(也颇为实用主义)的发展历程。 历史背景:su的时代与困境 ( 20 世纪 70 年代 - 80 年代初) 在 sudo 出现之前,Unix 系统管理员和需要特权操作的…...

ArcGIS Pro制作水平横向图例+多级标注

今天介绍下载ArcGIS Pro中如何设置水平横向图例。 之前我们介绍了ArcGIS的横向图例制作&#xff1a;ArcGIS横向、多列图例、顺序重排、符号居中、批量更改图例符号等等&#xff08;ArcGIS出图图例8大技巧&#xff09;&#xff0c;那这次我们看看ArcGIS Pro如何更加快捷的操作。…...

USB Over IP专用硬件的5个特点

USB over IP技术通过将USB协议数据封装在标准TCP/IP网络数据包中&#xff0c;从根本上改变了USB连接。这允许客户端通过局域网或广域网远程访问和控制物理连接到服务器的USB设备&#xff08;如专用硬件设备&#xff09;&#xff0c;从而消除了直接物理连接的需要。USB over IP的…...

【Go语言基础【12】】指针:声明、取地址、解引用

文章目录 零、概述&#xff1a;指针 vs. 引用&#xff08;类比其他语言&#xff09;一、指针基础概念二、指针声明与初始化三、指针操作符1. &&#xff1a;取地址&#xff08;拿到内存地址&#xff09;2. *&#xff1a;解引用&#xff08;拿到值&#xff09; 四、空指针&am…...

Go 语言并发编程基础:无缓冲与有缓冲通道

在上一章节中&#xff0c;我们了解了 Channel 的基本用法。本章将重点分析 Go 中通道的两种类型 —— 无缓冲通道与有缓冲通道&#xff0c;它们在并发编程中各具特点和应用场景。 一、通道的基本分类 类型定义形式特点无缓冲通道make(chan T)发送和接收都必须准备好&#xff0…...

C语言中提供的第三方库之哈希表实现

一. 简介 前面一篇文章简单学习了C语言中第三方库&#xff08;uthash库&#xff09;提供对哈希表的操作&#xff0c;文章如下&#xff1a; C语言中提供的第三方库uthash常用接口-CSDN博客 本文简单学习一下第三方库 uthash库对哈希表的操作。 二. uthash库哈希表操作示例 u…...