找回密码
 立即注册
查看: 13|回复: 8

AI8051U 学习感悟与笔记

[复制链接]
  • 打卡等级:初来乍到
  • 打卡总天数:2
  • 最近打卡:2025-09-14 12:21:06
已绑定手机

1

主题

9

回帖

45

积分

新手上路

积分
45
发表于 4 小时前 | 显示全部楼层 |阅读模式
本帖为学习感悟开山贴,看了视频之后一直没来得及总结,现在在本帖上传一下学习思路与过程,包括一些关键技巧
回复

使用道具 举报 送花

  • 打卡等级:初来乍到
  • 打卡总天数:2
  • 最近打卡:2025-09-14 12:21:06
已绑定手机

1

主题

9

回帖

45

积分

新手上路

积分
45
发表于 4 小时前 | 显示全部楼层

一、学习感悟

接触 AI8051U 单片机后,最直观的感受是它实现了从传统 51 单片机到 AI 化增强型单片机的显著跨越。相比 89C52、12C5A60S2 等旧款 51 单片机,以及上一代的 STC32G,AI8051U 无需依赖外接模块就能完成复杂功能,单芯片集成了多项实用硬件,同时还保持了出色的兼容性 ——PDIP40 封装可直接插入旧 51 开发板,既支持 8 位指令集适配原有程序,又能通过 32 位指令集提升运算速度,对拥有旧开发板的学习者十分友好。

在实际操作中,AI8051U 的功能落地效果突出:屏幕显示的刷屏率和大容量视频播放效果远超预期,汉字、英文、图片显示及旋转功能流畅;iPhone s 录放音只需外接喇叭即可实现,无需额外模块;PWM 与 DMA 的兼容功能能无干扰驱动 1000 多颗 WS2812 灯;硬件浮点乘除单元让频谱分析仪可实时捕捉频率(如响指的中心频点约 2.5k Hz),手写计算器也能精准完成 5+6×7.1=47、8+9×3=35 这类计算。这种烧录程序后能直接看到实际效果的体验,不仅快速巩固了知识点,更让人直观感受到它从 “传统 51” 向 “AI 化” 升级的价值,也深刻理解了重点学习这颗单片机的原因 —— 它的实用硬件功能远胜上一代,是衔接单片机入门与实战的关键器件。

二、学习笔记

1. AI8051U 核心功能与升级点

AI8051U 学名为 AI8051U,是一款 USB 型单片机,相比 STC32G 有多项强悍升级。在屏幕显示与视频播放方面,它优化了 QSPI Flash 芯片读写功能,让大容量视频播放有质的提升,同时支持 8080 八位并口,能实现界面刷新、汉字、英文、图片显示及旋转显示,帧率不低,适合做图形菜单和动态界面;iPhone s 数字录放音功能已做全,支持停止(1 键)、录音(2 键)、放音(3 键)、音量加(6 键)、音量减(7 键),需外接喇叭且频率需设为 36.864MHz,可用于语音留言等场景;PWM 与 DMA 兼容功能突破了 STC32G 仅能通过 SPI+DMA 实现的限制,可直接用 PWM+DMA 驱动,能无干扰控制 1000 多颗 WS2812 灯,主屏需设为 40MHz,适合做点阵屏时钟和创意灯具;频谱分析仪功能因内置硬件浮点乘除单元,运算速度快,可实现 FFT 快速刷新,需烧录 “频谱分析仪 256 点” 程序并选择 COM8(CDC 1)串口,打响指可捕捉到约 2.5k Hz 的中心频点,适用于频率检测;手写计算器基于坛友开源程序,需烧录 obj 文件夹下的 “touch FX” 文件,支持屏幕校准(依次点击小圆点)、手写输入数字或符号、清屏及改颜色,刷屏率高,适合便携式计算和简单图形界面;兼容性方面,它支持 32 位和 8 位指令集,能兼容 89C52、12C5A60S2 等旧 51 开发板,会用 8H8K64U 或 STC32G 的学习者可无缝衔接,旧开发板可直接复用。

2. 关键实验操作要点

手写计算器实验需先在 obj 文件夹下选择 “touch FX” 烧录文件,选择烧录端口后下载程序,屏幕会显示校准小圆点,依次校准成功后会提示校准参数,之后即可手写输入数字(如 “AI”“STC”)、清屏或改颜色。iPhone s 录放音实验要先找到 “数字录放音” 代码,将单片机频率改为 36.864MHz 后烧写代码,进入烧录界面完成下载,实验需外接外置喇叭,按说明书操作按键:1 键停止、2 键录音(如录制 “AI 8051 单片机”)、1 键停止录音,按 6 键可将音量调至最大(屏幕显示 “50” 为最大),再按 3 键即可放音。

PWM+DMA(WS2812 点阵屏)实验需参考论坛代码手工修改程序,将主屏改回 40MHz,先进入下载模式再烧写程序,硬件使用横向 32 颗、纵向 8 颗的 WS2812 点阵屏,可加装涂色亚克力面板提升显示清晰度,最终能显示未校准的简单时钟,且灯珠无干扰。频谱分析仪实验需烧录 “频谱分析仪 256 点” 程序,主屏设为 40MHz,进入下载模式完成下载后,选择单片机对应的 COM8(CDC 1)串口并打开,调亮屏幕后打响指,可实时观察到响指约 2.5k Hz 的中心频点。视频播放实验需使用 9341 型号屏幕,烧录 obj 文件夹下的 “视频集播放动画” 程序,主屏设为 40MHz,进入下载模式下载后关闭工具,按 P33 键和 power 键重新上电,识别 COM8 串口后打开 “串行 Flash 编译器”,选择对应 W25Q128 芯片,先读取 ID 确认正常,再擦除芯片,加载代码包下的所有动态图片文件并编程数据,最后按电源按钮即可正常播放视频。

三、学习思路与过程

1. 基础准备阶段

首先要明确 AI8051U 的核心升级价值,知道它并非替代旧 51 单片机,而是 “兼容旧板 + 增强功能”,重点关注 32 位指令集、硬件浮点单元、PWM+DMA 这三大升级点;接着整理学习资源,保存 STC 官网和论坛地址以备后续咨询,同时记录代码包路径(如录放音代码、频谱代码位置),避免后续找文件浪费时间;最后核对硬件,确认手中开发板是否兼容(PDIP40 封装可直接插旧 51 板),准备好外置喇叭、WS2812 点阵屏、9341 屏幕、涂色亚克力面板等必备配件。

