基于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(建议) TXD A2 RXD A3 GND GND GND GND B 485-B A 485-A
三、单片机和RS485综合传感器的通信配置
综合土壤传感器通过硬件连接后,32单片机和其的通信方式则为串口通信,单片机通过串口发送问询帧给综合土壤传感器,其接收到单片机的发送信息后,会反馈应答帧给单片机,单片机从应答帧接收到对的地址码后,则开始接收对应的信息,后通过转换数据得到我们想要的信息。
要想知道具体的通信细节,我们还得查看商家给我们对应传感器的文档,里面有我们需要的对应通信协议。
四、通信协议 (重要)
4.1 通讯基本参数
注意: 这里的波特率指的是串口通信中使用的波特率,而不同厂家生产的综合土壤传感器出厂默认的波特率也有可能不相同,有的可能为9600bit/s,具体到哪个需要找商家拿到对应的资料文档从而进行查看,本次使用的传感器出厂默认为4800bit/s。
编 码
8位二进制
数据位
8位
奇偶校验位
无
停止位
1位
错误校验
CRC(冗余循环码)
波特率
2400bit/s、4800bit/s、9600 bit/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
---------------------------------------------
————————————————