FT61EC23-RB 从入门到精通:一颗被低估的国产 8 位 MCU

目录

  1. 芯片概览
  2. 硬件架构详解
  3. 引脚功能与最小系统
  4. 时钟系统配置
  5. GPIO 输入输出
  6. 定时器模块
  7. PWM 与增强型 ECCP
  8. ADC 模数转换
  9. 模拟比较器与稳压器
  10. 中断系统
  11. 低功耗与电源管理
  12. EEPROM 数据存储
  13. 开发环境搭建
  14. 实战项目:多合一智能传感器节点
  15. 常见坑与排错指南
  16. 总结与选型建议

一、芯片概览

1.1 它是什么

FT61EC23-RB 是 辉芒微电子(Fremont Micro Devices, FMD) 推出的一款 8 位 RISC 架构 MCU,属于 FT61EC2X 系列。它基于 EEPROM 工艺,与经典的 PIC16 系列指令集高度相似(35 条精简指令),但集成了更丰富的外设,价格却非常亲民。

1.2 核心参数速览

类别参数备注
CPU8-bit RISC,35 条指令2T/4T 可选
最高主频16MHz @ 2T (VDD≥2.7V)最小指令周期 125ns
程序存储器2K × 14-bitFlash,支持读写保护
RAM128 × 8-bit足够中小型应用
数据 EEPROM256 × 8-bit10 万次擦写,>20 年保持
工作电压2.0V ~ 5.5V宽电压,适配电池供电
工作温度-40°C ~ +85°C工业级
GPIO最多 14 个PA0~PA7, PC0~PC5
ADC10-bit,7+1 通道真 10 位精度
PWM5 路(含 1 路 ECCP)支持全桥/半桥/死区
定时器6 个(含 WDT)3 个 12-bit 可在 SLEEP 下运行
模拟比较器2 路16 级可编程参考电压
待机功耗0.8 μA极致低功耗
封装SOP16 / DIP16 / SOP14 / SOP8手工焊接友好
ESD/EFT>4kV / >5.5kV工业级抗干扰

1.3 市场定位与对比

FT61EC23-RB 的定位是低成本、高集成度的 A/D 型 MCU,主要对标:

芯片近似对标FT61EC23-RB 优势
STM8S003F3意法半导体 8 位价格更低,集成 EEPROM
PIC16F684Microchip 8 位指令集兼容,外设更多
N76E003新唐 8051功耗更低,模拟外设更强
HC32F003华大 32 位8 位低功耗场景更适用

一句话定位:当你的项目需要一个可靠打杂、样样都能干功耗极低、价格极低的 MCU 时,FT61EC23-RB 是一个非常务实的选择。


二、硬件架构详解

2.1 芯片内部框图

                    ┌──────────────────────────────────────┐
                    │              FT61EC23-RB              │
    VDD ────────────┤                                       ├──── VSS
                    │  ┌─────────────────────────────────┐  │
    /MCLR ──────────┤  │    8-bit RISC CPU Core          │  │
                    │  │    (35 条指令, 2T/4T)            │  │
    OSC/CLKIN ──────┤  │    8 层硬件堆栈                  │  │
                    │  └───────────┬─────────────────────┘  │
                    │              │ 内部总线                │
                    │  ┌───────────┼─────────────────────┐  │
                    │  │  Flash    │  SRAM    │ EEPROM   │  │
                    │  │  2K×14    │ 128×8    │ 256×8    │  │
                    │  └───────────┴──────────┴──────────┘  │
                    │                                       │
                    │  ┌─────────────────────────────────┐  │
                    │  │           外设矩阵               │  │
                    │  │  ADC   │  PWM×5  │  CMP×2       │  │
                    │  │ 10bit  │(含ECCP)  │  16级VREF    │  │
                    │  │ 7+1CH  │          │              │  │
                    │  ├────────┼──────────┼──────────────┤  │
                    │  │ TMR0×1 │ TMR1×1   │  TMR2×1      │  │
                    │  │ TMR3/4 │ /5 ×3    │  WDT ×1      │  │
                    │  ├────────┼──────────┼──────────────┤  │
                    │  │ HIRC   │ LIRC     │  XT/LP/EC    │  │
                    │  │ 16MHz  │ 32k/256k │  外部时钟     │  │
                    │  └─────────────────────────────────┘  │
                    │                                       │
                    │  ┌─────────────────────────────────┐  │
                    │  │  PORTA(8)    │    PORTC(6)      │  │
                    │  │  PA0 ~ PA7   │    PC0 ~ PC5     │  │
                    │  └─────────────────────────────────┘  │
                    └──────────────────────────────────────┘

2.2 CPU 核心

FT61EC23-RB 采用哈佛架构(程序存储器和数据存储器总线分离),指令集为 RISC,共 35 条指令:

  • 字节操作类:MOVWF, CLRF, CLRW, ADDWF, SUBWF, DECF, INCF, COMF, SWAPF, ANDWF, IORWF, XORWF, RRF, RLF 等
  • 位操作类:BCF, BSF, BTFSC, BTFSS
  • 控制类:CALL, GOTO, RETURN, RETLW, RETFIE, SLEEP, CLRWDT, NOP
  • 立即数与直接寻址:MOVLW, MOVLP

2T 与 4T 时钟模式

  • 2T 模式:1 个指令周期 = 2 个时钟周期,16MHz 下指令周期 = 125ns,性能更高
  • 4T 模式:1 个指令周期 = 4 个时钟周期,16MHz 下指令周期 = 250ns,功耗更低

选择建议:需要高性能选 2T,电池供电选 4T 以降低功耗。

2.3 存储器映射

地址空间 (14-bit 宽)        功能
┌──────────────────────┐
│  0x0000              │ ← 复位向量 (Reset Vector)
│  ─────────────────   │
│  0x0004              │ ← 中断向量 (Interrupt Vector)
│  ─────────────────   │
│  ...    程序存储区    │    2K × 14-bit Flash
│  ─────────────────   │
│  0x07FF              │
└──────────────────────┘

地址空间 (8-bit 宽)         功能
┌──────────────────────┐
│  0x00 ~ 0x7F         │  Bank0: 通用 RAM + SFR
│  0x80 ~ 0xFF         │  Bank1: 映射区 (SFR)
└──────────────────────┘

三、引脚功能与最小系统

3.1 SOP16 封装引脚图

                    ┌───◁▷───┐
            VDD ────┤1      16├──── VSS
         RA5/OSC ────┤2      15├──── RA0/AN0/ICSPDA
       RA4/OSC2 ────┤3      14├──── RA1/AN1/ICSPCLK
    RA3//MCLR/VPP ────┤4      13├──── RA2/AN2/T0CKI/INT
            RC5 ────┤5      12├──── RC0/AN4/C2IN+
            RC4 ────┤6      11├──── RC1/AN5/C1IN-
            RC3 ────┤7      10├──── RC2/AN6/C1IN+
            RC6 ────┤8       9├──── RB3/AN3/C1OUT
                    └─────────┘

注:以上为典型引脚分布,具体封装请以官方数据手册为准。不同封装(SOP14/DIP16/SOP8)引脚排列不同。

3.2 引脚复用功能表

引脚主要功能复用功能 1复用功能 2复用功能 3
RA0I/OAN0 (ADC 通道 0)ICSPDA (编程数据)
RA1I/OAN1 (ADC 通道 1)ICSPCLK (编程时钟)
RA2I/OAN2 (ADC 通道 2)T0CKI (TMR0 外部时钟)INT (外部中断)
RA3I/O/MCLR (复位)VPP (编程高压)
RA4I/OOSC2 (晶振输出)
RA5I/OOSC1/CLKIN (晶振/外部时钟)
RC0I/OAN4 (ADC 通道 4)C2IN+ (比较器 2 正输入)
RC1I/OAN5 (ADC 通道 5)C1IN- (比较器 1 负输入)
RC2I/OAN6 (ADC 通道 6)C1IN+ (比较器 1 正输入)
RC3I/OAN3 (ADC 通道 3)C1OUT (比较器 1 输出)

3.3 最小系统电路

         +3.3V / 5V
              │
              ├────────────────────────────┐
              │                            │
             ┌┴┐ 10kΩ                     │
             │ │                          │
             └┬┘                          │
              │        ┌──────────────────┤
              ├────────┤4  /MCLR     VDD ├──1──┬── +3.3V/5V
              │        │                  │     │
             ─┴┐       │                  │    ─┴┐
         100nF───      │   FT61EC23-RB    │  100nF───
             ─┬┘       │                  │    ─┬┘
              │        │                  │     │
              ├────────┤16 VSS        OSC ├──2──┘ (悬空,使用内部 HIRC)
              │        │                  │
             ═╧═       └──────────────────┘
             GND

最小系统清单

元件参数说明
/MCLR 上拉电阻10kΩ接 VDD,防止意外复位
VDD 去耦电容100nF (104)必须紧贴芯片 VDD/VSS 引脚
调试/编程接口PGD + PGC接 FMD-Link 下载器

