1. 项目缘起为什么图传系统里非得塞一颗字符叠加芯片搞过无人机图传的兄弟都清楚飞控数据、电池电压、飞行高度、GPS星数这些信息如果只能回到地面站才能看那飞起来心里是没底的。尤其是FPV飞行或者行业应用场景里飞手需要实时看到关键OSD信息这就催生了字符叠加芯片的需求。AT7456E就是在这个背景下被大量采用的方案它本质上是一颗NTSC/PAL视频字符叠加芯片能直接在模拟视频信号上叠加文字和图形不需要额外的视频编码器。我最早接触这颗芯片是在一个农业植保机的项目上当时客户要求图传画面上必须实时显示亩用量、飞行速度和剩余药量。最初想用STM32直接驱动视频DAC做叠加实测下来CPU占用率太高而且同步头处理很麻烦。后来换成AT7456ESPI配置好寄存器之后芯片自己完成行场同步和字符点阵生成MCU只需要在需要更新数据的时候写几个寄存器就行负担一下子降下来了。这颗芯片的核心价值在于三点第一它兼容标准的NTSC和PAL制式直接串在摄像头和发射机之间就能工作第二内置了256个字符的显示内存支持自定义字符点阵第三SPI接口配置灵活STM32F103这种级别的MCU就能轻松驱动。适合谁参考做无人机图传、FPV眼镜、模拟视频监控、车载倒车影像叠加的嵌入式工程师尤其是那些需要在视频上叠加实时数据的场景。注意AT7456E是3.3V供电的芯片但它的视频输入输出是模拟信号布线的时候要特别注意模拟地和数字地的隔离否则画面上会出现横纹干扰。2. 硬件设计从原理图到PCB布局的实战要点2.1 最小系统连接与电源处理AT7456E的引脚不算多但每一个都有讲究。核心引脚包括视频输入VIN、视频输出VOUT、SPI接口SCLK、SDI、SDO、CS、复位引脚RESET、以及晶振输入。我一般推荐用27MHz的无源晶振配合两个22pF的负载电容实测下来起振很稳定。电源部分芯片需要3.3V的数字电源和模拟电源虽然数据手册上说可以共用但我在实际项目中会用一个磁珠把数字3.3V和模拟3.3V隔开再各自加一个0.1uF和10uF的退耦电容。视频输入部分摄像头出来的信号一般幅值是1Vpp左右需要经过一个交流耦合电容通常0.1uF再进入VIN引脚。这里有个坑如果你用的摄像头输出阻抗不是75欧姆画面可能会出现亮度异常。我习惯在VIN前面加一个75欧姆的对地电阻确保阻抗匹配。视频输出端同样需要75欧姆的驱动电阻然后直接接到图传发射机的输入端。SPI接口这边AT7456E支持SPI模式0和模式3我一般用模式0CPOL0CPHA0。CS片选信号建议用硬件片选虽然软件片选也能用但在高速更新OSD数据的时候硬件片选能避免时序错乱。SDO引脚如果不需要回读寄存器可以悬空但建议还是接上调试的时候能读回寄存器值确认配置是否成功。2.2 PCB布局的避坑经验PCB布局这块我踩过不少坑。第一次画板子的时候把晶振放在了板子边缘结果图传画面上总是有细密的竖条纹。后来用示波器一看晶振波形上叠加了很大的噪声。解决办法很简单晶振尽量靠近芯片走线尽量短晶振下方铺地铜皮并且不要走任何其他信号线。模拟视频走线要远离SPI数字信号线如果实在避不开中间加一根地线做隔离。我通常会把视频输入输出走线放在顶层SPI走线放在底层中间用完整的地平面隔开。电源走线要足够宽至少15mil以上确保压降不会影响芯片工作。还有一个细节AT7456E的RESET引脚内部有上拉但上电时序如果太慢芯片可能初始化失败。我一般会在RESET引脚上接一个10k上拉电阻和一个0.1uF电容到地形成一个简单的RC复位电路时间常数大概1ms实测下来很稳。3. SPI配置全流程从寄存器初始化到字符显示3.1 SPI底层驱动配置STM32F103的SPI外设配置AT7456E我一般用SPI1时钟分频到18MHz左右也就是72MHz的APB2时钟四分频。为什么不用更高的速度因为AT7456E的SPI最高支持20MHz但实际跑下来18MHz最稳定再高的话如果PCB走线稍长就容易出现数据错误。配置步骤用CubeMX的话很简单SPI模式选全双工主模式数据宽度8位CPOL0CPHA0NSS软件管理预分频系数选4。生成代码之后需要自己封装读写函数。写寄存器的时候先拉低CS发送寄存器地址最高位为0表示写再发送数据最后拉高CS。读寄存器的时候地址最高位为1然后发送一个空字节读取返回值。void AT7456E_WriteReg(uint8_t reg, uint8_t data) { HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_RESET); HAL_SPI_Transmit(hspi1, reg, 1, 100); HAL_SPI_Transmit(hspi1, data, 1, 100); HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_SET); } uint8_t AT7456E_ReadReg(uint8_t reg) { uint8_t txData reg | 0x80; uint8_t rxData 0; HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_RESET); HAL_SPI_Transmit(hspi1, txData, 1, 100); HAL_SPI_Receive(hspi1, rxData, 1, 100); HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_SET); return rxData; }提示HAL_SPI_Transmit和HAL_SPI_Receive的超时参数不要设成0否则一旦SPI总线被干扰程序会卡死。我一般设100ms足够完成一次传输。3.2 寄存器初始化序列AT7456E的寄存器不少但真正需要配置的就那么几个关键寄存器。我整理了一份最小初始化序列实测下来能正常工作寄存器地址功能推荐值说明0x00视频模式配置0x08NTSC模式PAL的话改成0x0C0x01视频输出配置0x00正常输出不启用测试模式0x02同步控制0x00内部同步自动检测行场0x04OSD使能0x01开启字符叠加0x05字符内存起始地址0x00从第一个字符开始0x06显示控制0x00正常显示不闪烁0x10行偏移0x20根据实际画面调整0x11列偏移0x30根据实际画面调整初始化的时候先写0x00配置视频制式然后写0x04开启OSD最后写0x10和0x11调整显示位置。这里有个细节0x10和0x11的值需要根据你的摄像头和显示屏实际效果来微调我一般会写一个测试字符然后一边看画面一边改这两个寄存器的值直到字符出现在理想位置。3.3 字符点阵数据的写入AT7456E内置了256个字符的显示内存每个字符是12x18的点阵占用54个字节。芯片出厂时已经预置了一套标准ASCII字符如果你只需要显示英文和数字直接调用预置字符就行。但如果你要显示中文或者自定义图标就需要自己生成点阵数据然后写入。写入自定义字符的流程是这样的先写0x05寄存器指定字符内存的起始地址然后连续写入54个字节的点阵数据。每个字节代表一行12个像素用两个字节表示高字节在前。我一般用PC端的小工具生成点阵数据然后导出成C数组直接嵌入代码里。const uint8_t customChar[54] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ... 省略中间数据 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; void AT7456E_LoadCustomChar(uint8_t charIndex) { AT7456E_WriteReg(0x05, charIndex * 54); for (int i 0; i 54; i) { AT7456E_WriteReg(0x06, customChar[i]); } }注意写入点阵数据的时候SPI速度不要太快我试过用18MHz写入偶尔会出现数据错位。后来降到9MHz就稳定了。如果你发现自定义字符显示乱码先检查SPI速度。4. 显示控制与动态更新让OSD数据实时刷新4.1 字符显示内存的映射关系AT7456E的显示内存是30列x16行的字符网格总共480个字符位置。每个位置对应一个字符索引写入索引值就能在对应位置显示字符。比如你想在第一行第一列显示字符A就往地址0x00写A的ASCII码。地址的计算方式是行号乘以30加上列号。我一般会定义一个二维数组来管理显示内容然后写一个刷新函数把数组内容批量写入芯片。批量写入的时候先写0x05寄存器设置起始地址然后连续写数据芯片会自动递增地址。这样比逐个写地址快很多。void AT7456E_UpdateDisplay(uint8_t displayBuf[16][30]) { AT7456E_WriteReg(0x05, 0x00); for (int row 0; row 16; row) { for (int col 0; col 30; col) { AT7456E_WriteReg(0x06, displayBuf[row][col]); } } }实测下来全屏刷新一次大概需要2ms左右对于OSD数据更新来说完全够用。如果你只更新部分区域可以只写变化的那几个字符进一步减少SPI占用。4.2 动态数据的格式化与显示无人机OSD数据一般包括飞行模式、电压、电流、高度、速度、GPS星数、回家方向等。这些数据需要实时更新我一般用定时器每100ms触发一次刷新。数据格式化的部分用sprintf把数值转成字符串然后逐个字符填入显示缓冲区。void UpdateOSDData(void) { char buf[16]; sprintf(buf, VOLT:%5.1fV, batteryVoltage); for (int i 0; i strlen(buf); i) { displayBuf[0][i] buf[i]; } sprintf(buf, ALT:%5.1fm, altitude); for (int i 0; i strlen(buf); i) { displayBuf[1][i] buf[i]; } AT7456E_UpdateDisplay(displayBuf); }这里有个性能优化的点不要每次刷新都全屏写入只写入变化的行。我一般会记录上一次的显示内容对比之后只更新有变化的字符位置。这样SPI占用能降低80%以上对于STM32F103这种资源有限的MCU来说很重要。4.3 闪烁与反色显示的实现AT7456E支持字符闪烁和反色显示通过0x06寄存器的特定位来控制。闪烁功能在显示报警信息的时候很有用比如电压过低的时候让电压值闪烁。反色显示可以用来高亮重要数据比如GPS星数少于6颗的时候把星数反色显示提醒飞手。实现方式是在写入字符数据的时候把最高位设置为1表示反色设置为0表示正常。闪烁的话需要配合一个定时器每隔500ms切换一次显示状态。我一般会在刷新函数里加一个标志位根据标志位决定是否写入反色数据。实操心得闪烁频率不要太高500ms到1s比较合适。太快了飞手看不清太慢了又起不到提醒作用。我试过200ms闪烁结果飞手反馈说眼睛累。5. 常见问题排查与避坑指南5.1 SPI通信不生效的排查思路SPI通信不生效是最常见的问题我遇到过好几次。排查的时候按这个顺序来先确认CS片选信号有没有正常拉低拉高用示波器看波形然后确认SCLK时钟有没有输出频率对不对再确认MOSI数据有没有变化。如果这三步都正常但芯片还是不工作那大概率是初始化序列有问题。我遇到过一次SPI波形完全正常但芯片就是不出字符。后来发现是RESET引脚一直处于低电平芯片根本没启动。所以调试的时候一定要先确认RESET引脚在上电后能正常拉高。还有一个坑AT7456E的SPI模式必须和MCU配置一致我试过用模式3配置结果芯片完全没反应改成模式0就好了。现象可能原因解决方法完全无字符显示RESET未拉高检查复位电路确保上电后RESET为高字符乱码SPI速度过快降低SPI时钟到9MHz以下画面有横纹电源噪声加强电源滤波模拟数字地隔离字符位置偏移行场偏移寄存器不对调整0x10和0x11寄存器值部分字符不显示显示内存地址错误检查地址计算方式确认行列映射5.2 视频画面干扰的解决方法视频画面干扰是另一个高频问题。表现是画面上出现横纹、竖纹、或者亮度异常。我总结了几种常见情况和对应的解决方法第一种是电源干扰表现为横纹滚动。解决方法是加强电源滤波在芯片的3.3V引脚旁边加一个10uF的钽电容和一个0.1uF的陶瓷电容。如果还不行就用一个LDO单独给AT7456E供电和MCU的电源分开。第二种是地线干扰表现为竖纹或者画面抖动。解决方法是检查地平面是否完整模拟地和数字地是否单点连接。我一般会在芯片下方铺一块完整的模拟地然后用一个0欧姆电阻连接到数字地。第三种是信号线干扰表现为画面有细密的噪点。解决方法是缩短视频走线远离SPI和晶振信号线。如果实在避不开就在视频线上加一个磁珠或者小电容滤波。5.3 自定义字符显示异常的调试技巧自定义字符显示异常一般有三种情况字符显示成方块、字符显示不全、字符位置错乱。第一种情况通常是点阵数据格式不对检查一下每个字节的位顺序AT7456E是高位在前。第二种情况可能是点阵数据长度不对确认一下是不是54个字节。第三种情况可能是字符索引计算错误确认一下写入的起始地址是否正确。我一般会先用预置字符测试确认硬件和SPI通信没问题之后再测试自定义字符。这样能快速定位问题出在硬件还是数据上。还有一个技巧写一个简单的测试图案比如全亮或者全暗写入自定义字符内存看看显示效果。如果全亮能正常显示说明硬件没问题问题出在点阵数据上。6. 性能优化与进阶玩法6.1 减少SPI占用的批量更新策略STM32F103的SPI带宽有限如果OSD数据更新频繁SPI占用率会很高。我一般用两种策略来优化第一种是只更新变化的字符前面已经提到了第二种是用DMA传输把SPI数据搬运交给DMACPU只负责准备数据。用DMA的话需要配置SPI的TX DMA通道然后把要发送的数据放到一个缓冲区启动DMA传输。传输完成之后DMA中断里拉高CS。这样CPU占用率能降到5%以下对于需要同时处理飞控数据的场景很有用。void AT7456E_WriteReg_DMA(uint8_t reg, uint8_t *data, uint16_t len) { uint8_t txBuf[len 1]; txBuf[0] reg; memcpy(txBuf[1], data, len); HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_RESET); HAL_SPI_Transmit_DMA(hspi1, txBuf, len 1); } void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi) { if (hspi hspi1) { HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_SET); } }注意DMA传输的时候txBuf必须是全局变量或者静态变量不能是局部变量否则DMA传输过程中栈被释放数据就乱了。6.2 多页面显示与菜单系统AT7456E的显示内存有480个字符位置足够做多页面显示。我一般会做两个页面一个飞行页面显示关键数据一个设置页面用来调整参数。页面切换通过一个按键或者遥控器通道来控制。实现方式是定义两个显示缓冲区切换的时候把对应的缓冲区内容写入芯片。设置页面的菜单项可以用反色显示来高亮当前选项用闪烁来提示可编辑状态。这套逻辑在STM32上实现起来不难关键是要管理好状态机避免页面切换的时候数据错乱。6.3 与飞控系统的数据对接无人机OSD数据一般来自飞控的串口或者CAN总线。我一般用UART接收飞控的Mavlink数据解析出需要的字段然后更新显示缓冲区。Mavlink解析可以用现成的库也可以自己写一个轻量级的解析器。关键是解析和显示要分开解析在中断或者任务里做显示在主循环里做避免相互阻塞。如果飞控数据更新频率很高比如50Hz那OSD刷新频率可以降到10Hz人眼看起来已经足够流畅了。我试过50Hz刷新画面反而因为SPI占用太高出现闪烁。所以刷新频率不是越高越好10Hz到20Hz是比较合适的范围。7. 我的实操体会与最后几句这个项目前前后后折腾了大概两个月从最开始的点不亮到后来能稳定显示自定义字符中间踩了不少坑。最大的体会是AT7456E这颗芯片本身不复杂但硬件设计和SPI时序调试需要耐心。尤其是PCB布局第一版没经验画出来的板子干扰严重后来重新布局之后画面干净了很多。另一个体会是不要迷信数据手册上的典型电路。数据手册上的电路是理想情况实际项目中电源质量、信号完整性、地线处理都会影响芯片工作。我一般会在数据手册电路的基础上加一些额外的滤波和隔离措施虽然成本增加了几毛钱但稳定性提升很明显。最后分享一个小技巧调试的时候先用一个固定的测试图案写入显示内存确认硬件和SPI通信没问题之后再接入动态数据。这样能把问题范围缩小快速定位是硬件问题还是软件问题。这个习惯帮我省了很多时间希望对你有用。