2. 分步实操阶段

按照 “由易到难” 的顺序开展实验,每完成一个实验就记录操作步骤和实际效果。先从屏幕显示与手写计算器实验入手,掌握程序烧录流程和屏幕校准方法,熟悉端口选择和文件路径,观察刷屏率是否正常;再进行 iPhone s 录放音实验,重点掌握频率设置(36.864MHz)和按键操作,确保外接喇叭后能正常录音、放音;之后做 PWM+DMA 点阵屏实验,对比 STC32G 的差异,观察点阵屏是否无闪烁,感受亚克力面板对显示清晰度的提升;接着进行频谱分析仪实验,确认串口选择正确,观察打响指时的频点捕捉效果,理解硬件浮点单元的作用;最后开展视频播放实验,掌握 Flash 编程器的使用流程(读取 ID→擦除芯片→加载图片→编程数据),重启后观察视频播放效果。

3. 总结与交流阶段

实验全部完成后,梳理各模块的操作步骤和核心参数(如不同实验的频率设置、文件路径),明确每个功能的应用场景;参考 “手工修改论坛代码” 的思路,尝试理解代码逻辑;若实验中遇到问题(如录放音无声、频谱无数据),先回顾操作步骤排查,仍未解决则去 STC 论坛提问,附上详细实验流程以便快速获得解答;若有 89C52 或 12C5A60S2 旧开发板,可将 AI8051U(PDIP40 封装)直接插入,先用 8 位指令集运行旧程序确认兼容性,再尝试 32 位指令集程序,直观感受两种指令集的运算速度差异。

四、关键学习技巧

烧录程序前务必做好 “三核对”,即核对频率是否匹配(录放音需 36.864MHz,其他实验为 40MHz)、烧录文件路径是否正确(如手写计算器需选 obj→touch FX)、串口是否为单片机当前使用的端口(如频谱实验选 COM8),避免因参数错误导致实验失败。进行硬件操作时,尤其是录放音实验的按键功能、点阵屏引脚定义,必须对照实验说明书,不可凭经验接线,防止损坏引脚。

调试过程中注重 “即时反馈”,屏幕类实验(如手写计算器、视频播放)若出现无显示或刷屏慢,先检查 8080 八位并口是否接对;声音类实验(如录放音)若无声,先按 6 键调至最大音量(屏幕显示 “50”),再检查喇叭接线,最后确认频率设置。遇到问题时充分利用资源,先查阅 STC 官网或论坛的同类问题解答,提问时清晰说明实验步骤和当前问题,方便他人快速提供帮助。若有旧 51 开发板,可直接插入 PDIP40 封装的 AI8051U,通过 “8 位指令集跑旧程序→32 位指令集跑新程序” 的方式,快速衔接原有知识,提升学习效率。

回复

使用道具 举报 送花

  • 打卡等级:初来乍到
  • 打卡总天数:2
  • 最近打卡:2025-09-14 12:21:06
已绑定手机

1

主题

9

回帖

45

积分

新手上路

积分
45
发表于 4 小时前 | 显示全部楼层

首个程序下载:流水灯实验

“能成功下载并运行流水灯程序” 是单片机入门的标志,整个过程需严格按步骤操作,确保软硬通联:

  1. 准备工作:打开桌面的 ISP 软件,首先在芯片选择栏找到 “AI8051U 系列 34K64” 并选中,确认与实验箱主芯片匹配;将实验箱通过 USB Type-A 线连接电脑,此时板子电源灯亮起,表明供电正常。
  2. 进入下载模式:这是关键步骤:按住实验箱左下角的 P32 按钮不松开,再按下电源按键(按一下后松开),此时 ISP 软件会扫描到 “HID USB” 设备,最后松开 P32 按钮,完成下载模式进入。
  3. 选择程序文件与配置主频:在 ISP 软件中点击 “打开程序文件”,找到之前下载的代码包,选择 “P0 口跑马灯” 实验的 C 语言程序;主频配置需与程序一致 —— 打开代码包中的 “main.c” 文件,头部会定义 “Fosc”( 24MHz),在 ISP 软件 “运行用户程序时的 IRC 频率” 栏选择 24MHz,避免因频率不匹配导致程序异常。
  4. 下载与验证:点击 ISP 软件的 “下载编程”,待界面提示 “操作成功” 后,观察实验箱 ——8 路流水灯从左到右依次点亮,形成 “跑马灯” 效果,表明程序已成功运行。

学习小结

第二节课的核心是 “认识硬件、搭建软件环境、完成入门验证”:实验箱硬件布局与各模块功能是后续所有实验的基础,需明确各接口、元器件的用途;软件安装需注意版本兼容与路径匹配,尤其是头文件和中断插件的配置,直接影响后续程序编写;流水灯实验虽简单,却是验证软硬通联的关键,其操作步骤(如进入下载模式的按键顺序、主频匹配)也是后续复杂实验的通用基础。课后建议结合实验箱说明书与代码包,尝试自主重复流水灯实验,熟悉软件操作与硬件交互,为后续录放音、时钟、频谱分析等实验做好准备。

1. 实验箱正面功能布局

  • 下载与供电:USB Type-A/C 接口、USB Link ED 接口(官方调试工具);
  • 基础显示与控制:8 路流水灯、8 位数码管(2 个 4 位拼接)、TFT 彩屏接口(插并口屏,上节课动画显示用);
  • 关键操作:LQFP48 封装的 AI8051U 主芯片、P32 键 + 电源键(配合进入下载模式,必记);
  • 其他核心模块:TF 卡插座、立体声放音 / 话筒录音接口、掉电检测模块、矩阵 / ADC 键盘、QSPI Flash 芯片、RTC 电池接口(接电池保时钟)。

2. 实验箱背面关键元器件

核心元器件及功能:32.768K 无源晶振(RTC 时钟基准)、24C02 存储芯片(存重要数据,单片机损坏数据不丢)、DS18B20 温度传感器、SP3485 芯片(485 通信)、AI8H2K12U 芯片( USB 转双串口)

回复

使用道具 举报 送花

  • 打卡等级:初来乍到
  • 打卡总天数:2
  • 最近打卡:2025-09-14 12:21:06
已绑定手机

1

主题

9

回帖

45

积分

新手上路

