雖然從I2C特性上知曉具有不同I2C地址的器件是可以掛載在同一個I2C總線上進行通訊的,但是,如果需要操作的I2C器件地址沖突呢?MCU的硬件I2C接口數量不夠呢?或者說MCU的I2C不支持從機多地址通訊功能呢?這時候,我們還是需要通過GPIO口來模擬I2C時序完成I2C主機/從機的功能。所以,并不是有了硬件I2C,軟件I2C就沒有發揮的空間了,恰恰是軟件和硬件這兩種實現方式共存互相補充。
?
對于I2C的基本概念及時序等知識點,本文不再詳細描述,大家可以下載附件中的《I2C總線概要》和《I2C總線規范》進行研究。本文將通過如下四個方面,講述I2C在MM32F032/MM32F0140系列MCU上的實現,以及使用I2C工具(圖莫斯USB2XXX總線適配器)進行實際測試:- 硬件I2C主機通訊
- 軟件模擬I2C主機通訊
- 硬件I2C從機通訊
- 軟件模擬I2C從機通訊(有難度)
- I2C總線協議轉換器/并行總線;
- 半雙工同步操作;
- 支持主從模式;
- 支持7位地址和10位地址;
- 支持標準模式100kbps、快速模式400kbps;
- 產生Start、Stop、Repeated Start,以及Acknowledge信號檢測;
- 在主機模式下只支持一個主機;
- 分別有2個字節的發送和接收緩沖;
- 在SCL和SDA上增加了無毛刺電路;
- 支持DAM、中斷和查詢操作方式;
- MM32F0140系列MCU在MM32F032的基礎上I2C做了更豐富的功能,支持多從機地址通訊的功能、支持時鐘延展等等……具體的可以參考官方的數據手冊。
?
一、硬件I2C主機通訊
MM32的硬件I2C是我使用到現在,在代碼程序段操作最為簡潔的了;不需要再去考慮START信號、ACK信號,以及各種EVENT事件等……這些復雜的操作、或者是可以省略的操作都由官方的底層庫程序和芯片IP去實現了,讓我們在設計驅動程序時變量簡單了。對于硬件I2C主機的配置,我們只需要復用的GPIO端口引腳、I2C通訊參數,以及從機地址即可;然后就可以編程去讀寫I2C從機設備了,初始化配置及對I2C從機設備的讀寫操作的實現代碼如下:- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
實測結果如下所示:void hI2C_MASTER_Init(uint8_t SlaveAddress){GPIO_InitTypeDef GPIO_InitStructure;I2C_InitTypeDef I2C_InitStructure;RCC_APB1PeriphClockCmd(RCC_APB1ENR_I2C1, ENABLE);I2C_StructInit(&I2C_InitStructure);I2C_InitStructure.I2C_Mode = I2C_Mode_MASTER;I2C_InitStructure.I2C_OwnAddress = 0;I2C_InitStructure.I2C_Speed = I2C_Speed_STANDARD;I2C_InitStructure.I2C_ClockSpeed = 100000;I2C_Init(I2C1, &I2C_InitStructure);I2C_Send7bitAddress(I2C1, SlaveAddress, I2C_Direction_Transmitter);I2C_Cmd(I2C1, ENABLE);RCC_AHBPeriphClockCmd(RCC_AHBENR_GPIOB, ENABLE);GPIO_PinAFConfig(GPIOB, GPIO_PinSource6, GPIO_AF_1);GPIO_PinAFConfig(GPIOB, GPIO_PinSource7, GPIO_AF_1);GPIO_StructInit(&GPIO_InitStructure);GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;GPIO_Init(GPIOB, &GPIO_InitStructure);}void hI2C_MASTER_Read(uint8_t Address, uint8_t *Buffer, uint8_t Length){uint8_t flag = 0, count = 0;I2C_SendData(I2C1, Address);while(!I2C_GetFlagStatus(I2C1, I2C_STATUS_FLAG_TFE));for(uint8_t i = 0; i < Length; i++){while(1){if((I2C_GetFlagStatus(I2C1, I2C_STATUS_FLAG_TFNF)) && (flag == 0)){I2C_ReadCmd(I2C1); count++;if(count == Length) flag = 1;}if(I2C_GetFlagStatus(I2C1, I2C_STATUS_FLAG_RFNE)){Buffer[i] = I2C_ReceiveData(I2C1); break;}}}I2C_GenerateSTOP(I2C1, ENABLE);while(!I2C_GetFlagStatus(I2C1, I2C_FLAG_STOP_DET));}void hI2C_MASTER_Write(uint8_t Address, uint8_t *Buffer, uint8_t Length){I2C_SendData(I2C1, Address);while(!I2C_GetFlagStatus(I2C1, I2C_STATUS_FLAG_TFE));for(uint8_t i = 0; i < Length; i++){I2C_SendData(I2C1, *Buffer++);while(!I2C_GetFlagStatus(I2C1, I2C_STATUS_FLAG_TFE));}I2C_GenerateSTOP(I2C1, ENABLE);while(!I2C_GetFlagStatus(I2C1, I2C_FLAG_STOP_DET));}void hI2C_MASTER_SHELL_Handler(uint8_t Mode){uint8_t Buffer[10] = {0x12, 0x23, 0x34, 0x45, 0x56, 0x67, 0x78, 0x89, 0x90, 0xAA};if(Mode == 1){hI2C_MASTER_Write(0x00, Buffer, sizeof(Buffer));}else{hI2C_MASTER_Read(0x00, Buffer, sizeof(Buffer));printf(" hI2C Master Read : ");for(uint8_t i = 0; i < sizeof(Buffer); i++){printf("0x%02x ", Buffer[i]);}printf(" ");}}SHELL_EXPORT_CMD(HI2C_MASTER, hI2C_MASTER_SHELL_Handler, Hardware I2C Master Read And Write);

