微信搜索:ReCclay,即可免费阅读博主蓝桥系列所有文章,后台回复“代码”即可获取蓝桥所有备赛代码!关注博主公众号,还可拥有加入博主粉丝群实时沟通技术难题、免费下载CSDN资源等多项福利,还在等什么呢?快快扫码关注,学习才不会迷路。
这里再向各位同学推荐一个CSDN博主 ReRrain 的蓝桥备赛博客,博主秉持初学者思路,向你讲述自己蓝桥备赛的心路历程,娓娓道来蓝桥备赛经验,个人觉得非常不错,值得细细品读。
文章目录
-
-
- 一、主要代码
- 二、程序讲解
- 三、注意事项
-
?导读:《蓝桥杯嵌入式组》专栏文章是博主2019年参加蓝桥杯的嵌入式组比赛所做的学习笔记,在当年的比赛中,由于忙于准备考研及保研相关工作,博主仅仅参加了当年的省赛,并获得了省赛一等奖的成绩。成绩虽谈不上最好,但至少问心无愧。如今2021年回头再看该系列文章,仍然感触颇多。为了能更好地帮助到单片机初学者,今年特地抽出时间对当年的文章逻辑和结构进行重构,以达到初学者快速上手的目的。需要指出的是,由于本人水平有限,如有错误还请读者指出,非常感谢。那么,接下来让我们一起开始愉快的学习吧。
“一叶遮目,不见泰山”。不论何事,只有把握事情的总体趋势,才能做到心中有数。
关于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);//定时2msUSART2Init(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; //输出速率10MHzGPIO_Init(GPIOB, &GPIO_InitStructure);
}/* 产生总线起始信号 */
void I2CStart(void)
{I2C_SDA_OUT = 1; //首先确保SDA、SCL都是高电平I2C_SCL_OUT = 1;delay_us(5);I2C_SDA_OUT = 0; //先拉低SDAdelay_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; //先拉高SCLdelay_us(5);I2C_SDA_OUT = 1; //再拉高SDAdelay_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; //拉高SCLdelay_us(5);I2C_SCL_OUT = 0; //再拉低SCL,完成一个位周期dat <<= 1; //左移将次高位变为最高位,实现高位在先低位在后的发送顺序}I2C_SDA_OUT = 1; //8位数据发送完后,主机释放SDA,以检测从机应答delay_us(5);I2C_SCL_OUT = 1; //拉高SCLack = 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; //数据接收变量赋初值0I2C_SDA_OUT = 1; //首先确保主机释放SDAfor (i=0; i<8; i++) //循环将总线上的8bit数据读入dat中{delay_us(5);I2C_SCL_OUT = 1; //拉高SCLdat <<= 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; //拉高SCLdelay_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; //拉高SCLdelay_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; //输出速率10MHzGPIO_Init(GPIOB, &GPIO_InitStructure);
因为你想啊,上电后为浮空输入,然后又有上拉电阻存在,自然变成高电平。到了I2C初始化这一点,因为数据输出寄存器复位默认值为0,如果先初始化再设置引脚输出电平,就会先输出低(初始化为开漏输出),然后再设置引脚输出电平高,自然有了 高 -> 低 -> 高的状态,当然会产生毛刺,所以这里先设置引脚电平,再初始化。
其实不这样事也不大。。。
结语:以上就是本篇文章的全部内容啦,希望大家可以多多支持我的原创文章。如有错误,请及时指正,非常感谢。
微信搜索:ReCclay,即可免费阅读博主蓝桥系列所有文章,后台回复“代码”即可获取蓝桥所有备赛代码!关注博主公众号,还可拥有加入博主粉丝群实时沟通技术难题、免费下载CSDN资源等多项福利,还在等什么呢?快快扫码关注,学习才不会迷路。