找回密码
 立即注册

学习STC8H8K64U

[复制链接]
  • 打卡等级:偶尔看看I
  • 打卡总天数:18
  • 最近打卡:2025-12-10 00:23:34
已绑定手机

5

主题

15

回帖

117

积分

注册会员

积分
117
发表于 2025-11-16 20:47:50 | 显示全部楼层
逻辑分析仪

一、学习目标
  • 学会使用逻辑分析仪分析、调试电路中的 IO 信号。


二、学习内容
(一)什么是逻辑分析仪逻辑分析仪(Logic Analyzer) 是一种专门用来分析数字信号的工具,常用于观察和调试:
  • 各种 控制信号(如片选、使能信号等)
  • 时钟信号
  • 串行总线波形(UART、SPI、I²C 等)
  • 单片机 IO 口的高低电平变化

它可以帮助我们:
  • 验证程序是否按预期输出正确的电平时序;
  • 对比多个信号之间的时序关系;
  • 调试和分析通讯协议是否正确。

本教程使用的是 mini 版 USB 逻辑分析仪
  • 10 个引脚

    • 8 个为 采样通道(CH0 ~ CH7),可同时采 8 路数字信号;
    • 1 个为 时钟或触发相关引脚(视模块而定);
    • 1 个为 GND 地线


(二)软件安装逻辑分析仪通常配套使用一款上位机软件(如 Logic)。
安装步骤大致为:
  • 打开提供的安装包,根据自己的系统选择合适版本(例如 2.4.9)。
  • 双击安装程序,按提示一步步“下一步”完成安装。
  • 安装完成后,桌面上会出现 Logic(或对应名称)的快捷方式。


(三)软件界面与基本功能打开逻辑分析仪软件后,可以看到主界面主要包括以下区域:
  • 顶部状态栏

    • 显示设备连接状态:

      • Disconnected:未连接逻辑分析仪;
      • Connected:已连接上逻辑分析仪。

  • 菜单栏

    • 用于选择工程、配置、协议解析、导出数据等高级功能。

  • 通道显示区

    • 中间部分是 CH0 ~ CH7 各个通道的波形显示区域;
    • 每个通道可以单独设置名称、颜色、是否启用。

  • 右侧控制区

    • 包含采样率、采样时长、触发条件等设置;
    • 绿色按钮一般是“开始采样 / Start”按钮。


(四)使用逻辑分析仪测试“点灯”IO1. 测试目标假设我们有一段控制 LED 闪烁的程序:
  • 要求 P5.3 引脚:

    • 高电平 1 秒
    • 低电平 1 秒
    • 循环往复。

我们希望用逻辑分析仪来 验证 P5.3 的波形是否真的是“高 1 秒、低 1 秒”
2. 接线方式
  • 找到开发板上的 P5.3 引脚
  • 用杜邦线将逻辑分析仪的 某一路通道(如 CH1) 接到 P5.3;
  • 将逻辑分析仪的 GND 线 与开发板的 GND 相连(一定要共地)。

在富文本中可插入硬件接线示意图:
  • CH1 → P5.3
  • GND → 开发板 GND

3. 开始测试
  • 将带有 LED 闪烁程序的固件烧录到开发板,并运行;
  • 打开 Logic 软件,确认状态栏显示 Connected;
  • 在右侧设置:

    • 采样率(Sample Rate):建议先选一个较高值(如 24MS/s),如果电脑带不动可适当降低;
    • 采样时长:设置为能看到多次高低翻转即可,如几秒;

  • 点击右侧绿色 开始采集 按钮。

采集完成后:
  • 在通道 1 的波形上可以看到一个个矩形波:高低电平交替;
  • 使用鼠标滚轮缩放时间轴,放大波形。
  • 将鼠标移动到波形区间上方,会显示一个悬浮提示框,提示:

    • 高电平持续时间(例如约 1.00s);
    • 低电平持续时间(同样约 1.00s)。

如果测得的时间与预期差别较大,就需要回头检查:
  • 程序中的延时或定时器配置是否有误;
  • 主频/时钟设置是否正确等。


(五)采样率与稳定性注意事项如果在实际使用中发现:
  • 逻辑分析仪采集时 频繁中断或报错
  • 或者软件提示数据丢失、USB 带宽不足;

可以尝试:
  • 将采样率从 24MS/s 降低到 1MS/s 或更低,例如:

    • 1MS/s(1 MHz)
    • 500kS/s

对于像 1 秒级别 的 IO 闪烁测试,1MS/s 已经绰绰有余。

截图202511162035549413.jpg
截图202511162036484149.jpg
截图202511162037337969.jpg
截图202511162038168395.jpg
截图202511162047232144.jpg
截图202511162047456895.jpg
回复

使用道具 举报 送花

  • 打卡等级:偶尔看看I
  • 打卡总天数:18
  • 最近打卡:2025-12-10 00:23:34
已绑定手机

5

主题

15

回帖

117

积分

注册会员

积分
117
发表于 2025-11-16 20:54:12 | 显示全部楼层
C51 版本 Keil 环境搭建

一、学习目标在这份环境搭建笔记中,你需要掌握:
  • 了解 Keil C51 开发环境 的用途与基本构成。
  • 掌握 Keil C51 的下载安装与激活步骤
  • 学会在 Keil 中集成 STC 单片机开发环境(STC-ISP、芯片数据库)。
  • 完成常用工具的基础配置(中文显示、字体、注释快捷键等)。


二、准备工作在开始搭建环境前,需要准备:
  • 一台 Windows 系统电脑。
  • Keil C51 安装包(可从 Keil 官方网站下载)。
  • 一块 8051 内核单片机开发板(例如 STC 系列)。


三、整体搭建流程概览可以先用一张“总流程”列表,帮助自己建立全局概念:
  • 从官网下载安装 Keil C51
  • 管理员身份 启动 Keil,完成 证书激活
  • 从 STC 官网下载 STC-ISP 烧录工具。
  • 通过 STC-ISP 将 STC MCU 数据库 导入 Keil。
  • 在 Keil 中新建项目,确认能选到 STC 系列芯片。
  • 配置 STC-ISP 烧录工具选项。
  • 在 Keil 中设置中文编码、字体、注释快捷键等。


四、Keil C51 下载与安装1. 下载 Keil C512. 安装 Keil运行下载好的安装包,按照安装向导依次:
  • 选择安装路径;
  • 同意许可协议;
  • 确认安装组件(确保勾选 C51 相关组件);
  • 开始安装,等待进度条完成;
  • 安装结束后点击 Finish。


五、Keil C51 激活(证书管理)
非常关键:一定要用“管理员身份”打开 Keil
在富文本中可以用“警告/提示框”或加粗高亮:
一定要用 管理员身份 打开 Keil!
一定要用 管理员身份 打开 Keil!
一定要用 管理员身份 打开 Keil!
1. 打开证书管理
  • 右键 Keil 图标 → 选择 “以管理员身份运行”
  • 进入 Keil 后,在菜单栏打开 File → License Management(证书管理)
  • 在弹出的界面中,可以看到当前机器的 CID(客户 ID)

2. 输入授权证书
  • 从代理商或官方购买到授权证书(通常是一串序列号)。
  • 在 License Management 窗口中,将获取到的 序列号 填入对应输入框。
  • 点击 Add安装/添加授权 按钮。
  • 激活成功后,下方会显示授权的有效时间或“永久有效”等信息。


