基于STM32单片机编写的驱动与设备分离的IIC驱动(模拟IIC,可兼容其他32系列单片机)

文章提供了一种基于STM32F103RE的IIC协议模拟驱动代码,详细解释了各个函数的功能,如启动/停止信号、数据传输和应答检测等。代码已验证可支持一条总线上挂载多个设备的通信,并且可以在FreeRTOS中稳定运行。文章还介绍了如何将该驱动移植到其他32系列芯片以及HAL库上。
摘要由CSDN通过智能技术生成
概述:
        IIC协议大家应该都是很熟悉的,这里我提供一种模拟IIC协议的一个驱动代码,复用起来非常方便,其驱动理念是基于设备与驱动分离的思想,是我学习linux期间备受感触的地方。废话不多说,直接贴代码:
(注:以下代码都基于STM32F103RE编写的,后边我会讲解如何移植到其他32系列芯片。)
(注:该驱动代码本人已经验证过,在一条IIC总线上挂载三个IIC从机设备可以正常通讯。)
(注:该驱动代码使用的是标准库,下面也会写出如何移植到hal库上)
(注:该驱动库也在FreeRTOS中调用,运行稳定)

驱动代码:

iic_driver.c:

#include "iic-driver.h"

/*
*********************************************************************************************************
*	函 数 名: i2c_Delay
*	功能说明: I2C总线位延迟,最快400KHz
*	形    参:无
*	返 回 值: 无
*********************************************************************************************************
*/
static void i2c_Delay(void)
{
	/* 
	 	下面的时间是通过逻辑分析仪测试得到的。
    工作条件:CPU主频72MHz ,MDK编译环境,1级优化

		循环次数为10时,SCL频率 = 205KHz
		循环次数为7时,SCL频率 = 347KHz, SCL高电平时间1.5us,SCL低电平时间2.87us
	 	循环次数为5时,SCL频率 = 421KHz, SCL高电平时间1.25us,SCL低电平时间2.375us
	*/
	for (i = 0; i < CycleCount; i++);
}

/*
*********************************************************************************************************
*	函 数 名: i2c_start
*	功能说明: CPU发起I2C总线启动信号
*	形    参:无
*	返 回 值: 无
*********************************************************************************************************
*/
void i2c_start(iic_node *iic){
	/* 当SCL高电平时,SDA出现一个下跳沿表示I2C总线启动信号 */
	iic->sda_on(iic->GPIOx,iic->GPIO_SDA_Pin);
	iic->scl_on(iic->GPIOx,iic->GPIO_SCL_Pin);
	i2c_Delay();
	iic->sda_off(iic->GPIOx,iic->GPIO_SDA_Pin);
	i2c_Delay();
	iic->scl_off(iic->GPIOx,iic->GPIO_SCL_Pin);
	i2c_Delay();
}
/*
*********************************************************************************************************
*	函 数 名: i2c_start
*	功能说明: CPU发起I2C总线停止信号
*	形    参:无
*	返 回 值: 无
*********************************************************************************************************
*/
void i2c_Stop(iic_node *iic){
	/* 当SCL高电平时,SDA出现一个上跳沿表示I2C总线停止信号 */
	iic->sda_off(iic->GPIOx,iic->GPIO_SDA_Pin);
	iic->scl_on(iic->GPIOx,iic->GPIO_SCL_Pin);
	i2c_Delay();
	iic->sda_on(iic->GPIOx,iic->GPIO_SDA_Pin);
}
/*
*********************************************************************************************************
*	函 数 名: i2c_SendByte
*	功能说明: CPU向I2C总线设备发送8bit数据
*	形    参:_ucByte : 等待发送的字节
*	返 回 值: 无
*********************************************************************************************************
*/