?
二、軟件模擬I2C主機通訊
對于軟件模擬I2C主機通訊的實現方式,主要是通過操作GPIO端口引腳的高低電平,在滿足I2C通訊時序的要求上完成對I2C從機設備的讀寫操作;在實現軟件模擬I2C主機時,需要正確的產生Start起始條件、Stop停止條件,以及Restart重啟條件;需要在適當的位置對GPIO端口引腳的輸入輸出狀態進行配置,以便能夠正確的判斷出ACK和NACK的應答信號;需要正確操作發送的字節格式,使地址內容、數據內容能夠被正確識別……
如下的軟件模擬I2C主機的實現方式通過定義了一個操作結構體,通過傳遞操作實例的方式,讓軟件模擬I2C主機的程序實現了面向對象的編程,借住同一段實現代碼,可以同時實現多個軟件模擬I2C主機通訊接口,在代碼實現上大大的節省了空間,同時也讓代碼的可移植性變得更加通用,具體的代碼實現如下所示:
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
實測結果如下所示:typedef struct{uint32_t SCL_RCC;GPIO_TypeDef *SCL_GPIO;uint16_t SCL_PIN;uint32_t SDA_RCC;GPIO_TypeDef *SDA_GPIO;uint16_t SDA_PIN;uint32_t TIME;uint8_t SlaveAddress;} sI2C_MASTER_TypeDef;sI2C_MASTER_TypeDef sI2C_MASTER ={RCC_AHBENR_GPIOB, GPIOB, GPIO_Pin_6,RCC_AHBENR_GPIOB, GPIOB, GPIO_Pin_7,100,0xA0};#define sI2C_MASTER_SCL_H(sI2C) GPIO_WriteBit(sI2C->SCL_GPIO, sI2C->SCL_PIN, Bit_SET)#define sI2C_MASTER_SCL_L(sI2C) GPIO_WriteBit(sI2C->SCL_GPIO, sI2C->SCL_PIN, Bit_RESET)#define sI2C_MASTER_SDA_H(sI2C) GPIO_WriteBit(sI2C->SDA_GPIO, sI2C->SDA_PIN, Bit_SET)#define sI2C_MASTER_SDA_L(sI2C) GPIO_WriteBit(sI2C->SDA_GPIO, sI2C->SDA_PIN, Bit_RESET)#define sI2C_MASTER_SCL_GET(sI2C) GPIO_ReadOutputDataBit(sI2C->SCL_GPIO, sI2C->SCL_PIN)#define sI2C_MASTER_SDA_GET(sI2C) GPIO_ReadInputDataBit( sI2C->SDA_GPIO, sI2C->SDA_PIN)void sI2C_MASTER_Delay(uint32_t Tick){while(Tick--);}void sI2C_MASTER_SDA_SetDirection(sI2C_MASTER_TypeDef *sI2C, uint8_t Direction){GPIO_InitTypeDef GPIO_InitStructure;RCC_AHBPeriphClockCmd(sI2C->SDA_RCC, ENABLE);GPIO_StructInit(&GPIO_InitStructure);GPIO_InitStructure.GPIO_Pin = sI2C->SDA_PIN;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;if(Direction) /* Input */{GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;}else /* Output */{GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;}GPIO_Init(sI2C->SDA_GPIO, &GPIO_InitStructure);}void sI2C_MASTER_SCL_SetDirection(sI2C_MASTER_TypeDef *sI2C, uint8_t Direction){GPIO_InitTypeDef GPIO_InitStructure;RCC_AHBPeriphClockCmd(sI2C->SCL_RCC, ENABLE);GPIO_StructInit(&GPIO_InitStructure);GPIO_InitStructure.GPIO_Pin = sI2C->SCL_PIN;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;if(Direction) /* Input */{GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;}else /* Output */{GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;}GPIO_Init(sI2C->SCL_GPIO, &GPIO_InitStructure);}void sI2C_MASTER_GenerateStart(sI2C_MASTER_TypeDef *sI2C){sI2C_MASTER_SDA_H(sI2C); sI2C_MASTER_Delay(sI2C->TIME);sI2C_MASTER_SCL_H(sI2C); sI2C_MASTER_Delay(sI2C->TIME);sI2C_MASTER_SDA_L(sI2C); sI2C_MASTER_Delay(sI2C->TIME);sI2C_MASTER_SCL_L(sI2C); sI2C_MASTER_Delay(sI2C->TIME);}void sI2C_MASTER_GenerateStop(sI2C_MASTER_TypeDef *sI2C){sI2C_MASTER_SDA_L(sI2C); sI2C_MASTER_Delay(sI2C->TIME);sI2C_MASTER_SCL_H(sI2C); sI2C_MASTER_Delay(sI2C->TIME);sI2C_MASTER_SDA_H(sI2C); sI2C_MASTER_Delay(sI2C->TIME);}void sI2C_MASTER_PutACK(sI2C_MASTER_TypeDef *sI2C, uint8_t ack){if(ack) sI2C_MASTER_SDA_H(sI2C); /* NACK */else sI2C_MASTER_SDA_L(sI2C); /* ACK */sI2C_MASTER_Delay(sI2C->TIME);sI2C_MASTER_SCL_H(sI2C); sI2C_MASTER_Delay(sI2C->TIME);sI2C_MASTER_SCL_L(sI2C); sI2C_MASTER_Delay(sI2C->TIME);}uint8_t sI2C_MASTER_GetACK(sI2C_MASTER_TypeDef *sI2C){uint8_t ack = 0;sI2C_MASTER_SDA_H(sI2C); sI2C_MASTER_Delay(sI2C->TIME);sI2C_MASTER_SDA_SetDirection(sI2C, 1);sI2C_MASTER_SCL_H(sI2C); sI2C_MASTER_Delay(sI2C->TIME);ack = sI2C_MASTER_SDA_GET(sI2C);sI2C_MASTER_SCL_L(sI2C); sI2C_MASTER_Delay(sI2C->TIME);sI2C_MASTER_SDA_SetDirection(sI2C, 0);return ack;}uint8_t sI2C_MASTER_ReadByte(sI2C_MASTER_TypeDef *sI2C){uint8_t Data = 0;sI2C_MASTER_SDA_H(sI2C); /* Must set SDA before read */sI2C_MASTER_SDA_SetDirection(sI2C, 1);for(uint8_t i = 0; i < 8; i++){sI2C_MASTER_SCL_H(sI2C); sI2C_MASTER_Delay(sI2C->TIME);Data <<= 1;if(sI2C_MASTER_SDA_GET(sI2C)) Data |= 0x01;sI2C_MASTER_SCL_L(sI2C); sI2C_MASTER_Delay(sI2C->TIME);}sI2C_MASTER_SDA_SetDirection(sI2C, 0);return Data;}void sI2C_MASTER_WriteByte(sI2C_MASTER_TypeDef *sI2C, uint8_t Data){for(uint8_t i = 0; i < 8; i++){if(Data & 0x80) sI2C_MASTER_SDA_H(sI2C);else sI2C_MASTER_SDA_L(sI2C);Data <<= 1;sI2C_MASTER_SCL_H(sI2C); sI2C_MASTER_Delay(sI2C->TIME);sI2C_MASTER_SCL_L(sI2C); sI2C_MASTER_Delay(sI2C->TIME);}}void sI2C_MASTER_Init(sI2C_MASTER_TypeDef *sI2C){sI2C_MASTER_SDA_SetDirection(sI2C, 0);sI2C_MASTER_SCL_SetDirection(sI2C, 0);sI2C_MASTER_SCL_H(sI2C); sI2C_MASTER_Delay(sI2C->TIME);sI2C_MASTER_SDA_H(sI2C); sI2C_MASTER_Delay(sI2C->TIME);}uint8_t sI2C_MASTER_Read(sI2C_MASTER_TypeDef *sI2C, uint8_t Address, uint8_t *Buffer, uint8_t Length){if(Length == 0) return 0;sI2C_MASTER_GenerateStart(sI2C);sI2C_MASTER_WriteByte(sI2C, sI2C->SlaveAddress);if(sI2C_MASTER_GetACK(sI2C)){sI2C_MASTER_GenerateStop(sI2C); return 1;}sI2C_MASTER_WriteByte(sI2C, Address);if(sI2C_MASTER_GetACK(sI2C)){sI2C_MASTER_GenerateStop(sI2C); return 1;}sI2C_MASTER_GenerateStart(sI2C);sI2C_MASTER_WriteByte(sI2C, sI2C->SlaveAddress + 1);if(sI2C_MASTER_GetACK(sI2C)){sI2C_MASTER_GenerateStop(sI2C); return 1;}while(1){*Buffer++ = sI2C_MASTER_ReadByte(sI2C);if(--Length == 0){sI2C_MASTER_PutACK(sI2C, 1); break;}sI2C_MASTER_PutACK(sI2C, 0);}sI2C_MASTER_GenerateStop(sI2C);return 0;}uint8_t sI2C_MASTER_Write(sI2C_MASTER_TypeDef *sI2C, uint8_t Address, uint8_t *Buffer, uint8_t Length){uint8_t i = 0;if(Length == 0) return 0;sI2C_MASTER_GenerateStart(sI2C);sI2C_MASTER_WriteByte(sI2C, sI2C->SlaveAddress);if(sI2C_MASTER_GetACK(sI2C)){sI2C_MASTER_GenerateStop(sI2C); return 1;}sI2C_MASTER_WriteByte(sI2C, Address);if(sI2C_MASTER_GetACK(sI2C)){sI2C_MASTER_GenerateStop(sI2C); return 1;}for(i = 0; i < Length; i++){sI2C_MASTER_WriteByte(sI2C, *Buffer++);if(sI2C_MASTER_GetACK(sI2C)) break;}sI2C_MASTER_GenerateStop(sI2C);if(i == Length) return 0;else return 1;}void sI2C_MASTER_SHELL_Handler(uint8_t Mode){uint8_t Buffer[10] = {0x11, 0x22, 0x33, 0x44, 0x55, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE};if(Mode == 1){sI2C_MASTER_Write(&sI2C_MASTER, 0x00, Buffer, sizeof(Buffer));}else{sI2C_MASTER_Read(&sI2C_MASTER, 0x00, Buffer, sizeof(Buffer));printf(" sI2C Master Read : ");for(uint8_t i = 0; i < sizeof(Buffer); i++){printf("0x%02x ", Buffer[i]);}printf(" ");}}SHELL_EXPORT_CMD(SI2C_MASTER, sI2C_MASTER_SHELL_Handler, Software I2C Master Read And Write);