六、集成 STC 单片机开发环境1. 下载 STC-ISP 烧录工具
  • 打开 STC 官方网站:
    https://www.stcai.com/gjrj
  • 按下 Ctrl + F,搜索关键字:STCAI-ISP下载。
  • 找到 “STCAI-ISP 下载” 的位置,点击进入下载链接。
  • 下载最新版本的 STC-ISP 工具并安装/解压。

2. 通过 STC-ISP 把 STC 环境导入 Keil
  • 打开 STC-ISP 工具。
  • 按照软件界面上的提示,找到“将 STC 支持添加到 Keil”相关按钮(原文中有截图,可放在这里)。
  • 点击按钮后,软件会弹出一个对话框,要求你选择 Keil 的安装目录。
  • 选择 Keil C51 的安装根目录(以你本机实际路径为准),确认。
  • 成功后会弹出提示框,表示 STC MCU Database 已经导入 Keil。

在富文本中,适合把三张截图按顺序插入文字下方:
  • 点击按钮
  • 选择 Keil 目录
  • 成功提示框

温馨提醒:导入完成后,需要关闭当前 Keil 并重新打开,才能加载新的 STC 环境。
可以用一个“提示框/加粗行”单独强调这一点。
3. 验证 STC 环境是否导入成功
  • 重新打开 Keil(建议仍然以管理员身份运行)。
  • 选择 Project → New µVision Project...,新建一个 Keil 工程。
  • 在弹出的“选择器件(Select Device for Target)”对话框里:

    • 下拉左侧的厂商/库选择框;
    • 如果能看到 “STC MCU Database” 这一项,说明导入成功。

  • 选中 “STC MCU Database” 后,在右侧搜索框中输入你要用的 STC 芯片型号(如 STC8H8K64U),确认可以搜索到。


七、常用工具与编辑环境配置1. STC-ISP 烧录工具关闭模式配置在 STC-ISP 工具中,根据原文截图提示:
  • 打开 “关闭模式/复位模式” 配置界面;
  • 按图示选择合适的关闭方式(例如下载后自动复位、选中合适的复位时间等);
  • 配置完成后保存。

这部分主要是为了保证每次下载程序后,单片机能正常复位并运行新程序。

2. Keil 支持中文注释为了避免中文注释出现乱码,可以在 Keil 中设置文件编码:
  • 在 Keil 中打开/新建一个 C 文件。
  • 在菜单中找到 “Edit → Configuration...” 或编辑器相关设置。
  • 在“Encoding / File Encoding”选项中选择:

    • GB2312(适合简体中文)。

  • 确保你当前编辑的文件也保存为该编码格式。


3. Keil 字体与字号设置为了获得更清晰的代码显示,可以统一字体与字号:
  • 推荐:

    • 字体:Consolas
    • 字号:16

设置步骤大致为:
  • 在 Keil 菜单中选择 “Edit → Configuration...”“View → Fonts”(具体以你的版本为准)。
  • 找到代码编辑器字体选项,选择 Consolas,字号设为 16

注意:字体设置只有在文件编码为 ANSIUTF-8 without signature 时才能生效,这一点可以在富文本中用加粗强调。

4. Keil 配置注释/取消注释快捷键为了提高编码效率,可以给 注释/取消注释 配上快捷键。
大致步骤:
  • 打开 Edit → Configuration → Shortcut Keys(或类似快捷键配置窗口)。
  • 搜索如下命令:

    • Edit: Advanced: Comment Selection(注释当前选中代码)
    • Edit: Advanced: Uncomment Selection(取消注释)

  • 分别给它们分配快捷键,例如:

    • 注释:Ctrl + /
    • 取消注释:Ctrl + Shift + /

  • 点击 OK 保存设置。


回复

使用道具 举报 送花

  • 打卡等级:偶尔看看I
  • 打卡总天数:18
  • 最近打卡:2025-12-10 00:23:34
已绑定手机

5

主题

15

回帖

117

积分

注册会员

积分
117
发表于 2025-11-17 15:55:14 | 显示全部楼层
extern 关键字

一、学习目标
  • 理解 extern 的作用。
  • 掌握 extern 在多文件工程中的基本用法。


二、学习内容1. extern 是什么?extern 是 C 语言中的一个关键字,用来说明:
“这个变量 / 函数不是在当前文件里定义的,而是在别的源文件里已经定义好了。”
编译器看到 extern 声明时,会知道:
  • 这里只是“声明”,
  • 真正的“定义和分配内存”在其他源文件中。

所以,当你想在 多个 .c 文件之间共享同一个全局变量或函数 时,就需要用到 extern。

2. extern 用于变量(1)在一个源文件中定义全局变量例如,在 a.c 中:
  1. // a.c
  2. int global_var = 10;   // 定义全局变量(真正的内存分配在这里)
复制代码
(2)在另一个源文件中使用这个变量在 b.c 中,如果要用 global_var,需要先声明:
  1. // b.c
  2. #include <stdio.h>
  3. extern int global_var;   // 告诉编译器:这个变量在别的文件里定义过了
  4. int main() {
  5.     printf("%d\n", global_var);  // 正常使用
  6.     return 0;
  7. }
复制代码
这里的 extern int global_var;:
  • 不会再分配一份内存
  • 只是“借用”了 a.c 里已经定义好的那个全局变量。


3. extern 用于函数函数的情况和变量类似。
(1)在一个源文件中定义函数
  1. // add.c
  2. int add(int a, int b) {
  3.     return a + b;
  4. }
复制代码
(2)在另一个源文件中声明并调用
  1. // main.c
  2. #include <stdio.h>
  3. extern int add(int a, int b);   // 函数声明
  4. int main() {
  5.     int result = add(1, 2);     // 调用函数
  6.     printf("%d\n", result);
  7.     return 0;
  8. }
复制代码
其实函数原型声明默认就带 extern 的含义,所以你经常只写:
  1. int add(int a, int b);  // 和 extern int add(...) 等价
复制代码

4. 把 extern 声明放到头文件中同一个变量 / 函数会被多个 .c 文件使用 时,通常的做法是:
  • 某一个 .c 文件中做“真正的定义”
  • 在一个 统一的 .h 头文件中写 extern 声明
  • 所有需要用到它的 .c 文件,#include 这个头文件。

(1)头文件:声明用 extern
  1. // myheader.h
  2. extern int global_var;
  3. extern int add(int a, int b);
复制代码
(2)实现文件:真正定义在一个 .c 里
  1. // myimpl.c
  2. #include "myheader.h"
  3. int global_var = 10;     // 定义(只有这一处!)
  4. int add(int a, int b) {  // 定义
  5.     return a + b;
  6. }
复制代码
(3)其它文件:只需包含头文件就能用
  1. // test.c
  2. #include <stdio.h>
  3. #include "myheader.h"
  4. int main() {
  5.     printf("%d\n", global_var);
  6.     int result = add(1, 2);
  7.     printf("%d\n", result);
  8.     return 0;
  9. }
复制代码

5. 常见注意点(结合实践记忆)
下面几点是结合上面内容归纳的小结,方便你在工程里少踩坑:
  • “定义”只能有一份

    • int global_var = 10; 在整个工程里只能写在 一个 .c 文件里
    • 其他文件要用它,写 extern int global_var; 即可。

  • 不要在头文件里写带初始化的全局变量定义

    • ✗ 不推荐:
      1. // myheader.h
      2. int global_var = 10;   // 这样每个包含这个头文件的 .c 都会“各自定义一份”
      复制代码

    • ✔ 正确做法:
      1. <pre><code class="language-c">// myheader.h
      2. extern int global_var;
      3. </code></pre>
      4. <pre><code class="language-c">// myimpl.c
      5. int global_var = 10;</code></pre>
      复制代码

  • 函数声明放头文件,函数定义放 .c 文件

    • 头文件里:函数原型(相当于 extern 声明)
    • 某个 .c 里:真正实现
    • 其它 .c 用这个函数,只需 #include "xxx.h"


