基于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); //初始化串口2
  
	USART_Cmd(USART2, ENABLE);                    //使能串口 
	
	//使能接收中断
	USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);//开启中断   
	
	//设置中断优先级
	NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2 ;//抢占优先级3
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;		//子优先级3
	NVIC_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);//延时1ms
			Send_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); //初始化串口2
  
	USART_Cmd(USART2, ENABLE);                    //使能串口 
	
	//使能接收中断
	USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);//开启中断   
	
	//设置中断优先级
	NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2 ;//抢占优先级3
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;		//子优先级3
	NVIC_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);//延时1ms
			Send_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;	//设置输出速率为50MHz
	GPIO_Init(GPIOB,&GPIO_InitStructure);	//调用库函数初始化GPIOA
	
	OLED_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);	//延迟1us
	OLED_SDIN_Clr();	//信号线置低
	delay_us(1);	//延迟1us
	OLED_SCLK_Clr();	//时钟线置低
	delay_us(1);	//延迟1us
}


/*
	@brief			模拟IIC停止信号
	@param			无
	@retval			无
 */
static void OLED_IIC_Stop(void)
{
	OLED_SDIN_Clr();	//信号线置低
	delay_us(1);	//延迟1us
	OLED_SCLK_Set();	//时钟线置高
	delay_us(1);	//延迟1us
	OLED_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);	//延迟1us
	OLED_SDIN_Set();	//信号线置高
	delay_us(1);	//延迟1us
	OLED_SCLK_Set();	//时钟线置高
	delay_us(1);	//延迟1us

	if(OLED_READ_SDIN())	//读取SDA的电平
		ack = IIC_NO_ACK;	//如果为1,则从机没有应答
	else
		ack = IIC_ACK;		//如果为0,则从机应答

	OLED_SCLK_Clr();//时钟线置低
	delay_us(1);	//延迟1us

	return 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);	//延迟1us

		if(IIC_Byte & 0x80)	//读取最高位
		  	OLED_SDIN_Set();//最高位为1
		else
			OLED_SDIN_Clr();	//最高位为0

		IIC_Byte <<= 1;  //数据左移1位
		delay_us(1);	//延迟1us
		OLED_SCLK_Set(); //时钟线置高,产生上升沿,把数据发送出去
		delay_us(1);	//延迟1us
	}
	OLED_SCLK_Clr();	//时钟线置低
	delay_us(1);	//延迟1us

	while(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 = 0
   Write_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 = 0
   Write_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);	//64duty
	
	Oled_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 Period
	Oled_Send_Cmd(0xF1);	//使用官方推荐值
	
	Oled_Send_Cmd(0xDA);	//设置 com pin configuartion
	Oled_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    显示汉字
返回值:void
    16*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    显示汉字
返回值:void
		8*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);	//起始页 0
	Oled_Send_Cmd(0x00);	//滚动时间间隔
	Oled_Send_Cmd(0x01);	//终止页 1
	Oled_Send_Cmd(0x00);	//虚拟字节
	Oled_Send_Cmd(0xFF);	//虚拟字节
	Oled_Send_Cmd(0x2F);	//开启滚动
}

六、结语

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

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

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

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

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

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Vue入门到精通:运行环境

Vue入门到精通&#xff1a;运行环境 Vue3的运行环境搭建主要有两种方法&#xff1a;一种是直接在页面中引入Vue库&#xff0c;另一种是通过脚手架工具创建Vue项目。 &#xff08;一&#xff09;页面直接引入Vue库 页面直接引入Vue库的方法&#xff0c;是指在HTML网页中通过s…

PHP项目从 php5.3 版本升级到 php8.3 版本时的一些问题和解决方法记录

一个原来的项目&#xff0c;因为业务需要&#xff0c;进行了PHP版本升级&#xff0c;从php5.3直接升级到php8.3。变化挺大的&#xff0c;原程序中有很多不再兼容&#xff0c;在此处进行一下记录。 一、Deprecated: 显式转换问题 报错内容&#xff1a;Deprecated: Implicit con…

【Python篇】PyQt5 超详细教程——由入门到精通(序篇)

文章目录 PyQt5 超详细入门级教程前言序篇&#xff1a;1-3部分&#xff1a;PyQt5基础与常用控件第1部分&#xff1a;初识 PyQt5 和安装1.1 什么是 PyQt5&#xff1f;1.2 在 PyCharm 中安装 PyQt51.3 在 PyCharm 中编写第一个 PyQt5 应用程序1.4 代码详细解释1.5 在 PyCharm 中运…

Apache Kylin最简单的解析、了解

官网&#xff1a;Overview | Apache Kylin 一、Apache Kylin是什么&#xff1f; 由中国团队研发具有浓厚的中国韵味&#xff0c;使用神兽麒麟&#xff08;kylin&#xff09;为名 的一个OLAP多维数据分析引擎:&#xff08;据官方给出的数据&#xff09; 亚秒级响应&#xff…

【工具】linux matlab 的使用

问题1 - 复制图表 在使用linux matlab画图后&#xff0c;无法保存figure。 例如在windows下 但是在linux下并没有这个“Copy Figure”的选项。 这是因为 “ The Copy Figure option is not available on Linux systems. Use the programmatic alternative.” 解决方案&…

opencv——(图像梯度处理、图像边缘化检测、图像轮廓查找和绘制、透视变换、举例轮廓的外接边界框)

一、图像梯度处理 1 图像边缘提取 cv2.filter2D(src, ddepth, kernel[, dst[, anchor[, delta[, borderType]]]]) 功能&#xff1a;用于对图像进行卷积操作。卷积是图像处理中的一个基本操作&#xff0c;它通过一个称为卷积核&#xff08;或滤波器&#xff09;的小矩阵在图像上…

