找回密码
 立即注册
查看: 977|回复: 17

学习STC8H8K64U

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

5

主题

15

回帖

117

积分

注册会员

积分
117
发表于 2025-11-14 15:59:14 | 显示全部楼层 |阅读模式
系统时钟
一、学习目标
  • 了解系统时钟概念
  • 了解时钟周期概念
  • 了解指令周期(机器周期)概念

二、学习内容
(一)时钟与周期
  • 系统时钟
系统时钟是计算机中用于控制各个设备协调工作的“节拍器”。
它是 CPU 的主频,是 CPU 和外设工作的基础,常用单位为 Hz(赫兹),如 1MHz、10MHz 等。
系统时钟的时钟信号通常由晶振提供。
STC8H 单片机支持 外部晶振内部晶振 两种时钟源,可以通过相应配置来选择。
  • 时钟周期
时钟周期是系统时钟完成一个完整周期所需要的时间。
时钟周期的倒数就是时钟频率(每秒的时钟周期数)。
例如:
若 STC8H 时钟频率为 24MHz,则每个时钟周期时间为:
1 / 24MHz ≈ 41.67ns
  • 机器周期(指令周期)
机器周期也叫指令周期,是 CPU 执行一条指令所需的时间。
早期 STC 单片机的机器周期 = 12 个时钟周期。
现在的 STC8H 可以配置为两种模式:1T 模式12T 模式
  • 12T 模式:
    • 当系统时钟为 24MHz 时
    • 每个机器周期时间:12 × 41.67ns ≈ 500ns
  • 1T 模式:
    • 当系统时钟为 24MHz 时
    • 每个机器周期时间:1 × 41.67ns ≈ 41.67ns

(二)NOP 指令
NOP 指令是汇编中的一条指令,全称 “No Operation”,即“不做任何操作”。
特点:
  • 不改变寄存器内容;
  • 不修改存储器数据;
  • 常用于 延时调整代码执行节奏
在大多数处理器中,NOP 指令会被翻译为一条或多条机器指令,从而实现“什么都不干,但占用时间”的效果。
在 STC8H 单片机中:
  • NOP 指令是一条 1 字节长的指令;
  • 不进行任何运算,只消耗一个指令周期。
用途包括:
  • 作为程序中的微小延时单元(可以理解为“睡一个 NOP 指令周期”);
  • 用来填充未使用空间,使程序达到某种对齐要求,提升执行效率。

(三)库函数中的系统时钟配置
在 config.h 中配置系统主时钟频率,例如:
  1. //#define MAIN_Fosc                22118400L        // 定义主时钟
  2. //#define MAIN_Fosc                12000000L        // 定义主时钟
  3. //#define MAIN_Fosc                11059200L        // 定义主时钟
  4. //#define MAIN_Fosc                 5529600L        // 定义主时钟
  5. #define MAIN_Fosc                24000000L        // 定义主时钟
复制代码

根据实际使用的晶振或系统时钟设置 MAIN_Fosc。
注意:
系统时钟配置确定后,烧录软件中的时钟频率设置必须与这里保持一致,否则容易出现各种“诡异问题”(例如波特率不对、延时不准等)。

(四)测试不同时钟下的执行周期
思路:
让程序在一高一低两个电平之间快速翻转,并在中间插入一个指令周期(例如 NOP1),然后用示波器观察引脚电平的高、低电平宽度。切换不同主频,观察输出波形变化,从而直观体会主频不同带来的影响。
示例代码:
  1. #include "config.h"
  2. #include "GPIO.h"
  3. #include "delay.h"
  4. void GPIO_config(void) {
  5.     GPIO_InitTypeDef GPIO_InitStructure;     // 结构定义
  6.     GPIO_InitStructure.Pin  = GPIO_Pin_3;    // 指定要初始化的 IO
  7.     GPIO_InitStructure.Mode = GPIO_PullUp;   // GPIO_PullUp, GPIO_HighZ, GPIO_OUT_OD, GPIO_OUT_PP
  8.     GPIO_Inilize(GPIO_P5, &GPIO_InitStructure); // 初始化
  9. }
  10. int main() {
  11.     GPIO_config();
  12.    
  13.     while (1) {
  14.         P53 = 1;
  15.         NOP1();   // 睡一个指令周期
  16.         P53 = 0;
  17.         //NOP1();
  18.     }
  19. }
复制代码

在不同主频下,用示波器观察 P5.3 引脚高电平和低电平的持续时间。
例如:
  • 24MHz 主频下的波形(此处可在富文本中插入示波器截图)
  • 12MHz 主频下的波形
  • 6MHz 主频下的波形
小结:
  • 主频越高,执行速度越快,同样一段代码用时越短;
  • 主频越高,电磁干扰也越强,设计和布线要求更高,更容易出现信号完整性问题。



回复

使用道具 举报 送花

  • 打卡等级:以坛为家I
  • 打卡总天数:324
  • 最近打卡:2026-02-12 09:05:32

835

主题

1万

回帖

2万

积分

管理员

积分
22170
发表于 2025-11-14 16:11:26 | 显示全部楼层
要 做到 USB不停电下载
要 尝试 图形化配置外设
推荐优先看printf_usb("Hello World !\r\n")及usb不停电下载, 演示视频链接
www.STCAI.com
下载 最新的 AiCube-ISP-V6.96F 或以上版本软件 !
下载 最新的 USB库函数,永远用最新的 USB库函数 !
下载 最新的 用户手册 !

下载 最新的 上机实践指导书 !



上面是 小李 演示:STC8H8K64U, printf_usb("Hello World !\r\n")及usb不停电下载@AiCube之图形化程序自动生成


上面是 小赵 演示:STC8H8K64U, printf_usb("Hello World !\r\n")及usb不停电下载@AiCube之图形化程序自动生成

回复

使用道具 举报 送花

  • 打卡等级:常住居民I
  • 打卡总天数:67
  • 最近打卡:2026-02-10 13:19:40

22

主题

89

回帖

913

积分

版主

积分
913
发表于 2025-11-14 16:30:25 | 显示全部楼层
请您后续打卡在同一帖子下,不同楼层回复,谢谢配合
回复

使用道具 举报 送花

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

5

主题

15

回帖

117

积分

注册会员

积分
117
发表于 2025-11-14 16:36:45 | 显示全部楼层
芯S*** 发表于 2025-11-14 16:30
请您后续打卡在同一帖子下,不同楼层回复,谢谢配合

好的谢谢
回复

使用道具 举报 送花

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

5

主题

15

回帖

117

积分

注册会员

积分
117
发表于 2025-11-14 16:45:12 | 显示全部楼层

GPIO 的理解


