前言

1.利用i2c协议驱动系统中的PCF8574模块进而控制蜂鸣器

一、i2c协议的简单描述

I2C设备连接I2C总线

esp01s的I2C ESP01S的i2c 8574_esp01s的I2C


SDA为数据线,SCL为时钟线,I2C由这两条线组成,其上连接由主机控制器和从设备。起始位和停止位的条件

esp01s的I2C ESP01S的i2c 8574_代码实现_02


当SCL时钟线为高电平,SDA数据线由高变低时,为起始信号。SCL时钟线为高电平,SDA数据线由低变高时,为停止信号。位传输

esp01s的I2C ESP01S的i2c 8574_代码实现_03


应答

esp01s的I2C ESP01S的i2c 8574_数据_04

二、硬件电路接口

1.I2C两条线的接口

esp01s的I2C ESP01S的i2c 8574_esp01s的I2C_05

2.PCF8574T原理图

esp01s的I2C ESP01S的i2c 8574_数据_06

3.蜂鸣器原理图

esp01s的I2C ESP01S的i2c 8574_代码实现_07

三、I2C协议的代码实现

#include "stm32f4xx_rcc.h"
#include "stm32f4xx_gpio.h"
#include "delay.h"


//IIC_SDA:PH5
//IIC_SCL:PH4

void iic_init(){
	GPIO_InitTypeDef GPIO_InitStruct; //定义gpio初始化结构体 
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOH, ENABLE); 
	//查找手册能知道GPIOH连接在AHB1时钟线上,所以用这个函数对GPIOH使能
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT;  //端口模式为输出模式
	GPIO_InitStruct.GPIO_OType = GPIO_OType_OD; //端口输出类型为开漏模式
	GPIO_InitStruct.GPIO_Pin		=	GPIO_Pin_4 | GPIO_Pin_5; //选择PH4 和 PH5 端口
	GPIO_InitStruct.GPIO_PuPd		=	GPIO_PuPd_UP;			//若外部有上拉,则可配置为无上下拉
	GPIO_InitStruct.GPIO_Speed	= GPIO_Speed_2MHz;	//标准速度仅100K
	GPIO_Init(GPIOH, &GPIO_InitStruct);  //
		//3. 初始状态:空闲状态
	GPIO_SetBits(GPIOH,GPIO_Pin_5);									//拉高SDA 
	GPIO_SetBits(GPIOH,GPIO_Pin_4);									//拉高SCL
	delay_us(5);																		//这里采用软件延时
}
//起始条件:在SCL高电平期间,SDA从高到低跳变
void iic_start(){
	GPIO_SetBits(GPIOH,GPIO_Pin_5);									//拉高SDA  此函数为设置高电平函数 第一个参数为具体GPIO 第二个是哪个端口设置为高电平
	GPIO_SetBits(GPIOH,GPIO_Pin_4);									//拉高SCL
	delay_us(5);                                    ///这里采用软件延时
	GPIO_ResetBits(GPIOH,GPIO_Pin_5);		            //SDA从高到低跳变 此函数为设置低电平函数,参数和上述一样
	GPIO_ResetBits(GPIOH,GPIO_Pin_4);								//每次操作完拉低SCL,继续占用总线
	delay_us(5);	
}

//停止条件:在SCL高电平期间,SDA从低到高跳变
void iic_stop(){
		GPIO_ResetBits(GPIOH,GPIO_Pin_5);								//SDA为低
		GPIO_SetBits(GPIOH,GPIO_Pin_4);									//SCL为高
		delay_us(5);	
		GPIO_SetBits(GPIOH,GPIO_Pin_5);									//SDA由低到高跳变
		delay_us(5);	
}
//数据发送:低电平发数据,以字节为单位,先传高位
void iic_sendByte(u8 dat)
{
	u8 i = 0;
	
	for(i=0;i<8;i++)
	{
		GPIO_ResetBits(GPIOH,GPIO_Pin_4);						//拉低时钟,低电平发数据
		delay_us(5);
		if(dat & 0x80)
			GPIO_SetBits(GPIOH,GPIO_Pin_5);						//发数据位1
		else
			GPIO_ResetBits(GPIOH,GPIO_Pin_5);					//发数据位0
		delay_us(5);
		
		GPIO_SetBits(GPIOH,GPIO_Pin_4);							//拉高SCL,从机读取数据
		delay_us(5);
		
		dat <<= 1;																	//移掉高位,准备发次高位
	}
	
	GPIO_ResetBits(GPIOH,GPIO_Pin_4);							//每次操作完拉低SCL,继续占用总线
	delay_us(5);	
}