⚠️ 关键提醒:烧录失败 90% 是因为 /MCLR 被外部电路拉低。烧录前务必断开外围电路对 /MCLR 的影响!


四、时钟系统配置

4.1 时钟源总览

FT61EC23-RB 提供了四种时钟源:

┌──────────────────────────────────────────────┐
│                  时钟系统                     │
│                                              │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐   │
│  │  HIRC    │  │  LIRC    │  │  XT/LP   │   │
│  │ 16MHz    │  │ 32kHz or │  │ 外部晶振  │   │
│  │ ±2%      │  │ 256kHz   │  │          │   │
│  └────┬─────┘  └────┬─────┘  └────┬─────┘   │
│       │             │             │         │
│       │    ┌────────┴────────┐    │         │
│       │    │  外部时钟 EC    │    │         │
│       │    └────────┬────────┘    │         │
│       │             │             │         │
│       └─────────────┼─────────────┘         │
│                     │                        │
│              ┌──────┴──────┐                 │
│              │   OSCCON    │                 │
│              │  (时钟控制)  │                 │
│              └──────┬──────┘                 │
│                     │                        │
│              ┌──────┴──────┐                 │
│              │  分频器      │                 │
│              │ ÷1/2/4/8/   │                 │
│              │ 16/32/64    │                 │
│              └──────┬──────┘                 │
│                     │                        │
│              系统时钟 SYSCLK                 │
└──────────────────────────────────────────────┘
时钟源频率精度启动时间功耗适用场景
HIRC16MHz±2% (25°C)~μs 级较高主时钟,高速运行
LIRC32kHz / 256kHz较低~μs 级极低SLEEP 唤醒定时、WDT
XT取决于晶振取决于晶振~ms 级中等需要高精度定时的场景
LP32.768kHz取决于晶振~ms 级RTC 时钟
EC外部输入取决于外部取决于外部取决于外部外部时钟源

4.2 OSCCON 寄存器

OSCCON 寄存器 (地址 0x8C)
┌──────┬──────┬──────┬──────┬──────┬──────┬──────┬──────┐
│ bit7 │ bit6 │ bit5 │ bit4 │ bit3 │ bit2 │ bit1 │ bit0 │
│ COSC │      IRCF[2:0]      │OSTS/HTS│     SCS[1:0]     │
└──────┴──────┴──────┴──────┴──────┴──────┴──────┴──────┘
  • COSC (bit7):当前振荡器状态(只读),1=HIRC, 0=其他
  • IRCF[2:0] (bit6:4):HIRC 频率选择,000=16MHz, 001=8MHz, 010=4MHz, 011=2MHz, 100=1MHz, 101=500kHz, 110=250kHz, 111=125kHz
  • SCS[1:0] (bit1:0):系统时钟选择,00=配置字决定, 01=LIRC, 10=HIRC, 11=保留

4.3 时钟配置示例

// === 场景1: 高速运行 (16MHz HIRC,不分频) ===
// 在 IDE Options 中将 TSEL 设为 2T
// OSCCON 使用默认值 (IRCF = 000 → 16MHz)

// === 场景2: 低功耗运行 (32kHz LIRC) ===
OSCCON = 0B00000001;  // SCS = 01 → LIRC, IRCF 无效
// 指令周期 = 2 / 32768 ≈ 61μs (2T 模式)

// === 场景3: 中等速度 (4MHz HIRC) ===
OSCCON = 0B01110010;  // IRCF = 010 → 4MHz, SCS = 10 → HIRC
// 指令周期 = 2 / 4MHz = 500ns (2T 模式)

// === 场景4: 双速启动 (从 LIRC 启动,稳定后切到 HIRC) ===
// 在 IDE Options 中启用双速启动
// 上电后先以 LIRC 运行,HIRC 稳定后自动切换

实用技巧:绝大多数应用使用内部 16MHz HIRC 即可满足需求,无需外部晶振。只有在需要高精度串口通信(如 115200bps 以上)或精确 RTC 时,才考虑使用外部晶振。


五、GPIO 输入输出

5.1 GPIO 相关的寄存器

FT61EC23-RB 的 GPIO 设计继承了 PIC 系列的特点——每个 I/O 功能由多个寄存器协同控制:

寄存器功能备注
TRISx方向控制1=输入, 0=输出
PORTx端口数据读取引脚电平/写入输出锁存
ANSEL模拟/数字选择1=模拟(关闭数字输入), 0=数字
WPUx弱上拉使能1=启用内部弱上拉
ODCONx开漏输出控制1=开漏模式

5.2 GPIO 初始化:一个重要的低功耗陷阱

这是 FT61EC23-RB 开发中最高频的"坑"之一

错误做法:将所有未使用的 IO 都初始化为输出

这样做会导致芯片功耗异常飙升——实际测试中,全部 IO 设为输出后,芯片电流可达 19mA,完全丧失了低功耗优势!

正确做法

// ===== GPIO 初始化最佳实践 =====

void GPIO_Init(void) {
    // 步骤1:先将所有 IO 设为输入(默认上电状态就是输入,但显式设置更安全)
    TRISA = 0xFF;  // PORTA 全部输入
    TRISC = 0xFF;  // PORTC 全部输入

    // 步骤2:将未使用的模拟功能关闭,设为数字模式
    ANSEL = 0x00;  // 全部设为数字口(后续按需开启模拟通道)

    // 步骤3:仅将实际需要输出的引脚设为输出
    // 例如:PA4 驱动 LED,PC0 驱动继电器
    TRISA4 = 0;    // PA4 → 输出
    TRISC0 = 0;    // PC0 → 输出

    // 步骤4:输入引脚按需配置上拉
    // 例如:PA2 接按键,配置内部弱上拉
    WPUA2 = 1;     // 使能 PA2 弱上拉
}

// 经过以上配置,未使用 IO 保持高阻输入状态,芯片电流可降至 1~2mA(16MHz 运行)

5.3 GPIO 基本操作示例

#include "SYSCFG.h"

#define LED_PIN     RA4
#define BUTTON_PIN  RA2
#define RELAY_PIN   RC0

void main(void) {
    // === GPIO 初始化 ===
    TRISA4 = 0;     // LED 输出
    TRISC0 = 0;     // 继电器输出
    ANSELA4 = 0;    // 数字口
    ANSELC0 = 0;

    TRISA2 = 1;     // 按键输入
    ANSELA2 = 0;    // 数字口
    WPUA2 = 1;      // 使能弱上拉(按键另一端接地)

    // === 主循环 ===
    while(1) {
        // 读取按键(低电平 = 按下)
        if (BUTTON_PIN == 0) {
            __delay_ms(20);              // 消抖
            if (BUTTON_PIN == 0) {       // 确认按下
                LED_PIN = 1;             // 点亮 LED
                RELAY_PIN = 1;           // 吸合继电器
                while(BUTTON_PIN == 0);   // 等待释放
                LED_PIN = 0;
                RELAY_PIN = 0;
            }
        }
        CLRWDT();  // 喂狗
    }
}

5.4 驱动能力

条件源电流 (Source)漏电流 (Sink)
VDD = 5V, VOL ≤ 0.6V22mA (单引脚)19mA (单引脚)
VDD = 3.3V约 12mA约 10mA
所有端口总电流≤ 90mA

LED 驱动建议串联 220Ω~1kΩ 限流电阻,不要直接驱动大功率负载。驱动继电器或电机时务必使用三极管 / MOS 管扩流。


六、定时器模块

FT61EC23-RB 配备了 6 个定时器,数量和灵活性在同价位芯片中相当突出:

6.1 定时器总览

定时器位宽预分频后分频特色功能
WDT16-bit7-bit独立 LIRC 时钟,防死机
Timer08-bit8-bit外部时钟输入,支持 SLEEP 唤醒
Timer116-bit3-bit门控功能,外部晶振可选
Timer28-bit4-bit4-bit配合 ECCP/PWM 使用
Timer312-bit7-bit8-bitSLEEP 下可运行!
Timer412-bit7-bit8-bitSLEEP 下可运行!
Timer512-bit7-bit8-bitSLEEP 下可运行!

亮点:Timer3/4/5 可以在 SLEEP 模式下继续运行,这对于需要周期性唤醒采样的电池供电设备(如传感器节点)非常有用。

6.2 Timer0 配置与定时计算

Timer0 相关寄存器:
  OPTION (0x81):  bit5=T0CS, bit3=PSA, bit[2:0]=PS[2:0]
  TMR0 (0x01):    计数值
  INTCON (0x0B):  TMR0IF 中断标志

定时时间计算公式:
  T = TMR0_val × 预分频值 × T_指令周期

  其中: T_指令周期 = TSEL / F_SYSCLK
       2T模式 T_指令周期 = 2 / 16MHz = 125ns
       4T模式 T_指令周期 = 4 / 16MHz = 250ns

示例:16MHz 主频,2T 模式,实现 1ms 定时