积分
45
发表于 4 小时前 | 显示全部楼层

新建工程 关键步骤与必要配置

1. 3 步创建工程

  1. 建空工程:打开开发软件忽略弹窗,点击菜单栏 Project→New,建议新建专属文件夹选择路径,项目名设为 Demo 保存后,选单片机型号 AI 8051U 32bit STC 或 AI 品牌均可 同属一家。
  2. 加源文件:点击小白纸图标新建文件,保存为 main.c 放 Demo 文件夹 main 是工程入口 必用此名,双击工程空白处选中 main.c 点击 Add 添加。
  3. 2 项核心配置 避坑关键
    • CPU 与存储:CPU mode 选 source 的 251 勾选 4bit 适配中断结构;存储模式下拉选 Xsmall 后续设 large 64K 代码≤64K 时够用。
    • 输出设置:切到 Output 选项卡选 HEX80,勾选生成 HEX 文件 不勾的话代码编好也没法下载到单片机。

添加头文件

  1. 获取头文件:打开 STC ISP 软件切到头文件选项卡,选 AI8051U 系列 32bit→C 语言格式即.h 格式,保存到 Demo 文件夹 文件名设为 AI8051U.h .h 是头文件后缀 必对应型号。
  2. 引用方式:代码首行写 #include "AI8051U.h" 用双引号优先读项目文件夹里的文件 避免传给别人时缺文件报错。

点亮 LED 代码 下载 问题排查

1. 核心代码

#include "AI8051U.h"  // 引用头文件 获取硬件定义
void main(void){
    // 配置P0 P4口为准双向口 既能输入也能输出 控制LED必用
    P0M0 = 0X00;  
    P0M1 = 0X00;  
    P4M0 = 0X00;  
    P4M1 = 0X00;  

    P40 = 0;  // P4.0引脚输出0伏 导通三极管开关
    P00 = 0;  // P0.0引脚输出0伏 点亮LED00

    while(1);  // 死循环 保持引脚状态 LED持续亮
}

2. 下载步骤

  1. 进下载模式:按开发板 P32 按钮再按 off 按键 松开后软件会识别到下载端口。
  2. 选文件:点击打开程序文件进 Demo 的 OBJ 文件夹,选最新日期的 HEX 文件 核对时间 别下错旧文件。
  3. 点击下载 提示成功即完成。

3. 常见问题

  • 灯不亮:检查是否漏配 P4 口模式 需和 P0 口一样设 0X00;或未正确进下载模式 重新按 P32+off。

关键原理

  1. LED 点亮逻辑:LED00 需 3.3V 电压差,单片机通过 P40 输出 0 伏导通三极管开关 + P00 输出 0 伏,形成电流回路 灯就亮。
  2. IO 口模式:P0M0/P0M1 组合控制模式,00 对应准双向口 控制 LED 按键常用 兼顾输入输出。

总结与小任务

  1. 重点:工程要配 HEX 输出,头文件放项目文件夹,LED 控制核心是准双向口 + 引脚 0 伏。
  2. 课后任务:改代码加 P01=0; 尝试点亮 P0.1 对应的 LED01 记得 P0 口已配置好模式。
回复

使用道具 举报 送花

  • 打卡等级:初来乍到
  • 打卡总天数:2
  • 最近打卡:2025-09-14 12:21:06
已绑定手机

1

主题

9

回帖

45

积分

新手上路

积分
45
发表于 3 小时前 | 显示全部楼层

第四节课

一、核心目标

实现 8051U 单片机的 USB 不停电下载功能,无需手动按 P32 按钮和断电;第一次需手动下载程序,后续代码编译后可自动完成下载,提升开发效率。

二、前期准备 下载并筛选 USB 库文件

1. 下载库文件

  1. 访问 STC 官网 ;
  2. 在 “软件工具 - 库函数” 栏目中,找到 “USB 库文件”,下载到 “第四章课程文件夹” 中。

2. 解压与筛选文件

  1. 将下载的库文件解压到当前文件夹;
  2. 筛选文件:库文件解压后含多个子文件夹,先删除后缀为汇编的文件(本课程用 C 语言 无需汇编文件);
  3. 选择模式与位数:优先选 “CDC 模式”(兼容性更好),再从 CDC 文件夹中找 “32 位相关文件”(之前课程用 32 位头文件 需匹配 32 位库),保留.lib 格式库文件和对应的.h 格式头文件。

三、代码移植 基于上节课点灯工程

1. 复制基础工程

  1. 将上节课的 “LED 点亮工程” 复制到新建的 “示例代码” 文件夹中;
  2. 打开工程文件 点击编译按钮 确保工程无错误无警告(基础工程正常才能移植新功能)。

2. 添加库文件与头文件

  1. 复制文件:从解压的 USB 库文件中,找到 32 位的.lib 文件和对应的.h 文件,复制到 “示例代码” 文件夹(与 main.c 同路径 避免文件缺失);
  2. 添加到工程:双击工程空白处,选中复制过来的.lib 文件,点击 “Add” 将其添加到工程中(与添加 main.c 步骤一致)。

3. 引用头文件

在 main.c 文件首行,添加 USB 库的头文件引用 代码格式为 #include "USB库对应的.h文件名"(直接复制.h 文件名粘贴 避免拼写错误)。

4. 编写关键代码 实现不停电功能

在 main 函数中,按顺序添加以下代码 同时保留上节课的 LED 点亮代码:

#include "AI8051U.h"
#include "USB库对应的.h文件名"  // 引用USB头文件

void main(void){
    // 1. 使能访问XFR区域 USB寄存器在XFR中
    P_SW2 |= 0X80;  // 将P_SW2的B7位置1 允许访问XFR
  
    // 2. 打开总中断和USB中断
    EA = 1;         // 打开总中断 所有中断需总中断使能
    IE2 |= 0X80;    // 打开USB中断 允许USB中断响应
  
    // 3. 初始化USB功能
    USB_Init();     // 调用USB初始化函数 库文件中自带
  
    // 4. 等待USB完成配置
    while(USB_GetState() != USB_STATE_CONFIGURED);
  
    // 5. 保留上节课的LED控制代码 配置IO口并点亮LED
    P0M0 = P0M1 = P4M0 = P4M1 = 0X00;  // IO口设为准双向口
    P00 = 0;  // 点亮第一颗LED
  
    // 6. USB数据处理 实现不停电下载核心逻辑
    while(1){
        if(USB_ReceiveDataReady()){  // 检测是否接收到数据
            uint8_t buf[64];
            uint16_t len = USB_ReceiveData(buf, 64);  // 接收数据
            USB_SendData(buf, len);  // 回发数据 同时触发下载检测
            // 接收到下载命令后 自动进入HID write模式 无需手动操作
        }
    }
}

