目录

前言

头文件

辅助函数

相关信号函数

初始化函数

起始信号

停止信号

接收应答信号

发送应答信号

应答

非应答

发送一个字节数据

接收一个字节数据

应用

最后

前言

在这篇文章发表之前,鄙人发表过一篇51的模拟IIC总线通信,因某短见,认为在51上写出的IIC不及在STM32上所写,同时某也在学习STM32,故将原来的程序改成STM32的程序,也是为了更方便地开发。

    当然,读者也应该有所了解,STM32中也自带硬件IIC,可以直接利用,只能说是各有优缺点吧。

    对于硬件IIC来说,其使用效率远高于软件IIC,但是它所使用的引脚是固定的,另外网上也有网友对这个硬件IIC似乎不是特别满意;而对于软件IIC来说,其使用起来较为灵活,可在任意引脚上进行开发,但是效率就不是特别让人满意。

以上皆为鄙人短见,还请各位网友多多指教。下面进入主要内容吧,同样的,网上对IIC的介绍甚多,故某将此省略,请见谅。

头文件

  由于要实现在任意引脚上的IIC开发和尽可能的提高效率,故在头文件中进行一系列的配置,如下:

#include<stm32f10x.h>
#include<delay.h>
 
/*设置数据传输方向*/
#define DIRECTION_OUT	GPIO_Mode_Out_PP			//输出
#define DIRECTION_IN	GPIO_Mode_IN_FLOATING		//输入
 
//上拉
#define SET_SCL		(IIC_PinInitStruct -> SCL_GPIOx -> BSRR = IIC_PinInitStruct -> SCL_Pin)
#define SET_SDA		(IIC_PinInitStruct -> SDA_GPIOx -> BSRR = IIC_PinInitStruct -> SDA_Pin)
 
//下拉
#define RESET_SCL	(IIC_PinInitStruct -> SCL_GPIOx -> BRR = IIC_PinInitStruct -> SCL_Pin)
#define RESET_SDA 	(IIC_PinInitStruct -> SDA_GPIOx -> BRR = IIC_PinInitStruct -> SDA_Pin)
 
//获取数据引脚上的电平
#define READ_SDA	(((IIC_PinInitStruct -> SDA_GPIOx -> IDR & IIC_PinInitStruct -> SDA_Pin) != 0x00) ? 1 : 0)
 
#define IIC_DELAY delay_us(1)	//IIC延时,可根据实际情况适当调整
 
typedef struct
{
	GPIO_TypeDef *SCL_GPIOx;
	GPIO_TypeDef *SDA_GPIOx;
	unsigned short int SCL_Pin;
	unsigned short int SDA_Pin;
}IIC_PinInitTypeDef;

文件中最主要的部分是创建了一个结构体,有了这个结构体,每当要接入新的IIC设备时,就只需定义一个结构体并且将其初始化,然后将其地址传入到IIC的各个函数中即可。

辅助函数

#include<iic_soft.h>
/******************************************************************************
	本文件为IIC的基本信号集合(作为主机),就目前测试,适合在各个频率下工作,
	针对不同的频率,仅需适当调整IIC延时时间和调整一小部分内容即可
	每个信号中都以SCL低电平为结尾(停止信号除外),这样一来可防止信号发送
	之后在SDA上的电平变化引起误触发
*******************************************************************************/
 
static GPIO_InitTypeDef GPIO_IIC;	//引脚初始化结构体
 