void i2c_SendByte(iic_node *iic,uint8_t _ucByte){
	uint8_t i;
	/* 先发送字节的高位bit7 */
	for (i = 0; i < 8; i++){
		if (_ucByte & 0x80){
			iic->sda_on(iic->GPIOx,iic->GPIO_SDA_Pin);
		}else{
			iic->sda_off(iic->GPIOx,iic->GPIO_SDA_Pin);
		}
		i2c_Delay();
		iic->scl_on(iic->GPIOx,iic->GPIO_SCL_Pin);
		i2c_Delay();
		iic->scl_off(iic->GPIOx,iic->GPIO_SCL_Pin);
		if (i == 7){
			iic->sda_on(iic->GPIOx,iic->GPIO_SDA_Pin); // 释放总线
		}
		_ucByte <<= 1;	/* 左移一个bit */
		i2c_Delay();
	}
}
/*
*********************************************************************************************************
*	函 数 名: i2c_ReadByte
*	功能说明: CPU从I2C总线设备读取8bit数据
*	形    参:无
*	返 回 值: 读到的数据
*********************************************************************************************************
*/
uint8_t i2c_ReadByte(iic_node *iic){
	uint8_t i;
	uint8_t value;
	/* 读到第1个bit为数据的bit7 */
	value = 0;
	for (i = 0; i < 8; i++){
		value <<= 1;
		iic->scl_on(iic->GPIOx,iic->GPIO_SCL_Pin);
		i2c_Delay();
		if (iic->get_sda_value(iic->GPIOx,iic->GPIO_SDA_Pin)){
			value++;
		}
		iic->scl_off(iic->GPIOx,iic->GPIO_SCL_Pin);
		i2c_Delay();
	}
	return value;
}
/*
*********************************************************************************************************
*	函 数 名: i2c_WaitAck
*	功能说明: CPU产生一个时钟,并读取器件的ACK应答信号
*	形    参:无
*	返 回 值: 返回0表示正确应答,1表示无器件响应
*********************************************************************************************************
*/
uint8_t i2c_WaitAck(iic_node *iic){
	uint8_t re;
	iic->sda_on(iic->GPIOx,iic->GPIO_SDA_Pin);	/* CPU释放SDA总线 */
	i2c_Delay();
	iic->scl_on(iic->GPIOx,iic->GPIO_SCL_Pin);	/* CPU驱动SCL = 1, 此时器件会返回ACK应答 */
	i2c_Delay();
	if (iic->get_sda_value(iic->GPIOx,iic->GPIO_SDA_Pin)){	/* CPU读取SDA口线状态 */
		re = 1;
	}else{
		re = 0;
	}
	iic->scl_off(iic->GPIOx,iic->GPIO_SCL_Pin);
	i2c_Delay();
	return re;
}
/*
*********************************************************************************************************
*	函 数 名: i2c_Ack
*	功能说明: CPU产生一个ACK信号
*	形    参:无
*	返 回 值: 无
*********************************************************************************************************
*/
void i2c_Ack(iic_node *iic){
	iic->sda_off(iic->GPIOx,iic->GPIO_SDA_Pin);	/* CPU驱动SDA = 0 */
	i2c_Delay();
	iic->scl_on(iic->GPIOx,iic->GPIO_SCL_Pin);	/* CPU产生1个时钟 */
	i2c_Delay();
	iic->scl_off(iic->GPIOx,iic->GPIO_SCL_Pin);
	i2c_Delay();
	iic->sda_on(iic->GPIOx,iic->GPIO_SDA_Pin);	/* CPU释放SDA总线 */
}
/*
*********************************************************************************************************
*	函 数 名: i2c_NAck
*	功能说明: CPU产生1个NACK信号
*	形    参:无
*	返 回 值: 无
*********************************************************************************************************
*/
void i2c_NAck(iic_node *iic){
	iic->sda_on(iic->GPIOx,iic->GPIO_SDA_Pin);	/* CPU驱动SDA = 1 */
	i2c_Delay();
	iic->scl_on(iic->GPIOx,iic->GPIO_SCL_Pin);	/* CPU产生1个时钟 */
	i2c_Delay();
	iic->scl_off(iic->GPIOx,iic->GPIO_SCL_Pin);
	i2c_Delay();
}

