串口模拟I2C代码记录
摘要
通常情况下在硬件资源不够或者某些芯片的I2C调试不通的时候会选择串口模拟I2C,本文记录串口模拟I2C的软件实现方法。
1、函数宏定义
#define IIC_SDA_PIN_PORT (GPIO0)
#define IIC_SDA_PIN (17)
#define IIC_SCL_PIN_PORT (GPIO0)
#define IIC_SCL_PIN (16)
2、串口初始化
void UserI2cIoInit(void) //io复用配置
{
//I2C1
//复用为普通io口
HAL_GPIO_AF_SET(IIC_SDA_PIN_PORT, IIC_SDA_PIN, GPIO_AF0);
HAL_GPIO_AF_SET(IIC_SCL_PIN_PORT, IIC_SCL_PIN, GPIO_AF0);
//输出使能
HAL_GPIO_OUT_EN(IIC_SDA_PIN_PORT, 1U << IIC_SDA_PIN);
HAL_GPIO_OUT_EN(IIC_SCL_PIN_PORT, 1U << IIC_SCL_PIN);
//输出为高电平
HAL_GPIO_BIT_SET(IIC_SDA_PIN_PORT, (1U << IIC_SDA_PIN));
HAL_GPIO_BIT_SET(IIC_SCL_PIN_PORT, (1U << IIC_SCL_PIN));
//SDA设置为开漏输出(软件模拟I2C)
HAL_GPIO_OUT_OD(IIC_SDA_PIN_PORT, 1U << IIC_SDA_PIN);
}
3、延时函数处理
_Pragma("push") \
_Pragma("O0")
__RAM_CODE void SysCycleUsDelay(volatile float_t delayInUs)
{
// 按照周期进行延时,不依赖于定时器,偏差0.1%左右
uint32_t cycCnt = DWT->CYCCNT;
uint32_t usCnt = (uint32_t)((float_t)SystemHclk / 1000000.0f * delayInUs);
usCnt -= 133; // 修正偏差
usCnt >>= 2;
while(usCnt--){;}
cycCnt = DWT->CYCCNT-cycCnt;
return;
}
_Pragma ("pop")
4、SDA SCL高低电平配置
//SDA输入模式
static void I2C_SDA_IN(void)
{
HAL_GPIO_IN_EN(IIC_SDA_PIN_PORT, (1U << IIC_SDA_PIN));
}
//SDA输出模式
static void I2C_SDA_OUT(void)
{
HAL_GPIO_OUT_EN(IIC_SDA_PIN_PORT, (1U << IIC_SDA_PIN));
}
//SDA电平变换配置
static void I2C_SDA(uint8_t sdaState)
{
if (sdaState == 1)
{
//开漏模式下设置为输入NMOS截止,由外部上拉输出为高电平
HAL_GPIO_IN_EN(IIC_SDA_PIN_PORT, 1U << IIC_SDA_PIN);
}
else
{
//开漏模式下设置为低NMOS导通接地输出为低电平
HAL_GPIO_OUT_EN(IIC_SDA_PIN_PORT, 1U << IIC_SDA_PIN);
HAL_GPIO_BIT_CLR(IIC_SDA_PIN_PORT, (1U << IIC_SDA_PIN));
}
}
//SCL电平配置
static void I2C_SCL(uint8_t sclState)
{
if (sclState == 1)
{
HAL_GPIO_BIT_SET(IIC_SCL_PIN_PORT, (1U << IIC_SCL_PIN));
}
else
{
HAL_GPIO_BIT_CLR(IIC_SCL_PIN_PORT, (1U << IIC_SCL_PIN));
}
}
5、I2C开始
//开始 sda high->low scl high->low , sda 下降沿跳变早于scl
static void I2C_Start(void)
{
I2C_SDA_OUT();
I2C_SDA(1);
I2C_SCL(1);
SysCycleUsDelay(4);
I2C_SDA(0);
SysCycleUsDelay(4);
I2C_SCL(0);c
}
6、I2C停止
//停止,SDA↓, SCL↑,SDA↑
static void I2C_Stop(void)
{
I2C_SDA_OUT();
I2C_SCL(0);
I2C_SDA(0);
SysCycleUsDelay(4);
I2C_SCL(1);
SysCycleUsDelay(4);
I2C_SDA(1);
}
7、I2C响应
//发送Ack
static void IIC_Ack(void)
{
I2C_SCL(0);
I2C_SDA_OUT();
I2C_SDA(0);//发送低电平,响应从机
SysCycleUsDelay(2);
I2C_SCL(1);
SysCycleUsDelay(2);
I2C_SCL(0);
}
8、I2C不响应
//发送Nack
static void IIC_NAck(void)
{
I2C_SCL(0);
I2C_SDA_OUT();
I2C_SDA(1);//发送低电平,响应从机
SysCycleUsDelay(2);
I2C_SCL(1);
SysCycleUsDelay(2);
I2C_SCL(0);
}
9、I2C等待响应
//等待Ack
static UINT8 IIC_Wait_Ack(uint32_t timeout_ms)
{
UINT32 t0;
I2C_SDA_IN(); //SDA设置为输入
I2C_SDA(1);
SysCycleUsDelay(1);
I2C_SCL(1); //时钟置高
SysCycleUsDelay(1);
t0 = GetMs(0);
while ((HAL_GPIO_IN_DATA_GET(IIC_SDA_PIN_PORT) & (1U << IIC_SDA_PIN)) != 0) //判断SDA端口状态
{
if (GetMs(t0) > timeout_ms)
{
I2C_Stop();
return 0;
}
}
I2C_SCL(0); //拉低,完成应答位
SysCycleUsDelay(4);
I2C_SDA_OUT(); //SDA设置为输出
return 1;
}
10、I2C写一个字节
//发送一个字节
static void I2C_WriteByte(uint8_t Data)
{
UINT8 cnt;
I2C_SDA_OUT();
I2C_SCL(0);
for (cnt = 0; cnt < 8; cnt++)
{
if (Data & 0x80)
{
I2C_SDA(1); //SDA高
}
else
{
I2C_SDA(0); //SDA低
}
Data <<= 1;
SysCycleUsDelay(2);
I2C_SCL(1);
SysCycleUsDelay(2);
I2C_SCL(0);
SysCycleUsDelay(2);
}
}
11、I2C读一个字节
//读一个字节
static uint8_t I2C_ReadByte(uint8_t ack)
{
uint8_t cnt;
uint8_t Data = 0;
I2C_SDA_IN(); //SDA设置为输入
for (cnt = 0; cnt < 8; cnt++)
{
I2C_SCL(0);
SysCycleUsDelay(2);
I2C_SCL(1);
Data <<= 1;
SysCycleUsDelay(1);
if ((HAL_GPIO_IN_DATA_GET(IIC_SDA_PIN_PORT) & (1U << IIC_SDA_PIN)) != 0) //SDA读数据
{
//Data++;
Data |= 0x01;
}
SysCycleUsDelay(1);
}
if (!ack) /*when ack = 0*/
{
IIC_NAck(); //发送NAck
}
else
{
IIC_Ack(); //发送Ack
}
return Data;
}
12、I2C连续写
//连续写
uint8_t I2cDataWrite(uint8_t address, uint32_t length, uint8_t *pData, uint32_t timeout_ms)
{
UINT8 Ack;
if (length)
{
I2C_Start();
I2C_WriteByte(address);
Ack = IIC_Wait_Ack(timeout_ms);
while (!Ack)
{
I2C_Stop();
return IICERR_NACK;
}
while (length != 0)
{
I2C_WriteByte(*pData++);
Ack = IIC_Wait_Ack(timeout_ms);
while (!Ack)
{
I2C_Stop();
return IICERR_NACK;
}
length--;
SysCycleUsDelay(4 * 10);
}
I2C_Stop();
return IIC_OK;
}
return IICERR_LENZERO;
}
13、I2C连续读
//连续读
uint8_t I2cDataRead(uint8_t address, uint32_t length, uint8_t *pData, uint32_t timeout_ms)
{
uint8_t Ack;
if (length)
{
I2C_Start();
I2C_WriteByte(address); //发送寄存器地址
Ack = IIC_Wait_Ack(timeout_ms); //检测有无应答以及是否超时
while (!Ack)
{
return IICERR_NACK;
}
while (length > 1)
{
*pData++ = I2C_ReadByte(1); //读一个字节
length --;
}
*pData ++ = I2C_ReadByte(0); //读最后一个字节
I2C_Stop();
return IIC_OK;
}
return IICERR_LENZERO;
}
小结
本文记录的串口模式拟2C的为一种实现模式,不同的代码有不同的实现方式,欢迎各位大佬提供宝贵的意见。