四、编译排错与测试

1. 编译排错 处理 L57 警告

  1. 编译工程时 若出现 “L57 警告”(提示 “未调用部分函数”),点击软件中的 “魔术棒” 图标;
  2. 在弹出的设置界面中,找到 “警告控制” 选项,输入 “57” 屏蔽该警告;
  3. 重新编译 。

2. 测试步骤

  1. 第一次手动下载:按开发板 P32 按钮 再按 off 按钮 进入下载模式;打开下载软件 选择 “示例代码 - OBJ 文件夹” 中最新的 HEX 文件 点击下载;
  2. 后续自动测试:修改 LED 控制代码(如将 P00=0 改为 P01=0),点击编译按钮 编译完成后 下载软件会自动识别端口并完成下载;
  3. 验证效果:观察开发板 LED 变化 确认代码修改后自动下载生效 无需再按 P32 按钮。

五、关键原理 理解核心配置

1. XFR 区域使能

P_SW2 是 8 位寄存器 其中 B7 位为 “EAXFR 位”:该位为 0 时禁止访问 XFR 区域 为 1 时允许访问;USB 相关寄存器都在 XFR 区域中 需用 “|=0X80” 将 B7 位置 1(“|=” 操作可保留其他位原有状态 避免覆盖)。

2. 中断控制逻辑

  • EA 是 “总中断开关” 所有中断功能需先打开总中断(EA=1);
  • IE2 是 “中断允许寄存器 2” 其中特定位对应 USB 中断 需用 “|=0X80” 打开 USB 中断;两者缺一不可 否则 USB 无法响应下载命令。

3. 不停电下载核心

下载软件会向单片机发送默认命令 “STCRSP” 单片机通过 USB 数据处理代码接收到该命令后 会自动进入 “HID write 下载模式” 无需手动按按钮触发。

六、总结与工程复用

1. 重点回顾

  1. 库文件选择:需选 32 位 CDC 模式的.lib 和.h 文件 与之前的 32 位头文件匹配;
  2. 关键配置:必须使能 XFR 区域和打开中断 否则 USB 功能无法生效;
  3. 下载特点:第一次需手动进入模式 后续完全自动。

2. 工程复用

将 “示例代码” 文件夹直接复制到后续课程文件夹中 无需重新移植 USB 代码 只需在 main 函数中添加新功能代码(如按键控制、数码管显示等) 即可继承 USB 不停电下载功能。

回复

使用道具 举报 送花

  • 打卡等级:初来乍到
  • 打卡总天数:2
  • 最近打卡:2025-09-14 12:21:06
已绑定手机

1

主题

9

回帖

45

积分

新手上路

积分
45
发表于 3 小时前 | 显示全部楼层

C 语言关键知识补讲

一、核心目标

掌握单片机开发中必备的 C 语言知识,包括 USB-CDC 串口 printf 函数使用、数的进制转换、变量基本类型、常用运算符,为后续硬件控制代码编写打基础。

二、USB-CDC 串口之 printf 函数实现

1. 开启 printf 功能

  1. 复制上节课的 USB 不停电下载工程,打开工程中的 USB 库头文件;
  2. 找到头文件中第六个位置的反斜杠,将其去掉(反斜杠是注释符,去掉后启用 printf 相关宏定义);
  3. 编译工程,确保无错误(若不停电下载失效,检查是否选错 CDC 模式、漏加.lib 文件或误删库文件)。

2. printf 函数的重定向原理

工程中通过 #define printf PRINTF_HID实现重定向:所有代码中出现的 printf,都会被替换为 PRINTF_HID(库文件中自带的打印函数),无需手动编写打印底层逻辑。

3. printf 使用方法

  • 普通字符:双引号内的普通文字会原样输出,例如 printf("8051U深度入门课程\r\n");,其中 \r\n是换行转义符,确保输出后光标换行。
  • 转换说明:用 %开头的符号替换变量或常量,常用类型如下:
    • %s:输出字符串(需用英文双引号括起字符串,如 printf("加油%s\r\n","STC");会输出 “加油 STC”);
    • %d:输出十进制整数(如 printf("今年是%d年\r\n",24);输出 “今年是 24 年”);
    • %u:输出无符号十进制整数(适配 unsigned 类型变量)。
  • 注意事项:双引号内有几个 %,后面需跟几个参数,用逗号隔开;若要输出 %本身,需写两个 %%(如 printf("占比50%%\r\n");)。

4. 测试 printf 效果

  1. 下载工程:打开下载软件,勾选三个不停电下载选项,选 OBJ 文件夹中最新 HEX 文件,下载到单片机;
  2. 串口调试:在下载软件中找到单片机生成的 CDC 串口(如 COM8),波特率任意(USB 通信不依赖波特率),发送任意数据;
  3. 查看结果:单片机接收到数据后,会通过 printf 返回设定内容(如 STC yyds\r\n),蓝色为接收数据,可在串口设置中关闭 “显示发送数据” 只看接收结果。

三、数的进制 二进制 十进制 十六进制

1. 进制转换方法

  • 其他进制转十进制:按 “位权” 计算,例如二进制 1010=1×2³+0×2²+1×2¹+0×2⁰=10(十进制);十六进制 0xA3=10×16¹+3×16⁰=163(十进制)。
  • 十进制转其他进制:用电脑 “程序员计算器” 直接转换(十进制 83 转十六进制为 53,转二进制为 1010011)。

2. 十六进制记忆要点

十进制 0-15 对应十六进制 0-F,需牢记:10=A、11=B、12=C、13=D、14=E、15=F,后续寄存器地址设置常用。

3. 文本与 HEX 模式的区别

  • 文本模式:显示字符(如 S对应 ASCII 表中的字符);
  • HEX 模式:显示字符对应的十进制数转十六进制结果(如 S的十进制 ASCII 码为 83,HEX 模式显示 53),调试数值变量时常用。

四、数的基本类型

1. 常用类型及取值范围

