我电容发光了 发表于 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 已经绰绰有余。

我电容发光了 发表于 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 C51
[*]打开 Keil 官方下载页面:
https://www.keil.com/download/product/

[*]在产品列表中选择 C51,进入下载页面。

[*]根据提示完成下载。

2. 安装 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。

注意:字体设置只有在文件编码为 ANSI 或 UTF-8 without signature 时才能生效,这一点可以在富文本中用加粗强调。
4. Keil 配置注释/取消注释快捷键为了提高编码效率,可以给 注释/取消注释 配上快捷键。
大致步骤:

[*]打开 Edit → Configuration → Shortcut Keys(或类似快捷键配置窗口)。

[*]搜索如下命令:


[*]Edit: Advanced: Comment Selection(注释当前选中代码)

[*]Edit: Advanced: Uncomment Selection(取消注释)

[*]分别给它们分配快捷键,例如:


[*]注释:Ctrl + /

[*]取消注释:Ctrl + Shift + /

[*]点击 OK 保存设置。


我电容发光了 发表于 2025-11-17 15:55:14

extern 关键字
一、学习目标
[*]理解 extern 的作用。

[*]掌握 extern 在多文件工程中的基本用法。

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

[*]这里只是“声明”,

[*]真正的“定义和分配内存”在其他源文件中。

所以,当你想在 多个 .c 文件之间共享同一个全局变量或函数 时,就需要用到 extern。
2. extern 用于变量(1)在一个源文件中定义全局变量例如,在 a.c 中:
// a.c
int global_var = 10;   // 定义全局变量(真正的内存分配在这里)(2)在另一个源文件中使用这个变量在 b.c 中,如果要用 global_var,需要先声明:
// b.c
#include <stdio.h>

extern int global_var;   // 告诉编译器:这个变量在别的文件里定义过了

int main() {
    printf("%d\n", global_var);// 正常使用
    return 0;
}这里的 extern int global_var;:

[*]不会再分配一份内存,

[*]只是“借用”了 a.c 里已经定义好的那个全局变量。

3. extern 用于函数函数的情况和变量类似。
(1)在一个源文件中定义函数// add.c
int add(int a, int b) {
    return a + b;
}(2)在另一个源文件中声明并调用// main.c
#include <stdio.h>

extern int add(int a, int b);   // 函数声明

int main() {
    int result = add(1, 2);   // 调用函数
    printf("%d\n", result);
    return 0;
}其实函数原型声明默认就带 extern 的含义,所以你经常只写:
int add(int a, int b);// 和 extern int add(...) 等价4. 把 extern 声明放到头文件中当 同一个变量 / 函数会被多个 .c 文件使用 时,通常的做法是:

[*]在 某一个 .c 文件中做“真正的定义”;

[*]在一个 统一的 .h 头文件中写 extern 声明;

[*]所有需要用到它的 .c 文件,#include 这个头文件。

(1)头文件:声明用 extern// myheader.h
extern int global_var;
extern int add(int a, int b);(2)实现文件:真正定义在一个 .c 里// myimpl.c
#include "myheader.h"

int global_var = 10;   // 定义(只有这一处!)

int add(int a, int b) {// 定义
    return a + b;
}(3)其它文件:只需包含头文件就能用// test.c
#include <stdio.h>
#include "myheader.h"

int main() {
    printf("%d\n", global_var);
    int result = add(1, 2);
    printf("%d\n", result);
    return 0;
}5. 常见注意点(结合实践记忆)下面几点是结合上面内容归纳的小结,方便你在工程里少踩坑:

[*]“定义”只能有一份


[*]int global_var = 10; 在整个工程里只能写在 一个 .c 文件里;

[*]其他文件要用它,写 extern int global_var; 即可。

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