一、学习目标

  1. 了解 C51 中 GPIO 的工作模式。
  2. 熟悉芯片手册中 IO 口相关章节的阅读方法。
  3. 掌握如何把手册里的寄存器/模式说明,转换成真正可用的代码。

二、学习内容

(一)电灯案例代码解析

#include "STC8H.H"

int main() {
    P5M0 = 0x00;
    P5M1 = 0x00;

    P53 = 1;
    while(1) {}
}

逐行说明:

  • #include "STC8H.H"
    引入 STC8H 系列专用的头文件,里面定义了各个特殊功能寄存器、位定义等。
  • P5M0 = 0x00;
  • P5M1 = 0x00;
    配置 P5 端口所有引脚为某种工作模式(这里是“准双向口”模式,后面会详细解释)。
  • P53 = 1;
    设置 P5 端口的 3 号引脚(P5.3)为高电平,用来点亮 LED 或控制外接电路。

(二)头文件 STC8H.H

  • 针对 STC8H 系列的寄存器、位地址、特殊功能都在 STC8H.H 中定义。
  • 如果 Keil 没有配置 STC8 环境,工程里是无法直接 #include "STC8H.H" 的。
  • 一般情况下,此文件在 Keil 安装目录下:
    C51\INC\STC
    这个目录下还有其它 STC 系列的头文件,如果你使用其它 STC 芯片,就需要 include 对应型号的头文件。

(三)引脚工作模式

  1. 从手册中获取的信息

通过 STC-ISP 下载 STC8H 用户手册,打开 IO 口章节,可以看到:

  • 一个端口对应 8 个引脚(Pn.0 ~ Pn.7)。
  • 每个端口都有自己的配置寄存器。
  • 不同系列芯片,端口数量不完全相同。
  • 每个引脚可以配置为 4 种不同工作模式。
  1. 四种工作模式(用 PnM1 / PnM0 组合控制)

每个 IO 引脚有两个配置位:PnM1PnM0。两位不同组合,对应不同工作模式:

  • 准双向口(PnM1 = 0,PnM0 = 0)
    • 内部有弱上拉电阻;
    • 既能输入也能输出;
    • 灌电流可达约 20 mA,上拉电流约 270~150 uA。
    • 适合做普通 IO,简单点灯、按键等。
  • 推挽输出(PnM1 = 0,PnM0 = 1)
    • 内部为强上拉+强下拉结构;
    • 可输出较大电流(约 20 mA),但外部一定要有合适的限流电阻;
    • 适合需要较强驱动能力的场景(如直接驱动 LED、小继电器驱动前级等)。
  • 高阻输入(PnM1 = 1,PnM0 = 0)
    • 引脚呈高阻状态;
    • 电流基本不流入、不流出,只用于检测外部电平;
    • 适合作为纯输入端口使用。
  • 开漏输出(PnM1 = 1,PnM0 = 1)
    • 内部上拉电阻断开,仅提供“拉低”能力;
    • 要正确读外部状态或输出高电平,通常需要外接上拉电阻
    • 适合多机总线(如 I²C)、线与逻辑等。
  1. P5.3 不同模式的配置示例
P5M1 &= ~0x08;  P5M0 &= ~0x08; // 准双向口
P5M1 &= ~0x08;  P5M0 |=  0x08; // 推挽输出
P5M1 |=  0x08;  P5M0 &= ~0x08; // 高阻输入
P5M1 |=  0x08;  P5M0 |=  0x08; // 开漏输出

说明:

  • P5 表示端口 5。
  • 0x08 对应二进制 0000 1000,表示第 3 位,即 P5.3。
  • &= ~0x08:把这一位清零。
  • |= 0x08:把这一位置 1。
  1. 引脚编号与掩码值对应关系
  • 0 号引脚 → 0x01
  • 1 号引脚 → 0x02
  • 2 号引脚 → 0x04
  • 3 号引脚 → 0x08
  • 4 号引脚 → 0x10
  • 5 号引脚 → 0x20
  • 6 号引脚 → 0x40
  • 7 号引脚 → 0x80

只要记住这一列映射,就可以按同样方式配置任意端口、任意引脚的工作模式。


(四)软延时(软件延时)

  1. 概念

软延时:通过执行一段“什么实事都不干、只消耗时间”的代码来实现延时。
常用于:

  • 简单点灯/闪烁;
  • 上电初始化等待;
  • 按键消抖等。
  1. 借助 STC-ISP 工具生成延时代码

在 STC-ISP 中,可以根据:

  • 系统频率(主频);
  • 期望的延时时长;
  • 芯片型号(指令集);

自动生成相应的 C 语言延时函数。

要点:

  • 系统频率:必须和你烧录软件里的设置一致,否则延时时间会不准确。
  • 指令集/芯片型号:要选择包含你实际使用的芯片系列,这里用 STC-Y6(兼容 STC8H)。
  • 延时时长:根据实际需要选择,比如 1 秒(1000 ms)。
  1. 让 LED 每隔 1 秒闪烁一次的完整示例
#include "STC8H.H"

void Delay1000ms()      //@11.0592MHz
{
    unsigned char i, j, k;

    i = 57;
    j = 27;
    k = 112;
    do
    {
        do
        {
            while (--k);
        } while (--j);
    } while (--i);
}

int main() {
    P5M1 &= ~0x08;  P5M0 &= ~0x08; // 准双向口
    //P5M1 &= ~0x08;  P5M0 |=  0x08; // 推挽输出
    //P5M1 |=  0x08;  P5M0 &= ~0x08; // 高阻输入
    //P5M1 |=  0x08;  P5M0 |=  0x08; // 开漏输出

    while (1) {
        P53 = 1;        // 开灯
        Delay1000ms();  // 延时 1 秒
        P53 = 0;        // 关灯
        Delay1000ms();  // 再延时 1 秒
    }
}

你可以把被注释掉的几行模式配置依次打开测试,观察在不同模式下点灯有无差别、是否稳定,顺便加深对各种模式的理解。

回复

使用道具 举报 送花

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

5

主题

15

回帖

117

积分

注册会员

积分
117
发表于 2025-11-14 16:52:15 | 显示全部楼层

库函数


一、学习目标

  1. 理解为什么需要学习库函数。
  2. 掌握基于库函数进行开发的大致流程。
  3. 会用常用库函数做 IO 操作(例如点灯、延时)。

二、学习内容

(一)开发过程回顾:以“点灯”为例

典型步骤:

  1. 查看原理图,找到控制 LED 的引脚。
  2. 查芯片手册:
    • 配置该引脚工作模式;
    • 控制该引脚输出高/低电平。

其中:

  • 第 1 步属于 硬件/开发板设计 范畴;
  • 第 2 步是 软件代码编写 范畴。

本质上,我们现在的做法是:

  • 面向芯片手册开发(也叫 面向寄存器开发)。
  • 换芯片就得换:
    • #include <STC8H.H> 这一类头文件;
    • 查新的数据手册,使用新寄存器名、位定义、配置方式。