?
三、硬件I2C從機通訊
對于硬件I2C從機通訊來說,更多的是采用中斷的響應方式來避免程序在某一處一直等待I2C主機的操作;而輪詢的方式很容易捕捉不到I2C的請求或者事件;所以如下硬件I2C從機通訊的方式使用的就是中斷處理方式,I2C主機任何操作和請求都會映射成對應的中斷,待從機檢測到了之后,進入中斷進行相應的處理,同時中斷的方式也保證了通訊的正常和穩定性。
現在市面上很多MCU的I2C從機模式都支持多地址模式,但每家的IP功能設計都不一樣:有些是直接通過寄存器設置從機地址方式,這種方式限制了所支持從機地址的個數;有些是通過地址掩碼的方式(類似于CAN通訊的ID濾波器),通過逐位比較的方式來判別所支持的I2C從機地址,這種方式可以支持很多個從機地址;第二種方式相比于第一種實現方式更靈活,支持的從機設備地址也更多!
MM32F032不支持多地址從機功能,但MM32F0140支持從機多地址通訊,可以根據實際項目需求選擇對應的芯片型號;從機多地址功能采用的是地址掩碼方式來過濾從機地址的,這樣可以支持更多的從機設備地址;通過設置從機設備地址和從機地址掩碼來實現從機多地址通訊功能;硬件I2C從機通訊具體的代碼實現如下:
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
實測結果如下所示:void hI2C_SLAVE_Init(uint8_t SlaveAddress){I2C_InitTypeDef I2C_InitStructure;GPIO_InitTypeDef GPIO_InitStructure;NVIC_InitTypeDef NVIC_InitStructure;QUEUE_INIT(QUEUE_HI2C_SLAVE_IDX);RCC_APB1PeriphClockCmd(RCC_APB1ENR_I2C1, ENABLE);I2C_StructInit(&I2C_InitStructure);I2C_InitStructure.Mode = I2C_Mode_SLAVE;I2C_InitStructure.OwnAddress = 0;I2C_InitStructure.Speed = I2C_Speed_FAST;I2C_InitStructure.ClockSpeed = 400000;I2C_Init(I2C1, &I2C_InitStructure);I2C_ITConfig(I2C1, I2C_IT_RD_REQ, ENABLE);I2C_ITConfig(I2C1, I2C_IT_RX_FULL, ENABLE);I2C_Cmd(I2C1, ENABLE);RCC_AHBPeriphClockCmd(RCC_AHBENR_GPIOB, ENABLE);GPIO_PinAFConfig(GPIOB, GPIO_PinSource6, GPIO_AF_1);GPIO_PinAFConfig(GPIOB, GPIO_PinSource7, GPIO_AF_1);GPIO_StructInit(&GPIO_InitStructure);GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_FLOATING;GPIO_Init(GPIOB, &GPIO_InitStructure);GPIO_StructInit(&GPIO_InitStructure);GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;GPIO_Init(GPIOB, &GPIO_InitStructure);NVIC_InitStructure.NVIC_IRQChannel = I2C1_IRQn;NVIC_InitStructure.NVIC_IRQChannelPriority = 0;NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;NVIC_Init(&NVIC_InitStructure);I2C_SendSlaveAddress(I2C1, SlaveAddress);}void I2C1_IRQHandler(void){static uint8_t Data = 0;if(I2C_GetITStatus(I2C1, I2C_IT_RD_REQ) != RESET){I2C_ClearITPendingBit(I2C1, I2C_IT_RD_REQ);while(1){I2C_SendData(I2C1, Data++);while(I2C_GetFlagStatus(I2C1, I2C_FLAG_TX_EMPTY) == RESET);if((Data % 10) == 0){I2C_GenerateSTOP(I2C1, ENABLE); break;}}}if(I2C_GetITStatus(I2C1, I2C_IT_RX_FULL) != RESET){QUEUE_WRITE(QUEUE_HI2C_SLAVE_IDX, I2C_ReceiveData(I2C1));}}