变量类型 长度(位) 取值范围 说明
unsigned char 8 0-255 无符号 8 位,最常用
signed char 8 -128-127 有符号 8 位
unsigned int 16 0-65535 无符号 16 位,避免乘法溢出
signed int 16 -32768-32767 有符号 16 位
double 64 需配合 program float64 64 位双精度浮点型,需在代码开头添加宏定义才能使用

2. 变量定义与简化

  • 基本定义:类型 + 变量名,如 unsigned char x=20;(x 为无符号 8 位变量,值为 20);
  • 宏定义简化:用 #define U8 unsigned char后,可直接用 U8 x=20;(减少重复书写);
  • 变量作用域:定义在大括号外的是全局变量(所有代码可调用),定义在大括号内的是局部变量(仅括号内可用,出括号无效)。

3. 避免变量溢出

8 位变量最大为 255、16 位最大为 65535,若计算结果超范围会溢出(如 200×10=2000,用 8 位变量存储会变成 208),需根据计算需求选择合适类型(如乘法用 unsigned int)。

五、C 语言常用运算符

1. 算术运算符

  • 加减乘除+(加)、-(减)、*(乘)、/(除,仅取整数部分),例如 20/11=1
  • 取余%(求余数),例如 20%11=9
  • 自增自减x++(x 自加 1)、x--(x 自减 1),例如 x=20;x++后 x=21。

2. 关系运算符(判断真假)

  • 单片机中 “0 为假,非 0 为真”,常用运算符:

    • ==(等于):20==10为假;
    • !=(不等于):20!=10为真;
    • >(大于)、<(小于)、>=(大于等于)、<=(小于等于),例如 20>10为真。
  • 应用场景:配合 if使用,条件为真执行大括号内代码,假则执行 else代码:

    U8 x=20,y=10;
    if(x>y){
        printf("条件为真\r\n");
    }else{
        printf("条件为假\r\n");
    }
    

3. 逻辑运算符

  • &&(与):两边都为真才为真,例如 (x>y)&&(x==20)为真;
  • ||(或):一边为真就为真,例如 (x<y)||(x==20)为真;
  • !(非):取反,例如 !(x>y)为假。

4. 赋值与位运算符

  • 赋值简写+=(加等于,c+=ac=c+a)、-=(减等于)、*=(乘等于)、/=(除等于);
  • 位运算
    • &(与):对应位都为 1 才为 1,例如 00000101 & 00001011=00000001
    • |(或):对应位有 1 就为 1,例如 00000101 | 00001011=00001111
    • ^(异或):对应位不同为 1,例如 00000101 ^ 00001011=00001110
    • <<(左移):左移 n 位补 n 个 0,例如 00000101<<2=00010100
    • >>(右移):右移 n 位砍 n 位补 0,例如 00000101>>2=00000001

六、总结与重点

  1. printf 关键:记住 %s(字符串)、%d(十进制)、%u(无符号),换行用 \r\n,输出 %%%
  2. 进制核心:会用计算器转换,牢记十六进制 0-F 对应十进制 10-15;
  3. 变量要点:根据取值范围选类型,避免溢出,全局 / 局部变量作用域要分清;
  4. 运算符重点%是取余不是百分比,==是判断等于不是赋值,位运算在寄存器配置中常用。
回复

使用道具 举报 送花

  • 打卡等级:初来乍到
  • 打卡总天数:2
  • 最近打卡:2025-09-14 12:21:06
已绑定手机

1

主题

9

回帖

45

积分

新手上路

积分
45
发表于 3 小时前 | 显示全部楼层

8051U 课程笔记第六课:GPIO 基础与按键输入检测

一、核心目标

掌握 GPIO 基本概念与工作原理,实现按键控制 LED 的三个核心任务(按下亮 / 灭、切换亮灭),理解按键抖动问题及软件消抖方法,为后续硬件交互开发打基础。

二、GPIO 基础 关键概念

1. GPIO 定义与作用

GPIO 是通用输入输出端口的简称,本质是单片机的引脚,可输出高低电平或检测外部输入的高低电平,是单片机与外部硬件(如 LED、按键)交互的核心接口。

2. 高低电平定义

  • 高电平(逻辑 1):端口电压无限接近电源正极(VCC),3.3V 供电时逻辑 1 为 3.3V,5V 供电时为 5V(需注意单片机极限电压:VCC 最大不超过 5.5V,其他引脚电压不超过 VCC+0.3V,避免损坏)。
  • 低电平(逻辑 0):端口电压等于电源负极(GND),即 0V。

3. GPIO 的四种工作模式

模式 特点与用途 电流能力
准双向口 既能输入也能输出,默认模式 灌电流 20mA,拉电流微安级
推挽输出 输出电流大,适合驱动大电流设备 灌电流、拉电流均达 20mA
高阻输入 仅用于输入,电阻极大 无输出电流
开漏模式 需外接上拉电阻,适合多设备并联 需外部电路配合
  • 常用模式:准双向口(多数场景无需特殊驱动,兼顾输入输出)。

4. 输入电压范围(3.3V 供电)

单片机默认使能施密特触发器(上电复位后开启),输入电压需满足:

  • 低电平:最大 0.99V(超过则无法识别为低电平);
  • 高电平:最小 1.09V(低于则无法识别为高电平);
  • 检测方法:若按键触发异常,可用万用表测量引脚电压,判断是否在阈值范围内。

三、按键输入检测 原理与任务实现

1. 按键检测原理(实验箱电路)

  • 电路结构:P32/P33 引脚经 300 欧姆电阻连接电源,再通过按键接地;
  • 电平逻辑:GPIO 默认复位为高电平(准双向口模式),按下按键时引脚接地,电平变为 0V;松开后引脚恢复高电平,因此判断 “P32==0” 即可检测按键是否按下。

2. 三个核心任务 代码实现(基于 USB 不停电下载工程)

前置准备

复制上节课工程,删除无用代码(如变量 x、y),保留 USB 初始化、IO 口配置代码;需添加 P40=0(LED 回路开关,仅需执行一次,放在 while 循环外)。

任务 1:按下 P32 灯亮,松开灯灭

#include "AI8051U.h"
#include "USB库头文件.h"