回复

使用道具 举报 送花

  • 打卡等级:偶尔看看I
  • 打卡总天数:18
  • 最近打卡:2025-12-10 00:23:34
已绑定手机

5

主题

15

回帖

117

积分

注册会员

积分
117
发表于 2025-11-17 16:06:13 | 显示全部楼层
LED 灯控制与流水灯设计

一、学习目标
  • 熟悉 LED 电路的原理图设计思路
  • 理解并掌握对应 IO 引脚 的功能与作用。
  • 能够使用单片机 IO 控制多个 LED 的开关
  • 能够编写程序实现 流水灯 / 走马灯 效果。


二、学习内容(一)原理图理解在开发板上,LED 并不是直接由单片机 IO 去“推灯”,而是通过 三极管 + 开关控制
  • 上电后,由一路电源通过 PNP 三极管 S8550 给整个 LED 灯组供电。
  • 单片机通过一个专门的控制引脚 LED_SW(接到三极管 B 极)来控制 整组 LED 是否上电
  • 每一个 LED 的 负极 接到不同的 IO 引脚,通过这些 IO 的高低电平控制某一颗 LED 是否点亮。


(二)控制分析1. S8550 PNP 三极管特性三极管引脚含义:
  • B(Base,基极):控制端,可以理解成“根据这个条件做事”。
  • E(Emitter,发射极):电流输出端,可理解为“发射端”。
  • C(Collector,集电极):负载所在端,可理解为“用电器所在区域”。

对于 PNP 型三极管(如 S8550)
  • E 极接正电源,C 极接负载(这里是 LED 灯组)。
  • B 极为高电平时

    • E→C 通路截止,三极管不导通,LED 不上电。

  • B 极为低电平时

    • E→C 导通,LED 灯组获得电源,可以通过各路 IO 控制点亮。

总结一句:
  • LED_SW = 0(低电平):整组 LED 允许工作;
  • LED_SW = 1(高电平):整组 LED 被关闭。

2. LED 控制逻辑每一颗 LED 的 负极 接到一个单片机的 IO 引脚:
  • 如果该 IO 输出 低电平

    • LED 正极端通过三极管接到电源,负极被拉低形成电位差 → LED 点亮

  • 如果该 IO 输出 高电平

    • 两端电位基本一致,没有有效电压 → LED 熄灭

因此:
  • 对单个 LED 来说,是典型的“低电平点亮,高电平熄灭”控制方式。


(三)功能设计示例1. 点亮单个 LED 的示例代码需求:使用库函数初始化相关 IO,引脚 P4.5 控制整组 LED 供电,引脚 P2.7 控制单颗 LED 的亮灭(低亮高灭)。
  1. #include "config.h"
  2. #include "GPIO.h"
  3. #include "delay.h"
  4. void GPIO_config(void)
  5. {
  6.     GPIO_InitTypeDef GPIO_InitStructure;     // 结构体定义
  7.     // LED 单灯负极:P2.7
  8.     GPIO_InitStructure.Pin  = GPIO_Pin_7;    // 指定要初始化的 IO
  9.     GPIO_InitStructure.Mode = GPIO_PullUp;   // GPIO_PullUp, GPIO_HighZ, GPIO_OUT_OD, GPIO_OUT_PP
  10.     GPIO_Inilize(GPIO_P2, &GPIO_InitStructure); // 初始化 P2.7
  11.     // LED 总开关:P4.5(控制三极管 B 极)
  12.     GPIO_InitStructure.Pin  = GPIO_Pin_5;    // 指定要初始化的 IO
  13.     GPIO_InitStructure.Mode = GPIO_OUT_PP;   // 推挽输出:GPIO_OUT_PP
  14.     GPIO_Inilize(GPIO_P4, &GPIO_InitStructure); // 初始化 P4.5
  15. }
  16. int main(void)
  17. {
  18.     // GPIO 初始化
  19.     GPIO_config();
  20.     // 打开 LED 总开关(三极管导通)
  21.     P45 = 0;     // 低电平让 PNP 三极管导通
  22.     while (1)
  23.     {
  24.         // 单灯控制:低亮高灭
  25.         P27 = 1;             // 熄灭
  26.         delay_ms(250);
  27.         delay_ms(250);
  28.         delay_ms(250);
  29.         delay_ms(250);
  30.         P27 = 0;             // 点亮
  31.         delay_ms(250);
  32.         delay_ms(250);
  33.         delay_ms(250);
  34.         delay_ms(250);
  35.     }
  36.     return 0;
  37. }
复制代码

2. 走马灯 / 流水灯示例需求:使用 8 颗 LED,依次点亮形成“从左到右,再从右到左”的流水灯效果。
宏定义映射
  1. #define LED1    P27
  2. #define LED2    P26
  3. #define LED3    P15
  4. #define LED4    P14
  5. #define LED5    P23
  6. #define LED6    P22
  7. #define LED7    P21
  8. #define LED8    P20
  9. #define LED_SW  P45      // 整组 LED 电源开关
复制代码
  • 通过这些宏,把“LED 编号”映射到具体的 IO 引脚;
  • 直接操作 LED1、LED2 等宏,就相当于操作对应 IO。

GPIO 初始化
  1. void GPIO_config(void)
  2. {
  3.     GPIO_InitTypeDef GPIO_InitStructure;
  4.     // LED 总开关:P4.5 推挽输出
  5.     GPIO_InitStructure.Pin  = GPIO_Pin_5;
  6.     GPIO_InitStructure.Mode = GPIO_OUT_PP;
  7.     GPIO_Inilize(GPIO_P4, &GPIO_InitStructure);
  8.     // P1.4、P1.5:对应 LED3、LED4
  9.     GPIO_InitStructure.Pin  = GPIO_Pin_4 | GPIO_Pin_5;
  10.     GPIO_InitStructure.Mode = GPIO_PullUp;
  11.     GPIO_Inilize(GPIO_P1, &GPIO_InitStructure);
  12.     // P2.0、P2.1、P2.2、P2.3、P2.6、P2.7:对应 LED1、LED2、LED5~LED8
  13.     GPIO_InitStructure.Pin  = GPIO_Pin_0 | GPIO_Pin_1 |
  14.                               GPIO_Pin_2 | GPIO_Pin_3 |
  15.                               GPIO_Pin_6 | GPIO_Pin_7;
  16.     GPIO_InitStructure.Mode = GPIO_PullUp;
  17.     GPIO_Inilize(GPIO_P2, &GPIO_InitStructure);
  18. }
