工程可見Github<傳送門>
關(guān)于24C02的基礎(chǔ)知識,之前有過很詳細的一篇文章,這里就不再贅述,直接上菜吧。
以下用到的位帶區(qū)及位帶別名區(qū)的相關(guān)知識可參考這里。
用eeprom
記錄上電次數(shù)這樣一個實例,來鞏固下eeprom
。
一、主要代碼
main.c
/*******************************************************************************
* 文件名:main.c
* 描 述:
* 作 者:CLAY
* 版本號:v1.0.0
* 日 期: 2019年1月25日
* 備 注:EEPROM記錄開機次數(shù),LCD顯示開機次數(shù)
*
*******************************************************************************
*/
#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; //程序啟動次數(shù)
u8 chk; //啟動次數(shù)校驗字節(jié)
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)//兩個字節(jié)不是反碼,歸0重新計數(shù)
{
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;//清空串口接收緩沖區(qū)
USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);//開啟串口接收中斷,處理下一幀數(shù)據(jù)
}
}
}
void KeyAction(int code)
{
if(code == 1)//按下B1,切換燈狀態(tài),蜂鳴器鳴叫0.1s
{
GPIOC->ODR ^= (1<<8);//PC8不斷取反
GPIOD->ODR |= (1<<2);//PD2置1,使能573鎖存器
GPIOD->ODR &= ~(1<<2);//PD2清0,關(guān)閉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初始輸出高電平(先設(shè)置引腳電平可以避免IO初始化過程中可能產(chǎn)生的毛刺)
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);
}
/* 產(chǎn)生總線起始信號 */
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
}
/* 產(chǎn)生總線停止信號 */
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-待寫入字節(jié),返回值-從機應(yīng)答位的值 */
u8 I2CWrite(u8 dat)
{
int i;
u8 ack; //用于暫存應(yīng)答位的值
for (i=0; i<8; i++) //循環(huán)將8bit數(shù)據(jù)輸出到總線上
{
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; //左移將次高位變?yōu)樽罡呶?,實現(xiàn)高位在先低位在后的發(fā)送順序
}
I2C_SDA_OUT = 1; //8位數(shù)據(jù)發(fā)送完后,主機釋放SDA,以檢測從機應(yīng)答
delay_us(5);
I2C_SCL_OUT = 1; //拉高SCL
ack = I2C_SDA_IN; //讀取此時的SDA值,即為從機的應(yīng)答值
delay_us(5);
I2C_SCL_OUT = 0; //再拉低SCL完成應(yīng)答位,并保持住總線
delay_us(5);
return (!ack); //應(yīng)答值取反以符合通常的邏輯:
//0=不存在或忙或?qū)懭胧。?=存在且空閑或?qū)懭氤晒?}
/* I2C總線讀取8位數(shù)據(jù),返回值-讀到的字節(jié) */
u8 I2CRead(void)
{
int i;
u8 dat = 0; //數(shù)據(jù)接收變量賦初值0
I2C_SDA_OUT = 1; //首先確保主機釋放SDA
for (i=0; i<8; i++) //循環(huán)將總線上的8bit數(shù)據(jù)讀入dat中
{
delay_us(5);
I2C_SCL_OUT = 1; //拉高SCL
dat <<= 1; //左移將己讀到的位向高位移動,實現(xiàn)高位在先低位在后的接收順序
if(I2C_SDA_IN != 0) //讀取SDA的值到dat最低位上
{
dat |= 0x01; //SDA為1時設(shè)置dat最低位為1,SDA為0時無操作,即仍為初始值的0
}
delay_us(5);
I2C_SCL_OUT = 0; //再拉低SCL,以使從機發(fā)送出下一位
}
return dat;
}
/* I2C總線讀操作,并發(fā)送非應(yīng)答信號,返回值-讀到的字節(jié) */
u8 I2CReadNAK(void)
{
u8 dat;
dat = I2CRead(); //讀取8位數(shù)據(jù)
I2C_SDA_OUT = 1; //8位數(shù)據(jù)讀取完后,拉高SDA,發(fā)送非應(yīng)答信號
delay_us(5);
I2C_SCL_OUT = 1; //拉高SCL
delay_us(5);
I2C_SCL_OUT = 0; //再拉低SCL完成非應(yīng)答位,并保持住總線
delay_us(5);
return dat;
}
/* I2C總線讀操作,并發(fā)送應(yīng)答信號,返回值-讀到的字節(jié) */
u8 I2CReadACK(void)
{
u8 dat;
dat = I2CRead(); //讀取8位數(shù)據(jù)
I2C_SDA_OUT = 0; //8位數(shù)據(jù)讀取完后,拉低SDA,發(fā)送應(yīng)答信號
delay_us(5);
I2C_SCL_OUT = 1; //拉高SCL
delay_us(5);
I2C_SCL_OUT = 0; //再拉低SCL完成應(yīng)答位,并保持住總線
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中的一個字節(jié),addr-字節(jié)地址 */
u8 E2ReadByte(u8 addr)
{
u8 dat;
do { //用尋址操作查詢當(dāng)前是否可進行讀寫
I2CStart();
if (I2CWrite(0x50<<1)) //尋址器件,應(yīng)答則跳出循環(huán),否則繼續(xù)查詢
{
break;
}
I2CStop();
} while(1);
I2CWrite(addr); //寫入存儲地址
I2CStart(); //發(fā)送重復(fù)啟動信號
I2CWrite((0x50<<1)|0x01); //尋址器件,后續(xù)為讀操作
dat = I2CReadNAK(); //讀取一個字節(jié)數(shù)據(jù)
I2CStop();
return dat;
}
/* 向EEPROM中寫入一個字節(jié),addr-字節(jié)地址 */
void E2WriteByte(u8 addr, u8 dat)
{
do { //用尋址操作查詢當(dāng)前是否可進行讀寫
I2CStart();
if (I2CWrite(0x50<<1)) //尋址器件,應(yīng)答則跳出循環(huán),否則繼續(xù)查詢
{
break;
}
I2CStop();
} while(1);
I2CWrite(addr); //寫入存儲地址
I2CWrite(dat); //寫入一個字節(jié)數(shù)據(jù)
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微秒延時函數(shù)(含函數(shù)調(diào)用及返回時間共計耗時約1/4微妙@72MHz主頻) */
void delay_qus(void)
{
__ASM ("nop");
__ASM ("nop");
__ASM ("nop");
__ASM ("nop");
__ASM ("nop");
__ASM ("nop");
__ASM ("nop");
__ASM ("nop");
}
/* 微秒延時函數(shù),us-延時時間 */
void delay_us(u16 us)
{
while (us--)
{
delay_qus();
delay_qus();
delay_qus();
delay_qus();
}
}
/* 毫秒延時函數(shù),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輸出數(shù)據(jù)寄存器地址0x4001080C
#define GPIOB_ODR_Addr (GPIOB_BASE+12) //GPIOB輸出數(shù)據(jù)寄存器地址0x40010C0C
#define GPIOC_ODR_Addr (GPIOC_BASE+12) //GPIOC輸出數(shù)據(jù)寄存器地址0x4001100C
#define GPIOD_ODR_Addr (GPIOD_BASE+12) //GPIOD輸出數(shù)據(jù)寄存器地址0x4001140C
#define GPIOE_ODR_Addr (GPIOE_BASE+12) //GPIOE輸出數(shù)據(jù)寄存器地址0x4001180C
#define GPIOF_ODR_Addr (GPIOF_BASE+12) //GPIOF輸出數(shù)據(jù)寄存器地址0x40011A0C
#define GPIOG_ODR_Addr (GPIOG_BASE+12) //GPIOG輸出數(shù)據(jù)寄存器地址0x40011E0C
#define GPIOA_IDR_Addr (GPIOA_BASE+8) //GPIOA輸入數(shù)據(jù)寄存器地址0x40010808
#define GPIOB_IDR_Addr (GPIOB_BASE+8) //GPIOB輸入數(shù)據(jù)寄存器地址0x40010C08
#define GPIOC_IDR_Addr (GPIOC_BASE+8) //GPIOC輸入數(shù)據(jù)寄存器地址0x40011008
#define GPIOD_IDR_Addr (GPIOD_BASE+8) //GPIOD輸入數(shù)據(jù)寄存器地址0x40011408
#define GPIOE_IDR_Addr (GPIOE_BASE+8) //GPIOE輸入數(shù)據(jù)寄存器地址0x40011808
#define GPIOF_IDR_Addr (GPIOF_BASE+8) //GPIOF輸入數(shù)據(jù)寄存器地址0x40011A08
#define GPIOG_IDR_Addr (GPIOG_BASE+8) //GPIOG輸入數(shù)據(jù)寄存器地址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
二、程序講解
特別注意程序校驗的那一點的算法,原數(shù)與取反后的數(shù)進行異或等于0xFF,說明次數(shù)正確,否則就清零開機次數(shù)。
三、注意事項
1、為了方便I2C.c
的管腳操作,在config.h
中加入了位帶操作
2、I2C
中的延時采用的是config.c
中的__ASM ("nop");
延時方法
3、I2C中先設(shè)置引腳輸出,再初始化。
GPIO_SetBits(GPIOB, GPIO_Pin_6|GPIO_Pin_7); //SCL和SDA初始輸出高電平(先設(shè)置引腳電平可以避免IO初始化過程中可能產(chǎn)生的毛刺)
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初始化這一點,因為數(shù)據(jù)輸出寄存器復(fù)位默認值為0,如果先初始化再設(shè)置引腳輸出電平,就會先輸出低(初始化為開漏輸出),然后再設(shè)置引腳輸出電平高,自然有了 高 -> 低 -> 高的狀態(tài),當(dāng)然會產(chǎn)生毛刺,所以這里先設(shè)置引腳電平,再初始化。
其實不這樣,事也不大。