如果连 STC8H.H 这种厂商提供的头文件都没有:

  • 你就得完全根据数据手册,自己用 sfr / sbit 把寄存器地址、位挨个定义出来,然后再写代码。

下面是一个 不依赖头文件、完全用寄存器定义 的点灯示例:

sfr     P5M1   = 0xC9;
sfr     P5M0   = 0xCA;
sfr     P5     = 0xC8;
sbit    P53    = P5^3;

void Delay1000ms()      //@11.0592MHz
{
    unsigned char i, j, k;

    i = 57;
    j = 27;
    k = 112;
    do
    {
        do
        {
            while (--k);
        } while (--j);
    } while (--i);
}

int main() {
    //P5M1 &= ~0x08;  P5M0 &= ~0x08; // 准双向口
    P5M1 &= ~0x08;  P5M0 |=  0x08; // 推挽输出
    //P5M1 |=  0x08;  P5M0 &= ~0x08; // 高阻输入
    //P5M1 |=  0x08;  P5M0 |=  0x08; // 开漏输出
  
    while (1) {
        P53 = 1;     // 开灯
        Delay1000ms();
        P53 = 0;     // 关灯
        Delay1000ms();
    }
}

说明:

  • sfr 关键字:定义一个 特殊功能寄存器(Special Function Register),如端口寄存器、定时器寄存器等。
  • sbit 关键字:定义寄存器中的某一位,方便按位读写。

通过 sfr / sbit,我们可以:

  • 直接访问和控制单片机内部的各种功能模块;
  • 但完全依赖手册、地址、位定义,工作量大且易出错

(二)使用库函数点灯

  1. 导入库函数文件

从 STC 官方库函数压缩包中,拷贝以下文件到你的工程目录(或某个公共目录):

  • Config.h
  • Type_def.h
  • GPIO.h
  • GPIO.c
  1. 新建工程 & 添加文件
  • 新建 main.c
  • 在工程中把 Config.h / Type_def.h / GPIO.h / GPIO.c 全部加进来;
  • 编译后能看到 GPIO.c 已经在工程文件树中。
  1. main.c 中用库函数控制 LED
#include "Config.h"
#include "GPIO.h"

void Delay500ms()       //@11.0592MHz
{
    unsigned char data i, j, k;

    i = 29;
    j = 14;
    k = 54;
    do
    {
        do
        {
            while (--k);
        } while (--j);
    } while (--i);
}

void GPIO_config() {
    GPIO_InitTypeDef gpioInit;

    gpioInit.Mode = GPIO_OUT_PP;   // 推挽输出
    gpioInit.Pin  = GPIO_Pin_3;    // P5.3
    GPIO_Inilize(GPIO_P5, &gpioInit);
}

void main() {
    //方式一:宏配置准双向
    //P5_MODE_IO_PU(GPIO_Pin_3);

    //方式二:用初始化函数配置推挽输出
    GPIO_config();
  
    while (1) {
        P53 = 1;
        Delay500ms();
      
        P53 = 0;
        Delay500ms();
    }
}

对比“纯寄存器写法”:

  • 现在只需要关心:
    • 我想把哪个 IO 设成什么模式?
    • 调哪个初始化函数?
  • 底层 P5M0 / P5M1 这些寄存器由库函数来配置。

(三)什么是“库函数”?

简单讲:

库函数就是别人已经写好的一组通用功能代码,你只需要 调用,不用自己从零写。

特点:

  1. 简化编程难度
    • 不必记一堆寄存器名、位含义;
    • 避免很多手误、低级错误。
  2. 提高可读性
    • GPIO_Inilize(GPIO_P5, &gpioInit);P5M1 / P5M0 直观多了。
  3. 节省时间
    • 常用功能已经调试好,可直接拿来用。
  4. 更易移植
    • 换同系列/同厂芯片时,库函数接口可以基本不变;
    • 只需替换底层库实现或配置。
  5. 更安全
    • 避免自己写寄存器操作时产生的溢出、死循环等隐蔽 bug。

但也要注意:

  • 对时间极端敏感的场景(比如极限优化中断响应),直接寄存器操作有时更高效
  • 实战里经常是:
    • 大部分用库函数保证效率和可靠性;
    • 少量关键路径用寄存器精调。

(四)使用 Delay 模块做延时

在库函数里,延时也通常封装成一个独立模块,方便复用。

  1. 拷贝延时模块文件

把库里这两个文件拷贝到工程:

  • Delay.c
  • Delay.h
  1. 在需要延时的 C 文件中包含头文件
#include "Delay.h"
  1. 使用库函数进行延时
delay_ms(250);   // 延时 250 ms(这里支持 1~255 ms)

注意:

  • 延时函数内部是按你工程里配置的系统时钟来算时间的;
  • 主频配置要和下载器/ISP 中的主频设置一致,否则延时会不准。

三、面向库函数 vs 面向寄存器

可以简单理解:

  • 面向寄存器开发
    • 查手册 → 记寄存器名和位 → 自己写 sfr / sbit / 位操作。
    • 优点:灵活、精细、极限优化时更适合。
    • 缺点:难度大、易出错、可读性差、移植麻烦。
  • 面向库函数开发
    • 调库里的初始化函数、设置结构体参数;
    • 大部分时候不直接碰寄存器。
    • 优点:简单、易读、可移植性好、开发快。
    • 缺点:极端场景下可能不如手写寄存器“极限压榨”性能。

实际项目里通常是 两者结合

  • 先用库函数快速把功能跑起来;
  • 确认瓶颈后,再在个别关键模块用寄存器级优化。
回复

使用道具 举报 送花

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

5

主题

15

回帖

117

积分

注册会员

积分
117
发表于 2025-11-14 16:54:31 | 显示全部楼层

中断系统 INT

一、学习目标

  1. 理解中断的概念,掌握中断的分类和优先级。
  2. 理解中断的响应机制和处理方法。

二、学习内容

(一)中断的概念

中断系统是为使 CPU 具有对外界紧急事件的实时处理能力而设置的。

当中央处理机 CPU 正在处理某件事情的时候,外界发生了紧急事件请求,要求 CPU 暂停当前的工作,转而去处理这个紧急事件。处理完以后,再回到原来被中断的地方,继续原来的工作,这样的过程称为“中断”。

实现这种功能的部件称为“中断系统”,向 CPU 发出中断请求的请求源称为“中断源”。

微型机的中断系统一般允许多个中断源。当几个中断源同时向 CPU 请求中断、要求为它服务时,就存在一个问题:CPU 优先响应哪一个中断源?

通常根据中断源的重要程度和紧急程度排队,优先处理最紧急事件的中断请求源,即规定每一个中断源有一个优先级别。CPU 总是先响应优先级别最高的中断请求。

