尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

LPC17XX例程开发指南:从代码骨架到时钟树与移植实战

LPC17XX例程开发指南:从代码骨架到时钟树与移植实战 简介这是一套面向LPC17XX系列MCU开发者的基础例程合集内容覆盖ADC、CAN、DAC、外部中断、GPDMA等常用外设模块并配套Keil工程模板适合刚接触Cortex-M3平台或需要快速搭建项目的嵌入式工程师学习。压缩包共1675个文件大小7.79MB以h头文件、c源码、uvproj/uvopt工程文件为主另含s启动文件、axf可执行文件、map映射文件及部分文档能够完整支撑代码阅读、编译与调试。目前已有405人学习使用。例程对每个外设都给出了初始化配置、数据收发/转换流程和中断处理等关键代码同时提供多个完整的Keil模板项目可直接修改复用通过研读这些代码可以理解LPC17XX的寄存器操作和开发流程减少项目起步阶段的摸索成本。 做嵌入式的人谁手里没几个芯片的例程包但“有例程”和“能用好例程”完全是两码事。尤其是LPC17XX这种Cortex-M3时代的老将官方例程包的风格跟STM32的标准库差别非常大我第一次拿到lpc17xx例程时第一反应是“这main函数怎么这么空初始化代码跑哪去了”后来才发现人家把大头都塞进了SystemInit和驱动库里不懂这套结构点灯都能卡半天。这篇就把我基于LPC17XX例程做开发的经验一次讲透从代码骨架、时钟树、外设改造到移植翻车点全是一条条踩出来的干货。1. 例程包先别急着编译先看懂代码骨架再动手1.1 目录结构里藏着LPC17XX的软件分层逻辑一份完整的LPC17XX官方例程包目录一般分成三块CMSIS层、驱动层、examples应用层。CMSIS层是ARM官方的Cortex-M3内核标准头文件负责寄存器地址映射、中断向量、系统节拍这些基础定义驱动层是NXP自己写的外设驱动文件名基本是lpc17xx_gpio.c、lpc17xx_uart.c、lpc17xx_timer.c这种examples才是你真正要看的应用代码。最容易被忽视的文件是startup_LPC17xx.s这是启动文件里面定义了复位向量、中断向量表和堆栈初始化。很多新手改例程时直接把中断服务函数写进main.c结果编译通过、运行没反应就是因为向量表里没有对应入口中断根本没接到你的函数上。老例程包里的中断处理函数名是固定的比如UART0_IRQHandler你写自己的实现时名字必须一字不差编译器才会用你的函数替代启动文件里的默认弱定义。1.2 寄存器直操作和驱动函数为什么同时存在看LPC17XX例程代码时会有一种精分感有的地方直接写LPC_GPIO0-FIODIR 0xFF有的地方又调GPIO_SetDir()。这不是代码风格混乱而是NXP想同时照顾两类用户——寄存器级用户和库函数用户。我的经验是调试阶段优先用驱动函数因为参数含义清楚、不容易写错性能敏感的中断服务里再用寄存器直操作省去函数调用开销。以GPIO为例LPC_GPIO0-FIOSET (1 4)和GPIO_SetValue(0, (1 4))效果完全一样前者操作的是端口0的置位寄存器后者内部封装了同样的寄存器操作。看例程时两种写法都要认识别被吓到。1.3 main函数为什么这么“空”LPC17XX例程的main往往就是这么几行调用几个init函数然后进while(1)死循环。真正的系统初始化在SystemInit()里它由启动文件在进入main之前调用负责配置系统时钟和存储加速器。也就是说你还没进mainCPU主频已经被设好了外设时钟也基本有个默认值。有些人拿到例程先找“时钟初始化”却找不到就是这个原因。要看主频配置得打开system_LPC17xx.c里面的SystemCoreClock全局变量存的就是当前内核时钟我在调试串口波特率异常时第一件事就是看这个值跟实际算出来的对不上说明时钟配置和预期不符。2. 点灯例程跑不起来九成卡在时钟树配置2.1 CCLK和PCLK是两套时钟别混为一谈LPC17XX例程里你能看到两个缩写CCLK和PCLK。CCLK是CPU内核和存储器接口的时钟体现在SystemCoreClockPCLK是外设总线时钟由PCLKSEL寄存器对CCLK做分频得到。UART、定时器、SPI这些外设用的都是PCLK而且不同外设组可以有不同的分频系数。知道这个有什么实际用处当你改例程把CCLK从默认的96MHz改成120MHz时如果不重新调PCLKSEL所有外设的PCLK都会跟着变最直观的后果是串口波特率瞬间全错。所以排查例程“为什么能编译但跑起来不对”时先检查PCLK实际值是否和你期望的一致。2.2 官方例程默认的晶振频率很可能和你的板子不一样这是LPC17XX例程移植时最高频的翻车点。官方评估板常用的外部晶振是12MHz但很多第三方核心板、自制板用的是8MHz、10MHz甚至25MHz。晶振频率不同PLL0的倍频系数就必须重新算否则最终的CCLK不是太高就是太低芯片直接死机或者乱跑。还有个更隐蔽的情况外部晶振没起振。LPC17XX例程里的SystemInit依赖外部晶振作为PLL0时钟源如果晶振虚焊、负载电容配错PLL0永远锁不住程序就停在那里出不来。判断方法很简单用调试器连上看程序卡在哪一行。如果正好卡在PLL0锁定等待循环里基本就是晶振问题。2.3 改一个宏定义救回点灯例程好在官方例程包的system_LPC17xx.c顶部通常有这两个宏__XTAL和__SYSTEM_CLOCK前者写的是外部晶振频率后者写的是目标系统主频。把__XTAL改成你板子上实际的晶振频率再确认__SYSTEM_CLOCK在你芯片允许的主频范围内重新编译就解决了大部分“点灯不亮”的问题。举个例子我手头一块板子外部晶振是8MHz例程默认12MHz直接把__XTAL从12000000改成8000000目标主频保持96MHz不变编译下载后LED和串口全正常。这里顺带说一句改完时钟宏之后最好在main开头读一下SystemCoreClock并打印出来确认别再凭感觉猜主频。3. 把点灯例程改造成UART收发一次完整的外设切换3.1 为什么直接写GPIO寄存器还是不亮引脚复用没配很多人把GPIO例程换成自己的引脚后发现LED死活不亮第一反应是代码写错了其实大多卡在引脚复用。LPC17XX几乎每个引脚都有多个功能到底当GPIO用还是当UART、SPI、PWM用由PINSEL寄存器决定每个引脚占用2位00表示GPIO其他值对应不同的外设功能。以UART0的TX为例P0.2的复用配置在PINSEL0的[5:4]两位改成01后P0.2才从GPIO变成UART0的TXD。用官方驱动库时PINSEL_ConfigPin(0, 2, 1)这种函数就是干这个的。我见过不少例程抄过来后PINSEL没配外设怎么调都不工作的情况先查这一项能省一天时间。3.2 UART初始化有三条前置条件少一条都不行在LPC17XX上初始化UART光调UART_Init还不够前面还有两个坑必须处理。第一个是外设时钟门控PCONP。LPC17XX为了省电部分外设复位后并不上电要用哪个外设先把PCONP里对应位置1。例程里如果漏了这一步UART寄存器写得再对外设根本没时钟状态寄存器万年不动。第二个是波特率。官方库的UART_Init(UART_TypeDef *UARTx, uint32_t baudrate, uint32_t pclk)第三个参数必须传准确的PCLK值。如果偷懒传了SystemCoreClock而PCLK实际是CCLK的四分之一波特率就会偏差很大收发全是乱码。我一般直接用UART_GetPCLK()或者根据PCLKSEL实际值算而不是写死数字。3.3 改造步骤从LED静态电平到串口回环把点灯例程改成UART收发流程其实很清晰我在例程基础上按四步走// 第一步打开外设时钟 LPC_SC-PCONP | (1 3); // 使能UART0时钟 // 第二步配置引脚复用 LPC_PINCON-PINSEL0 | (1 4); // P0.2 - TXD0 LPC_PINCON-PINSEL0 | (1 6); // P0.3 - RXD0 // 第三步初始化UART UART_Init(LPC_UART0, 115200, 24000000); // 第三个参数是PCLK实际值 // 第四步收发数据 UART_Send(LPC_UART0, (uint8_t *)hello\r\n, 8, BLOCKING);这里有个细节P0.2和P0.3的PINSEL字段都在PINSEL0内要分别移位到bit[5:4]和bit[7:6]。用“或”操作而不是直接赋值避免把P0.0和P0.1的配置冲掉。改造完先用一个“串口回环”验证收到什么就发什么电脑端串口助手发一条能原样回来就说明收发全通了。3.4 验证时别只看串口助手先把电平量一遍我自己调试时有个习惯先拿示波器或逻辑分析仪看UART TX引脚的空闲电平和起始位。如果TX引脚空闲时不在高电平说明引脚复用根本没生效如果有波形但对端收不到再看波特率是否匹配。这个排查顺序能快速把问题圈定在硬件接线、引脚配置、软件参数三层里比盲目改代码高效得多。4. 把例程移植到自制板卡四个高频翻车点与排查思路4.1 晶振频率和延时函数对不上官方例程的延时函数比如基于SysTick的Delay_Ms内部是用SystemCoreClock算节拍数的。如果你把外部晶振从12MHz换成8MHz__XTAL改了、SystemCoreClock也变了但延时函数里还写死着旧的SystemCoreClock / 1000延时就会整体放大1.5倍。症状表现为串口打印间隔明显变慢PWM频率对不上。排查手段不难直接看延时函数里用的是全局变量SystemCoreClock还是写死的常量。我在多个例程里见过这条写死的代码改成引用全局变量后频率计算就全自动了。4.2 Keil下载失败型号和Flash算法不匹配LPC17XX例程在Keil MDK里编译下载报Cannot access target或者Flash Download failed先别怀疑芯片坏了。最常见的原因是Device型号没选对比如LPC1768被选成了LPC1769Flash容量和地址映射对不上下载算法自然失败。另一个坑是Flash下载算法没选。Keil的Flash Download页面里必须勾选对应器件的Flash算法比如LPC17xx 512KB Flash如果算法缺失或选错下载器能连上但写不进Flash。从例程包移植到自己的工程时这个配置经常被忽略因为例程工程模板里已经配好了新工程却要自己弄。4.3 一个致命的操作把调试引脚复用成GPIO这个坑一旦踩进去会让人以为芯片锁死了但其实可以救。LPC17XX的调试口引脚TCK、TMS、TDI、TDO那一带分布在P1.2x区域默认是调试功能如果你在例程里把它们通过PINSEL配置成了GPIO程序跑起来后调试器握手信号就没法到达内核下次下载时Keil一直报连不上目标。遇到这种情况别急着换芯片。先把板子断电用ISP方式擦除Flash或者按住复位键同时点下载在芯片运行的瞬间抢回控制权。这种方法十次能成功八九次。它也算LPC17XX“抗造”的体现但前提是你知道有这个逃生通道。4.4 ISP下载是自制板最后的救命稻草LPC17XX自带ISP引导程序上电复位时如果P2.10引脚被拉低芯片会进入Boot ROM里的ISP模式通过UART0P0.2/P0.3与PC通信。用得最多的是Flash Magic这个软件选好芯片型号、串口号、波特率就能擦除、编程、校验整片Flash。我在移植例程到自制板时都会特意把ISP进入电路和UART0引脚提前引出来哪怕这次用不上。因为你永远不知道哪次调试会把Flash刷成砖有ISP通道在手例程怎么折腾都不慌。5. 跑通例程之后值得花时间做的调试增强5.1 把printf重定向到串口调试效率翻倍例程自带的串口发送函数用起来不如printf方便尤其打印变量值时要手动转字符串再发送很啰嗦。我拿到LPC17XX例程后第一件事就是把printf重定向到UART0这样随时可以用printf(value %d\r\n, val)输出调试信息。在Keil MDK下需要勾选MicroLIB避免半主机模式然后在工程里加一个fputc实现int fputc(int ch, FILE *f) { while ((LPC_UART0-LSR (1 5)) 0); // 等待THR空 LPC_UART0-THR ch; return ch; }这个函数加好之后记得先完成UART0初始化再调用printf。我的习惯是在main开头加一个printf(\r\n[SYSTEM] Boot OK\r\n)每次上电都能确认串口链路和主频都在预期状态。5.2 在线调试时学会看外设寄存器少打一堆打印LPC17XX例程跑通后我建议你别急着删调试代码而是打开Keil的Peripherals菜单里面能看到GPIO、UART、Timer这些外设的寄存器实时状态。比如UART接收不触发就看LSR的RDR位有没有置1GPIO输出没电平就看FIOPIN当前值到底是多少。这个习惯能帮你建立“代码逻辑”和“硬件电平”之间的对应关系。很多时候例程移植出问题不是代码逻辑不对而是某个配置位被覆盖、某个寄存器没生效打开寄存器窗口一眼就能定位比盲目加打印高效得多。5.3 注意例程里SysTick和看门狗的习惯用法LPC17XX例程里的延时函数大多基于SysTick但每个版本的实现不一样。有的用阻塞轮询有的用中断计数移植时要注意SysTick的中断优先级配置否则它会打断UART接收导致丢数据。还有个细节SysTick的时钟源可以是内核时钟或分频后的时钟不同配置下延时函数里的SystemCoreClock换算关系也要相应调整。至于看门狗官方外设例程通常会单独给出一个喂狗示例但极少开在应用模板里。如果你在产品代码里用了看门狗记得把例程中的喂狗代码挪到主循环同时注意喂狗位置不能放在可能长时间阻塞的分支后面否则例程演示时好好的一上产品就无限复位。LPC17XX这套例程本质上是一个“寄存器加封装”的双层架构看懂它之后不止这个系列再接触NXP后来的LPC43XX、甚至LPC55XX都能很快找到熟悉的软件骨架。把上面这几个环节捋顺——时钟宏、引脚复用、外设时钟门控、调试逃生通道——你手里的例程就不再是只能点的灯而是能真正长出业务逻辑的底子。本文还有配套的精品资源点击获取
返回列表