/*********************使能相应的端口时钟*********************/
static void IIC_SetClock(IIC_PinInitTypeDef *IIC_PinInitStruct)
{
	unsigned int RCC_APB2Periph;
	switch((unsigned int)(IIC_PinInitStruct -> SCL_GPIOx))
	{
		case (unsigned int)GPIOA:
			RCC_APB2Periph = RCC_APB2Periph_GPIOA;
			break;
		case (unsigned int)GPIOB:
			RCC_APB2Periph = RCC_APB2Periph_GPIOB;
			break;
		case (unsigned int)GPIOC:
			RCC_APB2Periph = RCC_APB2Periph_GPIOC;
			break;
		case (unsigned int)GPIOD:
			RCC_APB2Periph = RCC_APB2Periph_GPIOD;
			break;
		case (unsigned int)GPIOE:
			RCC_APB2Periph = RCC_APB2Periph_GPIOE;
			break;
		case (unsigned int)GPIOF:
			RCC_APB2Periph = RCC_APB2Periph_GPIOF;
			break;
		case (unsigned int)GPIOG:
			RCC_APB2Periph = RCC_APB2Periph_GPIOG;
			break;
	}
 
	switch((unsigned int)(IIC_PinInitStruct -> SDA_GPIOx))
	{
		case (unsigned int)GPIOA:
			RCC_APB2Periph |= RCC_APB2Periph_GPIOA;
			break;
		case (unsigned int)GPIOB:
			RCC_APB2Periph |= RCC_APB2Periph_GPIOB;
			break;
		case (unsigned int)GPIOC:
			RCC_APB2Periph |= RCC_APB2Periph_GPIOC;
			break;
		case (unsigned int)GPIOD:
			RCC_APB2Periph |= RCC_APB2Periph_GPIOD;
			break;
		case (unsigned int)GPIOE:
			RCC_APB2Periph |= RCC_APB2Periph_GPIOE;
			break;
		case (unsigned int)GPIOF:
			RCC_APB2Periph |= RCC_APB2Periph_GPIOF;
			break;
		case (unsigned int)GPIOG:
			RCC_APB2Periph |= RCC_APB2Periph_GPIOG;
			break;
	}
    RCC->APB2ENR |= RCC_APB2Periph;
}
 
/******************************设置数据线(SDA)的传输方向******************************/
static void IIC_DataDir(IIC_PinInitTypeDef *IIC_PinInitStruct,GPIOMode_TypeDef direction)
{
	GPIO_IIC.GPIO_Pin   = IIC_PinInitStruct -> SDA_Pin;
	GPIO_IIC.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_IIC.GPIO_Mode  = direction;						//数据传输方向
	GPIO_Init(IIC_PinInitStruct -> SDA_GPIOx,&GPIO_IIC);
}

相关信号函数

初始化函数

STM32与51不同,在使用前还需要对引脚的时钟、模式等进行配置。

/*******************IIC初始化*******************/
void IIC_Init(IIC_PinInitTypeDef *IIC_PinInitStruct)
{
	IIC_SetClock(IIC_PinInitStruct);						//使能端口时钟
 
	GPIO_IIC.GPIO_Pin = IIC_PinInitStruct -> SCL_Pin;
	GPIO_IIC.GPIO_Mode  = GPIO_Mode_Out_PP;
	GPIO_IIC.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(IIC_PinInitStruct -> SCL_GPIOx,&GPIO_IIC);	//初始化SCL引脚
 
	IIC_DataDir(IIC_PinInitStruct,DIRECTION_OUT);			//初始化SDA引脚(输出模式)
 
	RESET_SCL;
	SET_SDA;
	SET_SCL;
	delay_ms(50);
}

起始信号

/****IIC起始信号(SCL高电平下SDA的一个下降沿,后以SCL变为低电平收尾)****/
/*	     				 ____
						/    \4.7us
					___/      \___									 */
void IIC_Start(IIC_PinInitTypeDef *IIC_PinInitStruct)
{
	IIC_DataDir(IIC_PinInitStruct,DIRECTION_OUT);	//SDA为输出模式
 
	SET_SDA;
	SET_SCL;
	RESET_SDA;
	__nop();
	__nop();	//必须延时
	RESET_SCL;
}

此处某用__nop()进行延时而不用IIC_DELAY延时主要是因为此处不需要过长的延时,用IIC_DELAY显得过慢,下面的发送字节函数同样如此。

停止信号

/****IIC停止信号(SCL高电平下SDA一个上升沿,后以SCL变为低电平收尾)****/
/*							 _____________
					SCL ____/					
							4us_____
					SDA ______/	    \____						   */
void IIC_Stop(IIC_PinInitTypeDef *IIC_PinInitStruct)
{
	IIC_DataDir(IIC_PinInitStruct,DIRECTION_OUT);	//SDA为输出模式
 
	RESET_SDA;
	SET_SCL;
	SET_SDA;
//	RESET_SCL;
}

 此处的RESET_SCL可加可不加。

接收应答信号