[*]✗ 不推荐:
// myheader.h
int global_var = 10;   // 这样每个包含这个头文件的 .c 都会“各自定义一份”
[*]✔ 正确做法:
<pre><code class="language-c">// myheader.h
extern int global_var;
</code></pre>
<pre><code class="language-c">// myimpl.c
int global_var = 10;</code></pre>
[*]函数声明放头文件,函数定义放 .c 文件


[*]头文件里:函数原型(相当于 extern 声明)

[*]某个 .c 里:真正实现

[*]其它 .c 用这个函数,只需 #include "xxx.h"


我电容发光了 发表于 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 的亮灭(低亮高灭)。
#include "config.h"
#include "GPIO.h"
#include "delay.h"

void GPIO_config(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;   // 结构体定义

    // LED 单灯负极:P2.7
    GPIO_InitStructure.Pin= GPIO_Pin_7;    // 指定要初始化的 IO
    GPIO_InitStructure.Mode = GPIO_PullUp;   // GPIO_PullUp, GPIO_HighZ, GPIO_OUT_OD, GPIO_OUT_PP
    GPIO_Inilize(GPIO_P2, &GPIO_InitStructure); // 初始化 P2.7

    // LED 总开关:P4.5(控制三极管 B 极)
    GPIO_InitStructure.Pin= GPIO_Pin_5;    // 指定要初始化的 IO
    GPIO_InitStructure.Mode = GPIO_OUT_PP;   // 推挽输出:GPIO_OUT_PP
    GPIO_Inilize(GPIO_P4, &GPIO_InitStructure); // 初始化 P4.5
}

int main(void)
{
    // GPIO 初始化
    GPIO_config();

    // 打开 LED 总开关(三极管导通)
    P45 = 0;   // 低电平让 PNP 三极管导通

    while (1)
    {
      // 单灯控制:低亮高灭
      P27 = 1;             // 熄灭
      delay_ms(250);
      delay_ms(250);
      delay_ms(250);
      delay_ms(250);

      P27 = 0;             // 点亮
      delay_ms(250);
      delay_ms(250);
      delay_ms(250);
      delay_ms(250);
    }

    return 0;
}2. 走马灯 / 流水灯示例需求:使用 8 颗 LED,依次点亮形成“从左到右,再从右到左”的流水灯效果。
宏定义映射#define LED1    P27
#define LED2    P26
#define LED3    P15
#define LED4    P14
#define LED5    P23
#define LED6    P22
#define LED7    P21
#define LED8    P20
#define LED_SWP45      // 整组 LED 电源开关
[*]通过这些宏,把“LED 编号”映射到具体的 IO 引脚;

[*]直接操作 LED1、LED2 等宏,就相当于操作对应 IO。

GPIO 初始化void GPIO_config(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;

    // LED 总开关:P4.5 推挽输出
    GPIO_InitStructure.Pin= GPIO_Pin_5;
    GPIO_InitStructure.Mode = GPIO_OUT_PP;
    GPIO_Inilize(GPIO_P4, &GPIO_InitStructure);

    // P1.4、P1.5:对应 LED3、LED4
    GPIO_InitStructure.Pin= GPIO_Pin_4 | GPIO_Pin_5;
    GPIO_InitStructure.Mode = GPIO_PullUp;
    GPIO_Inilize(GPIO_P1, &GPIO_InitStructure);

    // P2.0、P2.1、P2.2、P2.3、P2.6、P2.7:对应 LED1、LED2、LED5~LED8
    GPIO_InitStructure.Pin= GPIO_Pin_0 | GPIO_Pin_1 |
                              GPIO_Pin_2 | GPIO_Pin_3 |
                              GPIO_Pin_6 | GPIO_Pin_7;
    GPIO_InitStructure.Mode = GPIO_PullUp;
    GPIO_Inilize(GPIO_P2, &GPIO_InitStructure);
}主函数与走马逻辑int main(void)
{
    int i;

    GPIO_config();

    EA = 1;          // 这里打开全局中断虽然本例未用中断,但一般工程习惯会加上

    LED_SW = 0;      // 打开 LED 总开关

    while (1)
    {
      // 从 LED1 → LED8 依次点亮
      for (i = 0; i < 8; i++)
      {
            LED1 = (i == 0) ? 0 : 1;
            LED2 = (i == 1) ? 0 : 1;
            LED3 = (i == 2) ? 0 : 1;
            LED4 = (i == 3) ? 0 : 1;
            LED5 = (i == 4) ? 0 : 1;
            LED6 = (i == 5) ? 0 : 1;
            LED7 = (i == 6) ? 0 : 1;
            LED8 = (i == 7) ? 0 : 1;

            delay_ms(100);
      }

      // 从 LED8 → LED1 反向点亮
      for (i = 7; i >= 0; i--)
      {
            LED1 = (i == 0) ? 0 : 1;
            LED2 = (i == 1) ? 0 : 1;
            LED3 = (i == 2) ? 0 : 1;
            LED4 = (i == 3) ? 0 : 1;
            LED5 = (i == 4) ? 0 : 1;
            LED6 = (i == 5) ? 0 : 1;
            LED7 = (i == 6) ? 0 : 1;
            LED8 = (i == 7) ? 0 : 1;

            delay_ms(100);
      }
    }
}逻辑说明(适合在富文本里配说明文字):

