江科协51单片机16-1 DA数模转换实验(PWM 呼吸灯)

24-DAC 数模转换实验项目总结(PWM 呼吸灯)

工程路径:C:\Users\54200\Desktop\51MCU\24-DAC数模转换实验

目标芯片:STC89C52RC @ 普中(PRECHIN)51 开发板

开发环境:Keil5 C51 + STC-ISP 烧录

实验现象:下载程序后,DAC(PWM) 模块指示灯 DA1 呈呼吸灯效果,由暗渐亮再由亮渐暗


目录

  • [1. 项目概述](#1. 项目概述)
  • [2. 工程目录结构](#2. 工程目录结构)
  • [3. 硬件平台与引脚分配](#3. 硬件平台与引脚分配)
  • [4. 实验原理](#4. 实验原理)
  • [5. 程序架构与模块划分](#5. 程序架构与模块划分)
  • [6. 关键代码解析](#6. 关键代码解析)
  • [7. 程序流程图](#7. 程序流程图)
  • [8. 关键参数计算](#8. 关键参数计算)
  • [9. 实验现象与结论](#9. 实验现象与结论)
  • [10. 学习要点与反思](#10. 学习要点与反思)

1. 项目概述

本工程为普中 51 单片机教程(BV1Mb411e7re)第 24 讲配套实验。STC89C52RC 片内没有硬件 DAC 外设 ,实验采用定时器 0 中断产生软件 PWM ,PWM 波经模块上的 RC 低通滤波电路取出平均值,等效实现数模转换(DAC),驱动 DA1 指示灯。主循环以"占空比递增---递减"往复扫描的方式改变平均电压,形成呼吸灯效果。

核心特征:

项目 说明
实现方式 软件 PWM(定时器 0、工作方式 1)+ RC 滤波
PWM 周期 100 × 0.01ms = 1ms(频率 1kHz)
占空比范围 0 ~ 70(70 以上肉眼分辨不出亮度变化)
输出引脚 P2.1(sbit PWM = P2^1
主循环节奏 每步 delay_ms(1),约 1.4s 完成一次呼吸周期

2. 工程目录结构

复制代码
24-DAC数模转换实验/
├── App/
│   └── pwm/            # PWM 驱动模块(核心)
│       ├── pwm.c       # pwm_init / pwm_set_duty_cycle / T0 中断服务
│       └── pwm.h       # 引脚定义、外部变量与函数声明
├── Public/
│   ├── public.c        # 通用软件延时 delay_10us / delay_ms
│   └── public.h        # 数据类型重定义 u8/u16/u32、延时函数声明
├── User/
│   └── main.c          # 主函数:占空比三角波扫描
├── Obj/                # Keil 编译输出(.obj/.lst/.hex/.m51)
├── template.uvproj     # Keil 工程文件
├── template.uvopt      # Keil 工程选项
└── 呼吸灯一样闪烁.jpg   # 实验现象参考图

分层结构:User(应用层)→ App(驱动层)→ Public(基础层),层次清晰、便于复用。

3. 硬件平台与引脚分配

  • 芯片:STC89C52RC(内核兼容 8051,12MHz 晶振,机器周期 1μs)
  • 开发板:普中(PRECHIN)51 开发板,DAC(PWM) 模块
  • PWM 输出:P2.1 (在 pwm.h 中以 sbit PWM = P2^1 定义,可移植时只需改这一处)
  • 后级电路:RC 低通滤波 → 运放缓冲 → LED(DA1)
  • 定时器:定时器 0,工作方式 1(16 位定时)

4. 实验原理

4.1 PWM 模拟 DAC

数字引脚只能输出 0 或 VCC 两个电平,无法直接产生中间电压。但 PWM 波形的平均值等于 VCC × 占空比

  • 占空比 0% → 平均电压 0V → LED 熄灭
  • 占空比 70% → 平均电压约 0.7×VCC → LED 较亮
  • 占空比连续变化 → 平均电压线性变化 → 亮度平滑变化

当 PWM 频率(本实验 1kHz)远高于 LED/人眼响应频率时,经 RC 滤波后即可得到稳定的模拟电压,等效于一个分辨率约 100 级(tim_scale=100)的 DAC。

4.2 呼吸灯的三角波调制

主循环令占空比 duty 先从 0 递增至 70,再递减回 0,形成三角波扫描。平均电压随之先升后降,LED 亮度同步"渐亮→渐暗",每步延时 1ms 保证过渡流畅。占空比上限取 70 而非 100,是因为亮度与人眼感知非线性,超过 70 后继续增大肉眼几乎无法分辨。

5. 程序架构与模块划分

模块 文件 职责
应用层 User/main.c 方向变量 dir + 占空比 duty 三角波扫描
驱动层 App/pwm/pwm.c PWM 初始化、占空比设置、定时器 0 中断服务程序
基础层 Public/public.c 通用软件延时函数

模块间通过全局变量 gduty(占空比)解耦:主循环只调用 pwm_set_duty_cycle() 写入目标值,波形生成完全由中断异步完成------这是典型的"前台逻辑 + 后台波形"前后台系统结构。

6. 关键代码解析

6.1 PWM 初始化(pwm.c)

c 复制代码
void pwm_init(u8 tim_h, u8 tim_l, u16 tim_scale, u8 duty)
{
    gtim_h = tim_h;      // 初值存全局,供中断重装
    gtim_l = tim_l;
    gduty = duty;
    gtim_scale = tim_scale;

    TMOD |= 0x01;        // 定时器0,工作方式1(16位)
    TH0 = gtim_h;
    TL0 = gtim_l;
    ET0 = 1;             // 允许定时器0中断
    EA = 1;              // 开总中断
    TR0 = 1;             // 启动定时器
}

入口参数解耦:初值、周期倍数、占空比均由调用方传入,模块可复用于任意频率/分辨率的 PWM 场景。

6.2 中断服务程序(波形生成核心)

c 复制代码
void pwm(void) interrupt 1
{
    static u16 time = 0;
    TH0 = gtim_h;        // 重装初值,保证精确 0.01ms
    TL0 = gtim_l;

    time++;
    if (time >= gtim_scale)   // 周期计满(100次)
        time = 0;
    if (time <= gduty)        // 高电平窗口
        PWM = 1;
    else
        PWM = 0;
}

要点:①中断内重装初值,抵消中断响应延迟,保证节拍精度;②static u16 time 只在中断上下文存活,主程序不可见;③比较 time <= gduty 输出高电平,gduty 即高电平计数值。

6.3 主循环三角波(main.c)

c 复制代码
if (dir == 0) {          // 递增方向
    duty++;
    if (duty == 70) dir = 1;   // 到达上限,反向
} else {                 // 递减方向
    duty--;
    if (duty == 0) dir = 0;    // 回到下限,再反向
}
pwm_set_duty_cycle(duty);      // 更新占空比
delay_ms(1);                   // 控制呼吸速度

dir 作为方向标志位,实现 0→70→0 的往复扫描;duty 是"逻辑亮度",与 PWM 硬件输出解耦。

7. 程序流程图

7.1 总体流程(Mermaid,Obsidian 可直接渲染)

#mermaid-svg-hXD5HXf1e6UAO5uG{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-hXD5HXf1e6UAO5uG .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-hXD5HXf1e6UAO5uG .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-hXD5HXf1e6UAO5uG .error-icon{fill:#552222;}#mermaid-svg-hXD5HXf1e6UAO5uG .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-hXD5HXf1e6UAO5uG .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-hXD5HXf1e6UAO5uG .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-hXD5HXf1e6UAO5uG .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-hXD5HXf1e6UAO5uG .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-hXD5HXf1e6UAO5uG .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-hXD5HXf1e6UAO5uG .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-hXD5HXf1e6UAO5uG .marker{fill:#333333;stroke:#333333;}#mermaid-svg-hXD5HXf1e6UAO5uG .marker.cross{stroke:#333333;}#mermaid-svg-hXD5HXf1e6UAO5uG svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-hXD5HXf1e6UAO5uG p{margin:0;}#mermaid-svg-hXD5HXf1e6UAO5uG .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-hXD5HXf1e6UAO5uG .cluster-label text{fill:#333;}#mermaid-svg-hXD5HXf1e6UAO5uG .cluster-label span{color:#333;}#mermaid-svg-hXD5HXf1e6UAO5uG .cluster-label span p{background-color:transparent;}#mermaid-svg-hXD5HXf1e6UAO5uG .label text,#mermaid-svg-hXD5HXf1e6UAO5uG span{fill:#333;color:#333;}#mermaid-svg-hXD5HXf1e6UAO5uG .node rect,#mermaid-svg-hXD5HXf1e6UAO5uG .node circle,#mermaid-svg-hXD5HXf1e6UAO5uG .node ellipse,#mermaid-svg-hXD5HXf1e6UAO5uG .node polygon,#mermaid-svg-hXD5HXf1e6UAO5uG .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-hXD5HXf1e6UAO5uG .rough-node .label text,#mermaid-svg-hXD5HXf1e6UAO5uG .node .label text,#mermaid-svg-hXD5HXf1e6UAO5uG .image-shape .label,#mermaid-svg-hXD5HXf1e6UAO5uG .icon-shape .label{text-anchor:middle;}#mermaid-svg-hXD5HXf1e6UAO5uG .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-hXD5HXf1e6UAO5uG .rough-node .label,#mermaid-svg-hXD5HXf1e6UAO5uG .node .label,#mermaid-svg-hXD5HXf1e6UAO5uG .image-shape .label,#mermaid-svg-hXD5HXf1e6UAO5uG .icon-shape .label{text-align:center;}#mermaid-svg-hXD5HXf1e6UAO5uG .node.clickable{cursor:pointer;}#mermaid-svg-hXD5HXf1e6UAO5uG .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-hXD5HXf1e6UAO5uG .arrowheadPath{fill:#333333;}#mermaid-svg-hXD5HXf1e6UAO5uG .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-hXD5HXf1e6UAO5uG .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-hXD5HXf1e6UAO5uG .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-hXD5HXf1e6UAO5uG .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-hXD5HXf1e6UAO5uG .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-hXD5HXf1e6UAO5uG .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-hXD5HXf1e6UAO5uG .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-hXD5HXf1e6UAO5uG .cluster text{fill:#333;}#mermaid-svg-hXD5HXf1e6UAO5uG .cluster span{color:#333;}#mermaid-svg-hXD5HXf1e6UAO5uG div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-hXD5HXf1e6UAO5uG .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-hXD5HXf1e6UAO5uG rect.text{fill:none;stroke-width:0;}#mermaid-svg-hXD5HXf1e6UAO5uG .icon-shape,#mermaid-svg-hXD5HXf1e6UAO5uG .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-hXD5HXf1e6UAO5uG .icon-shape p,#mermaid-svg-hXD5HXf1e6UAO5uG .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-hXD5HXf1e6UAO5uG .icon-shape .label rect,#mermaid-svg-hXD5HXf1e6UAO5uG .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-hXD5HXf1e6UAO5uG .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-hXD5HXf1e6UAO5uG .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-hXD5HXf1e6UAO5uG :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 定时器0中断(每0.01ms异步触发)
是(递增)


否(递减)






共享 gduty
上电复位
pwm_init(0xFF,0xF6,100,0)

T0方式1, 0.01ms定时, 周期1ms
开启 ET0/EA/TR0

PWM 中断启动
dir == 0 ?
duty++
duty == 70 ?
dir = 1
pwm_set_duty_cycle

更新占空比
duty--
duty == 0 ?
dir = 0
delay_ms 1ms
重装初值 TH0/TL0
time++
time >= 100 ?
time = 0
time <= gduty ?
PWM = 1
PWM = 0
RC低通滤波 → DA1 呼吸灯

7.2 时序关系

复制代码
time:    0    1    2   ...  duty ...  99   0    1  ...
PWM:    ┌────高────┐└──────低──────────┘┌───高──...
         |<--------- 1ms 周期 ---------->|
占空比 duty/tim_scale = 平均电压/VCC

8. 关键参数计算

参数 计算 结果
机器周期 12MHz / 12 1μs
定时器初值 0xFFF6 = 65526 定时 (65536−65526)×1μs = 10μs(0.01ms)
PWM 周期 tim_scale=100 × 10μs 1ms
PWM 频率 1 / 1ms 1kHz
PWM 分辨率 tim_scale 100 级(1%)
呼吸半周期 70 步 × 1ms 70ms,全周期约 140ms

9. 实验现象与结论

  • 现象 :程序烧录后 DA1 由暗渐亮、再由亮渐暗,循环往复,无可见频闪。

    蓝色灯右上方红色为呼吸灯,虽然与右侧D2灯对应,但D2没有呼吸现象。

  • 结论一:软件 PWM + RC 滤波可以在无硬件 DAC 的 51 单片机上有效实现模拟量输出,平均电压与占空比呈线性关系(V_avg = VCC × duty/tim_scale)。

  • 结论二:前后台架构下,主循环仅维护"逻辑亮度",波形由中断保证,占空比切换不会造成波形毛刺。

  • 结论三:占空比上限取 70 体现了感知层面的工程折中------超出人眼分辨范围的精度没有实际意义。

10. 学习要点与反思

  1. 软件 DAC 本质:PWM 占空比 ↔ 平均电压的线性映射,是"数字→模拟"的最低成本方案(对比 STM32F103 的硬件 DAC/高级定时器 PWM,原理相通)。
  2. 中断重装初值 :方式 1 不会自动重装,中断内先 TH0/TL0 赋值再计数,是保证精度的关键细节。
  3. 参数化设计pwm_init(h, l, scale, duty) 把频率与分辨率交给调用者,模块可移植性强,值得在后续项目中沿用。
  4. volatile 意识gduty 在主循环与中断间共享,严格来说应加 volatile 防止编译器优化(本例单次赋值场景未出问题,但习惯要养成)。
  5. 与前序实验的衔接:与 STM32 线"6-4 PWM 驱动舵机""6-5 PWM 驱动直流电机"形成对照------同一 PWM 概念,51 用软件定时器模拟,STM32 用硬件定时器外设,可对比加深理解。

相关推荐
hongmai6668881 小时前
告别Wi-Fi,专注Mesh:ESP32-H2-MINI-1U-H2S模组规格与应用
单片机·嵌入式硬件·物联网·5g·h.265
Yiran_G1 小时前
BLE通信距离怎么判断?从发射功率、PHY到天线设计分析
单片机·嵌入式硬件
女神下凡1 小时前
芯参谋(11): 各种存储芯片电路设计
开发语言·嵌入式硬件
HUI-4741 小时前
EG5101|TSSOP‑24 数字算法电流模式同步降压控制器,高性能数字化电源方案
单片机·嵌入式硬件
亿道电子Emdoor2 小时前
【Altium】DWG文件修改图层颜色问题
嵌入式硬件·硬件工程
单片机仿真设计2 小时前
【proteus仿真】基于 STM32 的久坐提醒与坐姿矫正器设计(仿真图+程序)
stm32·单片机·嵌入式硬件·proteus·毕设
jianqiang.xue2 小时前
ESP-IDF保姆级入门11|FreeRTOS队列通信全解:生产者消费者+中断数据转发+多任务解耦,彻底搞定任务间交互
单片机·嵌入式硬件·物联网·架构·esp32
jianqiang.xue2 小时前
ESP-IDF保姆级入门09|ADC模拟量采集全解:电位器调压+光敏传感器+OLED显示,打通模拟→数字全链路
stm32·单片机·物联网·架构·esp32
张海森-1688203 小时前
变量的作用域和生命周期
单片机