当 CPU 正在处理一个中断源请求(执行相应的中断服务程序)的时候,如果又发生了另外一个优先级比它还高的中断源请求,且 CPU 能够:

  1. 暂停当前正在执行的中断服务程序,
  2. 转去处理优先级更高的中断请求,
  3. 处理完以后再回到原来较低优先级的中断服务程序继续执行,

这样的过程称为“中断嵌套”。具有中断嵌套功能的中断系统称为多级中断系统,没有中断嵌套功能的称为单级中断系统。

用户可以通过总中断允许位 EA(IE.7)或相应中断的允许位来屏蔽相应的中断请求,也可以通过打开相应的中断允许位,使 CPU 响应对应的中断请求。

每一个中断源都可以用软件独立控制为“开中断”或“关中断”状态。部分中断的优先级别也可以用软件设置。

规则总结:

  1. 高优先级的中断请求可以打断低优先级的中断。
  2. 低优先级的中断请求不能打断高优先级的中断。
  3. 当两个相同优先级的中断同时产生时,由查询次序决定系统先响应哪个中断。

(此处原文包含中断系统结构相关示意图,可在富文本中插入示意图。)


(二)中断源

能向 CPU 提出中断请求的信号源称为“中断源”。

STC8H 中的中断源种类较多(如外部中断、定时器中断、串口中断等),具体可以参考芯片手册中的中断源表格和功能说明,在富文本中可插入该表格或图片作为辅助理解。


(三)中断寄存器

通过 STC8H 的用户手册可以查询所有与中断相关的寄存器,以及对应的中断请求位信息。

例如:中断允许寄存器 IE、中断优先级寄存器 IP 以及扩展中断控制寄存器等。

相关资料可参考:
STC8H 数据手册(PDF):
http://www.stcmcudata.com/STC8F-DATASHEET/STC8H.pdf


(四)中断函数

在 C 语言中,可以通过关键字 interrupt 定义中断函数。

示例:

void UART1_int (void) interrupt 0
{
}

说明:

  1. UART1_int 是中断函数的名称,可以根据需要自行命名。
  2. interrupt 关键字用于声明当前函数为中断函数。
  3. 0 是中断号(中断入口向量编号),需要根据实际业务、查阅用户手册后确定。

中断函数可以理解为“回调函数”:

  • 函数定义好之后,何时被调用不是由我们在主程序中直接调用决定的,而是由硬件事件触发、由系统自动调用。
  • 我们只需要关心“什么事件会触发这个中断函数”,并在中断函数中编写需要执行的业务逻辑。

(五)验证 UART 串口的中断函数

示例需求:
接收数据时点亮 LED,发送数据时熄灭 LED。

相关寄存器与位定义:

sfr     P5M1   = 0xC9;
sfr     P5M0   = 0xCA;
sfr     P5     = 0xC8;
sbit    P53    = P5^3;

sfr     T2L    = 0xD7;
sfr     T2H    = 0xD6;
sfr     AUXR   = 0x8E;

sfr     IE     = 0xA8;
sbit    EA     = IE^7;
sbit    ES     = IE^4;

sfr     SCON   = 0x98;
sfr     SBUF   = 0x99;
sbit    RI     = SCON^0;
sbit    TI     = SCON^1;

UART 中断服务函数:

void uart_hello(void) interrupt 4 {
    if (RI) {
        // 如果接收标志位 RI 触发了中断,打开灯
        RI = 0;
        P53 = 1;   // 开
    } 

    if (TI) {
        // 如果发送标志位 TI 触发了中断,关掉灯
        TI = 0;
        P53 = 0;   // 关
    }
}

简单延时函数(约 1000 ms,@11.0592MHz):

void Delay1000ms()      //@11.0592MHz
{
    unsigned char i, j, k;

    i = 57;
    j = 27;
    k = 112;
    do
    {
        do
        {
            while (--k);
        } while (--j);
    } while (--i);
}

主函数示例:

int main() {
    // P5.3 推挽输出,用作 LED 指示灯
    P5M1 &= ~0x08;
    P5M0 |=  0x08;

    // 串口 1 配置
    SCON = 0x50;

    // 波特率发生器配置:使用定时器 2
    // 65536 - 11059200 / 115200 / 4 = 0xFFE8
    T2L  = 0xE8;
    T2H  = 0xFF;
    AUXR = 0x15;   // 启动定时器 2,选择波特率时钟等

    // 开总中断与串口中断
    EA = 1;
    ES = 1;

    P53 = 0;       // 初始灯灭

    while (1) {
        // 休眠 1000 ms
        Delay1000ms();

        // 发送一个数据 0x11
        SBUF = 0x11;

        // 将 TI 位置 1(实际上只要给 SBUF 赋值,硬件会自动置位 TI)
        TI = 1;
    }
}

本例中完成的内容包括:

  1. 配置 UART 初始化(包括波特率定时发生器)。
  2. 查询并使用几个关键寄存器地址:如 SBUF、IE 等。
  3. 在中断服务函数中根据接收/发送标志位控制 LED 的亮灭,用于直观验证中断触发情况。
回复

使用道具 举报 送花

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

5

主题

15

回帖

117

积分

注册会员

积分
117
发表于 2025-11-15 16:25:27 | 显示全部楼层
串口通信 UART

一、学习目标
  • 了解串口通信的基本概念。
  • 掌握 STC8H 的串口通信原理。
  • 掌握 STC8H 的串口通信编程套路。
  • 学会使用逻辑分析仪调试串口。

二、学习内容
(一)什么是串口(UART)串口是一种非常常见的通信接口,全名 UART:Universal Asynchronous Receiver/Transmitter(通用异步收发传输器)
特点:
  • 异步:没有单独的时钟线,用约定好的波特率“约定俗成”。
  • 串行:一位一位按顺序发送。
  • 简单可靠:硬件资源少、调试方便,嵌入式里到处都是 UART。
常见用途:
  • 传感器、LCD 模块、WiFi 模块、蓝牙模块、GPS 模块 等通信;
  • 与 PC 进行调试(日志输出、命令交互)。
关键引脚:
  • TXD(Transmit Data):发送数据的引脚;
  • RXD(Receive Data):接收数据的引脚。
工作过程(概念版):
  • 发送端:把要发的数据写入串口发送缓冲区 → 串口硬件按位通过 TXD 发出去;
  • 接收端:通过 RXD 接收到的比特流 → 串口硬件组装成字节 → 放到接收缓冲区 → 软件再去读取。
需要注意:
  • 电平标准要统一
    • 常见有 TTL 电平(0~3.3V / 5V)RS232 电平(±12V 左右)
    • TTL 对接 RS232 必须加电平转换芯片(如 MAX232)。
  • 通信参数必须一致:
    • 波特率(Baud Rate)
    • 数据位(一般 8 位)
    • 校验位(无 / 奇 / 偶)
    • 停止位(一般 1 位)
