IIC_STM32F1_AT24C02实验:IO模拟IIC模块(2)

IIC(集成电路总线)-STM32F1系列-AT24C02-简易实验开发详细流程(个人总结)

基于正点原子代码的个人改编,本篇(IIC实验)共3个章节。
注:本博客无盈利行为,真诚希望能帮助到大家!如有错误,还请指正!

IIC_STM32F1_AT24C02实验:IO模拟IIC模块(2)

1.在写程序之前需要弄清楚三个问题,是什么?为什么?怎么做?

IIC是什么?为什么要使用IIC通信?

IIC_STM32F1_AT24C02实验:IIC介绍(1)

怎么写GPIO端口模拟IIC模块?

第一步:认识硬件图接线

IIC_STM32F1_AT24C02实验:IO模拟IIC模块(2)_第1张图片
STM32F103ZET6单片机
这里的 AT24C02 存储器模块(EEPROM:带电可擦可编程只读存储器)就相当于是从机设备,STM32 单片机就相当于主机设备。

注:因为是利用 IO 口模拟 IIC 通信,所以选取任意正常可用的 IO口 都可行,例如 GPIOA~GPIOG,这里选用 PB6 和 PB7 仅是因为 AT24C02 外接模块挂载在了这两个IO端口上。

第二步:根据需求编写模块化代码(一般在F1系列单片机上是通用的-基于Cortex-ARM3内核)

1.创建 iic.h 用户头文件

/*
* 基于 ST 官方固件库编程
* 通用 GPIO 模拟 IIC 模块
* 程序员:贬道
*/

#ifndef IIC_H
#define IIC_H

#include "stm32f10x.h"

void IIC_Idle(void);	// 空闲状态
void IIC_Start(void);	// 起始状态
void IIC_Stop(void);	// 停止状态

#endif

2.创建 iic.c 头文件

#include "iic.h"

void IIC_Idle(void){
}

void IIC_Start(void){
}

void IIC_Stop(void){
}

3.根据 IIC 时序图编写通信的基础实现功能(本文的代码都已通过测试)

(1)首先编写空闲状态,即初始化并配置 IIC。

iic.c 文件

#include "iic.h"
#include "delay.h"	// 滴答定时器延时模块


/**************************************/
void SCL_HIGH(void){
	GPIO_SetBits(GPIOB, GPIO_Pin_6);
}

void SCL_LOW(void){
	GPIO_ResetBits(GPIOB, GPIO_Pin_6);
}
/**************************************/

/**********************************************************************************/
GPIO_InitTypeDef GPIO_InitStruct_SDA;	// SDA 配置结构体变量

void SDA_IN(void){
	GPIO_InitStruct_SDA.GPIO_Pin = GPIO_Pin_7;
	GPIO_InitStruct_SDA.GPIO_Mode = GPIO_Mode_IN_FLOATING;	// 浮空输入
	GPIO_Init(GPIOB, &GPIO_InitStruct_SDA);
}

void SDA_OUT(void){
	GPIO_InitStruct_SDA.GPIO_Pin = GPIO_Pin_7;
	GPIO_InitStruct_SDA.GPIO_Mode = GPIO_Mode_Out_PP;		// 通用推挽输出
	GPIO_InitStruct_SDA.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStruct_SDA);
}

void SDA_HIGH(void){
	GPIO_SetBits(GPIOB, GPIO_Pin_7);
}

void SDA_LOW(void){
	GPIO_ResetBits(GPIOB, GPIO_Pin_7);
}

u8 READ_SDA(void){
	return GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_7);
}
/**********************************************************************************/


/************************************************************************************/
// IIC 空闲状态:两条线都为高电平
void IIC_Idle(void){
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
	
	// SCL 时钟线配置
	GPIO_InitTypeDef GPIO_InitStruct_SCL;
	GPIO_InitStruct_SCL.GPIO_Pin = GPIO_Pin_6;
	GPIO_InitStruct_SCL.GPIO_Mode = GPIO_Mode_Out_PP;		// 通用推挽输出
	GPIO_InitStruct_SCL.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStruct_SCL);
	
	SCL_HIGH();		// 空闲时拉高 SCL
	
	SDA_OUT();
	SDA_HIGH();		// 空闲时拉高 SDA
}

void IIC_Start(void){
}

void IIC_Stop(void){
}
/************************************************************************************/

