STM32 独立按键控制 AT24C02 读写实验

STM32 独立按键控制 AT24C02 读写实验 1. 实验项目信息实验名称按键控制 AT24C02 读写存储实验主控芯片STM32F103 系列标准库 V3.5开发语言C 语言配套模块2 路独立按键、AT24C02 EEPROM、LED 指示灯、串口 1通信方式软件模拟 IIC 驱动 AT24C02系统时钟72MHz2. 硬件接线规范表格外设模块模块引脚STM32 引脚补充说明独立按键K1PB8上拉输入低电平触发按下独立按键K2PB9上拉输入低电平触发按下AT24C02SCLPB10IIC 时钟线AT24C02SDAPB11IIC 数据线LEDD0PA0上电默认点亮LEDD1PA1程序运行闪烁指示灯AT24C02VCC3.3V禁止 5V 供电AT24C02GNDGND所有模块共地3. 实验功能说明上电自动初始化全部外设滴答时钟、中断、LED、串口、按键、IIC、AT24C02循环检测 AT24C02 硬件检测失败持续串口打印报错硬件正常后进入主循环K1 按键功能存储数值自增上限 255写入 EEPROM 地址 0串口打印写入数据K2 按键功能读取 EEPROM 地址 0 存储值更新本地变量并串口打印读取数据D1 指示灯持续周期性闪烁作为程序运行状态标识串口 1 波特率 9600所有读写、设备检测信息通过串口输出至上位机。一、工程文件分层说明工程分为 6 大驱动模块分层解耦各司其职main.c程序入口、业务逻辑主循环、外设统一初始化、EEPROM 设备自检key.c / key.hPB8/PB9 独立按键底层驱动、按键消抖扫描led.c / led.hLED 引脚 GPIO 初始化、推挽输出配置iic.c / iic.h软件模拟 IIC 时序底层驱动SCLPB10、SDAPB1124cxx.c / 24cxx.hAT24CXX 系列 EEPROM 上层读写封装兼容 24C02/04/08/16/32 等公共底层文件system.h系统基础定义、SysTick.h微秒 / 毫秒延时、usart.h串口 1 驱动。二、各模块详细代码功能说明模块 1led.c LED 驱动模块核心函数LED_Init()开启 LED 对应 GPIO 端口 APB2 时钟配置 LED 引脚为推挽输出、50MHz 速率上电默认状态PA0D0输出低电平LED 常亮PA1D1输出高电平LED 熄灭配套头文件宏LED_PORTLED 对应 GPIO 端口LED_PORT_RCC端口时钟LED_PINPA0PA1 引脚组合led0/led1位带操作宏快速控制 LED 亮灭翻转。模块 2key.c 独立按键驱动模块1.KEY_Init()按键初始化开启 GPIOB 端口时钟PB8、PB9 配置为上拉输入模式硬件上拉默认高电平按键按下引脚拉低。2.KEY_Scan(u8 mode)按键扫描函数参数说明mode0单次触发模式长按不会重复触发mode1连续触发模式长按持续返回按键值 返回值0无按键按下KEY1_VALUEK1PB8按下KEY2_VALUEK2PB9按下 核心逻辑静态变量key标记按键释放状态实现单次触发锁存检测引脚低电平后延时 10ms 软件消抖双按键释放后自动复位扫描状态。模块 3iic.c 软件模拟 IIC 底层驱动全部时序基于 72MHz 系统时钟微秒延时实现为 AT24C02 提供总线基础。IIC_Init()初始化 PB10 (SCL)、PB11 (SDA) 为推挽输出总线空闲拉高SDA_OUT()/SDA_IN()动态切换 SDA 引脚输出 / 输入模式满足读写时序总线时序函数IIC_Start()IIC 起始信号SCL 高SDA 下降沿IIC_Stop()IIC 停止信号SCL 高SDA 上升沿应答控制IIC_Wait_Ack()等待从机应答超时 250 次判定通信失败IIC_Ack()主机发送应答IIC_NAck()主机发送非应答数据收发IIC_Send_Byte()主机发送单字节高位先发IIC_Read_Byte(u8 ack)读取一字节参数控制读完是否发送应答。模块 424cxx.c AT24CXX EEPROM 上层封装基于 IIC 底层封装兼容全系列 AT24CXX 存储芯片当前配置EE_TYPEAT24C02。AT24CXX_Init()初始化底层 IIC 总线内部静态工具函数AT24CXX_GetDevWriteAddr() 自动适配不同容量芯片24C16 及以下将地址高位填充至设备地址单字节读写实验核心接口AT24CXX_WriteByte(u16 addr,u8 data)指定地址写入 1 字节写入后延时 10ms 等待 EEPROM 存储AT24CXX_ReadByte(u16 addr)读取指定地址单字节数据扩展多字节接口预留AT24CXX_WriteMultiByte/AT24CXX_ReadMultiByte读写 16/32 位整型数据AT24CXX_WriteBuffer/AT24CXX_ReadBuffer批量缓冲区读写设备检测AT24CXX_CheckDevice() 向地址 255 写入校验魔数0x36回读校验返回 0 硬件正常1 故障。模块 5main.c 主程序业务逻辑1. 外设统一初始化Hardware_Init()集中初始化所有硬件资源统一管理便于增删外设SysTick_Init(72)配置 72MHz 系统时钟对应的毫秒 / 微秒延时NVIC_PriorityGroupConfig中断优先级分组 2LED、串口 1、按键、AT24C02 依次初始化。2. EEPROM 硬件自检EEPROM_DeviceCheck()阻塞式检测循环读取校验硬件异常持续打印提示直到通信正常才进入主循环。3. 主循环while(1)核心业务流程循环周期 10ms流程顺序调用按键扫描函数获取按键状态K1 按下数值自增最大 255调用写入函数存入地址 0串口打印K2 按下读取地址 0 数据覆盖本地存储变量串口打印读取值LED 计数器自增计数满 20 翻转 D1 状态实现周期闪烁延时 10ms 进入下一次循环。业务宏定义统一参数管理c运行#define EEPROM_STORE_ADDR 0 // 数据存储地址 #define DATA_MAX_VAL 255 // 存储数值上限0~255 #define LED_FLASH_CNT 20 // LED翻转计数阈值 #define SCAN_DELAY_MS 10 // 主循环扫描间隔三、代码重构优化点说明代码去冗余IIC、LED、KEY 模块重复 GPIO 初始化逻辑抽取工具函数消除重复结构体赋值AT24CXX 芯片地址判断逻辑封装静态函数读写接口复用减少重复代码。标准化规范统一 Doxygen 注释格式函数添加brief param retval说明全部魔法数字封装宏定义修改参数仅调整头部宏无需遍历全代码变量语义化命名k→storeData、i→ledCnt、t→bitIdx可读性提升。分层封装底层驱动IO/IIC与上层业务EEPROM 读写、按键逻辑完全分离内部工具函数添加static限制文件内访问提高代码封装性防止外部误调用。健壮性优化LED 计数器满阈值清零避免无符号变量溢出EEPROM 写入后强制 10ms 延时满足芯片页写入时序要求IIC 应答超时机制防止总线卡死。工程可维护性外设初始化收拢为独立函数新增硬件仅需在初始化函数添加代码AT24CXX 兼容多型号切换芯片仅修改头文件EE_TYPE宏串口日志统一输出调试、故障定位直观。四、运行现象说明上电后串口持续打印AT24C02检测不正常!直到硬件接线无误、模块供电正常硬件识别成功串口输出AT24C02检测正常!D1 指示灯开始闪烁每按一次 K1数值 1最大 255 不再增加串口输出写入的数据是XX按下 K2读取 EEPROM 保存的数值串口输出读取的数据是XX断电后存储数据不丢失重新上电按 K2 可读取上次保存数值EEPROM 掉电保存特性四、运行现象说明上电后串口持续打印AT24C02检测不正常!直到硬件接线无误、模块供电正常硬件识别成功串口输出AT24C02检测正常!D1 指示灯开始闪烁每按一次 K1数值 1最大 255 不再增加串口输出写入的数据是XX按下 K2读取 EEPROM 保存的数值串口输出读取的数据是XX断电后存储数据不丢失重新上电按 K2 可读取上次保存数值EEPROM 掉电保存特性五、代码1. main.c#include system.h #include SysTick.h #include led.h #include usart.h #include key.h #include 24cxx.h /* 业务常量宏统一管理参数 */ #define EEPROM_STORE_ADDR 0 // 数据存储地址 #define DATA_MAX_VAL 255 // 数据最大值0~255 #define LED_FLASH_CNT 20 // LED翻转计数阈值 #define SCAN_DELAY_MS 10 // 主循环扫描延时 /** * brief 硬件外设统一初始化 * retval 无 */ static void Hardware_Init(void) { SysTick_Init(72); NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); LED_Init(); USART1_Init(9600); KEY_Init(); AT24CXX_Init(); } /** * brief 检测AT24C02设备阻塞直到检测正常 * retval 无 */ static void EEPROM_DeviceCheck(void) { while (AT24CXX_CheckDevice() ! 0) { printf(AT24C02检测不正常!\r\n); delay_ms(500); } printf(AT24C02检测正常!\r\n); } int main(void) { u8 ledCnt 0; u8 keyVal 0; u8 storeData 0; // EEPROM存储变量 // 1. 全部外设初始化 Hardware_Init(); // 2. 循环检测EEPROM硬件 EEPROM_DeviceCheck(); while (1) { keyVal KEY_Scan(0); // K1按键数值自增并写入EEPROM if (keyVal KEY1_VALUE) { if (storeData DATA_MAX_VAL) { storeData; } // 调用重构后的单字节写入接口 AT24CXX_WriteByte(EEPROM_STORE_ADDR, storeData); printf(写入的数据是%d\r\n, storeData); } // K2按键从EEPROM读取数值并刷新本地变量 if (keyVal KEY2_VALUE) { storeData AT24CXX_ReadByte(EEPROM_STORE_ADDR); printf(读取的数据是%d\r\n, storeData); } // D1指示灯定时翻转闪烁 ledCnt; if (ledCnt LED_FLASH_CNT) { led1 !led1; ledCnt 0; // 计数器清零防止溢出 } delay_ms(SCAN_DELAY_MS); } }2. 24c02.c#include 24cxx.h #include iic.h #include SysTick.h /** * brief AT24CXX硬件初始化 * retval 无 */ void AT24CXX_Init(void) { IIC_Init(); } /** * brief 根据地址获取设备写地址适配不同容量芯片分页 * param addr: 存储地址 * retval 设备写地址 */ static u8 AT24CXX_GetDevWriteAddr(u16 addr) { u8 devAddr AT24CXX_DEV_ADDR_W; if (EE_TYPE AT24C16) { return devAddr; } else { // 24C16及以下高位地址填充到器件地址 devAddr ((addr / 256) 1); return devAddr; } } /** * brief AT24CXX指定地址读取单字节 * param readAddr: 读取地址 * retval 读取到的数据 */ u8 AT24CXX_ReadByte(u16 readAddr) { u8 tempData 0; u8 devWrAddr AT24CXX_GetDevWriteAddr(readAddr); IIC_Start(); IIC_Send_Byte(devWrAddr); IIC_Wait_Ack(); if (EE_TYPE AT24C16) { IIC_Send_Byte(readAddr 8); IIC_Wait_Ack(); } IIC_Send_Byte(readAddr 0xFF); IIC_Wait_Ack(); // 切换读模式 IIC_Start(); IIC_Send_Byte(AT24CXX_DEV_ADDR_R); IIC_Wait_Ack(); tempData IIC_Read_Byte(0); IIC_Stop(); return tempData; } /** * brief AT24CXX指定地址写入单字节 * param writeAddr: 写入地址 * param data: 待写入字节 * retval 无 */ void AT24CXX_WriteByte(u16 writeAddr, u8 data) { u8 devWrAddr AT24CXX_GetDevWriteAddr(writeAddr); IIC_Start(); IIC_Send_Byte(devWrAddr); IIC_Wait_Ack(); if (EE_TYPE AT24C16) { IIC_Send_Byte(writeAddr 8); IIC_Wait_Ack(); } IIC_Send_Byte(writeAddr 0xFF); IIC_Wait_Ack(); IIC_Send_Byte(data); IIC_Wait_Ack(); IIC_Stop(); // EEPROM页写入等待周期 delay_ms(10); } /** * brief 读取多字节组合为16/32位数据 * param readAddr: 起始地址 * param len: 字节长度 2/4 * retval 拼接后的数值 */ u32 AT24CXX_ReadMultiByte(u16 readAddr, u8 len) { u32 value 0; u8 i; for (i 0; i len; i) { value 8; value AT24CXX_ReadByte(readAddr len - i - 1); } return value; } /** * brief 将16/32位数据分多字节写入 * param writeAddr: 起始地址 * param data: 待写入数值 * param len: 字节长度 2/4 * retval 无 */ void AT24CXX_WriteMultiByte(u16 writeAddr, u32 data, u8 len) { u8 i; for (i 0; i len; i) { AT24CXX_WriteByte(writeAddr i, (data (8 * i)) 0xFF); } } /** * brief 批量读取数据到缓冲区 * param startAddr: 起始地址 * param buf: 接收缓冲区指针 * param len: 读取字节数量 * retval 无 */ void AT24CXX_ReadBuffer(u16 startAddr, u8 *buf, u16 len) { while (len--) { *buf AT24CXX_ReadByte(startAddr); } } /** * brief 将缓冲区批量写入EEPROM * param startAddr: 起始地址 * param buf: 待写入数据缓冲区 * param len: 写入字节数量 * retval 无 */ void AT24CXX_WriteBuffer(u16 startAddr, u8 *buf, u16 len) { while (len--) { AT24CXX_WriteByte(startAddr, *buf); } } /** * brief 检测AT24CXX设备是否正常 * retval 0:正常 1:故障 */ u8 AT24CXX_CheckDevice(void) { u8 val AT24CXX_ReadByte(255); const u8 testMagic 0x36; if (val testMagic) { return 0; } AT24CXX_WriteByte(255, testMagic); val AT24CXX_ReadByte(255); if (val testMagic) { return 0; } return 1; } #ifndef __24CXX_H #define __24CXX_H #include system.h // 设备容量定义 #define AT24C01 1 #define AT24C02 2 #define AT24C04 4 #define AT24C08 8 #define AT24C16 16 #define AT24C32 32 #define AT24C64 64 #define AT24C128 128 #define AT24C256 256 // 使用的EEPROM型号当前24C02 #define EE_TYPE AT24C02 // EEPROM 基础地址 #define AT24CXX_DEV_ADDR_W 0xA0 #define AT24CXX_DEV_ADDR_R 0xA1 // 初始化 void AT24CXX_Init(void); // 单字节读写 u8 AT24CXX_ReadByte(u16 addr); void AT24CXX_WriteByte(u16 addr, u8 data); // 多字节读写2/4字节16/32位数据 u32 AT24CXX_ReadMultiByte(u16 addr, u8 len); void AT24CXX_WriteMultiByte(u16 addr, u32 data, u8 len); // 批量缓冲区读写 void AT24CXX_ReadBuffer(u16 startAddr, u8 *buf, u16 len); void AT24CXX_WriteBuffer(u16 startAddr, u8 *buf, u16 len); // 设备检测 u8 AT24CXX_CheckDevice(void); #endif3.key.c#include key.h #include SysTick.h /******************************************************************************* * 函 数 名 : KEY_Init * 函数功能 : 按键初始化 * 输 入 : 无 * 输 出 : 无 *******************************************************************************/ void KEY_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; //定义结构体变量 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE); GPIO_InitStructure.GPIO_PinKEY1_Pin|KEY2_Pin; GPIO_InitStructure.GPIO_ModeGPIO_Mode_IPU; //上拉输入 GPIO_InitStructure.GPIO_SpeedGPIO_Speed_50MHz; GPIO_Init(KEY_Port,GPIO_InitStructure); } /******************************************************************************* * 函 数 名 : KEY_Scan * 函数功能 : 按键扫描检测 * 输 入 : mode0:单次按下按键 mode1连续按下按键 * 输 出 : 0未有按键按下 KEY1_VALUEKEY1键按下 KEY2_VALUEKEY2键按下 *******************************************************************************/ u8 KEY_Scan(u8 mode) { static u8 key1; if(key1(KEY10||KEY20)) //任意一个按键按下 { delay_ms(10); //消抖 key0; if(KEY10) { return KEY1_VALUE; } else if(KEY20) { return KEY2_VALUE; } } else if(KEY11KEY21) //无按键按下 { key1; } if(mode1) //连续按键按下 { key1; } return 0; } #ifndef _key_H #define _key_H #include system.h #define KEY1_Pin GPIO_Pin_8 #define KEY2_Pin GPIO_Pin_9 #define KEY_Port (GPIOB) //定义端口 //使用位操作定义 #define KEY1 PBin(8) #define KEY2 PBin(9) //定义各个按键值 #define KEY1_VALUE 1 #define KEY2_VALUE 2 void KEY_Init(void); u8 KEY_Scan(u8 mode); #endif4.I2c.c#include iic.h #include SysTick.h /* 时序延时宏统一修改IIC通信速度 */ #define IIC_DELAY_US_1 1 #define IIC_DELAY_US_2 2 #define IIC_DELAY_US_5 5 #define IIC_DELAY_US_6 6 #define IIC_WAIT_ACK_MAX 250 /** * brief 静态工具初始化指定引脚为推挽输出 * param port: GPIO端口 * param pin: 引脚号 * retval 无 */ static void IIC_SetPinOut(GPIO_TypeDef* port, uint16_t pin) { GPIO_InitTypeDef gpioCfg {0}; gpioCfg.GPIO_Pin pin; gpioCfg.GPIO_Mode GPIO_Mode_Out_PP; gpioCfg.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(port, gpioCfg); } /** * brief 静态工具初始化SDA为上拉输入 * retval 无 */ static void IIC_SetSdaIn(void) { GPIO_InitTypeDef gpioCfg {0}; gpioCfg.GPIO_Pin IIC_SDA_PIN; gpioCfg.GPIO_Mode GPIO_Mode_IPU; GPIO_Init(IIC_SDA_PORT, gpioCfg); } /** * brief IIC外设初始化SCL/SDA默认推挽输出总线拉高 * retval 无 */ void IIC_Init(void) { // 开启SCL、SDA端口时钟 RCC_APB2PeriphClockCmd(IIC_SCL_PORT_RCC | IIC_SDA_PORT_RCC, ENABLE); IIC_SetPinOut(IIC_SCL_PORT, IIC_SCL_PIN); IIC_SetPinOut(IIC_SDA_PORT, IIC_SDA_PIN); // 总线空闲拉高 IIC_SCL 1; IIC_SDA 1; } /** * brief SDA切换为推挽输出模式 * retval 无 */ void SDA_OUT(void) { IIC_SetPinOut(IIC_SDA_PORT, IIC_SDA_PIN); } /** * brief SDA切换为上拉输入模式 * retval 无 */ void SDA_IN(void) { IIC_SetSdaIn(); } /** * brief 产生IIC起始时序 * retval 无 */ void IIC_Start(void) { SDA_OUT(); IIC_SDA 1; IIC_SCL 1; delay_us(IIC_DELAY_US_5); // SCL高电平下SDA拉低起始信号 IIC_SDA 0; delay_us(IIC_DELAY_US_6); IIC_SCL 0; // 拉低时钟占用总线 } /** * brief 产生IIC停止时序 * retval 无 */ void IIC_Stop(void) { SDA_OUT(); IIC_SCL 0; IIC_SDA 0; IIC_SCL 1; delay_us(IIC_DELAY_US_6); // SCL高电平下SDA拉高停止信号 IIC_SDA 1; delay_us(IIC_DELAY_US_6); } /** * brief 等待从机应答ACK * retval 0收到应答成功1超时无应答失败 */ uint8_t IIC_Wait_Ack(void) { uint16_t waitCnt 0; IIC_SDA 1; delay_us(IIC_DELAY_US_1); SDA_IN(); IIC_SCL 1; delay_us(IIC_DELAY_US_1); // 等待SDA被拉低 while (READ_SDA) { waitCnt; if (waitCnt IIC_WAIT_ACK_MAX) { IIC_Stop(); return 1; } } IIC_SCL 0; return 0; } /** * brief 主机发送应答ACK低电平 * retval 无 */ void IIC_Ack(void) { IIC_SCL 0; SDA_OUT(); IIC_SDA 0; delay_us(IIC_DELAY_US_2); IIC_SCL 1; delay_us(IIC_DELAY_US_5); IIC_SCL 0; } /** * brief 主机发送非应答NACK高电平 * retval 无 */ void IIC_NAck(void) { IIC_SCL 0; SDA_OUT(); IIC_SDA 1; delay_us(IIC_DELAY_US_2); IIC_SCL 1; delay_us(IIC_DELAY_US_5); IIC_SCL 0; } /** * brief IIC发送单字节数据 * param sendData: 待发送8位字节 * retval 无 */ void IIC_Send_Byte(uint8_t sendData) { uint8_t bitIdx; SDA_OUT(); IIC_SCL 0; for (bitIdx 0; bitIdx 8; bitIdx) { // 高位先发 if (sendData 0x80) IIC_SDA 1; else IIC_SDA 0; sendData 1; delay_us(IIC_DELAY_US_2); IIC_SCL 1; delay_us(IIC_DELAY_US_2); IIC_SCL 0; delay_us(IIC_DELAY_US_2); } } /** * brief IIC读取单字节 * param sendAck: 1读完发送ACK0读完发送NACK * retval 读取到的一字节数据 */ uint8_t IIC_Read_Byte(uint8_t sendAck) { uint8_t bitIdx; uint8_t recvData 0; SDA_IN(); for (bitIdx 0; bitIdx 8; bitIdx) { IIC_SCL 0; delay_us(IIC_DELAY_US_2); IIC_SCL 1; recvData 1; if (READ_SDA) recvData 1; delay_us(IIC_DELAY_US_1); } // 根据参数决定应答类型 if (sendAck) IIC_Ack(); else IIC_NAck(); return recvData; } #ifndef __IIC_H #define __IIC_H #include system.h /* IIC引脚硬件定义可根据开发板修改 */ #define IIC_SCL_PORT GPIOB #define IIC_SCL_PIN GPIO_Pin_10 #define IIC_SCL_PORT_RCC RCC_APB2Periph_GPIOB #define IIC_SDA_PORT GPIOB #define IIC_SDA_PIN GPIO_Pin_11 #define IIC_SDA_PORT_RCC RCC_APB2Periph_GPIOB /* IO位带操作宏 */ #define IIC_SCL PBout(10) #define IIC_SDA PBout(11) #define READ_SDA PBin(11) /* 对外函数声明 */ void IIC_Init(void); void SDA_OUT(void); void SDA_IN(void); void IIC_Start(void); void IIC_Stop(void); uint8_t IIC_Wait_Ack(void); void IIC_Ack(void); void IIC_NAck(void); void IIC_Send_Byte(uint8_t sendData); uint8_t IIC_Read_Byte(uint8_t sendAck); #endif5. led.c#include led.h /******************************************************************************* * 函 数 名 : LED_Init * 函数功能 : LED初始化函数 * 输 入 : 无 * 输 出 : 无 *******************************************************************************/ void LED_Init() { GPIO_InitTypeDef GPIO_InitStructure;//定义结构体变量 RCC_APB2PeriphClockCmd(LED_PORT_RCC,ENABLE); GPIO_InitStructure.GPIO_PinLED_PIN; //选择你要设置的IO口 GPIO_InitStructure.GPIO_ModeGPIO_Mode_Out_PP; //设置推挽输出模式 GPIO_InitStructure.GPIO_SpeedGPIO_Speed_50MHz; //设置传输速率 GPIO_Init(LED_PORT,GPIO_InitStructure); /* 初始化GPIO */ GPIO_ResetBits(LED_PORT,GPIO_Pin_0); GPIO_SetBits(LED_PORT,GPIO_Pin_1); } #ifndef _key_H #define _key_H #include system.h #define KEY1_Pin GPIO_Pin_8 #define KEY2_Pin GPIO_Pin_9 #define KEY_Port (GPIOB) //定义端口 //使用位操作定义 #define KEY1 PBin(8) #define KEY2 PBin(9) //定义各个按键值 #define KEY1_VALUE 1 #define KEY2_VALUE 2 void KEY_Init(void); u8 KEY_Scan(u8 mode); #endif