[*]通过循环变量 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

我电容发光了 发表于 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 顶部包含与宏定义#include "Config.h"
#include "Delay.h"
#include "GPIO.h"
#include "STC8H_PWM.h"
#include "NVIC.h"
#include "Switch.h"

#define MOTOR P01

#define PREQ   1000
#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 配置void GPIO_config(void) {
    GPIO_InitTypeDef GPIO_InitStructure;   // 结构定义

    GPIO_InitStructure.Pin= GPIO_Pin_1;    // 指定要初始化的 IO
    GPIO_InitStructure.Mode = GPIO_PullUp;   // GPIO_PullUp, GPIO_HighZ, GPIO_OUT_OD, GPIO_OUT_PP
    GPIO_Inilize(GPIO_P0, &GPIO_InitStructure); // 初始化 P0.1
}
[*]把 P0.1 配置成上拉输入/准双向口,后续会由 PWM 模块通过端口复用占用这个脚。

[*]如果你是用外部驱动(如 NPN/MOS 管),这个脚一般接到驱动三极管的基极 / 栅极。

3.3 PWM 配置函数void PWM_config(void)
{
    PWMx_InitDefine PWMx_InitStructure;

    // 1)配置单个 PWM 通道参数:模式 + 初始占空比 + 输出通道
    PWMx_InitStructure.PWM_Mode       = CCMRn_PWM_MODE1; // PWM 模式 1
    PWMx_InitStructure.PWM_Duty       = 0;               // 初始占空比时间 0(不震动)
    PWMx_InitStructure.PWM_EnoSelect= ENO6P;         // 选择 ENO6P 输出通道
    PWM_Configuration(PWM6, &PWMx_InitStructure);      // 初始化 PWM6 通道

    // 2)配置 PWMB 公共参数:周期、死区、主输出、计数器使能等
    PWMx_InitStructure.PWM_Period         = PERIOD - 1;// 周期时间 0~65535
    PWMx_InitStructure.PWM_DeadTime       = 0;         // 死区时间,0 表示无死区
    PWMx_InitStructure.PWM_MainOutEnable= ENABLE;      // 主输出使能
    PWMx_InitStructure.PWM_CEN_Enable   = ENABLE;      // 计数器使能
    PWM_Configuration(PWMB, &PWMx_InitStructure);      // 初始化 PWMB 通用寄存器

    // 3)PWM6 引脚切换:将 PWM6 输出映射到 P0.1
    PWM6_SW(PWM6_SW_P01); // 还可以选 PWM6_SW_P21、PWM6_SW_P54、PWM6_SW_P75 等

    // 4)关闭 PWMB 中断(本例不需要 PWM 中断)
    NVIC_PWM_Init(PWMB, DISABLE, Priority_0);
}关键点说明:

