STC15单片机实战:用IIC驱动ADC/DAC,复刻蓝桥杯省赛电压频率测量仪
STC15单片机实战IIC驱动ADC/DAC构建高精度测量仪在电子设计竞赛和实际工程项目中能够快速搭建一个功能完善的测量系统是工程师的核心能力之一。本文将带你从零开始基于STC15F2K60S2单片机通过IIC总线驱动ADC/DAC模块结合NE555定时器构建一个具备电压和频率测量功能的综合仪器。这个项目不仅复现了蓝桥杯省赛题目的技术要求更提供了完整的硬件连接方案和模块化代码实现适合希望提升实战能力的电子爱好者。1. 硬件系统架构设计一个完整的测量系统需要精心设计硬件架构。我们选择的STC15F2K60S2单片机具有丰富的外设资源包括多个定时器和GPIO非常适合此类应用。核心硬件组件STC15F2K60S2单片机主控芯片PCF8591模块集成ADC/DAC功能NE555定时器芯片频率信号生成四位共阳数码管数据显示LED指示灯组状态显示独立按键模块用户交互硬件连接示意图如下单片机引脚连接目标功能说明P2.0PCF8591 SCLIIC时钟线P2.1PCF8591 SDAIIC数据线P3.4NE555 OUT频率信号输入P1.0-P1.7数码管段选数字显示P3.0-P3.3独立按键用户输入控制P0.0-P0.7LED指示灯测量状态指示提示实际布线时IIC总线建议使用4.7kΩ上拉电阻确保信号稳定性。NE555电路需要配置合适的RC参数以获得可调频率范围。2. IIC总线驱动实现IIC总线是连接单片机与PCF8591模块的关键。STC15系列没有硬件IIC控制器需要软件模拟时序。2.1 IIC底层驱动代码以下是完整的IIC驱动实现包含起始信号、停止信号、应答检测等基本操作// IIC.h 头文件定义 #ifndef _IIC_H_ #define _IIC_H_ #include stc15f2k60s2.h #include intrins.h #define IIC_DELAY 5 void IIC_Init(void); void IIC_Start(void); void IIC_Stop(void); bit IIC_WaitAck(void); void IIC_SendByte(unsigned char dat); unsigned char IIC_RecByte(void); unsigned char ADC_Read(unsigned char channel); void DAC_Output(unsigned char value); #endif // IIC.c 实现文件 #include IIC.h sbit SDA P2^1; sbit SCL P2^0; void IIC_Delay(unsigned int t) { while(t--); } void IIC_Start(void) { SDA 1; IIC_Delay(IIC_DELAY); SCL 1; IIC_Delay(IIC_DELAY); SDA 0; IIC_Delay(IIC_DELAY); SCL 0; IIC_Delay(IIC_DELAY); } void IIC_Stop(void) { SDA 0; IIC_Delay(IIC_DELAY); SCL 1; IIC_Delay(IIC_DELAY); SDA 1; IIC_Delay(IIC_DELAY); } bit IIC_WaitAck(void) { bit ack; SDA 1; IIC_Delay(IIC_DELAY); SCL 1; IIC_Delay(IIC_DELAY); ack SDA; SCL 0; IIC_Delay(IIC_DELAY); return ack; } void IIC_SendByte(unsigned char dat) { unsigned char i; for(i0; i8; i) { SCL 0; SDA (dat 0x80) ? 1 : 0; IIC_Delay(IIC_DELAY); SCL 1; IIC_Delay(IIC_DELAY); dat 1; } SCL 0; } unsigned char IIC_RecByte(void) { unsigned char i, dat 0; SDA 1; for(i0; i8; i) { SCL 1; IIC_Delay(IIC_DELAY); dat 1; if(SDA) dat | 0x01; SCL 0; IIC_Delay(IIC_DELAY); } return dat; }2.2 ADC数据采集实现PCF8591的ADC功能用于电压测量支持4通道输入。以下是单通道电压读取的实现unsigned char ADC_Read(unsigned char channel) { unsigned char val; IIC_Start(); IIC_SendByte(0x90); // 器件地址写 IIC_WaitAck(); IIC_SendByte(0x40|channel);// 控制字模拟输出使能|通道选择 IIC_WaitAck(); IIC_Stop(); IIC_Start(); IIC_SendByte(0x91); // 器件地址读 IIC_WaitAck(); val IIC_RecByte(); IIC_Stop(); return val; }2.3 DAC输出实现PCF8591的DAC功能可用于输出基准电压或生成波形void DAC_Output(unsigned char value) { IIC_Start(); IIC_SendByte(0x90); // 器件地址写 IIC_WaitAck(); IIC_SendByte(0x40); // 控制字模拟输出使能 IIC_WaitAck(); IIC_SendByte(value); // DAC输出值 IIC_WaitAck(); IIC_Stop(); }3. 频率测量技术实现NE555构成的振荡电路产生的频率信号通过单片机定时器捕获实现精确测量。3.1 NE555电路配置典型的NE555无稳态多谐振荡器电路参数计算频率 f 1.44 / ((R1 2*R2) * C) 占空比 (R1 R2) / (R1 2*R2)建议元件值R1: 1kΩ电位器可调R2: 1kΩ固定电阻C: 0.1μF电容3.2 定时器测频实现STC15单片机使用两个定时器协同工作Timer0计数外部脉冲Timer1定时1ms用于计算频率。// 定时器初始化 void Timer_Init(void) { // Timer0配置为计数器模式计数NE555输出脉冲 TMOD 0xF0; // 清除Timer0控制位 TMOD | 0x05; // 设置为16位计数器模式 TH0 0; // 计数器初值清零 TL0 0; TR0 1; // 启动Timer0 // Timer1配置为1ms定时用于频率计算 AUXR | 0x40; // Timer1设置为1T模式 TMOD 0x0F; // 清除Timer1控制位 TMOD | 0x10; // 设置为16位定时器模式 TH1 0xD4; // 1ms定时初值(12MHz) TL1 0xCD; ET1 1; // 允许Timer1中断 TR1 1; // 启动Timer1 EA 1; // 全局中断使能 } // Timer1中断服务程序 void Timer1_ISR() interrupt 3 { static unsigned int count 0; TH1 0xD4; // 重装初值 TL1 0xCD; if(count 1000) { // 1秒时间到 count 0; TR0 0; // 暂停计数 frequency (TH0 8) | TL0; // 获取计数值 TH0 0; // 计数器清零 TL0 0; TR0 1; // 重新开始计数 } }4. 用户界面与系统集成完整的测量系统需要友好的用户界面包括数码管显示、LED状态指示和按键控制。4.1 数码管动态显示采用定时中断实现数码管动态扫描避免占用主循环资源// 数码管显示函数 void Display_Update() { static unsigned char pos 0; P2 (P2 0x1F) | 0xE0; // 位选锁存器使能 P0 0xFF; // 关闭所有位选 P2 0x1F; P2 (P2 0x1F) | 0xC0; // 段选锁存器使能 P0 1 pos; // 选择当前位 P2 0x1F; P2 (P2 0x1F) | 0xE0; P0 seg_table[display_buf[pos]]; // 输出段码 P2 0x1F; if(pos 8) pos 0; }4.2 状态机按键扫描采用状态机实现按键消抖和动作识别#define KEY_IDLE 0 #define KEY_DETECT 1 #define KEY_CONFIRM 2 #define KEY_RELEASE 3 unsigned char Key_Scan() { static unsigned char state KEY_IDLE; static unsigned char key_value 0; unsigned char current P3 0x0F; switch(state) { case KEY_IDLE: if(current ! 0x0F) { state KEY_DETECT; key_value current; } break; case KEY_DETECT: if(current key_value) { state KEY_CONFIRM; // 这里可以添加按键按下处理 } else { state KEY_IDLE; } break; case KEY_CONFIRM: if(current 0x0F) { state KEY_RELEASE; } break; case KEY_RELEASE: state KEY_IDLE; // 返回按键值0-3对应S4-S7 return (key_value ^ 0x0F); } return 0xFF; // 无按键按下 }4.3 主程序逻辑框架整合各模块功能的主程序结构void main() { System_Init(); // 系统初始化 Timer_Init(); // 定时器初始化 while(1) { unsigned char key Key_Scan(); if(key ! 0xFF) { switch(key) { case 0: // S4:切换测量模式 mode !mode; break; case 1: // S5:切换DAC输出模式 dac_mode !dac_mode; break; case 2: // S6:LED开关 led_en !led_en; break; case 3: // S7:数码管开关 display_en !display_en; break; } } if(adc_flag) { // ADC采样完成 adc_flag 0; voltage ADC_Read(0) * 5.0 / 255; // 转换为电压值 if(dac_mode) DAC_Output(adc_value); // DAC跟踪输出 } if(freq_flag) { // 频率测量完成 freq_flag 0; // 频率值已更新 } Update_Display(); // 更新显示内容 Update_LEDs(); // 更新LED状态 } }5. 系统优化与调试技巧在实际项目中系统稳定性和测量精度至关重要。以下是几个关键优化点5.1 ADC采样精度提升增加软件滤波算法采用滑动平均或中值滤波#define FILTER_LEN 8 unsigned char ADC_Filter(unsigned char channel) { static unsigned char buf[FILTER_LEN] {0}; static unsigned char index 0; unsigned int sum 0; unsigned char i; buf[index] ADC_Read(channel); if(index FILTER_LEN) index 0; for(i0; iFILTER_LEN; i) { sum buf[i]; } return sum / FILTER_LEN; }参考电压稳定为PCF8591提供精准的5V参考电压信号调理在ADC输入前增加RC低通滤波如1kΩ0.1μF5.2 频率测量误差补偿定时器时钟校准使用精确的外部晶振门控时间调整根据频率范围动态调整测量时间脉冲宽度验证排除干扰脉冲的影响5.3 功耗与EMC优化未使用的IO口设置为推挽输出低电平数码管扫描频率设置在60-100Hz之间避免可见闪烁高频信号线尽量短必要时增加终端匹配电阻电源回路增加去耦电容0.1μF10μF组合在项目开发过程中使用逻辑分析仪抓取IIC总线波形是调试通信问题的有效手段。特别是要检查SCL/SDA的上升/下降时间是否符合规范标准模式1000ns快速模式300ns。