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
---------------------------------------------
————————————————