GD 32 IIC 驱动代码

前言:


 学会IIC驱动的原理,时序和代码实现


1.0 GPIO初始化

从原理图中可以看出IIC对应的时钟线和数据线在PB端口,因此需要初始化GPIOB的时钟,同时初始化GPIOB_PIN_6 | 7,的引脚,设置为开漏输出模式。

什么事开漏?为什么使用开漏?


GPIO_MODE_OUT_OD 是一个配置选项,用于设置微控制器 GPIO(General Purpose Input/Output)引脚的工作模式。在 GD32 系列微控制器中,这代表“开漏输出”(Open Drain Output)模式。

在开漏输出模式下,GPIO 引脚可以被驱动到低电平(接地),或者高阻态(不连接)。当引脚设置为高阻态时,它既不拉高也不拉低,因此外部电路可以将其电平拉高或保持不变。这种模式常用于需要多个设备共享同一信号线的场合,例如 I2C 总线,或是当输出级需要与不同电压级别的逻辑电平兼容时。

在开漏输出模式下,如果需要稳定的高电平输出,通常会在引脚外部接一个上拉电阻至电源。这样,在 GPIO 设置为高阻态时,上拉电阻会将引脚电平拉高;而在 GPIO 设置为低电平时,引脚则会被拉低至地电平。


 GPIO初始化代码:

// GPIO初始化
static void GpioInit()
{
	// rcu 初始化Gpio时钟
	rcu_periph_clock_enable(RCU_GPIOB);
	// gpio 初始化
	gpio_init
	(
		GPIOB,
		GPIO_MODE_OUT_OD,
		GPIO_OSPEED_10MHZ,
		GPIO_PIN_6 | GPIO_PIN_7
	);
	
}


// 初始化GPIO
void EepromDrvInit(void)
{
	// GPIO初始化函数
	GpioInit();
}

 2.0 宏定义引脚

宏定义的作用是简化程序,防止程序出现魔鬼数字,本质是温文本展开,注意优先级问题

// 宏定义SCL 与 SDA
#define GET_I2C_SDA() gpio_input_bit_get(GPIOB,GPIO_PIN_7) 		// 读取SDA端口
#define SET_I2C_SCL() gpio_bit_set(GPIOB, GPIO_PIN_6)           // 时钟线SCL输出高电平
#define CLR_I2C_SCL() gpio_bit_reset(GPIOB, GPIO_PIN_6)         // 时钟线SCL输出低电平
#define SET_I2C_SDA() gpio_bit_set(GPIOB, GPIO_PIN_7)           // 数据线SDA输出高电平
#define CLR_I2C_SDA() gpio_bit_reset(GPIOB, GPIO_PIN_7)         // 数据线SDA输出低电平

3.0 IIC 起始信号

起始信号:

/**
*******************************************************************
* @function 产生IIC起始时序,准备发送或接收数据前必须由起始序列开始 
* @param
* @return 
* @brief    SCL为高电平时,SDA由高电平向低电平跳变,开始传输数据 
*           生成下图所示的波形图,即为起始时序 
*                1 2    3     4   
*                    __________     
*           SCL : __/          \_____ 
*                 ________          
*           SDA :         \___________ 
*******************************************************************
*/
static void I2CStart(void)
{
	SET_I2C_SDA();          // 1#数据线SDA输出高电平
	SET_I2C_SCL();          // 2#时钟线SCL输出高电平   
	DelayNus(4);            // 延时4us
	CLR_I2C_SDA();          // 3#数据线SDA输出低电平 
	DelayNus(4);            // 延时4us
	CLR_I2C_SCL();          // 4#时钟线SCL输出低电平,保持I2C的时钟线SCL为低电平,准备发送或接收数据 
	DelayNus(4);            // 延时4us
}

4.0 IIC 停止信号

停止信号:

/**
*******************************************************************
* @function 产生IIC停止时序  
* @param
* @return 
* @brief    SCL为高电平时,SDA由低电平向高电平跳变,结束传输数据 
*          生成下图所示的波形图,即为停止时序 
*                1 2   3  4   
*                       _______________     
*          SCL : ______/          
*                __        ____________  
*          SDA:    \______/
*******************************************************************
*/
static void I2CStop(void)
{
	CLR_I2C_SDA();          //2#数据线SDA输出低电平
	DelayNus(4);            //延时4us
	SET_I2C_SCL();          //3#时钟线SCL输出高电平
	DelayNus(4);  
	SET_I2C_SDA();          //4#数据线SDA输出高电平,发送I2C总线结束信号
}

5.0 IIC 发送一个字节

发送字节是从高位开始发送的,一步一步的将数据移动出去,需要使用for循环,并且将数据按位左移。

 

发送字节:

/**
*******************************************************************
* @function 发送一字节,数据从高位开始发送出去
* @param    byte
* @return 
* @brief    下面是具体的时序图 
*                1 2     3      4
*                         ______
*           SCL: ________/      \______    
*                ______________________    
*           SDA: \\\___________________
*******************************************************************
*/
static void I2CSendByte(uint8_t byte)
{                          
	for (uint8_t i = 0; i < 8; i++)   // 循环8次,从高到低取出字节的8个位
	{     
		if ((byte & 0x80))            // 2#取出字节最高位,并判断为‘0’还是‘1’,从而做出相应的操作
		{
			SET_I2C_SDA();            // 数据线SDA输出高电平,数据位为‘1’
		}
		else
		{  
			CLR_I2C_SDA();      	  // 数据线SDA输出低电平,数据位为‘0’
		}
		
		byte <<= 1;            		  // 左移一位,次高位移到最高位
		
		DelayNus(4);          		  // 延时4us
		SET_I2C_SCL();                // 3#时钟线SCL输出高电平
		DelayNus(4);          		  // 延时4us
		CLR_I2C_SCL();        		  // 4#时钟线SCL输出低电平
		DelayNus(4);                  // 延时4us  
	}  
}

6.0 IIC 接收一个字节

接收字节:

在这段I²C读取字节的代码中,byte++ 的作用是在检测到 SDA(Serial Data)线上的数据位为1时,将当前正在构建的字节值的最低位设为1。

具体来说,byte <<= 1; 这一行将 byte 的所有位都向左移动一位,腾出了最低位(最右边的位)。接下来,根据 SDA 线上的数据状态,byte 的最低位会被更新。如果 GET_I2C_SDA() 返回非零值(即SDA线上的数据位为1),那么 byte++ 将会使 byte 的值增加1,从而将最低位设为1。否则,如果 GET_I2C_SDA() 返回0(即SDA线上的数据位为0),byte 的值保持不变,最低位仍然为0。

/**
*******************************************************************
* @function 读取一字节数据
* @param    
* @return   读取的字节
* @brief    下面是具体的时序图
*                       ______
*           SCL: ______/      \___        
*                ____________________    
*           SDA: \\\\______________\\\
*******************************************************************
*/
static uint8_t I2CReadByte(void)
{
	uint8_t byte = 0;           		// byte用来存放接收的数据
	SET_I2C_SDA();                      // 释放SDA
	for (uint8_t i = 0; i < 8; i++)     // 循环8次,从高到低读取字节的8个位
	{
		SET_I2C_SCL();          		// 时钟线SCL输出高电平
		DelayNus(4);            		// 延时4us
		byte <<= 1;          			// 左移一位,空出新的最低位

		if (GET_I2C_SDA())       		// 读取数据线SDA的数据位
		{
			byte++;            			// 在SCL的上升沿后,数据已经稳定,因此可以取该数据,存入最低位
		}
		CLR_I2C_SCL();          		// 时钟线SCL输出低电平
		DelayNus(4);            		// 延时4us
	} 

	return byte;           				// 返回读取到的数据
}

7.0 IIC 接收应答

接收应答:

/**
*******************************************************************
* @function 等待接收端的应答信号
* @param    
* @return   1,接收应答失败;0,接收应答成功
* @brief    当SDA拉低后,表示接收到ACK信号,然后,拉低SCL,
*           此处表示发送端收到接收端的ACK
*                _______|____     
*           SCL:        |    \_________    
*                _______|     
*           SDA:         \_____________ 
*******************************************************************
*/
static bool I2CWaitAck(void)
{
	uint8_t errTimes = 0;
	
	SET_I2C_SDA();             // 释放SDA总线,很重要
	DelayNus(4);               // 延时4us
	
	SET_I2C_SCL();             // 时钟线SCL输出高电平
	DelayNus(4);               // 延时4us

	while (GET_I2C_SDA())      // 读回来的数据如果是高电平,即接收端没有应答
	{
		errTimes++;            // 计数器加1

		if (errTimes > 250)    // 如果超过250次,则判断为接收端出现故障,因此发送结束信号
		{
			I2CStop();         // 产生一个停止信号
			return false;      // 返回值为1,表示没有收到应答信号
		}
	}

	CLR_I2C_SCL();             // 表示已收到应答信号,时钟线SCL输出低电平
	DelayNus(4);               // 延时4us
	
	return true;               // 返回值为0,表示接收应答成功  
}

8.0 IIC 发送应答

发送应答:

