ReCclay
认证:VIP会员
所在专题目录 查看专题
蓝桥嵌入式之 按键控制LED闪烁
蓝桥嵌入式之 蜂鸣器
蓝桥嵌入式之 LCD使用
蓝桥嵌入式之 简单USART通信实现LCD显示
蓝桥嵌入式之 AT24C02
蓝桥嵌入式之 PWM波输出相关总结
作者动态 更多
【FPGA基础】基于 Pango Design Suite(PDS) 的FPGA开发流程
2021-05-14 11:22
【FPGA基础】基于Quartus Prime 17.1 的FPGA开发流程
2021-05-08 17:49
【AD快速入门】8051最小系统绘制
2021-04-22 10:03
蓝桥嵌入式之 ADC电压采集与显示
2021-04-14 11:48
蓝桥嵌入式之 实时时钟RTC
2021-04-13 14:50

蓝桥嵌入式之 AT24C02

工程可见Github<传送门>


关于24C02的基础知识,之前有过很详细的一篇文章,这里就不再赘述,直接上菜吧。

以下用到的位带区及位带别名区的相关知识可参考这里。

eeprom记录上电次数这样一个实例,来巩固下eeprom

一、主要代码

main.c

/*******************************************************************************
* 文件名:main.c
* 描  述:
* 作  者:CLAY
* 版本号:v1.0.0
* 日  期: 2019年1月25日
* 备  注:EEPROM记录开机次数,LCD显示开机次数
*         
*******************************************************************************
*/

#include "config.h"
#include "led.h"
#include "key.h"
#include "timer.h"
#include "beep.h"
#include "lcd.h"
#include "stdio.h"
#include "usart.h"
#include "i2c.h"
#include "eeprom.h"

int main(void)
{
	u8 cnt; //程序启动次数
	u8 chk; //启动次数校验字节
	u8 str1[25];
	
	u8 i;
	u8 str[25];
	u8 temp = 30;
	float AO = 3.845;
	
	STM3210B_LCD_Init();
	LCD_Clear(Blue);
	LEDInit();
	KeyInit();
	BeepInit();
	TIM2Init(2000, 72);//定时2ms
	USART2Init(9600);
	I2CInit();
	
	cnt = E2ReadByte(0x00);
	chk = E2ReadByte(0x01);
	if((cnt^chk) != 0xFF)//两个字节不是反码,归0重新计数
	{
		cnt = 0;
	}
	if(cnt < 250)
	{
		cnt ++;
	}
	LCD_ClearLine(Line8);
	sprintf((char*)str1," cnt = %d ",cnt);
	LCD_DisplayStringLine(Line8, str1);
	E2WriteByte(0x00, cnt);
	E2WriteByte(0x01, ~cnt);
	
	LCD_DisplayStringLine(Line1,(u8*) "qwertyuioplkjhgfdsazxcvb");
	sprintf((char*)str,"temp=%d   A0=%.1f  ",temp, AO);
	LCD_DisplayStringLine(Line2,str);
	
	while(1)
	{	
		KeyDriver();
		if(RxdOverFlag)
		{
			RxdOverFlag = 0;
			LCD_ClearLine(Line5);
			LCD_DisplayStringLine(Line5, RxdBuf);
			USART2_SendByte(RxdBuf);
			for(i=0; i<50; i++) RxdBuf[i] = 0;//清空串口接收缓冲区
			USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);//开启串口接收中断,处理下一帧数据
		} 
	}
}

void KeyAction(int code)
{
	if(code == 1)//按下B1,切换灯状态,蜂鸣器鸣叫0.1s
	{
		GPIOC->ODR ^= (1<<8);//PC8不断取反
		GPIOD->ODR |= (1<<2);//PD2置1,使能573锁存器
		GPIOD->ODR &= ~(1<<2);//PD2清0,关闭573锁存器
		Beep(100);
	}
	else if(code == 2)
	{
		Beep(-1);
	}
	else if(code == 3)
	{
		Beep(0);
	}
}



i2c.c

#include "i2c.h"

void I2CInit(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //使能GPIOB时钟       
    GPIO_SetBits(GPIOB, GPIO_Pin_6|GPIO_Pin_7);           //SCL和SDA初始输出高电平(先设置引脚电平可以避免IO初始化过程中可能产生的毛刺)
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7;  //选择SCL和SDA引脚
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;      //选择开漏输出
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz;     //输出速率10MHz
    GPIO_Init(GPIOB, &GPIO_InitStructure);
}