/*接收应答信号,若接收到非应答信号,则发送停止信号(SDA低电平表示应答,高电平表示非应答)*/
unsigned char IIC_ReceiveACK(IIC_PinInitTypeDef *IIC_PinInitStruct)
{
	unsigned char state;
	IIC_DataDir(IIC_PinInitStruct,DIRECTION_IN);	//SDA为输入模式
	
	SET_SDA;										//释放SDA
	SET_SCL;
	state = READ_SDA;
	RESET_SCL;
	if(state)										//若接收到非应答信号,发送停止信号
	{
		IIC_Stop(IIC_PinInitStruct);
	}
	return state;
}

发送应答信号

应答

/****应答(SDA保持为低电平)****/
/*							 	____
					SCL _______/	\_____				
						_____
					SDA	   	 \____________						   */
void IIC_SendACK(IIC_PinInitTypeDef *IIC_PinInitStruct)
{
	IIC_DataDir(IIC_PinInitStruct,DIRECTION_OUT);	//方向为输出
 
	RESET_SDA;
	SET_SCL;
	RESET_SCL;
}

非应答

/****非应答(SDA保持为高电平)****/
/*							 	____
					SCL _______/	\_____				
							 _____________
					SDA	____/		 	 							*/
void IIC_SendNACK(IIC_PinInitTypeDef *IIC_PinInitStruct)
{
	IIC_DataDir(IIC_PinInitStruct,DIRECTION_OUT);	//方向为输出
 
	SET_SDA;
	SET_SCL;
	RESET_SCL;
}

发送一个字节数据

/****IIC发送一个字节数据(每发送一个字节接收一次应答)****/
void IIC_SendByte(IIC_PinInitTypeDef *IIC_PinInitStruct,unsigned char dat)
{
	unsigned char i;
	IIC_DataDir(IIC_PinInitStruct,DIRECTION_OUT);	//方向为输出
	for(i = 0;i < 8;i++)
	{
		if((dat >> 7) & 0x01)
		{
			SET_SDA;
		}
		else
		{
			RESET_SDA;
		}
 
		__nop();
		__nop();
		__nop();
		__nop();	//此处必须延时,但用微秒级的延时显得过慢,故此处用nop延时
		SET_SCL;
		RESET_SCL;
		dat <<= 1;
	}
}

    此处使用__nop()延时的目的同起始信号相同,可前往参考。

接收一个字节数据

/****IIC接收一个字节(每接收一个字节发送一次应答/非应答)****/
unsigned char IIC_ReadByte(IIC_PinInitTypeDef *IIC_PinInitStruct)
{
	unsigned char i,receive;
	IIC_DataDir(IIC_PinInitStruct,DIRECTION_IN);	//方向为输入
	SET_SDA;										//释放数据线
 
	for(i = 0;i < 8;i++)
	{
		SET_SCL;
		receive <<= 1;
		receive |= (READ_SDA & 0x01);
		RESET_SCL;
		IIC_DELAY;		//必须延时
	}
	return receive;
}

应用

从以上一系列函数中不难发现,在每个函数中都需要传入一个结构体,这也是实现任何引脚上的IIC开发的关键。以下,鄙人举一个使用例子,就以OLED的发送一个字节函数为例,希望能够帮助读者应用。

IIC_PinInitTypeDef OLED_PinInitStruct;			//OLED引脚初始化结构体
 
/**********************往OLED中发送一个字节(数据/命令)**********************/
/*
	mode:字节性质
		命令(OLED_CMD)
		数据(OLED_DATA)
	data:发送内容
*/
static void OLED_IIC_SendByte(unsigned char mode,unsigned char data)
{
	IIC_Start(&OLED_PinInitStruct);
	IIC_SendByte(&OLED_PinInitStruct,(OLED_ADDRESS << 1) & 0xfe);		//direction is write
	IIC_ReceiveACK(&OLED_PinInitStruct);
	IIC_SendByte(&OLED_PinInitStruct,mode);
	IIC_ReceiveACK(&OLED_PinInitStruct);
	IIC_SendByte(&OLED_PinInitStruct,data);
	IIC_ReceiveACK(&OLED_PinInitStruct);
	IIC_Stop(&OLED_PinInitStruct);
}

最后

以上便是鄙人所写程序,其中也许存在些许不足,或者读者有更好的意见,请多指教,谢谢!