ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

LPC1114例程详解:寄存器操作、UART与定时器实战指南

LPC1114例程详解:寄存器操作、UART与定时器实战指南 简介面向嵌入式入门与进阶开发者这份LPC1114例程与教程合集以NXP Cortex-M0内核芯片为主线系统讲解外设配置与典型应用可广泛用于物联网节点、智能家居与教学实验等场景。资源源自《LPC1114芯片基础教程与应用实践》覆盖GPIO按键中断、UART串口通信、ADC模拟采集、PWM输出、NTC测温、RGB灯控制、旋转编码器等常见模块并展示GPS、Flash、SPILCD等综合项目适合对照学习裸机开发和工程搭建流程。包内共692个文件含大量c/h源文件、uvproj/uvopt等Keil工程配置、axf/hex烧录文件及lst/map编译输出另有txt说明、htm文档、PDF教程和示例图片便于边看边练。压缩包仅6.61MB整体轻量精简适合快速阅读与工程复用已有391人学习下载。通过阅读教程文档并结合工程例程可快速掌握LPC1114的初始化、时钟配置、中断处理和串口通信等核心技能也能以完整工程为模板迁移到实际开发中并据此扩展更多功能。1. LPC1114 例程与教程先别抄代码确认你在给谁写程序LPC1114 的例程和 STM32 那类 HAL 库风格完全不同很多从 GitHub 上下到的 LPC1114 教程代码打开后发现全是寄存器操作连外设结构体都要自己对着手册填。这颗 Cortex-M0 芯片主频 50MHz、Flash 和 RAM 都不大但它在工控小模块、传感器采集和低成本串口设备里仍然大量服役尤其适合那些不需要跑协议栈、只要 GPIO 加 UART 就能交付的场景。这篇内容按“先看懂例程结构再搭最小工程接着调 UART最后用定时器反向验证时钟”的顺序展开目标是让你拿到任意一份 LPC1114 例程都能在三十分钟内定位到 main 和外设初始化并且照着改出自己能用的版本。2. LPC1114 例程包里有什么从启动文件到 main 的调用链看例程先看目录。一份完整的裸机工程通常由四个文件组成启动文件、时钟初始化文件、外设头文件和应用程序。以 Keil 工程为例编译入口是启动文件它负责建立中断向量表、初始化堆栈然后调用 SystemInit()再跳进 main()。很多人把精力全放在 main 里的业务代码结果 UART 收发不对、延时不对最后发现是启动文件和时钟没配对。2.1 例程文件分工与常见命名文件名作用改错之后的表现startup_LPC111x.s中断向量表、堆栈初值和 Reset_Handler编译能过进不了 main或中断服务函数不生效system_LPC11xx.cSystemInit()、SystemCoreClock 变量延时比例全错UART 波特率全错LPC11xx.h芯片寄存器结构体和地址映射寄存器名对不上光标悬停没有类型信息main.c / 外设驱动业务逻辑和外设初始化直接看现象表现各异拿到例程先建立一个习惯把 system_LPC11xx.c 里的 SystemInit() 和 SystemCoreClock 的赋值看成整份代码的“原点”。例程里所有基于时间的操作SysTick、UART、定时器都默认用了这个全局变量。如果 SystemCoreClock 写的是 48000000而实际外部晶振只有 12MHz 且 PLL 没有真正把频率提上去那么点灯延时和串口波特率就会变成另一个值。2.2 时钟树是 LPC1114 例程的第一个分水岭LPC1114 内部有一个 12MHz IRC 振荡器很多例程因为板子小、走线短干脆直接用 IRC这也是一份例程能跑但串口 115200 乱码的根本原因——12MHz 时钟做 115200 波特率会产生接近 8.5% 的误差。例程里如果出现LPC_SYSCON-SYSPLLCTRL、SYSPLLCLKSEL这类寄存器说明作者打算启用 PLL把核时钟拉高到 48MHz 或 50MHz这时需要结合板载晶振计算倍频分频比。还有一个非常值得留意的寄存器LPC_SYSCON-SYSAHBCLKDIV。这个分频器决定 CPU 和外设总线最终拿到多少频率。很多教程只写了 PLL 设置却忽略了 SYSAHBCLKDIV 仍保留在默认除法链上导致 SystemCoreClock 和实际 CPU 频率对不上。遇到例程里“改了波特率计算值但是串口依然乱码”的问题先查这条链路不要一上来就怀疑芯片坏了。2.3 用 Git 管理例程而不是靠文件名区分版本我一般在 GitHub 上搜索 LPC1114 例程时会先看仓库的提交记录NXP 的老例子散在早期 LPCXpresso 项目里很多作者把寄存器定义升级过。下载回来之后用 git 初始化一次把所有原始文件作为第一个提交之后每改一个外设就单独提交一次。这样一旦把某个寄存器改坏git diff能直接看到改动点不至于对着三份相似的 main.c 找哪份才是能用的。这里不涉及复杂的 git 用法只需要git init、git add、git commit三个命令配合git diff看差异即可。注意如果你的 Keil5 还没装 LPC1100 系列的 Device Family Pack后面无论打开哪个例程Device 下拉框里都找不到 LPC1114先回安装流程把 pack 补上。3. 用 Keil5 创建 LPC1114 最小工程GPIO 点灯与 SysTick 延时不要直接照抄网上的工程文件我建议还是自己建一次最小工程这样后边换芯片型号、换封装、换外部晶振时才知道哪里需要动。Keil5 新建工程时在 Device 搜索框输入 LPC1114选择 LPC1114FBD48/302 或者其他和你手中芯片后缀一致的型号然后从 Pack 里勾选 CMSIS_CORE 和 Device:Startup。如果找不到这两个组件说明 Pack 没有装对。启动文件在 Keil 里通常以 Startup 形式出现它会自动匹配芯片的向量表。3.1 最小工程必须要有的三个文件工程建立之后至少保证项目里有startup_LPC111x.s、system_LPC11xx.c和main.c。Startup 文件负责把所有异常和中断的服务函数符号名暴露出来比如SysTick_Handler、UART_IRQHandler、TIMER32_0_IRQHandler你写的 C 函数如果名字和向量表里不一致就会静默地跑到死循环里没有任何编译报错。这也是例程教程里最隐蔽的一个坑。3.2 GPIO 点灯代码先把最小闭环跑起来以下代码按 CMSIS 头文件LPC11xx.h的寄存器命名来写用 PIO1_10 做示例输出脚实际引脚根据自己的板子修改。#include LPC11xx.h // 简单的毫秒级延时使用 SysTick void delay_ms(uint32_t ms) { SysTick-LOAD SystemCoreClock / 1000 - 1; // 1ms 需要的倒数周期数 SysTick-VAL 0; SysTick-CTRL 0x05; // 使能 SysTick时钟源选处理器时钟 while (ms) { if (SysTick-CTRL (1 16)) { // COUNTFLAG 置位表示倒计数到零 ms--; } } SysTick-CTRL 0; // 关闭避免干扰后续外设 } int main(void) { // 打开 GPIO 模块的 AHB 时钟门控bit6 对应 GPIO LPC_SYSCON-SYSAHBCLKCTRL | (1 6); // 把 PIO1_10 配置为输出方向寄存器 bit10 写 1 LPC_GPIO-DIR | (1 10); while (1) { LPC_GPIO-DATA | (1 10); // 输出高 delay_ms(200); LPC_GPIO-DATA ~(1 10); // 输出低 delay_ms(200); } }代码逻辑很简单但有两个参数值得强调。SystemCoreClock不是编译期宏它来自 system_LPC11xx.c启动阶段由 SystemInit() 赋值如果外部晶振或 PLL 配置变了必须同步修改这个全局变量否则 LOAD 算出来的时间就是错的。LPC_GPIO-DATA是整组 IO 口的读写窗口对单个 bit 做读改写如果并发中断里也在动同一个 IO 口会出现丢位问题常见做法是把要改的引脚掩码放入写路径或用预计算的整组数据一次性写入 DATA。3.3 编译烧录的成功标准与常见失败下载前检查 Target Options 里的 Debug 页选择实际使用的下载器。CMSIS-DAP 或 J-Link 都支持 SWDLPC1114 的 SWD 引脚一般在 PIO0_5 和 PIO0_6 上。如果点击 Load 后报Flash Download failed先看 Flash 算法列表里有没有 LPC1114 32KB Flash 对应的算法。这个算法在早期 Keil 里经常需要手动 Add。另一个更隐蔽的情况是低功耗模式下 SWD 被关闭或者复位引脚被复用成 GPIO这时要先把复位线接上或者在 Options 里把 Reset and Run 取消用“按住复位再点下载”的方式拉回可下载状态。烧录成功后不要急着调串口先确认灯在闪而且单次闪动周期大约 400ms亮 200ms 灭 200ms。如果灯常亮或完全不闪优先用调试器在 SystemInit() 和 main() 入口打断点确认复位后是否走到用户程序。这个闭环建立起来后面所有外设例程都可以在这个工程上叠加。4. LPC1114 串口例程UART 初始化、波特率误差与中断收发点灯通过之后一般第二个需求就是串口。LPC1114 的 UART 是 16C550 兼容设计寄存器风格和 STM32 的 USART 很不一样很多人照 STM32 的思路去找 BRR 寄存器结果发现根本没有。这里只有 DLL/DLM、THR、RBR、LSR、IIR 这一套老式寄存器。先记住一个原则UART 的时钟门控、引脚复用、波特率计算三个步骤按顺序做。4.1 初始化顺序里最容易漏掉的时钟门控UART 模块的时钟由SYSAHBCLKCTRL控制bit11 通常对应 UART。如果不先打开这个位后面的寄存器写入全部不生效现象是波特率寄存器写进去读回来还是 0。打开时钟之后还需要通过LPC_IOCON配置引脚复用功能。LPC1114 例程中最常见的串口引脚是 PIO1_6 和 PIO1_7FUNC 位写 1 时分别映射到 UART_RXD 和 UART_TXD。如果板子的 USB 转串口线接的是 PIO1_6/7就直接可以连如果板子用的是下载口复用的 UART要去查原理图确认引脚编号。4.2 波特率计算的三个数值UART 波特率公式是 PCLK / (16 * baud)PCLK 来自主时钟再除以UARTCLKDIV。这个公式里的 16 是 16C550 的过采样系数不能省。LPC1114 的系统时钟主要就两种场景12MHz IRC 直跑以及 48MHz PLL 后的总线时钟。差异非常明显系统时钟目标波特率分频计算值取整数实际波特率误差12 MHz960078.1257896150.16%12 MHz1152006.5161250008.5%48 MHz9600312.531296150.16%48 MHz11520026.04261153840.16%这就是为什么 LPC1114 老例程默认用 9600在 12MHz IRC 下 115200 的误差大到无法握手。如果你拿到一份教程代码默认 115200先确认它是否启用了 PLL 并让 SystemCoreClock 走到 48MHz如果内部 IRC 直跑改回 9600 是更稳妥的做法。4.3 可抄的 UART 中断回显例程下面代码在上一章点灯工程的基础上增加一个串口接收中断收到一个字节后原样回发。发送路径采用轮询 THRE 标志接收路径走 UART_IRQHandler。完整初始化函数如下void uart_init(uint32_t baud) { uint32_t div; // 1. 打开 UART 和外设引脚的时钟bit11 是 UART LPC_SYSCON-SYSAHBCLKCTRL | (1 11); LPC_SYSCON-UARTCLKDIV 1; // 不分频 // 2. 引脚复用 LPC_IOCON-PIO1_6 0x01; // RXD LPC_IOCON-PIO1_7 0x01; // TXD // 3. 写波特率先使能 DLAB再写 DLL/DLM最后关 DLAB LPC_UART-LCR 0x83; // 8 位数据1 停止位DLAB1 div (SystemCoreClock / LPC_SYSCON-UARTCLKDIV) / (16 * baud); LPC_UART-DLL div 0xFF; LPC_UART-DLM (div 8) 0xFF; LPC_UART-LCR 0x03; // 关 DLAB线控制恢复 8N1 // 4. FIFO 和中断使能 LPC_UART-FCR 0x07; // 使能 FIFO复位收发 FIFO LPC_UART-IER 0x01; // 允许接收数据可用中断 NVIC_EnableIRQ(UART_IRQn); // LPC1114 的 UART 中断号 } void uart_putc(uint8_t c) { while (!(LPC_UART-LSR (1 5))); // 等待 THR 为空 LPC_UART-THR c; } void UART_IRQHandler(void) { uint8_t ch; if ((LPC_UART-IIR 0x06) 0x04) { // IIR 位 3:1 表示 RDA ch LPC_UART-RBR; // 读数据同时清接收中断 uart_putc(ch); // 回发 } }有几个参数要特别说明。LPC_UART-LCR低三位不是使能位而是数据长度编码0x03 表示 8 位数据写波特率时必须让 DLAB1写完后一定要关掉 DLAB否则后续 THR 会写不进去。LPC_UART-IER的 bit0 是接收数据可用中断如果以后要开发送完成中断再置 bit1。回显函数里的uart_putc是阻塞的字符量不大时没问题但如果要做大量发送建议先做一版环形缓冲区再接中断写出。还有一个例程移植中的差异点不同版本的 LPC11xx.h 对 UART 中断号的命名可能不同有的版本把弱实现写成默认别名。遇到“编译通过但串口进不了中断”的情况第一步去启动文件里找UART_IRQHandler这个名字确认和你的函数名完全一致。5. 用 32 位定时器例程验证时钟500Hz 方波该不该信前面几章都在“用例程”这一章讲“反推验证”的办法。很多 LPC1114 教程里都有定时器例程但直接把MR0填一个固定常量如 5000这在不同的 SystemCoreClock 下结果完全不一样。真正可移植的写法是让MR0跟着 SystemCoreClock 走然后用外部测量结果反过来确认这个系统时钟变量是否和数据手册一致。5.1 让定时器输出 1ms 中断并翻转 GPIOvoid TIMER32_0_IRQHandler(void) { if (LPC_TIMER0-IR 1) { LPC_TIMER0-IR 1; // 写 1 清除中断标志 LPC_GPIO-DATA ^ (1 10); // 翻转 LED 引脚 } } void timer32_0_init(void) { // 打开定时器模块的 AHB 时钟不同头文件里可能是 TIMER0 或 TMR32B0 LPC_SYSCON-SYSAHBCLKCTRL | (1 14); LPC_TIMER0-TCR 0; // 先停止定时器 LPC_TIMER0-PR 0; // 预分频为 0直接数系统时钟 LPC_TIMER0-MR0 SystemCoreClock / 1000 - 1; // 1ms 匹配值注意减 1 LPC_TIMER0-MCR 3; // MR0 中断使能 计数归零 NVIC_EnableIRQ(TIMER32_0_IRQn); LPC_TIMER0-TCR 1; // 启动 }这里MR0减 1 是很多例程和教程容易踩的小坑。LPC1114 的定时器从 0 递增到 MR0 触发匹配后的周期是 MR0 1 个时钟。如果直接写SystemCoreClock / 1000最终频率会差大约 0.1%点亮 LED 看不出来但用示波器量频就能看到明显的系统误差。LED 引脚每进一次中断翻转一次所以输出的是频率为 500Hz 的方波。用示波器或逻辑分析仪测量 PIO1_10如果量到 500Hz ± 1Hz 以内说明 SystemCoreClock、PLL 和定时器预分频三者是自洽的如果量到 480Hz 甚至更低先回去查 system_LPC11xx.c 里的时钟主频赋值再查外部晶振频率是否和预计一致。更进一步的验证做法是让 SysTick 和 32 位定时器同时运行SysTick 翻转一个引脚定时器翻转另一个引脚观察两个边沿的偏差。偏差纹丝不动说明两边用的是同一个时钟源偏差随时间漂移则板子上大概率存在外部晶振偏频或者 PLL 锁定异常。值得再试的是把PR从 0 改成 99MR0保持SystemCoreClock / 1000 / 100 - 1这样你能得到一个 10kHz 的干净时基后续做软件 PWM 或脉冲计数都会更方便。本文还有配套的精品资源点击获取
返回列表