// 目标:1ms 定时中断
// T_指令周期 = 2/16MHz = 125ns
// 每 125ns,TMR0 递增 1(分频后)
// 选择预分频 = 8 (OPTION PS[2:0] = 010)
// 每 (125ns × 8) = 1μs,TMR0 递增 1
// 1ms = 1000μs → TMR0 需要计数值 = 256 - 250 = 6
// 因为 TMR0 从 6 计到 256 溢出,共 250 个计数 = 250 × 1μs = 250μs??
// 
// 重新计算:256 级 × 1μs = 256μs,不够 1ms
// 换预分频 = 64 (PS[2:0] = 101)
// 每 (125ns × 64) = 8μs,TMR0 递增 1
// 256 级 × 8μs = 2048μs ≈ 2ms,不够精确
// 
// 方案:预分频 = 32,TMR0 初值 = 6
// 每 (125ns × 32) = 4μs
// (256 - 6) × 4μs = 1000μs = 1ms ✓

void Timer0_Init(void) {
    OPTION = 0B00000100;  // T0CS=0(内部时钟), PSA=0(预分频给TMR0), PS=100(分频32)
    TMR0 = 6;             // 初值, 溢出周期 = (256-6)*32*125ns = 1ms
    TMR0IF = 0;           // 清除中断标志
    TMR0IE = 1;           // 使能中断
    GIE = 1;              // 开全局中断
}

// 中断服务函数
void interrupt ISR(void) {
    if (TMR0IF) {
        TMR0IF = 0;       // 清标志
        TMR0 = 6;         // 重装初值
        // 1ms 定时任务放这里
    }
}

6.3 Timer3/4/5 与 SLEEP 下工作

Timer3/4/5 是 FT61EC23-RB 的一大特色——它们能在 SLEEP 模式下继续由 LIRC 驱动计数,配合 WDT 可实现极低功耗的周期性唤醒采样。

// === Timer3 SLEEP 唤醒定时示例 ===

void Timer3_SleepWakeup_Init(void) {
    // Timer3 时钟源选 LIRC (32kHz)
    // 预分频 = 128, 后分频 = 256
    // 定时周期 = (1/32768) × 4096 × 128 × 256 ≈ 4096 秒??
    // 
    // 实际需要短一些的周期进行传感器采样
    // 选择预分频 = 1, 后分频 = 1
    // Timer3 为 12-bit = 4096 级
    // 周期 = (1/32768) × 4096 × 1 × 1 ≈ 125ms
    
    T3CON = 0B10000000;   // 使能 Timer3, LIRC 时钟
    // 配置预分频和后分频...
    TMR3IE = 1;           // 使能 Timer3 中断
}

// 主循环:周期性唤醒采样
void main(void) {
    Timer3_SleepWakeup_Init();
    GIE = 1;
    
    while(1) {
        SLEEP();           // 进入睡眠,0.8μA 功耗
        // Timer3 溢出后将 MCU 唤醒
        
        // 唤醒后执行采样任务
        ADC_Sample();
        Process_Data();
        // ... 任务完成后再次 SLEEP
    }
}

七、PWM 与增强型 ECCP

7.1 PWM 资源总览

FT61EC23-RB 的 PWM 模块分为两类:

PWM 类型数量分辨率特色
独立 PWM (基于 TMR3/4/5)3 路12-bitSLEEP 下可运行,独立周期/占空比/极性,蜂鸣器模式
增强型 ECCP1 路 (6 输出)10-bit半桥/全桥,互补输出+死区,自动关断,单脉冲

7.2 ECCP 工作模式详解

ECCP (Enhanced Capture/Compare/PWM) 是 PWM 的高级形态,支持以下模式:

┌─────────────────────────────────────────────┐
│               ECCP 工作模式                  │
│                                              │
│  单 PWM 模式                                 │
│  ┌──────┐                                   │
│  │ P1A  │──── 单一 PWM 输出                  │
│  └──────┘                                   │
│                                              │
│  半桥模式 (Half-Bridge)                      │
│  ┌──────┐         ┌──────┐                  │
│  │ P1A  │────     │ P1B  │────               │
│  └──────┘         └──────┘                  │
│  互补输出 + 死区                              │
│                                              │
│  全桥模式 (Full-Bridge)                      │
│  ┌──────┐     ┌──────┐                      │
│  │ P1A  │──── │ P1C  │──── 正向              │
│  └──────┘     └──────┘                      │
│  ┌──────┐     ┌──────┐                      │
│  │ P1B  │──── │ P1D  │──── 反向              │
│  └──────┘     └──────┘                      │
│  支持方向控制和同步整流                         │
└─────────────────────────────────────────────┘

7.3 ECCP PWM 配置流程

这是一个完整的 PWM 配置示例,每一步都有明确的解释:

#include "SYSCFG.h"

// === ECCP 单路 PWM 输出 (P1A) 配置 ===
// 目标:10kHz PWM, 50% 占空比,P1A 引脚输出

void ECCP_PWM_Init(void) {
    // ===== 步骤1: 禁止 PWM 引脚输出(安全初始化)=====
    TRISC5 = 1;          // P1A/RC5 先设为输入

    // ===== 步骤2: 设置 PWM 周期 =====
    // PWM 频率 = F_OSC / (4 × (PR2 + 1) × T2_Prescaler)
    // 目标 10kHz: PR2 + 1 = 16MHz / (4 × 10kHz × 1) = 400
    // PR2 = 399 = 0xFE (8-bit 只能到 255!)
    //
    // 需要加预分频:
    // T2_Prescaler = 4: PR2 + 1 = 16MHz / (4 × 10kHz × 4) = 100
    // PR2 = 99 = 0x63 ✓
    PR2 = 99;

    // ===== 步骤3: 设置占空比 =====
    // 10-bit 占空比: CCPR1L (高8位) + DC1B[1:0] (低2位)
    // 50% 占空比 = (PR2 + 1) × 4 × 50% = 100 × 4 × 0.5 = 200
    // 200 = 0b11001000
    //   CCPR1L = 0b110010 (高6位) = 50 → 实际是 200>>2 = 50
    //   DC1B = 0b00 (低2位)
    CCPR1L = 50;
    CCP1CONbits.DC1B = 0;  // 低 2 位

    // ===== 步骤4: 配置 CCP1 为 PWM 模式 =====
    CCP1CONbits.CCP1M = 0b1100;  // PWM 模式, P1A 高有效

    // ===== 步骤5: 配置 Timer2 并启动 =====
    T2CONbits.T2CKPS = 0b01;     // 预分频 = 4
    TMR2IF = 0;                   // 清除标志
    T2CONbits.TMR2ON = 1;         // 启动 Timer2

    // ===== 步骤6: 等待新 PWM 周期后使能输出 =====
    while (!PIR1bits.TMR2IF);     // 等待 Timer2 溢出
    TRISC5 = 0;                   // 使能 P1A 输出
}

// === 带死区的半桥 PWM 配置 ===
void ECCP_HalfBridge_Init(void) {
    TRISC5 = 1;  // P1A
    TRISC4 = 1;  // P1B

    // 周期设置
    PR2 = 99;          // 10kHz

    // 占空比
    CCPR1L = 50;       // 50%
    CCP1CONbits.DC1B = 0;

    // PWM 模式 + 半桥
    CCP1CONbits.CCP1M = 0b1100;  // PWM
    CCP1CONbits.P1M   = 0b00;    // 单输出 (后续可改半桥)

    // 死区时间设置
    // 死区 = DEADTIME × T_OSC × 预分频
    // DEADTIME = 10, T_OSC = 1/16MHz = 62.5ns, 4分频
    // 死区 = 10 × 62.5ns × 4 = 2.5μs
    PWM1CONbits.DEADTIME = 10;

    // 自动关断配置
    PWM1CONbits.ECCPAS = 0b010;  // 比较器1输出高时关断
    PWM1CONbits.ECCPASE = 0;     // 清除关断状态

    T2CONbits.T2CKPS = 0b01;
    TMR2IF = 0;
    T2CONbits.TMR2ON = 1;

    while (!PIR1bits.TMR2IF);
    TRISC5 = 0;  // P1A 输出
    TRISC4 = 0;  // P1B 输出
}

7.4 PWM 频率与占空比速查表

系统时钟预分频PR2PWM 频率占空比步进
16MHz125515.625kHz0.024% (10-bit)
16MHz42553.906kHz0.024%
16MHz19940kHz0.1% (8-bit)
16MHz49910kHz0.1%
16MHz16992.5kHz0.1%
8MHz4995kHz0.1%
4MHz4992.5kHz0.1%

实用参考:LED 调光用 1~5kHz 即可;电机驱动用 10~20kHz(避开人耳听觉范围);蜂鸣器用 2~4kHz。


八、ADC 模数转换

8.1 ADC 特性