STC8H 串口引脚分布STC8H 提供 4 组 UART,每组有多组可复用引脚:
[td]
串口
RXD 备选引脚
TXD 备选引脚
UART1
P3.0 / P3.6 / P1.6 / P4.3
P3.1 / P3.7 / P1.7 / P4.4
UART2
P1.0 / P4.6
P1.1 / P4.7
UART3
P0.0 / P5.0
P0.1 / P5.1
UART4
P0.2 / P5.2
P0.3 / P5.3
在富文本里可以插入芯片引脚图,用高亮标出各 UART 的 TXD/RXD 引脚位置。

(二)串口 TTL 通讯协议TTL 串口:指使用 TTL 电平的 UART 通信,电压范围一般为 0~5V 或 0~3.3V。
信号线:
  • TX(输出):本设备发出去的数据;
  • RX(输入):本设备接收进来的数据。
数据传输格式:
  • 采用 异步串行:数据被分成一个个“帧”:
一个典型的数据帧结构:
空闲线(高电平) → 起始位(1 位低电平) → 数据位(5~8 位,一般 8 位) → 校验位(可选) → 停止位(1 或 2 位高电平)
波特率(Baud Rate):
  • 表示每秒传输的比特数,单位 bps;
  • 常见:9600、19200、57600、115200 等;
  • 发送端和接收端必须 波特率一致,否则会出现乱码。
注意事项:
  • 不同设备的 TTL 电平可能是 3.3V 或 5V,混接时要看是否兼容;
  • 波特率越高,传输越快,但线太长 / 干扰太多 / 配置不合理时更容易丢帧。
在富文本中可以插入一张“UART 波形图”,标明起始位、数据位、停止位。

(三)串口转 USB串口转 USB 模块:把 UART(TXD/RXD)转换为 USB,让没有 USB 功能的单片机也能方便连到 PC。
内部结构:
  • 常见芯片:CH340、FT232/FTDI 等;
  • 一侧接 MCU 的 TX/RX,引脚;
  • 另一侧是 USB 插头接 PC;
  • PC 侧通过驱动程序,把它识别成一个“虚拟串口(COMx)”。
使用步骤:
  • 把 USB 头插到 PC;
  • 安装对应驱动(尤其是 CH340);
  • 串口工具中选择对应 COM 口;
  • 设置波特率等参数,与 MCU 侧一致;
  • 就可以像传统串口一样收发数据。
在富文本中可插入一张“USB 转串口模块照片” + 原理框图,更直观。

(四) STC8H 核心板上的串口调试
  • 原理图要点
    • USB 口(D+ / D-)→ CH340 → MCU 的 P3.0(RXD)/ P3.1(TXD)
    • PC 通过 USB → CH340 → UART1(P3.0/P3.1);
    • 所以 PC 串口工具看到的就是 MCU 的 UART1。
  • 为什么要在 PC 上安装串口驱动?
    • 驱动是给 CH340/FTDI 这类芯片用的;
    • 驱动把它映射成一个标准串口(COMx),串口工具才能识别。

(五)使用库函数写 UART 通信(回显程序)需求:通过 PC 串口助手发送数据到开发板,开发板收到后 原样返回
1. 工程准备
  • 新建工程,创建 main.c。
  • 拷贝并添加下面这些库函数文件到工程:
    • Config.h、Type_def.h
    • GPIO.h、GPIO.c
    • Delay.h、Delay.c
    • UART.h、UART.c、UART_Isr.c
    • NVIC.h、NVIC.c
    • Switch.h

2. 串口发送示例(只发不收)
  1. #include "Config.h"
  2. #include "GPIO.h"
  3. #include "UART.h"
  4. #include "Delay.h"
  5. #include "NVIC.h"
  6. #include "Switch.h"
  7. /************* 功能说明 **************
  8. 双串口全双工中断方式收发通讯程序。
  9. 通过 PC 向 MCU 发送数据, MCU 收到后通过串口把数据原样返回。
  10. 默认参数:115200, N, 8, 1。
  11. 通过在 UART.h 中开启 UART1~UART4 的宏定义,
  12. 可以启用不同通道的串口通信。
  13. ******************************************/
  14. /************* IO 配置 *****************/
  15. void GPIO_config(void) {
  16.     GPIO_InitTypeDef GPIO_InitStructure;
  17.     GPIO_InitStructure.Pin  = GPIO_Pin_0 | GPIO_Pin_1; // P3.0, P3.1
  18.     GPIO_InitStructure.Mode = GPIO_PullUp;
  19.     GPIO_Inilize(GPIO_P3, &GPIO_InitStructure);
  20. }
  21. /************* 串口初始化 *************/
  22. void UART_config(void) {
  23.     COMx_InitDefine COMx_InitStructure;
  24.     COMx_InitStructure.UART_Mode      = UART_8bit_BRTx; // 8位,可变波特率
  25.     COMx_InitStructure.UART_BRT_Use   = BRT_Timer1;     // 波特率发生器
  26.     COMx_InitStructure.UART_BaudRate  = 115200ul;       // 波特率
  27.     COMx_InitStructure.UART_RxEnable  = ENABLE;         // 允许接收
  28.     COMx_InitStructure.BaudRateDouble = DISABLE;        // 不加倍
  29.     UART_Configuration(UART1, &COMx_InitStructure);     // 配置 UART1
  30.     NVIC_UART1_Init(ENABLE, Priority_1);                // 开 UART1 中断,优先级 1
  31.     UART1_SW(UART1_SW_P30_P31); // UART1 使用 P3.0 / P3.1
  32. }
  33. void main(void) {
  34.     GPIO_config();
  35.     UART_config();
  36.     EA = 1; // 打开全局中断
  37.     TX1_write2buff(0x23); // 发送一个字符 '#'
  38.     printf("STC8H8K64U UART1 Test Programme!\r\n");
  39.     PrintString1("STC8H8K64U UART1 Test Programme!\r\n");
  40.     while (1) {
  41.         TX1_write2buff(0x2F); // 发送 '/'
  42.         delay_ms(250);
  43.         delay_ms(250);
  44.         delay_ms(250);
  45.         delay_ms(250);
  46.     }
  47. }
复制代码

