ARTICLE DETAIL

资讯详情

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

LPC1114例程详解:从GPIO到外设驱动开发实战

LPC1114例程详解:从GPIO到外设驱动开发实战 简介LPC1114是NXP基于ARM Cortex-M0内核的32位微控制器最高主频60MHz集成ADC、UART、I2C、SPI、GPIO等丰富外设以低功耗、高性价比被广泛用于小型控制系统、物联网终端和消费电子产品。资源以《LPC1114芯片基础教程与应用实践》为主线面向嵌入式初学者与进阶开发者通过原理讲解和大量可直接运行的工程例程帮助读者快速搭建开发环境并掌握常用模块编程。压缩包共692个文件以C源码、头文件和Keil工程文件uvproj/uvopt为主同时包含编译生成的axf、hex、lst、map文件以及PDF、TXT、HTML格式文档整体仅6.61MB便于离线查阅和工程对照。内容覆盖GPS、SPI液晶显示、PWM、NTC测温、旋转编码器、RGB灯、按键中断、UART串口等多类实验场景并配有初始化配置、驱动库示例和开发工具链说明从工程结构到编译下载均有完整演示能有效降低入门门槛也可支撑课程设计或小型项目二次开发。目前已有391人学习下载对希望系统掌握Cortex-M0开发流程的读者颇具参考价值。1. 从 axf 文件认识 LPC1114 的例程构成拿到 LPC1114 例程包的时候里面没有一长串 .c 文件而是一堆 .axf 文件LPC1114_PWM.axf、LPC1114_ADC.axf、旋转编码器.axf、UART.axf、LPC1114_RGB.axf、LPC1114_KEY_INT.axf。axf 是 Keil MDK 生成的 ARM 映像包含调试信息和地址映射每个外设都单独编译成了一个可烧录验证的版本。这意味着这套例程的学习路径是从现象往回找逻辑先烧一个 PWM 看波形再对照源码改占空比。LPC1114 是 NXP 的 Cortex-M0 微控制器封装最小到 TSSOP28主频在 50MHz 这一档片内资源不多但引脚定义清晰很适合用来把 GPIO、UART、ADC、PWM、SSP 这几类外设逐一跑通。下文按外设模块展开每一章都给出寄存器级配置、参数选择和实际调试中容易踩的边界。2. GPIO 与 KEY_INT 例程按键中断的时钟陷阱与清中断顺序第一次用 LPC1114 点灯最容易遇到的问题就是“寄存器写了没反应”。原因不在 GPIO 本身而在 SYSAHBCLKCTRL——LPC1114 把外设时钟集中在一个寄存器里管理GPIO 和引脚复用模块 IOCON 的时钟默认都是关闭的。KEY_INT 例程之所以看起来简单是因为它把Gpio_ClockEnable()写在了最前面很多人把它当模板复制却没理解为什么需要同时打开两个时钟位。下面从按键例程出发把配置链路拆开。2.1 先看 SYSAHBCLKCTRL否则 GPIO 写不进LPC1114 的SYSAHBCLKCTRL是系统控制寄存器bit6 对应 GPIO 外设时钟bit16 对应 IOCON 引脚配置时钟。GPIO 的方向、电平、中断寄存器都在 GPIO 外设里IOCON 则控制引脚功能、上下拉和滞回。两个时钟缺少任何一个都可能出现读取始终是 0、写方向位无效的怪现象。初始化代码很短但顺序必须是先打开时钟再写 IOCON。void Gpio_ClockEnable(void) { // bit6GPIO, bit16IOCON SYSAHBCLKCTRL | (1U 6) | (1U 16); }打开时钟之后IOCON 寄存器的读写才生效。注意 LPC1114 的 GPIO 数据寄存器是寄存器组结构GPIO1-DATA的高 16 位按地址线掩码访问例程里如果直接操作GPIO1-DATA | (14)也是可以的但更推荐在初始化时先把方向和初始电平写清楚避免 GPIO 输出端产生多余跳变。2.2 按键输入与 IOCON 上拉配置按键通常一端接地另一端接 MCU 引脚。KEY_INT 例程里我习惯把按键放在 PIO1_4引脚配置成输入内部上拉按下时电平从高跳低触发下降沿中断。如果不内部上拉引脚悬空时电平会在高阻态附近抖动中断会不断误触发。IOCON 的位 [3:4] 用来选择上拉/下拉取0x2表示上拉。#define KEY_PIN 4 void KEY_Init(void) { Gpio_ClockEnable(); // 功能0选择PIO1_4位[3:4]10 使能内部上拉 IOCON_PIO1_4 (1U 0) | (0x2U 3); GPIO1-DIR ~(1U KEY_PIN); // 方向为输入 GPIO1-IS ~(1U KEY_PIN); // 边沿触发模式 GPIO1-IBE ~(1U KEY_PIN); // 单边沿 GPIO1-IEV ~(1U KEY_PIN); // 下降沿触发 GPIO1-IE | (1U KEY_PIN); // 使能中断 NVIC_EnableIRQ(EINT1_IRQn); // GPIO1 对应的中断 }这段代码把边沿选择、单双边沿、触发极性三个寄存器分开写主要是为了可读性。LPC1114 的 GPIO 中断寄存器组包含IS、IBE、IEV、IE、RIS、MIS和IC。IS选择电平还是边沿IBE决定是否双边沿IEV选择高电平/上升沿还是低电平/下降沿。中断使能后硬件会把挂起状态放在RIS屏蔽之后的状态放在MIS。下面是按键初始化的寄存器对照表方便在调试器里对照。寄存器配置值作用IOCON_PIO1_4(10)|(0x23)PIO 功能、内部上拉GPIO1-DIRbit40输入方向GPIO1-ISbit40边沿触发GPIO1-IBEbit40单边沿GPIO1-IEVbit40下降沿GPIO1-IEbit41使能 GPIO1 引脚中断NVICEINT1_IRQn使能 Cortex-M0 中断2.3 中断服务函数先清挂起再做业务PIOINT1_IRQHandler是 GPIO1 的全部引脚共享的中断入口。进入中断后必须从GPIO1-MIS判断是哪一个引脚触发的处理完业务后向GPIO1-IC写 1 清除挂起。如果不写IC这个中断源会一直被置 1CPU 退出中断后立刻再次进入形成死循环一样的忙响应主循环根本没有机会执行。volatile uint8_t key_pressed 0; void PIOINT1_IRQHandler(void) { // MIS 保存屏蔽后的挂起状态 if (GPIO1-MIS (1U KEY_PIN)) { GPIO1-IC (1U KEY_PIN); // 写1清除该中断 key_pressed 1; // 只置标志主循环里再处理 } }中断服务函数里只做“清中断 置标志”这是 LPC1114 这类低主频 MCU 上最稳妥的写法。printf、LCD 刷屏这类耗时操作放进来按键按下 100ms 时中断会占用较长时间UART 接收和定时器中断都会受影响。更合理的做法是主循环读到key_pressed为 1 后启动一个 10ms 的软件定时器再次确认电平仍为低再做按键业务。2.4 去抖与长按检测不要在主循环里忙等机械按键的抖动一般持续 5~20ms常见做法是延时 10ms 再采样。但在按键中断里用for循环延时等于把整个中断优先级拉低到轮询水平。我一般用一个 32 位定时器产生 1ms 时基中断里只记录按键事件和首次触发时间主循环里通过时间差判断是短按还是长按。这样做的好处是旋转编码器和按键共享同一套时间基准代码结构也更统一。提示GPIO0、GPIO1、GPIO2 分别对应 EINT0、EINT1、EINT2 中断源。不要误以为所有引脚都在同一个 NVIC 通道里。3. UART 与 GPS 例程波特率误差和 NMEA 按行解析GPS 例程的入口是 UART.axf本质上就是一个带 FIFO 的串口收发程序。LPC1114 的 UART0 支持 16 字节收发 FIFO配合中断可以在 9600 波特率下不丢字节地接收 GPS 模块的 NMEA 语句。GPS 数据是连续文本流解析的难点不在串口而在“按行切分”和“异常帧处理”。下面先从串口初始化说起。3.1 UART 引脚复用与分频计算UART0 在 LPC1114 上默认复用 PIO1_6 为 TXD、PIO1_7 为 RXD。IOCON 功能值选 1然后把两个引脚从 GPIO 切换成串口功能。串口时钟来自 PCLK分频寄存器是 DLM/DLL 和 FDR。DLAB 位在U0LCR的 bit7需要先置 1 才能访问 DLM/DLL设置完再清零。例程里常用的初始化函数是下面这种写法。void UART0_Init(uint32_t baud) { SYSAHBCLKCTRL | (1U 12); // UART0 外设时钟 IOCON_PIO1_6 0x01; // 功能1: U0_TXD IOCON_PIO1_7 0x01; // 功能1: U0_RXD U0LCR 0x83; // DLAB1, 8数据位,1停止位,无校验 uint32_t div SystemCoreClock / (16U * baud); U0DLL div 0xFF; U0DLM (div 8) 0xFF; U0LCR 0x03; // 关闭DLAB U0FCR 0x07; // 使能FIFO, 接收触发点1字节 NVIC_EnableIRQ(UART0_IRQn); }SystemCoreClock在启动文件里由SystemInit()赋值如果使用内部 RC 再 PLL 到 48MHz这个值通常是 48MHz。div计算的是 16 倍过采样后的分频值所以 9600 波特率对应 48M/(16×9600)312。若使用 11.0592MHz 晶振计算出来的整数分频值对 9600 更友好使用 48MHz 时误差在 0.2% 以内GPS 模块通常能容忍。下面是 48MHz 时钟下常用波特率的分频和误差情况。波特率DLM/DLL 组合值实际波特率误差96000x00 0x013896150.16%384000x00 0x004E384610.16%1152000x00 0x001A1153840.16%如果误差超过 2%就要用U0FDR的小数分频寄存器补偿。U0FDR的高 4 位是 MULVAL低 4 位是 DIVADDVAL配置关系不是简单的除法建议查手册的推荐表。多数例程省略了这一步GPS 模块标准波特率是 9600误差 0.16% 已经足够。3.2 接收中断怎么不丢字节使能 FIFO 后到达触发点的字节会产生接收中断。把触发点设置为 1可以保证 GPS 模块每次发一个字节就进一次中断代价是中断频率高设置为 4 或 8则更省 CPU但缓冲区分组处理更复杂。例程里 9600 波特率每字节约 1.04ms用 1 字节触发点完全足够。volatile char uart_line[128]; volatile uint16_t uart_len 0; void UART0_IRQHandler(void) { // U0LSR 的 bit0 表示接收数据就绪 while (U0LSR (1U 0)) { char ch (char)(U0RBR 0xFF); if (ch \n) { // 一行结束交给解析函数 if (uart_len 0) { GPS_ParseLine((char *)uart_line, uart_len); uart_len 0; } } else { if (uart_len sizeof(uart_line) - 1) { uart_line[uart_len] ch; } else { uart_len 0; // 超长数据直接丢弃 } } } }U0LSR是行状态寄存器bit0 为 1 表示接收缓冲区有数据U0RBR是接收缓冲寄存器。用while循环把所有可用数据读完而不是只读一个字节能避免同一时刻多字节进入 FIFO 时剩下几个触发一次新的中断减少中断反复进出的开销。uart_len清零放在\n后面注意把回车\r也过滤掉GPS 模块每行以\r\n结尾解析时要用\r做结束位置。3.3 NMEA 解析函数先校验再取字段NMEA 语句以$开头*后跟两位校验和。校验和是$和*之间所有字符的异或值。处理 GPS 数据时如果只做字符串匹配遇到校验错误的帧可能会得出错误的定位数据。我一般在解析前先运行校验函数校验通过后再用逗号分隔字段。uint8_t GPS_CheckSum(const char *line) { const char *p line 1; // 跳过 $ uint8_t sum 0; while (*p *p ! *) { sum ^ (uint8_t)*p; } if (*p ! *) return 0; // 没有校验段 uint8_t chk (uint8_t)strtol(p 1, NULL, 16); return sum chk; } char *GPS_Field(char *line, int n) { while (n--) { line strchr(line, ,); if (!line) return NULL; line; } char *end strchr(line, ,); if (end) *end \0; return line; }GPS_Field每调用一次就把当前字段结尾的逗号替换成\0所以不能直接在串口中断里连续调用多次否则会把原始行缓冲区改坏。常见做法是先用校验函数判断把行内容拷到临时数组再在主循环或低优先级代码里调用GPS_Field。定位状态字段在$GPRMC的索引 2 位置取出来判断是否为AA表示定位有效V表示无效。// 调用示例主循环中 if (GPS_CheckSum(line)) { char tmp[128]; strcpy(tmp, line); char *status GPS_Field(tmp, 2); if (status status[0] A) { // 定位有效继续解析经纬度 } }3.4 常见坑波特率不一致和冷启动GPS 模块启动后如果半天没有输出先查串口回环把 TX 和 RX 短接自发自收。如果自己能收到自己发的字符说明 UART 初始化没问题问题在 GPS 模块的波特率配置。另外 GPS 冷启动第一次定位可能需要 30 秒到几分钟例程里看到$GPGSV却一直没有$GPRMC是正常现象不要急着改程序。$GPGSV是卫星视图$GPRMC才是推荐定位数据。4. ADC 与 NTC 测温参考电压、分压电阻和软件滤波ADC 例程在 LPC1114 上很典型10 位转换器12 个输入通道支持单次和突发转换。和一个 10k NTC 热敏电阻搭配就能做成温度采集。难点不在启动转换而在参考电压的波动、NTC 的电阻-温度曲线以及转换结果里夹杂的高频噪声。这一章按“硬件分压→ADC 配置→软件滤波→温度换算”的顺序做。4.1 ADC 时钟上限CLKDIV 不是随便设的LPC1114 的 ADC 时钟最高约 4.5MHz如果直接用 48MHz 做转换结果会不稳定。ADC 的CR寄存器的 bit8~15 是CLKDIV作用是分频分频后的时钟不能超过上限。48MHz 下设置 CLKDIV10ADC 时钟约 4.36MHz符合要求。void ADC0_Init(void) { SYSAHBCLKCTRL | (1U 13); // 使能 ADC0 时钟 // 引脚功能选择为 AD0例如 PIO1_0 IOCON_PIO1_0 0x01; // 通道0, 分频11, 单次转换模式 ADC0-CR (1U 0) | (10U 8); }SEL位 [0] 选择 ADC 通道。这里不使用BURST模式每次读取时手动启动转换写起来更直观。如果改用突发模式ADC0-CR的 bit16 置 1 后 ADC 会持续扫描SEL选中的通道结果不断写入对应的数据寄存器主循环随时读取都是最近一次采样值代价是功耗略高低功耗应用里不建议。4.2 从 ADC_DR 取 10 位结果ADC 数据寄存器ADC0-DR[ch]的 bit31 是转换完成标志bit6 到 bit15 是 10 位转换结果的有效位。也就是说结果要右移 6 位再取低 10 位。如果直接读整个寄存器当作数据会把状态位和溢出标志混进去数值明显错误。uint16_t ADC_ReadChannel(uint8_t ch) { ADC0-CR ~(0xFFU 0); // 清除通道选择位 ADC0-CR | (1U ch); // 选择目标通道 ADC0-CR | (1U 24); // 启动单次转换 while (!(ADC0-DR[ch] (1U 31))); // 等待DONE return (ADC0-DR[ch] 6) 0x3FF; // 取10位数据 }bit24 是软件启动转换位在单次模式下每调用一次触发一次转换。while等待循环在最坏情况下会占用几十微秒如果单片机主循环有严格时序要求可以用中断方式读取LPC1114 的 ADC 中断在 NVIC 中有独立通道。从数据手册的角度看结果寄存器最多就是 10 位不存在 12 位模式看到 12 位寄存器模型的是 LPC15xx别套用。4.3 NTC 分压与温度换算NTC 俗称负温度系数热敏电阻温度升高电阻下降。把 NTC 和固定电阻串联在 3.3V 与地之间ADC 引脚测量中点电压就能根据阻值推算温度。例程里最常见的接法是固定电阻接 3.3VNTC 接地ADC 采样点在两个电阻之间。此时电压关系是V_adc 3.3 * R_ntc / (R_fixed R_ntc)反推 NTC 阻值R_ntc R_fixed * V_adc / (3.3 - V_adc)在代码中可以用 ADC 码直接计算省去先转电压的步骤。假设固定电阻也是 10kΩadc_code是 ADC 读数则float NTCTemp_C(uint16_t adc_code) { float ratio (float)adc_code / (1023.0f - adc_code); float r_ntc 10.0f * ratio; // 固定电阻10kΩ float ln_r logf(r_ntc / 10.0f); // 相对25度的对数比 float t 1.0f / ((1.0f / 298.15f) (1.0f / 3950.0f) * ln_r); return t - 273.15f; }B 值公式是 25℃标称电阻和 B 常数组成的经验公式T 1 / (1/T0 (1/B) * ln(R/R0)) - 273.15。这里 T0 是 298.15KR0 是 10kΩB 取 3950是常见 10k NTC 的参数。该公式在 -20℃ 到 80℃ 范围误差可以控制在 ±1℃ 左右超过该范围建议改用 Steinhart-Hart 三参数查表。下面是用上述公式计算的典型 ADC 码值前提是 10 位 ADC、3.3V 参考电压、固定电阻 10kΩ。温度NTC 电阻ADC 码误差0℃32.6kΩ约 780±0.5℃25℃10.0kΩ5120℃50℃3.60kΩ272±0.8℃如果实际硬件里固定电阻不是 10kΩ表格里的 ADC 码要重新计算。公式里的1023.0f是满量程码换成 4095 就是 12 位 ADC 场景这行代码可以直接复用到其他设备。4.4 软件滤波移动平均和阈值判断ADC 值在读数稳定后仍会有 1~2 个码的随机噪声直接参与温度计算会让最终温度在 0.1℃ 附近抖动。最简单的处理是移动平均滤波保存最近 N 次采样值每次取平均。注意整个缓冲区是无符号 16 位求和变量至少是uint32_t否则 8 个 1023 累加就会溢出。#define ADC_WINDOW 8 uint16_t adc_window[ADC_WINDOW]; uint8_t adc_widx; uint16_t ADC_Filter(uint16_t new_sample) { adc_window[adc_widx] new_sample; adc_widx (adc_widx 1) % ADC_WINDOW; uint32_t sum 0; for (int i 0; i ADC_WINDOW; i) { sum adc_window[i]; } return (uint16_t)(sum 3); // 8 个样本求平均 }窗口越大输出越平滑但对温度变化的响应越慢。例程里测试 4 到 8 个样本效果都不错如果系统里有 50Hz 工频干扰最好让采样间隔取 20ms 的整数倍让滤波窗口能覆盖一个完整工频周期。5. PWM 与旋转编码器例程用定时器和 GPIO 中断各管一头LPC1114_PWM.axf 和旋转编码器.axf 都是“边沿敏感”型外设。PWM 要对输出波形精确控制靠的是 CT32B0 定时器的匹配寄存器旋转编码器则是连续输出的机械脉冲必须靠 GPIO 中断和软件去抖。这两类例程放在一起看恰好能说明为什么单片机上定时器和外部中断不能互相替代。5.1 用 CT32B0 产生三路 PWMLPC1114 的 32 位定时器 CT32B0 有三个匹配输出通道例程里 MR0 做周期基准MR1 和 MR2 做占空比。配置要点是先设置周期再设占空比然后把匹配输出使能到引脚上。顺序反了引脚在初始化到一半时会出现异常电平。void RGB_PWM_Init(uint32_t freq) { SYSAHBCLKCTRL | (1U 9); // CT32B0 时钟 // 引脚复用为定时器输出具体功能号见 LPC1114 数据手册 IOCON_PIO0_8 0x02; // CT32B0_MAT0 IOCON_PIO1_1 0x02; // CT32B0_MAT1 IOCON_PIO1_2 0x02; // CT32B0_MAT2 uint32_t period SystemCoreClock / freq - 1; TIMER32_0-MR0 period; // 周期 TIMER32_0-MR1 period / 3; // 通道1占空比 33% TIMER32_0-MR2 period / 2; // 通道2占空比 50% TIMER32_0-MCR (1U 1); // MR0 匹配后计数器复位 TIMER32_0-PWMCON (1U 1) | (1U 2); TIMER32_0-TCR 1; // 启动定时器 }MCR的 bit1 由匹配事件控制复位计数器使得 PWM 周期等于 MR0PWMCON 寄存器里位 1 和位 2 分别使能 MR1、MR2 作为 PWM 输出。注意MR0 本身不在这两个 bit 中不要写成(1U 0)。运行时修改TIMER32_0-MR1即可改变占空比。但需要避免一个极端值如果 MR1 大于 MR0输出就恒定高电平。应用里最好做一次限幅类似duty period ? period : duty防止逻辑错误导致 LED 全亮或电机全转。5.2 RGB 混色的参数调整三路 PWM 分别控制红、绿、蓝的亮度。同一个 RGB LED 的三个颜色正向压降不同如果三个通道用同一个占空比色温会偏。适合用下面的参数表先手工校准通道典型串联电阻100% 占空比电流视觉亮度系数R150Ω15mA0.3G100Ω15mA0.7B100Ω15mA0.5不同 LED 的亮度系数差异较大以上只是初始值。RGB 例程里常看到“渐进呼吸灯”效果用MR1的值从 0 加到 period再加回去。这个过程如果直接在主循环里 delay 会卡掉其他任务建议放到 1ms 定时器中断里逐步更新。5.3 旋转编码器用 A 相边沿判断 B 相电平机械旋转编码器的输出 A/B 是正交方波旋转方向不同A 相上升沿到达时 B 相的电平不同。LPC1114 没有专用正交解码器例程用的方法是A 相接入 GPIO0 的某引脚上升沿触发外部中断在中断里读 B 相电平。若高电平则为正转低电平为反转或者相反取决于接线顺序。volatile int32_t encoder_position 0; #define ENC_A_PIN 2 #define ENC_B_PIN 3 void PIOINT0_IRQHandler(void) { if (GPIO0-MIS (1U ENC_A_PIN)) { GPIO0-IC (1U ENC_A_PIN); // 上升沿时检查另一相 if (GPIO0-DATA (1U ENC_B_PIN)) { encoder_position; } else { encoder_position--; } } }这段代码把方向判定压在一次中断里速度很快。但要注意GPIO0 所有引脚的中断都走 EINT0A 相和 B 相如果同时接了不同引脚中断里不能只清 A 相而忽略 B 相的挂起。另一个问题是机械触点抖动每次旋转可能让 A 相在边沿附近抖动 5ms导致位置计数一次增加多格。工业做法是外接 100nF 电容软件上则在进入中断后延迟 1ms 再读 B 相或者读取多次取一致结果。5.4 中断优先级与慢外设的冲突旋转编码器和 UART 同时使用中断时要思考优先级分配编码器若设置为高优先级中断服务里应尽快清中断、改计数器、退出如果把 UART 接收也放在同样优先级两个中断交错时可能丢字符。LPC1114 的 NVIC 支持可编程优先级经验是把读取频率高、处理时间短的旋转编码器设为高优先级UART 设为低优先级UART 用 FIFO 缓冲即使短暂延迟也不会立即丢数据。提示不要在旋转编码器的中断里调用printf或 LCD 刷新函数。一次编码器旋转只产生十几个脉冲看起来不影响但连续快速旋转时中断占用会超过主循环时间CPU 基本卡在中断里。6. SPI LCD 与片内 Flash把例程烧进 LPC1114 的最后一公里SPILCD.axf 和 LPC1114_Flash.axf 是例程包里比较接近“成品”的两个。前者把 SSP0 配成 SPI 主模式驱动一块液晶屏后者演示了 LPC1114 的片内 Flash 如何通过 IAP 自编程。两者合在一起正好覆盖了“刷屏”和“保存参数”这两个产品功能的标配。6.1 SSP0 主模式的配置LPC1114 的 SSP0 可以工作在 SPI 模式例程里通常用 8 位数据长度时钟相位和极性看 LCD 控制器的手册。SPI 驱动 LCD 的常见参数是数据帧 8 位SPI 模式 0 或 3时钟频率低于 20MHz。初始化的重点有两个SSP0-CPSR是分频值必须为偶数SSP0-CR0同时设置了帧格式和时钟极性。void SSP0_SPI_Init(void) { SYSAHBCLKCTRL | (1U 20); // SSP0 时钟 IOCON_PIO0_15 0x01; // SCK IOCON_PIO0_17 0x01; // MOSI IOCON_PIO0_16 0x01; // MISO不读回时也要配 SSP0-CPSR 12; SSP0-CR0 (7U 0) | (0U 6); // 8位, SPI 模式 0 SSP0-CR1 (1U 1); // 主机模式 }CPSR12时 48MHz 时钟得到 4MHz SPI对大多数 LCD 控制器足够。如果刷屏卡顿试着把CPSR调到 4 或 2前提是确认 LCD 控制器能承受。例程里的 LCD 驱动一般会提供SPI_Write函数发送前等待SSP0-SR的 bit1 为 1表示发送 FIFO 为空。6.2 片内 Flash 的 IAP 调用LPC1114 的片内 Flash 支持串口 ISP也可以在程序里调用 IAP 命令。IAP 入口地址是 Boot ROM 中的固定位置0x1FFF1FF1调用方式是通过函数指针。命令数组的第一个元素是命令码返回状态在结果数组的第一个元素0表示成功。写 Flash 前必须先准备扇区再擦除最后写入。typedef void (*IAP_Func)(uint32_t[], uint32_t[]); #define IAP_ENTRY ((IAP_Func)0x1FFF1FF1) void Flash_Write_Sector(uint32_t addr, uint32_t *buf, uint32_t len) { uint32_t cmd[5], res[4]; cmd[0] 50; // 准备写扇区 cmd[1] addr; cmd[2] 0; IAP_ENTRY(cmd, res); if (res[0] ! 0) return; cmd[0] 51; // 擦除扇区 cmd[1] addr; cmd[2] 1; IAP_ENTRY(cmd, res); if (res[0] ! 0) return; cmd[0] 52; // 写Flash cmd[1] addr; cmd[2] (uint32_t)buf; cmd[3] len; IAP_ENTRY(cmd, res); }IAP 调用时 CPU 从 Flash 正常运行命令由 Boot ROM 中的程序执行。要注意写入目标和当前正在执行的 Flash 扇区在物理上不能是被擦除的那个扇区否则指令取指会失败。常见做法是应用程序放在 0x0000 区域用户参数放在最后 8KB两个空间互不重叠这样 Flash 例程每次修改参数时不会把包含中断向量表的 0 扇区擦掉。6.3 用 axf 验证最终工程Keil MDK 生成的 axf 文件可以用fromelf转成 hex 或 bin也可以直接通过调试器下载。SPILCD 例程跑黑屏时先检查 SSP0 的引脚功能对不对再用逻辑分析仪看 SCK 上有没有时钟片内 Flash 例程写不进数据时先用串口工具发 ISP 命令确认 Boot ROM 能正常访问 flash。最终固件如果要从 Flash 启动把 IAP 入口放在启动文件末尾不要被链接器的--remove优化掉。本文还有配套的精品资源点击获取
返回列表