参数规格
分辨率真 10-bit (≤1MHz ADC 时钟)
通道数7 外部 + 1 内部 (1/4 VDD)
参考电压内部 2.0V / 3.0V / VDD / 外部 VREF
时钟源SysClk (分频) 或 LIRC
特殊功能事件触发、转换完成中断

8.2 ADC 寄存器详解

ADCON0 (0x1F) — ADC 控制寄存器 0
┌──────┬──────┬──────┬──────┬──────┬──────┬──────┬──────┐
│ ADFM │ VCFG │  CHS[4:0]   │ GO/ │ ADON │
│      │      │             │DONE │      │
└──────┴──────┴─────────────┴─────┴──────┘
  ADFM:   结果对齐 (0=左对齐, 1=右对齐)
  VCFG:   参考电压 (00=VDD, 01=外部VREF, 10=内部2V, 11=内部3V)
  CHS:    通道选择 (0~6=AN0~AN6, 7=内部1/4 VDD)
  GO/DONE: 启动/状态 (1=启动, 硬件自动清0=完成)
  ADON:   使能 (1=开启)

ADCON1 (0x9F) — ADC 控制寄存器 1
┌──────┬──────┬──────┬──────┬──────┬──────┬──────┬──────┐
│ DIVS │              ADCS[5:0]                      │
└──────┴──────┴──────┴──────┴──────┴──────┴──────┴──────┘
  DIVS:   时钟源 (0=SysClk, 1=LIRC)
  ADCS:   分频系数,需保证 ADC时钟 ≤ 1MHz

8.3 ADC 电压采集完整示例

#include "SYSCFG.h"

// === 单通道 ADC 采样 ===
// 功能:读取 PA0/AN0 上的模拟电压,参考 VDD

unsigned int ADC_Read(unsigned char channel) {
    unsigned int result;
    
    // 步骤1: 设置通道
    ADCON0bits.CHS = channel;  // AN0 ~ AN6
    
    // 步骤2: 确保 ADC 时钟 ≤ 1MHz
    // 16MHz 系统时钟,需分频 ≥ 16
    ADCON1 = 0B01010000;  // DIVS=0(SysClk), ADCS=16 → 1MHz ADC时钟
    
    // 步骤3: 设置模拟输入
    switch(channel) {
        case 0: ANSELA0 = 1; TRISA0 = 1; break;
        case 1: ANSELA1 = 1; TRISA1 = 1; break;
        case 2: ANSELA2 = 1; TRISA2 = 1; break;
        case 3: ANSELC3 = 1; TRISC3 = 1; break;
        case 4: ANSELC0 = 1; TRISC0 = 1; break;
        case 5: ANSELC1 = 1; TRISC1 = 1; break;
        case 6: ANSELC2 = 1; TRISC2 = 1; break;
    }
    
    // 步骤4: 等待采样电容充电 (采集时间)
    __delay_us(20);  // 最小建议值
    
    // 步骤5: 启动转换
    ADCON0bits.GO = 1;
    
    // 步骤6: 等待转换完成
    while (ADCON0bits.GO);
    
    // 步骤7: 读取结果 (右对齐模式)
    result = (ADRESH << 8) | ADRESL;
    
    return result;  // 0~1023
}

// === 多次采样取平均 (消除噪声) ===
unsigned int ADC_Read_Average(unsigned char channel, unsigned char samples) {
    unsigned long sum = 0;
    unsigned char i;
    
    for (i = 0; i < samples; i++) {
        sum += ADC_Read(channel);
        __delay_us(50);  // 采样间隔
    }
    
    return (unsigned int)(sum / samples);
}

// === 电压计算示例 ===
void main(void) {
    unsigned int adc_val;
    float voltage;
    
    // 基础初始化
    ADCON0bits.ADON = 1;   // 开启 ADC
    ADCON0bits.ADFM = 1;   // 右对齐
    ADCON0bits.VCFG = 0;   // VDD 参考电压
    
    while(1) {
        // 采样 AN0,取 8 次平均
        adc_val = ADC_Read_Average(0, 8);
        
        // 计算电压 (VDD = 5.0V)
        voltage = (adc_val * 5.0) / 1024.0;
        
        // 电压 = 1023 → 5.0V
        // 电压 = 512  → 2.5V
        // 电压 = 0    → 0V
        
        __delay_ms(100);
        CLRWDT();
    }
}

8.4 ADC 输入电路设计建议

模拟信号输入 →─┼──────────── ADC 引脚
               │
              ┌┴┐
              │ │ R1 (100Ω ~ 1kΩ)  ← 限流 + 低通滤波
              └┬┘
               │
              ─┴┐
         C1  ───  (10nF ~ 100nF)  ← 滤除高频噪声
              ─┬┘
               │
              ═╧═ GND

经验法则

  • ADC 输入阻抗 ≤ 10kΩ,否则采样电容充电不充分
  • 高频信号前加 RC 低通滤波(截止频率 < 采样率的 1/2)
  • 测量电池电压时,可使用内部 1/4 VDD 通道作为基准,或使用内部分压电阻

九、模拟比较器与稳压器

9.1 模拟比较器

FT61EC23-RB 内置 2 路模拟比较器,配合 16 级可编程参考电压,可以实现很多不需要 ADC 的模拟功能:

比较器结构:
                     ┌──────────┐
  C1IN+ ─────────────┤          │
                     │  CMP1    ├── C1OUT (可输出到引脚或触发中断)
  C1IN- ────┬────────┤          │
            │        └──────────┘
    ┌───────┴───────┐
    │ 16 级 DAC     │  ← 可编程参考电压
    │ (VREF)        │
    └───────────────┘

典型应用

  • 过压/欠压检测(比 ADC 更快,μs 级响应)
  • 电池电量阈值判断
  • 简易温度开关(配合 NTC)
  • 过流保护(配合采样电阻)
// === 比较器欠压检测示例 ===
// 功能:监测 3.7V 锂电池,低于 3.3V 时触发报警

void Comparator_LowBattery_Init(void) {
    // 配置 16 级参考电压 = VDD × (level/16)
    // VDD = 3.7V, 需要阈值 3.3V → level = 3.3/3.7 × 16 ≈ 14.3 → 取 14
    // 实际阈值 = 3.7V × 14/16 ≈ 3.24V
    
    C1VREF = 14;           // 参考电压 14/16 VDD
    C1INSE = 1;            // C1IN- 选择 VREF (内部参考)
    // C1IN+ 接外部电池分压
    
    C1ON = 1;              // 使能比较器 1
    C1POL = 0;             // 输出极性:不反转
    
    // C1IN+ > C1IN- → C1OUT = 1 (电池 > 阈值)
    // C1IN+ < C1IN- → C1OUT = 0 (电池 < 阈值,欠压!)
    
    C1IE = 1;              // 使能比较器中断 (输出变化时触发)
}

// 配合 PWM 自动关断实现过流保护
void Comparator_OverCurrent_Init(void) {
    // C2IN+ 接电流采样电阻上的电压
    // C2IN- 接参考电压 (对应过流阈值)
    
    C2ON = 1;
    // 配置 ECCP 自动关断源为比较器 2
    PWM1CONbits.ECCPAS = 0b010;  // C2OUT 高电平时关断所有 PWM 输出
}

9.2 稳压器输出

2 路稳压器可输出 32 档可编程电压,可用作简易 DAC 或基准源:

// 输出 2.5V 稳压 (VDD = 5V)
// level = 2.5 / 5.0 × 32 = 16
REG1CON = 16;  // 设置输出电平
REG1ON = 1;    // 使能输出

十、中断系统

10.1 中断源一览

FT61EC23-RB 共有 15 个中断源

中断源标志位使能位说明
INT 外部中断INTFINTEPC1 引脚边沿触发
PORTA 电平变化PAIFPAIEPA0~PA7 任一变化
Timer0 溢出TMR0IFTMR0IE8-bit 定时器溢出
Timer1 门控TMR1GIFTMR1GIE门控事件
Timer1 溢出TMR1IFTMR1IE16-bit 定时器溢出
Timer2 溢出TMR2IFTMR2IE与 PWM 周期匹配
Timer3 溢出TMR3IFTMR3IESLEEP 唤醒
Timer4 溢出TMR4IFTMR4IESLEEP 唤醒
Timer5 溢出TMR5IFTMR5IESLEEP 唤醒
ADC 完成ADIFADIE转换完成
ECCP 捕捉CCP1IFCCP1IE捕捉事件
比较器 1C1IFC1IE输出变化
比较器 2C2IFC2IE输出变化
EEPROM 写完EEIFEEIE写操作完成
LVD 低电压检测LVDIFLVDIE电压低于阈值

10.2 中断服务函数框架

// FT61EC23-RB 中断统一入口:0x0004
// 在 C 语言中使用 interrupt 关键字

volatile unsigned int tick_1ms = 0;    // 全局时钟计数
volatile unsigned char adc_flag = 0;   // ADC 完成标志
unsigned int adc_result;

