恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
LPC2131/2132 GPIO、Timer与EXINT寄存器级配置实战
首页
资讯中心
/
LPC2131/2132 GPIO、Timer与EXINT寄存器级配置实战
LPC2131/2132 GPIO、Timer与EXINT寄存器级配置实战
发布时间:2026/9/13 10:36:44
简介面向嵌入式初学者的 LPC2131/2132 GPIO、定时器与外部中断仿真学习包。它基于 ARM7TDMI 内核的 NXP LPC213x 微控制器在 Proteus 环境中演示 GPIO 输入输出、定时器中断和 EXINT 外部中断的完整配置过程帮助读者理解裸机外设驱动的基本思路借助 PINSEL/PINMODE 寄存器可灵活配置引脚复用与上下拉便于观察外设行为。压缩包共 42 个文件大小约 165KB主要包含 Proteus 仿真工程、Keil 工程文件、C 语言源码、头文件、配置文件及生成的 hex 文件包含可直接打开的仿真原理图与 Keil 项目工程目录结构清晰源码中附有注释适合对照学习。目前已有 377 人浏览/学习作为入门阶段的小型嵌入式实例具有较高的参考价值。通过该工程读者可以掌握寄存器配置、定时器周期设定与中断服务程序编写以及外部事件触发时的快速响应机制为后续学习更复杂的嵌入式系统开发打下基础其中涉及的裸机配置思路对扩展学习串口、ADC 等外设同样有参考意义。1. 为什么 LPC2131/LPC2132 的 GPIO、TIMER、EXINT 要一起配置看到这个标题先别把 GPIO、TIMER、EXINT 当三个独立外设。实际项目里它们通常串成一条链外部中断 EXINT 负责抓输入沿定时器 Timer 负责在中断里打时间戳GPIO 负责输出响应缺一环就得靠主循环轮询硬扛。LPC2131 与 LPC2132 同属 NXP 的 LPC21xx ARM7 家族Flash 与 RAM 容量不同但外设寄存器布局完全一致LPC2132 上调通的 timer 代码可以直接烧进 LPC2131。两个型号在寄存器级可以当成同一颗芯片处理。这篇文章写给要在 LPC2131/LPC2132 上做裸机采集与控制、或维护老产线代码的人。按寄存器级把 GPIO、定时器 timer、EXINT 三条外设链路和 VIC 中断接线讲透最后给出 EXINT 抓沿、Timer 记时间戳、GPIO 翻转验证的组合例程。2. LPC2131 GPIO 寄存器配置PINSEL、IODIR 与 8 种工作模式对照LPC2131 的 GPIO 比 STM32 简单但没有 HAL 封装寄存器全挂在 0xE002C000 起的外设空间里。先把地址和位定义记牢后面所有例程都依赖这套宏。2.1 先回答 GPIO 是什么引脚复用寄存器 PINSEL 与方向寄存器 IODIRGPIO 是什么在 LPC2131 上GPIO 就是一组内存映射寄存器IO0PIN 读引脚电平IO0SET 写 1 置高IO0CLR 写 1 拉低IO0DIR 控制方向。但每个引脚默认不是普通 IO得先在 PINSEL0/1/2 里把对应 2bit 清成 00才能当作 GPIO 用。PINSEL 每个引脚占用 2bit00 是 GPIO01/10/11 对应三组复用功能。比如 P0.16 在 PINSEL1 的低 2bit00 是 GPIO01 是 EINT0 外部中断10 是 MAT0.211 是 CAP0.2。这就是「gpio 的 8 种工作模式」在 LPC 上的等价物LPC 没有输入浮空/上拉/下拉那套细分模式选择被拆成两个维度——复用由 PINSEL 决定上下拉由 PINMODE 决定。注意地址别记混。PINSEL00xE002C000IO0PIN0xE002C010IO0DIR0xE002C018。老例程里常见把 PINSEL 地址当 GPIO 基址用读回来全是 0先从这儿开始排查。2.2 按键输入 LED 输出GPIO 方向设置的最小编译单元#define PINSEL0 (*(volatile unsigned long *)0xE002C000) #define PINSEL1 (*(volatile unsigned long *)0xE002C004) #define IO0PIN (*(volatile unsigned long *)0xE002C010) #define IO0SET (*(volatile unsigned long *)0xE002C014) #define IO0DIR (*(volatile unsigned long *)0xE002C018) #define IO0CLR (*(volatile unsigned long *)0xE002C01C) void gpio_init(void) { PINSEL0 ~(3UL 18); /* P0.9 复用位清 0切到 GPIO */ PINSEL1 ~(3UL 0); /* P0.16 复用位清 0切到 GPIO */ IO0DIR ~(1UL 16); /* P0.16 输入接按键 */ IO0DIR | (1UL 9); /* P0.9 输出接 LED */ } unsigned char key_read(void) { return (IO0PIN 16) 1; /* 读引脚电平返回 0/1 */ } void led_write(int on) { if (on) IO0SET 1UL 9; /* 写 1 置高写 0 无动作 */ else IO0CLR 1UL 9; /* 写 1 拉低 */ }逻辑说明PINSEL 每次改引脚只用 ~(3UL n)连带清 2bit避免影响同寄存器的其它引脚。IODIR 是读改写先清输入位再置输出位顺序不能反过来否则中间态会同时输出。IO0SET/IO0CLR 是「写 1 生效、写 0 忽略」整寄存器赋值一位不会误动其它引脚。参数说明3UL 18对应 P0.9 在 PINSEL0 的第 18/19bit3UL 0对应 P0.16 在 PINSEL1 的第 0/1bit。按键若接 P0.16外部记得加上拉或启用内部上拉浮空输入读到的电平会随机跳变。2.3 GPIO 模式如何选择上拉、开漏与 8 种工作模式的差异STM32 按「输入/输出 × 浮空/上下拉/推挽/开漏」拆出 8 种模式LPC2131 没有同名概念但同样的需求可以映射到三组寄存器需求STM32 习惯叫法LPC2131 的做法按键输入外部不加上拉上拉输入IODIR 清 0PINMODE 配内部上拉驱动 LED/继电器推挽输出IODIR 置 1IOSET/IOCLR 控制多机共线、线与逻辑开漏输出输出低时置为输出释放时切回输入慢沿信号整形施密特触发输入输入自带迟滞无独立 SMT 位开漏输出在 LPC2131 上没有硬件位工程做法是软件模拟void od_write(int v) { if (v) { IO0DIR ~(1UL 4); /* 释放总线引脚切输入靠外部上拉拉高 */ } else { IO0CLR 1UL 4; /* 先输出 0 */ IO0DIR | 1UL 4; /* 再切输出拉低总线 */ } }逻辑说明先保证输出寄存器是 0再使能输出避免切输出瞬间吐出一个高电平毛刺。释放时切输入后电平由外部上拉决定这正是 I2C 等开漏总线的时序要求。参数说明内部上拉由 PINMODE0/1/2 控制每引脚 2bit默认 01 是上拉。MTK 平台 GPIO 驱动里常见的 IES 输入使能、SMT 施密特整形两个位在 LPC2131 上没有独立寄存器输入使能由 PINSEL 决定迟滞在模拟输入端自带慢沿信号更可靠的出路是走 EXINT 或加 RC 整形。对 5 年以上工程师多说一句gpio 模式如何选择核心不是背模式表而是判断「谁来提供默认电平」——上拉给高、下拉给低、开漏靠外部电阻这个判断在任何平台都通用。提示寄存器地址写错时读 IO0PIN 会稳定读到 0症状和上层「timer 执行查询是报空指针」几乎一致能编译、能跑就是逻辑全错。排查第一步永远是核对宏定义的地址。3. LPC2132 定时器 Timer0/Timer1 的计数链路与匹配中断3.1 PR、PC、TC、MR、MCR 五个寄存器怎么配合LPC2132 的定时器 timer 是 32 位计数器工作链路是一条链外设时钟 PCLK 先过预分频 PR预分频计数器 PC 每计满 (PR1) 次主计数器 TC 加 1。TC 和 4 个匹配寄存器 MR0~MR3 比较命中后按 MCR 配置执行中断、复位或停止。寄存器作用T0TCRbit0启动计数bit1复位 TCT0PR预分频值TC 每 (PR1) 个 PCLK 加 1T0PC预分频计数器硬件自增不可写T0TC32 位主计数器匹配复位前一直累加T0MR0~MR34 路匹配值T0MCR匹配动作bit0/3/6/9 中断bit1/4/7/10 复位bit2/5/8/11 停止T0CCR / T0CR0~CR3捕获配置与捕获快照用过 STM32CubeMX 的 timer 配置页再回来写 LPC2132最不适应的就是没有图形界面帮你算预分频和自动重载。好在公式只有一行中断周期 (PR1) × (MR01) / PCLK。要 1ms 周期且 PCLK60MHz通常取 PR0、MR059999两个参数留一个冗余方便后面跑高频输出时只改 MR0。注意LPC213x 的 Timer0/Timer1 只能对 PCLK 计数没有独立的「外部脉冲计数模式」。想数外部脉冲要么用捕获通道对边沿打时间戳要么走 EXINT 在中断里加计数器。这是 LPC21xx 和后面 LPC23xx 最大的区别老工程师也常在这里翻车。3.2 用 Timer0 搭 1ms 时基并注册到 VIC#define T0IR (*(volatile unsigned long *)0xE0004000) #define T0TCR (*(volatile unsigned long *)0xE0004004) #define T0PR (*(volatile unsigned long *)0xE000400C) #define T0MCR (*(volatile unsigned long *)0xE0004014) #define T0MR0 (*(volatile unsigned long *)0xE0004018) #define PCLK 60000000UL /* PLL 与 VPBDIV 配置后的外设时钟 */ static volatile unsigned long sys_tick; /* 1ms 递增的软件时钟 */ void timer0_init(void) { T0PR 0; /* 1 分频TC 每个 PCLK 加 1 */ T0MR0 PCLK / 1000 - 1; /* 1ms 匹配值-1 是因为从 0 开始计 */ T0MCR 0x03; /* bit0匹配进中断bit1匹配后 TC 清零 */ T0IR 0xFF; /* 写 1 清掉全部中断标志 */ T0TCR 0x01; /* TCR bit01 启动计数 */ }中断函数和 VIC 登记#define VICIntEnable (*(volatile unsigned long *)0xFFFFF010) #define VICVectCntl0 (*(volatile unsigned long *)0xFFFFF200) #define VICVectAddr0 (*(volatile unsigned long *)0xFFFFF100) #define VICVectAddr (*(volatile unsigned long *)0xFFFFF030) #define IRQ_SLOT_TIMER0 4 /* 多数例程用 4最终以本机 VIC 通道表为准 */ static void __irq timer0_irq(void) /* __irq 是 armcc 关键字 */ { if (T0IR 1) { T0IR 1; /* 写 1 清 MR0 标志漏清会不停进中断 */ sys_tick; } VICVectAddr 0; } void vic_timer0_enable(void) { VICVectCntl0 (1 5) | IRQ_SLOT_TIMER0; /* slot0 使能绑定 Timer0 */ VICVectAddr0 (unsigned long)timer0_irq; VICIntEnable 1 IRQ_SLOT_TIMER0; }逻辑说明T0MCR0x03 同时打开「匹配进中断」和「匹配后复位」TC 到达 MR0 自动归零下一周期重新计形成连续 1ms 中断。ISR 里判断 T0IR 的 bit0 再写 1 清除是 LPC 系列中断标志「写 1 清零」的典型写法。末尾必须写 VICVectAddr0否则 VIC 认为中断没处理完同优先级中断全部卡死。参数说明IRQ_SLOT_TIMER0 在 LPC2131/2132 的常见例程里是 4不同批号或换了 LPC214x 可能差 1移植时先查 UM10120 的 VIC 通道表。中断不进来而程序其它部分正常先怀疑这个数。GCC 工具链把__irq换成语义等价的__attribute__((interrupt(IRQ)))即可逻辑不受影响。3.3 捕获模式测脉宽和沿间隔不占 CPU如果需求是测一个脉冲的高电平宽度用匹配中断去轮询电平是错误的姿势。正确答案是捕获CCR 配置边沿捕获发生时硬件把 T0TC 快照进 CR0同时 T0IR 的 bit4 置位全程不需要主循环参与。#define T0CCR (*(volatile unsigned long *)0xE0004028) #define T0CR0 (*(volatile unsigned long *)0xE000402C) static unsigned long last_cap, width_ticks; static void __irq cap0_irq(void) { unsigned long cur T0CR0; /* 硬件快照读一次即可 */ if (T0IR 0x10) { T0IR 0x10; /* 清 CR0 捕获中断标志 */ width_ticks cur - last_cap; /* 无符号差天然处理回绕 */ last_cap cur; } VICVectAddr 0; } void cap0_init(void) { /* 先把 P0.2 配成 CAP0.0PINSEL0 对应 2bit 的编码查引脚描述表 */ T0CCR 0x07; /* bit0上升沿捕获 bit1下降沿捕获 bit2捕获中断 */ T0IR 0xFF; T0TCR 1; }逻辑说明同时使能上升沿和下降沿每个边沿都触发一次中断相邻两次 CR0 差值就是半周期即高电平宽度。用无符号减法cur - last_capTC 回绕自动正确不用额外判大小。捕获的优势是硬件打点ISR 只做一次寄存器读时间戳抖动只有中断响应延迟那几十个周期。参数说明T0CCR 的 bit2 是「捕获即中断」如果只记录不中断置 0 后主程序轮询 T0IR 的 bit4 也能读但轮询会错过窄脉冲。测更长的周期时把 PR 调大可以降低计数频率、延长 TC 溢出时间代价是时间分辨率下降这个权衡没有免费午餐。4. LPC213x EXINT 外部中断EXTINT、EXMODE 与 VIC 向量接线4.1 EXTINT 与触发方式电平触发、边沿触发的寄存器选择EXINT 有 4 个引脚对应三个寄存器EXTINT 是挂起/清除寄存器EXMODE 选电平还是边沿EXTPOLAR 选极性。三者都按位对应 EINT0~EINT3。寄存器bit n0bit n1EXMODE电平触发边沿触发EXTPOLAR低电平/下降沿有效高电平/上升沿有效EXTINT无挂起有挂起写 1 清除最容易踩的坑是复位默认值。LPC213x 复位后 EXMODE0、EXTPOLAR0等效低电平触发而 EINT 引脚的空闲电平由外部电路决定。如果外部平时是低电平、事件时拉高上电瞬间中断标志就会被置位。经验法则尽量用边沿触发并把极性设成与空闲电平相反。另外 INTWAKE 寄存器可以控制在掉电模式下哪些 EINT 能唤醒芯片做低功耗的老产品会用到。4.2 LPC2131 EXINT 完整配置从 PINSEL 复用到 VIC 向量注册以 P0.16 的 EINT0 为例完整初始化代码如下。#define EXTINT (*(volatile unsigned long *)0xE01FC140) #define EXMODE (*(volatile unsigned long *)0xE01FC148) #define EXTPOLAR (*(volatile unsigned long *)0xE01FC14C) #define VICIntEnable (*(volatile unsigned long *)0xFFFFF010) #define VICIntEnClr (*(volatile unsigned long *)0xFFFFF014) #define VICVectCntl1 (*(volatile unsigned long *)0xFFFFF204) #define VICVectAddr1 (*(volatile unsigned long *)0xFFFFF104) #define IRQ_SLOT_EINT0 14 /* 常见例程用 14最终以本机通道表为准 */ static volatile unsigned long event_count; void extint_init(void) { PINSEL1 (PINSEL1 ~(3UL 0)) | (1UL 0); /* P0.16 复用为 EINT0 */ EXMODE 0x01; /* EINT0 边沿触发 */ EXTPOLAR 0x01; /* EINT0 上升沿触发 */ EXTINT 0x0F; /* 一次清掉 4 个挂起位防止残留 */ VICVectCntl1 (1 5) | IRQ_SLOT_EINT0; VICVectAddr1 (unsigned long)eint0_irq; VICIntEnable 1 IRQ_SLOT_EINT0; } static void __irq eint0_irq(void) { EXTINT 0x01; /* 写 1 清 EINT0 挂起 */ event_count; VICVectAddr 0; }逻辑说明EXMODE 和 EXTPOLAR 只写 bit0其它 EINT 位保持默认避免改到没用的三路。清中断用 0x0F 一次清四个实际项目里只用了 EINT0写 0x01 更严谨。最后同样写 VICVectAddr0 释放向量。参数说明IRQ_SLOT_EINT0 在 LPC2131/2132 常见例程里是 14换型号可能差一位这是必须查表的原因。判断方法拉低信号后中断只进一次说明通道号正确一次都不进说明通道号和使能位没对上改宏一处即可不用动 ISR。注意EXTINT 清标志的要求与 EXMODE 相关。电平触发模式下如果信号电平还没恢复写了 1 也清不掉中断会立刻再次进入表现为「清不完、关不掉」。这是 LPC21xx 上排第一位的 EXINT 故障现象怀疑它之前先确认用的是边沿触发。4.3 边沿触发与软件消抖ISR 里只记账主循环里处理外部中断最陡的坑不是寄存器而是机械抖动。按键抖动会产生一串沿每个沿都会进 ISR直接在 ISR 里延时 10ms 是常见误用阻塞了其它中断还解决不了问题。惯例是 ISR 只置「事件发生」标志主循环看到标志后用定时器窗口去重新使能 EXINT。static volatile int event_flag; /* 主循环或低优先级任务里做消抖确认 */ if (event_flag) { event_flag 0; VICIntEnClr 1 IRQ_SLOT_EINT0; /* 先关 EXINT */ delay_ms(10); /* 跳过抖动窗口 */ if (key_read()) { /* 再确认一次电平 */ led_write(1); /* 有效沿GPIO 响应 */ } EXTINT 0x01; /* 清掉抖动期间积攒的挂起 */ VICIntEnable 1 IRQ_SLOT_EINT0; /* 重新打开 */ }逻辑说明关中断的瞬间EXTINT 挂起位里可能已经积了抖动产生的多个沿所以重新使能前要读一次 EXTINT 并写 1 清除否则一开中断又连续进入。VICIntEnClr 是独立的「写 1 清除使能」寄存器比读改写 VICIntEnable 安全避免竞态。参数说明10ms 窗口来自普通按键弹片的实测经验机械开关一般 5~20ms 内稳定。短按测试时把窗口调到 5ms响应会明显变快工业旋钮或微动开关建议 20ms误触率优先。5. 三合一实战EXINT 抓沿、Timer 记时间戳、GPIO 输出验证5.1 一条链EXINT 抓沿、Timer 打点、GPIO 翻转把前面三章凑到一起P0.16 作为 EINT0 抓外部脉冲的上升沿ISR 里直接读 T0TC 拿到时间戳两次沿的时间差就是脉冲间隔GPIO 收到间隔异常时翻转 LED。EXINT 负责边缘检测、Timer 提供计数量纲、GPIO 做现场指示一条链全部跑通。#define T0TC (*(volatile unsigned long *)0xE0004008) static volatile unsigned long last_tc, gap_ticks, event_seq; static void __irq eint0_irq(void) { unsigned long now; EXTINT 0x01; /* 先清挂起电平模式下必须最先做 */ now T0TC; /* 时间戳定时器计数器的当前值 */ gap_ticks now - last_tc; /* 两次沿之间的 tick 数 */ last_tc now; event_seq; VICVectAddr 0; }逻辑说明gap_ticks 就是主循环要判断的脉冲间隔。now - last_tc的无符号减法处理了 TC 回绕这是 32 位计数器测间隔的通用写法不要写成有符号比较。ISR 里只做读寄存器和减法不调函数沿间隔测量不受打印之类阻塞影响。把 LED 响应换成电机驱动使能脚就是云台转动控制里最常见的 GPIO 触发写法。参数说明换算成毫秒用之前的 1ms 时基gap_ms gap_ticks / (PCLK / 1000)。PCLK 必须和 timer0_init 里的数值同源主频改了这里不改测出的间隔会整体偏移好几倍——这是「定时器不准」最常见的原因。5.2 用 1Hz 自检源验证沿间隔抖动没有示波器时老工程师的做法是用一个已知信号源打进去。把 P0.9 的输出和 P0.16 的输入用跳线短接在 timer0 的 ISR 里翻转 P0.9产生 1Hz 方波然后让 EXINT 抓这个信号打印 gap_ms。1Hz 方波的沿间隔应当稳定在 1000ms偏差超过 2% 先查 PCLK 和 VPBDIV而不是怀疑 EXINT。static volatile unsigned int phase; static void __irq timer0_irq(void) { if (T0IR 1) { T0IR 1; sys_tick; if (phase 500) { /* 500ms 到达翻转一次 P0.9 */ phase 0; if (IO0PIN (1UL 9)) /* 当前是高就拉低 */ IO0CLR 1UL 9; else /* 当前是低就拉高 */ IO0SET 1UL 9; } } VICVectAddr 0; }逻辑说明phase 从 0 数到 499 正好 500msP0.9 每 500ms 翻转一次输出周期 1s 的方波。用 IO0PIN 读当前输出电平来决定置高还是拉低是为了不引入额外的状态变量在 ISR 里读 IO0PIN 开销极小。参数说明这个自检源依赖 Timer 的 1ms 基准所以它验证的是「Timer 基准 EXINT 抓沿 GPIO 翻转」整条链路的一致性而不是绝对精度。想要绝对精度用信号发生器或晶振分频做信源。60MHz PCLK 下理论分辨率约 16.7ns实测干净的沿间隔抖动通常在 1~2 个 tick这已经逼近中断响应延迟的物理下限。要再压就只能上捕获通道把时间戳交给硬件去记ISR 里连 T0TC 都不用读了。本文还有配套的精品资源点击获取