复制代码
主函数与走马逻辑
  1. int main(void)
  2. {
  3.     int i;
  4.     GPIO_config();
  5.     EA = 1;          // 这里打开全局中断虽然本例未用中断,但一般工程习惯会加上
  6.     LED_SW = 0;      // 打开 LED 总开关
  7.     while (1)
  8.     {
  9.         // 从 LED1 → LED8 依次点亮
  10.         for (i = 0; i < 8; i++)
  11.         {
  12.             LED1 = (i == 0) ? 0 : 1;
  13.             LED2 = (i == 1) ? 0 : 1;
  14.             LED3 = (i == 2) ? 0 : 1;
  15.             LED4 = (i == 3) ? 0 : 1;
  16.             LED5 = (i == 4) ? 0 : 1;
  17.             LED6 = (i == 5) ? 0 : 1;
  18.             LED7 = (i == 6) ? 0 : 1;
  19.             LED8 = (i == 7) ? 0 : 1;
  20.             delay_ms(100);
  21.         }
  22.         // 从 LED8 → LED1 反向点亮
  23.         for (i = 7; i >= 0; i--)
  24.         {
  25.             LED1 = (i == 0) ? 0 : 1;
  26.             LED2 = (i == 1) ? 0 : 1;
  27.             LED3 = (i == 2) ? 0 : 1;
  28.             LED4 = (i == 3) ? 0 : 1;
  29.             LED5 = (i == 4) ? 0 : 1;
  30.             LED6 = (i == 5) ? 0 : 1;
  31.             LED7 = (i == 6) ? 0 : 1;
  32.             LED8 = (i == 7) ? 0 : 1;
  33.             delay_ms(100);
  34.         }
  35.     }
  36. }
复制代码
逻辑说明(适合在富文本里配说明文字):
  • 通过循环变量 i 表示“当前应该点亮第几号灯”;
  • 对每一盏灯:

    • 如果当前编号与 i 相等 → 输出 0(低电平 → 点亮);
    • 否则输出 1(高电平 → 熄灭);

  • 第一段 for(i = 0; i < 8; i++):从左往右“扫一遍”;
  • 第二段 for(i = 7; i >= 0; i--):从右往左“扫回来”。


方向1:1→2→3→4→5→6→7→8方向2:8→7→6→5→4→3→2→1
回复

使用道具 举报 送花

  • 打卡等级:偶尔看看I
  • 打卡总天数:18
  • 最近打卡:2025-12-10 00:23:34
已绑定手机

5

主题

15

回帖

117

积分

注册会员

积分
117
发表于 2025-11-20 17:26:36 | 显示全部楼层
震动马达 PWM 控制(PWMB)

一、学习目标
  • 学习在 STC8H 上如何配置 PWMB
  • 掌握 PWMB 的各个关键配置项
  • 学会使用 PWMB 控制 震动马达的强弱(占空比)
  • 理解并掌握 调试 PWM 的基本方法


二、学习内容1. PWMB 的应用场景本例要实现的效果:
通过改变 PWM 占空比,让震动马达从 0% → 100% 循环变化,实现不同强度的震动体验。
核心思路:
  • 使用 STC8H 的 PWMB 模块(这里具体用到 PWM6 通道)。
  • 把 PWM 输出脚接到 震动马达驱动电路(通常是三极管或 MOS 管栅极,不建议 IO 直接推马达)。
  • 通过修改占空比,让马达得到的 平均电压 / 平均功率 变化,从而改变震动强度。


2. 工程依赖文件在自己的工程中,需要把以下库文件拷贝 / 添加进来:
  • STC8H_PWM.h、STC8H_PWM.c
  • NVIC.h、NVIC.c
  • Switch.h

这些文件提供了:
  • PWM 模块的初始化 & 更新函数;
  • 中断控制相关接口(本例中 PWM 中断关闭,但仍需 NVIC 支撑库);
  • 端口复用 / 开关相关的封装(Switch.h)。


3. 主程序 main.c 解析3.1 顶部包含与宏定义
  1. #include "Config.h"
  2. #include "Delay.h"
  3. #include "GPIO.h"
  4. #include "STC8H_PWM.h"
  5. #include "NVIC.h"
  6. #include "Switch.h"
  7. #define MOTOR P01
  8. #define PREQ   1000
  9. #define PERIOD (MAIN_Fosc / PREQ)
复制代码
说明:
  • MOTOR P01:给马达 IO 取别名(本例中最终由 PWM 接管该引脚,手动控制用得少)。
  • PREQ:目标 PWM 频率,这里设为 1000 Hz
  • PERIOD:PWM 的计数周期(计数步数),由主频和目标频率决定:
    PERIOD = MAIN_Fosc / PREQ
    例如 MAIN_Fosc = 24MHz,则 PERIOD = 24000(24MHz / 1kHz)。


3.2 GPIO 配置
  1. void GPIO_config(void) {
  2.     GPIO_InitTypeDef GPIO_InitStructure;     // 结构定义
  3.     GPIO_InitStructure.Pin  = GPIO_Pin_1;    // 指定要初始化的 IO
  4.     GPIO_InitStructure.Mode = GPIO_PullUp;   // GPIO_PullUp, GPIO_HighZ, GPIO_OUT_OD, GPIO_OUT_PP
  5.     GPIO_Inilize(GPIO_P0, &GPIO_InitStructure); // 初始化 P0.1
  6. }
复制代码
  • P0.1 配置成上拉输入/准双向口,后续会由 PWM 模块通过端口复用占用这个脚。
  • 如果你是用外部驱动(如 NPN/MOS 管),这个脚一般接到驱动三极管的基极 / 栅极。


3.3 PWM 配置函数
  1. void PWM_config(void)
  2. {
  3.     PWMx_InitDefine PWMx_InitStructure;
  4.     // 1)配置单个 PWM 通道参数:模式 + 初始占空比 + 输出通道
  5.     PWMx_InitStructure.PWM_Mode       = CCMRn_PWM_MODE1; // PWM 模式 1
  6.     PWMx_InitStructure.PWM_Duty       = 0;               // 初始占空比时间 0(不震动)
  7.     PWMx_InitStructure.PWM_EnoSelect  = ENO6P;           // 选择 ENO6P 输出通道
  8.     PWM_Configuration(PWM6, &PWMx_InitStructure);        // 初始化 PWM6 通道
  9.     // 2)配置 PWMB 公共参数:周期、死区、主输出、计数器使能等
  10.     PWMx_InitStructure.PWM_Period         = PERIOD - 1;  // 周期时间 0~65535
  11.     PWMx_InitStructure.PWM_DeadTime       = 0;           // 死区时间,0 表示无死区
  12.     PWMx_InitStructure.PWM_MainOutEnable  = ENABLE;      // 主输出使能
  13.     PWMx_InitStructure.PWM_CEN_Enable     = ENABLE;      // 计数器使能
  14.     PWM_Configuration(PWMB, &PWMx_InitStructure);        // 初始化 PWMB 通用寄存器
  15.     // 3)PWM6 引脚切换:将 PWM6 输出映射到 P0.1
  16.     PWM6_SW(PWM6_SW_P01); // 还可以选 PWM6_SW_P21、PWM6_SW_P54、PWM6_SW_P75 等
  17.     // 4)关闭 PWMB 中断(本例不需要 PWM 中断)
  18.     NVIC_PWM_Init(PWMB, DISABLE, Priority_0);
  19. }
复制代码
关键点说明:
  • PWM_Mode = CCMRn_PWM_MODE1

    • 常规的 PWM 输出模式:计数向上溢出,输出在比较值前后翻转。

  • PWM_Duty

    • 占空比对应的“计数时间”,范围是 0 ~ PERIOD;
    • 0 表示 0% 占空比(一直关),PERIOD 表示 100% 占空比(一直开)。

  • PWM_EnoSelect = ENO6P

    • 指定使用 PWM6 的正向输出
    • 不同通道(PWM1~PWM8)有不同 可选输出脚(ENO1P, ENO2P…)。

  • PWM_Period = PERIOD - 1

    • 计数从 0 数到 PERIOD - 1 共 PERIOD 个计数周期,对应 1kHz。

  • PWM6_SW(PWM6_SW_P01)

    • 把 PWM6 输出“切换”到 P0.1 这个物理引脚上。