/**
*******************************************************************
* @function 发送应答信号
* @param    
* @return   
* @brief    下面是具体的时序图 
*                 1 2     3      4      5     
*                         ______
*           SCL: ________/      \____________    
*                __                     ______
*           SDA:   \___________________/        
*******************************************************************
*/
void I2CSendAck(void)
{
	CLR_I2C_SDA();          // 2#数据线SDA输出低电平
	DelayNus(4);            // 延时4us
	SET_I2C_SCL();          // 3#时钟线SCL输出高电平,在SCL上升沿前就要把SDA拉低,为应答信号
	DelayNus(4);            // 延时4us
	CLR_I2C_SCL();          // 4#时钟线SCL输出低电平
	DelayNus(4);            // 延时4us
	SET_I2C_SDA();          // 5#数据线SDA输出高电平,释放SDA总线,很重要
}

9.0 IIC 发送非应答

/**
*******************************************************************
* @function 发送非应答信号
* @param    
* @return   
* @brief    下面是具体的时序图 
*               1 2     3      4
*                        ______
*          SCL: ________/      \______    
*               __ ___________________    
*          SDA: __/
*******************************************************************
*/
void I2CSendNack(void)
{
	SET_I2C_SDA();          // 2#数据线SDA输出高电平
	DelayNus(4);            // 延时4us
	SET_I2C_SCL();          // 3#时钟线SCL输出高电平,在SCL上升沿前就要把SDA拉高,为非应答信号
	DelayNus(4);            // 延时4us
	CLR_I2C_SCL();          // 4#时钟线SCL输出低电平
	DelayNus(4);            // 延时4us
}

10.0 完整代码

#include <stdint.h>
#include <stdio.h>
#include <stdbool.h>
#include "gd32f30x.h"
#include "delay.h"

#define GET_I2C_SDA()             gpio_input_bit_get(GPIOB, GPIO_PIN_7)    // 读取SDA端口
#define SET_I2C_SCL()             gpio_bit_set(GPIOB, GPIO_PIN_6)          // 时钟线SCL输出高电平
#define CLR_I2C_SCL()             gpio_bit_reset(GPIOB, GPIO_PIN_6)        // 时钟线SCL输出低电平
#define SET_I2C_SDA()             gpio_bit_set(GPIOB, GPIO_PIN_7)          // 数据线SDA输出高电平
#define CLR_I2C_SDA()             gpio_bit_reset(GPIOB, GPIO_PIN_7)        // 数据线SDA输出低电平

static void GpioInit(void)
{
	rcu_periph_clock_enable(RCU_GPIOB);
    gpio_init(GPIOB, GPIO_MODE_OUT_OD, GPIO_OSPEED_10MHZ, GPIO_PIN_6 | GPIO_PIN_7);
}

void EepromDrvInit(void)
{
	GpioInit();
}

/**
*******************************************************************
* @function 产生IIC起始时序,准备发送或接收数据前必须由起始序列开始 
* @param
* @return 
* @brief    SCL为高电平时,SDA由高电平向低电平跳变,开始传输数据 
*           生成下图所示的波形图,即为起始时序 
*                1 2    3     4   
*                    __________     
*           SCL : __/          \_____ 
*                 ________          
*           SDA :         \___________ 
*******************************************************************
*/
static void I2CStart(void)
{
	SET_I2C_SDA();          // 1#数据线SDA输出高电平
	SET_I2C_SCL();          // 2#时钟线SCL输出高电平   
	DelayNus(4);            // 延时4us
	CLR_I2C_SDA();          // 3#数据线SDA输出低电平 
	DelayNus(4);            // 延时4us
	CLR_I2C_SCL();          // 4#时钟线SCL输出低电平,保持I2C的时钟线SCL为低电平,准备发送或接收数据 
	DelayNus(4);            // 延时4us
}

/**
*******************************************************************
* @function 产生IIC停止时序  
* @param
* @return 
* @brief    SCL为高电平时,SDA由低电平向高电平跳变,结束传输数据 
*          生成下图所示的波形图,即为停止时序 
*                1 2   3  4   
*                       _______________     
*          SCL : ______/          
*                __        ____________  
*          SDA:    \______/
*******************************************************************
*/
static void I2CStop(void)
{
	CLR_I2C_SDA();          //2#数据线SDA输出低电平
	DelayNus(4);            //延时4us
	SET_I2C_SCL();          //3#时钟线SCL输出高电平
	DelayNus(4);  
	SET_I2C_SDA();          //4#数据线SDA输出高电平,发送I2C总线结束信号
}

/**
*******************************************************************
* @function 发送一字节,数据从高位开始发送出去
* @param    byte
* @return 
* @brief    下面是具体的时序图 
*                1 2     3      4
*                         ______
*           SCL: ________/      \______    
*                ______________________    
*           SDA: \\\___________________
*******************************************************************
*/
static void I2CSendByte(uint8_t byte)
{                          
	for (uint8_t i = 0; i < 8; i++)   // 循环8次,从高到低取出字节的8个位
	{     
		if ((byte & 0x80))            // 2#取出字节最高位,并判断为‘0’还是‘1’,从而做出相应的操作
		{
			SET_I2C_SDA();            // 数据线SDA输出高电平,数据位为‘1’
		}
		else
		{  
			CLR_I2C_SDA();      	  // 数据线SDA输出低电平,数据位为‘0’
		}
		
		byte <<= 1;            		  // 左移一位,次高位移到最高位
		
		DelayNus(4);          		  // 延时4us
		SET_I2C_SCL();                // 3#时钟线SCL输出高电平
		DelayNus(4);          		  // 延时4us
		CLR_I2C_SCL();        		  // 4#时钟线SCL输出低电平
		DelayNus(4);                  // 延时4us  
	}  
}

