VxWorks在ARM9上的BSP开发与移植实战详解
简介面向嵌入式系统开发者的VxWorks BSP移植资料基于Samsung S3C2410XARM9处理器帮助解决在ARM9硬件平台上定制VxWorks板级支持包时涉及的底层驱动、启动流程与内存中断管理问题。包内文件共37个以C语言源码c/h、汇编文件s/o、Makefile构建脚本及README说明为主同时包含串口、定时器、中断控制器和CS8900A网卡驱动等关键模块代码与注释可直接对照学习。压缩包大小约1000KB结构紧凑适合已有一定VxWorks基础、正在开展BSP移植或驱动开发的工程师参考。目前该资源已有188人学习内容覆盖从ROM启动初始化到设备驱动注册的完整过程并附有配置头文件与编译依赖关系便于读者结合S3C2410X数据手册梳理BSP的搭建思路与排错要点。 拿到一块ARM9开发板想跑VxWorks我劝你先别急着写应用第一步永远是BSP。VxWorks在工业控制、通信设备里活了这么多年实时性和稳定性是一方面更重要的是它的BSP机制把“千奇百怪的板子”和“同一个内核”之间的差异全部消化掉了。BSP全称Board Support Package板级支持包本质就是VxWorks内核和具体电路板之间的适配层负责在上电后的极短时间内把CPU、内存、串口、定时器、中断控制器这些基础外设从混沌状态带进可被内核接管的状态。这篇文章我就拿ARM9这个经典架构展开把VxWorks基于ARM9的BSP从概念、启动流程、移植要点到排障经验完整过一遍给正在学嵌入式、准备做课程项目或者刚接手BSP开发的工程师一个可以直接参考的路径。1. 为什么ARM9上跑VxWorksBSP是第一道坎1.1 BSP的职责边界它和普通驱动到底有什么区别很多人一开始会把BSP理解成“驱动合集”这个方向对了一半。驱动解决的是“内核跑起来之后某个外设怎么用”的问题而BSP解决的是“内核还没跑起来之前系统怎么活过来”的问题。VxWorks的BSP位于最底层直接面对CPU寄存器和板级资源它要保证三件事第一上电后CPU能稳定执行指令第二内存控制器配置完毕代码可以搬进RAM运行第三至少有一个串口能输出调试信息让开发者在黑暗里看见系统状态。这跟普通驱动的差异在于时序和依赖关系。普通驱动可以按需加载BSP的每个模块都有严格的先后顺序时钟先于外设内存先于搬移中断先于调度。VxWorks的BSP文档里有一句话我一直印象很深大意是“BSP is the first code that runs and the last code that finishes”意思是它从系统上电一直服务到内核完全接管硬件中间任何一步卡住后面全都无从谈起。1.2 ARM9上电后的真实状态什么都还没准备好ARM9比如S3C2440、AT91RM9200、EP93xx这些上电之后CPU并不是一个“干净”的状态。它默认从0x00000000地址取指此时外部存储控制器可能还没初始化DRAM完全不可用只有Flash或SRAM能响应中断是开启的或者处于不确定状态PLL没有锁定CPU可能跑在慢速时钟下看门狗如果默认开启可能在几毫秒内就把系统复位。所有这些都要在BSP的romInit和sysHwInit里逐项纠正。我见过很多从单片机转过来的开发者习惯性地在main函数里初始化外设。VxWorks不是这个套路它的入口是汇编级的romInit紧接着是romStart或usrInit在这些阶段连C运行环境都还不完整数据段、BSS段都是手工处理的。ARM9因为没有x86那样的BIOS所有硬件初始化都得BSP自己做这就是为什么很多人拿到板子后发现VxWorks比Linux更难“点着”的原因——Linux至少有个bootloader帮你做了大半VxWorks的bootrom本身就是一套微型bootloader全都得自己理解并掌控。1.3 先看标准BSP长什么样目录地图在VxWorks尤其是5.x和6.x时代的安装目录下target/config/bspname/就是一套完整BSP。核心文件就那么几个romInit.s上电入口汇编编写负责最低限度的CPU初始化和代码搬移。romVectors.s异常向量表处理复位、未定义指令、SWI、预取中止、数据中止、IRQ、FIQ。sysLib.c系统库函数实现sysHwInit()、sysHwInit2()、sysClkConnect()、sysSerialHwInit()等关键接口。sysAlib.s汇编写的辅助函数比如开关中断、上下文切换相关。config.h板级配置头文件内存地址、启动行、外设资源的宏定义几乎都在这。makefile决定编译成bootrom还是VxWorks镜像。新手最容易犯的错是拿到一个接近的BSP就从头改改了半天不知道哪些该动哪些不该动。我的建议是先把config.h里所有地址宏全部读懂再碰汇编和C源文件。地址错了后面的串口、网络、Flash全都会跟着错而且错得非常隐蔽。2. 从romInit到usrRootVxWorks启动链路的代码级拆解2.1 bootrom的三段式使命VxWorks的bootrom并不是“为了启动而启动”它的完整使命分三个阶段。第一阶段是上电后最早执行的romInit在Flash里运行任务是极简初始化关中断、设置CPU工作模式、关MMU和Cache、初始化最基本的存储控制器然后把自身代码从Flash搬到RAM或者至少把足够多的代码搬过去最后跳到C函数romStart。第二阶段是romStart在RAM里运行把bootrom的data段和bss段清理好然后根据配置决定是从Flash加载VxWorks镜像、从网络加载还是从串口加载。第三阶段是加载完成后跳到VxWorks的usrInit进入真正的内核初始化流程。这三个阶段的边界非常清晰任何一段出问题表现都不一样romInit挂了串口可能一个字都不会输出romStart挂了通常能看到打印但停在半路usrInit挂了打印能刷到内核启动中途然后看门狗复位。这种“阶段可定位”的特性是VxWorks启动设计得很成熟的地方调试时可以快速缩小范围。2.2 romInit.s的骨架长什么样以ARM9为例romInit.s开头一般长这样伪代码级示意具体寄存器依芯片而定.text .globl romInit .globl _romInit _romInit: romInit: /* 关中断屏蔽IRQ和FIQ */ mrs r0, cpsr orr r0, r0, #0xC0 msr cpsr, r0 /* 进入SVC模式确保特权级别 */ mrs r0, cpsr bic r0, r0, #0x1F orr r0, r0, #0xD3 msr cpsr, r0 /* 关MMU、D-Cache、I-Cache */ mrc p15, 0, r0, c1, 0, 0 bic r0, r0, #0x1000 /* I-Cache */ bic r0, r0, #0x0004 /* D-Cache */ bic r0, r0, #0x0001 /* MMU */ mcr p15, 0, r0, c1, 0, 0 /* 初始化基本存储控制器Bank选择、位宽、时序 */ /* 代码因芯片而异此处略 */ /* 设置栈指针准备跳C函数 */ ldr sp, STACK_ADRS bl romStart我特别强调关MMU和Cache这一步因为ARM9的D-Cache是写回write-back策略的话刚上电时若不关掉后面访问外设寄存器会出现数据不一致。很多移植新手在这里栽跟头bootrom编译出来能烧进去但一跑就飞查来查去最后发现是Cache没关或者没正确地invalid操作。2.3 串口输出启动链路的“仪表盘”启动阶段最关键的调试输出就是串口。一个可靠的串口驱动要放在romInit之后的早期阶段完成初始化。VxWorks的惯例是在usrInit的sysHwInit()里调用sysSerialHwInit()把UART控制器配好然后在usrSerialInit()阶段把标准I/O重定向到串口设备。对于bootrom自身来说romStart阶段通常已经可以打印了所以你在终端上看到的第一行字符大多来自romStart或usrInit早期。串口没输出时不用急着怀疑软件。我习惯先查三件事板子电源和复位引脚是否正常串口电平转换芯片是否工作ARM9一般是TTL电平很多调试板已经转了USB转串口终端波特率和BSP里config.h的CONSOLE_BAUD_RATE是否一致。最后才查代码里UART寄存器配置。这个排查顺序听起来很简单但真能省下一个下午。3. 移植到ARM9时四个最容易被忽略的硬件关键点3.1 时钟树PLL没配好所有外设都在说谎ARM9芯片通常有多个时钟源CPU主频FCLK、AHB总线时钟HCLK、APB外设时钟PCLK。这些时钟来自PLL倍频和分频。很多BSP的romInit里只配了Flash时序没有配PLL导致CPU跑在慢速时钟上后续串口波特率怎么算都不对——因为你算波特率时用了目标频率实际硬件跑的是另一个频率。以S3C2440为例MPLLCON的设置直接决定FCLK。举例来说想让FCLK400MHz、HCLK100MHz、PCLK50MHz就得用标准PLL参数表算出MDIV、PDIV、SDIV同时还要设置CLKDIVN控制HCLK和PCLK的分频比例。BSP里这部分的常见问题不是“没配”而是“配了但没配全”——只改了MPLL忘了CLKDIVN或者改了分频但没先设置CPU总线模式结果CPU访问外设的时序完全错乱表现出随机性极强的挂死。配置时钟树时一定要仔细读芯片手册的“Clock Power Management”章节把所有相关寄存器一次配齐别只盯着PLL。3.2 存储控制器代码能搬但DRAM得先活过来VxWorks的bootrom和最终镜像通常要加载到RAM里执行。ARM9的DRAM控制器初始化是个精细活行地址列地址位数、Bank数、刷新周期、时序参数tRCD、tRP、tRAS这些任何一个不对内存读写就可能随机出错。更麻烦的是内存控制器刚配置完不能立刻大规模访问否则会触发未决状态。有些BSP在sysHwInit()里专门做内存测试通过写-读-校验的模式验证可用内存范围这个步骤不要跳。我踩过一个很经典的坑config.h里RAM_LOW_ADRS设得比实际内存起始地址低结果VxWorks把自己的数据段覆盖了系统跑起来后每隔一段时间就莫名奇妙地死一次毫无规律。后来用内存测试工具才发现前0x20000字节根本不可用。所以拿到一块新板子第一件事就是把内存区域边界用Flash编程器、JTAG或者内存测试代码彻底确认一遍再写进地址宏。3.3 UARTBSP调试的第一根救命稻草串口驱动在BSP里占了非常特殊的地位。它既是控制台也是大多数场景下的调试通道。ARM9的UART初始化通常包括设置引脚复用为UART功能、配置波特率、数据位/停止位/校验位、FIFO开关、中断使能或者轮询模式。调试阶段我建议先用轮询模式也就是TX直接查状态位是否为空空就写数据。这样做的好处是不依赖中断控制器任何一个环节坏了你都能知道——因为坏在哪一步打印就停在哪一步。等整个BSP稳定了再切到中断模式把串口接收和发送交给驱动管理。很多BSP的sysSerial.c里已经帮你写好了两种模式直接看代码就知道怎么切。另外ARM9的UART波特率计算公式是UBRDIV (PCLK / (baud * 16)) - 1前提是PCLK算得准这又绕回时钟树的问题——时钟不对串口第一个字符就乱码。3.4 中断控制器和系统定时器让VxWorks的tick转起来VxWorks的调度依赖系统时钟中断tick。ARM9上通常有一个周期性的定时器比如S3C2440的PWM Timer4。BSP里sysClkConnect()把定时器中断服务程序和tick挂钩sysClkEnable()启动定时器。这套链路里最容易出问题的是中断优先级和向量号ARM9支持IRQ和FIQ两种异常VxWorks一般只用IRQ。芯片内部的中断控制器会有多个中断源映射到IRQBSP必须把定时器中断源正确使能并接到IRQ向量上。有一个细节很多人会漏ARM9处理器在响应IRQ时会把返回地址放在LR_irq寄存器但进入C中断处理函数后LR可能被覆盖所以BSP的汇编intEnt函数必须先把LR保存到栈上。VxWorks的标准BSP都处理了这一步但如果是从别的平台移植过来的BSP这个汇编层没抄对中断一触发就飞连定位的机会都没有。检查时可以从sysALib.s里搜索intEnt、intCnt这些符号确认LR保存和恢复逻辑完整。4. BSP和引导程序的边界bootrom与Uboot到底谁干谁的活4.1 bootrom自己就是一套微型引导很多从Linux转过来的同学会问VxWorks是不是也要先刷Uboot再引导VxWorks答案是不一定。VxWorks的bootrom天然就是一个引导程序它本身就包含Flash编程、网络加载通过TFTP、串口加载通过S-Record或YModem这些能力。也就是说BSP里的bootrom完全可以替代Uboot的角色负责把VxWorks镜像从Flash或网络加载到RAM并跳转执行。既然能替代为什么还有人要配Uboot因为实际项目中板上可能同时有Linux和VxWorks双系统切换或者硬件初始化太复杂DDR3/DDR4的training、FPGA加载等用Uboot做更合适。这时候VxWorks的BSP就把bootrom简化直接把VxWorks镜像做成可以由外部引导程序加载的格式共用同一个BSP里的romInit做重定位或者直接进sysHwInit。4.2 从外部引导程序交接的正确姿势如果你想用Uboot来引导VxWorks最常见的方式是Uboot完成DDR、串口、网络初始化后通过tftp命令把VxWorks镜像下载到内存地址然后go 0x30008000直接跳转。此时VxWorks的BSP应该编译成“RAM型”镜像即没有bootrom引导直接从sysInit或romInit的RAM版本进入。这里有个关键点VxWorks的sysInitRAM入口和bootrom的romInit不是同一个东西。RAM型镜像的入口汇编段会假设DRAM已经可用、时钟已配好所以只做最基础的异常向量设置、栈指针设置就跳进C代码。Uboot跳转前通常会把CPU置于SVC模式并关中断但这不是必然的所以BSP的RAM入口处还是要自己再做一次关中断和模式切换防止启动环境差异导致不可预测行为。关于“BSP和Uboot什么关系”这个问题我能给出的最简洁回答是两者不是替代与对立而是协作。Uboot负责“把VxWorks镜像弄进内存并跳到入口”BSP负责“从入口之后接管整块硬件”。边界一旦划清很多移植问题就自然清晰了。5. 移植路上的实测排坑Cache、向量表与调试手段5.1 串口无输出或乱码的排查顺序BSP移植第一道关卡永远是串口。如果板子上电后串口完全没反应我建议按这个顺序排查。先量硬件串口芯片供电、TX/RX连通性、地线共地这些是电表能解决的排除这些再怀疑软件。然后是软件配置UART寄存器是否真的被写进去了引脚复用寄存器是否切到了UART模式。再然后看波特率用示波器观察TX引脚的波形测量一位宽度是否和预期波特率匹配。如果波形在但终端乱码基本就是波特率或时钟分频问题。如果波形都不在说明UART驱动根本没跑到检查代码路径——很可能串口初始化在romInit里做了但sysHwInit里又被重新配置覆盖了。5.2 中断向量表必须重映射ARM7和ARM9的异常向量表默认位于地址0x00000000但这个地址往往是Flash或者SRAM如果系统把DRAM重映射到0地址很多ARM SoC支持内存重映射或者向量表想放进高速内存就必须在BSP里做重映射。VxWorks的标准做法是在sysLib.c或sysAlib.s里把异常向量复制到RAM的高地址或指定地址然后通过MCR p15指令把向量基址寄存器VBAR指向新地址。有一种很阴的问题向量表本身在但每个向量的跳转目标没更新。比如IRQ向量地址写的是一个绝对地址随着链接地址变化这个绝对地址失效中断触发后跳到一个非法区域系统瞬间跑飞。这类问题通过JTAG或仿真器看PC程序计数器寄存器跳到哪个地址最容易定位。5.3 Cache一致性问题一旦踩到就是血泪教训ARM9的D-Cache是VIPT虚拟索引物理标签或PIPT类型与DMA外设交互时Cache一致性问题非常突出。典型场景网络DMA把数据写到内存CPU去读时却读到了Cache里的旧数据或者CPU写完数据后DMA去读内存里还是旧值。BSP层面处理手段有两种一是操作DMA描述符和缓冲区前后调用cacheInvalidate、cacheFlush二是在驱动层直接把缓冲区设成非Cacheable。VxWorks提供CACHE_DMA_FLUSH和CACHE_DMA_INVALIDATE这类宏标准BSP的网络驱动里已经调用了。但如果你自己写外设驱动一定记得手动处理。另一个小技巧ARM9的协处理器CP15操作D-Cache的clean和invalidate是按MVA修改的虚拟地址或Set/Way操作的写代码时要注意操作范围别把整个Cache清了那会严重影响实时性能。5.4 我推荐的调试顺序和工具链BSP开发不是拿着代码埋头看就能出结果的要会组合调试手段。我的习惯是第一步确认串口物理通路用示波器或逻辑分析仪验证第二步用JTAG调试器连接目标板在romInit入口设断点单步看芯片是否按预期执行第三步在usrInit、sysHwInit、sysClkConnect这些关键函数打打印或者设断点确定启动进度第四步当串口和tick都正常后再用网络加载和Tornado/Workbench的host shell做远程调试。这套顺序能保证每一步都有可观测的输出而不是靠猜。用JTAG时我通常会在Flash里的romInit步骤之后设断点然后观察SDRAM读写是否正常。如果内存读写测试都过了再往后推进。这个阶段如果内存有问题所有后续步骤都会出错而且很难排查。绑定一个真实经历有一次板子跑起来后sysClkConnect总是返回ERROR查了很久发现是定时器中断的向量号被另一个驱动模块占用导致intConnect注册失败。最后通过Workbench的符号表查intVecTable才确认。这类跨模块的资源冲突只靠读代码很难发现借助工具看运行时状态才是正道。最后分享一点个人经验BSP开发最忌讳的就是“一次改一堆出了错不知道是谁干的”。挪到新板子时我会把改动控制在最小集合每次只动一个子系统编译烧录验证通过后再动下一个。比如先把串口调通再调定时器再调Flash和网络。这听起来慢但BSP涉及的细节太多变量一多问题定位时间成指数增长。如果你也正卡在ARM9的BSP移植上不妨把启动打印、内存测试、tick中断这三个里程碑列出来一个个啃很快你就会发现VxWorks其实没有传说中那么难伺候。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联
返回资讯列表 →