CubeMx笔记 -- IIC(位带操作实现)+ IO拓展

IO拓展件:PCF8574T
开发板:STM32F429
参考:
1、[正点原子]《STM32F429 开发指南(HAL 库版)》
2、STM32CubeMX使用之I2C通讯

1、理论基础

1.1、物理层特点

  • “总线”指多个设备共用的信号线。在I2C 通讯总线中,支持多个主机及多个从机
  • 一个 I2C 有两条总线, 数据线 (SDA) 用来表示数据,时钟线 (SCL)用于数据收发同步。
  • 每个连接到总线的设备都有一个独立的地址
    从机地址可以是 7位或 10 位
  • 总线通过上拉电阻接到电源。
    当 I2C 设备空闲时,会输出高阻态,当所有设备都空闲时,由上拉电阻把总线拉成高电平
  • 具有仲裁机制 (开漏输出:线与,低电平有效)
    多个主机同时使用总线时,为了防止数据冲突,会利用仲裁方式决定由哪个设备占用总线
  • 三种传输模式
    标准模式传输速率为 100kbit/s ,快速模式为 400kbit/s ,高速模式下可达 3.4Mbit/s
  • 连接到相同总线的 IC 数量受到总线的最大电容 400pF 限制
  • 主机Master 从机Slave

在这里插入图片描述

1.2、通信过程

1.2.1、通讯起始和停止信号

  • 空闲时 sda和scl都是高电平
  • 起始位s 停止位p
    在这里插入图片描述

1.2.2、有效数据

  • scl为高电平时数据有效
  • 每次数据传输都以字节为单位,每次传输的字节数不受限制
  • scl 1 sda 1 表示1
  • scl 1 sda 0 表示0

在这里插入图片描述
1.2.3、读写过程

  • 主机写数据到从机 (应答ACK)
    DATA数据包的大小为 8 位

在这里插入图片描述

  • 从机写数据到主机

在这里插入图片描述
主机等待从机的应答在这里插入图片描述

1.3、IIC架构

在这里插入图片描述

  • 数据寄存器 DR
  • 地址寄存器OAR1
  • 第二地址寄存器OAR2
  • 校验寄存器PEC
  • 控制寄存器CR1/2
  • 状态寄存器SR1/2
  • 控制寄存器CCR

2、工程建立(硬件iic)

ST公司实现了硬件的iic,但据说用着不方便,可以参考第3节软件实现

2.1、配置步骤

  • RCC
    设置外部晶振、PLL、主频
  • 调试口
    选择调试模式,选择调试IO口
  • IO分配
    打开相应外设开关
  • IIC参数
    设置速率、地址、主从、时序

2.2、cubemx具体配置

  • 选择开漏模式(一般只有这个选项)

在这里插入图片描述

  • 参数设置

在这里插入图片描述

3、应用(软件iic)

  • 利用EEPROM 24C02 测试
  • 这里先用标准库

3.1、原理图

在这里插入图片描述

3.2、端口模式+位带操作

端口模式设置
GPIO 端口模式寄存器 (GPIOx_MODER) (x = A…I)
在这里插入图片描述

  • 3(二进制11),将11左移10位,取反再与,使MODER5的10、11位寄存器清零
    0左移10位,使配置位10为0;即配置端口5为输入模式
    1左移10位,即配置位10为1;即配置端口5为输出模式
#define SDA_IN()  {GPIOH->MODER&=~(3<<(5*2));GPIOH->MODER|=0<<5*2;}	//PH5输入模式
#define SDA_OUT() {GPIOH->MODER&=~(3<<(5*2));GPIOH->MODER|=1<<5*2;} //PH5输出模式

位带操作
stm32之bit-band(位带)操作

3.3、iic头文件

  • 代码均来自正点原子,只是改了一点点
#ifndef _MYIIC_H
#define _MYIIC_H
	
#include "main.h"
#include "stm32f4xx_hal_gpio.h"

