目录
前言
头文件
辅助函数
相关信号函数
初始化函数
起始信号
停止信号
接收应答信号
发送应答信号
应答
非应答
发送一个字节数据
接收一个字节数据
应用
最后
前言
在这篇文章发表之前,鄙人发表过一篇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);
}
最后
以上便是鄙人所写程序,其中也许存在些许不足,或者读者有更好的意见,请多指教,谢谢!