蓝牙技术
直播中

刘艳

8年用户 2502经验值
私信 关注
[问答]

使用BLE(CYW20829)作为SPI slave和MCU(TC387)作为SPI master,调试SPI通信时遇到的问题求解

我们这边使用BLE(CYW20829)作为SPI slave和MCU(TC387)作为SPI master,调试SPI通信时,遇到两个问题: 1.
1.slave接收来自master的数据,rx中断不触发。
SPI 初始化如下: SPI 初始化如下



代码段参考:https://infineon.github.io/psoc6hal/html/group__group__ hal__spi .html文档中的Snippet4:SPI 事件中断
代码修改基于例程:https://github.com/Infineon/mtb-example-hal-spi-slave#readme

尝试:调试过程中发现,slave 使用 cyhal_spi_slave_write时,rx中断正常触发,看代码该函数调用了 cyhal_spi_transfer_async。
问题:只有slave一直调用cyhal_spi_transfer_async进行读/写操作,rx中断才正常触发?
解决方案:有没有配置可以让master写入数据时,slave就触发SPI rx中断?

2.slave使用函数cyhal_spi_slave_write向master发送数据时,没有等待master拉低片选取并生成时钟信号,导致接收数据异常。

尝试:使用Cy_GPIO_读取CS引脚,直到该引脚被拉低。
问题:容易死循环,读取引脚数据一直为高电平。
解决方案 1:能否帮忙调试出一个该方案可行的代码?
解决方案2:是否有其他slave write函数,是等待master拉低片选并生成时钟信号,才发送数据?

希望能给出上述问题的解决方案。

回帖(1)

carey123

2025-7-7 17:17:02

你遇到的两个SPI通信问题非常典型,与SPI协议的全双工本质以及Cypress HAL库对Slave角色操作方式的特定要求密切相关。以下是对每个问题的详细分析和解决方案:




核心问题根源:



  • SPI是全双工协议: 当Master产生SCLK信号进行传输时,Master必须同时写数据到MOSI线并从MISO线读数据,Slave必须同时读MOSI线并写数据到MISO线。无法只进行单向传输。

  • Cypress HAL Slave的启动要求: cyhal_spi_transfer_async(及其调用的底层API)需要Slave提前准备好即将要发送的数据(或数据指针)。只有当Slave准备好一个有效的“Tx Buffer”时,它的内部状态才会准备好接收Master发起的传输,包括接收数据的处理(触发RX中断)。




问题1:Slave接收中断不触发




  • 原因分析: 你之前的尝试(例如在main中只做接收相关操作)使得Slave没有准备好要发送的数据。因此:



    1. 当Master启动SPI传输时,Slave的硬件/软件状态可能未被正确激活。

    2. 内部逻辑认为“没有传输需要处理”,因此不会触发接收中断。

    3. 同时,因为Slave没有提供数据,Master可能在MISO线上读到全1(0xFF,取决于上拉电阻)。




  • 为什么cyhal_spi_slave_write()能触发中断? 这个函数同时初始化了发送!它内部调用cyhal_spi_transfer_async时,会传入有效的发送数据(或缓冲指针)。这使得Slave准备好处理双向传输:当Master发SCLK时,Slave既能接收(读MOSI)也能发送(写MISO),自然能触发RX中断(告诉你收到了Master的数据)TX中断(告诉你发送给Master的数据已完成)。即使你的目的是接收,API要求Slave必须提供一个有效的发送缓冲(即使里面是虚拟数据)。




  • 解决方案: Slave必须启动一个双向异步传输(即使只想接收)


    #define BUFFER_SIZE 256 // 或者你需要的最大传输大小

    cy_rslt_t rslt;
    uint8_t rx_buf[BUFFER_SIZE];        // 接收缓冲区
    uint8_t tx_buf_dummy[BUFFER_SIZE];  // 虚拟发送缓冲区 - 核心!

    // 在SPI Slave初始化之后,启动第一次异步传输
    // 这里我们只想接收,所以我们提供一个 "dummy" 发送缓冲区
    memset(tx_buf_dummy, 0x00, sizeof(tx_buf_dummy)); // 可选,但清晰,通常设为0x00

    rslt = cyhal_spi_transfer_async(&spi_slave, tx_buf_dummy, BUFFER_SIZE, rx_buf, BUFFER_SIZE, spi_event_handler);
    if (rslt != CY_RSLT_SUCCESS) {
        printf("启动异步传输失败: 0x%08Xn", rslt);
        CY_ASSERT(0);
    }



    • 关键点:



      • tx_buf_dummy: 即使你没有实际数据要发送给Master,也必须提供一个有效的缓冲区给cyhal_spi_transfer_asynctx参数。这就是你的“虚拟”发送缓冲区。

      • rx_buf: 这是你用来存放从Master接收到的数据的缓冲区。

      • spi_event_handler: 这是传输事件的回调函数(中断上下文),接收数据完成的中断会在这里触发




    • 中断处理函数 (spi_event_handler) 示例:


      void spi_event_handler(void *handler_arg, cyhal_spi_event_t event) {
          (void) handler_arg; // 防止未使用参数警告
          static bool transfer_complete = false;

          if (event & CYHAL_SPI_IRQ_DATA) {         // 数据收发传输完成
              transfer_complete = true;
          }
          if (event & CYHAL_SPI_IRQ_ERROR) {         // 传输错误
              printf("SPI传输错误!n");
              // 处理错误...
          }

          // 当传输完成事件发生
          if (transfer_complete) {
              transfer_complete = false; // 重置标志

              // 1. 处理接收到的数据!
              // 此时 rx_buf 中的前 '传输长度' 个字节是Master发来的数据
              printf("收到%d字节数据。内容:n", BUFFER_SIZE); // 通常长度由协议决定
              for (int i = 0; i < BUFFER_SIZE; i++) {
                  printf("%02X ", rx_buf[i]);
              }
              printf("n");

              // 2. 准备下一次传输 - 至关重要!
              // 再次启动异步传输 (包括虚拟发送缓冲区)
              memset(tx_buf_dummy, 0x00, BUFFER_SIZE); // 可选,清理虚拟发送缓冲
              rslt = cyhal_spi_transfer_async(&spi_slave, tx_buf_dummy, BUFFER_SIZE, rx_buf, BUFFER_SIZE, spi_event_handler);
              if (rslt != CY_RSLT_SUCCESS) {
                  printf("重启异步传输失败: 0x%08Xn", rslt);
                  // 处理错误,可能需要复位通讯
              }
          }
      }


      • 处理接收到的数据 (rx_buf) 是在传输完成事件 (CYHAL_SPI_IRQ_DATA) 被处理时进行的。

      • 必须重启传输 (cyhal_spi_transfer_async) 在同一个spi_event_handler内部处理完数据后!这样才能让Slave准备好接收Master的下一次传输请求。否则Slave会再次陷入“无发送准备”状态,导致下一次Master的传输无法被正确处理,RX中断也不会触发。








