嵌入式篇 | 通用模拟IO方式I2C主机协议框架


使用示例

本文以压力传感器GP6818D为例:

主函数

int main(void)
{
	float temp, pressure;
	delay_init();
	GZP6818D_Init();
	while(1)
	{
		GZP6818D_GetMeasure(&pressure, &temp);  // 注:这个型号无内置温度。
		printf("pressure:%0.2f    temp:%0.2f", pressure, temp);
		delay_ms(1000);
	}
}

GP6818D驱动

/**
 * @file gzp6818d.c
 * @author lakun (lakun@qq.com)
 * @brief
 * @version 1.0
 * @date 2024-10-14
 *
 * Copyright (c) 2024 by AnKun.
 * All rights reserved.
 *
 */

#include "gzp6818d.h"
#include "viic.h"


extern void delay_ms(uint32_t nms);
extern void delay_us(uint32_t nus);


#define SDA_GPIO_Port  GPIOA
#define SDA_GPIO_Pin   GPIO_Pin_4

#define SCL_GPIO_Port  GPIOA
#define SCL_GPIO_Pin   GPIO_Pin_0

void pinSclSet1(uint8_t state)
{
	GPIO_WriteBit(SCL_GPIO_Port, SCL_GPIO_Pin, state ? Bit_SET : Bit_RESET);
}

void pinSdaSet1(uint8_t state)
{
	GPIO_WriteBit(SDA_GPIO_Port, SDA_GPIO_Pin, state ? Bit_SET : Bit_RESET);
}

uint8_t pinSdaGet1(void)
{
	return GPIO_ReadInputDataBit(SDA_GPIO_Port, SDA_GPIO_Pin) == Bit_SET ? 1 : 0;
}

void pinSdaDir(uint8_t dir)
{
#if 0  // 开漏模式无需切换IO方向
	if(dir)
	{
		// 设置为输出模式
	}else
	{
		// 设置为输入模式
	}
#endif
}

VirtI2C_HandleTypeDef hvi2c1 = 
{
	.pinSclSet = pinSclSet1,
	.pinSdaSet = pinSdaSet1,
	.pinSdaGet = pinSdaGet1,
	.pinSdaDir = pinSdaDir,
	.delayUs   = delay_us,
};

void VirtI2C_GPIO_Init(void)
{
	GPIO_InitTypeDef GPIO_InitStruct = {0};
	
	RCC_AHBPeriphClockCmd(RCC_AHBENR_GPIOA, ENABLE);
	
	GPIO_InitStruct.GPIO_Pin   = SCL_GPIO_Pin;
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_High;
	GPIO_InitStruct.GPIO_Mode  = GPIO_Mode_Out_OD;
	GPIO_Init(SCL_GPIO_Port, &GPIO_InitStruct);

	GPIO_InitStruct.GPIO_Pin   = SDA_GPIO_Pin;
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_High;
	GPIO_InitStruct.GPIO_Mode  = GPIO_Mode_Out_OD;
	GPIO_Init(SDA_GPIO_Port, &GPIO_InitStruct);
}

void GZP6818D_Init(void)
{
	VirtI2C_GPIO_Init();
}

int GZP6818D_GetMeasure(float *pressure, int *temp)
{
	uint8_t buf[32] = {0};
	volatile int tmp = 0;
	
	VirtI2C_MasterWriteByte(&hvi2c1, GZP6818D_DEV_ADDR, 0x01, 0x01);  // 启动采集

	delay_ms(20);

	buf[0] = VirtI2C_MasterReadByte(&hvi2c1, GZP6818D_DEV_ADDR, 0x04);
	buf[1] = VirtI2C_MasterReadByte(&hvi2c1, GZP6818D_DEV_ADDR, 0x05);
	buf[2] = VirtI2C_MasterReadByte(&hvi2c1, GZP6818D_DEV_ADDR, 0x06);
	buf[3] = VirtI2C_MasterReadByte(&hvi2c1, GZP6818D_DEV_ADDR, 0x07);
	buf[4] = VirtI2C_MasterReadByte(&hvi2c1, GZP6818D_DEV_ADDR, 0x08);

	tmp = (buf[0] << 16) | (buf[1] << 8) | buf[2];
	if(tmp >= 8388608)
	{
		tmp -= 16777216;
	}
	*pressure = (float)tmp / 2097152 * (700000 - 100000) + 100000;

	tmp = (buf[3] << 8) | buf[4];
	if(tmp > 32768)
	{
		tmp = tmp - 65536;
	}
	*temp = tmp;

	return 0;
}

头文件

#ifndef __VIRT_IIC_H
#define __VIRT_IIC_H

#include <stdint.h>

#define VirtI2C_DelayUS 10

typedef struct
{
	void (*pinSdaSet)(uint8_t state);
	void (*pinSclSet)(uint8_t state);
	uint8_t (*pinSdaGet)(void);
	void (*pinSdaDir)(uint8_t dir);
	void (*delayUs)(uint32_t microseconds);
}VirtI2C_HandleTypeDef;

void VirtI2C_MasterWriteByte(VirtI2C_HandleTypeDef* hi2c, uint8_t DevAddress, uint8_t MemAddress, uint8_t Data);
uint8_t VirtI2C_MasterReadByte(VirtI2C_HandleTypeDef* hi2c, uint8_t DevAddress, uint8_t MemAddress);

#endif

源文件

#include "viic.h"

//产生IIC起始信号
void VirtI2C_Start(VirtI2C_HandleTypeDef* hi2c)
{
	hi2c->pinSdaDir(1);     //sda线输出
	hi2c->pinSdaSet(1);
	hi2c->pinSclSet(1);
	hi2c->delayUs(VirtI2C_DelayUS);
	hi2c->pinSdaSet(0);//START:when CLK is high,DATA change form high to low
	hi2c->delayUs(VirtI2C_DelayUS);
	hi2c->pinSclSet(0);//钳住I2C总线,准备发送或接收数据
}