3.4 main 函数:占空比渐变控制
  1. void main() {
  2.     PWMx_Duty duty;
  3.     u8 duty_percent = 0; // 0 - 100,占空比百分比
  4.    
  5.     EAXSFR();        // 允许扩展 SFR 访问(STC8H 特有)
  6.    
  7.     GPIO_config();
  8.     PWM_config();
  9.    
  10.     duty.PWM6_Duty = 0;
  11.     UpdatePwm(PWM6, &duty);  // 先把 PWM6 的占空比清零
  12.     while (1) {
  13.         delay_ms(10);        // 10ms 调一次占空比 → 1s 内扫一遍 0~100
  14.         // 设置占空比(按百分比换算到计数值)
  15.         duty.PWM6_Duty = PERIOD * duty_percent / 100;
  16.         UpdatePwm(PWMB, &duty);  // 更新 PWMB 中 PWM6 通道的占空比
  17.         // 让 duty_percent 从 0 → 100 循环
  18.         duty_percent++;
  19.         if (duty_percent > 100) {
  20.             duty_percent = 0;
  21.         }
  22.     }
  23. }
复制代码
逐条理解:
  • PWMx_Duty duty;

    • 这是一个用于保存各个 PWM 通道占空比的结构体;
    • 此处只用其中的 PWM6_Duty 字段。

  • u8 duty_percent = 0;

    • 用百分数(0~100)来直观控制占空比。

  • duty.PWM6_Duty = PERIOD * duty_percent / 100;

    • 先按百分比算出 比较值
    • 例如 duty_percent = 50,则占空比 = 50%,对应比较值 = PERIOD * 50 / 100。

  • UpdatePwm(PWMB, &duty);

    • 将新占空比写入 PWMB 寄存器并生效。

  • delay_ms(10); duty_percent++

    • 每 10ms 占空比 +1%;
    • 0→100 需要 101 步,约 1 秒左右扫完一遍;
    • 对震动马达来说,会感觉震动强度在约 1 秒内从无到强,再突然归零重复。

如果希望震动更“柔和”,可以:
  • 改成 0→100 再 100→0 的“三角波”变化;
  • 或适当增加步长 / 延长 delay。


三、PWM 调试方法简要说明在控制震动马达前,建议先“对线”确认 PWM 输出是否正确:
  • 用逻辑分析仪 / 示波器测 PWM 脚

    • 将探头接到 P0.1(或你选用的 PWM 输出引脚);
    • 确认输出波形为固定频率(约 1kHz)的方波;
    • 在 duty_percent 不同值下,检查高电平宽度是否随之变化。

  • 确认频率是否合适

    • 对大部分快速响应的震动马达,1kHz 是比较常见的 PWM 频率;
    • 频率太低:会感觉到“间断的敲击感”;
    • 频率太高:IO 上升沿 / 驱动器件开关损耗会增大。

  • 驱动能力与电源注意

    • 马达一般不能直连单片机 IO,要通过:

      • NPN 三极管 + 二极管续流;或
      • N 沟 MOS 管 + 二极管续流;

    • 要确保电源、驱动器件能承受马达启动电流。


回复

使用道具 举报 送花

  • 打卡等级:偶尔看看I
  • 打卡总天数:18
  • 最近打卡:2025-12-10 00:23:34
已绑定手机

5

主题

15

回帖

117

积分

注册会员

积分
117
发表于 2025-11-20 17:31:09 | 显示全部楼层
LED 呼吸灯(PWM 实现,PWMA 版)

一、学习目标
  • 理解 PWM 的基础概念和工作原理
  • 学会在 STC8H 上配置 PWMA
  • 熟悉 PWMA 的关键配置项(周期、占空比、模式、引脚切换等)。
  • 会用 PWMA 控制 LED 亮度,实现“呼吸灯”效果。
  • 掌握用 波形&概念图 来理解和调试 PWM。


二、PWM 基础概念回顾1. 什么是 PWMPWM:Pulse Width Modulation,脉宽调制
核心思想:
频率基本固定,通过改变“每个周期内高电平所占的时间比例”(占空比)来调节“平均输出能量”。
  • 如果控制的是 LED:占空比越大 → 平均电流越大 → 越亮
  • 如果控制的是 电机:占空比越大 → 平均电压越高 → 转得更快/力道更大

所以可以把 PWM 看成一种“用数字信号(高低电平)去模拟模拟量”的手段。

2. STC8H 的 PWM 模块概览STC8H 内部有 8 通道 16 位高级 PWM 定时器,分成两组:
  • PWMA:4 组互补 / 对称 / 死区控制的 PWM,可做电机驱动、互补输出等;
  • PWMB:4 路普通 PWM 输出,通常用来做简单调光、震动马达之类。

两组 PWM 的时钟频率可以 独立配置
数据手册中还有一张 “PWM 通道 vs 引脚” 对照表,你可以在富文本里用表格或图片插入原图。文件里已经给出了精简版表格,这里只强调和呼吸灯相关的一行:
  • 本例使用 PWMA 的 PWM4 通道
  • 选择输出脚:PWM4_SW_P26_P27

    • 即 PWM4P → P2.6,PWM4N → P2.7(本例重点用 P2.7 做 LED1)


三、PWMA 应用:呼吸灯效果设计目标:
  • P2.7(LED1) 做一颗呼吸灯;
  • 通过 PWM4 的占空比从 0% → 100% → 0% 往返变化,模拟 LED “渐亮—渐灭”的呼吸效果。

1. 工程准备在工程中(除了基础库)需要拷贝以下文件:
  • STC8H_PWM.c / STC8H_PWM.h
  • NVIC.c / NVIC.h
  • Switch.h


2. 头文件与宏定义
  1. #include "Config.h"
  2. #include "GPIO.h"
  3. #include "Delay.h"
  4. #include "NVIC.h"
  5. #include "Switch.h"
  6. #include "STC8H_PWM.h"
  7. #define LED_SW  P45
  8. #define LED1    P27
  9. #define LED2    P26
  10. #define LED3    P15
  11. #define FREQ    1000
  12. #define PERIOD  ((MAIN_Fosc / FREQ) - 1)    // 周期
  13. PWMx_Duty dutyA;
复制代码
说明:
  • LED_SW:整组 LED 的电源开关(类似上一节 LED 灯设计中的三极管开关)。
  • LED1/LED2/LED3:对应不同引脚的 LED。这里呼吸灯主要用 LED1 = P27。
  • FREQ = 1000:目标 PWM 频率为 1kHz
  • PERIOD = (MAIN_Fosc / FREQ) - 1:

    • 如果主频是 24MHz,则 PERIOD ≈ 24000 - 1;
    • 对应每秒 1000 个 PWM 周期,即 1kHz。

  • PWMx_Duty dutyA:保存 PWMA 各通道占空比的结构体,本例用其中的 PWM4 字段。


