一、学习目标
[*]学会使用逻辑分析仪分析、调试电路中的 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 已经绰绰有余。
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 保存设置。
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"
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
震动马达 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 管 + 二极管续流;
[*]要确保电源、驱动器件能承受马达启动电流。
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 的使用说明》
下面为你把 《电位器案例(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 的整体测量精度。
下面为你把 《热敏电阻(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]