//IO口位带操作
#define BITBAND(addr, bitnum) ((addr & 0xF0000000)+0x2000000+((addr &0xFFFFF)<<5)+(bitnum<<2)) 
#define MEM_ADDR(addr)  *((volatile unsigned long  *)(addr)) 
#define BIT_ADDR(addr, bitnum)   MEM_ADDR(BITBAND(addr, bitnum)) 

//IO方向设置
#define SDA_IN()  {GPIOH->MODER&=~(3<<(5*2));GPIOH->MODER|=0<<5*2;}	//PH5输入模式
#define SDA_OUT() {GPIOH->MODER&=~(3<<(5*2));GPIOH->MODER|=1<<5*2;} //PH5输出模式

//IO口地址映射
#define GPIOH_ODR_Addr    (GPIOH_BASE+20) //0x40021C14
#define GPIOH_IDR_Addr    (GPIOH_BASE+16) //0x40021C10 

//IO操作
#define PHout(n)   BIT_ADDR(GPIOH_ODR_Addr,n)  //输出 gpio_writepin
#define PHin(n)    BIT_ADDR(GPIOH_IDR_Addr,n)  //输入 gpio_readpin

#define IIC_SCL   PHout(4) //SCL
#define IIC_SDA   PHout(5) //SDA      
#define READ_SDA  PHin(5)  //输入SDA  


#define CPU_FREQUENCY_MHZ    180		// STM32时钟主频 用于微秒级延时

typedef unsigned          char u8;
typedef unsigned short     int u16;
typedef unsigned           int u32;


//IIC所有操作函数
void IIC_Init(void);                //初始化IIC的IO口				 
void IIC_Start(void);				//发送IIC开始信号
void IIC_Stop(void);	  			//发送IIC停止信号
void IIC_Send_Byte(u8 txd);			//IIC发送一个字节
u8 IIC_Read_Byte(unsigned char ack);//IIC读取一个字节
u8 IIC_Wait_Ack(void); 				//IIC等待ACK信号
void IIC_Ack(void);					//IIC发送ACK信号
void IIC_NAck(void);				//IIC不发送ACK信号

void delay_us(uint32_t delay); //hal库 没有微秒延时,需要自己写
void delay_ms(uint32_t ms);

#endif

3.4、iic具体实现

  • 为了配合AT24C02的使用,具体的设置可能和其他教程有所差别
#include "myiic.h"
 	
//IIC初始化
void IIC_Init(void)
{
	GPIO_InitTypeDef GPIO_Initure;
	
	__HAL_RCC_GPIOH_CLK_ENABLE();   //使能GPIOH时钟
	
	//PH4,5初始化设置
	GPIO_Initure.Pin=GPIO_PIN_4|GPIO_PIN_5;
	GPIO_Initure.Mode=GPIO_MODE_OUTPUT_PP;  //推挽输出
	GPIO_Initure.Pull=GPIO_PULLUP;          //上拉
	GPIO_Initure.Speed=GPIO_SPEED_FREQ_HIGH;     //快速
	HAL_GPIO_Init(GPIOH,&GPIO_Initure);
	
	//都拉高表示iic空闲
	IIC_SDA=1;
	IIC_SCL=1;  
}

//产生IIC起始信号:scl 拉高 sda产生下降沿
void IIC_Start(void)
{
	SDA_OUT();     //配置sda线为输出模式
	
	IIC_SDA=1;	  	  
	IIC_SCL=1;
	delay_us(4);
 	IIC_SDA=0;//产生下降沿
	delay_us(4);
	
	IIC_SCL=0;//钳住I2C总线,准备发送或接收数据 
}

//产生IIC停止信号:scl 拉高 sda产生上升沿
void IIC_Stop(void)
{
	SDA_OUT();//sda线输出
	IIC_SCL=0;//拉低用于sda高低电平变换
	
	IIC_SDA=0;
 	delay_us(4);
	IIC_SCL=1; 
	delay_us(4);			
	IIC_SDA=1;//产生上升沿
				   	
}