3. 串口接收 + 原样回写(回显)
  1. #include "Config.h"
  2. #include "GPIO.h"
  3. #include "UART.h"
  4. #include "Delay.h"
  5. #include "NVIC.h"
  6. #include "Switch.h"
  7. void GPIO_config(void) {
  8.     GPIO_InitTypeDef GPIO_InitStructure;
  9.     GPIO_InitStructure.Pin  = GPIO_Pin_0 | GPIO_Pin_1; // P3.0, P3.1
  10.     GPIO_InitStructure.Mode = GPIO_PullUp;
  11.     GPIO_Inilize(GPIO_P3, &GPIO_InitStructure);
  12. }
  13. void UART_config(void) {
  14.     COMx_InitDefine COMx_InitStructure;
  15.     COMx_InitStructure.UART_Mode      = UART_8bit_BRTx;
  16.     COMx_InitStructure.UART_BRT_Use   = BRT_Timer1;
  17.     COMx_InitStructure.UART_BaudRate  = 115200ul;
  18.     COMx_InitStructure.UART_RxEnable  = ENABLE;
  19.     COMx_InitStructure.BaudRateDouble = DISABLE;
  20.     UART_Configuration(UART1, &COMx_InitStructure);
  21.     NVIC_UART1_Init(ENABLE, Priority_1);
  22.     UART1_SW(UART1_SW_P30_P31);
  23. }
  24. // 收到一批数据后的处理函数:原样回传
  25. void on_uart1_recv(void) {
  26.     u8 i;
  27.     for (i = 0; i < COM1.RX_Cnt; i++) {
  28.         u8 dat = RX1_Buffer[i];
  29.         TX1_write2buff(dat); // 原样发回
  30.     }
  31. }
  32. /**
  33. * 功能:开启串口调试,接收数据并原样返回
  34. */
  35. void main(void) {
  36.     GPIO_config();
  37.     UART_config();
  38.     EA = 1; // 全局中断开关,必须要写!!!
  39.     TX1_write2buff(0x23);
  40.     PrintString1("STC8H8K64U UART1 Test Programme!\r\n");
  41.     printf("STC8H8K64U UART1 Test Programme!\r\n");
  42.     while (1) {
  43.         // 超时计数机制:
  44.         // 一旦收到了一个字节,COM1.RX_TimeOut 会被初始化为一个较小的值(如 5)
  45.         if ((COM1.RX_TimeOut > 0) && (--COM1.RX_TimeOut == 0)) {
  46.             if (COM1.RX_Cnt > 0) {
  47.                 // 一帧数据接收完成,处理之
  48.                 on_uart1_recv();
  49.             }
  50.             // 处理完清零计数
  51.             COM1.RX_Cnt = 0;
  52.         }
  53.         delay_ms(10);
  54.     }
  55. }
复制代码

(六)串口调试的重难点汇总
  • IO 模式配置
    • 对应的 TXD/RXD 引脚必须配置为正确的 GPIO 模式(通常为上拉输入/准双向等);
    • UART1 默认引脚一般是准双向口,可能“勉强能用”,但其他 UART 通道一定要记得配置,不要养成侥幸习惯。
  • UART Mode(UART_Mode)
    • UART_ShiftRight:同步移位模式,按位移,极少用。
    • UART_8bit_BRTx:最常用,8 位数据,可变波特率。
    • UART_9bit:9 位数据,固定波特率。
    • UART_9bit_BRTx:9 位数据 + 可变波特率,常用于带奇偶校验的通信。
  • 波特率(BaudRate)
    • 根据需求选,如 115200 基本够用;
    • 过高会增加丢帧风险,尤其是线长/干扰较大时。
  • 波特率发生器(BRT_Use)
    • 可以选择 Timer1 ~ Timer4 做波特率发生器:
      • BRT_Timer1 / BRT_Timer2 / BRT_Timer3 / BRT_Timer4
    • 注意:UART2 固定使用 Timer2 作为波特率发生器
  • 接收使能(RxEnable)
    • 必须置为 ENABLE,否则只发不收。
  • 波特率加倍(BaudRateDouble)
    • DISABLE:正常波特率;
    • ENABLE:波特率翻倍,容易过高导致丢帧,慎用。
  • 中断配置(Interrupt & Priority)
    • 串口的收发一般伴随 中断
      • 要开启 UART 对应的中断;
      • 还要配置中断优先级(Priority_0 ~ Priority_3)。
  • 端口切换(UARTx_SW / P_SW)
    • 同一个 UART 可以映射到不同引脚上,必须指定使用哪一组:
      • 如:UART1_SW_P30_P31 / UART1_SW_P36_P37 等。
  • 总中断开关 EA
    • 即使单个 UART 中断打开,如果 EA = 0,所有中断仍然失效。
    • 所以:
      • EA = 1; 必须写,重要的事情说三遍
        • 使用 UART 一定要记得打开中断总开关!!!
        • 使用 UART 一定要记得打开中断总开关!!!
        • 使用 UART 一定要记得打开中断总开关!!!
  • printf 输出串口选择
    • 在 UART.h 中:
      #define UART1 1//#define UART2 2//#define UART3 3//#define UART4 4#define PRINTF_SELECT  UART1
    • 确保 UART_Isr.c 已经添加进工程,否则 printf 也可能没输出。

(七)逻辑分析仪调试 UART通过逻辑分析仪同时观察 RXD/TXD 波形,可以非常直观地确认:
  • 数据有没有发出去?
  • 数据格式、波特率是否正确?
  • 原理图分析
    • 找到 UART1 的 TXD / RXD:
      • P3.0:RXD
      • P3.1:TXD
  • 接线方式
    • 逻辑分析仪 CH1 → 开发板 P3.0(RXD)
    • 逻辑分析仪 CH2 → 开发板 P3.1(TXD)
    • 逻辑分析仪 GND → 开发板 GND
  • 准备工作
    • 烧录好“接收后原样返回”的固件;
    • 打开 STC-ISP 串口调试助手:
      • 选对串口号;
      • 设置正确波特率;
      • 输入要发送的内容(例如 hello),可以设置自动周期性发送。
  • 分析步骤
    • 逻辑分析仪软件中选择协议解析:Async Serial;
    • 设置与代码一致的波特率;
    • 采集后:
      • CH1 上可看到 PC → MCU 的 RXD 数据帧;
      • CH2 上可看到 MCU → PC 的 TXD 回发数据帧;
    • 打开解析后的数据视图,可以直接看到 ASCII 内容,比如 hello。

(八)串口通信测试(两块板子互通)测试场景:
  • PC 通过各自的 UART1 和两个开发板通信;
  • 两个开发板之间通过 UART4 互联;
  • 实现:在 PC 的两个串口工具窗口里对着两块板子聊天。
连接方式示意:
  • PC 串口工具 A ↔ 开发板 A 的 UART1(P3.0 / P3.1)。
  • PC 串口工具 B ↔ 开发板 B 的 UART1。
  • 两块板子之间用 UART4 相连:
    • 开发板 A 的 UART4 TXD → 开发板 B 的 UART4 RXD;
    • 开发板 A 的 UART4 RXD ← 开发板 B 的 UART4 TXD;
    • 两块板子 GND 必须相连(共地)。
