I²C(Inter-Integrated Circuit)字面上的意思是集成电路之间,它其实是I²C Bus简称,所以中文应该叫集成电路总线,它是一种串行通信总线,使用多主从架构,由飞利浦公司在1980年代为了让主板、嵌入式系统或手机用以连接低速周边设备而发展。——维基百科
【背景】STM8运行在16MHz频率上。
本设计使用GPIO模拟方式来设计I2C通信的底层,包括:
①GPIO初始化
②GPIO输出设置
③GPIO输入读取
④生成起始信号
⑤生成结束信号
⑥发送字节
⑦读取字节
注1:④-⑦中的延时设计采用软件延时方式
注2:从⑥和⑦估算,峰值通信速度是50kbps
开发板上,PB4和PB5端口具有真开漏功能(因为这两个端口刚好是I2C外设的默认端口)。
将PB4和PB5初始化为开漏模式、高阻抗、快速模式。
开漏模式:开漏模式下,端口输出有线与特性;只有在开漏模式下,端口既可以当输入端口、也可以当输出端口。
高阻抗:相当于端口输出H
快速模式:-
#define I2C_SCL_GPIO_PORT GPIOB
#define I2C_SCL_GPIO_PIN GPIO_PIN_4
#define I2C_SDA_GPIO_PORT GPIOB
#define I2C_SDA_GPIO_PIN GPIO_PIN_5
#define I2C_SCL_INIT_STATE GPIO_MODE_OUT_OD_HIZ_FAST //GPIO_MODE_OUT_OD_HIZ_FAST or GPIO_MODE_OUT_OD_HIZ_SLOW
#define I2C_SDA_INIT_STATE GPIO_MODE_OUT_OD_HIZ_FAST
/**
* @brief I2C GPIO Init API
* @param None
* @retval None
*/
void I2C_HAL_Init(void)
{
GPIO_Init( I2C_SCL_GPIO_PORT, I2C_SCL_GPIO_PIN, I2C_SCL_INIT_STATE);
GPIO_Init( I2C_SDA_GPIO_PORT, I2C_SDA_GPIO_PIN, I2C_SDA_INIT_STATE);
}
/**
* @brief I2C GPIO Output API
* @param pin_id : the specific pin to write.
* @param bit_status : the output value to apply. Bit_SET or Bit_RESET
* @retval None
*/
void I2C_HAL_PinWrite(uint8_t pin_id, uint8_t bit_status)
{
switch(pin_id)
{
case I2C_PIN_SCL:
if( bit_status != Bit_RESET){
GPIO_WriteHigh( I2C_SCL_GPIO_PORT, I2C_SCL_GPIO_PIN);
}
else{
GPIO_WriteLow( I2C_SCL_GPIO_PORT, I2C_SCL_GPIO_PIN);
}
break;
case I2C_PIN_SDA:
if( bit_status != Bit_RESET){
GPIO_WriteHigh( I2C_SDA_GPIO_PORT, I2C_SDA_GPIO_PIN);
}
else{
GPIO_WriteLow( I2C_SDA_GPIO_PORT, I2C_SDA_GPIO_PIN);
}
break;
default:
break;
}
}
/**
* @brief I2C GPIO Read API
* @param pin_id : the specific pin to read.
* @retval the input value. Bit_SET or Bit_RESET.
*/
uint8_t I2C_HAL_PinRead(uint8_t pin_id)
{
uint8_t bit_status = 0;
switch( pin_id){
case I2C_PIN_SCL:
bit_status = !!GPIO_ReadInputPin( I2C_SCL_GPIO_PORT, I2C_SCL_GPIO_PIN);
break;
case I2C_PIN_SDA:
bit_status = !!GPIO_ReadInputPin( I2C_SDA_GPIO_PORT, I2C_SDA_GPIO_PIN);
break;
default:
break;
}
return bit_status;
}
/**
* @brief Generates I2C communication START condition.
* @param None
* @retval None
*/
void I2C_HAL_GenerateStart(void)
{
I2C_HAL_PinWrite(I2C_PIN_SDA, Bit_SET);
I2C_HAL_PinWrite(I2C_PIN_SCL, Bit_SET);
DELAY_APL_Delayus(5);
I2C_HAL_PinWrite(I2C_PIN_SDA, Bit_RESET);
DELAY_APL_Delayus(5);
I2C_HAL_PinWrite(I2C_PIN_SCL, Bit_RESET);
DELAY_APL_Delayus(5);
}
/**
* @brief Generates I2C communication STOP condition.
* @param None
* @retval None
*/
void I2C_HAL_GenerateStop(void)
{
I2C_HAL_PinWrite(I2C_PIN_SDA, Bit_RESET);
// I2C_HAL_PinWrite(I2C_PIN_SCL, Bit_RESET);
DELAY_APL_Delayus(5);
I2C_HAL_PinWrite(I2C_PIN_SCL, Bit_SET);
DELAY_APL_Delayus(5);
I2C_HAL_PinWrite(I2C_PIN_SDA, Bit_SET);
DELAY_APL_Delayus(5);
}
主节点发送每一个字节后,需要限时检查从节点是否接受到了数据。如果从节点接受到了数据,函数才返回ACK;如果返回了NACK,则说明通信失败,继续通信下去已经没有意义。
在/* Check slave’s ACK */步骤,使用了开漏模式的线与特性。
/**
* @brief Send byte to slave.
* @param data: the byte to send
* @retval Slave's ACK or NACK
*/
uint8_t I2C_HAL_SendByte(uint8_t data)
{
uint8_t bit_mask;
uint32_t time_out = 200;
uint8_t return_val = I2C_NACK; /* Init to Slave receive failure */
/* Send byte */
for( bit_mask = 0x80; bit_mask != 0; bit_mask >>= 1){
if( !!(bit_mask&data)){
I2C_HAL_PinWrite(I2C_PIN_SDA, Bit_SET);
}
else{
I2C_HAL_PinWrite(I2C_PIN_SDA, Bit_RESET);
}
DELAY_APL_Delayus(5);
I2C_HAL_PinWrite(I2C_PIN_SCL, Bit_SET);
DELAY_APL_Delayus(10);
I2C_HAL_PinWrite(I2C_PIN_SCL, Bit_RESET);
DELAY_APL_Delayus(5);
}
/* Release SDA Line */
I2C_HAL_PinWrite(I2C_PIN_SDA, Bit_SET);
/* Check slave's ACK */
DELAY_APL_Delayus(5);
I2C_HAL_PinWrite(I2C_PIN_SCL, Bit_SET);
while( time_out --){
if( I2C_HAL_PinRead(I2C_PIN_SDA) == Bit_RESET){
return_val = I2C_ACK; /* Get Slave receive Success */
break;
}
}
I2C_HAL_PinWrite(I2C_PIN_SCL, Bit_RESET);
DELAY_APL_Delayus(5);
return return_val;
}
从从节点接收每一个字节后,主节点必须告诉从节点是否继续接受下一个字节,即ack_status。
当ack_status是ACK时,表示“继续下一个字节的接收”;当ack_status是NACK时,表示“接收结束”。
/**
* @brief Receive byte from slave API.
* @param data: the pointor to the received data
* @param ack_status: Master's ACN or NACK
* @retval None
*/
void I2C_HAL_ReadByte(uint8_t* data, uint8_t ack_status)
{
uint8_t bit_mask;
uint8_t data_temp = 0;
I2C_HAL_PinWrite(I2C_PIN_SDA, Bit_SET);//Release SDA Line
/* Byte Receive */
for( bit_mask = 0x80; bit_mask != 0; bit_mask >>= 1)
{
DELAY_APL_Delayus(5);
I2C_HAL_PinWrite(I2C_PIN_SCL, Bit_SET);
DELAY_APL_Delayus(10);
if( I2C_HAL_PinRead(I2C_PIN_SDA) == Bit_SET)
{
data_temp |= bit_mask;
}
I2C_HAL_PinWrite(I2C_PIN_SCL, Bit_RESET);
DELAY_APL_Delayus(5);
}
*data = data_temp;
/* ACK/NACK Send */
if( ack_status == I2C_ACK)
{
I2C_HAL_PinWrite(I2C_PIN_SDA, Bit_RESET);
}
else //if( ack_status == I2C_NACK)
{
I2C_HAL_PinWrite(I2C_PIN_SDA, Bit_SET);
}
DELAY_APL_Delayus(5);
I2C_HAL_PinWrite(I2C_PIN_SCL, Bit_SET);
DELAY_APL_Delayus(10);
I2C_HAL_PinWrite(I2C_PIN_SCL, Bit_RESET);
DELAY_APL_Delayus(5);
}