iic.h 文件

/*
* 基于 ST 官方固件库编程
* 通用 GPIO 模拟 IIC 模块
* 程序员:贬道
*/

#ifndef IIC_H
#define IIC_H

#include "stm32f10x.h"

// SCL 时钟线
/*************************************************************/
void SCL_HIGH(void);	// SCL 输出高电平
void SCL_LOW(void);		// SCL 输出低电平
/*************************************************************/

// SDA 数据线
/*************************************************************/
void SDA_IN(void);		// SDA 输入模式
void SDA_OUT(void);		// SDA 输出模式

void SDA_HIGH(void);	// SDA 输出高电平
void SDA_LOW(void);		// SDA 输出低电平

u8 READ_SDA(void);		// 读 SDA 的输入电平
/*************************************************************/


// IIC 通信状态
/************************************/
void IIC_Idle(void);	// 空闲状态
void IIC_Start(void); 	// 起始状态
void IIC_Stop(void);	// 停止状态
/************************************/

#endif

(2)接着编写起始和停止状态(这里单独列出函数,否则篇幅过长,最后会附上本人所写的完整代码)。
IIC_STM32F1_AT24C02实验:IO模拟IIC模块(2)_第2张图片
iic.c 文件

// IIC 起始状态:SCL 高电平期间,SDA 由高变低
void IIC_Start(void){
	SDA_OUT();
	SDA_HIGH();		// 先对 SDA 拉高,以免产生停止信号
	SCL_HIGH();
	Delay_us(1);	// 延时时间需参考要通信模块的手册,这里延时时间最好大于0.6微妙
	
	SDA_LOW();		// SDA 拉低,起始信号产生
	Delay_us(1);	// 起始信号维持时间,这里延时时间最好大于0.6微妙
	
	SCL_LOW();		// 钳住 IIC 总线,准备收发数据
}

// IIC 停止状态:SCL 高电平期间,SDA 由低变高
void IIC_Stop(void){
	SDA_OUT();
	SCL_LOW();		// 这里正常的逻辑就是先对 SCL 拉低
	SDA_LOW();
	
	Delay_us(1);	// 停止信号建立时间,这里延时时间最好大于0.6微妙
	
	SCL_HIGH();		// 这里正常的逻辑就是先对 SCL 拉高
	SDA_HIGH();		// 停止信号触发
}

(3)再接着编写应答等待、应答、非应答函数功能。
IIC_STM32F1_AT24C02实验:IO模拟IIC模块(2)_第3张图片
iic.h 文件

void IIC_Ack(void);		// 应答信号
void IIC_NAck(void);	// 非应答信号
u8 IIC_WaitAck(void);	// 等待应答

iic.c 文件

// IIC 应答:SCL 第9个脉冲期间,SDA 保持低
void IIC_Ack(void){
	SCL_LOW();		// 这里正常的逻辑就是先对 SCL 拉低
	SDA_OUT();
	SDA_LOW();		// SDA 保持低电平
	Delay_us(2);	// SCL 低电平延时,延时时间自定义,保持相对正常的数值
	
	SCL_HIGH();
	Delay_us(2);	// SCL 高电平延时
	SCL_LOW();		// 应答信号产生,结束第9位的脉冲 
}

// IIC 非应答:SCL 第9个脉冲期间,SDA 保持高
void IIC_NAck(void){
	SCL_LOW();		// 这里正常的逻辑就是先对 SCL 拉低
	SDA_OUT();
	SDA_HIGH();		// SDA 保持高电平
	Delay_us(2);	// SCL 低电平延时,延时时间自定义,保持相对正常的数值
	
	SCL_HIGH();
	Delay_us(2);	// SCL 高电平延时
	SCL_LOW();		// 非应答信号产生,结束第9位的脉冲 
}

// 等待应答:
// 返回值:1,接收应答失败;0,接收应答成功
u8 IIC_WaitAck(void){
	u8 ucErrTime = 0;	// 应答超时时间
    SDA_IN();			// SDA 设置为输入  
    SDA_HIGH();			// SDA 先拉高,若被从机拉低则说明收到应答信号
    SCL_HIGH(); 
	Delay_us(2);		// SCL 拉高,产生第9位的脉冲
	
    while(READ_SDA()){
        ucErrTime++;
        if(ucErrTime > 250){	// 250 是自定义的数字上限,可以自行修改
            IIC_Stop();	// 应答超时,结束 IIC 通信
            return 1;
        }
    }
    SCL_LOW();			// SCL 拉低,结束第9位的脉冲 
    return 0;
}

