经过验证的GPIO模拟I2C时序代码
使用STM32的GPIO模拟I2C总线时序,GPIO设置为开漏模式,SDA和SCK外部必须使用上拉电阻,一般是4.7K。
开漏模式的好处是,可以同时读取输入电平,而无需切换输入/输出模式。
注意事项:在开漏模式输出高电平,是释放总线,从机可以拉低;而输出低电平,则是锁住总线,从机无法拉高。
下面是代码:
/******************************************************************************
* I2C驱动(GPIO模拟)
* 蒋晓岗<kerndev@foxmail.com>
* 2020.09.22
******************************************************************************/
#include <stdint.h>
#include "delay.h"
#include "gpio.h"
#include "i2c.h"
#define SCK_SET(ctx,x) gpio_write((ctx)->sck_port,(ctx)->sck_pin,(x))
#define SCK_GET(ctx) gpio_read((ctx)->sck_port,(ctx)->sck_pin)
#define SDA_SET(ctx,x) gpio_write((ctx)->sda_port,(ctx)->sda_pin,(x))
#define SDA_GET(ctx) gpio_read((ctx)->sda_port,(ctx)->sda_pin)
#define DELAY(x) delay_us(x)
struct i2c_contex
{
int sck_port;
int sck_pin;
int sda_port;
int sda_pin;
};
#define GET_HW_CTX(id) (&m_i2c_ctx[id])
static struct i2c_contex m_i2c_ctx[4]=
{
//SCK SDA
{PA,12, PA,11},
{PA, 8, PC, 9},
{PC,10, PC,12},
};
//初始化GPIO
static void i2c_gpio_init(int id)
{
struct i2c_contex *ctx;
ctx = GET_HW_CTX(id);
gpio_open(ctx->sck_port, ctx->sck_pin, GPIO_MODE_OUT, GPIO_OUT_OD);
gpio_open(ctx->sda_port, ctx->sda_pin, GPIO_MODE_OUT, GPIO_OUT_OD);
SDA_SET(ctx, 1);
SCK_SET(ctx, 1);
}
//产生起始条件
static void i2c_start(struct i2c_contex *ctx)
{
SDA_SET(ctx, 1);
SCK_SET(ctx, 1);
DELAY(2);
SDA_SET(ctx, 0);
DELAY(2);
SCK_SET(ctx, 0);
}
//产生停止条件
static void i2c_stop(struct i2c_contex *ctx)
{
SCK_SET(ctx, 0);
SDA_SET(ctx, 0);
DELAY(2);
SCK_SET(ctx, 1);
DELAY(2);
SDA_SET(ctx, 1);
}
//等待应答
static uint8_t i2c_wait_ack(struct i2c_contex *ctx)
{
uint8_t ack;
SDA_SET(ctx, 1);
SCK_SET(ctx, 1);
DELAY(2);
ack = !SDA_GET(ctx);
SCK_SET(ctx, 0);
return ack;
}
//发送应答
static void i2c_send_ack(struct i2c_contex *ctx, uint8_t ack)
{
SCK_SET(ctx, 0);
SDA_SET(ctx, !ack);
DELAY(2);
SCK_SET(ctx, 1);
DELAY(2);
SCK_SET(ctx, 0);
}
//发送字节
static void i2c_send_byte(struct i2c_contex *ctx, uint8_t data)
{
int i;
for(i=0; i<8; i++)
{
SDA_SET(ctx, (data & 0x80) >> 7);
data <<= 1;
DELAY(2);
SCK_SET(ctx, 1);
DELAY(2);
SCK_SET(ctx, 0);
}
}
//接收字节
static uint8_t i2c_recv_byte(struct i2c_contex *ctx)
{
int i;
uint8_t data;
data = 0;
SDA_SET(ctx, 1);
for(i=0; i<8; i++)
{
SCK_SET(ctx, 0);
DELAY(2);
SCK_SET(ctx, 1);
data <<= 1;
data |= SDA_GET(ctx);
DELAY(2);
}
return data;
}
//写从机数据
int i2c_xfer_tx(int id, uint8_t addr, uint8_t reg, uint8_t *buf, int size)
{
int i;
int ret;
struct i2c_contex *ctx;
ctx = GET_HW_CTX(id);
i2c_start(ctx);
i2c_send_byte(ctx, addr&0xfe);
ret = i2c_wait_ack(ctx);
if(!ret)
{
i2c_stop(ctx);
return -1;
}
i2c_send_byte(ctx, reg);
ret = i2c_wait_ack(ctx);
if(!ret)
{
i2c_stop(ctx);
return -2;
}
for(i=0; i<size; i++)
{
i2c_send_byte(ctx, buf[i]);
ret = i2c_wait_ack(ctx);
if(!ret)
{
i2c_stop(ctx);
return -3;
}
}
i2c_stop(ctx);
return 0;
}
//读从机数据
int i2c_xfer_rx(int id, uint8_t addr, uint8_t reg, uint8_t *buf, int size)
{
int i;
int ret;
struct i2c_contex *ctx;
ctx = GET_HW_CTX(id);
i2c_start(ctx);
i2c_send_byte(ctx, addr&0xfe);
ret = i2c_wait_ack(ctx);
if(!ret)
{
i2c_stop(ctx);
return -1;
}
i2c_send_byte(ctx, reg);
ret = i2c_wait_ack(ctx);
if(!ret)
{
i2c_stop(ctx);
return -2;
}
i2c_start(ctx);
i2c_send_byte(ctx, addr|0x01);
ret = i2c_wait_ack(ctx);
if(!ret)
{
i2c_stop(ctx);
return -3;
}
for(i=0; i<size; i++)
{
buf[i] = i2c_recv_byte(ctx);
i2c_send_ack(ctx, i!=(size-1));
}
i2c_stop(ctx);
return 0;
}
//初始化
void i2c_init(void)
{
i2c_gpio_init(0);
i2c_gpio_init(1);
i2c_gpio_init(2);
}
下面是头文件 :
#ifndef __I2C_H
#define __I2C_H
void i2c_init(void);
int i2c_xfer_rx(int id, uint8_t addr, uint8_t reg, uint8_t *buf, int size);
int i2c_xfer_tx(int id, uint8_t addr, uint8_t reg, uint8_t *buf, int size);
#endif