/*
*********************************************************************************************************
*	函 数 名: i2c_CheckDevice
*	功能说明: 检测I2C总线设备,CPU向发送设备地址,然后读取设备应答来判断该设备是否存在
*	形    参:_Address:设备的I2C总线地址
*	返 回 值: 返回值 0 表示正确, 返回1表示未探测到
*********************************************************************************************************
*/
uint8_t i2c_CheckDevice(iic_node *iic,uint8_t _Address){
	uint8_t ucAck = 1;
	i2c_start(iic);		/* 发送启动信号 */
	/* 发送设备地址+读写控制bit(0 = w, 1 = r) bit7 先传 */
	i2c_SendByte(iic,_Address | I2C_WR);
	ucAck = i2c_WaitAck(iic);	/* 检测设备的ACK应答 */

	i2c_Stop(iic);			/* 发送停止信号 */

	return ucAck;
}
/*
*********************************************************************************************************
*	函 数 名: iic_device_checkok
*	功能说明: 判断IIC设备是否正常
*	形    参:无
*	返 回 值: 1 表示正常, 0 表示不正常
*********************************************************************************************************
*/
uint8_t iic_device_checkok(iic_node *iic,uint8_t addr){
	if (i2c_CheckDevice(iic,addr) == 0){
		return 1;
	}else{
		/* 失败后,切记发送I2C总线停止信号 */
		i2c_Stop(iic);
		return 0;
	}
}
/*
*********************************************************************************************************
*	函 数 名: iic_write_byte
*	功能说明: 向从机设备写入一个字节
*	形    参:value:要写入的数据 addr:寄存器的地址
*	返 回 值: void
*********************************************************************************************************
*/
void iic_write_byte(iic_node *iic,uint8_t addr,uint8_t regster_addr,uint8_t value){
	i2c_start(iic);
	i2c_SendByte(iic,addr | I2C_WR);
	i2c_WaitAck(iic);
	i2c_SendByte(iic,regster_addr);
	i2c_WaitAck(iic);
	i2c_SendByte(iic,value);
	i2c_WaitAck(iic);
	i2c_Stop(iic);
}
/*
*********************************************************************************************************
*	函 数 名: iic_read_bytes
*	功能说明: 读取多个字节的数据
*	形    参:
*						iic : 设备
*						addr:设备地址
*						_usAddress : 起始地址
*			 			_usSize : 要读多少字节数据
*			 			buff : 存放读到的数据的缓冲区指针
*	返 回 值: 0 表示失败,1表示成功
*********************************************************************************************************
*/
uint8_t iic_read_bytes( iic_node *iic,uint8_t addr,uint16_t _usAddress,uint8_t *buff,uint8_t _usSize){
	uint8_t i = 0;

	/* 采用串行EEPROM随即读取指令序列,连续读取若干字节 */

	/* 第1步:发起I2C总线启动信号 */
	i2c_start(iic);

	/* 第2步:发起控制字节,高7bit是地址,bit0是读写控制位,0表示写,1表示读 */
	i2c_SendByte(iic,addr | I2C_WR);	/* 此处是写指令 */

	/* 第3步:等待ACK */
	if (i2c_WaitAck(iic) != 0)
	{
		goto cmd_fail;	/* EEPROM器件无应答 */
	}

	/* 第4步:发送寄存器地址,表示要读取的寄存器地址 */
	i2c_SendByte(iic,(uint8_t)_usAddress);

	/* 第5步:等待ACK */
	if (i2c_WaitAck(iic) != 0)
	{
		goto cmd_fail;	/* EEPROM器件无应答 */
	}

	/* 第6步:重新启动I2C总线。前面的代码的目的向EEPROM传送地址,下面开始读取数据 */
	i2c_start(iic);

	/* 第7步:发起控制字节,高7bit是地址,bit0是读写控制位,0表示写,1表示读 */
	i2c_SendByte(iic,addr | I2C_RD);	/* 此处是读指令 */

	/* 第8步:发送ACK */
	if (i2c_WaitAck(iic) != 0)
	{
		goto cmd_fail;	/* EEPROM器件无应答 */
	}
	/* 第9步:循环读取数据 */
	for (i = 0; i < _usSize; i++)
	{
		buff[i] = i2c_ReadByte(iic);	/* 读1个字节 */

		/* 每读完1个字节后,需要发送Ack, 最后一个字节不需要Ack,发Nack */
		if (i != _usSize - 1)
		{
			i2c_Ack(iic);	/* 中间字节读完后,CPU产生ACK信号(驱动SDA = 0) */
		}
		else
		{
			i2c_NAck(iic);	/* 最后1个字节读完后,CPU产生NACK信号(驱动SDA = 1) */
		}
	}
	/* 发送I2C总线停止信号 */
	i2c_Stop(iic);
	return 1;	/* 执行成功 */

cmd_fail: /* 命令执行失败后,切记发送停止信号,避免影响I2C总线上其他设备 */
	/* 发送I2C总线停止信号 */
	i2c_Stop(iic);
	return 0;
}

