STM8硬件IIC從機

一.平臺

芯片: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的速率,能正常運行

  1. CPU時鐘:分頻配置爲不分頻,16M

     	CLK_HSIPrescalerConfig(CLK_PRESCALER_HSIDIV1);
    
  2. 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) ;  
    
  3. 開總中斷

    __enable_interrupt();

  4. 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); // 清零
      }
  
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章