STM8S GPIO模擬I2C的底層代碼

此GPIO模擬I2C代碼已在STM8S103K3上測試通過,測試所用下位機爲SHT20溫溼度傳感器。SHT20測量代碼請參考其數據手冊。此篇Blog只提供GPIO模擬I2C的代碼。
文中,假定MCU叫做主機,SHT20叫做從機。

【調試日誌】

  1. @2020-03-15,STM8S–SHT20聯調通過

底層代碼

你好! 這是你第一次使用 Markdown編輯器 所展示的歡迎頁。如果你想學習如何使用Markdown編輯器, 可以仔細閱讀這篇文章,瞭解一下Markdown的基本語法知識。

GPIO初始化設置

這顆STM8S上,具有真·開漏功能的引腳是PB4和PB5,這兩個引腳也正是片上I2C外設的默認引腳。端口設置如下:

  1. PB4 :I2C的SCL時鐘線,開漏輸出模式
  2. PB5 :I2C的SDA數據線,開漏輸出模式
  3. SCL和SDA初始化時,推薦選用高阻態輸出
/**
  * @brief  I2C端口初始化
  * @param  None
  * @retval None
  */
void I2C_GPIO_Init(void)
{
  GPIO_Init(I2C_PORT, I2C_SDA_PIN, GPIO_MODE_OUT_OD_HIZ_SLOW);
  GPIO_Init(I2C_PORT, I2C_SCL_PIN, GPIO_MODE_OUT_OD_HIZ_SLOW);
  I2C_SDA_OUT(1);
  I2C_SCL_OUT(1);
}

GPIO輸出電平設置,輸出/輸入方向設置

  1. 電平設置
/**
  * @brief  SDA輸出電平
  * @param  value:1=輸出高電平  0=輸出低電平 
  * @retval None
  */
void I2C_SDA_OUT(unsigned char value)
{
  if(value==1)
    GPIO_WriteHigh(I2C_PORT, I2C_SDA_PIN);
  else if(value==0)
    GPIO_WriteLow(I2C_PORT, I2C_SDA_PIN);
}

/**
  * @brief  SCL輸出電平
  * @param  value:1=輸出高電平  0=輸出低電平 
  * @retval None
  */
void I2C_SCL_OUT(unsigned char value)
{
  if(value==1)
    GPIO_WriteHigh(I2C_PORT, I2C_SCL_PIN);
  else if(value==0)
    GPIO_WriteLow(I2C_PORT, I2C_SCL_PIN);
}
  1. I2C主機向從機發送一個字節,都會等待從機通過SDA線返回一個“已接收(ACK)”指令。此時主機的SDA應設置爲輸入,而在其他環節中應該保持開漏輸出。而根據不同從機的I2C時序,有時也需要將SCL設置爲輸入。
/**
  * @brief  SDA設爲輸出
  * @param  None
  * @retval None
  */
void I2C_SDA_SET_OUTPUT(void)
{
  GPIO_Init(I2C_PORT, I2C_SDA_PIN, GPIO_MODE_OUT_OD_HIZ_SLOW);
}

/**
  * @brief  SCL設爲輸出
  * @param  None
  * @retval None
  */
void I2C_SCL_SET_OUTPUT(void)
{
  GPIO_Init(I2C_PORT, I2C_SCL_PIN, GPIO_MODE_OUT_OD_HIZ_SLOW);
}

/**
  * @brief  SDA設爲輸入
  * @param  None
  * @retval None
  */
void I2C_SDA_SET_INPUT(void)
{
  GPIO_Init(I2C_PORT, I2C_SDA_PIN, GPIO_MODE_IN_FL_NO_IT);
}

/**
  * @brief  SCL設爲輸入
  * @param  None
  * @retval None
  */
void I2C_SCL_SET_INPUT(void)
{
  GPIO_Init(I2C_PORT, I2C_SCL_PIN, GPIO_MODE_IN_FL_NO_IT);
}

GPIO輸入檢測

既然有了SDA或SCL的輸入設置,自然需要有判斷輸入值的函數。

/**
  * @brief  SDA輸入電平檢測
  * @param  None
  * @retval 1=輸入爲高電平  0=輸入爲低電平
  */
unsigned char I2C_SDA_IN(void)
{
  return (!!GPIO_ReadInputPin(I2C_PORT, I2C_SDA_PIN));
}

/**
  * @brief  SCL輸入電平檢測
  * @param  None
  * @retval 1=輸入爲高電平  0=輸入爲低電平
  */
unsigned char I2C_SCL_IN(void)
{
  return (!!GPIO_ReadInputPin(I2C_PORT, I2C_SCL_PIN));
}

模擬I2C代碼

包括:

  1. 起始條件,結束條件
  2. 回從機ACK,回從機NACK: 用於主機從從機讀1字節(此時主從的身份對調)後,還要向其發送請繼續(ACK)請停下(NACK) 信號
  3. 等待從機回ACK: 主機向從機發送1字節後,需要等待從機返回接收到(ACK) ,如果接收到的是 接收到(ACK)
  4. 從從機讀1字節: 完成讀1字節後,需要向從機發送請繼續(ACK)請停下(NACK) 信號
  5. 向從機寫1細節: 完成寫1字節後,需要等待從機返回接收到(ACK) 信號。

起始條件,結束條件

/**
  * @brief  I2C傳輸開始
  * @param  None
  * @retval None
  */
void I2C_START(void)
{
  I2C_SDA_SET_OUTPUT();
  
  I2C_SDA_OUT(1);
  I2C_SCL_OUT(1);
  Delay_1us(10);/////////////
  I2C_SDA_OUT(0);
  Delay_1us(10);
  I2C_SCL_OUT(0);
  Delay_1us(10);
}