void main(void){
    // 系统初始化:设置指令延时、使能XFR访问、提高X2速度
    WTST = 0;          // 程序指令延时参数设为最小
    EAXFR = 1;         // 使能扩展XFR区域访问
    CKCON = 0;         // 提高X2访问速度
  
    P0M0 = P0M1 = P4M0 = P4M1 = 0X00;  // P0、P4设为准双向口
    P40 = 0;  // 打开LED回路开关(必须设置,否则LED不亮)
  
    while(1){
        if(P32 == 0){  // 检测P32按键是否按下(按下为低电平0)
            P00 = 0;   // 按下时P00输出0,LED亮
        }else{
            P00 = 1;   // 松开时P00输出1,LED灭
        }
    }
}

任务 2:按下 P32 灯灭,松开灯亮

基于任务 1 修改 LED 电平逻辑,仅需交换 P00=0P00=1的位置:

while(1){
    if(P32 == 0){  
        P00 = 1;   // 按下时P00输出1,LED灭
    }else{
        P00 = 0;   // 松开时P00输出0,LED亮
    }
}

任务 3:按一下 P32 灯切换亮灭(需标志位与消抖)

  • 核心需求:按一次切换状态,避免按下时频繁切换(需等待按键松开);
  • 关键:定义标志位记录 LED 状态,按下后取反状态,等待按键松开。
#include "AI8051U.h"
#include "USB库头文件.h"
#include "intrins.h"  // 延时函数需引用此头文件

// 生成20ms延时函数(用RSP软件延时计算器,24MHz主屏、AR32指令级)
void Delay20ms(void){
    unsigned int i, j;
    for(i=247; i>0; i--)
        for(j=201; j>0; j--);
}

void main(void){
    WTST = 0;
    EAXFR = 1;
    CKCON = 0;
  
    P0M0 = P0M1 = P4M0 = P4M1 = 0X00;
    P40 = 0;
    unsigned char state = 0;  // 定义LED状态标志位,初始0(灭)
  
    while(1){
        if(P32 == 0){  // 检测到按键按下
            Delay20ms();  // 20ms软件消抖(解决机械按键抖动)
            if(P32 == 0){  // 再次确认按键按下
                state = !state;  // 状态取反(0变1,1变0)
                P00 = state;     // 输出状态到P00,控制LED
                while(P32 == 0); // 等待按键松开(避免持续取反)
            }
        }
    }
}

四、关键问题 按键消抖

1. 抖动原因

机械按键按下 / 松开时,内部金属触点会有 20ms 内的高频抖动,导致引脚电平反复跳变(如按下时瞬间从 3.3V 降到 0V 又跳回,再稳定 0V),程序会误判为多次按下。

2. 解决方法:软件延时消抖

  • 工具:用 RSP 软件的 “软件延时计算器” 生成延时函数;
  • 步骤:
    1. 打开 RSP 工具,选择系统主屏 24MHz(与下载时一致);
    2. 输入延时时间 20ms,指令级选 AR32(32 位单片机);
    3. 生成 C 语言延时函数,复制到代码中(需放在 main 函数前);
    4. 检测到按键按下后,先延时 20ms,再二次确认按键状态,确保是真实按下。

3. 系统初始化代码

需在 main 函数开头添加三句代码,确保延时准确和 XFR 区域访问:

  1. WTST = 0;:将程序指令延时参数设为最小;
  2. EAXFR = 1;:使能扩展 XFR 区域访问(USB 及部分寄存器在 XFR 中);
  3. CKCON = 0;:提高 X2 区域访问速度。

五、课后小练习

  1. 任务 1:按一下 P32 按钮,LED 亮;再按一下 P33 按钮,LED 灭(可扩展:按 P32 左边四盏 LED 亮、右边四盏灭,按 P33 相反);
  2. 任务 2:按一下 P32 按钮,亮 1 盏 LED;再按一下,亮 2 盏;依次递增,直到 8 盏全亮。
回复

使用道具 举报 送花

  • 打卡等级:初来乍到
  • 打卡总天数:2
  • 最近打卡:2025-09-14 12:21:06
已绑定手机

1

主题

9

回帖

45

积分

新手上路

积分
45
发表于 3 小时前 | 显示全部楼层

8051U 课程笔记第七课:定时器中断与函数基础

一、核心目标

掌握定时器中断的原理与配置方法,解决延时函数阻塞主程序的问题;理解函数的定义、声明与调用逻辑;实现 LED 定时控制(3 秒闪烁、按亮延时灭)、救护车红蓝灯交替等任务,为多任务处理打基础。

二、定时器基础 为什么需要定时器

1. 痛点:延时函数的局限性

单片机是单核 CPU,同一时间只能执行一个任务。若使用 delay函数,主程序会被 “卡住”—— 期间无法响应按键、串口等操作。

2. 定时器的核心作用

  • 周期性执行任务:按固定时间自动触发操作,无需阻塞主程序;
  • 替代长延时:释放 CPU 资源,主程序可同时处理其他任务;
  • 中断特性:无论主程序执行到哪,定时器中断均可打断它,执行完中断任务后返回主程序继续执行。

3. 定时器关键配置

  • 支持定时器:T0、T1、T2、T3、T4、T11,均支持 24 位自动重载模式;
  • 系统时钟:需与下载时选择的时钟一致;
  • 分频与模式
    • 分频系数(TM0PS):控制定时器时钟速度,公式为 “分频后时钟 = 系统时钟 /(TM0PS+1)”,例:TM0PS=91 时,分频后为 24MHz/(91+1)≈260.87kHz;
    • 12T/1T 模式:12T 模式下定时器时钟 = 分频后时钟 / 12,1T 模式不分频(仅两档可选);
    • 自动重载:计数到 65536 后,自动加载预设值(TH0/TL0),无需手动重置,实现周期性中断。

三、定时器中断 任务一实现 LED3 秒闪烁 + 按键计数

1. 用 RSP 软件生成定时器代码

  1. 打开 RSP 软件,找到 “定时器计算器”;
  2. 配置参数:系统时钟 24MHz→定时时间 3 秒→选择定时器 0→模式 24 位自动重载→勾选 “定时器中断”→指令级选 AR32(32 位单片机);
  3. 生成代码:点击 “生成 C 语言代码”,得到定时器初始化函数(Timer0_Init)和中断函数(Timer0_ISR)。

2. 完整代码实现

#include "AI8051U.h"
#include "USB库头文件.h"
#include "intrins.h"

// 函数声明(若函数定义在main后,需提前声明)
void Timer0_Init(void);
void Timer0_ISR(void) interrupt 1;