void interrupt ISR(void) {
    // ===== Timer0 溢出 (1ms 定时) =====
    if (TMR0IE && TMR0IF) {
        TMR0IF = 0;        // 必须先清标志!
        TMR0 = 6;          // 重装初值
        tick_1ms++;
    }
    
    // ===== ADC 完成 =====
    if (ADIE && ADIF) {
        ADIF = 0;
        adc_result = (ADRESH << 8) | ADRESL;
        adc_flag = 1;
    }
    
    // ===== INT 外部中断 (按键) =====
    if (INTE && INTF) {
        INTF = 0;
        // 处理按键事件
    }
    
    // ===== Timer3 溢出 (SLEEP 唤醒) =====
    if (TMR3IE && TMR3IF) {
        TMR3IF = 0;
        // SLEEP 唤醒后的任务
    }
    
    // ... 其他中断源处理
}

⚠️ 中断编写要点

  1. 先清标志,再处理,避免嵌套重入
  2. ISR 中尽量简短,耗时操作放主循环
  3. 共享变量加 volatile 修饰
  4. 修改多字节共享变量时需临时关中断保护

十一、低功耗与电源管理

11.1 功耗数据

FT61EC23-RB 在功耗方面表现非常出色,尤其适合电池供电设备:

模式电流 (5V, 25°C)说明
SLEEP (待机)0.8 μA仅 POR 工作,RAM 保持
SLEEP + WDT3.2 μAWDT 使用 LIRC 运行
SLEEP + TMR34~5 μATMR3 使用 LIRC 运行
LIRC 运行 (32kHz)~10 μACPU 低速运行
HIRC 运行 (16MHz, 2T)~330 μA全速运行

11.2 低功耗设计策略

// === 策略1: 周期性唤醒采样 (传感器节点典型模式) ===
// 99.9% 时间在 SLEEP,功耗接近 0.8μA

void LowPower_Sensor_Node(void) {
    System_Init();
    
    while(1) {
        SLEEP();              // 进入深度睡眠,~0.8μA
        
        // WDT 或 TMR3 唤醒 (例如每 1 秒)
        Sensor_Read();        // 10ms 采样 (ADC)
        Data_Process();       // 5ms 数据处理
        RF_Send();            // 20ms 无线发送
        
        // 总活跃时间 ~35ms,周期 1s
        // 占空比 = 35ms / 1000ms = 3.5%
        // 平均电流 ≈ 0.8μA × 96.5% + 300μA × 3.5% ≈ 11.3μA
        // CR2032 (225mAh) 可运行 ≈ 2.3 年!
    }
}

// === 策略2: 动态时钟切换 ===
void Dynamic_Clock_Switch(void) {
    // 需要高速处理时切到 HIRC
    OSCCONbits.SCS = 0b10;   // 切换到 HIRC 16MHz
    Heavy_Computation();      // 快速完成计算
    
    // 处理完后切回 LIRC 低速
    OSCCONbits.SCS = 0b01;   // 切换到 LIRC 32kHz
    Idle_Loop();              // 低速等待下一个任务
}

// === 策略3: GPIO 漏电管理 ===
void GPIO_LowPower_Config(void) {
    // 所有未使用 IO → 输入 (高阻)
    TRISA = 0xFF;
    TRISC = 0xFF;
    
    // 关闭所有模拟模块 (节省 ~50μA)
    ADCON0bits.ADON = 0;
    C1ON = 0;
    C2ON = 0;
    
    // 关闭未使用的内部参考
    // ...
}

11.3 电源监控与保护

// === 低电压检测 + 掉电保护 ===

void Power_Monitor_Init(void) {
    // LVD 设为 3.1V (5V 系统) 或 2.4V (3.3V 系统)
    LVDCON = 0B00010011;  // 阈值 3.1V, 使能
    LVDIE = 1;            // 使能 LVD 中断
}

void interrupt ISR(void) {
    // ... 其他中断 ...
    
    if (LVDIE && LVDIF) {
        LVDIF = 0;
        // 电压跌落到阈值以下!
        // 紧急保存数据到 EEPROM
        EEPROM_Write(0, critical_data);
        // 关闭大功耗外设
        // 进入安全状态
    }
}

十二、EEPROM 数据存储

12.1 EEPROM 特性

FT61EC23-RB 内置 256 字节 Data EEPROM,这是它最大的差异化卖点之一——同价位的很多 MCU 需要外挂 EEPROM 芯片:

参数规格
容量256 × 8-bit
擦写次数10 万次 (典型)
数据保持> 20 年 @ 85°C
写入时间~5ms (典型)
字节/块写入支持

12.2 典型应用场景

EEPROM (256 字节) 应用规划示例:

地址 0x00 ~ 0x0F  (16B):  设备配置 (校准值、ID、波特率等)
地址 0x10 ~ 0x1F  (16B):  用户设置 (阈值、模式等)
地址 0x20 ~ 0x7F  (96B):  运行日志 (循环写入)
地址 0x80 ~ 0xFF (128B):  掉电保护数据备份

12.3 读写操作示例

// === EEPROM 单字节写入 ===
void EEPROM_WriteByte(unsigned char addr, unsigned char data) {
    EEADDR = addr;       // 设置地址
    EEDATA = data;       // 设置数据
    EECON1bits.WREN = 1; // 使能写操作
    
    // 写操作序列 (防止误写)
    EECON2 = 0x55;
    EECON2 = 0xAA;
    EECON1bits.WR = 1;   // 启动写操作
    
    while(EECON1bits.WR); // 等待完成 (~5ms)
    EECON1bits.WREN = 0;  // 关闭写使能
}

// === EEPROM 单字节读取 ===
unsigned char EEPROM_ReadByte(unsigned char addr) {
    EEADDR = addr;
    EECON1bits.RD = 1;   // 启动读操作
    return EEDATA;        // 返回数据 (立即获取,无需等待)
}

// === EEPROM 多字节写入 (批量) ===
void EEPROM_WriteBlock(unsigned char addr, unsigned char *data, unsigned char len) {
    unsigned char i;
    for (i = 0; i < len; i++) {
        EEPROM_WriteByte(addr + i, data[i]);
    }
}

// === 掉电时紧急保存数据 (配合 LVD 中断) ===
typedef struct {
    unsigned char mode;
    unsigned char brightness;
    unsigned int  total_runtime;   // 2 字节
    unsigned int  error_count;
} Config_t;

Config_t g_config;

void EEPROM_SaveConfig(void) {
    unsigned char *p = (unsigned char *)&g_config;
    unsigned char i;
    
    // 关闭全局中断,确保写入不被打断
    GIE = 0;
    for (i = 0; i < sizeof(Config_t); i++) {
        EEPROM_WriteByte(i, p[i]);
    }
    GIE = 1;
}

void EEPROM_LoadConfig(void) {
    unsigned char *p = (unsigned char *)&g_config;
    unsigned char i;
    
    for (i = 0; i < sizeof(Config_t); i++) {
        p[i] = EEPROM_ReadByte(i);
    }
    
    // 有效性检查
    if (g_config.brightness > 100) {
        // 数据异常,恢复默认值
        g_config.mode = 0;
        g_config.brightness = 50;
        g_config.total_runtime = 0;
        g_config.error_count = 0;
    }
}

12.4 EEPROM 磨损均衡策略

当需要频繁写入(如运行日志)时,简单的磨损均衡策略可以大幅延长有效寿命:

// === 简易循环写入 (磨损均衡) ===
#define LOG_START_ADDR  0x20
#define LOG_END_ADDR    0x7F
#define LOG_SIZE        (LOG_END_ADDR - LOG_START_ADDR + 1)  // 96 字节

unsigned char log_index = 0;

void Log_Write(unsigned char event) {
    // 循环写入
    unsigned char addr = LOG_START_ADDR + log_index;
    EEPROM_WriteByte(addr, event);
    
    log_index++;
    if (log_index >= LOG_SIZE) {
        log_index = 0;  // 回到开头,覆盖最早的记录
    }
}

// 读取最后 N 条记录
unsigned char Log_ReadLast(unsigned char *buf, unsigned char count) {
    unsigned char i, idx;
    if (count > LOG_SIZE) count = LOG_SIZE;
    
    idx = log_index;  // 当前位置是最新记录的前一位置
    for (i = 0; i < count; i++) {
        if (idx == 0) idx = LOG_SIZE;
        idx--;
        buf[i] = EEPROM_ReadByte(LOG_START_ADDR + idx);
    }
    return count;
}

十三、开发环境搭建

13.1 工具链总览

┌────────────────────────────────────────────┐
│              开发工具链                     │
│                                             │
│  ┌──────────┐   ┌──────────┐               │
│  │ FMD IDE  │   │ PICC-Lite│               │
│  │  (编辑器) │──▶│ (编译器) │──▶ .hex 文件  │
│  └──────────┘   └──────────┘               │
│                                             │
│  ┌──────────────────────┐                   │
│  │     FMD-Link 下载器   │                   │
│  │  ┌─────┐  ┌────────┐ │                   │
│  │  │ PGD │  │  PGC   │ │──▶ 目标板          │
│  │  └─────┘  └────────┘ │                   │
│  │  ┌─────┐              │                   │
│  │  │ GND │              │                   │
│  │  └─────┘              │                   │
│  └──────────────────────┘                   │
│                                             │
│  ┌──────────────────────┐                   │
│  │  FlashPro (烧录软件)  │                   │
│  │  批量烧录 / 生产用     │                   │
│  └──────────────────────┘                   │
└────────────────────────────────────────────┘