3. GPIO 配置
  1. void GPIO_config(void) {
  2.     GPIO_InitTypeDef GPIO_InitStructure; // 结构定义
  3.    
  4.     // LED_SW 总开关:P4.5
  5.     GPIO_InitStructure.Pin  = GPIO_Pin_5;
  6.     GPIO_InitStructure.Mode = GPIO_OUT_PP;          // 推挽输出
  7.     GPIO_Inilize(GPIO_P4, &GPIO_InitStructure);
  8.     // P2.6 / P2.7:对应 LED2 / LED1,引脚后面会被 PWM4 占用
  9.     GPIO_InitStructure.Pin  = GPIO_Pin_6 | GPIO_Pin_7;
  10.     GPIO_InitStructure.Mode = GPIO_PullUp;          // 上拉 / 准双向
  11.     GPIO_Inilize(GPIO_P2, &GPIO_InitStructure);
  12. }
复制代码
  • P4.5 用来控制 LED 整体电源;
  • P2.7 在本例分配给 PWM4N 用来驱动 LED1 做呼吸灯。


4. PWM 配置(PWMA + PWM4)
  1. void PWM_config(void)
  2. {
  3.     PWMx_InitDefine PWMx_InitStructure;
  4.    
  5.     // 1)配置 PWM4 通道
  6.     PWMx_InitStructure.PWM_Mode      = CCMRn_PWM_MODE2;    // 模式:PWM 模式 2
  7.     PWMx_InitStructure.PWM_Duty      = 0;                  // 初始占空比 0
  8.     PWMx_InitStructure.PWM_EnoSelect = ENO4P | ENO4N;      // 使能 PWM4P 与 PWM4N 输出
  9.     PWM_Configuration(PWM4, &PWMx_InitStructure);
  10.     // 2)配置 PWMA 公共部分
  11.     PWMx_InitStructure.PWM_Period        = PERIOD;         // 计数周期
  12.     PWMx_InitStructure.PWM_DeadTime      = 0;              // 死区时间 0(本例非互补驱动,设 0 即可)
  13.     PWMx_InitStructure.PWM_MainOutEnable = ENABLE;         // 主输出使能
  14.     PWMx_InitStructure.PWM_CEN_Enable    = ENABLE;         // 计数器使能
  15.     PWM_Configuration(PWMA, &PWMx_InitStructure);          // 初始化 PWMA
  16.     // 3)引脚切换:把 PWM4 映射到 P2.6 / P2.7
  17.     PWM4_SW(PWM4_SW_P26_P27); // 可选:PWM4_SW_P16_P17, PWM4_SW_P26_P27, PWM4_SW_P66_P67, PWM4_SW_P34_P33
  18.     // 4)PWMA 中断(本例用不到中断,直接禁用)
  19.     NVIC_PWM_Init(PWMA, DISABLE, Priority_0);
  20. }
复制代码
关键点:
  • CCMRn_PWM_MODE2:PWM 模式 2(和模式 1 的占空比“高电平逻辑”相反)。
  • PWM_EnoSelect = ENO4P | ENO4N:同时启用 PWM4 的正/负两路输出;
  • PWM_Period = PERIOD:周期计数值,用来决定 PWM 基本频率;
  • PWM_MainOutEnable / PWM_CEN_Enable:不开这两个,PWM 不会真正输出。
  • PWM4_SW(PWM4_SW_P26_P27):一定要切到正确的引脚,否则即便 PWM 配置正确,外部也看不到波形。


5. 主函数:实现“呼吸”曲线
  1. void main() {
  2.     char direction = 1;
  3.     u8 duty_percent = 0;  // 0 -> 100
  4.     EAXSFR();             // 扩展寄存器访问使能,必须先开!
  5.     GPIO_config();
  6.     PWM_config();
  7.     EA = 1;
  8.     // 总开关:使能 LED 供电
  9.     LED_SW = 0;
  10.     LED1 = 0; // P2.7 PWM4
  11.     LED2 = 0;
  12.     LED3 = 0;
  13.     // 循环之前,预先设置一次 PWM4 占空比(可选)
  14.     dutyA.PWM4_Duty = PERIOD * duty_percent / 100;
  15.     UpdatePwm(PWM4, &dutyA);
  16.     while (1) {
  17.         duty_percent += direction;
  18.         // 让 duty_percent 在 0 ~ 100 来回往返
  19.         if (duty_percent >= 100) {
  20.             duty_percent = 100;
  21.             direction = -1;
  22.         } else if (duty_percent <= 0) {
  23.             duty_percent = 0;
  24.             direction = 1;
  25.         }
  26.         // 根据百分比更新 PWM4 占空比
  27.         dutyA.PWM4_Duty = PERIOD * duty_percent / 100;
  28.         UpdatePwm(PWM4, &dutyA);
  29.         delay_ms(10);   // 平滑度控制:10ms 调整一次占空比
  30.     }
  31. }
复制代码
逻辑解释(可以在富文本里配几行“伪代码”说明):
  • duty_percent:0~100,代表占空比百分数;
  • direction:

    • 为 +1 时:占空比逐步 递增(LED 渐亮);
    • 为 -1 时:占空比逐步 递减(LED 渐灭)。

  • 每 10ms 更新一次占空比,100 次变化 ≈ 1 秒,感受到一个比较顺滑的“呼吸”过程。


四、PWM 配置进一步理解1. 周期(Period)
  • 系统主频 MAIN_Fosc:表示 1 秒内时钟跳动次数(例如 24MHz → 1 秒 2400 万次)。
  • 我们希望一秒有多少个 PWM 周期?这就是 频率 FREQ,单位 Hz。

公式:
PWM_Period = MAIN_Fosc / FREQ举例:
  • 主频 24MHz,FREQ = 8Hz:

    • PWM_Period = 24,000,000 / 8 = 3,000,000
    • 周期时间 = 1 / 8 = 0.125 秒

本例中:
  1. #define FREQ    1000
  2. #define PERIOD  (MAIN_Fosc / FREQ)
  3. PWMx_InitStructure.PWM_Period = PERIOD - 1;
复制代码
  • 这里的 1000 就是指 每秒 1000 个 PWM 周期,也就是 1kHz;
  • PERIOD - 1 是因为计数器是从 0 开始数(0 ~ PERIOD-1 一共 PERIOD 次)。


2. 占空比(Duty)在“一个周期”的计数过程中,高电平持续的计数步数 / 总步数 = 占空比。
  • 占空比 0%:LED 一直关;
  • 占空比 50%:LED 半亮(肉眼是均匀的中等亮度);
  • 占空比 100%:LED 常亮。

代码里通过:
dutyA.PWM4_Duty = PERIOD * duty_percent / 100;把百分比换成对应的比较值(计数步数)。

3. 模式(PWM Mode)常见模式:
  • CCMRn_FREEZE:冻结输出
  • CCMRn_MATCH_VALID:匹配时输出有效电平
  • CCMRn_MATCH_INVALID:匹配时输出无效电平
  • CCMRn_ROLLOVER:翻转
  • CCMRn_FORCE_INVALID:强制无效
  • CCMRn_FORCE_VALID:强制有效
  • CCMRn_PWM_MODE1:PWM 模式 1
  • CCMRn_PWM_MODE2:PWM 模式 2

实际开发里:
  • 最常用的是 PWM_MODE1 和 PWM_MODE2
  • 二者的“占空比高电平含义”是相反的:

    • 一个是“占空比越大越亮”;
    • 另一个是“占空比越大越暗”。

本例使用 CCMRn_PWM_MODE2,并在软件中让 duty_percent 从 0→100→0 往返,最终实现的是“亮-灭”的呼吸效果。