// 全局变量:LED状态、按键计数
unsigned char LED_State = 0;  // 0灭1亮
unsigned int Key_Count = 1;   // 按键计数,初始1(第一次按显示1次)

// 定时器0初始化函数(RSP生成,3秒中断一次)
void Timer0_Init(void){
    TM0PS = 0x5B;  // 分频系数91,24MHz/(91+1)≈260.87kHz
    AUXR &= 0x7F;  // 定时器0工作在12T模式
    TMOD &= 0xF0;  // 清除定时器0模式位
    TMOD |= 0x00;  // 定时器0为16位自动重载模式(24位兼容)
    TH0 = 0x01;    // 定时器高位初值
    TL0 = 0x3F;    // 定时器低位初值(0x013F=319,65536-319=65217)
    TF0 = 0;       // 清除溢出标志
    TR0 = 1;       // 启动定时器0
    ET0 = 1;       // 使能定时器0中断
    EA = 1;        // 使能总中断
}

// 定时器0中断函数(3秒执行一次)
void Timer0_ISR(void) interrupt 1{
    TH0 = 0x01;    // 重新加载初值(自动重载需手动补初值)
    TL0 = 0x3F;
    LED_State = !LED_State;  // 状态取反
    P00 = LED_State;         // 控制LED亮灭
}

void main(void){
    // 系统初始化
    WTST = 0;
    EAXFR = 1;
    CKCON = 0;
  
    P0M0 = P0M1 = P4M0 = P4M1 = 0X00;  // IO设为准双向口
    P40 = 0;  // 打开LED回路开关
    Timer0_Init();  // 调用定时器初始化函数
  
    while(1){
        // 按键检测:按下P32打印计数
        if(P32 == 0){
            Delay20ms();  // 20ms消抖(函数需提前定义,RSP生成)
            if(P32 == 0){
                // 打印按键次数,“次”后加\xFD解决乱码
                printf("按键按下次数:%d次\xFD\r\n", Key_Count);
                Key_Count++;  // 计数加1
                while(P32 == 0);  // 等待按键松开
            }
        }
    }
}

// 20ms延时函数(RSP软件生成,24MHz时钟)
void Delay20ms(void){
    unsigned int i, j;
    for(i=247; i>0; i--)
        for(j=201; j>0; j--);
}

3. 关键说明

  • 乱码解决:中文 “次” 属于特殊字符,需在其后加 \xFD(手册附录:0XFD/0XFE/0XFF 字符需额外补充,避免编译跳过);
  • 中断执行逻辑:定时器每 3 秒溢出一次,触发 Timer0_ISR,LED 状态取反;主循环同时检测按键,按下后打印计数,无阻塞;
  • 自动重载:中断中需重新加载 TH0/TL0初值,确保下次定时仍为 3 秒。

四、函数基础 定义、声明与调用

1. 核心概念

类型 特点与格式 示例
函数定义 含返回值、函数名、参数、具体功能(大括号) void Timer0_Init(void){ /* 初始化代码 */ }
函数声明 仅含返回值、函数名、参数,无功能(分号结尾) void Timer0_Init(void);
函数调用 函数名 + 小括号,有参数则传入 Timer0_Init();

2. 关键规则

  • 命名:用有意义的英文,不与 C 语言关键字重名,不包含特殊字符 / 中文;
  • 声明必要性:若函数定义在 main函数之后,需提前声明;
  • 参数与返回值:无参数写 void,无返回值也写 void;有返回值需用 return返回对应类型的值。

3. 示例:解决 “函数定义在 main 后” 的报错

  1. Timer0_Init定义在 main之后,直接调用会报错;
  2. 解决方案:在 main前添加声明 void Timer0_Init(void);
  3. 编译验证:声明后编译器可索引到函数,报错消失。

五、其他任务实现

任务二:按一下 LED 亮,3 秒后灭

#include "AI8051U.h"
#include "USB库头文件.h"
#include "intrins.h"

void Timer0_Init(void);
void Timer0_ISR(void) interrupt 1;

bit Timer_Flag = 0;  // 定时器完成标志(0未完成,1完成)

void Timer0_Init(void){
    TM0PS = 0x5B;
    AUXR &= 0x7F;
    TMOD &= 0xF0;
    TMOD |= 0x00;
    TH0 = 0x01;
    TL0 = 0x3F;
    TF0 = 0;
    TR0 = 1;
    ET0 = 1;
    EA = 1;
}

void Timer0_ISR(void) interrupt 1{
    TH0 = 0x01;
    TL0 = 0x3F;
    Timer_Flag = 1;  // 3秒到,置位标志
    TR0 = 0;         // 关闭定时器(避免重复触发)
}

void main(void){
    WTST = 0;
    EAXFR = 1;
    CKCON = 0;
  
    P0M0 = P0M1 = P4M0 = P4M1 = 0X00;
    P40 = 0;
  
    while(1){
        if(P32 == 0){
            Delay20ms();
            if(P32 == 0){
                P00 = 0;        // 按下点亮LED
                Timer0_Init();  // 启动3秒定时
                while(!Timer_Flag);  // 等待定时完成
                P00 = 1;        // 3秒后熄灭LED
                Timer_Flag = 0; // 重置标志
                while(P32 == 0);
            }
        }
    }
}

void Delay20ms(void){
    unsigned int i, j;
    for(i=247; i>0; i--)
        for(j=201; j>0; j--);
}

任务三:救护车红蓝灯交替(按 P32 启停)

#include "AI8051U.h"
#include "USB库头文件.h"
#include "intrins.h"

void Timer0_Init(void);
void Timer0_ISR(void) interrupt 1;

unsigned char Run_State = 0;  // 0停止,1运行
unsigned char LED_State = 0;  // 0红灯亮,1蓝灯亮

void Timer0_Init(void){
    TM0PS = 0x17;  // 分频系数23,定时0.5秒(24MHz/(23+1)/12=833.33kHz,计数65536-41667=23869→0.5秒)
    AUXR &= 0x7F;
    TMOD &= 0xF0;
    TMOD |= 0x00;
    TH0 = 0x5D;    // 初值41667→0x5D83
    TL0 = 0x83;
    TF0 = 0;
    TR0 = 1;
    ET0 = 1;
    EA = 1;
}

void Timer0_ISR(void) interrupt 1{
    TH0 = 0x5D;
    TL0 = 0x83;
    if(Run_State == 1){
        LED_State = !LED_State;
        P00 = LED_State;  // P00红灯,P01蓝灯
        P01 = !LED_State;
    }
}