[*]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 函数:占空比渐变控制void main() {
    PWMx_Duty duty;
    u8 duty_percent = 0; // 0 - 100,占空比百分比
   
    EAXSFR();      // 允许扩展 SFR 访问(STC8H 特有)
   
    GPIO_config();
    PWM_config();
   
    duty.PWM6_Duty = 0;
    UpdatePwm(PWM6, &duty);// 先把 PWM6 的占空比清零

    while (1) {
      delay_ms(10);      // 10ms 调一次占空比 → 1s 内扫一遍 0~100

      // 设置占空比(按百分比换算到计数值)
      duty.PWM6_Duty = PERIOD * duty_percent / 100;
      UpdatePwm(PWMB, &duty);// 更新 PWMB 中 PWM6 通道的占空比

      // 让 duty_percent 从 0 → 100 循环
      duty_percent++;
      if (duty_percent > 100) {
            duty_percent = 0;
      }
    }
}逐条理解:

[*]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 管 + 二极管续流;

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


我电容发光了 发表于 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. 头文件与宏定义#include "Config.h"
#include "GPIO.h"
#include "Delay.h"
#include "NVIC.h"
#include "Switch.h"
#include "STC8H_PWM.h"

#define LED_SWP45

#define LED1    P27
#define LED2    P26
#define LED3    P15

#define FREQ    1000

#define PERIOD((MAIN_Fosc / FREQ) - 1)    // 周期

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 配置void GPIO_config(void) {
    GPIO_InitTypeDef GPIO_InitStructure; // 结构定义
   
    // LED_SW 总开关:P4.5
    GPIO_InitStructure.Pin= GPIO_Pin_5;
    GPIO_InitStructure.Mode = GPIO_OUT_PP;          // 推挽输出
    GPIO_Inilize(GPIO_P4, &GPIO_InitStructure);

    // P2.6 / P2.7:对应 LED2 / LED1,引脚后面会被 PWM4 占用
    GPIO_InitStructure.Pin= GPIO_Pin_6 | GPIO_Pin_7;
    GPIO_InitStructure.Mode = GPIO_PullUp;          // 上拉 / 准双向
    GPIO_Inilize(GPIO_P2, &GPIO_InitStructure);
}
[*]P4.5 用来控制 LED 整体电源;

[*]P2.7 在本例分配给 PWM4N 用来驱动 LED1 做呼吸灯。

4. PWM 配置(PWMA + PWM4)void PWM_config(void)
{
    PWMx_InitDefine PWMx_InitStructure;
   
    // 1)配置 PWM4 通道
    PWMx_InitStructure.PWM_Mode      = CCMRn_PWM_MODE2;    // 模式:PWM 模式 2
    PWMx_InitStructure.PWM_Duty      = 0;                  // 初始占空比 0
    PWMx_InitStructure.PWM_EnoSelect = ENO4P | ENO4N;      // 使能 PWM4P 与 PWM4N 输出
    PWM_Configuration(PWM4, &PWMx_InitStructure);

    // 2)配置 PWMA 公共部分
    PWMx_InitStructure.PWM_Period      = PERIOD;         // 计数周期
    PWMx_InitStructure.PWM_DeadTime      = 0;            // 死区时间 0(本例非互补驱动,设 0 即可)
    PWMx_InitStructure.PWM_MainOutEnable = ENABLE;         // 主输出使能
    PWMx_InitStructure.PWM_CEN_Enable    = ENABLE;         // 计数器使能
    PWM_Configuration(PWMA, &PWMx_InitStructure);          // 初始化 PWMA

    // 3)引脚切换:把 PWM4 映射到 P2.6 / P2.7
    PWM4_SW(PWM4_SW_P26_P27); // 可选:PWM4_SW_P16_P17, PWM4_SW_P26_P27, PWM4_SW_P66_P67, PWM4_SW_P34_P33

    // 4)PWMA 中断(本例用不到中断,直接禁用)
    NVIC_PWM_Init(PWMA, DISABLE, Priority_0);
}关键点:

[*]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. 主函数:实现“呼吸”曲线void main() {
    char direction = 1;
    u8 duty_percent = 0;// 0 -> 100

    EAXSFR();             // 扩展寄存器访问使能,必须先开!
    GPIO_config();
    PWM_config();
    EA = 1;

    // 总开关:使能 LED 供电
    LED_SW = 0;
    LED1 = 0; // P2.7 PWM4
    LED2 = 0;
    LED3 = 0;

    // 循环之前,预先设置一次 PWM4 占空比(可选)
    dutyA.PWM4_Duty = PERIOD * duty_percent / 100;
    UpdatePwm(PWM4, &dutyA);

    while (1) {
      duty_percent += direction;

      // 让 duty_percent 在 0 ~ 100 来回往返
      if (duty_percent >= 100) {
            duty_percent = 100;
            direction = -1;
      } else if (duty_percent <= 0) {
            duty_percent = 0;
            direction = 1;
      }

      // 根据百分比更新 PWM4 占空比
      dutyA.PWM4_Duty = PERIOD * duty_percent / 100;
      UpdatePwm(PWM4, &dutyA);

      delay_ms(10);   // 平滑度控制:10ms 调整一次占空比
    }
}逻辑解释(可以在富文本里配几行“伪代码”说明):

[*]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 秒

本例中:
#define FREQ    1000
#define PERIOD(MAIN_Fosc / FREQ)
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几个关键配置别忘:
PWMx_InitStructure.PWM_MainOutEnable = ENABLE;   // 主输出
PWMx_InitStructure.PWM_CEN_Enable    = ENABLE;   // 计数器
PWM_Configuration(PWMA, &PWMx_InitStructure);

PWM4_SW(PWM4_SW_P26_P27);            // 把 PWM4 切到 P2.6 / P2.7

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 的使用说明》


我电容发光了 发表于 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 通道:

通道引脚通道引脚
ADC0P1.0ADC8P0.0
ADC1P1.1ADC9P0.1
ADC2P5.4ADC10P0.2
ADC3P1.3ADC11P0.3
ADC4P1.4ADC12P0.4
ADC5P1.5ADC13P0.5
ADC6P1.6ADC14P0.6
ADC7P1.7

本案例使用 ADC13(P0.5)。
五、ADC 软件实现流程步骤 1:GPIO 配置为高阻输入ADC 输入脚必须配置为高阻输入(High-Z):
void GPIO_config(void) {
    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.Pin= GPIO_Pin_5;   // P0.5
    GPIO_InitStructure.Mode = GPIO_HighZ;   // 高阻输入
    GPIO_Inilize(GPIO_P0, &GPIO_InitStructure);
}目的:

[*]确保引脚不被内部上拉或输出干扰
[*]使外接模拟电压能稳定输入 ADC
步骤 2:ADC 初始化配置void ADC_config(void)
{
    ADC_InitTypeDef ADC_InitStructure;

    ADC_InitStructure.ADC_SMPduty   = 31;// 采样时间(不能低于 10)
    ADC_InitStructure.ADC_CsSetup   = 0;   // 通道选择时间
    ADC_InitStructure.ADC_CsHold    = 1;   // 通道保持时间
    ADC_InitStructure.ADC_Speed   = ADC_SPEED_2X1T;   // 工作时钟
    ADC_InitStructure.ADC_AdjResult = ADC_RIGHT_JUSTIFIED; // 右对齐
    ADC_Inilize(&ADC_InitStructure);

    ADC_PowerControl(ENABLE);      // 开启 ADC 电源
    NVIC_ADC_Init(DISABLE, Priority_0); // 不使用中断
}关键解释:

