此GPIO模擬I2C代碼已在STM8S103K3上測試通過,測試所用下位機爲SHT20溫溼度傳感器。SHT20測量代碼請參考其數據手冊。此篇Blog只提供GPIO模擬I2C的代碼。
文中,假定MCU叫做主機,SHT20叫做從機。
【調試日誌】
- @2020-03-15,STM8S–SHT20聯調通過
GPIO模擬I2C的函數
底層代碼
你好! 這是你第一次使用 Markdown編輯器 所展示的歡迎頁。如果你想學習如何使用Markdown編輯器, 可以仔細閱讀這篇文章,瞭解一下Markdown的基本語法知識。
GPIO初始化設置
這顆STM8S上,具有真·開漏功能的引腳是PB4和PB5,這兩個引腳也正是片上I2C外設的默認引腳。端口設置如下:
- PB4 :I2C的SCL時鐘線,開漏輸出模式
- PB5 :I2C的SDA數據線,開漏輸出模式
- 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輸出電平設置,輸出/輸入方向設置
- 電平設置
/**
* @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);
}
- 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代碼
包括:
- 起始條件,結束條件
- 回從機ACK,回從機NACK: 用於主機從從機讀1字節(此時主從的身份對調)後,還要向其發送請繼續(ACK) 或 請停下(NACK) 信號
- 等待從機回ACK: 主機向從機發送1字節後,需要等待從機返回接收到(ACK) ,如果接收到的是 接收到(ACK)
- 從從機讀1字節: 完成讀1字節後,需要向從機發送請繼續(ACK) 或 請停下(NACK) 信號
- 向從機寫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