(4)最后编写 IIC 的数据传输逻辑函数
IIC_STM32F1_AT24C02实验:IO模拟IIC模块(2)_第4张图片
iic.h 文件

void IIC_Send_Byte(u8 txd);				// IIC 发送一个字节
u8 IIC_Read_Byte(unsigned char ack);	// IIC 读取一个字节

iic.c 文件

// IIC 发送一个字节:IIC 传输数据特点——高位先行。
/*
每个时钟脉冲传送一位数据。
SCL 为高时 SDA 必须保持稳定,因为此时 SDA 的改变被认为是控制信号。
每发送一个字节,产生8次时序的循环,8个时钟信号,
并将 SDA 的高低电平输出分别赋值为 1 或 0。
*/
void IIC_Send_Byte(u8 txd){
	u8 t;				// 数据位数
	SDA_OUT();
	SCL_LOW();			// 拉低时钟开始数据传输
    for(t = 0; t < 8; t++){
        // 数据高位先行,SDA 高低电平表示数据 1 和 0
		if( ((txd & 0x80)>>7) == 0 ){
			SDA_LOW();
		}else{
			SDA_HIGH();
		}
        txd <<= 1;		// 数据高位先行,因此需将其移位存储
        Delay_us(2);	// 延时时间自定义(下面两个同理),保持相对正常的数值
        SCL_HIGH();		// SCL先上升
        Delay_us(2);
        SCL_LOW();		// SCL再下降,形成一个脉冲,发送一位数据生效
        Delay_us(2);
    }
}

// IIC 读取一个字节:ack为1时,发送ACK;ack为0时,发送NACK。
/*
每读取一个字节,产生8次时序的循环,8个时钟信号,
并读取 SDA 的高低电平信号,最后还需要考虑要不要
继续读下一个字节,发送第9位的 Ack 或 NACK。
*/
u8 IIC_Read_Byte(unsigned char ack){
	unsigned char i,receive = 0;	// i 数据位数,receive 返回读取值
    SDA_IN();						// SDA 输入模式
    for(i = 0; i < 8; i++){
        SCL_LOW();             		// SCL 先下降,通过循环,形成时钟脉冲
        Delay_us(2);				// 延时时间自定义,但最好与写字节保持一致
        SCL_HIGH();
        receive <<= 1;				// 数据高位先行,因此需将其移位存储
        if(READ_SDA())
            receive++;				// 读取并组合记录数据,++表示读到1了
        Delay_us(2);
    }
	
    // 读取8位后,主机需要变为发送模式,在第9位进行应答或不应答
    // 此时 SCL 还是高电平状态,不过下面的应答会先将 SCL 拉低的 
    if (!ack){
        IIC_NAck();					// 不应答
    }else{
        IIC_Ack();					// 应答
    }
	
    return receive;
}

IIC 位传输总结:
1.写字节:SDA为输出模式,输出高电平表示1,输出低电平表示0。
2.读字节:SDA为输入模式,读取高电平表示1,读取低电平表示0,包含第9位应答位。
**读写字节共同点:**每个时钟脉冲传送一位数据。数据高位先行。

附录(IO模拟IIC模块完整代码)

iic.h

/*
* 基于 ST 官方固件库编程
* 通用 GPIO 模拟 IIC 模块
* 程序员:贬道
*/

#ifndef IIC_H
#define IIC_H

#include "stm32f10x.h"


// SCL 时钟线
/*************************************************************/
void SCL_HIGH(void);	// SCL 输出高电平
void SCL_LOW(void);		// SCL 输出低电平
/*************************************************************/

// SDA 数据线
/*************************************************************/
void SDA_IN(void);		// SDA 输入模式
void SDA_OUT(void);		// SDA 输出模式

void SDA_HIGH(void);	// SDA 输出高电平
void SDA_LOW(void);		// SDA 输出低电平

u8 READ_SDA(void);		// 读 SDA 的输入电平
/*************************************************************/


// IIC 通信状态
/************************************/
void IIC_Idle(void);	// 空闲状态
void IIC_Start(void); 	// 起始状态
void IIC_Stop(void);	// 停止状态
/************************************/