/*
*********************************************************************************************************
*	函 数 名: iic_read_two_byte
*	功能说明: 读取两个字节的数据
*	形    参:	_usAddress : 起始地址
*			 			_usSize : 要读多少字节数据
*			 			_pReadBuf : 存放读到的数据的缓冲区指针
*	返 回 值: 0 表示失败,1表示成功
*********************************************************************************************************
*/
uint16_t iic_read_two_byte( iic_node *iic,uint8_t addr,uint16_t _usAddress)
{
	uint16_t temp = 0;

	/* 采用串行EEPROM随即读取指令序列,连续读取若干字节 */

	/* 第1步:发起I2C总线启动信号 */
	i2c_start(iic);

	/* 第2步:发起控制字节,高7bit是地址,bit0是读写控制位,0表示写,1表示读 */
	i2c_SendByte(iic,addr | I2C_WR);	/* 此处是写指令 */

	/* 第3步:等待ACK */
	if (i2c_WaitAck(iic) != 0)
	{
		goto cmd_fail;	/* EEPROM器件无应答 */
	}

	/* 第4步:发送寄存器地址,表示要读取的寄存器地址 */
	i2c_SendByte(iic,(uint8_t)_usAddress);

	/* 第5步:等待ACK */
	if (i2c_WaitAck(iic) != 0)
	{
		goto cmd_fail;	/* EEPROM器件无应答 */
	}

	/* 第6步:重新启动I2C总线。前面的代码的目的向EEPROM传送地址,下面开始读取数据 */
	i2c_start(iic);

	/* 第7步:发起控制字节,高7bit是地址,bit0是读写控制位,0表示写,1表示读 */
	i2c_SendByte(iic,addr | I2C_RD);	/* 此处是读指令 */

	/* 第8步:发送ACK */
	if (i2c_WaitAck(iic) != 0)
	{
		goto cmd_fail;	/* EEPROM器件无应答 */
	}
	temp =(i2c_ReadByte(iic) << 8);	/* 读1个字节 */
	i2c_Ack(iic);	/* 中间字节读完后,CPU产生ACK信号(驱动SDA = 0) */
	temp |=(i2c_ReadByte(iic) & 0xff);
	/* 每读完1个字节后,需要发送Ack, 最后一个字节不需要Ack,发Nack */
	i2c_NAck(iic);	/* 最后1个字节读完后,CPU产生NACK信号(驱动SDA = 1) */
	/* 发送I2C总线停止信号 */
	i2c_Stop(iic);
	return temp;	/* 执行成功 */

cmd_fail: /* 命令执行失败后,切记发送停止信号,避免影响I2C总线上其他设备 */
	/* 发送I2C总线停止信号 */
	i2c_Stop(iic);
	return 0;
}