引脚选择(示例):
  • UART1:RXD = P3.0,TXD = P3.1
  • UART4:RXD = P5.2,TXD = P5.3
代码思路:
  • 板子 A:
    • 从 UART1(PC A)接收 → 转发到 UART4(发给板子 B);
    • 从 UART4 接收 → 转发到 UART1(返回给 PC A)。
  • 板子 B:
    • 逻辑同理,只是对接的是 PC B。
这样就可以在 PC 的两个串口工具窗口中互发消息,验证两块板子的串口链路是否正常。

(九)常见问题与排查
  • 串口窗口出现乱码 可能原因:
    • 烧录时选择的 IRC 频率不是 24.000 MHz,而代码里按照 24MHz 来算波特率;
    • 串口工具中选择的波特率和代码中的不一致(例如代码 115200,工具选了 9600)。
  • 完全没数据 / 发送失败
    • 没有开启对应 UART 中断;
    • 忘了 EA = 1;;
    • IO 没配置成正确模式;
    • 端口复用(UARTx_SW)配置错了引脚;
    • GND 没共地(两个板子或者板子与逻辑分析仪之间)。
  • 两个板子之间通信异常、死循环收发
    • TXD/RXD 接反或接错 UART 通道;
    • 没共地;
    • 两边都在“无脑回显”,容易形成“回声放大”,需要设计好协议或判定条件。

回复

使用道具 举报 送花

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

5

主题

15

回帖

117

积分

注册会员

积分
117
发表于 2025-11-15 16:27:49 | 显示全部楼层
AStyle 代码格式化工具

一、AStyle 是什么?
AStyle(Artistic Style) 是一个开源的自动代码格式化工具,支持多种语言:
  • C / C++ / C# / Objective-C / Objective-C++
  • Java / Pike / 汇编 等

作用:
  • 自动整理缩进、括号位置、空格风格等
  • 统一整个工程的代码风格
  • 提高代码的可读性可维护性

AStyle 可以:
  • 直接在 命令行 使用
  • 也可以集成到 IDE / 编辑器(比如 Keil) 里,一键格式化当前文件或整个工程


二、在 Keil 里集成 AStyle
下面是把 AStyle 挂到 Keil 的 Tools 菜单里的典型做法,你可以在富文本中给每一步配上截图说明:
1. 解压 AStyle
  • 把 AStyle 压缩包解压到某个固定目录,例如:

    • D:\softwares\AStyle\

  • 确认里面能找到可执行文件:

    • bin\AStyle.exe 或类似路径

建议放在一个不会乱动的目录,后面 Keil 的配置需要填绝对路径。

2. 打开 Keil 的 Tools 自定义菜单在 Keil 中:
  • 菜单栏选择:Tools → Customize Tools Menu...
  • 弹出的窗口里可以为每一行定义一个外部工具命令(名称、路径、参数等)。

你可以添加两条命令:
  • 一条:格式化当前文件
  • 一条:格式化当前目录下所有 .c / .h 文件


3. 添加“格式化当前文件”命令在 Customize Tools Menu 窗口中选择一个空行,填入:
  • Menu Command(菜单名)

    • 比如:格式化当前文件

  • Command(程序路径)

    • D:\softwares\AStyle\bin\AStyle.exe
    • 路径按你自己的解压位置填写

  • Arguments(参数)
    常规写法:
    !E推荐写法(避免生成 .orig 备份文件,并使用 4 空格缩进):
    !E -n -s4含义:

    • !E:Keil 的占位符,表示“当前编辑的文件名”;
    • -n:不生成 .orig 备份文件;
    • -s4:缩进使用 4 个空格。

设置完成后,点击 OK 保存。

4. 添加“格式化所有 .c / .h 文件”命令同样在 Customize Tools Menu 里再添加一条:
  • Menu Command

    • 比如:格式化当前目录所有 C 文件

  • Command

    • 指向 AStyle 主程序,例如:
      D:\softwares\AStyle\bin\AStyle.exe或解压目录中的 astyle.exe:
      xxxx\astyle-3.4-x64\astyle.exe其中 xxxx 是你的安装路径。

  • Arguments
    "$E*.c" "$E*.h" -n含义说明:

    • "$E*.c":当前工程目录下所有 .c 文件;
    • "$E*.h":当前工程目录下所有 .h 文件;
    • -n:不生成 .orig 备份文件。


5. 关于 .orig 备份文件AStyle 默认会在格式化前,给原文件做一个备份:
  • 源文件:xxx.c
  • 备份文件:xxx.c.orig

如果你不想看到这些 .orig 文件:
  • 在 Arguments 里追加 -n 参数即可关闭备份功能,例如:

!E -n -s4"$E*.c" "$E*.h" -n
建议刚开始尝试时 不要加 -n,先观察一下 AStyle 的格式化风格,确认没问题再关闭备份。

三、配置快捷键(Keil 内)
为了用得顺手,可以给“格式化当前文件”设置快捷键,比如 Alt + Shift + F:
操作步骤:
  • 菜单:Edit → Configuration...
  • 切换到 Shortcut Keys 选项卡。
  • 找到:

    • Tools: 格式化当前文件(刚才自定义的菜单命令)
    • 给它设置一个快捷键,比如:Alt + Shift + F

顺带可以把常用的注释快捷键也配上:
  • Edit: Advanced: Comment Selection → Ctrl + /
  • Edit: Advanced: Uncomment Selection → Ctrl + Shift + /

设置完成后,点击 OK
这样:
  • 在 Keil 中编辑代码时,按 Alt + Shift + F 就能直接调用 AStyle 格式化当前文件;
  • 选中一段代码再用 Ctrl + / / Ctrl + Shift + / 可以快速注释/取消注释。


四、使用小提示
  • 先备份项目

    • 第一次大规模格式化整个工程前,建议先打一个备份或提交一次 Git。

  • 先少量文件试用

    • 可以先只格式化 1~2 个 .c 文件,看看风格是否符合你的习惯。

  • 统一团队格式

    • 如果你以后和别人协作开发,可以把 AStyle 的参数(如缩进、括号风格)统一约定好,大家用同一套命令行参数。


回复

使用道具 举报 送花

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

5

主题

15

回帖

117

积分

注册会员

积分
117
发表于 2025-11-15 16:49:47 | 显示全部楼层
定时器 Timer(库函数版理解)

一、学习目标
  • 理解 定时器的基本概念
  • 掌握 定时器的常见配置项编程操作套路


二、学习内容
(一)定时器是什么在单片机里,定时器本质是:计数器 + 时钟源
  • 时钟源:来自晶振(系统时钟)或外部脉冲。
  • 计数器:按一定频率“数数”,数到一定值就溢出。
  • 溢出时

    • 可以产生一次 中断
    • 或者在某个引脚上输出脉冲。