//产生IIC停止信号
void VirtI2C_Stop(VirtI2C_HandleTypeDef* hi2c)
{
	hi2c->pinSdaDir(1);//sda线输出
	hi2c->pinSclSet(0);
	hi2c->pinSdaSet(0);//STOP:when CLK is high DATA change form low to high
	hi2c->delayUs(VirtI2C_DelayUS);
	hi2c->pinSclSet(1);
	hi2c->pinSdaSet(1);//发送I2C总线结束信号
	hi2c->delayUs(VirtI2C_DelayUS);
}

// 等待应答信号到来
// 返回值:0,接收应答失败
//        1,接收应答成功
uint8_t VirtI2C_Wait_Ack(VirtI2C_HandleTypeDef* hi2c)
{
	uint8_t retry = 0;
	hi2c->pinSdaDir(0);      //SDA设置为输入
	hi2c->pinSdaSet(1);
	hi2c->delayUs(VirtI2C_DelayUS);
	hi2c->pinSclSet(1);
	hi2c->delayUs(VirtI2C_DelayUS);
	while(hi2c->pinSdaGet())
	{
		if(++retry > VirtI2C_DelayUS)
		{
			VirtI2C_Stop(hi2c);
			return 0;
		}
		hi2c->delayUs(1);
	}
	hi2c->pinSclSet(0);//时钟输出0
	return 1;
}

//产生ACK应答
void VirtI2C_Ack(VirtI2C_HandleTypeDef* hi2c)
{
	hi2c->pinSclSet(0);
	hi2c->pinSdaDir(1);
	hi2c->pinSdaSet(0);
	hi2c->delayUs(VirtI2C_DelayUS);
	hi2c->pinSclSet(1);
	hi2c->delayUs(VirtI2C_DelayUS);
	hi2c->pinSclSet(0);
}

//不产生ACK应答
void VirtI2C_NAck(VirtI2C_HandleTypeDef* hi2c)
{
	hi2c->pinSclSet(0);
	hi2c->pinSdaDir(1);
	hi2c->pinSdaSet(1);
	hi2c->delayUs(VirtI2C_DelayUS);
	hi2c->pinSclSet(1);
	hi2c->delayUs(VirtI2C_DelayUS);
	hi2c->pinSclSet(0);
}

// IIC发送一个字节
void VirtI2C_Send_Byte(VirtI2C_HandleTypeDef* hi2c, uint8_t txd)
{
	uint8_t t;
	hi2c->pinSdaDir(1);
	hi2c->pinSclSet(0);//拉低时钟开始数据传输
	for(t = 0; t < 8; t++)
	{
		if((txd & 0x80) >> 7)
		{
			hi2c->pinSdaSet(1);
		}
		else
		{
			hi2c->pinSdaSet(0);
		}
		txd <<= 1;
		hi2c->delayUs(VirtI2C_DelayUS);   //对TEA5767这三个延时都是必须的
		hi2c->pinSclSet(1);
		hi2c->delayUs(VirtI2C_DelayUS);
		hi2c->pinSclSet(0);
		hi2c->delayUs(VirtI2C_DelayUS);
	}
}

//读1个字节,ack=1时,发送ACK,ack=0,发送nACK
uint8_t VirtI2C_Recv_Byte(VirtI2C_HandleTypeDef* hi2c, uint8_t ack)
{
	uint8_t i, receive = 0;
	hi2c->pinSdaDir(0);//SDA设置为输入
	for(i = 0; i < 8; i++)
	{
		hi2c->pinSclSet(0);
		hi2c->delayUs(VirtI2C_DelayUS);
		hi2c->pinSclSet(1);
		receive <<= 1;
		if(hi2c->pinSdaGet())receive++;
		hi2c->delayUs(VirtI2C_DelayUS);
	}
	if(!ack)
		VirtI2C_NAck(hi2c);//发送nACK
	else
		VirtI2C_Ack(hi2c); //发送ACK
	return receive;
}

void VirtI2C_MasterWriteByte(VirtI2C_HandleTypeDef* hi2c, uint8_t DevAddress, uint8_t MemAddress, uint8_t Data)
{
	VirtI2C_Start(hi2c);
	VirtI2C_Send_Byte(hi2c, DevAddress & 0xFE);
	VirtI2C_Wait_Ack(hi2c);
	VirtI2C_Send_Byte(hi2c, MemAddress);
	VirtI2C_Wait_Ack(hi2c);
	VirtI2C_Send_Byte(hi2c, Data);
	VirtI2C_Wait_Ack(hi2c);
	VirtI2C_Stop(hi2c);
}

uint8_t VirtI2C_MasterReadByte(VirtI2C_HandleTypeDef* hi2c, uint8_t DevAddress, uint8_t MemAddress)
{
	uint8_t Data = 0;
	VirtI2C_Start(hi2c);
	VirtI2C_Send_Byte(hi2c, DevAddress & 0xFE);
	VirtI2C_Wait_Ack(hi2c);
	VirtI2C_Send_Byte(hi2c, MemAddress);
	VirtI2C_Wait_Ack(hi2c);
	VirtI2C_Start(hi2c);
	VirtI2C_Send_Byte(hi2c, DevAddress | 0x01);  // 切换为接收模式
	VirtI2C_Wait_Ack(hi2c);
	Data = VirtI2C_Recv_Byte(hi2c, 0);
	VirtI2C_Stop(hi2c);
	return Data;
}