本文例子參考《STM32單片機開發實體——基于Proteus虛擬仿真與HAL/LL庫》
源代碼:https://github.com/LanLinnet/STM33F103R6
專案要求
掌握\(I^2C\)的通訊方法和時序,通過串口發送資料,單片機接收并存入AT24C02首地址中,按下按鍵BTN,單片機將存放在AT24C02首地址中的資料取出并通過串口發送,串口通信引數:波特率為19200bits/s;無校驗,
硬體設計
-
在第一節的基礎上,在Proteus中添加電路如下圖所示,其中我們添加了一個I2C通信的外設:EEPROM芯片
AT24C02(在Proteus中為FM24C02),

此外,還添加了\(I^2C\)總線除錯工具I2C DEBUGGER,用于讀取\(I^2C\)輸入輸出的資料,

串口和按鍵的相關電路可以參考第13節,COMPIM設定如下圖所示,

-
\(I^2C\):
1)簡介:\(I^2C\)(Inter-Integrated Circuit)總線是由Philips公司提出的一種兩線式串行總線,\(I^2C\)總線屬于多主總線,每個節點都可以設定唯一的地址,向總線發送資料的設備作為發送器,從總線接收資料的設備作為接收器,
2)\(I^2C\)總線:由時鐘信號線SCL和雙向資料線SDA組成,
3)通信時序:\(I^2C\)總線的通信時序分為發送器啟動/停止通信、資料位傳送、接收器回傳回應信號三種,-
發送器啟動/停止通信:SCL保持高電平期間,SDA產生下降沿,即通信啟動信號;SCL保持高電平期間,SDA產生上升沿,即通信停止信號,

-
資料位傳送:資料發送器在啟動通信之后,便向\(I^2C\)總線發送資料,發送資料位元組長度為1位元組,發送順序高位在前,低位在后,逐位發送,如下圖所示,在SCL處于高電平期間,SDA必須保持穩定,SDA低電平代表資料0,高電平代表資料1;只有在SCL處于低電平期間,SDA才能改變電平狀態,

-
接收器回傳回應信號:資料發送器每發送1個位元組,資料接收器都必須回傳1位回應信號,回應信號若為低電平則規定為應答回應位(ACK),表示資料接收器接收該位元組資料成功;反之,則稱為非應答回應位(NACK),表示資料接收器接收該位元組資料失敗,

如果資料接收器是主機,則在它收到最后一位元組資料后,回傳一個非應答位,通知資料發送器結束資料發送,接著主機向\(I^2C\)總線發送一個停止通信信號,結束通信程序,
-
-
AT24C02
1)簡介:AT24Cxx是美國Atmel公司出品的單行\(E^PROM\)系列芯片,xx表示不同的容量,如02表示該芯片的總容量為2kbits(256位元組),
2)引腳:AT24C02芯片引腳如下圖所示,引腳功能如下表所示,


其中,1-3引腳參與構成AT24C02在\(I^2C\)總線上的地址,如圖1K/2K的地址所示,地址高4位固定為1010B,低4位的最低位在總線“寫”指令中固定為0,在總線“讀”指令中固定為1,其余3位就由1-3引腳決定,

3)讀寫時序:AT24C02的讀寫方式有寫入位元組、寫入頁、讀當前地址、隨機讀取和連續讀取5種方式,下面我們介紹本專案中使用的兩種,-
寫入位元組時序(Byte Write):寫入位元組即向AT24C02寫入1位元組,由下面8步組成,
①主機發送啟動通信(Start)信號
②發送器件(芯片)地址(Device Address)
③產生應答回應(ACK)
④發送字地址(Word Address)
⑤產生應答回應(ACK)
⑥發送資料(Data)
⑦產生應答回應(ACK)
⑧發送停止通信(Stop)信號

-
隨機讀取時序(Random Read):隨機讀取即從AT24C02讀取1位元組,由下面11步組成,
①主機發送啟動通信(Start)信號
②發送器件(芯片)地址(Device Address)
③產生應答回應(ACK)
④發送字地址(Word Address)
⑤產生應答回應(ACK)
⑥再次發送啟動通信(Start)信號
⑦發送器件(芯片)地址(Device Address)
⑧產生應答回應(ACK)
⑨讀取資料(Data)
⑩發送非應答回應(No ACK)
?發送停止通信(Stop)信號

-
-
打開CubeMX,建立工程,設定PB6、PB7為
GPIO_Output,PC0為GPIO_Input,點擊“Categories”中的“GPIO”的“User Label”設定如下圖所示,