/*
*********************************************************************************************************
*	函 数 名: iic_read_one_byte
*	功能说明: 读取1个字节的数据
*	形    参:	_usAddress : 起始地址
*			 			_usSize : 要读多少字节数据
*			 			_pReadBuf : 存放读到的数据的缓冲区指针
*	返 回 值: 0 表示失败,1表示成功
*********************************************************************************************************
*/
uint8_t iic_read_one_byte( iic_node *iic,uint8_t addr,uint16_t _usAddress){
	uint8_t temp = 0;

	/* 采用串行EEPROM随即读取指令序列,连续读取若干字节 */

	/* 第1步:发起I2C总线启动信号 */
	i2c_start(iic);

	/* 第2步:发起控制字节,高7bit是地址,bit0是读写控制位,0表示写,1表示读 */
	i2c_SendByte(iic,addr | I2C_WR);	/* 此处是写指令 */

	/* 第3步:等待ACK */
	if (i2c_WaitAck(iic) != 0)
	{
		goto cmd_fail;	/* EEPROM器件无应答 */
	}

	/* 第4步:发送寄存器地址,表示要读取的寄存器地址 */
	i2c_SendByte(iic,(uint8_t)_usAddress);

	/* 第5步:等待ACK */
	if (i2c_WaitAck(iic) != 0)
	{
		goto cmd_fail;	/* EEPROM器件无应答 */
	}

	/* 第6步:重新启动I2C总线。前面的代码的目的向EEPROM传送地址,下面开始读取数据 */
	i2c_start(iic);

	/* 第7步:发起控制字节,高7bit是地址,bit0是读写控制位,0表示写,1表示读 */
	i2c_SendByte(iic,addr | I2C_RD);	/* 此处是读指令 */

	/* 第8步:发送ACK */
	if (i2c_WaitAck(iic) != 0)
	{
		goto cmd_fail;	/* EEPROM器件无应答 */
	}
	temp =i2c_ReadByte(iic)&0xff;	/* 读1个字节 */
	/* 每读完1个字节后,需要发送Ack, 最后一个字节不需要Ack,发Nack */
	i2c_NAck(iic);	/* 最后1个字节读完后,CPU产生NACK信号(驱动SDA = 1) */
	/* 发送I2C总线停止信号 */
	i2c_Stop(iic);
	return temp;	/* 执行成功 */

cmd_fail: /* 命令执行失败后,切记发送停止信号,避免影响I2C总线上其他设备 */
	/* 发送I2C总线停止信号 */
	i2c_Stop(iic);
	return 0;
}



iic_driver.h

#ifndef _BSP_I2C_GPIO_H
#define _BSP_I2C_GPIO_H

#include "stm32f10x.h"

#define I2C_WR	0		/* 写控制bit */
#define I2C_RD	1		/* 读控制bit */

//循环次数,用来决定iic的延时时间
#define CycleCount 10

