基于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

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

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

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

相关文章

SpringBoot【十一】mybatis-plus实现多数据源配置,开箱即用!

一、前言&#x1f525; 环境说明&#xff1a;Windows10 Idea2021.3.2 Jdk1.8 SpringBoot 2.3.1.RELEASE 正常情况下我们在开发系统的时候都是使用一个数据源&#xff0c;但是由于有些项目同步数据的时候不想造成数据库io消耗压力过大&#xff0c;便会一个项目对应多个数据源…

Node.js教程入门第一课:环境安装

对于一个程序员来说&#xff0c;每学习一个新东西的时候&#xff0c;第一步基本上都是先进行环境的搭建&#xff01; 从本章节开始让我们开始探索Node.js的世界吧! 什么是Node.js? 那么什么是Node.js呢&#xff1f;简单的说Node.js 就是运行在服务端的 JavaScript JavaScript…

vim优化

1.编辑如下内容&#xff1a; cat > /root/.vimrc <<EOF set tabstop2 " 设置 Tab 为 2 个空格 set shiftwidth2 " 设置自动缩进为 2 个空格 set expandtab " 将 Tab 转换为空格 " 基本设置 set number syntax on" 快捷键设置…

移动网络的原理

无线网络是如何解决移动通信问题的 场景&#xff1a;用户在一辆轿车内以150km/h的时速沿高速公路急速行驶时穿过多个无线接入网&#xff0c;用户希望在整个旅程中保持一个与远程应用的不间断的TCP连接。 解决方案&#xff1a;移动节点的间接路由选择方法可解决TCP链接不间断的…

python学opencv|读取图像(十三)BGR图像和HSV图像互相转换深入

【1】引言 前序学习过程中&#xff0c;我们偶然发现&#xff1a;如果原始图像是png格式&#xff0c;将其从BGR转向HSV&#xff0c;再从HSV转回BGR后&#xff0c;图像的效果要好于JPG格式。 文章链接为&#xff1a; python学opencv|读取图像&#xff08;十二&#xff09;BGR图…

java基础概念49-数据结构2

一、树 1-1、树的基本概念 1、树的节点 2、二叉树 3、树的高度 1-2、二叉查找树 普通二叉树没有规律&#xff0c;不方便查找&#xff0c;没什么作用。 1、基本概念 2、添加节点 此时&#xff0c;该方式添加形成的二叉查找树&#xff0c;根节点就是第一个节点。 3、查找节点 4…

12.12 枚举 共用体 数据结构 创建顺序表

1.思维导图 2. 创建顺序表 1>头文件 test.h #ifndef __TEST_H__ #define __TEST_H__#include<stdlib.h> #include<stdio.h> #include<string.h>#define MAX 30 //typedef int datatype;typedef struct sequence {int data[MAX];int len;}seqlist,*se…

如何对小型固定翼无人机进行最优的路径跟随控制?

控制架构 文章继续采用的是 ULTRA-Extra无人机&#xff0c;相关参数如下&#xff1a; 这里用于guidance law的无人机运动学模型为&#xff1a; { x ˙ p V a cos ⁡ γ cos ⁡ χ V w cos ⁡ γ w cos ⁡ χ w y ˙ p V a cos ⁡ γ sin ⁡ χ V w cos ⁡ γ w sin ⁡ χ…

【Flink-scala】DataStream编程模型之延迟数据处理

DataStream API编程模型 1.【Flink-Scala】DataStream编程模型之数据源、数据转换、数据输出 2.【Flink-scala】DataStream编程模型之 窗口的划分-时间概念-窗口计算程序 3.【Flink-scala】DataStream编程模型之水位线 4.【Flink-scala】DataStream编程模型之窗口计算-触发器-…

2024告别培训班 数通、安全、云计算、云服务、存储、软考等1000G资源分享

大类有&#xff1a;软考初级 软考中级 软考高级 华为认证 华三认证&#xff1a; 软考初级&#xff1a; 信息处理技术员 程序员 网络管理员 软考中级&#xff1a; 信息安全工程师 信息系统监理师 信息系统管理工程师 嵌入式系统设计时 数据库系统工程师 电子商务设…

