代码实现at24c02主设备与从设备之间的I2C数据通信
(at24c02设备原理图资料在分享的stm32资料中)
代码的编辑思路是实现开始信号,结束信号,发送从设备地址和写信号,发送写片内地址,发送写的数据,把这些步骤都封装成代码函数,调用即可。
#include <stm32f4xx.h>
#include <iic.h>
#include <delay.h>
#include <stdio.h>
void ic_init(void)
{
//时钟初始化结构体
GPIO_InitTypeDef GPIO_InitStruct;
//1.开启GPIOB的时钟
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB,ENABLE);
//2.GPIO初始化 PB8 PB9引脚
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT; //输出模式
GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;//推挽输出
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;//高速
GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL;//无上下拉
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_8|GPIO_Pin_9;//引脚为9和10
GPIO_Init(GPIOB,&GPIO_InitStruct);
}
//设置串行数据线SDA传输方向(双向通信),传入引脚的模式
void set_sda_io(GPIOMode_TypeDef IO)
{
GPIO_InitTypeDef GPIO_InitStruct;
//2.GPIO初始化 PB9
GPIO_InitStruct.GPIO_Mode = IO;
GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;//推挽输出
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;//高速
GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL;//无上下拉
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9;//引脚为9号引脚
GPIO_Init(GPIOB,&GPIO_InitStruct);
}
//起始信号
void iic_start(void)
{
//设置串行数据总线SDA输出模式
set_sda_io(GPIO_Mode_OUT);
//两条总线处于空闲状态,默认为高电平
SCL = 1; //时钟总线高电平
SDA_OUT = 1; //数据总线低电平
delay_us(5);
//串行数据总线SDA拉低
SDA_OUT = 0;
delay_us(5);
//SDA拉低之后,再拉低时钟总线SCL,钳住总线
SCL = 0;
}
//停止信号
void iic_stop(void)
{
//串行数据总线SDA输出模式
set_sda_io(GPIO_Mode_OUT);
//串行数据总线和时钟总线为低电平
SCL = 0;
SDA_OUT = 0;
delay_us(5);
//串行时钟总线SCL拉高
SCL = 1;
delay_us(5);
//拉高串行数据总线SDA,此时SCL和SDA均为高,总线空闲
SDA_OUT = 1;
}
//等待从设备应答,返回0表示有效应答,返回1表示无效应答
u8 iic_wait_ack(void)
{
u8 ack = 0;
//串行数据总线SDA输入模式,接收应答
set_sda_io(GPIO_Mode_IN);
//拉高串行时钟总线SCL,允许从设备操作SDA
SCL = 1;
delay_us(5);
//读取串行数据总线SDA电平
if(SDA_IN){
ack = 1;//无效应答
iic_stop();//收到无效应答,立即停止通信
}
else{
ack = 0;
}
SCL = 0;
delay_us(5);
return ack;
}
//产生应答信号,传参ack=0有效应答,ack=1无效应答
void iic_ack(u8 ack)
{
//设置串行数据总线SDA输出模式
set_sda_io(GPIO_Mode_OUT);
//拉低时钟线,准备修改数据线电平
SCL = 0;
delay_us(5);
//设置串行数据总线SDA电平
if(ack){
SDA_OUT = 1;
}
else{
SDA_OUT = 0;//有效应答
}
delay_us(5);
//数据稳定后拉高时钟线让从设备接收ack
SCL = 1;
delay_us(5);
SCL = 0;
}
//发送1字节数据 --------- 先高后低
void iic_send_byte(u8 txd)
{
u8 i;
//设置数据串行总线SDA输出模式
set_sda_io(GPIO_Mode_OUT);
SCL = 0;
delay_us(5);
//从高到低一次发送每一位
//判断txd是高电平还是低电平,只需要&(与运算)上 1<<(7-i)就可以判断
//1<<(7-i) 实际上就是 1左移7位,不清楚的同学好好复习位运算
for(i=0;i<8;i++){
if(txd&1<<(7-i)){
SDA_OUT = 1;
}
else{
SDA_OUT = 0;
}
delay_us(5);
//拉高时钟线总线,让从设备读走这一位
SCL = 1;
delay_us(5);
//拉低时钟线,准备发送下一位
SCL = 0;
}
}
//接收1字节数据
u8 iic_recv_byte(void)
{
u8 rxd = 0,i;
//设置串行时钟总线SDA输入模式
set_sda_io(GPIO_Mode_IN);
SCL = 0;
//从高到低依次接收每一位
for(i=0;i<8;i++){
//等待对方设置好电平
delay_us(5);
//高电平期间读取1位数据
SCL = 1;
if(SDA_IN)
rxd |= 1<<(7-i);
//准备接收下一位
delay_us(5);
SCL = 0;
}
return rxd;
}
//at24c02写1字节数据
void at24c02_write_byte(u8 addr,u8 data)
{
u8 ack;
//起始信号
iic_start();
//发送从设备地址+写信号 0x50<<1 | 0 = 0xa0
//从设备地址(0x50)左移1位 或运算 0
iic_send_byte(0xa0);
//等待ACK
ack = iic_wait_ack();
if(ack){
printf("ack failed 1\r\n");
return ;
}
//发送写的片内地址
iic_send_byte(addr);
//等待ACK
ack = iic_wait_ack();
if(ack){
printf("ack failed 2\r\n");
return ;
}
//发送写的数据
iic_send_byte(data);
//等待ACK
ack = iic_wait_ack();
if(ack){
printf("ack failed 3\r\n");
return ;
}
//停止信号
iic_stop();
}
//at24c02写1页数据
void at24c02_write_page(u8 addr,u8 *data,u8 len)
{
u8 ack;
//起始信号
iic_start();
//发送从设备地址+写信号 0x50<<1 | 0 = 0xa0
iic_send_byte(0xa0);
//等待ACK
ack = iic_wait_ack();
if(ack){
printf("ack failed 1\r\n");
return ;
}
//发送写的片内地址
iic_send_byte(addr);
//等待ACK
ack = iic_wait_ack();
if(ack){
printf("ack failed 2\r\n");
return ;
}
//发送写的数据
while(len--){
iic_send_byte(*data++);
//等待ACK
ack = iic_wait_ack();
if(ack){
printf("ack failed 3\r\n");
return ;
}
}
//停止信号
iic_stop();
}
//at24c02读1字节
u8 at24c02_read_byte(u8 addr)
{
u8 ack,data;
//起始信号
iic_start();
//发送从设备地址+写信号 0x50<<1 | 0 = 0xa0
iic_send_byte(0xa0);
//等待ACK
ack = iic_wait_ack();
if(ack){
printf("ack failed 1\r\n");
return 0;
}
//发送读的片内地址
iic_send_byte(addr);
//等待ACK
ack = iic_wait_ack();
if(ack){
printf("ack failed 2\r\n");
return 0;
}
//起始信号
iic_start();
//发送从设备地址+读信号 0x50<<1 | 1 = 0xa1
iic_send_byte(0xa1);
//等待ACK
ack = iic_wait_ack();
if(ack){
printf("ack failed 3\r\n");
return 0;
}
//接收读的1字节数据
data = iic_recv_byte();
//无效应答
iic_ack(1);
//停止信号
iic_stop();
return data;
}
//at24c02连续读
void at24c02_seq_read(u8 addr,u8 *data,u8 len)
{
u8 ack;
//起始信号
iic_start();
//发送从设备地址+写信号 0x50<<1 | 0 = 0xa0
iic_send_byte(0xa0);
//等待ACK
ack = iic_wait_ack();
if(ack){
printf("ack failed 1\r\n");
return;
}
//发送读的片内地址
iic_send_byte(addr);
//等待ACK
ack = iic_wait_ack();
if(ack){
printf("ack failed 2\r\n");
return;
}
//起始信号
iic_start();
//发送从设备地址+读信号 0x50<<1 | 1 = 0xa1
iic_send_byte(0xa1);
//等待ACK
ack = iic_wait_ack();
if(ack){
printf("ack failed 3\r\n");
return;
}
//读取数据
while(len--){
//接收读的数据
*data++ = iic_recv_byte();
if(len>0){
//有效应答
iic_ack(0);
}
else{
iic_ack(1);
}
}
//停止信号
iic_stop();
}