13.2 安装与配置步骤

第一步:获取 IDE

FMD IDE 可通过以下途径获取:

  • 辉芒微官方代理商(推荐,可获取最新版)
  • 淘宝商家提供的资料包
  • 技术论坛(如 51hei、CSDN 等)

当前主流版本为 FMDIDE 3.1.1.2,内嵌 PICC-Lite 编译器,安装后无需额外配置编译器。

第二步:新建工程
1. 打开 FMD IDE
2. File → New Project
3. 选择芯片型号: FT61EC23-RB (注意后缀 -RB)
4. 选择保存路径
5. IDE 自动生成:
   ├── SYSCFG.h       (系统配置头文件)
   ├── FT61EC23.h     (寄存器定义头文件)
   └── main.c         (主程序模板)
第三步:配置 IDE Options(配置字)
在 IDE 菜单 → Options 中设置:

┌─────────────────────────────────────┐
│  Oscillator:   Internal HIRC 16MHz  │
│  TSEL:         2T (高性能模式)       │
│  WDTE:         Enable/Disable       │
│  MCLRE:        Enable (使用/MCLR)   │
│  LVR:          2.8V (或根据需求)    │
│  BOR:          Enable               │
│  Code Protect: Disable (调试期间)    │
└─────────────────────────────────────┘
第四步:硬件连接(调试/烧录)
FMD-Link 下载器          FT61EC23-RB 目标板
┌──────────┐            ┌──────────────┐
│  VDD ────┼────────────┤ VDD (Pin1)   │
│  GND ────┼────────────┤ VSS (Pin16)  │
│  PGD ────┼────────────┤ RA0/ICSPDA   │
│  PGC ────┼────────────┤ RA1/ICSPCLK  │
│  /MCLR ──┼────────────┤ RA3//MCLR    │ (可选)
└──────────┘            └──────────────┘

注意:
  - PGD/PGC 线上的上拉/下拉电阻需断开(或 >10kΩ)
  - /MCLR 不能有强下拉
  - 目标板可自供电或由 FMD-Link 供电 (3.3V/5V 可选)
第五步:编译与烧录
1. 点击 Build (或 F7) 编译
   输出窗口显示:
   "BUILD SUCCESSFUL" → 编译成功
   生成 .hex 文件

2. 点击 Program (或 F8) 烧录
   输出窗口显示:
   "Program Success!" → 烧录成功

3. 调试模式:
   Debug → Start Debugging
   支持断点、单步、变量查看

排错提示:如果烧录失败,首先检查 /MCLR 上拉是否正确,PGD/PGC 线上是否有电容或强上拉/下拉。

13.3 简易最小开发板设计

          +5V ─────────────────────────────┐
                                           │
          ┌────┐                           │
          │ USB│   ┌───────────────────────┤
          │转串│   │  ┌────────────────┐   │
          │ 口 │   │  │                │   │
          └────┘   │  │  FT61EC23-RB   │   │
                   │  │                │   │
    TXD ───────────┤RC3 (串口 TX 软件)   │   │
    RXD ───────────┤RC4 (串口 RX 软件)   │   │
                   │  │                │   │
    LED ──[330Ω]───┤RA4                │   │
    BTN ───────────┤RA2 (内部上拉)      │   │
                   │  │                │   │
                   │  └────────────────┘   │
                   │                       │
    ┌──────────┐   │                       │
    │ FMD-Link│───┼── PGD/PGC//MCLR       │
    └──────────┘                           │
                                           │
          GND ─────────────────────────────┘

十四、实战项目:多合一智能传感器节点

14.1 项目概述

设计一个完整的电池供电传感器节点,展示 FT61EC23-RB 各项外设的综合运用:

功能需求:
├── 温湿度采集 (ADC 读取模拟传感器)
├── 光照强度检测 (ADC 读取光敏电阻)
├── 电池电压监测 (内部 1/4 VDD 通道)
├── LED 状态指示 (GPIO + PWM 调光)
├── 按键交互 (外部中断)
├── 蜂鸣器报警 (PWM 蜂鸣器模式)
├── 低电压保护 (LVD + EEPROM 数据保存)
├── 定时唤醒 (Timer3 SLEEP 唤醒)
└── 串口调试输出 (软件 UART)

14.2 完整代码

/********************************************************************
 * FT61EC23-RB 智能传感器节点
 * 
 * 功能:
 *  - 周期性采集温度、光照、电池电压
 *  - 超阈值蜂鸣器报警 + LED 闪烁
 *  - 按键切换工作模式
 *  - 低功耗周期性唤醒 (SLEEP + TMR3)
 *  - 配置参数保存到 EEPROM
 *  - 串口调试输出
 * 
 * 硬件连接:
 *  PA0/AN0 → NTC 温度传感器 (分压)
 *  PA1/AN1 → 光敏电阻 (分压)
 *  PA2     → 按键 (低有效, 内部弱上拉)
 *  PA4     → LED (PWM 调光)
 *  PC3     → 蜂鸣器 (PWM 输出)
 *  PC4     → 串口 TX (软件 UART, 115200bps)
 ********************************************************************/

#include "SYSCFG.h"
#include <string.h>

// ======================== 系统配置 ========================
#define FOSC            16000000UL
#define TSEL            2

// ======================== 引脚定义 ========================
#define LED_PIN         RA4
#define BUZZER_PIN      RC3
#define BUTTON_PIN      RA2
#define UART_TX         RC4

// ======================== 阈值配置(可存入 EEPROM) ========================
#define TEMP_ALARM_HIGH     350     // ADC 值对应高温阈值
#define TEMP_ALARM_LOW      100     // ADC 值对应低温阈值
#define LIGHT_ALARM_LOW     50      // ADC 值对应光线过暗阈值
#define BATTERY_LOW         580     // ADC 值对应 3.3V 电池低电压
#define SAMPLE_INTERVAL     60      // 采样间隔(秒)

// ======================== 全局变量 ========================
volatile unsigned int g_tick_1s = 0;       // 秒计数器
volatile unsigned char g_btn_pressed = 0;  // 按键标志
volatile unsigned char g_adc_done = 0;     // ADC 完成标志
volatile unsigned char g_wakeup = 0;       // 唤醒标志
volatile unsigned int  g_adc_result = 0;

unsigned int  g_temp_raw;       // 温度 ADC 值
unsigned int  g_light_raw;      // 光照 ADC 值
unsigned int  g_battery_raw;    // 电池 ADC 值
unsigned char g_mode = 0;       // 工作模式
unsigned char g_alarm = 0;      // 报警状态
unsigned int  g_sample_count = 0;  // 采样计数

// ======================== 系统时钟配置 ========================
void SystemClock_Init(void) {
    // 主时钟: HIRC 16MHz
    // TSEL: 2T (在 IDE Options 中设置)
    // 指令周期 = 2/16MHz = 125ns
}

// ======================== GPIO 初始化 ========================
void GPIO_Init(void) {
    // 全部默认为输入
    TRISA = 0xFF;
    TRISC = 0xFF;
    
    // 关闭模拟功能
    ANSEL = 0x00;
    
    // 配置输出引脚
    TRISA4 = 0;    // LED
    TRISC3 = 0;    // 蜂鸣器
    TRISC4 = 0;    // UART TX
    
    // 配置输入引脚
    TRISA2 = 1;    // 按键
    WPUA2 = 1;     // 内部弱上拉使能
}

// ======================== ADC 初始化 ========================
void ADC_Init(void) {
    ADCON0bits.ADON = 1;   // 使能 ADC
    ADCON0bits.ADFM = 1;   // 右对齐
    ADCON0bits.VCFG = 0;   // VDD 参考电压
    
    // ADC 时钟 = 16MHz / 32 = 500kHz (< 1MHz)
    ADCON1 = 0B00100000;  // DIVS=0(SysClk), ADCS=32
}

// ======================== ADC 采样 ========================
unsigned int ADC_Read(unsigned char channel) {
    ADCON0bits.CHS = channel;
    
    // 配置模拟输入
    switch(channel) {
        case 0: ANSELA0 = 1; TRISA0 = 1; break;
        case 1: ANSELA1 = 1; TRISA1 = 1; break;
        case 7: break;  // 内部通道 1/4 VDD,无需配置引脚
    }
    
    __delay_us(50);        // 采样电容充电
    
    ADCON0bits.GO = 1;     // 启动转换
    while(ADCON0bits.GO);  // 等待完成
    
    return (ADRESH << 8) | ADRESL;
}