//接收数据:在SCL高电平期间读取数据
u8 iic_recvByte()
{
	u8 i = 0;
	u8 temp = 0;			//用于保存接收的数据
	
	for(i=0;i<8;i++)
	{
		GPIO_ResetBits(GPIOH,GPIO_Pin_4);							//主机拉低SCL,让从机发数据过来
		GPIO_SetBits(GPIOH,GPIO_Pin_5);								//主机读数据前,先切断输出通道,切换为输入模式
		delay_us(5);	
		GPIO_SetBits(GPIOH,GPIO_Pin_4);								//拉高SCL,准备接收数据
		delay_us(5);	
		if(GPIO_ReadInputDataBit(GPIOH, GPIO_Pin_5) == Bit_SET)
			temp |= 1<<(7-i);														//接收数据位
		delay_us(5);	
	}
	
	GPIO_ResetBits(GPIOH,GPIO_Pin_4);							//每次操作完拉低SCL,继续占用总线
	delay_us(5);	
	
	return temp;
}

//主机向从机发送应答信号
void iic_ackToSlave()
{
	GPIO_ResetBits(GPIOH,GPIO_Pin_4);							//拉低SCL,发应答信号
	delay_us(5);	
	GPIO_ResetBits(GPIOH,GPIO_Pin_5);							//拉低SDA,发应答
	delay_us(5);	
	GPIO_SetBits(GPIOH,GPIO_Pin_4);								//拉高SCL,使从机接收应答
	delay_us(5);
	
	GPIO_ResetBits(GPIOH,GPIO_Pin_4);							//每次操作完拉低SCL,继续占用总线
	delay_us(5);
}

//主机向从机发送非应答信号
void iic_noAckToSlave()
{
	GPIO_ResetBits(GPIOH,GPIO_Pin_4);							//拉低SCL,发应答信号
	delay_us(5);	
	GPIO_SetBits(GPIOH,GPIO_Pin_5);								//拉高SDA,发非应答
	delay_us(5);	
	GPIO_SetBits(GPIOH,GPIO_Pin_4);								//拉高SCL,使从机接收应答
	delay_us(5);
	
	GPIO_ResetBits(GPIOH,GPIO_Pin_4);							//每次操作完拉低SCL,继续占用总线
	delay_us(5);
}

//从机向主机发应答,主机读取SDA,判断应答状态
u8 iic_checkAck()
{
	u8 ack = 0;
	
	GPIO_ResetBits(GPIOH,GPIO_Pin_4);							//拉低SCL,使从机发应答信号
	GPIO_SetBits(GPIOH,GPIO_Pin_5);								//主机读数据前,先切断输出通道,切换为输入模式
	delay_us(5);
	GPIO_SetBits(GPIOH,GPIO_Pin_4);								//拉高SCL,准备接收应答
	delay_us(5);
	if(GPIO_ReadInputDataBit(GPIOH, GPIO_Pin_5) == Bit_SET)					//从机向主机发非应答信号
		ack = 1;
	else if(GPIO_ReadInputDataBit(GPIOH, GPIO_Pin_5) == Bit_RESET)	//从机向主机发应答信号
		ack = 0;
	delay_us(5);
	
	GPIO_ResetBits(GPIOH,GPIO_Pin_4);							//每次操作完拉低SCL,继续占用总线
	delay_us(5);
	
	return ack;
}