《操作系统 - 清华大学》8 -1:进程的组成

文章目录 1. 进程的组成2. 进程与程序的联系3. 进程与程序的区别4. 进程与程序关系 1. 进程的组成 进程具体包含哪些东西&#xff1a; 首先要执行相应的代码&#xff0c;所以执行代码需要放到内存中代码执行需要处理数据&#xff0c;数据需要放到内存中需要知道现在要执行哪条…

【Java】String类API

创建字符串 字符串字面量"Hello"高效&#xff0c;常量池复用常见、简单的字符串创建 new 关键字new String("Hello")每次创建新对象&#xff0c;性能开销较高显式创建新对象 字符数组new String(char[])转换字符数组字符数组转字符串 StringBuilder/St…

数据结构初阶---二叉树---堆

一、树 1.树的概念 树是一种非线性的数据结构&#xff0c;由n(n≥0)个有限结点组成的一个有层次关系的集合。形状类似一棵倒挂的树&#xff0c;根朝上&#xff0c;分支向下。 根结点没有前驱结点&#xff0c;可以有n(n≥0)个后继结点。 其余结点被分为M个互不相交的集合&am…

C语言 字符串输入输出函数、scanf(“%[^\n]“,)可输入空格 、fgets删除换行符

字符串输入函数&#xff1a; scanf&#xff08;"%s"&#xff0c;数组名&#xff09; gets&#xff08;数组名&#xff09; fgets&#xff08;&#xff09; --- 文件流输入函数 函数原型&#xff1a; int scanf( const char *format, ...…

深度学习camp-第J4周:ResNet与DenseNet结合探索

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 本周任务&#xff1a; 探索ResNet和DenseNet的结合可能性本周任务较难&#xff0c;我们在chatGPT的帮助下完成 一、网络的构建 设计一种结合 ResNet 和 Den…

「iOS」通过CoreLocation Framework深入了解MVC架构

「iOS」通过CoreLocation Framework重新了解多界面传值以及MVC架构 文章目录 「iOS」通过CoreLocation Framework重新了解多界面传值以及MVC架构前言CoreLocation了解根据需求建模设计属性方法设计协议传值Block传值KVONotification通知方式 总结参考文章 前言 在这个学期的前…

ArrayList源码分析、扩容机制面试题,数组和List的相互转换,ArrayList与LinkedList的区别

目录 1.java集合框架体系 2. 前置知识-数组 2.1 数组 2.1.1 定义&#xff1a; 2.1.2 数组如何获取其他元素的地址值&#xff1f;&#xff08;寻址公式&#xff09; 2.1.3 为什么数组索引从0开始呢&#xff1f;从1开始不行吗&#xff1f; 3. ArrayList 3.1 ArrayList和和…

【C++】- 掌握STL List类:带你探索双向链表的魅力

文章目录 前言&#xff1a;一.list的介绍及使用1. list的介绍2. list的使用2.1 list的构造2.2 list iterator的使用2.3 list capacity2.4 list element access2.5 list modifiers2.6 list的迭代器失效 二.list的模拟实现1. list的节点2. list的成员变量3.list迭代器相关问题3.1…

Docker--Docker Container(容器) 之容器实战

对docker容器的前两篇文章 Docker–Docker Container(容器) 之 操作实例 Docker–Docker Container(容器&#xff09; Mysql容器化安装 我们可以先在Docker Hub上查看对应的Mysql镜像,拉取对应的镜像&#xff1a; 拉取mysql5.7版本的镜像&#xff1a; docker pull mysql:5.7…

【汇编语言】内中断(二) —— 安装自己的中断处理程序:你也能控制0号中断

文章目录 前言1. 编程处理0号中断1.1 效果演示1.2 分析所要编写的中断处理程序1.2.1 引发中断1.2.2 中断处理程序1.2.3 中断处理程序do0应该存放的位置1.2.4 中断向量表的修改1.2.5 总结 1.3 程序框架1.4 注意事项1.5 从CPU的角度看中断处理程序1.6 一些问题的思考与解答 2. 安…