又花了好幾天的時間調I2C,前前後後出現了很多問題,有一些是不仔細的問題,有一些是對於I2C協議不夠了解,手冊看的不夠認真,總之繼續學習,這裏將遇到的問題記錄下,以便日後查閱
錯誤一:HardFault硬件錯誤,邏輯分析儀上顯示數據發送到一半就中斷了,但是總線上並沒有檢測到停止信號
原因:這個錯誤我犯了兩次了,亂用指針,直接定義uint8_t * Rxbuffer來接收I2C數據,也沒有指定它的大小,造成野指針以及數組越界的問題
錯誤二:硬件IO口上沒有數據輸出,檢測到總線一直處於busy狀態
原因:I2C的IO口的複用功能沒有配置正確,PB6和PB7應該對應的是GPIO_AF_1而不是GPIO_AF_2(看手冊不夠仔細)
錯誤三:主機循環發送數據,但從機中斷接收完一次數據後就不繼續接收
解決辦法:從機中斷中檢測到StopF信號後重新使能一次I2C,I2C_Cmd(I2C1,ENABLE)
錯誤四:主機循環查詢接收數據,但從機中斷髮送發送完一次數據後不繼續發送
原因:
在103的中文參考手冊p511有關於STOPF的描述如下:
STOPF:停止條件檢測位(從模式) (Stop detection (slave mode))
0:沒有檢測到停止條件;
1:檢測到停止條件。
– 在一個應答之後(如果ACK=1),當從設備在總線上檢測到停止條件時,硬件將該位置’1’。
– 軟件讀取SR1寄存器後,對CR1寄存器的寫操作將清除該位,或當PE=0時,硬件清除該位。
注:在收到NACK後,STOPF位不被置位。
最關鍵的是這個注,我們知道主機接收數據時,每接收到一個字節都會發回一個ACK信號(前提是主機配置要使能Ack_Enable),而當發送完最後一個字節時,主機這時發送的NACK。也就是說從機如果使能了AF中斷,當監測到總線上有NACK信號時就會進入中斷,而之後的Stop信號就算從機檢測到了也不會產生置位Stopf不會進入相應的中斷。
而且,AF中斷是屬於I2C1_ER_IRQHandler這個中斷通道,在EV_IRQHandler中是檢測不到的,並且千萬記得要開啓I2C_IT_ERR中斷以及配置它的NVIC,不然中斷也不會響應
解決:開啓I2C_IT_ERR中斷並配置它的NVIC通道,在I2C1_ER_IRQHandler中處理AF中斷,清除AF標誌
代碼整理
STM32F070RB I2C硬件配置
I2C_DeInit(I2C1);
RCC_I2CCLKConfig(RCC_I2C1CLK_SYSCLK);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE);
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOB, ENABLE);
/********I2C1 GPIO_Pins configuration***********/
GPIO_PinAFConfig(GPIOB,GPIO_PinSource6,GPIO_AF_1);
GPIO_PinAFConfig(GPIOB,GPIO_PinSource7,GPIO_AF_1);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7; //PB6 - SCL PB7 - SDA
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_OType = GPIO_OType_OD;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_Init(GPIOB,&GPIO_InitStructure);
/********I2C1 InitStruct configuration***********/
I2C_InitStruct.I2C_Mode = I2C_Mode_I2C;
I2C_InitStruct.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
I2C_InitStruct.I2C_AnalogFilter = I2C_AnalogFilter_Enable;
I2C_InitStruct.I2C_DigitalFilter = 0x00;
I2C_InitStruct.I2C_Ack = I2C_Ack_Enable;
/*ÉèÖÃI2Cʱ¼ä¼Ä´æÆ÷Öµ,Ò²ÅäÖÃÁËI2C_Speed_Frequency ²Î¿¼ÅäÖù¤¾ßI2C_Timing_Config_Tool*/
I2C_InitStruct.I2C_Timing = I2C_TIMING;
I2C_InitStruct.I2C_OwnAddress1 = 0x00;
I2C_Init(I2C1,&I2C_InitStruct);
//I2C_ClearFlag(I2C1,I2C_FLAG_BUSY);
I2C_Cmd(I2C1,ENABLE);
I2C主機查詢發送數據
/******************************************************************
* Function Name : I2C_Write
* Description : Write more than one byte to the Device with a
* single WRITE cycle
* Input : - DevAddr : Device Address
- RegAddr : Register Address
- pBuffer : pointer to the buffer containing the
data to be written to the Device
- NumByteToWrite : number of bytes to write
* Return : I2C_Status
******************************************************************/
I2C_Status I2C_Write(uint8_t DevAddr, uint8_t* pBuffer, uint8_t NumByteToWrite)
{
uint16_t timeout = I2C_TIMEOUT;
/* while the bus is busy */
while(I2C_GetFlagStatus(I2C1,I2C_FLAG_BUSY) != RESET)
{
if((timeout--) == 0)
{
return I2C_FAIL;
}
}
I2C_TransferHandling(I2C1,DevAddr,NumByteToWrite ,I2C_AutoEnd_Mode,I2C_Generate_Start_Write);
for(write_Num = 0;write_Num < NumByteToWrite;write_Num++)
{
I2C_SendData(I2C1,pBuffer[write_Num]);
timeout = I2C_TIMEOUT;
while(I2C_GetFlagStatus(I2C1,I2C_FLAG_TXE) == RESET)
{
if((timeout--) == 0)
{
return1++;
return I2C_FAIL;
}
}
}
timeout = I2C_TIMEOUT;
while(I2C_GetFlagStatus(I2C1,I2C_FLAG_STOPF) == RESET)
{
if((timeout--) == 0)
{
return2++;
return I2C_FAIL;
}
}
return I2C_OK;
}
STM32F103的從機中斷接收
void IIC_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
I2C_InitTypeDef I2C_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1,ENABLE);
/******** I2C1 GPIO_Pins Configuration***********/
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
/******** I2C1 NVIC Configuration ***************/
NVIC_InitStructure.NVIC_IRQChannel = I2C1_EV_IRQn; //I2C Event IRQ
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
/********** I2C1 initial Configuration***********/
I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;
I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;
I2C_InitStructure.I2C_OwnAddress1 = SLAVE_ADDRESS;
I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;
I2C_InitStructure.I2C_ClockSpeed = 100000;
I2C_Init(I2C1,&I2C_InitStructure);
I2C_ITConfig(I2C1,I2C_IT_EVT | I2C_IT_BUF | I2C_IT_ERR,ENABLE);
I2C_Cmd(I2C1,ENABLE);
}
void I2C1_EV_IRQHandler(void)
{
switch(I2C_GetLastEvent(I2C1))
{
case I2C_EVENT_SLAVE_RECEIVER_ADDRESS_MATCHED:
break;
/******* Slave receive mode *************/
case I2C_EVENT_SLAVE_BYTE_RECEIVED:
read_buffer[Rx++] = I2C_ReceiveData(I2C1);
break;
case I2C_EVENT_SLAVE_STOP_DETECTED:
Rx = 0;
I2C_Cmd(I2C1,ENABLE); //持續接收改成 I2C_Cmd(I2C1,ENABLE)
break;
}
}
主機中斷髮送
I2C_Status I2C_INT_Write(uint8_t DevAddr, uint8_t* pBuffer, uint8_t NumByteToWrite)
{
uint16_t timeout = I2C_TIMEOUT;
uint8_t write_Num = 0;
Txptr = pBuffer;
/* while the bus is busy */
while(I2C_GetFlagStatus(I2C1,I2C_FLAG_BUSY) != RESET)
{
if((timeout--) == 0)
{
return I2C_FAIL;
}
}
I2C_TransferHandling(I2C1,DevAddr,NumByteToWrite,I2C_AutoEnd_Mode,I2C_Generate_Start_Write);
I2C_ITConfig(I2C1,I2C_IT_TXI,ENABLE);
return I2C_OK;
}
主機查詢接收
I2C_Status I2C_Read(uint8_t DevAddr,uint8_t* pBuffer, uint8_t NumByteToRead)
{
uint16_t timeout = I2C_TIMEOUT;
/* while the bus is busy */
while(I2C_GetFlagStatus(I2C1,I2C_FLAG_BUSY) != RESET)
{
if((timeout--) == 0)
return I2C_FAIL;
}
/* Send Device address for read */
I2C_TransferHandling(I2C1,DevAddr,NumByteToRead,I2C_AutoEnd_Mode,I2C_Generate_Start_Read);
for(uint8_t read_Num = 0;read_Num < NumByteToRead;read_Num++)
{
timeout = I2C_TIMEOUT;
while(I2C_GetFlagStatus(I2C1,I2C_FLAG_RXNE) == RESET)
{
if((timeout--) == 0)
return I2C_FAIL;
}
pBuffer[read_Num] = I2C_ReceiveData(I2C1);
}
timeout = I2C_TIMEOUT;
while(I2C_GetFlagStatus(I2C1,I2C_FLAG_STOPF) == RESET)
{
if((timeout--) == 0)
{
return I2C_FAIL;
}
}
return I2C_OK;
}
從機中斷髮送
void I2C1_EV_IRQHandler(void)
{
switch(I2C_GetLastEvent(I2C1))
{
case I2C_EVENT_SLAVE_RECEIVER_ADDRESS_MATCHED:
case I2C_EVENT_SLAVE_TRANSMITTER_ADDRESS_MATCHED:
I2C_ClearITPendingBit(I2C1,I2C_IT_ADDR);
break;
/******* Slave transmit mode ************/
case I2C_EVENT_SLAVE_BYTE_TRANSMITTED:
case I2C_EVENT_SLAVE_BYTE_TRANSMITTING:
I2C_SendData(I2C1,write_buffer[Tx++]);
break;
/******* Slave receive mode *************/
case I2C_EVENT_SLAVE_BYTE_RECEIVED:
read_buffer[Rx++] = I2C_ReceiveData(I2C1);
break;
case I2C_EVENT_SLAVE_STOP_DETECTED:
Rx = 0;
I2C_Cmd(I2C1,DISABLE);
break;
}
}
void I2C1_ER_IRQHandler(void)
{
switch(I2C_GetLastEvent(I2C1))
{
case I2C_EVENT_SLAVE_ACK_FAILURE:
Tx = 0;
I2C_ClearITPendingBit(I2C1,I2C_IT_AF);
}
}
補充:
1、關於工作模式的切換
硬件I2C默認工作在Slave模式,當其產生一個Start信號時自動切換爲Master模式
而當仲裁丟失或者產生一個Stop信號時則自動由Slave模式切換爲Master模式
2、數據傳輸
在Master模式下,數據的傳輸在產生Start信號後開始,產生Stop信號後結束,而無論是Start信號或是Stop信號,都由軟件產生。
Start信號後緊跟的第一個字節(8 bit)包含着Slave的地址和讀寫位(0 write / 1 read)
3、7 bit地址和10 bit地址
7 bit地址模式下,Start信號後一個字節表示從機地址和讀寫位 Slave address + W/R );10 bit地址模式下,Start信號後的兩個字節表示從機地址和讀寫位(W/R + 1111 0 + Slave address)
4、關於I2C的幾個模式解析:
I2C_AutoEnd_Mode —— 這種模式下,I2C傳輸完NBytes個字節後會自動產生停止信號以終止信號的傳輸,所以這個模式下的設備肯定是主機模式
I2C_Reload_Mode —— 自動裝載模式,在這種模式下傳輸完NBytes個字節後,I2C的TXR又會重新裝載需要傳輸的數據,每傳輸完NBytes個字節後TCR標誌位會置位