Redis - 集合 Set 及代码实战

Set 类型 定义&#xff1a;类似 Java 中的 HashSet 类&#xff0c;key 是 set 的名字&#xff0c;value 是集合中的值特点 无序元素唯一查找速度快支持交集、并集、补集功能 常见命令 命令功能SADD key member …添加元素SREM key member …删除元素SCARD key获取元素个数SI…

androidstudio导入项目至成功运行(保姆级教程)

目录 一、下载资源包 二、创建一个新的项目 三、替换配置文件 四、打开Android Studio 一、下载资源包 将你得到的资源包下载到你能够找到的位置然后解压&#xff0c;方便操作 二、创建一个新的项目 如果已经有新创建的项目就不用新建了&#xff0c;但是需要注意的是&am…

SpringBoot SPI

参考 https://blog.csdn.net/Peelarmy/article/details/106872570 https://javaguide.cn/java/basis/spi.html#%E4%BD%95%E8%B0%93-spi SPI SPI(service provider interface)是JDK提供的服务发现机制。以JDBC为例&#xff0c;JDK提供JDBC接口&#xff0c;在包java.sql.*。MY…

linux部署ansible自动化运维

ansible自动化运维 1&#xff0c;编写ansible的仓库&#xff08;比赛已经安装&#xff0c;无需关注&#xff09; 1、虚拟机右击---设置---添加---CD/DVD驱动器---完成---确定 2、将ansible.iso的光盘连接上&#xff08;右下角呈绿色状态&#xff09; 3、查看光盘挂载信息 df -h…

Java——网络编程(中)—TCP通讯(上)

一 (网络编程常用类) (Java为了跨平台&#xff0c;在网络应用通信时是不允许直接调用操作系统接口的&#xff0c;而是由java.net包来提供网络功能。下面来介绍几个java.net包中的常用的类) 1 InetAddress类的使用 (封装计算机的IP地址和DNS) (这个类没有构造方法——>如…

SQL server学习05-查询数据表中的数据(上)

目录 一&#xff0c;基本格式 1&#xff0c;简单的SQL查询语句 2&#xff0c;关键字TOP 3&#xff0c;关键字DISTINCT 二&#xff0c;模糊查询 1&#xff0c;通配符 三&#xff0c;对结果集排序 1&#xff0c;不含关键字DISTINCT 2&#xff0c;含关键字DISTINCT 3&…

Gitlab ci/cd

关于gitlab ci/cd&#xff0c;就是实现DevOps的能力&#xff0c;即Development &Operations的缩写&#xff0c;也就是开发&运维。CI/CD 指的是软件开发的持续集成方法&#xff0c;我们可以持续构建、测试和部署软件。通过持续方法的迭代能使得我们减少在错误代码或者错…

Leetcode42-环形链表

题目 给定一个链表的头节点 head &#xff0c;返回链表开始入环的第一个节点。 如果链表无环&#xff0c;则返回 null。 如果链表中有某个节点&#xff0c;可以通过连续跟踪 next 指针再次到达&#xff0c;则链表中存在环。 为了表示给定链表中的环&#xff0c;评测系统内部使…

计算机网络错题

文章目录 码分复用透明传输差错检测停止-等待协议回退N帧协议CSMA/CD协议以太网交换机Vlanip地址的无分类编制方法ip地址的应用规划ip数据包的发送和转发过程路由信息协议IPI2016201720202022 2.5信道 码分复用 透明传输 差错检测 停止-等待协议 回退N帧协议 CSMA/CD协议 以太网…

改进系列(5):在ResNet网络添加SelfAttention自注意力层实现的遥感卫星下的土地利用情况图像分类

目录 1. ResNet介绍 2. SelfAttention 层 3. ResNet34 SelfAttention 4. 遥感卫星下的土地使用情况分类 4.1 土地使用情况数据集 4.2 训练 4.3 训练结果 4.4 推理 1. ResNet介绍 ResNet&#xff08;残差网络&#xff09;是一种深度卷积神经网络模型&#xff0c;由Ka…

ARM循环程序和子程序设计

1、计算下列两组数据的累加和并存入到sum1和 sum2 单元中。datal:0x12,0x935,0x17,0x100,0x95,0x345。 data2:0x357,0x778,0x129,0x188,0x190,0x155,0x167。 1.定义数据段 ;定义数据段&#xff0c;类型为data(表示为数据段)&#xff0c;权限为可读可写(程序可以读取和修改这…

蓝桥杯刷题——day5

蓝桥杯刷题——day5 题目一题干解题思路一代码解题思路二代码 题目二题干解题思路代码 题目一 题干 给定n个整数 a1,a2,⋯ ,an&#xff0c;求它们两两相乘再相加的和&#xff0c;即&#xff1a; 示例一&#xff1a; 输入&#xff1a; 4 1 3 6 9 输出&#xff1a; 117 题目链…

1_linux系统网络性能如何优化——几种开源网络协议栈比较

之前合集《计算机网络从入门到放弃》第一阶段算是已经完成了。都是理论&#xff0c;没有实操&#xff0c;让“程序猿”很难受&#xff0c;操作性不如 Modbus发送的报文何时等到应答和 tcp通信测试报告单1——connect和send。开始是想看linux内核网络协议栈的源码&#xff0c;然…

opencv——识别图片颜色并绘制轮廓

图像边缘检测 本实验要用到Canny算法&#xff0c;Canny边缘检测方法常被誉为边缘检测的最优方法。 首先&#xff0c;Canny算法的输入端应为图像的二值化结果&#xff0c;接收到二值化图像后&#xff0c;需要按照如下步骤进行&#xff1a; 高斯滤波。计算图像的梯度和方向。非极…