iT邦幫忙

2021 iThome 鐵人賽

DAY 10
1
Arm Platforms

基於ARM-M0架構MCU之落摔檢測韌體開發系列 第 10

[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裡有什麼東西吧。


上一篇
[DAY 9] _I²C協議介紹
下一篇
[DAY 11] _軟體實現I2C協議以三軸感測器為例 (ADXL345)
系列文
基於ARM-M0架構MCU之落摔檢測韌體開發32

尚未有邦友留言

立即登入留言