/**
*******************************************************************
* @function 读取一字节数据
* @param    
* @return   读取的字节
* @brief    下面是具体的时序图
*                       ______
*           SCL: ______/      \___        
*                ____________________    
*           SDA: \\\\______________\\\
*******************************************************************
*/
static uint8_t I2CReadByte(void)
{
	uint8_t byte = 0;           		// byte用来存放接收的数据
	SET_I2C_SDA();                      // 释放SDA
	for (uint8_t i = 0; i < 8; i++)     // 循环8次,从高到低读取字节的8个位
	{
		SET_I2C_SCL();          		// 时钟线SCL输出高电平
		DelayNus(4);            		// 延时4us
		byte <<= 1;          			// 左移一位,空出新的最低位

		if (GET_I2C_SDA())       		// 读取数据线SDA的数据位
		{
			byte++;            			// 在SCL的上升沿后,数据已经稳定,因此可以取该数据,存入最低位
		}
		CLR_I2C_SCL();          		// 时钟线SCL输出低电平
		DelayNus(4);            		// 延时4us
	} 

	return byte;           				// 返回读取到的数据
}

/**
*******************************************************************
* @function 等待接收端的应答信号
* @param    
* @return   1,接收应答失败;0,接收应答成功
* @brief    当SDA拉低后,表示接收到ACK信号,然后,拉低SCL,
*           此处表示发送端收到接收端的ACK
*                _______|____     
*           SCL:        |    \_________    
*                _______|     
*           SDA:         \_____________ 
*******************************************************************
*/
static bool I2CWaitAck(void)
{
	uint8_t errTimes = 0;
	
	SET_I2C_SDA();             // 释放SDA总线,很重要
	DelayNus(4);               // 延时4us
	
	SET_I2C_SCL();             // 时钟线SCL输出高电平
	DelayNus(4);               // 延时4us

	while (GET_I2C_SDA())      // 读回来的数据如果是高电平,即接收端没有应答
	{
		errTimes++;            // 计数器加1

		if (errTimes > 250)    // 如果超过250次,则判断为接收端出现故障,因此发送结束信号
		{
			I2CStop();         // 产生一个停止信号
			return false;      // 返回值为1,表示没有收到应答信号
		}
	}

	CLR_I2C_SCL();             // 表示已收到应答信号,时钟线SCL输出低电平
	DelayNus(4);               // 延时4us
	
	return true;               // 返回值为0,表示接收应答成功  
}

/**
*******************************************************************
* @function 发送应答信号
* @param    
* @return   
* @brief    下面是具体的时序图 
*                 1 2     3      4      5     
*                         ______
*           SCL: ________/      \____________    
*                __                     ______
*           SDA:   \___________________/        
*******************************************************************
*/
void I2CSendAck(void)
{
	CLR_I2C_SDA();          // 2#数据线SDA输出低电平
	DelayNus(4);            // 延时4us
	SET_I2C_SCL();          // 3#时钟线SCL输出高电平,在SCL上升沿前就要把SDA拉低,为应答信号
	DelayNus(4);            // 延时4us
	CLR_I2C_SCL();          // 4#时钟线SCL输出低电平
	DelayNus(4);            // 延时4us
	SET_I2C_SDA();          // 5#数据线SDA输出高电平,释放SDA总线,很重要
}

/**
*******************************************************************
* @function 发送非应答信号
* @param    
* @return   
* @brief    下面是具体的时序图 
*               1 2     3      4
*                        ______
*          SCL: ________/      \______    
*               __ ___________________    
*          SDA: __/
*******************************************************************
*/
void I2CSendNack(void)
{
	SET_I2C_SDA();          // 2#数据线SDA输出高电平
	DelayNus(4);            // 延时4us
	SET_I2C_SCL();          // 3#时钟线SCL输出高电平,在SCL上升沿前就要把SDA拉高,为非应答信号
	DelayNus(4);            // 延时4us
	CLR_I2C_SCL();          // 4#时钟线SCL输出低电平
	DelayNus(4);            // 延时4us
}

头文件

#ifndef _EEPROM_DRV_H_
#define _EEPROM_DRV_H_

void EepromDrvInit(void);
void EepromDrvTest(void);

#endif

后记:


进一步的完成连续发送和接收字节...


  • 14
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值