/* 产生总线起始信号 */
void I2CStart(void)
{
    I2C_SDA_OUT = 1; //首先确保SDA、SCL都是高电平
    I2C_SCL_OUT = 1;
    delay_us(5);
    I2C_SDA_OUT = 0; //先拉低SDA
    delay_us(5);
    I2C_SCL_OUT = 0; //再拉低SCL
}

/* 产生总线停止信号 */
void I2CStop(void)
{
    I2C_SCL_OUT = 0; //首先确保SDA、SCL都是低电平
    I2C_SDA_OUT = 0;
    delay_us(5);
    I2C_SCL_OUT = 1; //先拉高SCL
    delay_us(5);
    I2C_SDA_OUT = 1; //再拉高SDA
    delay_us(5);
}

/* I2C总线写操作,dat-待写入字节,返回值-从机应答位的值 */
u8 I2CWrite(u8 dat)
{
    int i;
    u8 ack;   //用于暂存应答位的值
    
    for (i=0; i<8; i++)  //循环将8bit数据输出到总线上
    {
        I2C_SDA_OUT = (dat&0x80) ? 1 : 0; //将最高位的值输出到SDA上
        delay_us(5);
        I2C_SCL_OUT = 1; //拉高SCL
        delay_us(5);
        I2C_SCL_OUT = 0; //再拉低SCL,完成一个位周期
        dat <<= 1;       //左移将次高位变为最高位,实现高位在先低位在后的发送顺序
    }
    I2C_SDA_OUT = 1;  //8位数据发送完后,主机释放SDA,以检测从机应答
    delay_us(5);
    I2C_SCL_OUT = 1;  //拉高SCL
    ack = I2C_SDA_IN; //读取此时的SDA值,即为从机的应答值
    delay_us(5);
    I2C_SCL_OUT = 0;  //再拉低SCL完成应答位,并保持住总线        
    delay_us(5);
    
    return (!ack); //应答值取反以符合通常的逻辑:
                   //0=不存在或忙或写入失败,1=存在且空闲或写入成功
}

/* I2C总线读取8位数据,返回值-读到的字节 */
u8 I2CRead(void)
{
    int i;
    u8 dat = 0; //数据接收变量赋初值0
    
    I2C_SDA_OUT = 1;    //首先确保主机释放SDA
    for (i=0; i<8; i++) //循环将总线上的8bit数据读入dat中
    {
        delay_us(5);
        I2C_SCL_OUT = 1;    //拉高SCL
        dat <<= 1;          //左移将己读到的位向高位移动,实现高位在先低位在后的接收顺序
        if(I2C_SDA_IN != 0) //读取SDA的值到dat最低位上
        {
            dat |= 0x01;    //SDA为1时设置dat最低位为1,SDA为0时无操作,即仍为初始值的0
        }
        delay_us(5);
        I2C_SCL_OUT = 0;    //再拉低SCL,以使从机发送出下一位
    }
    return dat;
}

/* I2C总线读操作,并发送非应答信号,返回值-读到的字节 */
u8 I2CReadNAK(void)
{
    u8 dat;
    
    dat = I2CRead();  //读取8位数据
    I2C_SDA_OUT = 1;  //8位数据读取完后,拉高SDA,发送非应答信号
    delay_us(5);
    I2C_SCL_OUT = 1;  //拉高SCL
    delay_us(5);
    I2C_SCL_OUT = 0;  //再拉低SCL完成非应答位,并保持住总线
    delay_us(5);
    
    return dat;
}

/* I2C总线读操作,并发送应答信号,返回值-读到的字节 */
u8 I2CReadACK(void)
{
    u8 dat;
    
    dat = I2CRead();  //读取8位数据
    I2C_SDA_OUT = 0;  //8位数据读取完后,拉低SDA,发送应答信号
    delay_us(5);
    I2C_SCL_OUT = 1;  //拉高SCL
    delay_us(5);
    I2C_SCL_OUT = 0;  //再拉低SCL完成应答位,并保持住总线
    delay_us(5);
    
    return dat;
}

i2c.h

#ifndef _I2C_H
#define _I2C_H

#include "config.h"

#define I2C_SCL_OUT   PB_OUT(6)
#define I2C_SDA_OUT   PB_OUT(7)
#define I2C_SDA_IN    PB_IN(7)

void I2CInit(void);
void I2CStart(void);
void I2CStop(void);
u8 I2CReadNAK(void);
u8 I2CReadACK(void);
u8 I2CWrite(u8 dat);


#endif

eeprom.c

#include "i2c.h"
#include "eeprom.h"