這里要注意,STM32F103R6自帶一個\(I^2C\)總線通信模塊,但是為了便于移植,我們這里采用GPIO引腳PB6、PB7模擬\(I^2C\)總線的時序,
隨后進行串口設定,如下圖所示,這里就不贅述了,具體可以參考第13節,


-
點擊“Generator Code”生成Keil工程,
軟體撰寫
-
考慮到代碼的可移植性,這里將\(I^2C\)總線時序模擬和AT24C02操作代碼分別寫入頭檔案“vI2C.h”“AT24C02.h”中,我們可以先在
...\Core\Src檔案夾中建立這兩個頭檔案,此時Keil可能找不到對應檔案,可以直接將檔案拽入Keil中進行編輯,然后再在“main.c”檔案中進行include, -
點擊“Open Project”在Keil中打開工程,打開“vI2C.h”,添加代碼如下,
//I2C總線時序模擬 #ifndef VI2C_H_ #define VI2C_H_ #include "main.h" //延時1μs void delay_us(uint16_t n) { uint16_t i = n*8; //8MHz,周期為1/8μs while(i--); } //設定資料線模式: I-輸入 O-輸出 void Pin_vSDA_Mode(char status) { GPIO_InitTypeDef GPIO_InitStruct = {0}; HAL_GPIO_WritePin(GPIOB, vSDA_Pin, GPIO_PIN_SET); GPIO_InitStruct.Pin = vSDA_Pin; GPIO_InitStruct.Pull = GPIO_PULLUP; if(status == 'I') //輸入 { GPIO_InitStruct.Mode = GPIO_MODE_INPUT; } else if(status == 'O') //輸出 { GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; } HAL_GPIO_Init(GPIOB, &GPIO_InitStruct); } //時鐘線輸出 void vSCL_Out(uint8_t dat) { switch(dat) { case 0: HAL_GPIO_WritePin(GPIOB, vSCL_Pin, GPIO_PIN_RESET); break; default: HAL_GPIO_WritePin(GPIOB, vSCL_Pin, GPIO_PIN_SET); break; } } //寫資料線 void vSDA_Out(uint8_t dat) { switch(dat) { case 0: HAL_GPIO_WritePin(GPIOB, vSDA_Pin, GPIO_PIN_RESET); break; default: HAL_GPIO_WritePin(GPIOB, vSDA_Pin, GPIO_PIN_SET); break; } } //讀資料線 uint8_t vSDA_In() { GPIO_PinState PinState; uint8_t rt; PinState = HAL_GPIO_ReadPin(GPIOB, vSDA_Pin); switch(PinState) { case GPIO_PIN_RESET: rt = 0; break; default: rt = 1; break; } return rt; } //啟動I2C通信 void I2C_Start() { Pin_vSDA_Mode('O'); vSDA_Out(1); delay_us(6); //至少延時4.7μs vSCL_Out(1); delay_us(6); //至少延時4.7μs vSDA_Out(0); //下降沿 delay_us(6); //至少延時4.7μs vSCL_Out(0); } //停止I2C通信 void I2C_Stop() { Pin_vSDA_Mode('O'); vSDA_Out(0); delay_us(6); //至少延時4.7μs vSCL_Out(1); delay_us(6); //至少延時4.7μs vSDA_Out(1); //上升沿 delay_us(6); //至少延時4.7μs } //發送應答-低電平 void I2C_Ack() { Pin_vSDA_Mode('O'); vSDA_Out(0); delay_us(6); //至少延時4.7μs vSCL_Out(1); delay_us(6); //至少延時4.7μs vSCL_Out(0); delay_us(6); //至少延時4.7μs vSDA_Out(1); delay_us(6); //至少延時4.7μs } //寫1位元組資料 void I2C_WtByte(uint8_t Dat) { uint8_t i, tmp; Pin_vSDA_Mode('O'); for(i = 0; i < 8; i++) { tmp = Dat & (0x80>>i); //高位在前,低位在后,逐位發送 vSCL_Out(0); delay_us(6); (tmp == 0) ? (vSDA_Out(0)) : (vSDA_Out(1)); delay_us(6); vSCL_Out(1); delay_us(6); } vSCL_Out(0); delay_us(6); vSDA_Out(1); delay_us(6); } //讀1位元組資料 uint8_t I2C_RdByte() { uint8_t Dat = 0, tmp, i; Pin_vSDA_Mode('I'); vSCL_Out(0); delay_us(6); for(i = 0; i < 8; i++) { vSCL_Out(1); delay_us(6); tmp = vSDA_In(); Dat = Dat << 1; //讀1位左移1位 Dat = Dat | tmp; delay_us(6); vSCL_Out(0); delay_us(6); } return Dat; } #endif /* VI2C_H_ */打開“AT24C02.h”,添加代碼如下,
//AT24C02操作 #ifndef AT24C02_H_ #define AT24C02_H_ #define AT24C02_ADDR 0xa0 #include "main.h" #include "vI2C.h" //寫入1位元組 void AT24C02_Write(uint8_t DatAddr, uint8_t Dat) { I2C_Start(); //主機發送啟動通信信號 I2C_WtByte(AT24C02_ADDR + 0); //發送器件(芯片)地址 I2C_Ack(); //產生應答回應 I2C_WtByte(DatAddr); //發送字地址 I2C_Ack(); //產生應答回應 I2C_WtByte(Dat); //發送資料 I2C_Ack(); //產生應答回應 I2C_Stop(); //發送停止通信信號 } //讀取1位元組 uint8_t AT24C02_Read(uint8_t DatAddr) { uint8_t Dat; I2C_Start(); //主機發送啟動通信信號 I2C_WtByte(AT24C02_ADDR + 0); //發送器件地址 I2C_Ack(); //產生應答回應 I2C_WtByte(DatAddr); //發送字地址 I2C_Ack(); //產生應答回應 I2C_Start(); //再次發送啟動通信信號 I2C_WtByte(AT24C02_ADDR + 1); //發送器件地址 I2C_Ack(); //產生應答回應 Dat = I2C_RdByte(); //讀取資料 I2C_Stop(); //產生非應答信號,發送停止通信信號 return Dat; } #endif /* AT24C02_H_ */ -
隨后我們需要在main.c檔案中的最前面引入我們自定義的頭檔案
/* USER CODE BEGIN Includes */ #include "vI2C.h" //參考I2C總線時序模擬頭檔案 #include "AT24C02.h" //參考AT24C02操作頭檔案 /* USER CODE END Includes */在main函式中定義一些全域變數
/* USER CODE BEGIN PV */ uint8_t RcvDat[1]; //存放接收資料陣列 uint8_t SndDat[1]; //存放發送資料陣列 uint8_t rf = 0; //接收完成標志位 /* USER CODE END PV */進行串口相關操作
/* USER CODE BEGIN 2 */ HAL_UART_Receive_IT(&huart1, RcvDat, 1); //串口1接收中斷 /* USER CODE END 2 *//* USER CODE BEGIN WHILE */ while (1) { if(rf==1) //若接收完成 { rf = 0; //清0標志位 AT24C02_Write(0, RcvDat[0]); //寫入1位元組 HAL_UART_Receive_IT(&huart1, RcvDat, 1); //每次接收前需呼叫一次 } else if(HAL_GPIO_ReadPin(BTN_GPIO_Port, BTN_Pin) == GPIO_PIN_RESET) //若按下按鍵 { SndDat[0] = AT24C02_Read(0); //讀1位元組資料,并存入陣列 HAL_UART_Transmit(&huart1, SndDat, 1, 0xffff); //串口1發送1位元組,超時65535ms while(HAL_GPIO_ReadPin(BTN_GPIO_Port, BTN_Pin) == GPIO_PIN_RESET); //直到按鍵松開 } /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ } /* USER CODE END 3 *//* USER CODE BEGIN 4 */ void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) //串口接收完畢回呼函式 { if(huart == &huart1) { rf = 1; //若接收完成,則標志位置1 } } /* USER CODE END 4 */
聯合除錯
-
點擊運行,生成HEX檔案,
-
在Proteus中加載相應HEX檔案,點擊運行,
-
打開串口除錯助手“XCOM”,選擇
COM4,設定相應的波特率、停止位、資料位、奇偶校驗等,勾選“16進制顯示”和“16進制發送”,點擊“打開串口”,在發送框輸入“CD”,點擊“發送”,在Proteus中我們可以看到“I2C Debug”接收到資料“CD”,按下按鍵,同時再觀察串口除錯助手“XCOM”,可以看到接收視窗收到資料“CD”,


轉載請註明出處,本文鏈接:https://www.uj5u.com/caozuo/482104.html
標籤:其他
