[DAY 10] _软件实现I2C协议

因为我是用控制high、low,我接下来就贴上部分程序来个别说明,首先要先写上基本时序的规范,协议的规范我会写在这两个档案:bsp_I2C_gpio.h 和 bsp_I2C_gpio.c 会这样命名是为了好区分各个函式所在的位置,像读写三轴的函示的程序我会写在:bsp_I2C_adxl345.h 和 bsp_I2C_adxl345.c里面,我这样命名的原因是:bsp代表是支持包的意思,这个.h和.c可以移植到其他颗mcu上只要做小幅的修改就能正常运作,I2C_gpio这命名是因为用GPIO口去做I2C基本时序,I2C_adxl345这命名是因为这里面都是写关於三轴感测器的时序规则,接下来我会简单讲解我写了甚麽。

bsp_I2C_gpio.h

#ifndef __BSP_I2C_gpio_H
#define __BSP_I2C_gpio_H

#include "stm32f0xx.h"

#define I2C_WR	0		
#define I2C_RD	1		

#define I2C2_WR	0		
#define I2C2_RD	1	

#define I2C_GPIO_PORT		GPIOA			
#define I2C_GPIO_CLK		RCC_AHBPeriph_GPIOA		
#define I2C_SCL_PIN			GPIO_Pin_9			
#define I2C_SDA_PIN			GPIO_Pin_10		

#define I2C_SCL_SOURCE		GPIO_PinSource9
#define I2C_SDA_SOURCE		GPIO_PinSource10

/*以下是使用GPIO的库函数实现拉VDD和GND*/
#define I2C_SCL_1()  GPIO_SetBits(I2C_GPIO_PORT, I2C_SCL_PIN)	 /* SCL = 1 */
#define I2C_SCL_0()  GPIO_ResetBits(I2C_GPIO_PORT, I2C_SCL_PIN)	 /* SCL = 0 */

#define I2C_SDA_1()  GPIO_SetBits(I2C_GPIO_PORT, I2C_SDA_PIN)	/* SDA = 1 */
#define I2C_SDA_0()  GPIO_ResetBits(I2C_GPIO_PORT, I2C_SDA_PIN)	/* SDA = 0 */

/*以下是I2C的基本时序实现函式*/
void i2c_Start(void); /*开始信号*/
void i2c_Stop(void);  /*停止信号*/
void i2c_SendByte(uint8_t _ucByte); /*传送八位元资料*/
uint8_t i2c_ReadByte(void);         /*接收八位元资料*/
uint8_t i2c_WaitAck(void);          /*等待主机或从机回应*/
void i2c_Ack(void);                 /*发送应答SDA=0*/
void i2c_NAck(void);                /*发送应答SDA=1*/

uint8_t i2c_CheckDevice(uint8_t _Address);  /*用来侦测检测设备是存在*/

#endif /*__BSP_I2C_gpio_H*/

define定义好,之後要换角位只需要改变define的最後不分就好,其他都不需要去动到可以提高移植性,再来看看我.c如何实现吧。


bsp_I2C_gpio.c

以上是.h定义的部分,再来是.c实现的部分,由於程序码较长就不一次贴上,我会分段讲解

// bsp board support package
#include "bsp_I2C_gpio.h"

static void i2c_CfgGpio(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	RCC_AHBPeriphClockCmd(I2C_GPIO_CLK, ENABLE);	/* 打开GPIOA时钟 */
	GPIO_InitStructure.GPIO_Pin = I2C_SCL_PIN | I2C_SDA_PIN;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;  	
	GPIO_InitStructure.GPIO_OType = GPIO_OType_OD;  	/* 开漏输出 */
	GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
	GPIO_Init(I2C_GPIO_PORT, &GPIO_InitStructure);
    
	i2c_Stop();
}
static void I2C_Delay(void)
{
	uint8_t i;
	//循环次数为7时,SCL频率为= 198KHz   //逻辑分析仪测试
	//循环次数为20时,SCL频率为= 101KHz 
	for (i = 0;i<20;i++);
}

首先看到这两个函式第1个是我正在做初始化IO口,再来是I2C_Delay,这边Delay是用比较偷懒的方式,利用计数再用逻辑分析仪去测试SCL的频率为多少,当然还有种方法,写个准确的延时函式。
附上参考网站,这是利用ARM-mo内核里的滴答计时器去做计时,这会比直接用记属还来的精准一些些。

起始信号

void i2c_Start(void)
{
	//当SCL高电位时,SDA出现一个下降沿表示I2C总线启动信号
	I2C_SDA_1();
	I2C_SCL_1();
	I2C_Delay();
	I2C_SDA_0();
	I2C_Delay();
	I2C_SCL_0();
	I2C_Delay();
}

有没有感觉这好像不难的样子XD,简单明了的函式,I2C_SDA_1();这个函式可以看到我.h里的定义,是使用库函式的方式让SDA拉高电位,以下我就连续贴出程序码,只要你有点嵌入是开发的经验都可以轻易移植到自己的开发版上,因为控制IO口是每个MCU都可以做到的事...

