MADL!AR
Code is cheap, show me the PPT!
首页
分类
Fragment
关于
实现PSSI HOST
分类:
硬件
发布于: 2026-05-01
stm32U5/N6/H7等系列的很多芯片都内置了PSSI(Parallel Synchronous Slave Interface,并行同步从接口),而且协议十分简单,最小包含一条时钟线和8条(或16条)数据线,可选DE和RDY信号,可以视作一个并行的的SPI接口,数据吞吐量非常大。 这是多种工作模式的一种,即发送方和接收方都实施流控: [https://www.st.com.cn/.../rm0456-stm32u5-series-....pdf](https://www.st.com.cn/resource/zh/reference_manual/rm0456-stm32u5-series-armbased-32bit-mcus-stmicroelectronics.pdf) > 双向 PSSI_DE/PSSI_RDY 信号 如果 PSSI_CR 寄存器中的 DEPOL 和 RDYPOL 均置 1 并且 DERDYCFG 置为 111 或 100,则 可以使用单个引脚同时实现数据使能 (PSSI_DE) 和就绪 (PSSI_RDY) 功能。在这种情况下, 与所选复用功能(DERDYCFG=111 时的 PSSI_DE,或 DERDYCFG=100 时的 PSSI_RDY) 相对应的 GPIO 必须配置为漏极开路。另一个器件也必须将线路驱动为漏极开路,并且必须 对该线路应用弱上拉。 双向信号因此得以实现。如果发送器将线路驱动为低电平(以指示数据无效)或接收器将线 路驱动为低电平(以指示未对当前数据进行采样),则两个器件都知道当前周期内未进行数 据传输。  事实上,它和DCMI接口类似,都是用作数据高速通信的接口,但DCMI一般只能支持到14bit,硬件时序更加复杂,且只可以用作输入,在与FPGA、MPU通信时,PSSI还是更具有优势。难受的是,PSSI是从接口,虽然也能作为发送方,但它只能被动接受时钟,无法主动发起。于是探究了几个方法,让普通的MCU可以充当主机的角色,通过某种总线接口,连接到从机的PSSI接口实现双向通信。 ### 几种容易想到的方法 PSSI的主机其核心在于,产生合适的时钟并将数据搬运到IO,由此想到可能通过这些方式实现: 1. 使用FMC的SRAM/LCD模式 2. 使用LTDC接口 3. 使用QSPI或、Octo-SPI/Hexa-SPI接口 4. 使用mdma+TIM方式 于是逐一进行验证: #### 1. FMC的SRAM/LCD模式 (✔ 可行) STM32绝大多数的芯片都有FMC或者FSMC接口,实际上只需用到SRAM模式即可,这里需要查询一下FMC总线在内存空间中的映射关系,针对stm32H7A3: [https://www.st.com.cn/.../rm0455-stm32h7a37b3-and-stm32h7b0....pdf](https://www.st.com.cn/resource/en/reference_manual/rm0455-stm32h7a37b3-and-stm32h7b0-value-line-advanced-armbased-32bit-mcus-stmicroelectronics.pdf)  因此配置FMC ```BANK1 NOR/PSRAM 1```,此时NE1对应的地址空间即为0x60000000这一段。Memory Type选择LCD Interface, Timing参数都选择为最小,在主函数中实现下述逻辑: ``` #define txBuffLen (64*64*2) uint8_t txBuff[txBuffLen]; void main() { ... MX_FMC_Init(); ... for (unsigned int i = 0; i < txBuffLen; i++) { txBuff[i] = 1 << (i%8); } HAL_StatusTypeDef state; while (1) { HAL_Delay(1000); state = HAL_SRAM_Write_8b(&hsram1, (uint32_t *)0x60000001, txBuff, 16); printf("send state: %d\n", state); ... } } ``` 其实是实现类似流水灯的效果,通过逻辑分析仪抓取波形:  通道0是NE1信号,1是NOE信号,这里非常奇怪,每个bytes输出完成之后,NOE还产生了几个没用的时钟,与NE1也对不上。这显然没有办法使用任何一条信号线用作时钟线。调整timing参数和其他参数,也无法解决这个问题。 FMC一共有4种模式,LCD type下支持A和D,但均呈现此现象,本来都准备放弃的时候,发现有人遇到类似的问题:[https://forum.anfulai.cn/forum.php?mod=viewthread&tid=127182](https://forum.anfulai.cn/forum.php?mod=viewthread&tid=127182) ,帖子中硬汉哥指出“这个务必要配置FMC地址空间的MPU/Cache属性为Strongly order或者Device模式”。 于是针对NE1的地址空间,MPU做如下配置: ``` Speculation default mode Settings Speculation default mode : Disabled Cortex Interface Settings CPU ICache : Disabled CPU DCache : Disabled Cortex Memory Protection Unit Control Settings MPU Control Mode : Background Region Privileged accesses only + MPU Enabled during hard fault, NMI and FAULTMASK handlers Cortex Memory Protection Unit Region 0 Settings MPU Region : Enabled MPU Region Base Address : 0x60000000 MPU Region Size : 64MB MPU SubRegion Disable : 0x0 MPU TEX field level : level 0 MPU Access Permission : ALL ACCESS PERMITTED MPU Instruction Access : DISABLE MPU Shareability Permission: DISABLE MPU Cacheable Permission : DISABLE MPU Bufferable Permission : DISABLE ``` 烧录验证,完美实现波形,这里发送端在时钟下降沿准备数据,接收方可以在上升沿采集数据:  其中,通道0为 NWE,可用作时钟;通道1 为NOE,暂时没什么用处;通道2为NE1,可以用作DE信号;通道3为A16即1bit地址。 #### 2. LTDC接口 (✘ 未成功) 按照AI和手册给出的参考,LTDC天然适合做PSSI HOST,因为有像素时钟,配合合适的timing,可以实现“完美”的时序。 在stm32需要注意window、layer等、Sync/Back Porch等设置,尤其layer行缓存跨1K的问题,应该是可以产生类似时钟的,但在实际测试时发现两个硬伤,直接宣告此方案的失败:1. LTDC最小只能配置到16bit,即RGB565,如果从机是8bit总线,则要浪费一半的IO或者RAM。2. 一旦LTDC被configured,其像素时钟就会一直启动,这个控制器就会周而复始的从RAM搬运数据到IO,根本无法终止或者控制启停边界。虽然可以借助DMA2D和软件定义消息边界等措施,解决问题2,但引入了额外的开销,显然不合适。 #### 3. 使用QSPI或、Octo-SPI/Hexa-SPI接口(✘ 未成功) 虽然查看时序图有作为主机的可能性,但实际测试中发现,写入和读取过程十分繁琐,因为此类总线的主要用途为存储器通信,据流强制包含指令(Instruction)、地址(Address)、空周期(Dummy)和数据(Data)等固定阶段,带宽利用率低下。PSSI总线是没有地址和指令的概念,为了让接收方只接受纯数据的部分,要么在软件上做额外的处理(无法兼容DMA),要么在硬件时序中启用DQS或者引入其他的电路生成DE信号,并且大量传输时还要配合MDMA,配置起来十分繁琐。 当然还有一点,并非所有芯片都有OSPI,很多只有QSPI,更别提如果从机是16bit位宽的场景,OSPI更没辙了。权衡之后,还是放弃了这个方案。 #### 4. 使用DMA+TIM方式(✘ 未成功) 这个方式的参考资料来自: * [https://forum.anfulai.cn/forum.php?mod=viewthread&tid=86980](https://forum.anfulai.cn/forum.php?mod=viewthread&tid=86980) * [https://forum.anfulai.cn/forum.php?mod=viewthread&tid=112449](https://forum.anfulai.cn/forum.php?mod=viewthread&tid=112449) 其原理是通过DMAMUX产生请求信号,触发DMA将数据从Memory搬运到IO 如GPIOX->ODR。为了保持稳定的时钟,通常借助定时器产生TIGO信号,用作DMAMUX的输入。但除了数据IO之外,还需要级联一个DMAMUX来生成时钟信号,而且要实现双缓冲的话,也是非常复杂。这个方案在调试半天未果之后,还是选择了放弃,其与硬件太过于耦合,不够灵活。 ### 意外发现:外部时钟 + 双机PSSI对接(✔ 可行) 在做总结的时候,突然发现事情似乎没有很麻烦。仔细翻看PSSI的介绍,虽然它是Slave接口,但其实是支持Transmit/Receive双向通信的,只是它自己无法产生时钟。既然如此,如果将两个设备的PSSI总线对等连接,其中一个配置为发送,另一个为接收,再从外部产生一个时钟施加给双方,岂不就OK了? 于是在stm32N6的板子上验证一番,首先配置一个时钟,频率定为1Mhz,用跳线短接到PSSI_PDCK引脚上。PSSI的参数配置为: | 配置项 | 参数值 | |--------------------|---------------------| | Data Width | 8 Bits | | Control Signals | Neither DE nor RDY | | Clock Polarity | Rising Edge | | Data Enable Polarity | Active Low | | Ready Polarity | Active Low | 在main.c中: ```c void main() { ... HAL_StatusTypeDef state; uint8_t buff[1024]; for (int i = 0; i < 1024; i++) { buff[i] = 1 << (i % 8); } while (1) { state = HAL_PSSI_Transmit(&hpssi, buff, 12, 0xFFFF); if (state != HAL_OK) { ... } } } ``` 烧录后发现不能work,state的值为:```#define HAL_PSSI_ERROR_UNDER_RUN 0x00000002U /*!< FIFO Under-run error */```。猜测是在HAL_PSSI_Transmit在写寄存器的临界时间内,PSSI控制器在时钟的驱动下尝试从FIFO中获取数据,而此时尚未就绪从而导致报错。  解决办法是,在调用HAL_PSSI_Transmit函数之后,再发生时钟,这里为了便于调试,拿出了另一块板子,在按键按下之后,发送16个时钟脉冲,硬件上还是把时钟输出短接到PSSI_PDCK上。 再次尝试,呈现了完美的波形:  需要注意的是,前面Clock Polarity配置为Rising Edge,但从逻辑分析仪的波形来看,第一个上升沿时,数据并未准备就绪,但数据引脚已全部拉低,第二个上升沿时,数据线上才开始准备数据。(实测将极性改为Falling Edge,也是从第二个下降沿开始准备数据)也就是说,如果采用外部时钟驱动,在data就绪前,会有一个额外的时钟。 巧合的是,搜索PSSI的官方示例,有一个例程恰好是DMA双机通信: ```text ##
PSSI_Transmit_Receive_DMA Example Description
This example describes how to perform PSSI data buffer transmission/reception between a slave configured on one board and a master simulated by another board. _________________________ _________________________ | ______________| |______________ | | |PSSI | | PSSI| | | | | | | | | | D0 |_____________________| D0 | | | | . |_____________________| . | | | | . |_____________________| . | | | | . |_____________________| . | | | | D7 | | D7 | | | | Clk |_____________________| Clk _________|__ | | | DE |_____________________| DE | | | | | | | | | | | | | | | | | | | | | TIM_PWM|__| | | | | | | | | |______________| |______________| | | | | | | GND|_____________________|GND | |_STM32_Board Slave ______| |_STM32_Board Master______| ... ``` 这与我的想法不谋而合! 查阅相关代码后发现,官方示例中Master和Slave的配置是一致的: ``` static void MX_PSSI_Init(void) { hpssi.Instance = PSSI; hpssi.Init.DataWidth = HAL_PSSI_32BITS; hpssi.Init.BusWidth = HAL_PSSI_8LINES; hpssi.Init.ControlSignal = HAL_PSSI_DE_ENABLE; hpssi.Init.ClockPolarity = HAL_PSSI_FALLING_EDGE; hpssi.Init.DataEnablePolarity = HAL_PSSI_DEPOL_ACTIVE_LOW; hpssi.Init.ReadyPolarity = HAL_PSSI_RDYPOL_ACTIVE_LOW; if (HAL_PSSI_Init(&hpssi) != HAL_OK) { Error_Handler(); } } ``` 示例中并没有对多出来的这一个时钟做额外的处理,应该是通过DE信号屏蔽了多余的时钟。另外,官方在Master端生成时钟,先接受、后发送,也没有对HAL_PSSI_ERROR_UNDER_RUN做额外处理,猜测DMA的方式可以快速填充FIFO,规避了此错误的发生。另外,一开始我推测发送方和接收方需要设置不同的时钟极性,如发送方上升沿准备数据,接收方下降沿采样,但如上文所述,Master和Slave的配置是保持一致的,所以合理的推测是,PSSI控制器可能会根据接收或者发送,自动适配时钟极性,否则不可能完美配合。手头没有两块同时支持PSSI的板子,同时精力有限,这些问题仅做记录,留待将来硬件齐备时再行验证。