基于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.pswp.cn/web/63035.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/63035.shtml
英文地址,請注明出處:http://en.pswp.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" 快捷鍵設置…

字符串性能對比

效率(1) : String.indexOf與String.contains效率測試_string contains效率-CSDN博客 結論是前者效率高&#xff0c;源碼里面conatins是使用indexof 在jdk8中contains直接調用的indexOf(其他版本沒有驗證),所以要說效率來說肯定是indexOf高,但contains也就多了一層方法棧,so 什…

移動網絡的原理

無線網絡是如何解決移動通信問題的 場景&#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圖…

解決node.js的req.body為空的問題

從昨晚一直在試&#xff0c;明明之前用的封裝的axios發送請求給其他的后端&#xff08;springboot&#xff09;是可以的&#xff0c;但昨天用了新項目的后端&#xff08;node.js&#xff09;就不行。 之前用了代理&#xff0c;所以瀏覽器發送的post請求不會被攔截&#xff0c;…

【嵌入式】嵌入式面試題 36 問

1. volatile 是否可以修飾 const 是的&#xff0c;volatile 可以修飾 const。const 表示變量的值不能被修改&#xff0c;而 volatile 表示變量的值可能在程序之外被修改&#xff08;例如&#xff0c;由硬件修改&#xff09;。 將 volatile 用于 const 變量意味著該變量的值雖然…

java基礎概念49-數據結構2

一、樹 1-1、樹的基本概念 1、樹的節點 2、二叉樹 3、樹的高度 1-2、二叉查找樹 普通二叉樹沒有規律&#xff0c;不方便查找&#xff0c;沒什么作用。 1、基本概念 2、添加節點 此時&#xff0c;該方式添加形成的二叉查找樹&#xff0c;根節點就是第一個節點。 3、查找節點 4…

GhatGPT缺陷不足和商業應用

1. 引言 ChatGPT的興起&#xff1a; 2022年末推出&#xff0c;迅速在自然語言處理和人工智能領域引起廣泛關注。數億用戶體驗其強大智能&#xff0c;感嘆機器智能的飛速發展。 存在的缺陷&#xff1a; 事實性錯誤&#xff1a;生成的文本中包含錯誤信息。無法實時更新&#xff1…

【Linux】Macvlan介紹及LInux下例子實現

Macvlan Macvlan 是一種網絡虛擬化技術&#xff0c;允許在同一物理網絡接口上創建多個虛擬網絡接口&#xff0c;每個虛擬接口都有自己獨立的 MAC 地址。這對于需要在同一物理主機上運行多個網絡隔離的應用程序或容器時非常有用。 Macvlan 的特點和用途 獨立的 MAC 地址 每個 …

Jackson @JsonInclude 注解

1. 概述 Jackson 是一個著名的Java庫&#xff0c;以轉換Java對象為JSON格式以及從JSON反序列化回Java對象而聞名。有時候&#xff0c;我們可能希望僅在某些字段滿足特定條件時才將其包含在JSON輸出中&#xff0c;而Jackson的JsonInclude注解正是為此目的量身定制的。 JsonInc…

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…

next.js 存在緩存中毒漏洞(CVE-2024-46982)

免責聲明: 本文旨在提供有關特定漏洞的深入信息,幫助用戶充分了解潛在的安全風險。發布此信息的目的在于提升網絡安全意識和推動技術進步,未經授權訪問系統、網絡或應用程序,可能會導致法律責任或嚴重后果。因此,作者不對讀者基于本文內容所采取的任何行為承擔責任。讀者在…

如何對小型固定翼無人機進行最優的路徑跟隨控制?

控制架構 文章繼續采用的是 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編程模型之窗口計算-觸發器-…

react useRef、useContext、useReducer使用中遇到的問題及解決辦法

在 React 中&#xff0c;useRef、useContext 和 useReducer 是三個非常有用的 Hook&#xff0c;它們可以幫助我們更好地管理組件的狀態和行為。然而&#xff0c;在使用這些 Hook 時&#xff0c;可能會遇到一些問題和困惑。本文將詳細解釋這三個 Hook 的工作原理&#xff0c;并提…

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…