/* 读取EEPROM中的一个字节,addr-字节地址 */
u8 E2ReadByte(u8 addr)
{
    u8 dat;
    
    do { //用寻址操作查询当前是否可进行读写
        I2CStart();
        if (I2CWrite(0x50<<1)) //寻址器件,应答则跳出循环,否则继续查询
        {
            break;
        }
        I2CStop();
    } while(1);
    I2CWrite(addr);           //写入存储地址
    I2CStart();               //发送重复启动信号
    I2CWrite((0x50<<1)|0x01); //寻址器件,后续为读操作
    dat = I2CReadNAK();       //读取一个字节数据
    I2CStop();
    
    return dat;
}

/* 向EEPROM中写入一个字节,addr-字节地址 */
void E2WriteByte(u8 addr, u8 dat)
{
    do { //用寻址操作查询当前是否可进行读写
        I2CStart();
        if (I2CWrite(0x50<<1)) //寻址器件,应答则跳出循环,否则继续查询
        {
            break;
        }
        I2CStop();
    } while(1);
    I2CWrite(addr);  //写入存储地址
    I2CWrite(dat);   //写入一个字节数据
    I2CStop();
}

eeprom.h

#ifndef _EEPROM_H
#define _EEPROM_H

#include "config.h"

u8 E2ReadByte(u8 addr);
void E2WriteByte(u8 addr, u8 dat);

#endif

config.c

#include "config.h"

/* 1/4微秒延时函数(含函数调用及返回时间共计耗时约1/4微妙@72MHz主频) */
void delay_qus(void)
{
	__ASM ("nop");
	__ASM ("nop");
	__ASM ("nop");
	__ASM ("nop");
	__ASM ("nop");
	__ASM ("nop");
	__ASM ("nop");
	__ASM ("nop");
}

/* 微秒延时函数,us-延时时间 */
void delay_us(u16 us)
{
	while (us--)
	{
		delay_qus();
		delay_qus();
		delay_qus();
		delay_qus();
	}
}

/* 毫秒延时函数,ms-延时时间 */
void delay_ms(u16 ms)
{
    while (ms--)
    {
        delay_us(1000);
    }
}

config.h

#ifndef _CONFIG_H
#define _CONFIG_H

#include "stm32f10x.h"


//位带宏定义
#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 GPIOA_ODR_Addr    (GPIOA_BASE+12) //GPIOA输出数据寄存器地址0x4001080C
#define GPIOB_ODR_Addr    (GPIOB_BASE+12) //GPIOB输出数据寄存器地址0x40010C0C
#define GPIOC_ODR_Addr    (GPIOC_BASE+12) //GPIOC输出数据寄存器地址0x4001100C
#define GPIOD_ODR_Addr    (GPIOD_BASE+12) //GPIOD输出数据寄存器地址0x4001140C
#define GPIOE_ODR_Addr    (GPIOE_BASE+12) //GPIOE输出数据寄存器地址0x4001180C
#define GPIOF_ODR_Addr    (GPIOF_BASE+12) //GPIOF输出数据寄存器地址0x40011A0C
#define GPIOG_ODR_Addr    (GPIOG_BASE+12) //GPIOG输出数据寄存器地址0x40011E0C

#define GPIOA_IDR_Addr    (GPIOA_BASE+8) //GPIOA输入数据寄存器地址0x40010808
#define GPIOB_IDR_Addr    (GPIOB_BASE+8) //GPIOB输入数据寄存器地址0x40010C08
#define GPIOC_IDR_Addr    (GPIOC_BASE+8) //GPIOC输入数据寄存器地址0x40011008
#define GPIOD_IDR_Addr    (GPIOD_BASE+8) //GPIOD输入数据寄存器地址0x40011408
#define GPIOE_IDR_Addr    (GPIOE_BASE+8) //GPIOE输入数据寄存器地址0x40011808
#define GPIOF_IDR_Addr    (GPIOF_BASE+8) //GPIOF输入数据寄存器地址0x40011A08
#define GPIOG_IDR_Addr    (GPIOG_BASE+8) //GPIOG输入数据寄存器地址0x40011E08

//单个IO口位带操作
#define PA_OUT(n)   BIT_ADDR(GPIOA_ODR_Addr,n)  //PAx输出
#define PA_IN(n)    BIT_ADDR(GPIOA_IDR_Addr,n)  //PAx输入

#define PB_OUT(n)   BIT_ADDR(GPIOB_ODR_Addr,n)  //PBx输出
#define PB_IN(n)    BIT_ADDR(GPIOB_IDR_Addr,n)  //PBx输入