unsigned int ADC_ReadAvg(unsigned char ch, unsigned char n) {
    unsigned long sum = 0;
    for (unsigned char i = 0; i < n; i++) {
        sum += ADC_Read(ch);
    }
    return (unsigned int)(sum / n);
}

// ======================== 软件 UART (调试输出) ========================
// 波特率 115200bps @ 16MHz
// 位时间 = 16MHz / 115200 ≈ 139 个指令周期 (2T模式)

void UART_SendByte(unsigned char dat) {
    unsigned char i;
    
    GIE = 0;  // 关中断,保证时序
    
    // 起始位 (低电平)
    UART_TX = 0;
    __delay_us(8.68);  // 1/115200 ≈ 8.68μs
    
    // 数据位 (LSB first)
    for (i = 0; i < 8; i++) {
        UART_TX = (dat >> i) & 0x01;
        __delay_us(8.68);
    }
    
    // 停止位 (高电平)
    UART_TX = 1;
    __delay_us(8.68);
    
    GIE = 1;
}

void UART_SendString(const char *str) {
    while (*str) {
        UART_SendByte(*str++);
    }
}

// 简易十六进制打印
void UART_SendHex(unsigned int val) {
    unsigned char i;
    unsigned char nibble;
    UART_SendString("0x");
    for (i = 0; i < 4; i++) {
        nibble = (val >> (12 - i * 4)) & 0x0F;
        if (nibble < 10)
            UART_SendByte('0' + nibble);
        else
            UART_SendByte('A' + nibble - 10);
    }
}

// ======================== Timer3 SLEEP 唤醒定时 ========================
void Timer3_SleepWakeup_Init(void) {
    // Timer3 时钟: LIRC 32kHz
    // 12-bit 定时器, 预分频 128, 后分频 256
    // 溢出时间 = (1/32768) × 4096 × 128 × 256 ≈ 4096 秒 (约 68 分钟)
    // 
    // 实际需要的采样间隔较短 (如 1 分钟):
    // 预分频 = 16, 后分频 = 2
    // 溢出时间 = (1/32768) × 4096 × 16 × 2 ≈ 4 秒
    // 通过软件计数 15 次达到 60 秒
    
    // 简化配置: 使用预分频和后分频使溢出 ≈ 1~2 秒,软件累计
    T3CON = 0B10000111;  // 使能, LIRC 时钟, 预分频=128
    // 后分频 = 16 (具体寄存器位请参考数据手册)
    
    TMR3IE = 1;          // 使能 Timer3 中断
}

// ======================== LED PWM 调光 ========================
void LED_PWM_Init(void) {
    TRISA4 = 1;          // 先禁止输出
    PR2 = 99;            // 10kHz PWM
    T2CONbits.T2CKPS = 0b01;  // 预分频 4
    // 使用独立 PWM 或 ECCP 的 P1A 输出...
    // (具体寄存器配置见第七章)
}

void LED_SetBrightness(unsigned char percent) {
    if (percent > 100) percent = 100;
    // 占空比 = percent%
    // ...
}

// ======================== 蜂鸣器 ========================
void Buzzer_Beep(unsigned int ms) {
    // 启动蜂鸣器 PWM
    TRISC3 = 0;
    // 输出 2.5kHz 方波
    __delay_ms(ms);
    TRISC3 = 1;  // 停止 (高阻)
}

void Buzzer_Alarm(unsigned char times) {
    for (unsigned char i = 0; i < times; i++) {
        Buzzer_Beep(200);
        __delay_ms(100);
    }
}

// ======================== EEPROM 配置存储 ========================
#define EE_ADDR_MODE        0x00
#define EE_ADDR_VALID_FLAG  0xFF   // 有效性标志

void EEPROM_WriteByte(unsigned char addr, unsigned char dat) {
    EEADDR = addr;
    EEDATA = dat;
    EECON1bits.WREN = 1;
    EECON2 = 0x55;
    EECON2 = 0xAA;
    EECON1bits.WR = 1;
    while(EECON1bits.WR);
    EECON1bits.WREN = 0;
}

unsigned char EEPROM_ReadByte(unsigned char addr) {
    EEADDR = addr;
    EECON1bits.RD = 1;
    return EEDATA;
}

void Config_Save(void) {
    EEPROM_WriteByte(EE_ADDR_MODE, g_mode);
    EEPROM_WriteByte(EE_ADDR_VALID_FLAG, 0xA5);  // 标记数据有效
}

void Config_Load(void) {
    if (EEPROM_ReadByte(EE_ADDR_VALID_FLAG) == 0xA5) {
        g_mode = EEPROM_ReadByte(EE_ADDR_MODE);
    } else {
        // 首次使用,使用默认值
        g_mode = 0;
    }
}

// ======================== 主传感器采样 ========================
void Sensor_Sample(void) {
    // 温度采样 (NTC 分压: VDD → NTC → AN0 → 固定电阻 → GND)
    g_temp_raw = ADC_ReadAvg(0, 8);
    
    // 光照采样 (光敏电阻分压: VDD → 光敏 → AN1 → 固定电阻 → GND)
    g_light_raw = ADC_ReadAvg(1, 8);
    
    // 电池电压 (内部 1/4 VDD 通道)
    g_battery_raw = ADC_ReadAvg(7, 8);
    // 实际 VDD = (g_battery_raw / 1024) × 4 × VREF
    
    g_sample_count++;
}

// ======================== 报警检测 ========================
void Alarm_Check(void) {
    g_alarm = 0;
    
    // 高温报警
    if (g_temp_raw > TEMP_ALARM_HIGH) {
        g_alarm |= 0x01;
    }
    // 低温报警
    if (g_temp_raw < TEMP_ALARM_LOW) {
        g_alarm |= 0x02;
    }
    // 光线过暗
    if (g_light_raw < LIGHT_ALARM_LOW) {
        g_alarm |= 0x04;
    }
    // 电池低电压
    if (g_battery_raw > BATTERY_LOW) {  // 注意: 1/4 VDD 通道,值越大电压越高
        g_alarm |= 0x08;  // 实际这里是电压正常,逻辑反过来
    }
}

// ======================== 中断服务函数 ========================
void interrupt ISR(void) {
    // Timer3 溢出 (SLEEP 唤醒)
    if (TMR3IE && TMR3IF) {
        TMR3IF = 0;
        g_wakeup = 1;
    }
    
    // INT 外部中断 (按键)
    if (INTE && INTF) {
        INTF = 0;
        g_btn_pressed = 1;
    }
    
    // ADC 完成
    if (ADIE && ADIF) {
        ADIF = 0;
        g_adc_result = (ADRESH << 8) | ADRESL;
        g_adc_done = 1;
    }
}

// ======================== 按键处理 ========================
void Button_Handler(void) {
    if (!g_btn_pressed) return;
    g_btn_pressed = 0;
    
    // 消抖
    __delay_ms(20);
    if (BUTTON_PIN == 1) return;  // 误触发
    
    // 等待释放
    while (BUTTON_PIN == 0) {
        CLRWDT();
    }
    
    // 切换模式
    g_mode++;
    if (g_mode > 3) g_mode = 0;
    
    Config_Save();
    
    // 模式指示
    switch(g_mode) {
        case 0: LED_PIN = 1; __delay_ms(200); LED_PIN = 0; break;  // 闪1次
        case 1: 
            LED_PIN = 1; __delay_ms(200); LED_PIN = 0;
            __delay_ms(200);
            LED_PIN = 1; __delay_ms(200); LED_PIN = 0; break;  // 闪2次
        case 2: Buzzer_Beep(100); break;  // 蜂鸣指示
        case 3: Buzzer_Beep(300); break;
    }
}

// ======================== 调试输出 ========================
void Debug_Print(void) {
    UART_SendString("\r\n=== Sensor Report #");
    UART_SendHex(g_sample_count);
    UART_SendString(" ===\r\n");
    
    UART_SendString("Temp ADC: ");   UART_SendHex(g_temp_raw);   UART_SendString("\r\n");
    UART_SendString("Light ADC: ");  UART_SendHex(g_light_raw);  UART_SendString("\r\n");
    UART_SendString("Batt ADC: ");   UART_SendHex(g_battery_raw);UART_SendString("\r\n");
    UART_SendString("Mode: ");       UART_SendHex(g_mode);       UART_SendString("\r\n");
    UART_SendString("Alarm: ");      UART_SendHex(g_alarm);      UART_SendString("\r\n");
}