//等待应答信号到来
//返回值:1,接收应答失败
//        0,接收应答成功
u8 IIC_Wait_Ack(void)
{
	u8 ucErrTime=0;
	SDA_IN();      //SDA设置为输入  
	IIC_SDA=1;
	delay_us(1);	   
	IIC_SCL=1;
	delay_us(1);	
	 
	while(READ_SDA)//sda为低表示应答信号
	{
		ucErrTime++;
		if(ucErrTime>250)
		{
			IIC_Stop();
			return 1;
		}
	}
	IIC_SCL=0;	   
	return 0;  
} 

//产生ACK应答(这里我还没弄清楚)
void IIC_Ack(void)
{
	IIC_SCL=0;
	SDA_OUT();
	IIC_SDA=0;
	delay_us(2);
	IIC_SCL=1;
	delay_us(2);
	IIC_SCL=0;
}

//不产生ACK应答		    
void IIC_NAck(void)
{
	IIC_SCL=0;
	SDA_OUT();
	IIC_SDA=1;
	delay_us(2);
	IIC_SCL=1;
	delay_us(2);
	IIC_SCL=0;
}		

//IIC发送一个字节
//返回从机有无应答
//1,有应答
//0,无应答			  
void IIC_Send_Byte(u8 txd)
{                        
	u8 t;   
	SDA_OUT(); 	    
	IIC_SCL=0;//拉低时钟开始数据传输
	for(t=0;t<8;t++)//一位一位的发
	{              
		IIC_SDA=(txd&0x80)>>7;//发送txd的最高位
		txd<<=1; 	  
		delay_us(2);   //对TEA5767这三个延时都是必须的
		IIC_SCL=1;
		delay_us(2); 
		IIC_SCL=0;	//拉低时钟,变化sda
		delay_us(2);
	}	 
} 	    

//读1个字节,ack=1时,发送ACK,ack=0,发送nACK   
u8 IIC_Read_Byte(unsigned char ack)
{
	unsigned char i,receive=0;//uint8_t
	SDA_IN();//SDA设置为输入
	for(i=0;i<8;i++ )
	{
		IIC_SCL=0; 
		delay_us(2);
		IIC_SCL=1;
		receive<<=1;
		if(READ_SDA)
		{
			receive++;   
		}
		delay_us(1); 
	}					 
		if (!ack)
		IIC_NAck();//发送nACK
		else
		IIC_Ack(); //发送ACK   
		return receive;
}


void delay_us(uint32_t delay)
{
    int last, curr, val;
    int temp;

    while (delay != 0)
    {
        temp = delay > 900 ? 900 : delay;
        last = SysTick->VAL;
        curr = last - CPU_FREQUENCY_MHZ * temp;
        if (curr >= 0)
        {
            do
            {
                val = SysTick->VAL;
            }
            while ((val < last) && (val >= curr));
        }
        else
        {
            curr += CPU_FREQUENCY_MHZ * 1000;
            do
            {
                val = SysTick->VAL;
            }
            while ((val <= last) || (val > curr));
        }
        delay -= temp;
    }
}

void delay_ms(uint32_t ms){
	
	for(int i=0;i<ms;i++){
		delay_us(1000);
	}
}

3.5、AT24C02读写驱动

  • 头文件
#ifndef _24CXX_H
#define _24CXX_H
#include "myiic.h"

#define AT24C01		127
#define AT24C02		255
#define AT24C04		511
#define AT24C08		1023
#define AT24C16		2047
#define AT24C32		4095
#define AT24C64	  8191
#define AT24C128	16383
#define AT24C256	32767  
//STM32F429开发板使用的是24c02,所以定义EE_TYPE为AT24C02
#define EE_TYPE AT24C02
					  