问题2:关于数据未发送却持续收到0xFF




  • 原因分析:



    1. 当Master启动传输但Slave未提供有效发送数据时:

      • Slave的MISO线默认为高阻态(Hi-Z)。

      • 如果MISO线上有上拉电阻(很多硬件设计默认会加),该线会被上拉到逻辑高电平(1)


    2. Master在读取数据时(通过MISO线),会持续地读到0xFF (8位情况下),这正是二进制的全1。

    3. 即使Master实际在发送数据给Slave,如果Slave没有准备好要发送的数据,Master的接收寄存器会被0xFF填充。




  • 解决方案:



    • 使用 虚拟发送缓冲区 (tx_buf_dummy),如问题1的解决方案所示。

    • 默认将这个虚拟缓冲区填充为某个固定值(如全0 0x00)。这样:

      • Slave永远有“数据”可发送。

      • 当Master没有特别期望Slave发特定数据,或者Slave需要通过另一条通信通道(例如BLE本身、GPIO中断)向Master报告“有数据要发送”时,Master在常规轮询/查询传输中收到的就是 0x00 (0x00, 0x00, ...)。

      • 如果Master读到全0 (0x00) 而不是之前的全1 (0xFF),这是虚拟缓冲机制开始正常工作的明确标志! 这意味着Slave现在成功提供了数据(虽然是虚拟的)。


    • 当你需要Slave向Master发送真实数据时:

      1. 在Slave端准备好要发送的真实数据(放入 next_tx_buf)。

      2. spi_event_handler处理完当前接收的数据后,将下一次传输的发送缓冲区指针指向 next_tx_buf 而不是 tx_buf_dummy

      3. 调用cyhal_spi_transfer_async时,将真实的next_tx_buf及其长度作为发送参数传入。







总结与关键步骤



  1. Slave始终启动双向传输:main初始化SPI Slave之后,立即调用 cyhal_spi_transfer_async 启动第一个异步传输。

  2. 使用虚拟发送缓冲 (Dummy Tx): 当Slave没有真正数据要主动发送给Master时,使用一个预填充好固定值(通常是0x00)的缓冲区作为 cyhal_spi_transfer_asynctx 参数。

  3. 在中断处理程序中处理接收数据:spi_event_handler 里检测 CYHAL_SPI_IRQ_DATA 事件,处理 rx_buf 中的数据(Master发来的)。

  4. 在中断处理程序中重启传输: 在处理完接收到的数据后,必须在同一个中断处理函数中立即再次调用 cyhal_spi_transfer_async

  5. 真实数据发送: 当Slave准备好真实数据发送时,将下一个 cyhal_spi_transfer_asynctx 参数指向真实数据缓冲区即可(同时仍需提供有效的 rx_buf)。

  6. 确保模式匹配: 再次确认TC387(Master)和CYW20829(Slave)的SPI配置完全匹配:

    • ssel_pol (CPOL/Clock Polarity)

    • cpha (CPHA/Clock Phase)

    • data_bits (通常8位)

    • oversample (如使用应匹配)

    • 时钟速度(Slave配置需支持Master的速度)

    • bit_order (MSB先还是LSB先)



特别注意: SPI模式通常定义为 (CPOL, CPHA):



  • Mode 0: CPOL=0, CPHA=0

  • Mode 1: CPOL=0, CPHA=1

  • Mode 2: CPOL=1, CPHA=0

  • Mode 3: CPOL=1, CPHA=1


请务必查阅 TC387 SPI Master配置CYW20829 SPI Slave HAL 库文档 (cyhal_spi_cfg_t) 确认双方设置的CPOL/CPHA具体值相同。


通过实施这个 “使用虚拟发送缓冲的持续双向异步传输” 策略,应该能够成功触发Slave的RX中断,并正确处理Master发来的数据,同时解决Master接收端收到0xFF的问题。这种方法符合SPI协议标准和Cypress HAL对Slave操作的要求。

举报

更多回帖

发帖
×
20
完善资料,
赚取积分