前言
1.利用i2c协议驱动系统中的PCF8574模块进而控制蜂鸣器
一、i2c协议的简单描述
I2C设备连接I2C总线
SDA为数据线,SCL为时钟线,I2C由这两条线组成,其上连接由主机控制器和从设备。起始位和停止位的条件
当SCL时钟线为高电平,SDA数据线由高变低时,为起始信号。SCL时钟线为高电平,SDA数据线由低变高时,为停止信号。位传输
应答
二、硬件电路接口
1.I2C两条线的接口
2.PCF8574T原理图
3.蜂鸣器原理图
三、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; //返回读取的结果
}
五、蜂鸣器代码实现
由PCF8574T原理图可知道蜂鸣器接口连在P0口
#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模块下的蜂鸣器和简单的代码实现。有表述错误的地方请见谅!