// IIC 通信操作
/************************************************************/
/*
主机每向从机发送完一个字节的数据,总是需要等待从机给出一个
应答信号,来确认从机是否成功接收到了数据,从机应答主机
所需要的时钟也是由主机提供的。应答出现在每一次主机完成 8 个
数据位传输后紧跟着的时钟周期:
	低电平 0 表示应答,1 表示非应答。
需要应答时,数据发出方将 SDA 总线设置为 3 态输入,
由于 IIC 总线上有上拉电阻,因此此时总线默认高电平,若数据接
收方正确接收到数据,则数据接收方将SDA总线拉低,以示正确应答。
*/
void IIC_Ack(void);		// 应答信号
void IIC_NAck(void);	// 非应答信号
u8 IIC_WaitAck(void);	// 等待应答

void IIC_Send_Byte(u8 txd);				// IIC 发送一个字节
u8 IIC_Read_Byte(unsigned char ack);	// IIC 读取一个字节
/************************************************************/

#endif

iic.c

#include "iic.h"
#include "delay.h"


/**************************************/
void SCL_HIGH(void){
	GPIO_SetBits(GPIOB, GPIO_Pin_6);
}

void SCL_LOW(void){
	GPIO_ResetBits(GPIOB, GPIO_Pin_6);
}
/**************************************/

/**********************************************************************************/
GPIO_InitTypeDef GPIO_InitStruct_SDA;	// SDA 配置结构体变量

void SDA_IN(void){
	GPIO_InitStruct_SDA.GPIO_Pin = GPIO_Pin_7;
	GPIO_InitStruct_SDA.GPIO_Mode = GPIO_Mode_IN_FLOATING;	// 浮空输入
	GPIO_Init(GPIOB, &GPIO_InitStruct_SDA);
}

void SDA_OUT(void){
	GPIO_InitStruct_SDA.GPIO_Pin = GPIO_Pin_7;
	GPIO_InitStruct_SDA.GPIO_Mode = GPIO_Mode_Out_PP;		// 通用推挽输出
	GPIO_InitStruct_SDA.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStruct_SDA);
}

void SDA_HIGH(void){
	GPIO_SetBits(GPIOB, GPIO_Pin_7);
}

void SDA_LOW(void){
	GPIO_ResetBits(GPIOB, GPIO_Pin_7);
}

u8 READ_SDA(void){
	return GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_7);
}
/**********************************************************************************/


/************************************************************************************/
// IIC 空闲状态:两条线都为高电平
void IIC_Idle(void){
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
	
	// SCL 时钟线配置
	GPIO_InitTypeDef GPIO_InitStruct_SCL;
	GPIO_InitStruct_SCL.GPIO_Pin = GPIO_Pin_6;
	GPIO_InitStruct_SCL.GPIO_Mode = GPIO_Mode_Out_PP;		// 通用推挽输出
	GPIO_InitStruct_SCL.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStruct_SCL);
	
	SCL_HIGH();		// 空闲时拉高 SCL
	
	SDA_OUT();
	SDA_HIGH();		// 空闲时拉高 SDA
}

// IIC 起始状态:SCL 高电平期间,SDA 由高变低
void IIC_Start(void){
	SDA_OUT();
	SDA_HIGH();		// 先对 SDA 拉高,以免产生停止信号
	SCL_HIGH();
	Delay_us(1);	// 延时时间需参考要通信模块的手册,这里延时时间最好大于0.6微妙
	
	SDA_LOW();		// SDA 拉低,起始信号产生
	Delay_us(1);	// 起始信号维持时间,这里延时时间最好大于0.6微妙
	
	SCL_LOW();		// 钳住 IIC 总线,准备收发数据
}

// IIC 停止状态:SCL 高电平期间,SDA 由低变高
void IIC_Stop(void){
	SDA_OUT();
	SCL_LOW();		// 这里正常的逻辑就是先对 SCL 拉低
	SDA_LOW();
	
	Delay_us(1);	// 停止信号建立时间,这里延时时间最好大于0.6微妙
	
	SCL_HIGH();		// 这里正常的逻辑就是先对 SCL 拉高
	SDA_HIGH();		// 停止信号触发
}
/************************************************************************************/