4. 使能 PWM & 引脚切换 & EAXSFR几个关键配置别忘:
  1. PWMx_InitStructure.PWM_MainOutEnable = ENABLE;   // 主输出
  2. PWMx_InitStructure.PWM_CEN_Enable    = ENABLE;   // 计数器
  3. PWM_Configuration(PWMA, &PWMx_InitStructure);
  4. PWM4_SW(PWM4_SW_P26_P27);            // 把 PWM4 切到 P2.6 / P2.7
  5. EAXSFR();                           // 扩展寄存器访问使能(访问 PWMA 相关寄存器前必须调用)
复制代码
  • PWM_MainOutEnable & PWM_CEN_Enable 没开 → PWM 不工作;
  • PWM4_SW(...) 没配对 → 引脚不输出波形;
  • EAXSFR() 没调用 → PWMA 的扩展 SFR 不能正确访问(STC8 系列特有)。

详细说明可查数据手册:
  • 3.1.2 《外设端口切换控制寄存器 2(P_SW2)》
  • 9.2.8 《扩展 SFR 使能寄存器 EAXFR 的使用说明》


回复

使用道具 举报 送花

  • 打卡等级:偶尔看看I
  • 打卡总天数:18
  • 最近打卡:2025-12-10 00:23:34
已绑定手机

5

主题

15

回帖

117

积分

注册会员

积分
117
发表于 2025-11-26 16:33:56 | 显示全部楼层
下面为你把 《电位器案例(ADC)》 md 文档内容完整改写成 富文本结构化版,可直接粘贴进任何富文本编辑器中使用,已保持逻辑、结构清晰,格式友好阅读,同时保留了你原文的所有技术要点。


电位器案例(ADC 模数转换)⭐⭐
一、学习目标本案例通过滑动变阻器(电位器)模拟不同电压输入,最终实现使用 STC8H 的 ADC 模块读取电压值。通过本节你将掌握:
  • 理解 ADC(模数转换器)基本原理与工作机制
  • 熟悉 STC8H 的 ADC 模块结构与转换流程
  • 掌握 ADC 的硬件电路设计要点
  • 通过代码完成 ADC 采样 → 获取 → 转换为电压值
  • 能够根据测量值推算 电源电压(VCC)
  • 学会 ADC 的常见 调试方法与注意事项

二、案例说明:电位器调节电压1. 目标
  • 手动旋转滑动变阻器,改变输出电压
  • 使用万用表直接测量 P0.5(ADC13 通道) 的电压
  • 使用单片机 ADC 读取并计算对应电压值
2. 电路示意图(此处放你原文里的电路图,可直接插图)
连接说明:
  • 电位器三端:
    • 一端接 3.3V
    • 一端接 GND
    • 中心滑动端输出至 P0.5(ADC13)
  • ADC 负责采样滑动端的电压变化

三、使用万用表测量电压
  • 万用表调至 直流电压档(DCV)
  • 红表笔接 P0.5
  • 黑表笔接 GND
  • 扭动电位器,观察电压变化(一般在 0~3.3V 之间)

四、ADC 基础概念1. ADC 是什么?ADC:Analog to Digital Converter —— 模拟转数字转换器
作用:将连续变化的模拟电压 → 离散的数字量(0~4095)
2. 两个核心概念
  • 采样(Sampling)
    每隔固定时间取一次模拟信号的数值
  • 量化(Quantization)
    将模拟电压转换成 数字编码(通常是二进制)
3. ADC 应用场景
  • 医疗设备(血压计、心电仪)
  • 音频处理(音频数字化)
  • 工业电压、电流采集
  • 环境传感器(光照、温度等)
4. STC8H ADC 资源(重点)STC8H 共 15 个 ADC 通道
通道
引脚
通道
引脚
ADC0
P1.0
ADC8
P0.0
ADC1
P1.1
ADC9
P0.1
ADC2
P5.4
ADC10
P0.2
ADC3
P1.3
ADC11
P0.3
ADC4
P1.4
ADC12
P0.4
ADC5
P1.5
ADC13
P0.5
ADC6
P1.6
ADC14
P0.6
ADC7
P1.7

本案例使用 ADC13(P0.5)

五、ADC 软件实现流程
步骤 1:GPIO 配置为高阻输入ADC 输入脚必须配置为高阻输入(High-Z):
  1. void GPIO_config(void) {
  2.     GPIO_InitTypeDef GPIO_InitStructure;
  3.     GPIO_InitStructure.Pin  = GPIO_Pin_5;     // P0.5
  4.     GPIO_InitStructure.Mode = GPIO_HighZ;     // 高阻输入
  5.     GPIO_Inilize(GPIO_P0, &GPIO_InitStructure);
  6. }
复制代码
目的:
  • 确保引脚不被内部上拉或输出干扰
  • 使外接模拟电压能稳定输入 ADC

步骤 2:ADC 初始化配置
  1. void ADC_config(void)
  2. {
  3.     ADC_InitTypeDef ADC_InitStructure;
  4.     ADC_InitStructure.ADC_SMPduty   = 31;  // 采样时间(不能低于 10)
  5.     ADC_InitStructure.ADC_CsSetup   = 0;   // 通道选择时间
  6.     ADC_InitStructure.ADC_CsHold    = 1;   // 通道保持时间
  7.     ADC_InitStructure.ADC_Speed     = ADC_SPEED_2X1T;   // 工作时钟
  8.     ADC_InitStructure.ADC_AdjResult = ADC_RIGHT_JUSTIFIED; // 右对齐
  9.     ADC_Inilize(&ADC_InitStructure);
  10.     ADC_PowerControl(ENABLE);        // 开启 ADC 电源
  11.     NVIC_ADC_Init(DISABLE, Priority_0); // 不使用中断
  12. }
复制代码
关键解释:
  • ADC_SMPduty:采样时间越长,测量越稳定
  • ADC_SPEED_xx:决定 ADC 工作时钟
  • RIGHT_JUSTIFIED:结果右对齐(即 0~4095 直接读取)

步骤 3:ADC 数据读取与电压换算
  1. result = Get_ADCResult(ADC_CH13);
  2. v = result * 2.5 / 4096;
复制代码
说明:
  • STC8H 的 ADC 精度 = 12 位
  • 数字量范围 = 0 ~ 4095(2¹² = 4096)
  • 基准电压 = Vref+ = 2.5V
因此电压换算公式:
[
电压值 = \frac{ADC值 \times 基准电压}{4096}
]
也就是:
[
V = ADC \times \frac{2.5V}{4096}
]

六、为什么基准电压是 2.5V?(重点)ADC 精度依赖基准电压,ADC_Vref+ 引脚在本开发板上连接一个 精准基准电压芯片 CJ431/CD431,它提供:
稳定的 2.5V 参考电压
如果直接用 3.3V LDO 输出,会:
  • 随温度漂移
  • 负载变化导致波动
  • 测量不准确
因此使用独立参考芯片能显著提升 ADC 精度。

七、反向计算电源电压(VCC)STC8H 内置固定参考电压 1.19V(CH15 通道)。
如果把 Vref+ 接到 VCC,则可通过读取 CH15 推算系统电源电压:
[
VCC = \frac{1.19V \times 4096}{ADC15_Value}
]
用途:
  • 检测电池电量
  • 程序自动判断是否欠压
  • 电源监控系统

八、测量标定方法(提升测量精度)外接一个高精度参考源(如 2.000V),通过 CH15 测量:
[
\frac{x}{V_{ref}} = \frac{ADC15_Value}{4096}
]
即可得出更精确的 Vref,从而提升 ADC 的整体测量精度。

截图202511261632492241.jpg
截图202511261633112115.jpg
截图202511261633221249.jpg
截图202511261633318956.jpg
回复

