这两天学习STM32的IIC,实现读写24c02的数据,对IIC不是那么的陌生,在这里,将这两天的学习的流程总结下,整理整理自己的思路。
IIC是一种通信协议,通信方式相对比较简单,主要有两条线,SDA,SCL。SDA是数据总线,上面走命令和数据,而SCL只是一条时钟线,其保证数据按照时钟节拍来进行传输。IIC上面可以外挂很多的器件,每一个器件对应者不同的地址,通过地址将不同的器件进行分开,保证不同芯片之间的数据传输,由于每一个器件都是可以独立的收发,故,每一个器件都是主机/从机。
大致的一个数据传输流程是:主机向SDA线上发送一个起始信号,表示有信号进行传输,此时所有连接到IIC总线上的芯片都处于接收状态,接下来,主机发送想要与其进行数据传输的从机地址信号,所有的从机都会接收到该地址信号并和自己固有的地址信号进行匹配,当配对成功时,接下来就在时钟信号的带动下进行数据传输,数据的传输是按照每8位一个单元进行数据的传输。每一位的传输过程中,在SCL高电平期间,一定要保证SDA数值的稳定,否则会出现出错的情况,SDA数值的改变发生在SCL的低电平期间。最终8位全部传输完毕,从机产生一个应答信号给主机,主机在接收到该应答信号后决定接下来是发送一组新的数据还是终止发送。
分析之前,将一些定义告诉大家:
#define IIC_SCL_H GPIO_SetBits(GPIOB,GPIO_Pin_6) #define IIC_SDA_H GPIO_SetBits(GPIOB,GPIO_Pin_7) #define IIC_SCL_L GPIO_ResetBits(GPIOB,GPIO_Pin_6) #define IIC_SDA_L GPIO_ResetBits(GPIOB,GPIO_Pin_7) #define READ_SDA GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_7)①起始信号
SDA、SCL线默认是高,表示总线处于空闲状态,接着SDA线被主机拉低,表示主机有信号进行传输,要么是发数据,或者是要进行读数据,当SDA线拉低之后,SCL线也同样被拉低,准备接下来的数据传输。此开始信号的产生如下:
//IIC起始信号 void IIC_Start(void) { IIC_SCL_H; IIC_SDA_H; delau_us(5); IIC_SDA_L; delau_us(5); IIC_SCL_L; }②终止信号
终止信号:就是在SCL为高电平时,SDA出现一个上升沿的跳变,即表示终止信号
//产生IIC停止信号 void IIC_Stop(void) { IIC_SDA_L; IIC_SCL_L; delau_us(5); IIC_SCL_H; delau_us(4); IIC_SDA_H; delau_us(5); }③等待应答信号
主机将一组数据发送完毕,接下来就进入等待从机发送应答信号到来,从机会在第9个时钟信号时,发送应答信号,此应答信号就是将SDA信号拉低。
u8 IIC_Wait_Ack(void) { u8 flag_ack=0; IIC_SCL_L; IIC_SDA_H; delau_us(5); IIC_SCL_H; delau_us(5); while(READ_SDA && flag_ack<4) { flag_ack++; delau_us(1); } if(flag_ack>=4) { IIC_Stop(); return 1; } IIC_SCL_L; return 0; }④产生应答信号
有效应答位ACK的要求是,接收器在第9个时钟脉冲之前的低电平期间将SDA线拉低,并且确保在该时钟的高电平期间为稳定的低电平。
void IIC_Ack(void) { IIC_SCL_L; IIC_SDA_L; delau_us(5); IIC_SCL_H; delau_us(5); }⑤不产生应答信号
void IIC_NAck(void) { IIC_SCL_L; IIC_SDA_H; delau_us(4); IIC_SCL_H; delau_us(4); }⑥发送接收数据
时钟信号为高电平期间:数据线上的数据必须保持稳定 时钟信号为低电平期间:数据线上的高或低电平状态才允许变化。
void IIC_Send_Byte(u8 txd) { u8 t=0; /*时钟信号拉低,数据准备好再拉高进行传输*/ for(t = 0; t < 8; t++) { IIC_SCL_L; if(txd&0x80) IIC_SDA_H; else IIC_SDA_L; delau_us(5); /*SCL拉高传输数据*/ IIC_SCL_H; delau_us(5); txd <<= 1; } } u8 IIC_Read_Byte(void) { u8 receive = 0; u8 i=8; for(i = 0; i < 8; i++ ) { /*SCL拉低*/ IIC_SCL_L; delau_us(4); /*拉高SCL产生一个有效的时钟信号*/ IIC_SCL_H; /*读取总线上的数据*/ receive<<=1; if(READ_SDA) receive|=1; delau_us(4); } return receive; }
针对24c02进行编程
1、在AT24C02指定地址读出一个数据
先来看AT24C02的随机读数据的时序图
此时序图上需要注意两个地方,箭头所标记的两次开始信号,以及椭圆圈起来的写信号和读信号,接下来就分析该过程
首先,发送一个开始信号,接下来发送器件的地址,注意最后一位为Write标志位(0),等待应答,然后发送要读的地址,等待应答,又要发送一个起始信号,发送器件的地址,注意此时,最后一位为Read标志位(1),等待应答,接下来数据就可以读回来了。
器件的地址:
地址按照这个来进行设置,AT24CXX,这个XX即表示存储量XX(K),很显然AT24C02为2K,有256Byte,A2,A1,A0定义看芯片,如下
故,AT24C02的写地址为0XA0,读地址为0XA1,到这里,任督二脉已打通,接下来,上源码
#include "24c02.h" #include "iic.h" #include<stdio.h> #include<string.h> //总线初始化 void AT_24c02_int(void) { IIC_Init(); } //data_addr 字节地址 //data 数据 void AT_24c02_write_data(u8 data_addr,u8 data) { u8 ack_flag=0; IIC_Start(); IIC_Send_Byte(0xa0); ack_flag=IIC_Wait_Ack(); IIC_Send_Byte(data_addr); ack_flag=IIC_Wait_Ack(); IIC_Send_Byte(data); ack_flag=IIC_Wait_Ack(); IIC_Stop(); } u8 AT_24c02_read_data(u8 data_addr) { u8 ack_flag=0; u8 data=0; IIC_Start(); IIC_Send_Byte(0xa0); ack_flag=IIC_Wait_Ack(); IIC_Send_Byte(data_addr); ack_flag=IIC_Wait_Ack(); IIC_Start(); IIC_Send_Byte(0xa1); ack_flag=IIC_Wait_Ack(); data=IIC_Read_Byte(); IIC_NAck(); IIC_Stop(); return data; }主函数:
#include "key.h" #include "led.h" #include "beep.h" #include "interrupt.h" #include "usart.h" #include "timecatch.h" #include "Adc.h" #include <stdio.h> #include "iic.h" #include "24c02.h" u8 key_flag=0; u8 usart_rx_buf[20]={0}; u8 TIM4CH3_CAPTURE_STA=0; u32 temp=0; u16 ad_result=0; u8 ad_temp[3]; int i; int main(void) { delay_init(); gpio_beep_configuration(); NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); exti_configuration(); gpio_led_configuration(); gpio_key_configuration(); exti_nvic_configuration(); usart_configuration(); AT_24c02_int(); AT_24c02_write_data(0x01,0x55); delau_ms(10); key_flag=AT_24c02_read_data(0x01); usart_send(&key_flag); }