/************************************************************************************/
// IIC 应答:SCL 第9个脉冲期间,SDA 保持低
void IIC_Ack(void){
	SCL_LOW();		// 这里正常的逻辑就是先对 SCL 拉低
	SDA_OUT();
	SDA_LOW();		// SDA 保持低电平
	Delay_us(2);	// SCL 低电平延时,延时时间自定义,保持相对正常的数值
	
	SCL_HIGH();
	Delay_us(2);	// SCL 高电平延时
	SCL_LOW();		// 应答信号产生,结束第9位的脉冲 
}

// IIC 非应答:SCL 第9个脉冲期间,SDA 保持高
void IIC_NAck(void){
	SCL_LOW();		// 这里正常的逻辑就是先对 SCL 拉低
	SDA_OUT();
	SDA_HIGH();		// SDA 保持高电平
	Delay_us(2);	// SCL 低电平延时,延时时间自定义,保持相对正常的数值
	
	SCL_HIGH();
	Delay_us(2);	// SCL 高电平延时
	SCL_LOW();		// 非应答信号产生,结束第9位的脉冲 
}

// 等待应答:
// 返回值:1,接收应答失败;0,接收应答成功
u8 IIC_WaitAck(void){
	u8 ucErrTime = 0;	// 应答超时时间
    SDA_IN();			// SDA 设置为输入  
    SDA_HIGH();			// SDA 先拉高,若被从机拉低则说明收到应答信号
    SCL_HIGH(); 
	Delay_us(2);		// SCL 拉高,产生第9位的脉冲
	
    while(READ_SDA()){
        ucErrTime++;
        if(ucErrTime > 250){	// 250 是自定义的数字上限,可以自行修改
            IIC_Stop();	// 应答超时,结束 IIC 通信
            return 1;
        }
    }
    SCL_LOW();			// SCL 拉低,结束第9位的脉冲 
    return 0;
}

// IIC 发送一个字节:IIC 传输数据特点——高位先行。
/*
每个时钟脉冲传送一位数据。
SCL 为高时 SDA 必须保持稳定,因为此时 SDA 的改变被认为是控制信号。
每发送一个字节,产生8次时序的循环,8个时钟信号,
并将 SDA 的高低电平输出分别赋值为 1 或 0。
*/
void IIC_Send_Byte(u8 txd){
	u8 t;				// 数据位数
	SDA_OUT();
	SCL_LOW();			// 拉低时钟开始数据传输
    for(t = 0; t < 8; t++){
        // 数据高位先行,SDA 高低电平表示数据 1 和 0
		if( ((txd & 0x80)>>7) == 0 ){
			SDA_LOW();
		}else{
			SDA_HIGH();
		}
        txd <<= 1;		// 数据高位先行,因此需将其移位存储
        Delay_us(2);	// 延时时间自定义(下面两个同理),保持相对正常的数值
        SCL_HIGH();		// SCL先上升
        Delay_us(2);
        SCL_LOW();		// SCL再下降,形成一个脉冲,发送一位数据生效
        Delay_us(2);
    }
}

// IIC 读取一个字节:ack为1时,发送ACK;ack为0时,发送NACK。
/*
每读取一个字节,产生8次时序的循环,8个时钟信号,
并将 SDA 的高低电平输出分别赋值为 1 或 0。
并读取 SDA 的高低电平信号,最后还需要考虑要不要
继续读下一个字节,发送第9位的 Ack 或 NACK。
*/
u8 IIC_Read_Byte(unsigned char ack){
	unsigned char i,receive = 0;	// i 数据位数,receive 返回读取值
    SDA_IN();						// SDA 输入模式
    for(i = 0; i < 8; i++){
        SCL_LOW();             		// SCL 先下降,通过循环,形成时钟脉冲
        Delay_us(2);				// 延时时间自定义,但最好与写字节保持一致
        SCL_HIGH();
        receive <<= 1;				// 数据高位先行,因此需将其移位存储
        if(READ_SDA())
            receive++;				// 读取并组合记录数据,++表示读到1了
        Delay_us(2);
    }
	
    // 读取8位后,主机需要变为发送模式,在第9位进行应答或不应答
    // 此时 SCL 还是高电平状态,不过下面的应答会先将 SCL 拉低的 
    if (!ack){
        IIC_NAck();					// 不应答
    }else{
        IIC_Ack();					// 应答
    }
	
    return receive;
}
/************************************************************************************/

至此,IIC模块代码编写完毕!

你可能感兴趣的:(stm32,单片机,嵌入式硬件,c语言,经验分享)