常见用途:
  • 按固定时间间隔跑中断(做“系统心跳”)。
  • 测量输入信号周期 / 占空比。
  • 产生固定频率方波 / PWM。

STC8H 中,内置 5 个 16 位定时器:T0、T1、T2、T3、T4。

(二)Timer 实战案例:用定时器控制板载 LED需求:
  • 使用 定时器 0 周期性中断,在中断里翻转 P5.3,实现 LED 闪烁。

1. 完整示例代码
  1. #include "Config.h"
  2. #include "Timer.h"
  3. #include "GPIO.h"
  4. #include "NVIC.h"
  5. void GPIO_config(void) {
  6.     GPIO_InitTypeDef GPIO_InitStructure;     // 结构定义
  7.     GPIO_InitStructure.Pin  = GPIO_Pin_3;    // 指定要初始化的 IO
  8.     GPIO_InitStructure.Mode = GPIO_PullUp;   // GPIO_PullUp, GPIO_HighZ, GPIO_OUT_OD, GPIO_OUT_PP
  9.     GPIO_Inilize(GPIO_P5, &GPIO_InitStructure); // 初始化
  10. }
  11. //int arr[];
  12. //int counter = 3;
  13. void TIMER_config(void) {
  14.     TIM_InitTypeDef TIM_InitStructure;       // 结构定义
  15.     // 定时器 0:16 位自动重装,产生 1000Hz 中断,每 1ms 进一次中断
  16.     TIM_InitStructure.TIM_Mode      = TIM_16BitAutoReload;   // 工作模式: 16 位自动重装
  17.     TIM_InitStructure.TIM_ClkSource = TIM_CLOCK_1T;          // 时钟源: 1T(跟随主频)
  18.     TIM_InitStructure.TIM_ClkOut    = DISABLE;               // 不从 P3.5 输出高速脉冲
  19.     TIM_InitStructure.TIM_Value     = 65536UL - (MAIN_Fosc / 1000UL);
  20.     // 初值计算:希望中断频率为 1000Hz(每秒 1000 次,每次 1ms)
  21.     // 注意:不要小于约 367Hz(2.7ms 周期),也不要大于 1 000 000Hz(1us 周期)
  22.     TIM_InitStructure.TIM_Run       = ENABLE;                // 初始化后立即启动定时器
  23.     Timer_Inilize(Timer0, &TIM_InitStructure);               // 初始化 Timer0(Timer0~Timer4)
  24.     // 打开 Timer0 中断,优先级 Priority_0(最低)
  25.     NVIC_Timer0_Init(ENABLE, Priority_0);
  26. }
  27. void main() {
  28.     GPIO_config();
  29.     TIMER_config();
  30.     EA = 1;          // 开启全局中断
  31.     P53 = 0;         // 初始灭灯
  32.     while (1);
  33. }
复制代码
定时器 0 中断服务函数:
  1. //========================================================================
  2. // 函数: Timer0_ISR_Handler
  3. // 描述: Timer0 中断函数(进中断时硬件已清除标志位)
  4. //========================================================================
  5. void Timer0_ISR_Handler (void) interrupt TMR0_VECTOR {
  6.     // 在此处添加用户代码
  7.     P53 = ~P53;      // 翻转 P5.3,引脚电平取反 → LED 闪烁
  8. }
复制代码

(三)定时器配置项逐条理解1. 工作模式(TIM_Mode)Timer 内部是 16 位计数器(0~65535),不同模式决定计数、重装载方式:
  • 16 位自动重装载模式(最常用)

    • 溢出后,计数器自动装载预设值继续计数;
    • 每次溢出都能产生中断 / 输出信号;
    • 非常适合周期性定时。

  • 16 位不可重装载模式

    • 计数到设定值后停止;
    • 需要重新初始化才能再跑。

  • 8 位自动重装载模式

    • 只用 8 位计数器,周期更短、分辨率更高。

  • 不可屏蔽中断的 16 位自动重装载模式

    • 本质仍是 16 位自动重装载,只是对应的中断更“强”,不容易被屏蔽。

建议:日常绝大部分场景直接用 TIM_16BitAutoReload。

2. 中断配置
定时器不光要“会数数”,还要“会叫你”。
  • NVIC_Timer0_Init(ENABLE, Priority_0);:打开 Timer0 中断 & 设定优先级。
  • EA = 1;:打开 总中断开关

缺一不可:
  • 忘记配 NVIC:定时器内部溢出,但不会进中断函数。
  • 忘记 EA = 1:所有外设中断都被关掉。


3. 时钟源(TIM_ClkSource)常用两种:
  • TIM_CLOCK_1T:

    • 计数频率 = 主频(如 24MHz)。

  • TIM_CLOCK_12T:

    • 计数频率 = 主频 / 12;
    • 相当于在硬件上再做一次 12 分频。

也可以选用外部时钟 TIM_CLOCK_Ext,用于“计数外部脉冲”场景。

4. 是否输出高速脉冲(TIM_ClkOut)
  • DISABLE:只内部计时。
  • ENABLE:在 P3.5 引脚 输出定时器时钟脉冲。

通常调试高频、测真实频率时会打开 ENABLE,然后用示波器量 P3.5。

5. 时钟周期 / 中断频率设置定时器溢出周期由 计数频率装载值共同决定:
计数步数 = 65536 − TIM_Value
溢出周期 T = 计数步数 / 计数频率
例如代码中:
TIM_InitStructure.TIM_Value = 65536UL - (MAIN_Fosc / 10000UL);
  • MAIN_Fosc / 10000UL 表示:

    • 希望定时器 每秒中断 10000 次

  • 于是预装载值 = 65536 −(每次中断要计的步数)。

注意约束:
  • (MAIN_Fosc / Timer频率) 不能大于 65536UL,否则算出的初值为负数(超出 16 位范围)。
  • 理论上可以把 Timer 频率开得很高,但:

    • 24MHz 主频下,1 个时钟周期 ≈ 41.67ns;
    • 一条指令需要多个时钟周期,一段代码需要多条指令;
    • 如果你把 Timer 设成 500000UL(2µs 一次中断) 这种级别,中断函数里几乎什么都干不了。

**经验法则:**中断频率不要开到“极限”,要预留足够执行时间。

6. 启动配置(TIM_Run)TIM_InitStructure.TIM_Run = ENABLE;
  • ENABLE:初始化完立即启动计数。
  • DISABLE:先只配置不启动,需要时再手动开。


回复

使用道具 举报 送花

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

本版积分规则

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

GMT+8, 2026-2-12 18:06 , Processed in 0.124255 second(s), 83 queries .

Powered by Discuz! X3.5

© 2001-2026 Discuz! Team.

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