四、PCF8574代码实现

#include "iic.h"
#include "stm32f4xx_rcc.h"
#include "stm32f4xx_gpio.h"
#include "pcf8574.h"

void pcf8574_init()
{
	iic_init();
}
void pcf8574_writePort(u8 dat,u8 *err)
{
	u8 ack =0;
	iic_start();//主机启动总线
	iic_sendByte(PCF8574_ADD<<1);  //发写数据地址 (rw=0)  查手册能知道从设备PCF8574的地址是0x20 
    //#define PCF8574_ADD			0x20
	ack = iic_checkAck();//从机向主机发应答,主机读取SDA,判断应答状态
	if(ack!=0)  //寻址出错
	{   
		iic_stop(); //停止总线
		*err=1; //寻址出错,保存错误值
		return;  //结束程序
	}
	iic_sendByte(dat);				//发数据
	ack = iic_checkAck(); //
	if(ack !=0){ //从机不接受数据,写数据出错
		iic_stop();										
		*err = 2;											
		return;		
	}
	iic_stop();					//正常停止总线
	*err = 0;
	return;
}
//主机读取pcf8574端口状态
u8 pcf8574_readPort(u8 *err){
	u8 ack =0;
	u8 temp = 0;
	iic_start();  //主机启动总线
	iic_sendByte(PCF8574_ADD<<1); //发写数据的地址
	ack = iic_checkAck(); //从机应答
	if(ack != 0)				//寻址出错
	{
		iic_stop();				//停止总线
		*err = 1;				//寻址出错,保存错误值
		return;				//结束程序
	}
	temp = temp = iic_recvByte();			//读取数据
	iic_noAckToSlave();				//主机回应不应答信号,不继续读取数据
	iic_stop();					//停止总线
	*err = 0;					//无错误
	return temp;				//返回读取的结果
}

五、蜂鸣器代码实现

esp01s的I2C ESP01S的i2c 8574_代码实现_08

由PCF8574T原理图可知道蜂鸣器接口连在P0口

esp01s的I2C ESP01S的i2c 8574_#include_09

#include "pcf8574.h"
#include "stm32f4xx.h"
#include <stdio.h>

//初始化蜂鸣器,上电时不响
//蜂鸣器接口:接PCF8574的P0,低电平响
void beep_init()
{
	u8 temp = 0;
	u8 err = 0;
	
	pcf8574_init();
	temp = pcf8674_readPort(&err);
	if(err == 0)
	{
		temp |= 0x01;  //I/O端口从p0-p7 p0置1 蜂鸣器不响 所以进行或运算
		pcf8574_writePort(temp,&err); //得到的temp 写入pcf8574模块来驱动蜂鸣器
		if(err == 0)
			printf("beep init ok!\r\n");
	}
	else
		printf("beep init error!\r\n");
}

//蜂鸣器发声
void beep_on()
{
	u8 err = 0;
	u8 temp = 0;
	
	temp = pcf8674_readPort(&err); 
	if(err == 0)
	{
		temp &= 0xfe;  //p0置0
		pcf8574_writePort(temp,&err);  //写入数据
		if(err != 0)
			printf("pcf8574 write error!\r\n");
	}
	else 
		printf("pcf8574 status read error!\r\n");
}

//蜂鸣器停止发声
void beep_off()  
{
	u8 err = 0;
	u8 temp = 0;
	
	temp = pcf8674_readPort(&err);
	if(err == 0)
	{
		temp |= 0x01;
		pcf8574_writePort(temp,&err);
		if(err != 0)
			printf("pcf8574 write error!\r\n");
	}
	else 
		printf("pcf8574 status read error!\r\n");
}

总结

本章是小白的第一篇博客,主要表述了I2C总线是怎么驱动PCF8574模块下的蜂鸣器和简单的代码实现。有表述错误的地方请见谅!