u8 AT24CXX_ReadOneByte(u16 ReadAddr);							//指定地址读取一个字节
void AT24CXX_WriteOneByte(u16 WriteAddr,u8 DataToWrite);		//指定地址写入一个字节
void AT24CXX_WriteLenByte(u16 WriteAddr,u32 DataToWrite,u8 Len);//指定地址开始写入指定长度的数据
u32 AT24CXX_ReadLenByte(u16 ReadAddr,u8 Len);					//指定地址开始读取指定长度数据
void AT24CXX_Write(u16 WriteAddr,u8 *pBuffer,u16 NumToWrite);	//从指定地址开始写入指定长度的数据
void AT24CXX_Read(u16 ReadAddr,u8 *pBuffer,u16 NumToRead);   	//从指定地址开始读出指定长度的数据

u8 AT24CXX_Check(void);  //检查器件
void AT24CXX_Init(void); //初始化IIC
#endif

  • 具体实现
#include "24cxx.h"

//初始化IIC接口
void AT24CXX_Init(void)
{
	IIC_Init();//IIC初始化
}
//在AT24CXX指定地址读出一个数据
//ReadAddr:开始读数的地址  
//返回值  :读到的数据
u8 AT24CXX_ReadOneByte(u16 ReadAddr)
{				  
	u8 temp=0;		  	    																 
	IIC_Start();  
	if(EE_TYPE>AT24C16)
	{
		IIC_Send_Byte(0XA0);	   //发送写命令
		IIC_Wait_Ack();
		IIC_Send_Byte(ReadAddr>>8);//发送高地址	    
	}
	else 
		IIC_Send_Byte(0XA0+((ReadAddr/256)<<1));   //发送器件地址0XA0,写数据 	   
	IIC_Wait_Ack(); 
	IIC_Send_Byte(ReadAddr%256);   //发送低地址
	IIC_Wait_Ack();	    
	IIC_Start();  	 	   
	IIC_Send_Byte(0XA1);           //进入接收模式			   
	IIC_Wait_Ack();	 
	temp=IIC_Read_Byte(0);		   
	IIC_Stop();//产生一个停止条件	    
	return temp;
}
//在AT24CXX指定地址写入一个数据
//WriteAddr  :写入数据的目的地址    
//DataToWrite:要写入的数据
void AT24CXX_WriteOneByte(u16 WriteAddr,u8 DataToWrite)
{				   	  	    																 
	IIC_Start();  
	if(EE_TYPE>AT24C16)
	{
		IIC_Send_Byte(0XA0);	    //发送写命令
		IIC_Wait_Ack();
		IIC_Send_Byte(WriteAddr>>8);//发送高地址	  
	}
	else 
		IIC_Send_Byte(0xA0+((WriteAddr/256)<<1));   //发送器件地址0XA0,写数据 	 
	IIC_Wait_Ack();	   
	IIC_Send_Byte(WriteAddr%256);   //发送低地址
	IIC_Wait_Ack(); 	 										  		   
	IIC_Send_Byte(DataToWrite);     //发送字节							   
	IIC_Wait_Ack();  		    	   
	IIC_Stop();//产生一个停止条件 
	delay_ms(10);	 
}

//在AT24CXX里面的指定地址开始写入长度为Len的数据
//该函数用于写入16bit或者32bit的数据.
//WriteAddr  :开始写入的地址  
//DataToWrite:数据数组首地址
//Len        :要写入数据的长度2,4
void AT24CXX_WriteLenByte(u16 WriteAddr,u32 DataToWrite,u8 Len)
{  	
	u8 t;
	for(t=0;t<Len;t++)
	{
		AT24CXX_WriteOneByte(WriteAddr+t,(DataToWrite>>(8*t))&0xff);
	}												    
}

//在AT24CXX里面的指定地址开始读出长度为Len的数据
//该函数用于读出16bit或者32bit的数据.
//ReadAddr   :开始读出的地址 
//返回值     :数据
//Len        :要读出数据的长度2,4
u32 AT24CXX_ReadLenByte(u16 ReadAddr,u8 Len)
{  	
	u8 t;
	u32 temp=0;
	for(t=0;t<Len;t++)
	{
		temp<<=8;
		temp+=AT24CXX_ReadOneByte(ReadAddr+Len-t-1); 	 				   
	}
	return temp;												    
}