?
四、軟件模擬I2C從機通訊
軟件模擬I2C從機通訊是I2C通訊時序逆向的實現過程,它需要通過捕捉I2C主機的信號時序對主機的事件、請求,以及發送過來的數據進行解析,又要正確的回復I2C主機,所以它的實現方式比I2C模擬主機完全不同。這需要開發者對I2C時序十分熟悉,所以在研讀下面軟件模擬I2C從機通訊程序時,建議對照I2C時序一點點分析(提示:這部分內容有點難度)。
對于軟件模擬I2C從機通訊的實現是通過兩個GPIO端口引腳分別與I2C主機的SCL和SDA進行連接,程序中將這兩個GPIO端口引腳配置成外部中斷EXTI工作模式,通過捕獲GPIO端口引腳的上升沿、下降沿,以及高低電平狀態,配合軟件模擬I2C從機的狀態管理,實現與I2C主機之間的通訊功能,在如下的程序中添加了詳細的注釋和說明,方便大家閱讀和理解,具體的代碼實現如下:
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
實測結果如下所示:typedef struct{uint32_t SCL_RCC;GPIO_TypeDef *SCL_GPIO;uint16_t SCL_PIN;uint8_t SCL_EXTI_PortSource;uint8_t SCL_EXTI_PinSource;uint32_t SCL_EXTI_Line;uint32_t SDA_RCC;GPIO_TypeDef *SDA_GPIO;uint16_t SDA_PIN;uint8_t SDA_EXTI_PortSource;uint8_t SDA_EXTI_PinSource;uint32_t SDA_EXTI_Line;uint8_t SlaveAddress;} sI2C_SLAVE_TypeDef;sI2C_SLAVE_TypeDef sI2C_SLAVE ={RCC_AHBENR_GPIOB, GPIOB, GPIO_Pin_6, EXTI_PortSourceGPIOB, EXTI_PinSource6, EXTI_Line6,RCC_AHBENR_GPIOB, GPIOB, GPIO_Pin_7, EXTI_PortSourceGPIOB, EXTI_PinSource7, EXTI_Line7,0xA0,};#define sI2C_SLAVE_STATE_NA 0#define sI2C_SLAVE_STATE_STA 1#define sI2C_SLAVE_STATE_ADD 2#define sI2C_SLAVE_STATE_ADD_ACK 3#define sI2C_SLAVE_STATE_DAT 4#define sI2C_SLAVE_STATE_DAT_ACK 5#define sI2C_SLAVE_STATE_STO 6uint8_t sI2C_SLAVE_State = sI2C_SLAVE_STATE_NA;uint8_t sI2C_SLAVE_ShiftCounter = 0;uint8_t sI2C_SLAVE_SlaveAddress = 0;uint8_t sI2C_SLAVE_ReceivedData = 0;uint8_t sI2C_SLAVE_TransmitData = 0x50;uint8_t sI2C_SLAVE_TransmitBuffer[16] ={0x01, 0x12, 0x23, 0x34, 0x45, 0x56, 0x67, 0x78,0x89, 0x9A, 0xAB, 0xBC, 0xCD, 0xDE, 0xEF, 0xF0,};uint8_t sI2C_SLAVE_TransmitIndex = 0;bool sI2C_SLAVE_READ_SCL(sI2C_SLAVE_TypeDef *sI2C){return GPIO_ReadInputDataBit(sI2C->SCL_GPIO, sI2C->SCL_PIN);}bool sI2C_SLAVE_READ_SDA(sI2C_SLAVE_TypeDef *sI2C){return GPIO_ReadInputDataBit(sI2C->SDA_GPIO, sI2C->SDA_PIN);}/******************************************************************************** [url=home.php?mod=space&uid=247401]@brief[/url] 配置模擬I2C的GPIO端口, 默認設置成輸入模式, 并使能相應的外部觸發* 中斷功能(上升沿和下降沿)* @param* @retval* [url=home.php?mod=space&uid=93590]@Attention[/url]*******************************************************************************/void sI2C_SLAVE_Init(sI2C_SLAVE_TypeDef *sI2C){GPIO_InitTypeDef GPIO_InitStructure;EXTI_InitTypeDef EXTI_InitStructure;NVIC_InitTypeDef NVIC_InitStructure;RCC_AHBPeriphClockCmd(sI2C->SCL_RCC, ENABLE);RCC_AHBPeriphClockCmd(sI2C->SDA_RCC, ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE);GPIO_StructInit(&GPIO_InitStructure);GPIO_InitStructure.GPIO_Pin = sI2C->SCL_PIN;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;GPIO_Init(sI2C->SCL_GPIO, &GPIO_InitStructure);GPIO_StructInit(&GPIO_InitStructure);GPIO_InitStructure.GPIO_Pin = sI2C->SDA_PIN;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;GPIO_Init(sI2C->SDA_GPIO, &GPIO_InitStructure);SYSCFG_EXTILineConfig(sI2C->SCL_EXTI_PortSource, sI2C->SCL_EXTI_PinSource);EXTI_StructInit(&EXTI_InitStructure);EXTI_InitStructure.EXTI_Line = sI2C->SCL_EXTI_Line;EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising_Falling;EXTI_InitStructure.EXTI_LineCmd = ENABLE;EXTI_Init(&EXTI_InitStructure);SYSCFG_EXTILineConfig(sI2C->SDA_EXTI_PortSource, sI2C->SDA_EXTI_PinSource);EXTI_StructInit(&EXTI_InitStructure);EXTI_InitStructure.EXTI_Line = sI2C->SDA_EXTI_Line;EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising_Falling;EXTI_InitStructure.EXTI_LineCmd = ENABLE;EXTI_Init(&EXTI_InitStructure);NVIC_InitStructure.NVIC_IRQChannel = EXTI4_15_IRQn;NVIC_InitStructure.NVIC_IRQChannelPriority = 0x00;NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;NVIC_Init(&NVIC_InitStructure);}/******************************************************************************** [url=home.php?mod=space&uid=247401]@brief[/url] 設置SDA信號線的輸入輸出方便, 0代表Output輸出, 1代表Input輸入* @param* @retval* [url=home.php?mod=space&uid=93590]@Attention[/url]*******************************************************************************/void sI2C_SLAVE_SDA_SetDirection(sI2C_SLAVE_TypeDef *sI2C, uint8_t Direction){GPIO_InitTypeDef GPIO_InitStructure;if(Direction) /* Input */{GPIO_StructInit(&GPIO_InitStructure);GPIO_InitStructure.GPIO_Pin = sI2C->SDA_PIN;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;GPIO_Init(sI2C->SDA_GPIO, &GPIO_InitStructure);}else /* Output */{GPIO_StructInit(&GPIO_InitStructure);GPIO_InitStructure.GPIO_Pin = sI2C->SDA_PIN;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;GPIO_Init(sI2C->SDA_GPIO, &GPIO_InitStructure);}}/******************************************************************************* @brief 設置SDA信號線的輸出電平(高電平 / 低電平)* @param* @retval* @attention******************************************************************************/void sI2C_SLAVE_SDA_SetLevel(sI2C_SLAVE_TypeDef *sI2C, uint8_t Level){sI2C_SLAVE_SDA_SetDirection(sI2C, 0);if(Level){GPIO_WriteBit(sI2C->SDA_GPIO, sI2C->SDA_PIN, Bit_SET);}else{GPIO_WriteBit(sI2C->SDA_GPIO, sI2C->SDA_PIN, Bit_RESET);}}/******************************************************************************* @brief 當SCL觸發上升沿外部中斷時的處理* @param* @retval* @attention******************************************************************************/void sI2C_SLAVE_SCL_RiseHandler(sI2C_SLAVE_TypeDef *sI2C){/* SCL為上升沿, 數據鎖定, 主從機從SDA總線上獲取數據位 */switch(sI2C_SLAVE_State){case sI2C_SLAVE_STATE_ADD:/* I2C發送遵義MSB, 先發送高位, 再發送低位, 所以在接收的時候, 數據進行左移 */sI2C_SLAVE_SlaveAddress <<= 1;sI2C_SLAVE_ShiftCounter += 1;if(sI2C_SLAVE_READ_SDA(sI2C) == Bit_SET){sI2C_SLAVE_SlaveAddress |= 0x01;}/* 當接收到8位地址位后, 從機需要在第9個時鐘給出ACK應答, 等待SCL下降沿的時候給出ACK信號 */if(sI2C_SLAVE_ShiftCounter == 8){sI2C_SLAVE_State = sI2C_SLAVE_STATE_ADD_ACK;}break;case sI2C_SLAVE_STATE_ADD_ACK:/* 從機地址的ACK回復后, 切換到收發數據狀態 */sI2C_SLAVE_State = sI2C_SLAVE_STATE_DAT;sI2C_SLAVE_ShiftCounter = 0; /* 數據移位計數器清零 */sI2C_SLAVE_ReceivedData = 0; /* sI2C_SLAVE的接收數據清零 */break;case sI2C_SLAVE_STATE_DAT:if((sI2C_SLAVE_SlaveAddress & 0x01) == 0x00){/* 主機寫操作:此時從機應該獲取主機發送的SDA信號線電平狀態, 進行位存儲 */sI2C_SLAVE_ReceivedData <<= 1;sI2C_SLAVE_ShiftCounter += 1;if(sI2C_SLAVE_READ_SDA(sI2C) == Bit_SET){sI2C_SLAVE_ReceivedData |= 0x01;}/* 當收到一個完整的8位數據時, 將收到的數據存放到I2C接收消息隊列中, 狀態轉換到給主機發送ACK應答 */if(sI2C_SLAVE_ShiftCounter == 8){QUEUE_WRITE(QUEUE_SI2C_SLAVE_IDX, sI2C_SLAVE_ReceivedData);sI2C_SLAVE_ShiftCounter = 0; /* 數據移位計數器清零 */sI2C_SLAVE_ReceivedData = 0; /* sI2C_SLAVE的接收數據清零 */sI2C_SLAVE_State = sI2C_SLAVE_STATE_DAT_ACK;}}else{/* 主機讀操作:在SCL上升沿的時候, 主機獲取當前SDA的狀態位, 如果到了第8個數位的上升沿,* 那接下來就是主機回復從機的應答或非應答信號了, 所以將狀態切換到等待ACK的狀態, 同時準備下一個需要發送的數據*/if(sI2C_SLAVE_ShiftCounter == 8){sI2C_SLAVE_ShiftCounter = 0; /* sI2C_SLAVE的接收數據清零 */sI2C_SLAVE_TransmitData = sI2C_SLAVE_TransmitBuffer[sI2C_SLAVE_TransmitIndex++];sI2C_SLAVE_TransmitIndex %= 16;sI2C_SLAVE_State = sI2C_SLAVE_STATE_DAT_ACK;}}break;case sI2C_SLAVE_STATE_DAT_ACK:if((sI2C_SLAVE_SlaveAddress & 0x01) == 0x00){/* 主機寫操作:從機發送ACK, 等待主機讀取從機發送的ACK信號 */sI2C_SLAVE_State = sI2C_SLAVE_STATE_DAT; /* 狀態切換到數據接收狀態 */}else{/* 主機讀操作:主機發送ACK, 從機可以讀取主機發送的ACK信號 */uint8_t ack = sI2C_SLAVE_READ_SDA(sI2C);if(ack == Bit_RESET){sI2C_SLAVE_State = sI2C_SLAVE_STATE_DAT; /* 接收到 ACK, 繼續發送數據 */}else{sI2C_SLAVE_State = sI2C_SLAVE_STATE_STO; /* 接收到NACK, 停止發送數據 */}}break;default:break;}}/******************************************************************************* @brief 當SCL觸發下降沿外部中斷時的處理* @param* @retval* @attention******************************************************************************/void sI2C_SLAVE_SCL_FallHandler(sI2C_SLAVE_TypeDef *sI2C){/* SCL為下降沿, 數據可變 */switch(sI2C_SLAVE_State){case sI2C_SLAVE_STATE_STA:/** 檢測到START信號后, SCL第一個下降沿表示開始傳輸Slave Address,* 根據數據有效性的規則, 地址的第一位需要等到SCL變為高電平時才可以讀取* 切換到獲取Slave Address的狀態, 等待SCL的上升沿觸發*/sI2C_SLAVE_State = sI2C_SLAVE_STATE_ADD;sI2C_SLAVE_ShiftCounter = 0; /* 數據移位計數器清零 */sI2C_SLAVE_SlaveAddress = 0; /* sI2C_SLAVE的從機地址清零 */sI2C_SLAVE_ReceivedData = 0; /* sI2C_SLAVE的接收數據清零 */break;case sI2C_SLAVE_STATE_ADD:/** 在主機發送Slave Address的時候, 從機只是讀取SDA狀態, 進行地址解析, 所以這邊沒有處理*/break;case sI2C_SLAVE_STATE_ADD_ACK:/* SCL低電平的時候, 給I2C總線發送地址的應答信號, 狀態不發生改變, 等待下一個上升沿將ACK發送出去 */sI2C_SLAVE_SDA_SetLevel(sI2C, 0); /* 將SDA信號拉低, 向主機發送ACK信號 */break;case sI2C_SLAVE_STATE_DAT:/* 在SCL時鐘信號的下降沿, SDA信號線處理可變的狀態 */if((sI2C_SLAVE_SlaveAddress & 0x01) == 0x00){/* 主機寫操作:將SDA信號線設置成獲取狀態, 等待下一個SCL上升沿時獲取數據位 */sI2C_SLAVE_SDA_SetDirection(sI2C, 1);}else{/* 主機讀操作:根據發送的數據位設置SDA信號線的輸出電平, 等待下一個SCL上升沿時發送數據位 */if(sI2C_SLAVE_TransmitData & 0x80){sI2C_SLAVE_SDA_SetLevel(sI2C, 1);}else{sI2C_SLAVE_SDA_SetLevel(sI2C, 0);}sI2C_SLAVE_TransmitData <<= 1;sI2C_SLAVE_ShiftCounter += 1;}break;case sI2C_SLAVE_STATE_DAT_ACK:/* 在第8個SCL時鐘信號下降沿的處理 */if((sI2C_SLAVE_SlaveAddress & 0x01) == 0x00){/* 主機寫操作:從機在接收到數據后, 需要給主機一個ACK應答信號, 狀態不發生改變, 等待下一個上升沿將ACK發送出去 */sI2C_SLAVE_SDA_SetLevel(sI2C, 0); /* 將SDA信號拉低, 向主機發送ACK信號 */}else{/* 主機讀操作:從機需要釋放當前的SDA信號線, 以便主機發送ACK或NACK給從機, 狀態不發生改變, 等待下一個上升沿讀取ACK信號 */sI2C_SLAVE_SDA_SetDirection(sI2C, 1);}break;default:break;}}/*** @brief 當SDA觸發上升沿外部中斷時的處理* @param None* @retval None*/void sI2C_SLAVE_SDA_RiseHandler(sI2C_SLAVE_TypeDef *sI2C){if(sI2C_SLAVE_READ_SCL(sI2C) == Bit_SET) /* SCL為高時,SDA為上升沿:STOP */{sI2C_SLAVE_State = sI2C_SLAVE_STATE_STO;}else /* SCL為低時,SDA為上升沿:數據的變化 */{}}/*** @brief 當SDA觸發下降沿外部中斷時的處理* @param None* @retval None*/void sI2C_SLAVE_SDA_FallHandler(sI2C_SLAVE_TypeDef *sI2C){if(sI2C_SLAVE_READ_SCL(sI2C) == Bit_SET) /* SCL為高時,SDA為下降沿:START */{sI2C_SLAVE_State = sI2C_SLAVE_STATE_STA;}else /* SCL為低時,SDA為下降沿:數據的變化 */{}}/******************************************************************************** @brief* @param* @retval* @attention*******************************************************************************/void EXTI4_15_IRQHandler(void){/* I2C SCL */if(EXTI_GetITStatus(sI2C_SLAVE.SCL_EXTI_Line) != RESET){if(sI2C_SLAVE_READ_SCL(&sI2C_SLAVE) == Bit_SET){sI2C_SLAVE_SCL_RiseHandler(&sI2C_SLAVE);}else{sI2C_SLAVE_SCL_FallHandler(&sI2C_SLAVE);}EXTI_ClearITPendingBit(sI2C_SLAVE.SCL_EXTI_Line);}/* I2C SDA */if(EXTI_GetITStatus(sI2C_SLAVE.SDA_EXTI_Line) != RESET){if(sI2C_SLAVE_READ_SDA(&sI2C_SLAVE) == Bit_SET){sI2C_SLAVE_SDA_RiseHandler(&sI2C_SLAVE);}else{sI2C_SLAVE_SDA_FallHandler(&sI2C_SLAVE);}EXTI_ClearITPendingBit(sI2C_SLAVE.SDA_EXTI_Line);}}

電子發燒友App































評論