void i2c_Stop(void)
{
	//当SCL高电位时,SDA出现一个上升沿表示I2C汇流排停止信号
	I2C_SDA_0();
	I2C_SCL_1();
	I2C_Delay();
	I2C_SDA_1();
}

void i2c_SendByte(uint8_t _ucByte)
{
	uint8_t i;
	/* 先发送字节的高位bit7 */
	for (i = 0; i < 8; i++)
	{		
		if (_ucByte & 0x80)
		{
			I2C_SDA_1();
		}
		else
		{
			I2C_SDA_0();
		}
		I2C_Delay();
		I2C_SCL_1();
		I2C_Delay();	
		I2C_SCL_0();
		if (i == 7)
		{
			I2C_SDA_1(); // 释放总线
		}
		_ucByte <<= 1;	/* 左移一个bit */
		I2C_Delay();
	}
}

uint8_t i2c_ReadByte(void)
{
	uint8_t i;
	uint8_t value;
	/* 读到第1个bit为数据的bit7 */
	value = 0;
	for (i = 0; i < 8; i++)
	{
		value <<= 1;
		I2C_SCL_1();
		I2C_Delay();
		if (I2C_SDA_READ())
		{
			value++;
		}
		I2C_SCL_0();
		I2C_Delay();
	}
	return value;
}

uint8_t i2c_WaitAck(void)
{
	uint8_t re;
	
	I2C_SDA_1();	/* CPU释放SDA汇流排 */
	I2C_Delay();
	I2C_SCL_1();	/* CPU驱动SCL = 1, 此时器件会返回ACK应答 */
	I2C_Delay();
	if (I2C_SDA_READ())	/* CPU读取SDA口线状态 */
	{
		re = 1;
	}
	else
	{
		re = 0;
	}
	I2C_SCL_0();
	I2C_Delay();
	return re;
}

void i2c_Ack(void)
{
	I2C_SDA_0();	
	I2C_Delay();
	I2C_SCL_1();	
	I2C_Delay();
	I2C_SCL_0();
	I2C_Delay();
	I2C_SDA_1();	
	I2C_Delay();
}

void i2c_NAck(void)
{
	I2C_SDA_1();	/* CPU驱动SDA = 1 */
	I2C_Delay();
	I2C_SCL_1();	/* CPU产生1个时钟 */
	I2C_Delay();
	I2C_SCL_0();
	I2C_Delay();	
}

//************************************************
//*	函 数 名: i2c_CheckDevice
//*	功能说明: 检测I2C汇流排设备,CPU向发送设备位址,然後读取设备应答来判断该设备是否存在
//*	形    参:_Address:设备的I2C汇流排位址
//*	return  : 返回值 0 表示正确, 返回1表示未探测到
//************************************************
uint8_t i2c_CheckDevice(uint8_t _Address)
{
	uint8_t ucAck;
	i2c_CfgGpio();		/* 配置GPIO */
	i2c_Start();			/* 发送启动信号 */
	/* 发送设备位址+读写控制bit(0 = w, 1 = r) bit7 先传 */
	i2c_SendByte(_Address | I2C_WR);
	ucAck = i2c_WaitAck();	/* 检测设备的ACK应答 */
	i2c_Stop();				/* 发送停止信号 */
	return ucAck;
}

以上就是用IO口实现I2C基本协议的函式,看不懂的话可以交叉看我昨天介绍协议的部分,1个1个慢慢看就会懂,真的不难,再看不懂可以下面发问,我无法1个个慢慢讲解太花时间了,我也不是短时间内学会的,与其我给你们鱼不如教你们如何钓鱼这样更好,自己完完整整看过一遍胜过别人的讲解。
你学会这种软件定义方式你就可以把这协议了解的很透彻了,接下来就根据各个感测器手册里说明的协议规定用以上函是去组成就可以了啦~~
今天就先这样了,明天来说bsp_I2C_adxl345.h 和 bsp_I2C_adxl345.c里有什麽东西吧。


<<:  Day8:终於要进去新手村了-Javascript-资料型态

>>:  Day 23 - Speech Synthesis

Day 1 - 什麽是 HomeLab 及网路

网路,是我们生活圈不可缺少的一部分。 每天一早,不少人都会打开手机查看新的讯息、新闻或影片。 由此可...

[Day 30] 保护资讯资产

CISA书最後一章为资讯资产安全控制及安全事件管理,与CISSP内容大致重叠,差别在需以稽核角度查看...

堆叠 - 递回 - 费氏数列 - DAY 7

堆叠定义 具有线性串列结构,资料遵循着先进後出,後进先出的存取顺序 费氏数列(又称黄金分割数) 可以...

用 Python 畅玩 Line bot - 06:Image Message

现在我们可以来尝试能对收到的 Image message 做怎样的操作,我们可以使用line_bot...

课堂笔记 - 深度学习 Deep Learning (2)

Machine Learning 上篇文章有简单提及Machine Learning的定义: Ma...