//检查AT24CXX是否正常
//这里用了24XX的最后一个地址(255)来存储标志字.
//如果用其他24C系列,这个地址要修改
//返回1:检测失败
//返回0:检测成功
u8 AT24CXX_Check(void)
{
u8 temp;
temp = AT24CXX_ReadOneByte(255);//避免每次开机都写AT24CXX			   
	if(temp==0X55)
		return 0;		   
	else//排除第一次初始化的情况
	{
		AT24CXX_WriteOneByte(255,0X55);
	    temp=AT24CXX_ReadOneByte(255);	  
		if(temp==0X55)
			return 0;
	}
	return 1;											  
}

//在AT24CXX里面的指定地址开始读出指定个数的数据
//ReadAddr :开始读出的地址 对24c02为0~255
//pBuffer  :数据数组首地址
//NumToRead:要读出数据的个数
void AT24CXX_Read(u16 ReadAddr,u8 *pBuffer,u16 NumToRead)
{
	while(NumToRead)
	{
		*pBuffer++=AT24CXX_ReadOneByte(ReadAddr++);	
		NumToRead--;
	}
} 

//在AT24CXX里面的指定地址开始写入指定个数的数据
//WriteAddr :开始写入的地址 对24c02为0~255
//pBuffer   :数据数组首地址
//NumToWrite:要写入数据的个数
void AT24CXX_Write(u16 WriteAddr,u8 *pBuffer,u16 NumToWrite)
{
	while(NumToWrite--)
	{
		AT24CXX_WriteOneByte(WriteAddr,*pBuffer);
		WriteAddr++;
		pBuffer++;
	}
}

  • 部分测试代码
const u8 TEXT_Buffer[]={"IIC AT24c02 测试"};
#define SIZE sizeof(TEXT_Buffer)
//需要自行配置串口
#define u1_printf(...) HAL_UART_Transmit((UART_HandleTypeDef *)&huart1,\
									(const uint8_t *)u1_data,\
									(uint16_t)sprintf((char *)u1_data,__VA_ARGS__),\
									(uint32_t)0xffff)

uint8_t u1_data[2048];//设为全局
u8 datatemp[SIZE] = {0};

AT24CXX_Init();				    //初始化IIC 
while(AT24CXX_Check());//检测不到24c02
u1_printf("AT24C02 OK\r\n");

AT24CXX_Write(0,(u8*)TEXT_Buffer,SIZE);
u1_printf("write ok\r\n");
AT24CXX_Read(0,datatemp,SIZE);
u1_printf("%s\r\n",datatemp)

在这里插入图片描述

4、利用iic拓展io

4.1、原理图

在这里插入图片描述
INT为拓展件的中断线 低电平有效
就是mcu用scl 和 sda 两根线 来 读写 p0 ~ p7 的引脚电平值

  • mcu往拓展件写数据

在这里插入图片描述

  • mcu从拓展件读数据
    在这里插入图片描述

注意:一旦中断有效后,必须对 PCF8574T 进行一次读取/写入操作,复位中断,
才可以输出下一次中断,否则中断将一直保持(无法输出下一次输入信号变化所产生的中断)。

4.2、代码

代码就直接照搬正点原子的源码了

  • 头文件
#ifndef __PCF8574_H
#define __PCF8574_H
#include "sys.h"
#include "myiic.h"
	
#define PCF8574_INT  PBin(12) //PCF8574 INT脚

#define PCF8574_ADDR 	0X40	//PCF8574地址(左移了一位)