[*]ADC_SMPduty:采样时间越长,测量越稳定
[*]ADC_SPEED_xx:决定 ADC 工作时钟
[*]RIGHT_JUSTIFIED:结果右对齐(即 0~4095 直接读取)
步骤 3:ADC 数据读取与电压换算result = Get_ADCResult(ADC_CH13);
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 的整体测量精度。

我电容发光了 发表于 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 功能引脚
ADC0P1.0
ADC1P1.1
ADC2P5.4
ADC3P1.3
ADC4P1.4
ADC5P1.5
ADC6P1.6
ADC7P1.7
ADC8P0.0
ADC9P0.1
ADC10P0.2
ADC11P0.3
ADC12P0.4
ADC13P0.5(本例使用)
ADC14P0.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 代码)(已保持原文完整,可直接粘贴到工程内)
u16 code temp_table[]= {
    58354, // -55
    55464, // -54
    52698, // -53
    ...
    32,    // 125
};使用方法:根据 R_ntc 寻找与表中最接近的阻值,索引 -55 即温度值。
六、程序设计1. 创建 NTC.h#ifndef __NTC_H__
#define __NTC_H__

#include "Config.h"

// 求绝对值
#define abs(x)      ((x > 0) ? (x) : (-(x)))

#define NTC_GPIO                        GPIO_P0
#define NTC_GPIO_PIN            GPIO_Pin_4
#define NTC_ACD_CH                  ADC_CH12

void NTC_init();               // 初始化NTC
intNTC_get_temperature();    // 获取温度值

#endif2. 创建 NTC.c#include "NTC.h"
#include "GPIO.h"
#include "ADC.h"
#include "NVIC.h"
#include <stdio.h>

static void GPIO_config(void) {
    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.Pin= NTC_GPIO_PIN;
    GPIO_InitStructure.Mode = GPIO_HighZ;
    GPIO_Inilize(NTC_GPIO, &GPIO_InitStructure);
}

/******************* AD配置函数 *******************/
void ADC_config(void)
{
    ADC_InitTypeDef ADC_InitStructure;

    ADC_InitStructure.ADC_SMPduty   = 31;
    ADC_InitStructure.ADC_CsSetup   = 0;
    ADC_InitStructure.ADC_CsHold    = 1;
    ADC_InitStructure.ADC_Speed   = ADC_SPEED_2X1T;
    ADC_InitStructure.ADC_AdjResult = ADC_RIGHT_JUSTIFIED;

    ADC_Inilize(&ADC_InitStructure);
    ADC_PowerControl(ENABLE);
    NVIC_ADC_Init(DISABLE,Priority_0);
}

void NTC_init() {
    GPIO_config();
    ADC_config();
}

static int search_temp(float rst_Rx10){
    int i, min_index = 0;
    float min_diff = abs(rst_Rx10 - temp_table);
    int len = sizeof(temp_table) / sizeof(u16);

    for (i = 1; i < len; i++){
      float diff = abs(rst_Rx10 - temp_table);
      if(diff < min_diff){
            min_diff= diff;
            min_index = i;
      }
    }
    return min_index;
}

int NTC_get_temperature() {
    u16 adc_value;
    float rst_V;
    float rst_R;
    int rst_T;

    // 获取ADC值
    adc_value = Get_ADCResult(NTC_ACD_CH);

    // 换算成电压
    rst_V = adc_value * 2.5 / 4096;

    // 计算电阻
    rst_R = rst_V * 10 / (3.3 - rst_V);

    // 换算温度
    rst_T = search_temp(rst_R * 100) - 55;

    printf("ADC: %dV=%.2fR=%.2fT=%d℃\n",
      adc_value, rst_V, rst_R, rst_T);

    return rst_T;
}3. 主函数调用int rst_T;

NTC_init();
rst_T = NTC_get_temperature();

printf("温度:%d℃\n", rst_T);
页: 1 [2]
查看完整版本: 学习STC8H8K64U