一.平臺
芯片:STM8S103F3P6
環境:IAR + STVP
系統:WIN7
二. 目的
STM8S103F3P6:使用STM8標準庫開發
角色------從機
方式----------硬件IIC
STM32H7:
角色------主機
方式----------IO口模擬IIC主機
主機發送命令包,從機接收後進行判斷
主機發送讀取命令,從機返回上次命令包判斷後要返回的數據包
三.STM8硬件IIC
STM8S103時鐘
由於該芯片實際應用是放到控制板作爲一個附屬芯片,不考慮功耗、儘可能採用高的頻率,且需要滿足硬件I2C對時鐘的需求(後續講到),由STM8S103參考手冊時鐘章節可看出,接外部高速時鐘最高是16M,內部RC最高16M,因此時鐘最高是16M。測試時使用的是內部RC振盪器時鐘。
由STM8S系列參考手冊時鐘章節,時鐘樹上無對時鐘進行倍頻的單元,因此HSE或者HSI都是16M時鐘輸入,如果不進行分頻,那麼同樣對CPU時鐘而言都是16M。爲了方便 (懶。。。),在初步測試時使用的是內部RC振盪器16M。
硬件IIC
從STM8S103數據手冊上看I2C有兩種支持速率:最高到100K和400K
使用的是PB5複用I2C_SDA,PB4複用I2C_SCL
需要注意的是:選項字節中的OPT2中的AFR4需要是0,如果不是0則PB4和PB5不是複用爲I2C引腳
另外,需要注意的是,由下圖看:
I2C在100K和400K速率下對時鐘要求不同,且。。且。。。且主機用的是IO口模擬IIC,必須符合相應的最短電平持續時間
還有一點:如果使用400K速度,那麼主時鐘至少是8M
硬件IIC庫
從IIC庫的相關文件上開放了一些接口,我僅用到了兩個,就只說這兩個
I2C_Init
void I2C_Init(uint32_t OutputClockFrequencyHz, uint16_t OwnAddress,
I2C_DutyCycle_TypeDef I2C_DutyCycle, I2C_Ack_TypeDef Ack,
I2C_AddMode_TypeDef AddMode, uint8_t InputClockFrequencyMHz )
功能:I2C硬件初始化
參數:OutputClockFrequencyHz ----- 輸出時鐘頻率
OwnAddress-------本I2C設備地址
I2C_DutyCycle-----佔空比,高低電平的時間,和速率有關,詳情看手冊CCR寄存器
Ack---------應答模式,接收一個字節數據後是否產生應答
AddMode----採用7/10位地址設置
InputClockFrequencyMHz----提供給I2C硬件時鐘
I2C_ITConfig
void I2C_ITConfig(I2C_IT_TypeDef I2C_IT, FunctionalState NewState)
功能:I2C的中斷配置
參數:I2C_IT ----- 中斷類型,如接收到本機地址則產生中斷
NewState----中斷使能/關閉
四.代碼
代碼利用測試板和測試過,跑的是100K的速率,能正常運行
-
CPU時鐘:分頻配置爲不分頻,16M
CLK_HSIPrescalerConfig(CLK_PRESCALER_HSIDIV1);
-
I2C硬件初始化
u32 CPU_CLK;// CPU時鐘 // GPIOB4作爲上拉無中斷輸入,GPIOB5高阻態快速輸出(10M大小) GPIO_Init(GPIOB,GPIO_PIN_4, GPIO_MODE_IN_PU_NO_IT); GPIO_Init(GPIOB,GPIO_PIN_5, GPIO_MODE_OUT_OD_HIZ_FAST); // 獲取當前CPU時鐘,M爲單位 CPU_CLK = CLK_GetClockFreq()/1000000; /* I2C初始化: 不需輸出時鐘,隨便寫個400000,設備地址0XA0,實際上最後一位是讀寫控制位 每接收一個字節迴應答,7位地址模式,輸入I2C時鐘是上面的CPU_CLK */ I2C_Init(400000, 0XA0 , I2C_DUTYCYCLE_2, I2C_ACK_CURR, I2C_ADDMODE_7BIT, CPU_CLK);//I2C初始化 /* I2C中斷配置: 開啓 錯誤中斷、事件中斷(如匹配了地址、接收了一字節數據等)、BUF中斷(接收和發送相關) */ I2C_ITConfig((I2C_IT_TypeDef)(I2C_IT_ERR | I2C_IT_EVT | I2C_IT_BUF), ENABLE) ;
-
開總中斷
__enable_interrupt();
-
I2C中斷函數
在寫中斷函數之前,先看一下參考手冊關於I2C硬件的部分功能描述
I2C接口在接收到地址並且匹配上時,根據ACK可以自動產生一個應答信號,且會將ADDR標誌寫1,產生一箇中斷
總結:I2C設備接收到別人發過來的地址,匹配上時,可以自動應答,並且產生中斷。
但是,如何對本設備進行讀和寫的區分呢?因爲本次我們配置的是7位地址模式,上面配置本機地址爲0XA0,按照最後一位是0還是1,I2C硬件自動識別是讀還是寫的動作。如果起始信號後,主機發送0XA0,那麼主機對從機進行寫操作;如果起始信號後,主機發送0XA1,那麼主機對從機進行讀操作。
從設備接收模式(主機發送、從機接收,因此DR寄存器用來接收數據)
可以得出信息:
.接收到地址後,要清除ADDR標誌
.在清除標誌後,纔會從移位寄存器的數據放到DR寄存器上(因此,接收時要及時清除ADDR標誌)
.每接收一個字節數據可以自動應答
.接收一個數據後,RxNE位置1,可產生中斷
注意:在接收數據後,DR寄存器沒被讀出時,I2C接口SCL是一直保持爲低電平的,這個時候,主機進行發送是沒效果的。因此要在中斷中及時讀取走DR寄存器
EV1是在地址應答階段後,解釋說明指出要清除ADDR,通過讀SR1和SR3
EV2是每接收到一個數據,要通過讀DR來清除,如果不讀則出現上述情況
EV4是接收到停止信號後,通過讀SR1,寫CR2寄存器清除標誌
從設備發送模式(主機接收、從機發送,因此DR寄存器用來發送數據)
可以得出信息:
.在接收地址後並清除了ADDR標誌位後,從設備纔會將DR寄存器發出
.如果ADDR標誌不清除、DR寄存器沒及時寫入,SCL會一直被拉低,主機操作無效果(特別重要,清除標誌,對DR寄存器寫操作越快越往前越好)
.如果發送完成,可以產生中斷
請注意地址應答後的階段是 EV1/EV3-1/EV3,這個時候就是需要及時清標誌位和寫DR寄存器進行發送。如果不呢?
像我當時在中斷中還執行了一些語句,並沒有把寫DR寄存器放到靠前位置,也有一些多餘未優化的語句,導致從邏輯分析儀的圖形中可以明顯看出SCL被一直拉低了,其實我在主機IO模擬中,應答動作後SCL拉低沒有那麼長的延時的,而是很規律的時鐘。
狀態寄存器
SR1:
TXE-------發送狀態,如果DR寄存器沒有發送,那麼置1
RXNE----接收到數據,置1
STOPF–接收到停止信號,置1
BTF-----接收到數據/發送數據完成,但還沒讀取/再次寫入
ADDR–匹配到設備地址
SR2:一些錯誤標誌位
SR3:
TRA:處於接收還是發送的狀態
代碼:由於一些原因,將I2C的數據包格式協議這些用一些變量和數組代替,有需要用的記得根據自己的需求改動
// 測試相關
u8 Test_read[50] = “HELLO ,THANK U,THANK U VERY MUCH”;
u8 Test_r_num = 31; //從機發送個數
u8 Test_r_pos = 0; // 發送指針
u8 Test_Write[50] = {0};
u8 Test_w_num = 0; // 主機發送數據個數
u8 Commucation_flag = 0;// 通信完成標誌
/**
* @brief I2C Interrupt routine.
* @param None
* @retval None
*/
INTERRUPT_HANDLER(I2C_IRQHandler, 19)
//void IIC_Interrupt(void)
{
/* In order to detect unexpected events during development,
it is recommended to set a breakpoint on the following instruction.
*/
//接收發送
if (I2C->SR1&0x02)//地址已經匹配 ADDR標誌
{
// 清ADDR標誌:讀SR1,讀SR3
// 主機讀,從機發出的情況,DR寄存器寫數據越快越好
// 判斷是否是讀操作 TRA
if ((I2C->SR3)&0x04)
{
if (Test_r_pos < Test_r_num)
{
I2C->DR = Test_read[Test_r_pos ];
Test_r_pos ++;
}
else
I2C->DR = 0XFF;
}
else
{
Test_r_pos=0;
}
Test_w_num= 0;
}
else if ((I2C->SR3)&0x04)//如果是接收狀態:
{
// 查看TXE是否DR爲空
if ((((I2C->SR1)&0x80) == 0X80))
{
if (Test_r_pos < Test_r_num)
{
I2C->DR = Test_read[Test_r_pos ];
Test_r_pos ++;
}
else
I2C->DR = 0XFF;
}
}
else if (((I2C->SR1)&0x40)&&(!((I2C->SR3)&0x04)))
{
// 讀DR,清接收標誌
u8 data = I2C->DR;
// 如果主機發送,從機接收到數據
if (Test_w_num < 50)
Test_Write[Test_w_num ++] = data ;
}
else if ((I2C->SR1)&0x10)
{
//檢測到停止位----清除停止位---通過寫CR2
I2C->CR2 = I2C->CR2;
// 停止標誌位置
Test_r_pos = 0;
Commucation_flag = 1;
}
// 錯誤管理
if ((I2C->SR2)&0X0F)
{
I2C->SR2 &= ~(0X0F); // 清零
}
}