void main(void){
    WTST = 0;
    EAXFR = 1;
    CKCON = 0;
  
    P0M0 = P0M1 = P4M0 = P4M1 = 0X00;
    P40 = 0;
    P00 = 1; P01 = 1;  // 初始灯灭
  
    while(1){
        if(P32 == 0){
            Delay20ms();
            if(P32 == 0){
                Run_State = !Run_State;  // 切换运行/停止
                if(Run_State == 0){
                    P00 = 1; P01 = 1;  // 停止时灯灭
                    TR0 = 0;           // 关闭定时器
                }else{
                    Timer0_Init();     // 运行时启动定时器
                }
                while(P32 == 0);
            }
        }
    }
}

void Delay20ms(void){
    unsigned int i, j;
    for(i=247; i>0; i--)
        for(j=201; j>0; j--);
}

六、课后小练习:电子功德箱

任务要求

  1. 按按钮 1(P32):切换 “双倍功德时间”/“单倍功德时间”,串口打印当前模式;
  2. 按按钮 2(P33):
    • 双倍模式:功德 + 2,LED 亮 2 秒;
    • 单倍模式:功德 + 1,LED 亮 1 秒;
  3. 功德值累计,串口打印当前功德。

实现提示

  • 用全局变量 MeritMode(0 单倍,1 双倍);
  • 生成两个定时器初始化函数(Timer1s_InitTimer2s_Init),分别对应 1 秒、2 秒定时;
  • 按按钮 2 时,根据 Mode调用对应定时器,定时到后熄灭 LED,更新并打印功德值。
回复

使用道具 举报 送花

  • 打卡等级:初来乍到
  • 打卡总天数:2
  • 最近打卡:2025-09-14 12:21:06
已绑定手机

1

主题

9

回帖

45

积分

新手上路

积分
45
发表于 3 小时前 | 显示全部楼层

8051U 课程笔记:数码管静态显示实现

一、核心目标

掌握共阴数码管的显示原理与硬件连接,通过 8051U 单片机控制数码管显示数字(0 - 9),理解段码表的作用,为后续动态显示、多位数码管级联打基础。

二、数码管基础 原理与硬件连接

1. 数码管结构

共阴数码管由 8 个 LED(7 个段 + 1 个小数点)组成,公共端接地(低电平)。当某一段的引脚为高电平时,该段 LED 点亮。

2. 段码定义

数码管的 8 个段分别对应 a、b、c、d、e、f、g、dpdp 为小数点),引脚通常按顺序连接到单片机的 P0 端口(P00 对应 a、P01 对应 b … P07 对应 dp)。

3. 硬件连接(以实验箱为例)

  • 数码管公共端接 GND(共阴类型);
  • 数码管段引脚 a - g、dp 依次连接到单片机 P00 - P07;
  • 单片机 P40 需置 0(打开数码管回路电源,否则数码管无供电无法点亮)。

三、段码表 数字与电平的映射

1. 段码表原理

要显示数字,需为每个数字生成对应的段码(即 P0 端口的电平组合)。例如:

  • 显示 “0”:需 a、b、c、d、e、f 亮,对应二进制 00111111,十六进制 0x3F
  • 显示 “1”:需 b、c 亮,对应二进制 00000110,十六进制 0x06

2. 共阴数码管段码表定义

unsigned char seg_table[] = {
    0x3F, // 0: 对应二进制 00111111
    0x06, // 1: 对应二进制 00000110
    0x5B, // 2: 对应二进制 01011011
    0x4F, // 3: 对应二进制 01001111
    0x66, // 4: 对应二进制 01100110
    0x6D, // 5: 对应二进制 01101101
    0x7D, // 6: 对应二进制 01111101
    0x07, // 7: 对应二进制 00000111
    0x7F, // 8: 对应二进制 01111111
    0x6F  // 9: 对应二进制 01101111
};

四、代码实现 显示数字 “5”

1. 工程准备

基于 “USB 不停电下载工程”,删除无关代码,保留系统初始化、USB 初始化(若需串口调试可保留)。

2. 完整代码

#include "AI8051U.h"
#include "USB库头文件.h"  // 若需USB功能,引用对应头文件

// 共阴数码管段码表(0-9)
unsigned char seg_table[] = {0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 0x7F, 0x6F};

void main(void) {
    // 系统基础初始化
    WTST = 0;    // 程序指令延时参数设为最小
    EAXFR = 1;   // 使能扩展XFR区域访问(USB等外设寄存器在XFR中)
    CKCON = 0;   // 提高X2区域访问速度

    P0M0 = P0M1 = P4M0 = P4M1 = 0x00; // P0、P4端口设为「准双向口」模式
    P40 = 0;     // 打开数码管回路电源(必须设置,否则数码管不亮)

    // 【可选】USB初始化(若需串口打印等功能)
    USB_Init();
    while (USB_GetState() != USB_STATE_CONFIGURED); // 等待USB配置完成

    // 显示数字「5」:段码表中索引为5的元素是0x6D
    P0 = seg_table[5];

    while (1) {
        // 主循环可添加其他任务(如按键切换显示数字)
    }
}

3. 关键说明

  • 段码表索引seg_table[5] 对应数字 “5”,直接将该值赋值给 P0 端口,即可控制数码管显示;
  • 回路电源P40 = 0 是数码管点亮的必要条件,若省略此步,数码管无供电会始终不亮;
  • 扩展任务:若要通过按键切换显示数字,可在 while(1)中检测 P32 按键,每按一次让 “数字索引” 加 1(如 index = (index + 1) % 10),再将 seg_table[index] 赋值给 P0。

五、课后小练习

  1. 任务 1:实现数码管从 0 到 9循环显示,每 1 秒切换一次(结合定时器中断实现定时);
  2. 任务 2:用两个按键(P32 “加 1”、P33 “减 1”)控制数字,数码管实时显示结果;
  3. 拓展:查找共阳数码管的段码表规律(共阳需低电平点亮段),修改代码让共阳数码管显示数字。
回复

使用道具 举报 送花

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|手机版|深圳国芯人工智能有限公司 ( 粤ICP备2022108929号-2 )

GMT+8, 2025-9-14 16:54 , Processed in 0.118963 second(s), 94 queries .

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

快速回复 返回顶部 返回列表