使用道具 举报 送花

  • 打卡等级:偶尔看看I
  • 打卡总天数:18
  • 最近打卡:2025-12-10 00:23:34
已绑定手机

5

主题

15

回帖

117

积分

注册会员

积分
117
发表于 2025-11-26 16:48:37 | 显示全部楼层
下面为你把 《热敏电阻(NTC)》 md 文件完整改写成 清晰分层的富文本版本,适合直接粘贴到 Word、钉钉文档、飞书文档、WPS、Notion 等富文本编辑器中。内容结构化、美观、可读性强,并保留所有原始技术细节。


热敏电阻(NTC)温度检测案例
一、学习目标通过本案例,你将完成:
  • 理解 NTC 热敏电阻的特性与工作原理
  • 学习使用 ADC 对电压进行采样并推算阻值
  • 借助数学公式推导 R_ntc → 温度值
  • 使用阻值查表获取最终温度
  • 编写可实际运行的 NTC 温度检测驱动程序

二、NTC 热敏电阻基本概念1. NTC 是什么?NTC(Negative Temperature Coefficient):
负温度系数热敏电阻,温度升高 → 阻值降低。
特点:
  • 温度越高,电阻越小
  • 常用于空气温度检测、电池温度检测、充电过温保护等

三、硬件电路结构下图为 NTC + 分压电阻构成的电压分压 ADC 采样结构:

分压规则:
  • 电位点电压随温度变化
  • 单片机 ADC 读取电压
  • 通过公式推算 NTC 阻值
  • 再通过查表确定温度
ADC 通道表(STC8H)
ADC 功能
引脚
ADC0
P1.0
ADC1
P1.1
ADC2
P5.4
ADC3
P1.3
ADC4
P1.4
ADC5
P1.5
ADC6
P1.6
ADC7
P1.7
ADC8
P0.0
ADC9
P0.1
ADC10
P0.2
ADC11
P0.3
ADC12
P0.4
ADC13
P0.5(本例使用)
ADC14
P0.6

四、温度计算流程步骤 1:ADC 获取 NTC 节点电压 V_ntc公式:
[
\frac{V_{ntc}}{2.5} = \frac{ADC_Value}{4096}
]
[
V_{ntc} = 2.5 \times \frac{ADC_Value}{4096}
]
说明:
  • ADC_Value 范围:0 ~ 4096(12bit)
  • 2.5V 为 ADC 基准电压(Vref+)

步骤 2:根据分压关系求出 R_ntc 阻值依据串联分压(电流相等):
[
\frac{V_{ntc}}{R_{ntc}} = \frac{3.3 - V_{ntc}}{R_{10k}}
]
推导公式:
[
R_{ntc} = \frac{V_{ntc} \cdot 10k}{3.3 - V_{ntc}}
]
说明:
  • 10kΩ 为固定分压电阻
  • NTC 阻值随温度变化,用这个公式可精确求出当下阻值

步骤 3:根据阻值查表得温度系统提供了一份从 -55℃ 到 125℃ 的阻值表:
(可在此插入原始完整表格截图)
查表方式:
  • 找到表中与当前阻值最接近的一项
  • 该项在数组中的索引(index)减去 55,就是对应摄氏度
例如:
  • 表中第 60 项是温度 5℃
  • 因为数组第 0 项对应 -55℃
  • 所以:温度 = index - 55

五、完整温度表 temp_table[](C 代码)(已保持原文完整,可直接粘贴到工程内)
  1. u16 code temp_table[]= {
  2.     58354, // -55
  3.     55464, // -54
  4.     52698, // -53
  5.     ...
  6.     32,    // 125
  7. };
复制代码
使用方法:根据 R_ntc 寻找与表中最接近的阻值,索引 -55 即温度值。

六、程序设计1. 创建 NTC.h
  1. #ifndef __NTC_H__
  2. #define __NTC_H__
  3. #include "Config.h"
  4. // 求绝对值
  5. #define abs(x)        ((x > 0) ? (x) : (-(x)))
  6. #define NTC_GPIO                        GPIO_P0
  7. #define NTC_GPIO_PIN            GPIO_Pin_4
  8. #define NTC_ACD_CH                    ADC_CH12
  9. void NTC_init();               // 初始化NTC
  10. int  NTC_get_temperature();    // 获取温度值
  11. #endif
复制代码

2. 创建 NTC.c
  1. #include "NTC.h"
  2. #include "GPIO.h"
  3. #include "ADC.h"
  4. #include "NVIC.h"
  5. #include <stdio.h>
  6. static void GPIO_config(void) {
  7.     GPIO_InitTypeDef GPIO_InitStructure;
  8.     GPIO_InitStructure.Pin  = NTC_GPIO_PIN;
  9.     GPIO_InitStructure.Mode = GPIO_HighZ;
  10.     GPIO_Inilize(NTC_GPIO, &GPIO_InitStructure);
  11. }
  12. /******************* AD配置函数 *******************/
  13. void ADC_config(void)
  14. {
  15.     ADC_InitTypeDef ADC_InitStructure;
  16.     ADC_InitStructure.ADC_SMPduty   = 31;
  17.     ADC_InitStructure.ADC_CsSetup   = 0;
  18.     ADC_InitStructure.ADC_CsHold    = 1;
  19.     ADC_InitStructure.ADC_Speed     = ADC_SPEED_2X1T;
  20.     ADC_InitStructure.ADC_AdjResult = ADC_RIGHT_JUSTIFIED;
  21.     ADC_Inilize(&ADC_InitStructure);
  22.     ADC_PowerControl(ENABLE);
  23.     NVIC_ADC_Init(DISABLE,Priority_0);
  24. }
  25. void NTC_init() {
  26.     GPIO_config();
  27.     ADC_config();
  28. }
  29. static int search_temp(float rst_Rx10){
  30.     int i, min_index = 0;
  31.     float min_diff = abs(rst_Rx10 - temp_table[0]);
  32.     int len = sizeof(temp_table) / sizeof(u16);
  33.     for (i = 1; i < len; i++){
  34.         float diff = abs(rst_Rx10 - temp_table[i]);
  35.         if(diff < min_diff){
  36.             min_diff  = diff;
  37.             min_index = i;
  38.         }
  39.     }
  40.     return min_index;
  41. }
  42. int NTC_get_temperature() {
  43.     u16 adc_value;
  44.     float rst_V;
  45.     float rst_R;
  46.     int rst_T;
  47.     // 获取ADC值
  48.     adc_value = Get_ADCResult(NTC_ACD_CH);
  49.     // 换算成电压
  50.     rst_V = adc_value * 2.5 / 4096;
  51.     // 计算电阻
  52.     rst_R = rst_V * 10 / (3.3 - rst_V);
  53.     // 换算温度
  54.     rst_T = search_temp(rst_R * 100) - 55;
  55.     printf("ADC: %d  V=%.2f  R=%.2f  T=%d℃\n",
  56.         adc_value, rst_V, rst_R, rst_T);
  57.     return rst_T;
  58. }
复制代码

3. 主函数调用
  1. int rst_T;
  2. NTC_init();
  3. rst_T = NTC_get_temperature();
  4. printf("温度:%d℃\n", rst_T);
复制代码

截图202511261647282290.jpg
截图202511261648053231.jpg
截图202511261648243853.jpg
回复

使用道具 举报 送花

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

本版积分规则

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

GMT+8, 2025-12-19 05:28 , Processed in 0.151584 second(s), 73 queries .

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

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