蓝桥杯嵌入式G4实战:用按键控制MCP4017电阻,把电压值存进AT24C02(附完整工程)
·
蓝桥杯嵌入式G4实战:按键控制MCP4017电阻与AT24C02存储系统开发指南
在嵌入式系统开发中,I²C总线因其简单的两线制设计和多设备支持能力,成为连接各类外设的首选方案。本文将深入探讨如何基于STM32G4系列微控制器,构建一个完整的交互式电阻控制系统,通过按键调节MCP4017数字电位器阻值,利用ADC采集电压数据,并将结果存储至AT24C02 EEPROM中。这个实战项目不仅涵盖了蓝桥杯嵌入式竞赛的核心考点,更是工业控制系统中参数配置与数据记录的典型应用场景。
1. 系统架构设计与硬件配置
1.1 硬件模块选型与连接
本系统采用STM32G431RB作为主控制器,这是蓝桥杯嵌入式竞赛官方指定的开发板核心芯片。关键外设包括:
- MCP4017:Microchip生产的7位数字电位器,127级可调,最大阻值100kΩ
- AT24C02:2Kbit(256×8)串行EEPROM,支持100kHz(1.8V)和400kHz(≥2.5V)两种速度
- ADC通道:PB14引脚配置为ADC输入,用于测量分压电路电压
硬件连接示意图:
| 设备 | SDA引脚 | SCL引脚 | 其他连接 |
|---|---|---|---|
| MCP4017 | PB7 | PB6 | VDD→3.3V, VSS→GND |
| AT24C02 | PB7 | PB6 | A0-A2→GND |
| 分压电路 | - | - | 中间点→PB14 |
1.2 I²C总线初始化关键代码
void I2C_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
__HAL_RCC_GPIOB_CLK_ENABLE();
/* PB6(SCL), PB7(SDA) 配置为开漏输出 */
GPIO_InitStruct.Pin = GPIO_PIN_6|GPIO_PIN_7;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
/* 初始状态置高 */
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_6|GPIO_PIN_7, GPIO_PIN_SET);
}
注意:虽然STM32G4内置硬件I²C控制器,但蓝桥杯官方库采用GPIO模拟实现,需严格遵循时序要求。
2. MCP4017数字电位器控制实现
2.1 器件特性与寄存器配置
MCP4017采用7位线性电阻网络,电阻值计算公式为: [ R_{wiper} = R_{max} \times \frac{D}{127} ] 其中D为写入的数值(0-127),Rmax为100kΩ。
通信时序特点:
- 写操作地址字节:0x5E
- 读操作地址字节:0x5F
- 单字节数据传输,无子地址需求
2.2 完整驱动代码实现
#define MCP4017_WRITE_ADDR 0x5E
#define MCP4017_READ_ADDR 0x5F
void MCP4017_Write(uint8_t value)
{
I2C_Start();
I2C_SendByte(MCP4017_WRITE_ADDR);
I2C_WaitAck();
I2C_SendByte(value & 0x7F); // 确保最高位为0
I2C_WaitAck();
I2C_Stop();
}
uint8_t MCP4017_Read(void)
{
uint8_t value;
I2C_Start();
I2C_SendByte(MCP4017_READ_ADDR);
I2C_WaitAck();
value = I2C_ReceiveByte();
I2C_SendNAck();
I2C_Stop();
return value;
}
2.3 电阻-电压转换计算
在10kΩ分压电阻的典型应用中,输出电压计算如下:
float CalculateVoltage(uint8_t digital_value)
{
float resistance = 787.4f * digital_value; // 单位:欧姆
return 3.3f * resistance / (resistance + 10000.0f);
}
3. AT24C02数据存储方案
3.1 EEPROM特性与地址规划
AT24C02存储空间组织:
- 256字节容量,分为32页,每页8字节
- 器件地址:0xA0(写)/0xA1(读)
- 写入周期:5ms(典型值)
建议的数据存储结构:
| 地址范围 | 用途 | 数据类型 |
|---|---|---|
| 0x00-0x01 | 电压值整数部分 | uint16_t |
| 0x02-0x05 | 电压值小数部分 | float |
| 0x06-0x07 | 电阻设置值 | uint8_t |
3.2 增强型存储函数实现
void EEPROM_WriteMulti(uint8_t addr, uint8_t *data, uint8_t len)
{
uint8_t i;
if(addr + len > 256) return; // 地址越界保护
I2C_Start();
I2C_SendByte(0xA0);
I2C_WaitAck();
I2C_SendByte(addr);
I2C_WaitAck();
for(i=0; i<len; i++) {
I2C_SendByte(data[i]);
I2C_WaitAck();
HAL_Delay(1); // 页写入间隔
}
I2C_Stop();
HAL_Delay(5); // 等待写入完成
}
void EEPROM_ReadMulti(uint8_t addr, uint8_t *buf, uint8_t len)
{
uint8_t i;
if(addr + len > 256) return;
I2C_Start();
I2C_SendByte(0xA0);
I2C_WaitAck();
I2C_SendByte(addr);
I2C_WaitAck();
I2C_Start();
I2C_SendByte(0xA1);
I2C_WaitAck();
for(i=0; i<len-1; i++) {
buf[i] = I2C_ReceiveByte();
I2C_SendAck();
}
buf[len-1] = I2C_ReceiveByte();
I2C_SendNAck();
I2C_Stop();
}
4. 多任务按键管理系统
4.1 按键功能分配与消抖处理
开发板按键配置方案:
| 按键 | 引脚 | 功能 |
|---|---|---|
| B1 | PB0 | 读取EEPROM存储值 |
| B2 | PB1 | 设置电阻为中间值(64) |
| B3 | PB2 | 电阻值递增(+10) |
| B4 | PB3 | 保存当前设置到EEPROM |
按键消抖状态机实现:
typedef struct {
uint8_t current_state;
uint16_t debounce_cnt;
uint8_t single_flag;
uint8_t long_flag;
} Key_TypeDef;
Key_TypeDef keys[4];
void Key_Scan(void)
{
static const uint16_t DEBOUNCE_TH = 20;
static const uint16_t LONG_PRESS_TH = 1000;
uint8_t i;
for(i=0; i<4; i++) {
uint8_t pin_state = HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_0<<i);
if(pin_state == GPIO_PIN_RESET) {
if(keys[i].debounce_cnt < 0xFFFF) keys[i].debounce_cnt++;
if(keys[i].debounce_cnt == DEBOUNCE_TH) {
keys[i].single_flag = 1;
}
else if(keys[i].debounce_cnt >= LONG_PRESS_TH) {
keys[i].long_flag = 1;
keys[i].debounce_cnt = LONG_PRESS_TH; // 防止溢出
}
}
else {
if(keys[i].debounce_cnt >= DEBOUNCE_TH &&
keys[i].debounce_cnt < LONG_PRESS_TH) {
// 短按已触发,不做处理
}
keys[i].debounce_cnt = 0;
}
}
}
4.2 主控制逻辑实现
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_ADC1_Init();
MX_TIM3_Init();
LCD_Init();
I2C_GPIO_Init();
uint8_t current_res = 0;
float current_voltage = 0.0f;
uint16_t adc_value = 0;
while(1) {
Key_Scan();
// 按键1:读取存储值
if(keys[0].single_flag) {
keys[0].single_flag = 0;
uint8_t stored_val = EEPROM_Read(0x06);
MCP4017_Write(stored_val);
current_res = stored_val;
}
// 按键2:设置中间值
if(keys[1].single_flag) {
keys[1].single_flag = 0;
MCP4017_Write(64);
current_res = 64;
}
// 按键3:电阻递增
if(keys[2].single_flag) {
keys[2].single_flag = 0;
if(current_res < 127) current_res++;
MCP4017_Write(current_res);
}
// 按键4:保存当前设置
if(keys[3].single_flag) {
keys[3].single_flag = 0;
EEPROM_Write(0x06, current_res);
HAL_ADC_Start(&hadc1);
adc_value = HAL_ADC_GetValue(&hadc1);
current_voltage = adc_value * 3.3f / 4095.0f;
uint16_t vol_int = (uint16_t)current_voltage;
uint16_t vol_frac = (uint16_t)((current_voltage - vol_int)*1000);
EEPROM_Write(0x00, vol_int >> 8);
EEPROM_Write(0x01, vol_int & 0xFF);
EEPROM_Write(0x02, vol_frac >> 8);
EEPROM_Write(0x03, vol_frac & 0xFF);
}
// LCD显示更新
char disp_buf[20];
sprintf(disp_buf, "Res: %3d/127", current_res);
LCD_DisplayStringLine(Line1, (uint8_t*)disp_buf);
float res_kohm = 0.7874f * current_res;
sprintf(disp_buf, "R: %.2f kΩ", res_kohm);
LCD_DisplayStringLine(Line2, (uint8_t*)disp_buf);
HAL_ADC_Start(&hadc1);
adc_value = HAL_ADC_GetValue(&hadc1);
current_voltage = adc_value * 3.3f / 4095.0f;
sprintf(disp_buf, "V: %.3f V", current_voltage);
LCD_DisplayStringLine(Line3, (uint8_t*)disp_buf);
HAL_Delay(50);
}
}
5. 系统调试与性能优化
5.1 常见问题排查指南
-
I²C通信失败
- 检查上拉电阻(通常4.7kΩ)
- 确认设备地址正确
- 用逻辑分析仪捕获时序波形
-
AT24C02写入异常
- 确保写入间隔≥5ms
- 页写入不超过8字节边界
- 验证ACK信号是否正常
-
ADC读数不稳定
- 添加0.1μF去耦电容
- 软件多次采样取平均
- 检查参考电压稳定性
5.2 关键参数测量结果
测试数据记录表:
| 设定值 | 理论电阻(kΩ) | 实测电阻(kΩ) | 理论电压(V) | 实测电压(V) |
|---|---|---|---|---|
| 0 | 0.00 | 0.02 | 0.000 | 0.002 |
| 32 | 25.20 | 25.18 | 0.820 | 0.818 |
| 64 | 50.39 | 50.35 | 1.380 | 1.378 |
| 96 | 75.59 | 75.52 | 1.800 | 1.795 |
| 127 | 100.00 | 99.91 | 2.231 | 2.225 |
5.3 低功耗优化策略
- 间歇工作模式
void Enter_LowPowerMode(void)
{
HAL_ADC_Stop(&hadc1);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_6|GPIO_PIN_7, GPIO_PIN_SET);
HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI);
SystemClock_Config(); // 唤醒后重新配置时钟
}
-
动态速率调整
- 非关键操作时降低I²C时钟频率
- ADC采样速率根据需求动态调整
-
智能唤醒机制
- 按键中断唤醒MCU
- 定时采集模式
在完成这个项目的过程中,最值得注意的经验是I²C总线的时序稳定性对系统可靠性影响极大。实际调试时,通过增加关键操作后的延时,并严格检查每个ACK信号,可以显著提高通信成功率。对于需要精确控制的应用,建议将电阻调节步进值存储在数组中,通过查表法实现非线性调节,这比简单的线性递增更能满足实际需求。
更多推荐


所有评论(0)