#define PC_OUT(n)   BIT_ADDR(GPIOC_ODR_Addr,n)  //PCx输出
#define PC_IN(n)    BIT_ADDR(GPIOC_IDR_Addr,n)  //PCx输入

#define PD_OUT(n)   BIT_ADDR(GPIOD_ODR_Addr,n)  //PDx输出
#define PD_IN(n)    BIT_ADDR(GPIOD_IDR_Addr,n)  //PDx输入

#define PE_OUT(n)   BIT_ADDR(GPIOE_ODR_Addr,n)  //PEx输出
#define PE_IN(n)    BIT_ADDR(GPIOE_IDR_Addr,n)  //PEx输入

#define PF_OUT(n)   BIT_ADDR(GPIOF_ODR_Addr,n)  //PFx输出
#define PF_IN(n)    BIT_ADDR(GPIOF_IDR_Addr,n)  //PFx输入

#define PG_OUT(n)   BIT_ADDR(GPIOG_ODR_Addr,n)  //PGx输出
#define PG_IN(n)    BIT_ADDR(GPIOG_IDR_Addr,n)  //PGx输入

void delay_us(u16 us);
void delay_ms(u16 ms);

#endif

二、程序讲解

特别注意程序校验的那一点的算法,原数与取反后的数进行异或等于0xFF,说明次数正确,否则就清零开机次数。

三、注意事项

1、为了方便I2C.c的管脚操作,在config.h中加入了位带操作

2、I2C中的延时采用的是config.c中的__ASM ("nop");延时方法

3、I2C中先设置引脚输出,再初始化。

    GPIO_SetBits(GPIOB, GPIO_Pin_6|GPIO_Pin_7);           //SCL和SDA初始输出高电平(先设置引脚电平可以避免IO初始化过程中可能产生的毛刺)
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7;  //选择SCL和SDA引脚
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;      //选择开漏输出
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz;     //输出速率10MHz
    GPIO_Init(GPIOB, &GPIO_InitStructure);

因为你想啊,上电后为浮空输入,然后又有上拉电阻存在,自然变成高电平。到了I2C初始化这一点,因为数据输出寄存器复位默认值为0,如果先初始化再设置引脚输出电平,就会先输出低(初始化为开漏输出),然后再设置引脚输出电平高,自然有了 高 -> 低 -> 高的状态,当然会产生毛刺,所以这里先设置引脚电平,再初始化。

其实不这样,事也不大。

声明:本内容为作者独立观点,不代表电子星球立场。未经允许不得转载。授权事宜与稿件投诉,请联系:editor@netbroad.com
觉得内容不错的朋友,别忘了一键三连哦!
赞 161
收藏 161
关注 431
成为作者 赚取收益
全部留言
0/200
  • dy-i2UfRuvP 2021-05-19 13:17
    什么时候更新
    回复
  • dy-iipPZRPN 2021-05-13 22:39
    学习了
    回复
  • dy-3EbVR6Ei 2021-05-13 22:27
    讲的真好!
    回复
  • dy-Xq2JxpfN 2021-05-13 22:13
    不亚于看了一篇高质量论文
    回复
  • dy-9g42stbW 2021-05-13 21:57
    不亚于看了一篇高质量论文
    回复
  • dy-mLj7kl5v 2021-05-13 20:33
    围观学习
    回复
  • dy-apcih3c1 2021-05-13 20:12
    围观学习
    回复
  • dy-k78ZHtFD 2021-05-13 20:02
    思路清晰,受益匪浅
    回复
  • dy-nAWdnPGS 2021-05-13 18:50
    思路清晰,受益匪浅
    回复
  • dy-jqGVYqsF 2021-05-13 16:07
    学习了
    回复
  • dy-VIQ9auhf 2021-05-13 14:54
    期待继续
    回复
  • dy-7mura2gg 2021-05-13 14:44
    比论文强一万倍
    回复
  • dy-YN3DYTeH 2021-05-13 14:34
    思路清晰,受益匪浅
    回复
  • dy-9hjGevyn 2021-05-13 13:33
    大开眼界,真是好文
    回复
  • dy-H1WY5jXH 2021-05-13 13:17
    围观学习
    回复
  • dy-wVQjSHHX 2021-05-13 12:59
    比论文强一万倍
    回复
  • dy-88VlYaFf 2021-05-12 15:35
    围观学习
    回复
  • dy-9QTV6UZW 2021-05-12 15:23
    不亚于看了一篇高质量论文
    回复
  • dy-WYS5BCmB 2021-05-12 15:10
    期待继续
    回复
  • dy-24clvK2q 2021-05-12 14:57
    期待继续
    回复