1. 协处理器系统集成到底在解决什么问题第一次接触“协处理器系统集成”这个说法的人很容易把它理解成“把一块加速卡插到主板上装个驱动就完事”。如果真这么简单它就不会单独成为一章的内容了。协处理器系统集成的本质是让一个异构计算单元和主处理器之间形成稳定、高效、可管理的协作关系——主处理器负责通用逻辑和调度协处理器负责特定类型的密集计算两者通过总线、内存、中断和软件栈连成一个整体。我在实际项目里踩过的最大的一个坑就是早期把协处理器当成一个“外设”来对待。外设的思路是主处理器发命令外设执行执行完给个结果。但协处理器不是外设它有自己的指令流、自己的本地存储、自己的执行节奏甚至在某些架构里它还能反向访问主存。如果你用外设的思维去集成它最后一定会遇到数据不一致、任务堆积、中断丢失这类问题。这一章要解决的核心问题可以拆成四层物理层协处理器怎么挂到系统总线上地址空间怎么划分时钟和复位怎么同步。数据层主处理器和协处理器之间怎么共享数据缓存一致性怎么保证DMA通道怎么配置。控制层任务怎么下发状态怎么回读中断怎么路由异常怎么处理。软件层驱动怎么组织运行时怎么调度上层接口怎么封装。这四层任何一层没打通整个系统就会表现出“时好时坏”的症状——跑小数据量没问题一上真实负载就崩单任务没问题多任务并发就死锁。这类问题最难查因为现象和根因之间隔了好几层。适合读这一章的人应该是对主处理器架构有基本了解、写过裸机驱动或者RTOS任务调度的开发者。如果你连中断向量表和MMU都没接触过建议先把基础补一补再回来。但如果你已经做过一两个嵌入式项目想搞清楚“为什么我的加速器一接进来系统就不稳”那这一章的内容应该能帮你把思路理顺。下面我会按照“先讲清楚硬件怎么连、再讲数据怎么走、然后讲控制怎么管、最后讲软件怎么搭”的顺序展开中间穿插我在实际调试中总结的判断方法和避坑经验。很多细节在官方手册里只有一句话但那一句话背后可能是一整天的调试。2. 总线挂载与地址空间划分的实操细节2.1 协处理器挂载方式的选择逻辑协处理器和主处理器的连接方式直接决定了后面所有软件设计的复杂度。常见的挂载方式有三种共享总线挂载、点对点高速链路挂载、片上网络挂载。这三种没有绝对优劣关键看你的数据吞吐需求和延迟容忍度。共享总线挂载是最传统的方式协处理器和主处理器挂在同一组地址/数据总线上通过片选信号区分。优点是硬件简单、地址统一编址、软件上可以直接用指针访问。缺点是总线带宽被所有设备共享协处理器一旦开始大批量传输主处理器的取指和数据访问都会被拖慢。我做过一个图像预处理的项目协处理器挂在共享总线上结果一开DMA主处理器的中断响应延迟从2微秒直接飙到30微秒整个控制环路都抖了。点对点高速链路挂载是现在比较主流的做法协处理器和主处理器之间走独立的串行或并行链路比如常见的差分对链路。这种方式带宽独立、延迟可控但软件上不能直接指针访问必须通过寄存器窗口或者DMA描述符来搬数据。很多人在这一步会低估软件工作量——你以为只是换个访问方式实际上整个数据流都要重新设计。片上网络挂载通常出现在多核SoC里协处理器作为网络上的一个节点通过路由器和主处理器通信。这种方式扩展性最好但调试也最麻烦因为数据包的路径不固定延迟有抖动。如果你的应用对确定性延迟有要求片上网络需要额外做流量整形。选择挂载方式时我一般会问三个问题第一协处理器的峰值数据吞吐是多少主处理器能不能承受这个带宽占用第二任务下发和结果回读的延迟要求是多少是硬实时还是软实时第三软件团队有没有能力维护一套独立的通信协议栈。这三个问题的答案基本就能锁定方案。2.2 地址空间划分的常见误区地址空间划分看起来只是“分配一段地址给协处理器”但实际做的时候有几个容易翻车的地方。第一个误区是地址对齐。协处理器的寄存器块通常要求按它支持的最大位宽对齐比如64位寄存器要求8字节对齐。如果你在链接脚本里随便放一个地址编译能过但运行时访问会触发总线错误。我见过一个项目协处理器的控制寄存器基地址被放在了0x40000004结果所有64位写操作都变成非对齐访问硬件直接返回错误。查了两天才发现是链接脚本里少了一个ALIGN(8)。第二个误区是地址空间重叠。主处理器和协处理器如果都能访问同一段物理内存必须明确这段内存的归属和访问规则。常见做法是划分三个区域主处理器专用区、协处理器专用区、共享区。共享区再细分为“主写协读”“协写主读”“双向读写”三种。如果不做这个划分两边同时写同一块内存数据就乱了。第三个误区是地址映射的缓存属性。共享内存区域必须配置成非缓存或者写穿透否则主处理器写的数据可能还在缓存里协处理器读到的就是旧值。这个问题在调试时特别隐蔽因为单步调试时缓存会被刷新看起来一切正常一跑全速就出错。我的经验是所有共享内存区域在MMU配置里一律标成Device或者Strongly-ordered宁可牺牲一点性能也要保证一致性。下面这张表是我总结的地址空间划分检查清单每次新项目我都会对着过一遍检查项常见错误正确做法寄存器基地址对齐按4字节对齐按协处理器最大位宽对齐共享内存缓存属性默认缓存非缓存或写穿透地址空间重叠未划分专用区明确三段式划分保留区域未留空洞每个区域间留4KB保护带地址译码片选逻辑错误用独立译码器验证2.3 时钟与复位同步的处理经验协处理器和主处理器往往工作在同一个时钟域但也有一些设计里协处理器有独立的时钟分频。如果时钟不同步跨时钟域的信号就必须做同步处理否则会出亚稳态。我处理过的一个案例是协处理器的中断信号直接连到主处理器的中断控制器没有做同步。大部分时候没问题但偶尔会丢中断。后来用逻辑分析仪抓波形发现中断信号的建立时间刚好在时钟边沿附近主处理器采样到了不确定的值。加了两级触发器同步之后问题消失。复位也是类似。协处理器的复位如果和主处理器复位不同步可能出现主处理器已经开始访问协处理器寄存器但协处理器还没完成复位的情况。标准做法是主处理器复位释放后先延时一段时间再释放协处理器复位或者协处理器提供一个“复位完成”状态位主处理器轮询这个位之后再开始初始化。提示跨时钟域的信号不管是中断、握手还是数据都必须做同步处理。不要相信“大部分时候没问题”在批量产品里那“偶尔”的一次失败就是致命的。3. 数据通路与缓存一致性的处理策略3.1 共享内存与DMA的配合方式数据在协处理器和主处理器之间怎么走是系统集成里最影响性能的部分。常见的方式有三种主处理器直接访问协处理器本地存储、通过共享内存交换、通过DMA搬运。主处理器直接访问协处理器本地存储的方式延迟最低但通常容量有限而且会占用协处理器的本地总线。适合小数据量的命令和状态交换不适合大批量数据。共享内存交换是最通用的方式两边都能访问同一块物理内存。但这里有个关键问题谁负责搬数据如果主处理器用CPU指令搬会占用大量CPU周期如果用DMA搬就要配置DMA控制器而且DMA和CPU之间的缓存一致性又要处理。DMA搬运是吞吐量最高的方式但配置复杂度也最高。我一般会建议如果数据量超过几KB就上DMA如果只是几十字节的命令直接寄存器访问更简单。DMA配置里有几个参数必须算清楚。以常见的描述符链表模式为例每个描述符包含源地址、目的地址、传输长度、下一个描述符指针。描述符本身也要放在共享内存里而且必须保证DMA控制器能看到最新的描述符内容。这意味着描述符内存也必须是非缓存的或者在启动DMA之前手动刷缓存。我踩过的一个坑是描述符链表只分配了一个描述符DMA传完第一个就停了。后来发现是描述符的“下一个指针”没有初始化成NULLDMA控制器读到了一个随机值跑飞了。所以描述符内存分配后一定要先清零再填有效字段。3.2 缓存一致性的三种解决思路缓存一致性是异构计算里最头疼的问题之一。主处理器有缓存协处理器可能也有缓存两边看到的内存视图不一致数据就错了。解决思路有三种第一种是禁用缓存。共享内存区域不经过缓存所有访问直接打到物理内存。优点是简单可靠缺点是性能损失大。如果共享数据量不大这种方式完全可以接受。第二种是软件维护一致性。在访问共享数据前后手动执行缓存刷新和无效化操作。比如主处理器写完数据后执行一次cache clean把数据刷到内存协处理器读完数据后执行一次cache invalidate把旧缓存丢掉。这种方式灵活但容易漏操作而且代码可移植性差。第三种是硬件维护一致性。有些SoC提供了硬件一致性互连能自动保证缓存一致。这种方式对软件最友好但硬件成本高而且不是所有平台都支持。我的经验是如果平台支持硬件一致性优先用硬件方案如果不支持共享数据量小就用禁用缓存共享数据量大且访问频繁就老老实实做软件维护并且把刷新操作封装成宏或者内联函数减少遗漏。下面这段代码是我常用的缓存维护封装基于常见的CMSIS接口// 共享内存区域定义为非缓存但为了兼容某些场景保留手动维护接口 #define SHARED_BUF_ADDR (0x20000000UL) #define SHARED_BUF_SIZE (4096U) static inline void shared_cache_clean(void *addr, uint32_t size) { uint32_t start (uint32_t)addr ~(CACHE_LINE_SIZE - 1); uint32_t end ((uint32_t)addr size CACHE_LINE_SIZE - 1) ~(CACHE_LINE_SIZE - 1); for (uint32_t p start; p end; p CACHE_LINE_SIZE) { DCACHE_CleanByAddr(p); } DSB(); } static inline void shared_cache_invalidate(void *addr, uint32_t size) { uint32_t start (uint32_t)addr ~(CACHE_LINE_SIZE - 1); uint32_t end ((uint32_t)addr size CACHE_LINE_SIZE - 1) ~(CACHE_LINE_SIZE - 1); for (uint32_t p start; p end; p CACHE_LINE_SIZE) { DCACHE_InvalidateByAddr(p); } DSB(); ISB(); }注意这里的地址对齐处理缓存操作是按缓存行进行的如果只刷新部分数据必须把地址向下对齐到缓存行边界长度向上对齐。否则相邻数据可能被误刷或者漏刷。3.3 数据通路的性能估算方法在设计阶段怎么判断数据通路能不能满足需求我一般会做一个简单的带宽和延迟估算。带宽方面先算协处理器的峰值数据率。比如一个图像协处理器每秒钟处理60帧每帧1920x1080像素每个像素2字节那么原始数据率是60 × 1920 × 1080 × 2 ≈ 249 MB/s。如果协处理器还要回写处理结果带宽需求翻倍。然后看总线能提供多少带宽共享总线通常只有几百MB/s点对点链路可以到GB/s级别。如果带宽不够要么降帧率要么做数据压缩要么换挂载方式。延迟方面算任务下发到结果回读的总时间。包括主处理器写命令的时间、命令通过总线传到协处理器的时间、协处理器执行时间、结果回写时间、中断响应时间。如果这个总时间超过了系统的实时性要求就要考虑流水线化——把一个大任务拆成多个小任务让协处理器和主处理器并行工作。我做过一个运动控制的项目协处理器负责计算PID输出主处理器负责生成PWM。一开始是主处理器发命令、等结果、再发命令循环周期是50微秒。后来改成双缓冲主处理器在协处理器计算当前周期的同时准备下一个周期的数据循环周期降到了20微秒。这个优化没有改任何硬件只是改了软件流程。4. 任务下发、中断路由与异常处理4.1 命令队列的设计与实现主处理器和协处理器之间的任务下发最简单的做法是“写一个命令寄存器等一个状态位”。但这种方式在任务多的时候效率很低因为主处理器要轮询等待。更好的做法是做一个命令队列。命令队列的基本结构是在共享内存里分配一段环形缓冲区主处理器作为生产者往队尾写命令协处理器作为消费者从队头取命令。两边各维护一个读写指针通过比较指针判断队列空满。这里有几个细节要注意。第一读写指针的更新必须是原子的否则两边同时改会丢更新。如果指针是32位的在32位总线上单次写是原子的可以直接用。如果指针是64位的或者总线宽度小于指针宽度就要加锁或者用原子指令。第二队列满和队列空的条件要定义清楚。常见做法是留一个空位当写指针加一等于读指针时认为满当读写指针相等时认为空。这样最多只能存N-1个命令但逻辑简单可靠。第三命令的格式要固定长度方便索引。如果命令长度可变环形缓冲区的管理会复杂很多。我一般会把命令设计成固定64字节或者128字节不够的用保留字段填充。下面是一个命令队列的结构定义示例typedef struct { uint32_t cmd_id; // 命令标识 uint32_t cmd_type; // 命令类型 uint32_t src_addr; // 源数据地址 uint32_t dst_addr; // 目的数据地址 uint32_t data_len; // 数据长度 uint32_t flags; // 标志位 uint32_t reserved[10]; // 保留凑齐64字节 } coproc_cmd_t; typedef struct { volatile uint32_t write_idx; volatile uint32_t read_idx; coproc_cmd_t cmds[CMD_QUEUE_DEPTH]; } cmd_queue_t;主处理器下发命令的流程是检查队列是否满如果不满把命令写到write_idx位置然后write_idx加一最后写一个“门铃”寄存器通知协处理器。协处理器收到门铃中断后从read_idx位置取命令执行执行完read_idx加一。注意门铃寄存器最好设计成“写任意值触发”而不是“写1触发”。因为如果协处理器还没来得及清中断主处理器又写了一次门铃写1触发的设计可能会丢中断。写任意值触发的设计每次写都会产生一个边沿不会丢。4.2 中断路由的配置与调试中断是协处理器通知主处理器“任务完成”或者“出错”的主要手段。中断路由配置错了系统就变成聋子协处理器干完活主处理器不知道。中断路由一般涉及三个环节协处理器产生中断信号、中断控制器接收并分发、主处理器响应中断。每个环节都可能出问题。协处理器侧要确认中断是电平触发还是边沿触发。电平触发的话协处理器必须保持中断信号有效直到主处理器清除中断源边沿触发的话协处理器只要产生一个脉冲就行。我见过一个设计协处理器产生的是边沿中断但中断控制器配置成了电平触发结果中断一直挂着主处理器反复进中断服务程序系统卡死。中断控制器侧要确认优先级和屏蔽设置。如果协处理器的中断优先级低于某个正在处理的中断它会被挂起直到高优先级中断处理完。如果这个高优先级中断是周期性的协处理器的中断可能永远得不到响应。所以协处理器的中断优先级通常要设得比较高。主处理器侧中断服务程序要做三件事清除协处理器的中断源、处理任务结果、如果还有后续任务就继续下发。清除中断源这一步特别容易漏漏了的话中断会反复触发。调试中断问题时我一般会先看中断控制器的状态寄存器确认中断有没有到达控制器再看主处理器的中断挂起寄存器确认中断有没有到达CPU最后在中断服务程序入口加一个计数器确认实际进了多少次。这三步基本能定位问题在哪一环。4.3 异常处理与超时保护协处理器不是永远可靠的。它可能因为数据错误、指令错误、硬件故障而挂死。如果主处理器只是傻等中断协处理器一挂整个系统就挂了。所以必须做超时保护。超时保护的基本做法是主处理器下发任务后启动一个定时器。如果在定时器到期前收到完成中断就取消定时器如果定时器到期还没收到中断就认为协处理器异常执行恢复流程。恢复流程包括复位协处理器、清空命令队列、标记未完成任务为失败、通知上层应用。复位协处理器时要注意不能影响主处理器和其他外设。如果协处理器和主处理器共享复位那就只能做软复位通过协处理器的控制寄存器触发。超时时间怎么定我的经验是取正常执行时间的3到5倍。比如正常任务执行10毫秒超时设30到50毫秒。太短会误报太长会拖慢故障恢复。如果任务执行时间波动很大可以做成动态超时根据任务类型查表得到基准时间再乘以一个系数。还有一个容易被忽略的点协处理器异常后它可能还在往共享内存里写数据破坏主处理器的数据结构。所以恢复流程里复位协处理器之后要重新初始化共享内存区域把残留数据清掉。5. 驱动分层与运行时调度的组织方式5.1 驱动分层的设计原则协处理器的驱动不应该是一个大文件而应该分层。我一般分成三层硬件抽象层、核心驱动层、接口层。硬件抽象层直接操作寄存器提供读写寄存器的函数。这一层和具体硬件相关换一个协处理器就要重写。但它的接口要稳定比如coproc_reg_write(offset, value)和coproc_reg_read(offset)。核心驱动层实现命令队列管理、中断处理、超时保护、异常恢复。这一层和硬件无关只依赖硬件抽象层的接口。换协处理器时这一层基本不用改。接口层向上层应用提供任务下发和结果获取的API。可以是同步阻塞的也可以是异步回调的。同步接口简单但会阻塞调用线程异步接口复杂但吞吐量高。分层的价值在于调试时能快速定位问题在哪一层。如果寄存器读写不对问题在硬件抽象层如果命令队列乱了问题在核心驱动层如果上层调用返回错误问题在接口层。5.2 运行时调度的任务拆分策略一个复杂的计算任务是整体丢给协处理器还是拆成多个小任务这取决于任务的特性和协处理器的能力。如果协处理器有足够的本地存储能放下整个任务的数据和中间结果那整体下发最简单通信开销最小。但如果数据量超过了本地存储就必须拆分成多个阶段每个阶段处理一部分数据中间结果通过共享内存交换。拆分的粒度怎么定太粗了本地存储放不下太细了通信开销占比太高。我一般会按协处理器本地存储容量的70%来定每个阶段的数据量留30%给中间变量和栈。拆分之后阶段之间的依赖关系要管理好。如果阶段B依赖阶段A的输出那阶段B必须等阶段A完成才能开始。如果阶段之间没有依赖可以并行下发让协处理器自己调度。还有一种情况是任务可以流水线化。比如图像处理里的滤波和缩放滤波的输出是缩放的输入。如果协处理器支持流水线可以让滤波和缩放同时工作在不同数据块上吞吐量翻倍。但这要求协处理器有多个执行单元或者至少能在执行当前任务的同时接收下一个任务。5.3 上层接口的封装与错误码设计上层应用不关心协处理器的寄存器细节它只关心“我提交一个任务你告诉我成功还是失败结果在哪里”。所以接口层要封装得足够简单。我常用的接口形式是这样的typedef enum { COPROC_OK 0, COPROC_ERR_BUSY, COPROC_ERR_TIMEOUT, COPROC_ERR_HARDWARE, COPROC_ERR_INVALID_PARAM, COPROC_ERR_QUEUE_FULL, } coproc_status_t; coproc_status_t coproc_submit_task(const coproc_task_t *task, uint32_t timeout_ms); coproc_status_t coproc_query_result(uint32_t task_id, coproc_result_t *result); coproc_status_t coproc_cancel_task(uint32_t task_id);错误码的设计要能区分“可重试”和“不可重试”。比如COPROC_ERR_BUSY和COPROC_ERR_QUEUE_FULL是可重试的上层可以等一会儿再提交COPROC_ERR_HARDWARE是不可重试的上层应该走故障处理流程。超时参数由调用者指定而不是驱动写死。因为不同任务的执行时间差异很大统一超时要么误报要么漏报。调用者最清楚自己的任务需要多长时间。还有一个细节任务ID的分配。如果任务ID是全局递增的要考虑溢出。32位ID在每秒一万个任务的场景下大约5天溢出一次。如果系统需要长时间运行要么用64位ID要么在溢出时做特殊处理。6. 联调阶段的问题定位与实测经验6.1 从现象到根因的排查链路协处理器系统集成的问题现象往往很模糊“系统跑一会儿就卡死”“数据偶尔不对”“中断有时候丢”。要从这些现象定位到根因需要一套系统的排查方法。我的排查链路一般是这样的第一步确认问题是否可复现。如果只是偶尔出现先想办法提高复现频率。比如加大数据量、提高任务频率、升高环境温度。可复现的问题才好查。第二步缩小问题范围。把系统拆成最小可工作单元只跑一个任务、只用一小块数据、只开一个中断。如果最小系统正常逐步加回复杂度直到问题出现。这样能定位到是哪个模块引入的问题。第三步抓取现场数据。在关键路径上加日志或者用逻辑分析仪抓波形。日志要包含时间戳、任务ID、状态寄存器的值。逻辑分析仪要抓总线信号、中断信号、握手信号。第四步对比正常和异常的数据。正常时的寄存器值是什么异常时是什么正常时的时序是什么异常时是什么。差异点往往就是根因。我遇到过一个案例系统跑几分钟后数据开始出错。抓取共享内存的内容发现某些位置的数据被写成了0。进一步排查发现是协处理器在异常复位后DMA还在继续搬运把共享内存覆盖了。修复方法是在复位协处理器之前先停止DMA通道。6.2 常见问题的快速对照表下面这张表是我整理的协处理器集成常见问题速查表覆盖了大部分我遇到过的情况现象可能原因排查方法修复方案中断丢失跨时钟域未同步逻辑分析仪抓中断信号加两级触发器同步数据不一致缓存未维护对比缓存和内存内容禁用缓存或手动刷新任务堆积队列满未处理读队列指针增加队列深度或流控系统卡死中断未清除读中断状态寄存器在ISR中清除中断源数据错位地址对齐错误检查访问地址按位宽对齐偶发错误时序违例降低时钟频率测试调整时序约束复位后异常复位不同步检查复位释放顺序增加复位同步逻辑DMA跑飞描述符未初始化读描述符内存分配后先清零这张表不能覆盖所有情况但能覆盖80%的常见问题。遇到新问题时我会先对照这张表排除已知原因再深入分析。6.3 压力测试与稳定性验证功能调通只是第一步稳定性验证才是决定能不能量产的关键。我一般会做三类测试长时间运行测试、边界条件测试、异常注入测试。长时间运行测试是让系统连续跑24到72小时观察有没有性能下降、内存泄漏、任务丢失。协处理器的温度、功耗、错误计数器都要监控。如果跑几个小时就出问题说明有资源没释放或者有累积误差。边界条件测试是故意用极端参数最大数据量、最小数据量、最高频率、最低频率、队列满、队列空。这些边界往往藏着指针越界、溢出、死锁的问题。异常注入测试是人为制造故障拔掉数据源、注入错误数据、模拟协处理器无响应。看系统能不能检测到异常并正确恢复。这个测试最能暴露超时保护和异常恢复的缺陷。我做过一个项目功能测试全过但压力测试跑到第8个小时的时候协处理器突然不响应了。查下来是命令队列的读写指针在长时间运行后发生了溢出回绕而队列空满判断逻辑没有处理回绕后的情况。修复方法是在指针比较时用无符号数的自然回绕特性而不是直接比较大小。提示压力测试不要只跑一遍。很多问题是概率性的跑一遍没出不代表没问题。我一般会跑至少三遍每遍24小时三遍都过才认为稳定。6.4 性能调优的几个实用手段系统稳定之后如果性能不达标还有几个调优手段可以试。第一调整任务粒度。如果任务太小通信开销占比高如果任务太大并行度不够。找到最佳粒度通常需要做几组实验画一条性能曲线。第二优化数据布局。协处理器访问连续内存的效率通常高于随机访问。如果数据在内存里是分散的可以先做一次整理让协处理器顺序读取。第三重叠通信和计算。主处理器准备下一批数据的同时协处理器处理当前批数据。用双缓冲或者多缓冲实现。这个优化通常能带来30%到50%的性能提升。第四减少中断次数。如果每个小任务都产生一个中断中断开销会很大。可以改成批量完成中断协处理器处理完一批任务后只产生一个中断。第五调整时钟频率。如果协处理器和主处理器共享时钟提高频率能提升性能但也会增加功耗和时序风险。如果独立时钟可以单独调整协处理器的频率。我在一个视频处理项目里通过双缓冲加批量中断把端到端延迟从15毫秒降到了6毫秒而且主处理器的CPU占用率从40%降到了12%。这些优化没有改硬件全是软件层面的调整。7. 写在最后的一些个人体会协处理器系统集成这件事硬件设计决定了上限软件设计决定了下限。我见过硬件很先进但软件一团糟的系统性能还不如纯软件实现也见过硬件一般但软件设计精巧的系统跑出了超出预期的效果。如果让我给刚接触这个领域的开发者一个建议那就是先把最小系统跑通再逐步加功能。不要一上来就搞复杂的命令队列、多级中断、DMA链表。先用最简单的轮询方式让主处理器能读写协处理器的寄存器能启动一个任务能读到结果。这个最小系统跑通了再往上加中断、加DMA、加队列。每加一个功能都做一次完整的回归测试。这样出问题时你很清楚是哪个功能引入的。另一个体会是日志和计数器比调试器好用。协处理器的问题往往和时序相关用调试器单步会改变时序问题可能就不出现了。在关键路径上加计数器记录任务下发次数、完成次数、超时次数、错误次数跑一段时间后看计数器的值往往能发现规律。最后不要相信“应该没问题”。共享内存的缓存一致性、中断的跨时钟域同步、复位释放的顺序这些地方只要有一点没处理到位在实验室可能跑得好好的到现场就出问题。该加的同步逻辑要加该做的压力测试要做该留的余量要留。
阅读完成 · 觉得有帮助?