//PCF8574各个IO的功能
#define BEEP_IO         0		//蜂鸣器控制引脚  	P0
#define AP_INT_IO       1   	//AP3216C中断引脚	P1
#define DCMI_PWDN_IO    2    	//DCMI的电源控制引脚	P2
#define USB_PWR_IO      3    	//USB电源控制引脚	P3
#define EX_IO      	  	4    	//扩展IO,自定义使用 	P4
#define MPU_INT_IO      5   	//MPU9250中断引脚	P5
#define RS485_RE_IO     6    	//RS485_RE引脚		P6
#define ETH_RESET_IO    7    	//以太网复位引脚		P7

u8 PCF8574_Init(void); //初始化
u8 PCF8574_ReadOneByte(void); 
void PCF8574_WriteOneByte(u8 DataToWrite);
void PCF8574_WriteBit(u8 bit,u8 sta);//写入数据到指定引脚
u8 PCF8574_ReadBit(u8 bit);
#endif

  • 具体实现
#include "pcf8574.h"
#include "delay.h"
 	

//初始化PCF8574
u8 PCF8574_Init(void)
{
	u8 temp=0;
	GPIO_InitTypeDef GPIO_Initure;
	__HAL_RCC_GPIOB_CLK_ENABLE();           //使能GPIOB时钟

	GPIO_Initure.Pin=GPIO_PIN_12;           //PB12
	GPIO_Initure.Mode=GPIO_MODE_INPUT;      //输入
	GPIO_Initure.Pull=GPIO_PULLUP;          //上拉
	GPIO_Initure.Speed=GPIO_SPEED_HIGH;     //高速
	HAL_GPIO_Init(GPIOB,&GPIO_Initure);     //初始化
	IIC_Init();					            //IIC初始化 	
//检查PCF8574是否在位
	IIC_Start();    	 	   
	IIC_Send_Byte(PCF8574_ADDR);            //写地址			   
	temp=IIC_Wait_Ack();		            //等待应答,通过判断是否有ACK应答,来判断PCF8574的状态
	IIC_Stop();					            //产生一个停止条件
	PCF8574_WriteOneByte(0XFF);	            //默认情况下所有IO输出高电平
	return temp;
}

//读取PCF8574的8位IO值
//返回值:读到的数据
u8 PCF8574_ReadOneByte(void)
{				  
	u8 temp=0;		  	    																 
	IIC_Start();    	 	   
	IIC_Send_Byte(PCF8574_ADDR|0X01);   //进入接收模式			   
	IIC_Wait_Ack();	 
	temp=IIC_Read_Byte(0);		   
	IIC_Stop();							//产生一个停止条件	    
	return temp;
}

//向PCF8574写入8位IO值  
//DataToWrite:要写入的数据
void PCF8574_WriteOneByte(u8 DataToWrite)
{				   	  	    																 
	IIC_Start();  
	IIC_Send_Byte(PCF8574_ADDR|0X00);   //发送器件地址0X40,写数据 	 
	IIC_Wait_Ack();	    										  		   
	IIC_Send_Byte(DataToWrite);    	 	//发送字节							   
	IIC_Wait_Ack();      
	IIC_Stop();							//产生一个停止条件 
	delay_ms(10);	 
}

//设置PCF8574某个IO的高低电平
//bit:要设置的IO编号,0~7
//sta:IO的状态;0或1
void PCF8574_WriteBit(u8 bit,u8 sta)
{
	u8 data;
	data=PCF8574_ReadOneByte(); //先读出原来的设置
	if(sta==0)
	{
		data&=~(1<<bit); //把bit位的置零    
	}
	else 
	{
		data|=1<<bit;
	}
	PCF8574_WriteOneByte(data); //写入新的数据
}

//读取PCF8574的某个IO的值
//bit:要读取的IO编号,0~7
//返回值:此IO的值,0或1
u8 PCF8574_ReadBit(u8 bit)
{
	u8 data;
	data=PCF8574_ReadOneByte(); //先读取这个8位IO的值 
	if(data&(1<<bit))
	{
		return 1;
	}
	else 
		return 0;   
}

  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值