STM32 第十天
- IIC
1、IIC概述
I2C(IIC,Inter-Integrated Circuit),两线式串行总线,由PHILIPS公司开发用于连接微控制器及其外围设备。
它是由数据线SDA和时钟SCL构成的串行总线,可发送和接收数据。在CPU与被控IC之间、IC与IC之间进行双向传送,高速IIC总线一般可达400kbps。
IIC是半双工通信方式,可实现一对多。
SDA:数据线,用于传输数据;可主机到从机,也可以从从机到主机。
SCL:时钟线,只能由主机发送,用于数据同步,一个脉冲发送/接收一位数据。
2、I2C通信原理-I2C协议
- 空闲状态
- 开始信号
- 停止信号
- 应答信号
- 数据的有效性
空闲状态
I2C总线总线的SDA和SCL两条信号线同时处于高电平时,规定为总线的空闲状态。此时各个器件的输出级场效应管均处在截止状态,即释放总线,由两条信号线各自的上拉电阻把电平拉高。
起始信号(主机发出,起始信号是针对所有的从机)
起始信号:当SCL为高期间,SDA由高到低的跳变;启动信号是一种电平跳变时序(上升沿或者下降沿)信号,而不是一个电平信号。
注意看启动信号的时序是由左到右进行查看。
//启动信号
void Iic_Start(void)
{
//设置数据线为输出模式
Iic_Sda_Mode(GPIO_Mode_OUT);
SCL = 1;
SDA_OUT = 1;
delay_us(5);
SDA_OUT = 0;
delay_us(5);
SCL = 0;
}
停止信号(主机发出)
停止信号:当SCL为高期间,SDA由低到高的跳变;停止信号也是一种电平跳变时序信号,而不是一个电平信号。
注意看停止信号的时序是由左到右进行查看。
//停止信号
void Iic_Stop(void)
{
//设置数据线为输出模式
Iic_Sda_Mode(GPIO_Mode_OUT);
SCL = 0;
SDA_OUT = 0;
delay_us(5);
SCL = 1;
delay_us(5);
SDA_OUT = 1;
}
数据有效性
I2C总线进行数据传送时,时钟信号为高电平期间,数据线上的数据必须保持稳定,只有在时钟线上的信号为低电平期间,数据线上的高电平或低电平状态才允许变化。
即:数据在SCL的上升沿到来之前就需准备好。并在在下降沿到来之前必须稳定。
注意:在主机接受数据时,使用是的上面相同的时序,只不过SCL由主机发出,SDA由从机发出,那么主机要做为输入,进行接受数据。同样的道理,在SCL为低电平区间,从机在这区间可改变数据,在SCL为高电平区间保持数据的稳定。所以主机在SCL为高电平区间判断引脚的电平从而得到对应的数据。
应答信号ACK
发送器(STM32或者是从机)每发送一个字节,就在时钟脉冲9(前面8个脉冲周期用来发8位)期间释放数据线,由接收器反馈一个应答信号(反馈:发送一位数据,做为应答)。 应答信号为低电平时,规定为有效应答位(ACK简称应答位),表示接收器已经成功地接收了该字节;应答信号为高电平时,规定为非应答位(NACK),一般表示接收器接收该字节没有成功。
对于反馈有效应答位ACK的要求是,接收器在第9个时钟脉冲之前的低电平期间将SDA线拉低,并且确保在该时钟的高电平期间为稳定的低电平。 如果接收器是主控器,则在它收到最后一个字节后,发送一个NACK信号,以通知被控发送器结束数据发送,并释放SDA线,以便主控接收器发送一个停止信号P。
结论:
- 每发送(主机与从机都适用)一个字节,则会(主机与从机都适用)接受一个应答信号(信号其实就是一位数据);每接收(主机与从机都适用)一个字节,则会(主机与从机都适用)发送一位应答信号(信号其实就是一位数据);
- 规定低电平为有效应答,表示成功接受字节;规定高电平为无效应答,表示没有成功接受字节。
- 假设主机接受数据,如果不想再接受,则发送一个无效应答(非应答)来通知从机不要再进行传输数据。
- AT24C02
1、 24C02简介
- 24C02是一个2K位串行CMOS 的EEPROM,内部含有256个8位字节(大小:256字节)。
- 与 400KHz I2C 总线兼容
- 1.8 到 6.0 伏工作电压范围
- 低功耗 CMOS 技术
- 写保护功能 当 WP 为高电平时进入写保护状态
- 页写缓冲器
- 自定时擦写周期
- 1,000,000 编程/擦除周期
- 可保存数据 100 年
- 8 脚 DIP SOIC 或 TSSOP 封装
- 温度范围 商业级 工业级和汽车级
2、AT24C02引脚
3、 AT24C02地址查找
- LSB的意思是:全称为Least Significant Bit,在二进制数中意为最低有效位,一般来说,MSB位于二进制数的最左侧,LSB位于二进制数的最右侧。
2、MSB的意思是:全称为Most Significant Bit,在二进制数中属于最高有效位,MSB是最高加权位,与十进制数字中最左边的一位类似。
4、 AT24CO2读写时序
写时序
读时序
iic
#include "iic.h"
/****************************************
引脚说明
SCL -- PB8
SDA -- PB9
*****************************************/
void Iic_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
//打开GPIOB组时钟
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9|GPIO_Pin_8; //引脚8
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT; //输出模式
GPIO_InitStruct.GPIO_OType = GPIO_OType_PP; //推挽输出
GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_UP; //上拉
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; //速度
GPIO_Init(GPIOB, &GPIO_InitStruct);
//空闲状态
SCL = 1;
SDA_OUT = 1;
}
//引脚模式变更--SDA
void Iic_Sda_Mode(GPIOMode_TypeDef mode)
{
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //第9号引脚
GPIO_InitStructure.GPIO_Mode = mode; //输入/输出模式
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推挽输出,增强驱动能力,引脚的输出电流更大
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //引脚的速度最大为100MHz
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; //没有使用内部上拉电阻
GPIO_Init(GPIOB, &GPIO_InitStructure);
}
//启动信号
void Iic_Start(void)
{
//设置数据线为输出模式
Iic_Sda_Mode(GPIO_Mode_OUT);
SCL = 1;
SDA_OUT = 1;
delay_us(5);
SDA_OUT = 0;
delay_us(5);
SCL = 0;
}
//停止信号
void Iic_Stop(void)
{
//设置数据线为输出模式
Iic_Sda_Mode(GPIO_Mode_OUT);
SCL = 0;
SDA_OUT = 0;
delay_us(5);
SCL = 1;
delay_us(5);
SDA_OUT = 1;
}
//发送一位数据 发1引脚置1 发0引脚置0
void Iic_Send_Ack(u8 ack)
{
//设置数据线为输出模式
Iic_Sda_Mode(GPIO_Mode_OUT);
//一个脉冲周期
SCL = 0;
//时钟为低电平时准备数据
if(ack == 1)
{
SDA_OUT = 1;
}
else
{
SDA_OUT = 0;
}
delay_us(5);
SCL = 1;
delay_us(5);
SCL = 0;
}
//发送一个字节 先发高位
void Iic_Send_Byte(u8 data)
{
u8 i;
//设置数据线为输出模式
Iic_Sda_Mode(GPIO_Mode_OUT);
SCL = 0;
for(i=0; i<8; i++)
{
//时钟为低电平时准备数据
if(data & (0x01<<(7-i)))
{
SDA_OUT = 1;
}
else
{
SDA_OUT = 0;
}
delay_us(5);
SCL = 1;
delay_us(5);
SCL = 0;
}
}
//接受一位数据
u8 Iic_Recv_Ack(void)
{
u8 ack = 0;
//设置数据线为输出模式
Iic_Sda_Mode(GPIO_Mode_IN);
//一个脉冲周期
SCL = 0;
delay_us(5);
SCL = 1;
delay_us(5);
//时钟为低电平时准备数据
if(SDA_IN == 1)
{
ack = 1;
}
else
{
ack = 0;
}
SCL = 0;
return ack;
}
//接受一个字节数据
u8 Iic_Recv_Byte(void)
{
u8 i;
u8 data = 0x00; //0x00
//设置数据线为输出模式
Iic_Sda_Mode(GPIO_Mode_IN);
SCL = 0;
for(i=0; i<8; i++)
{
delay_us(5);
SCL = 1;
delay_us(5);
//时钟为低电平时准备数据
if(SDA_IN == 1)
{
data |= 0x01<<(7-i);
}
SCL = 0;
}
return data;
}
#ifndef __IIC_H
#define __IIC_H
#include "stm32f4xx.h"
#include "sys.h"
#include "delay.h"
/*******************************
引脚说明
SCL -- PB8
SDA -- PB9
********************************/
#define SCL PBout(8)
#define SDA_IN PBin(9)
#define SDA_OUT PBout(9)
void Iic_Init(void);
#endif
iic_at24c02
#include "iic.h"
/****************************************
引脚说明
SCL -- PB8
SDA -- PB9
*****************************************/
void Iic_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
//打开GPIOB组时钟
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9|GPIO_Pin_8; //引脚8
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT; //输出模式
GPIO_InitStruct.GPIO_OType = GPIO_OType_PP; //推挽输出
GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_UP; //上拉
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; //速度
GPIO_Init(GPIOB, &GPIO_InitStruct);
//空闲状态
SCL = 1;
SDA_OUT = 1;
}
//引脚模式变更--SDA
void Iic_Sda_Mode(GPIOMode_TypeDef mode)
{
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //第9号引脚
GPIO_InitStructure.GPIO_Mode = mode; //输入/输出模式
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推挽输出,增强驱动能力,引脚的输出电流更大
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //引脚的速度最大为100MHz
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; //没有使用内部上拉电阻
GPIO_Init(GPIOB, &GPIO_InitStructure);
}
//启动信号
void Iic_Start(void)
{
//设置数据线为输出模式
Iic_Sda_Mode(GPIO_Mode_OUT);
SCL = 1;
SDA_OUT = 1;
delay_us(5);
SDA_OUT = 0;
delay_us(5);
SCL = 0;
}
//停止信号
void Iic_Stop(void)
{
//设置数据线为输出模式
Iic_Sda_Mode(GPIO_Mode_OUT);
SCL = 0;
SDA_OUT = 0;
delay_us(5);
SCL = 1;
delay_us(5);
SDA_OUT = 1;
}
//发送一位数据 发1引脚置1 发0引脚置0
void Iic_Send_Ack(u8 ack)
{
//设置数据线为输出模式
Iic_Sda_Mode(GPIO_Mode_OUT);
//一个脉冲周期
SCL = 0;
//时钟为低电平时准备数据
if(ack == 1)
{
SDA_OUT = 1;
}
else
{
SDA_OUT = 0;
}
delay_us(5);
SCL = 1;
delay_us(5);
SCL = 0;
}
//发送一个字节 先发高位
void Iic_Send_Byte(u8 data)
{
u8 i;
//设置数据线为输出模式
Iic_Sda_Mode(GPIO_Mode_OUT);
SCL = 0;
for(i=0; i<8; i++)
{
//时钟为低电平时准备数据
if(data & (0x01<<(7-i)))
{
SDA_OUT = 1;
}
else
{
SDA_OUT = 0;
}
delay_us(5);
SCL = 1;
delay_us(5);
SCL = 0;
}
}
//接受一位数据
u8 Iic_Recv_Ack(void)
{
u8 ack = 0;
//设置数据线为输出模式
Iic_Sda_Mode(GPIO_Mode_IN);
//一个脉冲周期
SCL = 0;
delay_us(5);
SCL = 1;
delay_us(5);
//时钟为低电平时准备数据
if(SDA_IN == 1)
{
ack = 1;
}
else
{
ack = 0;
}
SCL = 0;
return ack;
}
//接受一个字节数据
u8 Iic_Recv_Byte(void)
{
u8 i;
u8 data = 0x00; //0x00
//设置数据线为输出模式
Iic_Sda_Mode(GPIO_Mode_IN);
SCL = 0;
for(i=0; i<8; i++)
{
delay_us(5);
SCL = 1;
delay_us(5);
//时钟为低电平时准备数据
if(SDA_IN == 1)
{
data |= 0x01<<(7-i);
}
SCL = 0;
}
return data;
}
//u8 addr:写在AT24C02时的起始地址
void AT24c02_Write(u8 addr, u8 *data, u8 len)
{
u8 ack = 0;
//启动信号
Iic_Start();
//发送从机设备地址,并执行写操作
Iic_Send_Byte(0xA0);
ack = Iic_Recv_Ack();
if(ack == 1)
{
printf("ack failure\r\n");
Iic_Stop();
return;
}
//发要在AT24C02内写数据的起始地址
Iic_Send_Byte(addr);
ack = Iic_Recv_Ack();
if(ack == 1)
{
printf("ack failure\r\n");
Iic_Stop();
return;
}
while(len--)
{
//写数据
Iic_Send_Byte(*data);
ack = Iic_Recv_Ack();
if(ack == 1)
{
printf("ack failure\r\n");
Iic_Stop();
return;
}
//地址指向下一个数据空间
data++;
}
Iic_Stop();
printf("write finish\r\n");
}
void AT24c02_Read(u8 addr, u8 *data, u8 len)
{
u8 ack = 0;
//启动信号
Iic_Start();
//发送从机设备地址,并执行写操作
Iic_Send_Byte(0xA0);
ack = Iic_Recv_Ack();
if(ack == 1)
{
printf("ack failure\r\n");
Iic_Stop();
return;
}
//发要在AT24C02内读数据的起始地址
Iic_Send_Byte(addr);
ack = Iic_Recv_Ack();
if(ack == 1)
{
printf("ack failure\r\n");
Iic_Stop();
return;
}
//启动信号
Iic_Start();
//发送从机设备地址,并执行读操作
Iic_Send_Byte(0xA1);
ack = Iic_Recv_Ack();
if(ack == 1)
{
printf("ack failure\r\n");
Iic_Stop();
return;
}
while(len--)
{
*data = Iic_Recv_Byte();
if(len > 0)
Iic_Send_Ack(0);
//地址指向下一个数据空间
data++;
}
Iic_Send_Ack(1);
Iic_Stop();
}
//u8 addr:页起始地址
void At24c02_Data(u8 addr, u8 *write_buff, u8 len)
{
u8 i, page, data;
//计算有多少页及剩余多少个字节
page = len/8;
data = len%8;
for(i=0; i<page; i++)
{
AT24c02_Write(addr, write_buff, 8);
write_buff+=8; //取一个8个字节
addr+=8; //下一页
delay_ms(10);
}
AT24c02_Write(addr, write_buff, data);
}
#ifndef __IIC_H
#define __IIC_H
#include "stm32f4xx.h"
#include "sys.h"
#include "delay.h"
/*******************************
引脚说明
SCL -- PB8
SDA -- PB9
********************************/
#define SCL PBout(8)
#define SDA_IN PBin(9)
#define SDA_OUT PBout(9)
void Iic_Init(void);
void AT24c02_Write(u8 addr, u8 *data, u8 len);
void AT24c02_Read(u8 addr, u8 *data, u8 len);
void At24c02_Data(u8 addr, u8 *write_buff, u8 len);
#endif
#include "stm32f4xx.h"
#include "led.h"
#include "key.h"
#include "exti.h"
#include "delay.h"
#include "tim.h"
#include "pwm.h"
#include "usart.h"
#include "sys.h"
#include "dht11.h"
#include "infrared.h"
#include "iwdg.h"
#include "iic.h"
u8 buffer[64] = {0};
u8 rx_buffer[64] = {0};
u8 count = 0, rx_i = 0;
u8 rx_flag = 0; //接受标志位,rx_flag = 表示数据帧完毕
void USART1_IRQHandler(void)
{
//判断接收标志位是否为1
if(USART_GetITStatus(USART1, USART_IT_RXNE) == SET)
{
//清空接受标志位
USART_ClearITPendingBit(USART1, USART_IT_RXNE);
//接受数据
buffer[count++] = USART_ReceiveData(USART1);
//判断数据是否为':',如果是':'数据帧结束
if(buffer[count-1] == ':')
{
//数据赋值到rx_buffer,并过滤帧尾
for(rx_i=0; rx_i<(count-1); rx_i++)
{
rx_buffer[rx_i] = buffer[rx_i];
}
//清空数组
memset(buffer, 0, sizeof(buffer));
//标志位置1
rx_flag = 1;
//下一帧数据从buffer[0]开始接受
count = 0;
}
}
}
void USART2_IRQHandler(void)
{
u8 data;
//判断接收标志位是否为1
if(USART_GetITStatus(USART2, USART_IT_RXNE) == SET)
{
//清空接受标志位
USART_ClearITPendingBit(USART2, USART_IT_RXNE);
//接受数据
buffer[count++] = USART_ReceiveData(USART2);
//判断数据是否为':',如果是':'数据帧结束
if(buffer[count-1] == ':')
{
//数据赋值到rx_buffer,并过滤帧尾
for(rx_i=0; rx_i<(count-1); rx_i++)
{
rx_buffer[rx_i] = buffer[rx_i];
}
//清空数组
memset(buffer, 0, sizeof(buffer));
//标志位置1
rx_flag = 1;
//下一帧数据从buffer[0]开始接受
count = 0;
}
}
}
void USART3_IRQHandler(void)
{
u8 data;
//判断接收标志位是否为1
if(USART_GetITStatus(USART3, USART_IT_RXNE) == SET)
{
//清空接受标志位
USART_ClearITPendingBit(USART3, USART_IT_RXNE);
//接受数据
buffer[count++] = USART_ReceiveData(USART3);
//判断数据是否为':',如果是':'数据帧结束
if(buffer[count-1] == ':')
{
//数据赋值到rx_buffer,并过滤帧尾
for(rx_i=0; rx_i<(count-1); rx_i++)
{
rx_buffer[rx_i] = buffer[rx_i];
}
//清空数组
memset(buffer, 0, sizeof(buffer));
//标志位置1
rx_flag = 1;
//下一帧数据从buffer[0]开始接受
count = 0;
}
}
}
int main(void)
{
u8 write_buff[13] = "helloworld";
u8 read_buff[13] = {0};
//设置NVIC分组(一个工程只能设置一个分组)
//第二分组;抢占优先组取值范围:0~3 响应先组取值范围:0~3
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
Led_Init();
Delay_Init();
Usart1_Init(115200);
Iic_Init();
//AT24c02_Write(0x00, write_buff, 8);
At24c02_Data(0x00, write_buff, 10);
delay_ms(50);
AT24c02_Read(0x00, read_buff, 10);
printf("read_buff:%s\r\n", read_buff);
while(1)
{
//这里的循环相当应用程序的时间
delay_s(4);
}
}