本文例子參考《STM32單片機開發實例——基於Proteus虛擬模擬與HAL/LL庫》 源代碼:https://github.com/LanLinnet/STM33F103R6 項目要求 掌握$I^2C$的通訊方法和時序,通過串口發送數據,單片機接收並存入AT24C02首地址中。按下按鍵BTN,單片 ...
本文例子參考《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”。