FT61EC23-RB从入门到精通
FT61EC23-RB 从入门到精通:一颗被低估的国产 8 位 MCU
目录
- 芯片概览
- 硬件架构详解
- 引脚功能与最小系统
- 时钟系统配置
- GPIO 输入输出
- 定时器模块
- PWM 与增强型 ECCP
- ADC 模数转换
- 模拟比较器与稳压器
- 中断系统
- 低功耗与电源管理
- EEPROM 数据存储
- 开发环境搭建
- 实战项目:多合一智能传感器节点
- 常见坑与排错指南
- 总结与选型建议
一、芯片概览
1.1 它是什么
FT61EC23-RB 是 辉芒微电子(Fremont Micro Devices, FMD) 推出的一款 8 位 RISC 架构 MCU,属于 FT61EC2X 系列。它基于 EEPROM 工艺,与经典的 PIC16 系列指令集高度相似(35 条精简指令),但集成了更丰富的外设,价格却非常亲民。
1.2 核心参数速览
| 类别 | 参数 | 备注 |
|---|---|---|
| CPU | 8-bit RISC,35 条指令 | 2T/4T 可选 |
| 最高主频 | 16MHz @ 2T (VDD≥2.7V) | 最小指令周期 125ns |
| 程序存储器 | 2K × 14-bit | Flash,支持读写保护 |
| RAM | 128 × 8-bit | 足够中小型应用 |
| 数据 EEPROM | 256 × 8-bit | 10 万次擦写,>20 年保持 |
| 工作电压 | 2.0V ~ 5.5V | 宽电压,适配电池供电 |
| 工作温度 | -40°C ~ +85°C | 工业级 |
| GPIO | 最多 14 个 | PA0~PA7, PC0~PC5 |
| ADC | 10-bit,7+1 通道 | 真 10 位精度 |
| PWM | 5 路(含 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 |
| PIC16F684 | Microchip 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 |
|---|---|---|---|---|
| RA0 | I/O | AN0 (ADC 通道 0) | ICSPDA (编程数据) | — |
| RA1 | I/O | AN1 (ADC 通道 1) | ICSPCLK (编程时钟) | — |
| RA2 | I/O | AN2 (ADC 通道 2) | T0CKI (TMR0 外部时钟) | INT (外部中断) |
| RA3 | I/O | /MCLR (复位) | VPP (编程高压) | — |
| RA4 | I/O | OSC2 (晶振输出) | — | — |
| RA5 | I/O | OSC1/CLKIN (晶振/外部时钟) | — | — |
| RC0 | I/O | AN4 (ADC 通道 4) | C2IN+ (比较器 2 正输入) | — |
| RC1 | I/O | AN5 (ADC 通道 5) | C1IN- (比较器 1 负输入) | — |
| RC2 | I/O | AN6 (ADC 通道 6) | C1IN+ (比较器 1 正输入) | — |
| RC3 | I/O | AN3 (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 │
└──────────────────────────────────────────────┘
| 时钟源 | 频率 | 精度 | 启动时间 | 功耗 | 适用场景 |
|---|---|---|---|---|---|
| HIRC | 16MHz | ±2% (25°C) | ~μs 级 | 较高 | 主时钟,高速运行 |
| LIRC | 32kHz / 256kHz | 较低 | ~μs 级 | 极低 | SLEEP 唤醒定时、WDT |
| XT | 取决于晶振 | 取决于晶振 | ~ms 级 | 中等 | 需要高精度定时的场景 |
| LP | 32.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.6V | 22mA (单引脚) | 19mA (单引脚) |
| VDD = 3.3V | 约 12mA | 约 10mA |
| 所有端口总电流 | ≤ 90mA | — |
LED 驱动建议串联 220Ω~1kΩ 限流电阻,不要直接驱动大功率负载。驱动继电器或电机时务必使用三极管 / MOS 管扩流。
六、定时器模块
FT61EC23-RB 配备了 6 个定时器,数量和灵活性在同价位芯片中相当突出:
6.1 定时器总览
| 定时器 | 位宽 | 预分频 | 后分频 | 特色功能 |
|---|---|---|---|---|
| WDT | 16-bit | — | 7-bit | 独立 LIRC 时钟,防死机 |
| Timer0 | 8-bit | 8-bit | — | 外部时钟输入,支持 SLEEP 唤醒 |
| Timer1 | 16-bit | 3-bit | — | 门控功能,外部晶振可选 |
| Timer2 | 8-bit | 4-bit | 4-bit | 配合 ECCP/PWM 使用 |
| Timer3 | 12-bit | 7-bit | 8-bit | SLEEP 下可运行! |
| Timer4 | 12-bit | 7-bit | 8-bit | SLEEP 下可运行! |
| Timer5 | 12-bit | 7-bit | 8-bit | SLEEP 下可运行! |
亮点: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-bit | SLEEP 下可运行,独立周期/占空比/极性,蜂鸣器模式 |
| 增强型 ECCP | 1 路 (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 频率与占空比速查表
| 系统时钟 | 预分频 | PR2 | PWM 频率 | 占空比步进 |
|---|---|---|---|---|
| 16MHz | 1 | 255 | 15.625kHz | 0.024% (10-bit) |
| 16MHz | 4 | 255 | 3.906kHz | 0.024% |
| 16MHz | 1 | 99 | 40kHz | 0.1% (8-bit) |
| 16MHz | 4 | 99 | 10kHz | 0.1% |
| 16MHz | 16 | 99 | 2.5kHz | 0.1% |
| 8MHz | 4 | 99 | 5kHz | 0.1% |
| 4MHz | 4 | 99 | 2.5kHz | 0.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 外部中断 | INTF | INTE | PC1 引脚边沿触发 |
| PORTA 电平变化 | PAIF | PAIE | PA0~PA7 任一变化 |
| Timer0 溢出 | TMR0IF | TMR0IE | 8-bit 定时器溢出 |
| Timer1 门控 | TMR1GIF | TMR1GIE | 门控事件 |
| Timer1 溢出 | TMR1IF | TMR1IE | 16-bit 定时器溢出 |
| Timer2 溢出 | TMR2IF | TMR2IE | 与 PWM 周期匹配 |
| Timer3 溢出 | TMR3IF | TMR3IE | SLEEP 唤醒 |
| Timer4 溢出 | TMR4IF | TMR4IE | SLEEP 唤醒 |
| Timer5 溢出 | TMR5IF | TMR5IE | SLEEP 唤醒 |
| ADC 完成 | ADIF | ADIE | 转换完成 |
| ECCP 捕捉 | CCP1IF | CCP1IE | 捕捉事件 |
| 比较器 1 | C1IF | C1IE | 输出变化 |
| 比较器 2 | C2IF | C2IE | 输出变化 |
| EEPROM 写完 | EEIF | EEIE | 写操作完成 |
| LVD 低电压检测 | LVDIF | LVDIE | 电压低于阈值 |
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 唤醒后的任务
}
// ... 其他中断源处理
}
⚠️ 中断编写要点:
- 先清标志,再处理,避免嵌套重入
- ISR 中尽量简短,耗时操作放主循环
- 共享变量加
volatile修饰- 修改多字节共享变量时需临时关中断保护
十一、低功耗与电源管理
11.1 功耗数据
FT61EC23-RB 在功耗方面表现非常出色,尤其适合电池供电设备:
| 模式 | 电流 (5V, 25°C) | 说明 |
|---|---|---|
| SLEEP (待机) | 0.8 μA | 仅 POR 工作,RAM 保持 |
| SLEEP + WDT | 3.2 μA | WDT 使用 LIRC 运行 |
| SLEEP + TMR3 | 4~5 μA | TMR3 使用 LIRC 运行 |
| LIRC 运行 (32kHz) | ~10 μA | CPU 低速运行 |
| 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 软件设计坑
| 问题 | 现象 | 原因 | 解决方案 |
|---|---|---|---|
| 中断不响应 | 程序不进 ISR | GIE 未开 / 标志位未清 | 确保 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 k | W ← k | — | 1 |
| MOVWF f | f ← W | — | 1 |
| MOVF f,d | d ← f | Z | 1 |
| CLRF f | f ← 0 | Z | 1 |
| CLRW | W ← 0 | Z | 1 |
| ADDWF f,d | d ← f+W | C,DC,Z | 1 |
| SUBWF f,d | d ← f-W | C,DC,Z | 1 |
| DECF f,d | d ← f-1 | Z | 1 |
| INCF f,d | d ← f+1 | Z | 1 |
| ANDWF f,d | d ← f & W | Z | 1 |
| IORWF f,d | d ← f | W | Z | 1 |
| XORWF f,d | d ← f ^ W | Z | 1 |
| COMF f,d | d ← ~f | Z | 1 |
| SWAPF f,d | d ← f 高/低半字节交换 | — | 1 |
| RRF f,d | d ← f 右移(带进位) | C | 1 |
| RLF f,d | d ← f 左移(带进位) | C | 1 |
| BCF f,b | f 的 b 位清 0 | — | 1 |
| BSF f,b | f 的 b 位置 1 | — | 1 |
| BTFSC f,b | f 的 b 位为 0 则跳过下一条 | — | 1(2) |
| BTFSS f,b | f 的 b 位为 1 则跳过下一条 | — | 1(2) |
| DECFSZ f,d | f-1→d, 结果为 0 则跳过 | — | 1(2) |
| INCFSZ f,d | f+1→d, 结果为 0 则跳过 | — | 1(2) |
| GOTO k | PC ← k | — | 2 |
| CALL k | 堆栈←PC+1, PC←k | — | 2 |
| RETURN | PC ← 堆栈弹出 | — | 2 |
| RETLW k | W←k, PC←堆栈弹出 | — | 2 |
| RETFIE | PC←堆栈弹出, GIE←1 | — | 2 |
| SLEEP | 进入休眠模式 | — | 1 |
| CLRWDT | 清除看门狗 | — | 1 |
| NOP | 空操作 | — | 1 |
B. 寄存器速查表 (Bank0)
| 地址 | 名称 | 功能 |
|---|---|---|
| 0x00 | INDF | 间接寻址 |
| 0x01 | TMR0 | Timer0 计数器 |
| 0x02 | PCL | 程序计数器低字节 |
| 0x03 | STATUS | 状态寄存器 (C,DC,Z,PD,TO,RP0,RP1,IRP) |
| 0x04 | FSR | 间接寻址指针 |
| 0x05 | PORTA | PORTA 数据寄存器 |
| 0x06 | PORTB | PORTB 数据寄存器 |
| 0x07 | PORTC | PORTC 数据寄存器 |
| 0x0A | PCLATH | 程序计数器高字节锁存 |
| 0x0B | INTCON | 中断控制 (GIE,PEIE,T0IE,INTE,RAIE,T0IF,INTF,RAIF) |
| 0x0C | PIR1 | 外设中断标志 1 |
| 0x0D | PIR2 | 外设中断标志 2 |
| 0x11 | TMR1L | Timer1 低字节 |
| 0x12 | TMR1H | Timer1 高字节 |
| 0x13 | T1CON | Timer1 控制 |
| 0x14 | TMR2 | Timer2 计数器 |
| 0x15 | T2CON | Timer2 控制 |
| 0x1E | ADRESH | ADC 结果高位 |
| 0x1F | ADCON0 | ADC 控制 0 |
更多推荐


所有评论(0)