//定义IIC设备结构体类型
typedef struct {
	uint8_t flag;    //预留标志位
	GPIO_TypeDef* GPIOx;
	uint16_t GPIO_SDA_Pin;
	uint16_t GPIO_SCL_Pin;
	void (*scl_on)(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
	void (*scl_off)(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
	void (*sda_on)(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
	void (*sda_off)(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
	uint8_t (*get_sda_value)(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
}iic_node;

void i2c_start(iic_node *iic);
void i2c_Stop(iic_node *iic);
void i2c_SendByte(iic_node *iic,uint8_t _ucByte);
uint8_t i2c_ReadByte(iic_node *iic);
uint8_t i2c_WaitAck(iic_node *iic);
void i2c_Ack(iic_node *iic);
void i2c_NAck(iic_node *iic);
uint8_t i2c_CheckDevice(iic_node *iic,uint8_t _Address);
uint8_t iic_device_checkok(iic_node *iic,uint8_t addr);
void iic_write_byte(iic_node *iic,uint8_t addr,uint8_t regster_addr,uint8_t value);
uint8_t iic_read_bytes( iic_node *iic,uint8_t addr,uint16_t _usAddress,uint8_t *buff,uint8_t _usSize);
uint16_t iic_read_two_byte( iic_node *iic,uint8_t addr,uint16_t _usAddress);
uint8_t iic_read_one_byte( iic_node *iic,uint8_t addr,uint16_t _usAddress);



#endif

设备代码:(以SD2508时钟芯片为例)

sd2508.c

#include "sd2058.h"


iic_node iic_sd2058 = {
	.GPIOx = GPIOC,
	.GPIO_SDA_Pin = IIC_SDA_PIN,
	.GPIO_SCL_Pin = IIC_SCK_PIN,
	.scl_on = &GPIO_SetBits,
	.scl_off = &GPIO_ResetBits,
	.sda_on = &GPIO_SetBits,
	.sda_off = &GPIO_ResetBits,
	.get_sda_value = &GPIO_ReadInputDataBit,
};
/**********************************************************************
  * @ 函数名  : iic_gpio_init
  * @ 功能说明:  
  * @ 参数    : 
  * @ 返回值  :
  ********************************************************************/
void iic_gpio_init(void){
	GPIO_InitTypeDef GPIO_InitStructure;
	
	RCC_APB2PeriphClockCmd( IIC_PORT_CLK, ENABLE);
	
	GPIO_InitStructure.GPIO_Pin = IIC_SDA_PIN |IIC_SCK_PIN;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(IIC_SDA_GPIO, &GPIO_InitStructure);	
	i2c_Stop(&iic_sd2058);
}
/**********************************************************************
  * @ 函数名  : check_at24c02_device
  * @ 功能说明: 检测at24c02是否在线
  * @ 参数    : 
  * @ 返回值  :
  ********************************************************************/
uint8_t check_sd2058_device(iic_node *driver,uint8_t device_addr){
	return i2c_CheckDevice(driver,device_addr);
}
/**********************************************************************
  * @ 函数名  : write_time_on
  * @ 功能说明: 时钟芯片写开锁
  * @ 参数    : 
  * @ 返回值  :
  ********************************************************************/
static uint8_t write_time_on(iic_node *driver){
	if(check_sd2058_device(driver,SD2058_ADDR) != 0)return 0;
	iic_write_byte(driver,SD2058_ADDR,0x10,0x80);
	iic_write_byte(driver,SD2058_ADDR,0x0f,0x84);
	return 1;	
}
/**********************************************************************
  * @ 函数名  : write_time_off
  * @ 功能说明: 时钟芯片写上锁
  * @ 参数    : 
  * @ 返回值  :
  ********************************************************************/
static uint8_t write_time_off(iic_node *driver){
	if(check_sd2058_device(driver,SD2058_ADDR) != 0)return 0;
	iic_write_byte(driver,SD2058_ADDR,0x0f,0);
	iic_write_byte(driver,SD2058_ADDR,0x10,0);
	return 1;
}
/**********************************************************************
  * @ 函数名  : write_sd2058_time
  * @ 功能说明: 向sd2058内部更新时间 
  * @ 参数    : 
  * @ 返回值  :
  ********************************************************************/
uint8_t write_sd2058_time(iic_node *driver,Time_Struct *time_value){
	if(write_time_on(driver)){
		i2c_start(driver);
		i2c_SendByte(driver,SD2058_ADDR | I2C_WR);
		if(i2c_WaitAck(driver)){
			return 0;
		}
		i2c_SendByte(driver,0);
		if(i2c_WaitAck(driver)){
			return 0;
		}
		i2c_SendByte(driver,time_value->second);
		if(i2c_WaitAck(driver)){
			return 0;
		}
		i2c_SendByte(driver,time_value->min);
		if(i2c_WaitAck(driver)){
			return 0;
		}
		i2c_SendByte(driver,time_value->hour | 0x80);
		if(i2c_WaitAck(driver)){
			return 0;
		}	
		i2c_SendByte(driver,time_value->week);
		if(i2c_WaitAck(driver)){
			return 0;
		}
		i2c_SendByte(driver,time_value->day);
		if(i2c_WaitAck(driver)){
			return 0;
		}
		i2c_SendByte(driver,time_value->month);
		if(i2c_WaitAck(driver)){
			return 0;
		}
		i2c_SendByte(driver,time_value->year);
		if(i2c_WaitAck(driver)){
			return 0;
		}
		i2c_Stop(driver);
		write_time_off(driver);
		return 1; 
	}
	return 0;
}
/**********************************************************************
  * @ 函数名  : read_sd2058_time
  * @ 功能说明: 读取sd2058内部时间 
  * @ 参数    : 
  * @ 返回值  :
  ********************************************************************/
void read_sd2058_time(iic_node *driver,Time_Struct *time_value){
	uint8_t buff[7] = {0};
	iic_read_bytes(driver,SD2058_ADDR,0,buff,7);
	time_value->second = buff[0];
	time_value->min = buff[1];
	time_value->hour = buff[2] & 0x7F;
	time_value->week = buff[3];
	time_value->day = buff[4];
	time_value->month = buff[5];
	time_value->year = buff[6];
}
/**********************************************************************
  * @ 函数名  : comp_time_size
  * @ 功能说明: 比较时间段
  * @ 参数    : 
  * @ 返回值  :
  ********************************************************************/
uint8_t comp_time_size(Time_Struct *time_value,time_t at_time,uint8_t begin_hour,uint8_t begin_min,uint8_t end_hour,uint8_t end_min){
	time_t begin_time = 0,end_time = 0;
	struct tm temp = {
    .tm_year = (time_value->year >> 4) * 10 + (time_value->year & 0x0F)+2000,
    .tm_mon = ((time_value->month & 0x10) >> 4) * 10 + (time_value->month & 0x0F),
    .tm_mday = ((time_value->day & 0x30) >> 4) * 10 + (time_value->day & 0x0F),
    .tm_hour = ((begin_hour & 0x70) >> 4) * 10 + (begin_hour & 0x0F),
    .tm_min = ((begin_min & 0x70) >> 4) * 10 + (begin_min & 0x0F),
    .tm_sec = ((time_value->second & 0x70) >> 4) * 10 + (time_value->second & 0x0F),
	};
	begin_time = mktime(temp);
	temp.tm_hour = ((end_hour & 0x70) >> 4) * 10 + (end_hour & 0x0F),
	temp.tm_min = ((end_min & 0x70) >> 4) * 10 + (end_min & 0x0F),
	end_time = mktime(temp);
	if((at_time > begin_time) && (at_time < end_time))
		return 1;
	else
		return 0;
}

sd2058.h 

#ifndef __SD2058_H_
#define __SD2058_H_

#include "stm32f10x.h"
#include "iic-driver.h"
#include "localtime.h"

#define SD2058_ADDR 0x64

#define IIC_PORT_CLK	RCC_APB2Periph_GPIOC

#define IIC_SDA_GPIO		GPIOC
#define IIC_SDA_PIN			GPIO_Pin_2

#define IIC_SCK_GPIO		GPIOC	
#define IIC_SCK_PIN			GPIO_Pin_3

typedef struct{
	uint8_t second;
	uint8_t min;
	uint8_t	hour;
	uint8_t week;
	uint8_t	day;
	uint8_t	month;
	uint8_t year;
}Time_Struct;

extern iic_node iic_sd2058;
extern Time_Struct Time_Data;

uint8_t check_sd2058_device(iic_node *driver,uint8_t device_addr);
uint8_t write_sd2058_time(iic_node *driver,Time_Struct *time_value);
void read_sd2058_time(iic_node *driver,Time_Struct *time_value);
uint8_t comp_time_size(Time_Struct *time_value,time_t at_time,uint8_t begin_hour,uint8_t begin_min,uint8_t end_hour,uint8_t end_min);

#endif

IIC驱动使用讲解:(以上面的SD2508为例)
        

 IIC驱动代码为两个两个文件,一个是iic_driver.c与其头文件iic_driver.h。

iic_driver.c存放的是驱动的实现,我们主要关心的是iic_driver.h这个头文件,因为不管是我们做iic通讯还是移植到其他32设备上,主要关心的只是iic_driver.h头文件,而iic_driver.c我们一般不需要动他。

在iic_driver.h中,设备结构体是实现iic关联硬件GPIO口的关键,该结构体实现了硬件GPIO的关联与GPIO的读写。

//定义IIC设备结构体类型
typedef struct {
	uint8_t flag;
	GPIO_TypeDef* GPIOx;
	uint16_t GPIO_SDA_Pin;
	uint16_t GPIO_SCL_Pin;
	void (*scl_on)(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
	void (*scl_off)(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
	void (*sda_on)(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
	void (*sda_off)(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
	uint8_t (*get_sda_value)(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
}iic_node;

我们在编写IIC设备代码时,只需要初始化这个结构体实现IIC设备硬件连接的GPIO就实现了硬件与设备的关联。

iic_node iic_sd2058 = {
	.GPIOx = GPIOC,
	.GPIO_SDA_Pin = IIC_SDA_PIN,
	.GPIO_SCL_Pin = IIC_SCK_PIN,
	.scl_on = &GPIO_SetBits,    //标准库中的GPIO置1函数
	.scl_off = &GPIO_ResetBits, //标准库中的GPIO复0函数
	.sda_on = &GPIO_SetBits,
	.sda_off = &GPIO_ResetBits,
	.get_sda_value = &GPIO_ReadInputDataBit, //标准库中的GPIO读取位值函数
};

第二步实现GPIO的初始化:(将IIC引脚初始化成开漏状态)

/**********************************************************************
  * @ 函数名  : iic_gpio_init
  * @ 功能说明:
  * @ 参数    : 
  * @ 返回值  :
  ********************************************************************/
void iic_gpio_init(void){
	GPIO_InitTypeDef GPIO_InitStructure;
	
	RCC_APB2PeriphClockCmd( IIC_PORT_CLK, ENABLE);
	
	GPIO_InitStructure.GPIO_Pin = IIC_SDA_PIN |IIC_SCK_PIN;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(IIC_SDA_GPIO, &GPIO_InitStructure);	
	i2c_Stop(&iic_sd2058);
}

第三步调用iic_driver.h中的各各函数开始实现iic设备的读写操作。具体怎么操作可以翻看以下上面的sd2058.c的实现。很简单。

以上是基于STM32F103标准库的实现与使用。下面讲一下如何对这个iic驱动文件进行移植。该驱动的移植也很简单,需要改动两个地方,第一个是iic_driver.h中的

#define CycleCount 10

 该宏定义定义了iic驱动通讯频率,该值由自己单片机的自身的主频而定,请自行计算。

第二个需要更改的地方为iic_driver.h中的iic设备结构体。

//定义IIC设备结构体类型
typedef struct {
	uint8_t flag;
	GPIO_TypeDef* GPIOx;
	uint16_t GPIO_SDA_Pin;
	uint16_t GPIO_SCL_Pin;
	void (*scl_on)(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
	void (*scl_off)(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
	void (*sda_on)(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
	void (*sda_off)(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
	uint8_t (*get_sda_value)(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
}iic_node;

以移植到hal库为例:(我使用的是stm32l051c8) 

首先打开HAL库的GPIO对应的头文件找到我们需要用到的gpio设置函数与gpio读取函数:

 然后根据hal库的GPIO设置函数与读取函数的声明来更改iic_driver.h中的设备结构体:

(注:要看一下要移植芯片的    

    GPIO_TypeDef* GPIOx;
    uint16_t GPIO_SDA_Pin;
    uint16_t GPIO_SCL_Pin;

    的具体l类型,要适应对应的芯片)

更改完成之后,就可以编写GPIO初始化,设备的读写函数了。跟上面的sd2058的步骤一样了。

需要注意的是: HAL库中的GPIO设置函数只有一个,也就是说GPIO置1和置0函数只有一个。

而 我们的iic结构体中是有gpio的on和off操作的。所以在移植到这个芯片的时候,我不得不改一下iic_driver.c文件以适应该驱动。我的更改如下:

 在iic_driver中的驱动函数中增加GPIO_PIN_SET和GPIO_PIN_RESET参数。但是如果我们移植的是GD系列的,或其他国产芯片系列的芯片,GPIO的置1和置0操作是分开的时候,iic_driver.c是不需要动的,我们只需要将厂商提供的操作库中的gpio操作头文件的gpio置1和gpio置0和读取gpio的值的函数声明复制然后粘贴到iic_driver.h中的iic设备结构体中的相应函数上即可,移植起来很是方便。

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值