/**
  * @brief  I2C傳輸結束
  * @param  None
  * @retval None
  */
void I2C_STOP(void)
{
  I2C_SDA_SET_OUTPUT();
  
  I2C_SDA_OUT(0);
  I2C_SCL_OUT(0);
  Delay_1us(10);//////////////
  I2C_SCL_OUT(1);
  Delay_1us(10);
  I2C_SDA_OUT(1);
  Delay_1us(10);
}

回從機ACK,回從機NACK

/**
  * @brief  MCU回覆IC ACK信號
  * @param  None
  * @retval None
  */
void I2C_SEND_ACK(void)
{
  I2C_SDA_SET_OUTPUT();
  
  I2C_SDA_OUT(0);
  Delay_1us(10);//////////////
  I2C_SCL_OUT(1);
  Delay_1us(10);
  I2C_SCL_OUT(0);
  Delay_1us(10);
}

/**
  * @brief  MCU回覆NACK信號
  * @param  None
  * @retval None
  */
void I2C_SEND_NACK(void)
{
  I2C_SDA_SET_OUTPUT();
  
  I2C_SDA_OUT(1);
//  Delay_1us(10);///////////////
  I2C_SCL_OUT(1);
  Delay_1us(10);
  I2C_SCL_OUT(0);
  Delay_1us(10);
}

從從機讀1字節

/**
  * @brief  I2C MCU接收1字節
  * @param  ACK_CHOICE:  數據讀取後主機如何回覆,I2C_ACK=回覆ACK  I2C_NACK=回覆NACK  I2C_JUMP_REPLY=跳過回覆
  * @retval read_data:   接收的字節
  */
unsigned char I2C_READ_BYTE(unsigned char ACK_CHOICE)
{
  unsigned char read_data=0;
  
  I2C_SDA_SET_INPUT();
  
//  I2C_SCL_OUT(0);//////////////////
//  Delay_1us(10);///////////////////
  
  for(unsigned char i=0x80; i!=0; i>>=1)
  {
    I2C_SCL_OUT(1);
    Delay_1us(10);
    if(I2C_SDA_IN() == 1)
      read_data |= i;
    I2C_SCL_OUT(0);
    Delay_1us(10);
  }
  I2C_SDA_SET_OUTPUT();
  I2C_SDA_OUT(1);//?????????????????
  
  if(ACK_CHOICE==I2C_ACK)       I2C_SEND_ACK();
  if(ACK_CHOICE==I2C_NACK)      I2C_SEND_NACK();
  
  return read_data;
}

向從機寫1字節

/**
  * @brief  I2C MCU發送1字節
  * @param  send_data:  發送的字節
  * @retval ack回覆:    I2C_NACK=異常=檢測到NACK  I2C_ACK=正常=檢測到ACK
  */
unsigned char I2C_SEND_BYTE(unsigned char send_data)
{
  unsigned char ack=I2C_NACK;   //初始值
  unsigned char time_out=200;   //ACK查詢次數
  

  I2C_SDA_SET_OUTPUT();
//  I2C_SCL_SET_OUTPUT();
  
//  I2C_SCL_OUT(0);////////////////
//  Delay_1us(10);/////////////////////////
  
  for(unsigned char i=0x80; i!=0; i>>=1)
  {
    if(send_data&i)     I2C_SDA_OUT(1);
    else                I2C_SDA_OUT(0);
    I2C_SCL_OUT(1);
    Delay_1us(10);
    I2C_SCL_OUT(0);
    Delay_1us(10);
  }
  
  //開始ACK查詢
  I2C_SDA_SET_INPUT();
  I2C_SCL_OUT(1);
  while(time_out--)
  {
    if(I2C_SDA_IN()==1)
      ack=I2C_NACK;
    else
    {
      ack=I2C_ACK;
      break;
    }
    Delay_1us(1);
  }
  
  I2C_SCL_OUT(0);
  
  I2C_SDA_SET_OUTPUT();
  I2C_SDA_OUT(1);//???????????????????
  
  return ack;
}

其他代碼

上述全部代碼位於i2c.c 源文件中,而下列代碼則全部位於i2c.h 頭文件。

#ifndef __I2C_H
#define __I2C_H
/* Includes ------------------------------------------------------------------*/
#include "main.h"

/* Private defines -----------------------------------------------------------*/
#define I2C_PORT                GPIOB
#define I2C_SDA_PIN             GPIO_PIN_5
#define I2C_SCL_PIN             GPIO_PIN_4

#define I2C_ACK                 0	//用於接收:SDA=L=從機有回覆  用於發送:回覆從機ACK
#define I2C_NACK                1	//用於接收:SDA=H=從機未回覆  用於發送:回覆從機ACK
#define I2C_JUMP_REPLY          2	//調試用
/* Private macro -------------------------------------------------------------*/
/* Private variables ---------------------------------------------------------*/
/* Private function prototypes -----------------------------------------------*/
void I2C_GPIO_Init(void);
void I2C_SDA_SET_OUTPUT(void);
void I2C_SCL_SET_OUTPUT(void);
void I2C_SDA_SET_INPUT(void);
void I2C_SCL_SET_INPUT(void);

void I2C_SDA_OUT(unsigned char value);
void I2C_SCL_OUT(unsigned char value);
unsigned char I2C_SDA_IN(void);
unsigned char I2C_SCL_IN(void);

void I2C_START(void);
void I2C_STOP(void);

void I2C_SEND_ACK(void);
void I2C_SEND_NACK(void);

unsigned char I2C_READ_BYTE(unsigned char ACK_CHOICE);
unsigned char I2C_SEND_BYTE(unsigned char send_data);


#endif

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章