DSP开发这件事,入门门槛其实不在写算法,而在"让芯片先跑起来"。我见过太多人拿到C6678的开发板,CCS装了三遍,工程建了五遍,最后卡在"程序烧进去没反应"这一步——不是代码写错了,是启动模式没配对。这篇内容就围绕C6678这颗八核DSP,把CCS环境搭建、多核工程创建、启动模式配置这条链路完整走一遍,把每个环节"为什么这么做"讲透。适合刚接触TI多核DSP的工程师、做信号处理项目的学生,以及从单核C2000或STM32转过来的朋友。读完你至少能独立完成一个可加载、可调试、可固化启动的C6678多核工程。
1. 先把C6678这颗芯片的脾气摸清楚
1.1 八核架构带来的第一个认知转变
C6678是TI Keystone架构下的八核定点/浮点DSP,每核主频最高1.25GHz,单核定点算力40GMAC、浮点20GFLOP。这些参数网上到处都是,但真正影响你建工程方式的,是它的多核共享架构:八个C66x核心共享一块4MB的MSMC(Multicore Shared Memory Controller)内存,每个核心还有自己独立的32KB L1P、32KB L1D和512KB L2。这个结构决定了你在CCS里建工程时,不能像单核那样"一个main函数走天下"。
我第一次在C6678上跑LED闪烁,习惯性地写了个while(1)死循环,结果Core0跑起来了,Core1到Core7完全没动静。后来才明白,多核工程的核心问题是:谁负责初始化、谁负责干活、核间怎么通信。这跟单核开发的思维完全不一样。单核是你一个人的活,多核是一个团队的活,你得先定好谁是队长、谁负责什么、怎么传消息。
从工程创建的角度,这意味着你需要至少两个层面的代码:一个是主核(Core0)的引导代码,负责系统初始化、外设配置、把其他核从等待状态唤醒;另一个是从核的应用程序,被唤醒后执行各自的任务。CCS的工程模板里,TI提供了"Multicore"类型的工程,但很多人建完发现编译能过、加载报错,问题就出在没理解这个主从关系。
1.2 内存映射:为什么你的变量地址总是对不上
C6678的地址空间分得很细,L1、L2、MSMC、DDR3各有各的地址段。L2的起始地址是0x00800000,MSMC是0x0C000000,DDR3通常映射在0x80000000。这些地址在写链接命令文件(.cmd)时必须严格对应,否则程序加载到错误的地址,跑起来就是随机崩溃。
我踩过的一个典型坑:从核的代码默认链接到L2,但L2每个核只有512KB,如果从核程序比较大,链接阶段就会报"section overflow"。解决办法是把大块的数据段和代码段放到MSMC或DDR3里,通过修改.cmd文件里的SECTION映射来实现。比如:
SECTIONS { .text: > MSMC .data: > DDR3 .bss: > DDR3 .stack: > L2SRAM }这里有个经验:栈(stack)一定要放在L2,因为L2是每个核私有的,访问速度最快,栈操作频繁,放共享内存会引入不必要的仲裁开销。而大数组、大缓冲区放DDR3,虽然慢一点,但容量大。MSMC适合放核间共享的数据结构,比如核间通信的消息队列。
1.3 外设资源分配:八个核抢一个外设怎么办
C6678的外设(EMIF、SRIO、PCIe、千兆网等)是全局共享的,八个核都能访问。但如果你不做分配,两个核同时去配置同一个UART,结果就是寄存器状态混乱。工程实战里的做法是:外设初始化统一由Core0完成,其他核只读不写,或者通过核间中断来请求外设操作。
举个例子,你要用EMIF接Flash,EMIF的时序寄存器配置必须由Core0在启动阶段完成,Core1到Core7如果需要读Flash数据,直接读映射后的地址就行,不要再去碰EMIF的配置寄存器。这个规则在工程文档里往往一笔带过,但实际调试时,外设冲突导致的问题最难查——因为现象是偶发的,可能跑十分钟才崩一次。
2. CCS环境配置:那些安装教程不会告诉你的细节
2.1 版本选择不是越新越好
CCS(Code Composer Studio)的版本和C6678的支持包是绑定的。我实测下来,CCS 5.5、6.2、8.3、10.4这几个版本对C6678的支持最稳定。太新的版本(比如CCS 12以上)虽然界面好看,但Keystone架构的器件支持包更新滞后,有时候建工程时找不到C6678的选项。
具体来说,你需要确认两件事:一是CCS版本对应的C6000编译器版本,C6678需要v8.x以上的编译器才能完整支持C66x指令集;二是MCSDK(Multicore SDK)的版本,TI的MCSDK提供了多核通信、外设驱动等基础库,C6678对应的是MCSDK 3.x系列。这两个东西版本不匹配,编译时会出现头文件找不到、库函数链接失败等问题。
安装时的顺序也有讲究:先装CCS主程序,再装器件支持包(Device Support),最后装MCSDK。如果顺序反了,CCS可能识别不到MCSDK的组件。安装路径不要有中文和空格,这是老生常谈但每年都有人踩的坑,路径里的空格会导致编译脚本解析失败。
2.2 仿真器配置:XDS系列的选择与连接
C6678开发板通常配XDS100v2、XDS200或XDS560v2仿真器。XDS100v2最便宜但速度慢,适合小工程调试;XDS560v2最快,支持高速跟踪,但价格高。我日常用的是XDS200,速度和价格比较平衡。
在CCS里配置仿真器时,需要新建一个Target Configuration(.ccxml文件),选择器件型号C6678,然后选仿真器类型。这里有个细节:C6678的JTAG链上有多个核,配置时要确认是"Single Core"还是"Multicore"模式。如果你要同时调试八个核,选Multicore;如果只调一个核,选Single Core并指定核号。
连接失败的常见原因我列一下:
| 现象 | 可能原因 | 排查方法 |
|---|---|---|
| 报"Error connecting to the target" | 仿真器驱动未装或JTAG线序错 | 设备管理器看驱动,检查排线方向 |
| 能连上但读不到核 | 目标板未供电或复位状态异常 | 万用表测核心电压,按复位键重试 |
| 连接时断时续 | JTAG时钟太快 | 在.ccxml里把TCK频率降到1MHz |
| 多核连接只识别到一个核 | 核间复位未释放 | 检查启动模式引脚,确认非等待状态 |
提示:JTAG时钟频率在初次连接时建议设低一点(500kHz到1MHz),等连接稳定后再逐步提高。很多"连不上"的问题其实是时钟太快导致信号完整性下降。
2.3 工作空间与工程目录的组织方式
CCS的工作空间(Workspace)默认放在用户目录下,但C6678工程文件多、编译产物大,建议把Workspace设在空间充足的盘符,并且路径全英文。工程目录我习惯这样组织:
C6678_Project/ ├── common/ # 公共头文件、链接脚本 ├── core0_app/ # 主核应用 ├── core1_app/ # 从核1应用 ├── ... ├── core7_app/ # 从核7应用 ├── libraries/ # MCSDK库、驱动库 └── output/ # 编译产物、可执行文件每个核一个工程,通过"Project References"关联公共库。这样组织的好处是:单独修改某个核的代码不影响其他核,编译时只重编改动的部分,节省时间。坏处是工程数量多,CCS里打开一堆工程,切换起来有点烦。折中方案是用一个"Master Project"包含所有核的源文件,通过不同的Build Configuration来区分核,但这种方式配置复杂,新手容易搞混。
3. 多核工程创建:从空工程到能跑起来
3.1 新建工程的正确姿势
在CCS里新建C6678工程,菜单路径是File > New > CCS Project。关键选项有三个:Family选C6000,Variant选C6678,Project Templates选Empty Project。不要选那些带main函数的模板,因为多核工程的main函数结构特殊,模板生成的代码反而要删。
工程建好后,第一件事是配置编译器版本和链接器命令文件。右键工程 > Properties > Build > C6000 Compiler,确认版本是v8.x以上。然后在Linker > File Search Path里添加MCSDK的库路径,在Linker > Basic Options里指定.cmd文件。
.cmd文件是C6678工程的灵魂,它决定了代码和数据放在哪个内存段。一个典型的多核工程.cmd文件长这样:
MEMORY { L2SRAM (RWX) : org = 0x00800000, len = 0x00080000 MSMC (RWX) : org = 0x0C000000, len = 0x00400000 DDR3 (RWX) : org = 0x80000000, len = 0x10000000 } SECTIONS { .text > L2SRAM .cinit > L2SRAM .const > L2SRAM .data > DDR3 .bss > DDR3 .far > DDR3 .stack > L2SRAM .sysmem > DDR3 }这里L2SRAM的长度0x80000是512KB,MSMC是4MB,DDR3假设外挂了256MB。实际项目中要根据板子的DDR3容量调整。注意.stack段必须放在L2,前面说过原因。
3.2 主核代码的骨架
主核(Core0)的代码负责三件事:初始化系统、加载从核程序、启动从核。一个最小化的主核骨架如下:
#include <c6x.h> #include <stdio.h> extern void core1_main(void); extern void core2_main(void); void main(void) { /* 1. 系统初始化:PLL、DDR3、外设 */ Init_PLL(); Init_DDR3(); Init_UART(); /* 2. 将从核程序从Flash/DDR搬到各核L2 */ Copy_Core1_Code(); Copy_Core2_Code(); /* 3. 唤醒从核 */ Wakeup_Core(1, (Uint32)core1_main); Wakeup_Core(2, (Uint32)core2_main); /* 4. 主核自己的任务 */ while(1) { /* 主核业务逻辑 */ } }Wakeup_Core这个函数是核心,它通过写IPC寄存器(Inter-Processor Communication)来触发从核的启动。C6678每个核有一组IPC寄存器,主核往目标核的IPCGR寄存器写1,目标核就会收到一个中断,从核的中断服务程序里跳转到指定的入口地址。
这里有个关键细节:从核的入口地址必须是偶数对齐的,因为C66x的指令是32位,地址最低位用于区分ARM/Thumb状态(虽然C66x不涉及Thumb,但硬件设计沿用了这个约定)。如果地址是奇数,从核跳转后会跑飞。我调试时遇到过这个问题,现象是从核一启动就进异常,查了半天才发现是函数指针地址没对齐。
3.3 从核代码的启动流程
从核的代码结构和主核不同,它没有传统的main函数入口,而是通过中断向量表进入。从核上电后处于"等待唤醒"状态,主核写IPC寄存器后,从核触发IPC中断,在中断服务程序里读取主核传来的入口地址,然后跳转执行。
从核的代码骨架:
#include <c6x.h> void core1_main(void) { /* 从核初始化 */ Init_Core1_L2(); /* 从核业务逻辑 */ while(1) { /* 处理任务 */ } } /* IPC中断服务程序 */ interrupt void IPC_ISR(void) { Uint32 entry_addr; entry_addr = Read_IPC_Entry(); ((void (*)(void))entry_addr)(); }从核的工程需要单独编译,生成的可执行文件(.out)由主核在启动时加载到从核的L2。加载方式有两种:一种是通过CCS的调试器直接加载(调试阶段用),另一种是主核代码里用memcpy搬运(固化启动用)。调试阶段我建议用CCS的"Load Program"功能分别给每个核加载程序,这样改代码后重新加载快,不用每次烧Flash。
4. 启动模式详解:程序怎么从Flash跑到DDR
4.1 C6678的启动模式引脚与Boot ROM
C6678上电后,首先执行的是芯片内部固化的Boot ROM代码。Boot ROM会根据启动模式引脚(BOOTMODE[12:0])的电平状态,决定从哪里加载程序。常见的启动模式有:
| 启动模式 | BOOTMODE配置 | 适用场景 |
|---|---|---|
| No Boot / Wait | 全0或特定组合 | 调试阶段,等JTAG加载 |
| SPI Boot | SPI模式引脚拉高 | 小容量程序,直接从SPI Flash启动 |
| NAND Boot | NAND模式 | 大容量程序 |
| EMIF16 Boot | EMIF模式 | 并行NOR Flash |
| PCIe Boot | PCIe模式 | 主机通过PCIe加载 |
| SRIO Boot | SRIO模式 | 高速接口加载 |
| Ethernet Boot | 网口模式 | 网络加载 |
调试阶段最常用的是No Boot模式,芯片上电后停在Boot ROM里等JTAG连接,CCS通过仿真器把程序加载到内存并运行。固化阶段根据板子上的存储介质选SPI或EMIF16。
Boot ROM的代码是TI固化在芯片里的,你改不了,但可以通过二级引导程序(Second Stage Bootloader)来接管。Boot ROM先把二级引导程序加载到L2,然后跳转执行,二级引导程序再负责把主程序从Flash搬到DDR并启动。这个机制叫多级引导,是C6678工程实战里必须掌握的。
4.2 二级引导程序的编写要点
二级引导程序(通常叫bootloader或IBL)的核心任务:初始化DDR3、把应用程序从Flash读到DDR、跳转到应用程序入口。它本身必须足够小,能塞进L2(512KB以内),因为Boot ROM只会把它加载到L2。
一个简化的二级引导流程:
void main(void) { /* 1. 初始化DDR3控制器 */ Init_DDR3(); /* 2. 从SPI Flash读取应用程序到DDR3 */ SPI_Read(APP_FLASH_ADDR, APP_DDR_ADDR, APP_SIZE); /* 3. 跳转到应用程序入口 */ ((void (*)(void))APP_DDR_ADDR)(); }这里的关键是DDR3初始化。C6678的DDR3控制器配置涉及一堆寄存器:SDCFG、SDTIM1、SDTIM2、SDRFC等,参数包括时序、刷新周期、bank配置等。这些参数必须和板子上实际的DDR3芯片匹配,配错了要么读不出数据,要么跑一段时间后崩溃。TI提供了DDR3配置工具(在MCSDK里),输入DDR3芯片的型号和频率,工具会生成配置代码。我建议直接用工具生成,手写寄存器太容易出错。
注意:DDR3初始化后需要做一次写-读校验,确认内存工作正常再继续。我遇到过DDR3参数差一点导致偶发位翻转的情况,程序跑几分钟就崩,查了很久才发现是DDR3时序问题。校验方法很简单:往DDR3写一个已知模式(比如0xAA55AA55),读回来比对,不一致就报错。
4.3 多核程序的固化:八个核怎么一起启动
单核程序的固化相对简单,多核程序的固化要复杂一些。核心问题是:Boot ROM只加载一个引导程序,八个核的程序怎么组织。
常见做法是:把八个核的可执行文件(.out或转换后的.bin)打包成一个镜像文件,二级引导程序负责解析这个镜像,把每个核的代码搬到对应的L2或共享内存,然后依次唤醒各核。TI的MCSDK里提供了多核镜像生成工具(Multicore Image Creator),可以把多个.out文件合并成一个.app文件,再通过工具转换成Flash可烧写的.bin格式。
具体步骤:
- 每个核单独编译,生成各自的.out文件
- 用
multicore_image_creator工具把八个.out合并成一个镜像 - 用
hex6x或ofd工具把镜像转换成二进制格式 - 用Flash烧写工具(如CCS的Flash Programmer)把二进制文件烧到SPI Flash
- 把BOOTMODE引脚设成SPI启动模式,上电复位
这里有个容易忽略的点:镜像文件里要包含每个核的入口地址和代码长度信息,二级引导程序靠这些信息来搬运代码。TI的工具会自动生成这些元数据,但如果你自己写引导程序,就要自己定义镜像格式。我建议直接用TI的工具链,省事且不容易出错。
4.4 启动失败的排查链路
程序烧进去不启动,是C6678开发里最常见的问题。我总结了一套排查顺序,按这个链路走,大部分问题能定位:
第一步:确认BOOTMODE引脚。用万用表测启动模式引脚的电平,对照数据手册确认是不是你想要的模式。我遇到过板子上的拨码开关接触不良,引脚电平飘忽,导致启动模式随机。
第二步:看Boot ROM有没有跑。C6678有个BOOTCOMPLETE寄存器,Boot ROM执行完会置位。通过JTAG连上后读这个寄存器,如果没置位,说明Boot ROM卡住了,可能是启动模式不对或Flash通信失败。
第三步:确认二级引导程序有没有执行。在引导程序里翻转一个GPIO,用示波器看有没有波形。如果有波形,说明引导程序跑起来了,问题在应用程序加载或跳转;如果没有,问题在Boot ROM到引导程序这一段。
第四步:检查DDR3初始化。如果应用程序要加载到DDR3,DDR3没初始化好,加载必然失败。用CCS连上后手动读写DDR3地址,看数据对不对。
第五步:检查核间唤醒。主核跑起来了但从核没动静,检查IPC寄存器有没有写、从核的中断有没有使能、入口地址对不对齐。
这套链路我用了很多次,基本上能在半小时内定位到问题环节。关键是要分段验证,不要一上来就怀疑应用程序,先从最底层的启动模式查起。
5. 调试技巧与实战经验
5.1 用CCS的多核调试功能同时看八个核
CCS的Debug视图支持多核同步调试。连接目标板时选Multicore模式,CCS会列出八个核,可以单独暂停、单步、查看寄存器。我常用的操作是:Group Core,把八个核编成一组,一起Run、一起Halt。调试核间同步问题时,这个功能特别有用——你可以同时看八个核的PC指针,判断哪个核跑飞了。
还有一个技巧是用CCS的Memory Browser看共享内存。核间通信的数据放在MSMC里,调试时打开Memory Browser,输入MSMC的地址,实时看数据变化。如果数据没更新,说明发送核没写或者接收核没读,问题就定位到通信环节了。
5.2 核间通信的几种方式与选择
C6678的核间通信方式主要有三种:IPC寄存器中断、共享内存+信号量、SRIO/PCIe高速接口。选择哪种取决于数据量和实时性要求。
IPC寄存器中断适合小数据量、事件通知,比如主核告诉从核"有新任务了",从核去共享内存取数据。共享内存+信号量适合中等数据量、需要同步的场景,比如一个核写缓冲区,另一个核读,用信号量保证读写不冲突。SRIO/PCIe适合大数据量、跨板通信,比如图像处理里一帧图像几MB,走SRIO比走共享内存快。
我实际项目里用得最多的是共享内存+IPC中断的组合:数据放MSMC,主核写完数据后写IPC寄存器通知从核,从核收到中断后去MSMC读数据。这个模式简单可靠,调试也方便。
5.3 性能优化的几个实用手段
C6678的八核算力很强,但用不好也跑不出性能。几个实测有效的优化手段:
代码放L2,数据放MSMC。L2访问延迟几个周期,MSMC几十个周期,DDR3上百个周期。把频繁执行的代码放L2,能显著提升速度。如果代码太大放不下,至少把热点循环放L2。
用EDMA搬数据。C6678的EDMA控制器可以在不占用CPU的情况下搬数据,CPU只管计算。图像处理、FFT这类任务,用EDMA把数据从DDR搬到L2,算完再搬回去,CPU利用率能提高不少。
核间负载均衡。八个核不要一个干死、七个闲着。把任务拆成小块,动态分配给各核。我做过一个矩阵运算的项目,把矩阵分块后分给八个核并行算,速度接近单核的七倍(有通信开销,达不到八倍)。
Cache配置。C6678的L1和L2都有Cache,合理配置Cache能减少内存访问次数。但Cache一致性是多核的坑——一个核改了数据,另一个核的Cache里还是旧值。解决办法是用Cache一致性操作(如CACHE_wbInvL2)在读写共享数据前后刷新Cache。这个操作有开销,不要滥用,只在核间共享数据时用。
6. 从工程模板到实际项目的距离
6.1 外设驱动的集成方式
实际项目不可能只用DSP核心,必然要接外设:UART、SPI、I2C、EMIF、网口等。MCSDK里提供了平台库(Platform Library)和外设驱动(LLD),但直接拿来用往往不顺手。我的做法是:在MCSDK驱动上封装一层,把初始化、读写、中断处理包装成简洁的API,上层应用不直接碰寄存器。
比如UART,MCSDK的LLD用起来要配置一堆结构体,我封装成:
void UART_Init(Uint32 baudrate); void UART_SendByte(Uint8 data); Uint8 UART_RecvByte(void); void UART_SendString(const char *str);这样应用层代码干净,换平台时只改底层封装,上层不动。封装时注意中断和轮询两种模式都要支持,调试时用轮询简单,实际项目用中断效率高。
6.2 链接脚本的模块化管理
项目大了以后,.cmd文件会变得很长,各种段映射混在一起。我的做法是按模块拆分.cmd文件,用#include组合。比如:
/* main.cmd */ #include "memory_map.cmd" #include "core0_sections.cmd" #include "shared_sections.cmd"memory_map.cmd定义所有内存段的起始地址和长度,core0_sections.cmd定义Core0的段映射,shared_sections.cmd定义共享内存的段映射。这样改内存配置时只动memory_map.cmd,改某个核的映射时只动对应的文件,清晰且不容易出错。
6.3 版本管理与团队协作
多核工程文件多,团队协作时版本管理很重要。我建议只把源文件、头文件、.cmd文件、工程配置文件(.project、.cproject)纳入版本管理,编译产物(.out、.obj、.map)不要提交。CCS的工程配置文件里包含绝对路径,不同人电脑上的路径不一样,提交后别人拉下来要改路径。解决办法是用路径变量(Path Variable),在CCS里定义WORKSPACE_LOC等变量,工程配置里用变量引用路径,这样换电脑不用改配置。
还有一点:每个核的工程单独编译、单独版本号。不要八个核共用一个版本号,否则改了一个核,其他核的版本号也跟着变,追溯问题时很麻烦。我习惯用core0_v1.2.3这样的命名,每个核独立管理。
6.4 从调试模式到固化模式的切换清单
调试跑通了,要固化到Flash,中间还有一段路。我整理了一个切换清单,每次固化前对照检查:
- [ ] 启动模式引脚改成目标模式(SPI/EMIF等)
- [ ] 二级引导程序已编译并烧写到Flash的引导区
- [ ] 应用程序镜像已生成并烧写到Flash的应用区
- [ ] DDR3初始化参数与板子实际芯片匹配
- [ ] 各核入口地址已正确写入镜像元数据
- [ ] 中断向量表已正确映射(从核的IPC中断)
- [ ] 看门狗已配置(防止程序跑飞后死机)
- [ ] 上电复位测试至少三次,确认稳定启动
这个清单看着简单,但每次固化我都至少能查出一个小问题。固化启动和调试启动的环境差异很大——调试时JTAG连着,芯片状态受仿真器控制;固化时芯片完全自主运行,任何初始化遗漏都会导致启动失败。
7. 几个让我印象深刻的踩坑记录
7.1 从核程序加载到错误地址
有一次调试,主核跑得好好的,从核一唤醒就进异常。查了两天,最后发现是从核的.out文件链接地址和主核搬运的目标地址不一致。从核工程里.cmd文件配的是链接到0x00800000(L2起始),但主核代码里搬运的目标地址写的是0x00840000,差了256KB。从核代码被搬到错误位置,跳转过去执行的就是垃圾数据。
教训:主核搬运地址必须和从核链接地址严格一致。后来我在主核代码里用宏定义统一管理这些地址,从核的.cmd文件也用同一个宏,避免手写地址不一致。
7.2 IPC中断没使能导致从核不响应
另一次,主核写了IPC寄存器,但从核没反应。查了IPC寄存器的值,确实写进去了,但从核的中断服务程序没执行。原因是从核的全局中断使能位没打开。C66x的中断需要三层使能:全局中断使能(GIE)、中断控制器使能(IER)、中断向量表映射。少一层,中断就进不来。
这个问题的隐蔽性在于:主核写IPC寄存器是成功的,从核的IPC中断标志位也置位了,但因为GIE没开,CPU不响应中断。调试时看寄存器都对,就是没反应。后来我养成了习惯:从核初始化代码里第一件事就是开全局中断,然后再配置其他。
7.3 DDR3参数不匹配导致的偶发崩溃
最坑的一次是DDR3问题。程序跑起来正常,但跑几分钟到几十分钟不等就会崩溃,崩溃位置随机。用CCS连上后看,发现是DDR3里的数据被篡改了。查了很久,最后用DDR3测试程序跑memtest,发现特定地址段有偶发位翻转。原因是DDR3的刷新周期配置偏大,高温下数据保持不住。
调整刷新周期后问题消失。这个坑的教训是:DDR3初始化后一定要跑memtest,不要假设配置对了就没问题。memtest要覆盖全部地址空间,跑至少十分钟,确认没有位翻转。TI的MCSDK里有现成的DDR3测试代码,直接拿来用就行。
7.4 CCS工程路径含空格导致的编译失败
这个坑不算技术问题,但很浪费时间。CCS的编译脚本对路径里的空格处理不好,工程路径里有空格时,编译会报"找不到文件"或"命令解析失败"。我一开始把工程放在"Program Files"类似的目录下,编译一直报错,换成全英文无空格路径后正常。
后来我定了个规矩:所有TI工具链相关的路径,全英文、无空格、无中文。包括CCS安装路径、Workspace路径、工程路径、MCSDK路径。这个规矩看着简单,但能避免很多莫名其妙的编译问题。
8. 关于C6678工程实战的一点个人体会
C6678这颗芯片,入门曲线陡,但一旦跑通一次完整的"建工程-调试-固化"流程,后面就顺了。我刚开始接触时,觉得多核、启动模式、链接脚本这些东西很玄乎,踩了几次坑之后发现,本质上就是把每个环节的因果关系搞清楚:为什么代码要放L2、为什么从核要等主核唤醒、为什么启动模式引脚决定加载路径。搞清楚这些"为什么",遇到问题就能顺着因果链排查,而不是瞎试。
另外,TI的文档虽然多,但分散在数据手册、用户指南、应用笔记、Wiki里,找起来费劲。我的习惯是建一个自己的知识库,把调试中遇到的问题、解决方法、关键寄存器配置记下来,下次遇到类似问题直接查。C6678的坑就那么多,踩一遍记一遍,两三个项目下来就基本覆盖了。
最后说一个实际项目里的经验:不要等到项目后期才做固化。我见过团队调试阶段跑得好好的,临近交付才做Flash固化,结果发现一堆问题:镜像太大塞不进Flash、启动时间超预期、多核同步在固化模式下失效。固化要尽早做,最好在工程框架搭好后就走一遍固化流程,把问题暴露在前面。调试模式和固化模式的差异,比你想象的大。