// ======================== 主函数 ========================
void main(void) {
    // === 系统初始化 ===
    SystemClock_Init();
    GPIO_Init();
    ADC_Init();
    Timer3_SleepWakeup_Init();
    
    // 配置 INT 外部中断 (PA2, 下降沿)
    INTE = 1;
    INTEDG = 0;  // 下降沿触发
    
    // 加载配置
    Config_Load();
    
    // 初始采样
    Sensor_Sample();
    Debug_Print();
    
    GIE = 1;  // 开全局中断
    
    // === 主循环 ===
    while(1) {
        // 处理按键
        Button_Handler();
        
        // 处理定时唤醒后的采样
        if (g_wakeup) {
            g_wakeup = 0;
            
            Sensor_Sample();      // 采集传感器
            Alarm_Check();        // 检查报警
            
            if (g_alarm) {
                LED_PIN = 1;
                Buzzer_Alarm(3);
                LED_PIN = 0;
            } else {
                // LED 闪烁指示正常运行
                LED_PIN = 1;
                __delay_ms(50);
                LED_PIN = 0;
            }
            
            Debug_Print();        // 串口输出
            
            if (g_alarm & 0x08) {  // 电池低电压
                Config_Save();     // 紧急保存配置
            }
        }
        
        // 如果报警持续,保持 LED 闪烁
        if (g_alarm) {
            LED_PIN = !LED_PIN;
            __delay_ms(500);
        }
        
        // 进入睡眠(低功耗)
        CLRWDT();
        SLEEP();
        // ... WDT 或 Timer3 唤醒后继续循环
    }
}

/********************************************************************
 * 功耗估算:
 * 
 * SLEEP 模式 (LIRC + WDT 运行): ~3.2 μA
 * 活跃时间 (采样+处理+发送): ~100ms
 * 采样间隔: 60 秒
 * 
 * 平均电流 = 3.2μA × (1 - 100ms/60s) + 300μA × (100ms/60s)
 *          = 3.2μA × 99.83% + 300μA × 0.17%
 *          ≈ 3.2μA + 0.5μA
 *          ≈ 3.7μA
 * 
 * 使用 CR2032 (225mAh):
 * 理论续航 ≈ 225mAh / 3.7μA ≈ 60,810 小时 ≈ 6.9 年
 * 
 * 实际考虑电池自放电等因素,保守估计 3~5 年续航。
 ********************************************************************/

十五、常见坑与排错指南

15.1 硬件设计坑

问题现象原因解决方案
烧录失败FlashPro 报错/MCLR 被外部电路拉低烧录时断开 /MCLR 外部电路,确保 10kΩ 上拉到 VDD
功耗异常高空片就 10~20mA全部 IO 设为输出未用 IO 设为输入(高阻态)
ADC 读数跳动数值不稳定输入阻抗太大或没有滤波电容ADC 输入加 100Ω+100nF RC 滤波
复位异常随机重启看门狗未喂狗 / 电源纹波大定期 CLRWDT();VDD 加 10μF+100nF 去耦
PWM 无输出引脚无波形TRIS 未清 0 / PWM 占空比全 0检查 TRIS 方向、CCPR1L 和 DC1B 配置

15.2 软件设计坑

问题现象原因解决方案
中断不响应程序不进 ISRGIE 未开 / 标志位未清确保 GIE=1, PEIE=1, 检查各中断使能位
EEPROM 写不进去读出来是旧值未执行解锁序列必须写 0x55, 0xAA 到 EECON2 后置 WR 位
延时不准__delay_ms 偏差大系统时钟配置与实际不符核对 OSCCON 和 IDE Options 中 TSEL 设置
SLEEP 不唤醒芯片睡了醒不过来未使能唤醒源中断确保 WDT/TMR3/INT 等唤醒源的中断已使能
变量异常变化值无规律跳动中断中修改了共享变量但主循环无保护共享变量加 volatile,关键操作临时关中断

15.3 调试技巧

// === 技巧1: 用空闲引脚输出状态标志 ===
// 在关键代码段置位/清除某个 GPIO,用示波器观察时序
#define DEBUG_PIN  RC5
TRISC5 = 0;

// 测量 ISR 执行时间
void interrupt ISR(void) {
    DEBUG_PIN = 1;   // 进入 ISR
    // ... ISR 代码 ...
    DEBUG_PIN = 0;   // 退出 ISR
}
// 示波器看 DEBUG_PIN 高电平宽度 = ISR 执行时间

// === 技巧2: 软件 UART 调试输出 ===
// 使用前面定义的 UART_SendString/UART_SendHex
// 在关键位置输出变量值和状态

// === 技巧3: 利用 LVD 检测供电问题 ===
// 如果芯片反复复位,怀疑电源问题
// 用 LVD 中断记录复位原因

十六、总结与选型建议

16.1 FT61EC23-RB 的优势

维度评价
性价比★★★★★ 同配置下价格极具竞争力
模拟外设★★★★★ 真 10-bit ADC + 双比较器 + 双稳压器
PWM 能力★★★★☆ 5 路 + ECCP 全桥,中小功率电机控制足够
低功耗★★★★★ 0.8μA 待机,同价位最优之一
EEPROM★★★★★ 256 字节内置,省掉外挂 EEPROM 成本
开发难度★★★☆☆ 类 PIC 架构,有经验的开发者上手快
生态丰富度★★★☆☆ 中文资料有限,不如 STM8/STM32 丰富
供货稳定性★★★★☆ 国产芯片,供应稳定

16.2 适用场景推荐

场景推荐度理由
电池供电传感器★★★★★极致低功耗 + 内置 EEPROM
LED 照明控制★★★★★多路 PWM + ADC 调光
小家电控制★★★★★性价比高 + 抗干扰强
温控器/温湿度计★★★★★ADC + EEPROM 参数存储
电机驱动★★★★☆ECCP 全桥控制
智能家居节点★★★★☆低功耗 + IO 充足
仪器仪表★★★★☆模拟前端 + 精度足够
高速数据处理★★★☆☆8bit @ 16MHz 上限

16.3 学习路线建议

入门阶段 (1~3 天)
├── 跑通 LED 闪烁 → 理解 GPIO / 时钟
├── 按键控制 LED → 理解输入 / 上拉 / 消抖
├── 定时器精确延时 → 理解中断 / TMR0
└── 看门狗实验 → 理解 WDT / 复位

进阶阶段 (1~2 周)
├── ADC 采样 (电位器/NTC/光敏) → 理解模拟前端
├── PWM 调光 (LED 呼吸灯) → 理解 PWM 周期/占空比
├── EEPROM 读写 (保存用户设置) → 理解非易失存储
└── SLEEP 唤醒 (电池供电) → 理解低功耗设计

精通阶段 (1 个月+)
├── ECCP 电机控制 (全桥驱动) → 理解死区/互补输出
├── 多传感器融合 (温湿度+光照+电池) → 综合项目
├── 比较器过流保护 → 理解硬件级快速响应
└── 量产考虑 (校准、测试、固件升级) → 工程化思维

附录

A. 指令集速查表

助记符操作影响标志周期
MOVLW kW ← k1
MOVWF ff ← W1
MOVF f,dd ← fZ1
CLRF ff ← 0Z1
CLRWW ← 0Z1
ADDWF f,dd ← f+WC,DC,Z1
SUBWF f,dd ← f-WC,DC,Z1
DECF f,dd ← f-1Z1
INCF f,dd ← f+1Z1
ANDWF f,dd ← f & WZ1
IORWF f,dd ← f | WZ1
XORWF f,dd ← f ^ WZ1
COMF f,dd ← ~fZ1
SWAPF f,dd ← f 高/低半字节交换1
RRF f,dd ← f 右移(带进位)C1
RLF f,dd ← f 左移(带进位)C1
BCF f,bf 的 b 位清 01
BSF f,bf 的 b 位置 11
BTFSC f,bf 的 b 位为 0 则跳过下一条1(2)
BTFSS f,bf 的 b 位为 1 则跳过下一条1(2)
DECFSZ f,df-1→d, 结果为 0 则跳过1(2)
INCFSZ f,df+1→d, 结果为 0 则跳过1(2)
GOTO kPC ← k2
CALL k堆栈←PC+1, PC←k2
RETURNPC ← 堆栈弹出2
RETLW kW←k, PC←堆栈弹出2
RETFIEPC←堆栈弹出, GIE←12
SLEEP进入休眠模式1
CLRWDT清除看门狗1
NOP空操作1

B. 寄存器速查表 (Bank0)

地址名称功能
0x00INDF间接寻址
0x01TMR0Timer0 计数器
0x02PCL程序计数器低字节
0x03STATUS状态寄存器 (C,DC,Z,PD,TO,RP0,RP1,IRP)
0x04FSR间接寻址指针
0x05PORTAPORTA 数据寄存器
0x06PORTBPORTB 数据寄存器
0x07PORTCPORTC 数据寄存器
0x0APCLATH程序计数器高字节锁存
0x0BINTCON中断控制 (GIE,PEIE,T0IE,INTE,RAIE,T0IF,INTF,RAIF)
0x0CPIR1外设中断标志 1
0x0DPIR2外设中断标志 2
0x11TMR1LTimer1 低字节
0x12TMR1HTimer1 高字节
0x13T1CONTimer1 控制
0x14TMR2Timer2 计数器
0x15T2CONTimer2 控制
0x1EADRESHADC 结果高位
0x1FADCON0ADC 控制 0

更多推荐