记录一个关于GD32H759的 SPI+DMA 循环接收的BUG

[复制链接]
1203|1
lewlew 发表于 2025-9-24 12:09 | 显示全部楼层 |阅读模式
, , ,
UP最近在用 GD32H759IMK6 进行开发,有个功能需要用到 SPI 的 DMA 循环接收功能,

经测试发现该芯片的 SPI 遇到 DMA 循环模式就会出问题。

具体问题现象是:
  当SPI发送了奇数字节(例如1字节)后,SPI 对应的循环 DMA 一旦产生回绕,就会在 DMA Buffer 首部多一个垃圾数据(内容一般为 0)
  此时统计发送和接收的字节数量,会发现接收的数量比发送多 1 个字节。

我已使用官方 Example 代码加以修改复现了该 BUG,并提交给 GD32 FAE 确认此事。(复现代码见附件)

由于手头没有其他型号的 GD32 MCU,目前还不清楚该 BUG 在其他型号上是否存在,坛友们如果有时间可以试一下。

SPI_BUG_3.zip (2.88 KB, 下载次数: 1)



  1. #include "gd32h7xx.h"
  2. #include "gd32h7xx_dma.h"
  3. #include "gd32h7xx_gpio.h"
  4. #include "gd32h7xx_spi.h"
  5. //#include "gd32h759i_eval.h"
  6. //#include "Utils.h" // TODO
  7. #define DbgPrintf printf
  8. #define Assert(x) while(1);

  9. #include <stdio.h>
  10. #include <stdlib.h>

  11. // TODO: #pragma clang optimize off

  12. #define  ARRAYSIZE         32
  13. #define SET_SPI0_NSS_HIGH          gpio_bit_set(GPIOA,GPIO_PIN_4);
  14. #define SET_SPI0_NSS_LOW           gpio_bit_reset(GPIOA,GPIO_PIN_4);

  15. static volatile uint8_t spi0_send_array[ARRAYSIZE] = {};
  16. static volatile uint8_t spi0_receive_array[ARRAYSIZE] = {};

  17. static uint8_t g_TxCount;
  18. static uint8_t g_RxCount;

  19. ErrStatus memory_compare(uint8_t *src, uint8_t *dst, uint8_t length);
  20. void cache_enable(void);
  21. void rcu_config(void);
  22. void gpio_config(void);
  23. void dma_config(void);
  24. void spi_config(void);

  25. // 计算循环 DMA 的增量
  26. static uint32_t GetCircularIncrease(uint32_t last, uint32_t curr)
  27. {
  28.         if (last <= curr)
  29.         {
  30.                 // 增量区间数量
  31.                 return curr - last;
  32.         }
  33.         else
  34.         {
  35.                 // 回绕情况, 尾部数量 + 首部数量
  36.                 return (ARRAYSIZE - last) + curr;
  37.         }
  38. }

  39. static uint32_t SPIDMA_Tx()
  40. {
  41.         uint32_t periph = DMA0;
  42.         dma_channel_enum channel = DMA_CH0;
  43.         uint8_t *memAddr = spi0_send_array;
  44.         // TODO: 发送 1 字节或者 2 字节
  45.         uint32_t count = (rand() & 1) + 1;
  46.         uint32_t i;

  47.         // TODO: 生成用于测试的顺序数据
  48.         for (i = 0; i < count; ++i)
  49.         {
  50.                 // TODO: 使用非 0 的固定值
  51.                 memAddr[i] = 0x66;
  52.         }
  53.         // TODO: DCache commit
  54.         //SCB_CleanDCache();

  55.         // TODO: 内存同步
  56.         __DSB();

  57.         dma_channel_disable(periph, channel);
  58.         dma_memory_address_config(periph, channel, DMA_MEMORY_0, (uint32_t)memAddr);
  59.         dma_transfer_number_config(periph, channel, count);
  60.         dma_channel_enable(periph, channel);

  61.         // SPI 开始传输
  62.         spi_master_transfer_start(SPI0, SPI_TRANS_START);

  63.         return count;
  64. }

  65. /*!
  66.     \brief      main function
  67.     \param[in]  none
  68.     \param[out] none
  69.     \retval     none
  70. */
  71. int main(void)
  72. {
  73.         uint32_t txTotal = 0;
  74.         uint32_t rxTotal = 0;
  75.         uint32_t rxLast = 0;
  76.         uint32_t i, j;

  77.         SCB_EnableICache();
  78.         // TODO: 关闭 DCache, 排除缓存一致性问题
  79.         SCB_DisableDCache();

  80.         /* peripheral clock enable */
  81.         rcu_config();
  82.         /* configure GPIO */
  83.         gpio_config();
  84.         /* configure SPI */
  85.         spi_config();
  86.         /* configure DMA */
  87.         dma_config();

  88.         /* configure SPI current data number  */
  89.         // TODO: SPI 使用无限循环发送模式
  90.         spi_current_data_num_config(SPI0, 0);

  91.         SET_SPI0_NSS_HIGH

  92.         /* SPI enable */
  93.         spi_enable(SPI0);

  94.         SET_SPI0_NSS_LOW

  95.         /* SPI DMA enable */
  96.         spi_dma_enable(SPI0, SPI_DMA_RECEIVE);
  97.         spi_dma_enable(SPI0, SPI_DMA_TRANSMIT);

  98.         // TODO: 先发送 1 个字节
  99.         txTotal += SPIDMA_Tx();

  100.         // 循环收发
  101.         for (j = 0; j < ARRAYSIZE * 1000; ++j)
  102.         {
  103.                 uint32_t rxCurr;
  104.                 uint32_t rxInc;

  105.                 // TODO: 等待发送完毕
  106.                 if (dma_flag_get(DMA0, DMA_CH0, DMA_FLAG_FTF))
  107.                 {
  108.                         dma_flag_clear(DMA0, DMA_CH0, DMA_FLAG_FTF);

  109.                         // 发送完毕后继续发送, 直到发送一定次数后停止发送
  110.                         if (j < 9999)
  111.                         {
  112.                                 // 继续发送 1 个字节
  113.                                 txTotal += SPIDMA_Tx();
  114.                         }
  115.                 }

  116.                 // 计算循环 DMA 的增量, 累计接收的字节数
  117.                 rxCurr = ARRAYSIZE - dma_transfer_number_get(DMA0, DMA_CH1);

  118.                 // 在接收 DMA 半满/全满时, 检查第一个字节是不是有问题
  119.                 if (dma_flag_get(DMA0, DMA_CH1, DMA_FLAG_HTF) ||
  120.                         dma_flag_get(DMA0, DMA_CH1, DMA_FLAG_FTF))
  121.                 {
  122.                         dma_flag_clear(DMA0, DMA_CH1, DMA_FLAG_HTF);
  123.                         dma_flag_clear(DMA0, DMA_CH1, DMA_FLAG_FTF);

  124.                         // TODO: 检测到数据有问题时打印
  125.                         //if (spi0_receive_array[0] == 0)
  126.                         {
  127.                                 DbgPrintf("!! Got, %02X\n", spi0_receive_array[0]);
  128.                         }
  129.                 }

  130.                 rxInc = GetCircularIncrease(rxLast, rxCurr);
  131.                 rxLast = rxCurr;

  132.                 rxTotal += rxInc;

  133.                 //DbgPrintf("tx=%u, rx=%u, diff=%d\n", txTotal, rxTotal, (int)(txTotal - rxTotal));

  134.                 // TODO: 检测到 BUG, 退出循环
  135.                 if ((int)(txTotal - rxTotal) < 0)
  136.                 {
  137.                         DbgPrintf("tx=%u, rx=%u, diff=%d\n", txTotal, rxTotal, (int)(txTotal - rxTotal));
  138.                         DbgPrintf("!!! ERR: Receive larger than Send!\n");
  139.                         break;
  140.                 }
  141.         }

  142.         DbgPrintf("Done\n");

  143.         /* wait DMA transmit complete */
  144.         while (dma_flag_get(DMA0, DMA_CH0, DMA_FLAG_FTF) == RESET);
  145.         while (dma_flag_get(DMA0, DMA_CH1, DMA_FLAG_FTF) == RESET);

  146.         /* Clear DMA Transfer Complete Flags */
  147.         dma_flag_clear(DMA0, DMA_CH0, DMA_FLAG_FTF);
  148.         dma_flag_clear(DMA0, DMA_CH1, DMA_FLAG_FTF);

  149.         SET_SPI0_NSS_HIGH

  150.         /* SPI disable */
  151.         spi_disable(SPI0);

  152.         while (1);
  153. }

  154. /*!
  155.     \brief      configure different peripheral clocks
  156.     \param[in]  none
  157.     \param[out] none
  158.     \retval     none
  159. */
  160. void rcu_config(void)
  161. {
  162.         /* enable the peripherals clock */
  163.         rcu_periph_clock_enable(RCU_GPIOA);
  164.         rcu_periph_clock_enable(RCU_GPIOB);
  165.         rcu_periph_clock_enable(RCU_GPIOG);
  166.         rcu_periph_clock_enable(RCU_SPI0);
  167.         rcu_periph_clock_enable(RCU_DMA0);
  168.         rcu_periph_clock_enable(RCU_DMAMUX);
  169.         rcu_spi_clock_config(IDX_SPI0, RCU_SPISRC_PLL0Q);
  170. }

  171. /*!
  172.     \brief      configure the GPIO peripheral
  173.     \param[in]  none
  174.     \param[out] none
  175.     \retval     none
  176. */
  177. void gpio_config(void)
  178. {
  179.         /* connect port to SPI0_NSS->PA4
  180.                            SPI0_SCK->PA5
  181.                            SPI0_MISO->PA6
  182.                            SPI0_MOSI->PA7 */
  183.         gpio_mode_set(GPIOA, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO_PIN_4);
  184.         gpio_output_options_set(GPIOA, GPIO_OTYPE_PP, GPIO_OSPEED_60MHZ, GPIO_PIN_4);

  185.         gpio_af_set(GPIOA, GPIO_AF_5, GPIO_PIN_5 | GPIO_PIN_6 | GPIO_PIN_7);

  186.         gpio_mode_set(GPIOA, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_5 | GPIO_PIN_6 | GPIO_PIN_7);
  187.         gpio_output_options_set(GPIOA, GPIO_OTYPE_PP, GPIO_OSPEED_60MHZ, GPIO_PIN_5 | GPIO_PIN_6 | GPIO_PIN_7);
  188. }

  189. /*!
  190.     \brief      configure the DMA peripheral
  191.     \param[in]  none
  192.     \param[out] none
  193.     \retval     none
  194. */
  195. void dma_config(void)
  196. {
  197.         dma_single_data_parameter_struct dma_init_struct;
  198.         /* deinitialize DMA registers of a channel */
  199.         dma_deinit(DMA0, DMA_CH0);
  200.         dma_deinit(DMA0, DMA_CH1);
  201.         dma_single_data_para_struct_init(&dma_init_struct);

  202.         /* SPI0 transmit DMA config: DMA_CH0 */
  203.         dma_init_struct.request = DMA_REQUEST_SPI0_TX;
  204.         dma_init_struct.direction = DMA_MEMORY_TO_PERIPH;
  205.         dma_init_struct.memory0_addr = (uint32_t)spi0_send_array;
  206.         dma_init_struct.memory_inc = DMA_MEMORY_INCREASE_ENABLE;
  207.         dma_init_struct.periph_memory_width = DMA_PERIPH_WIDTH_8BIT;
  208.         dma_init_struct.number = ARRAYSIZE;
  209.         dma_init_struct.periph_addr = (uint32_t)&SPI_TDATA(SPI0);
  210.         dma_init_struct.periph_inc = DMA_PERIPH_INCREASE_DISABLE;
  211.         dma_init_struct.priority = DMA_PRIORITY_HIGH;
  212.         dma_init_struct.circular_mode = DMA_CIRCULAR_MODE_DISABLE;
  213.         dma_single_data_mode_init(DMA0, DMA_CH0, &dma_init_struct);

  214.         /* SPI0 receive DMA config: DMA_CH1 */
  215.         dma_init_struct.request = DMA_REQUEST_SPI0_RX;
  216.         dma_init_struct.direction = DMA_PERIPH_TO_MEMORY;
  217.         dma_init_struct.memory0_addr = (uint32_t)spi0_receive_array;
  218.         dma_init_struct.periph_addr = (uint32_t)&SPI_RDATA(SPI0);
  219.         dma_init_struct.priority = DMA_PRIORITY_ULTRA_HIGH;
  220.         dma_init_struct.circular_mode = DMA_CIRCULAR_MODE_ENABLE; // TODO: 循环接收
  221.         dma_single_data_mode_init(DMA0, DMA_CH1, &dma_init_struct);

  222.         /* enable DMA channel */
  223.         //dma_channel_enable(DMA0, DMA_CH0);
  224.         dma_channel_enable(DMA0, DMA_CH1);
  225. }

  226. /*!
  227.     \brief      configure the SPI peripheral
  228.     \param[in]  none
  229.     \param[out] none
  230.     \retval     none
  231. */
  232. void spi_config(void)
  233. {
  234.         spi_parameter_struct spi_init_struct;
  235.         /* deinitialize SPI and the parameters */
  236.         spi_i2s_deinit(SPI0);
  237.         spi_struct_para_init(&spi_init_struct);

  238.         /* SPI0 parameter configuration */
  239.         spi_init_struct.trans_mode = SPI_TRANSMODE_FULLDUPLEX;
  240.         spi_init_struct.device_mode = SPI_MASTER;
  241.         spi_init_struct.data_size = SPI_DATASIZE_8BIT;
  242.         spi_init_struct.clock_polarity_phase = SPI_CK_PL_LOW_PH_1EDGE;
  243.         spi_init_struct.nss = SPI_NSS_SOFT;
  244.         spi_init_struct.prescale = SPI_PSC_256;
  245.         spi_init_struct.endian = SPI_ENDIAN_MSB;
  246.         spi_init(SPI0, &spi_init_struct);

  247.         /* enable SPI byte access */
  248.         spi_byte_access_enable(SPI0);

  249.         /* enable SPI NSS output */
  250.         spi_nss_output_enable(SPI0);

  251.         //spi_fifo_threshold_level_set(SPI0, SPI_FIFO_TH_02DATA);
  252. }


 楼主| lewlew 发表于 2025-9-26 10:50 | 显示全部楼层
本帖最后由 lewlew 于 2025-9-26 10:57 编辑

这个 BUG 已经通过一种非常诡异的方式解决了。

解决方式如下:
1、SPI 必须使用非零的 FIFOLVL 配置,例如:
  1. spi_fifo_threshold_level_set(periph, SPI_FIFO_TH_02DATA);

2、此时 DMA 循环接收将不会出现0数据的情况,但是接收的数据里,最后一字节将被缓存在 SPI RxFIFO 里;

3、在发送数量-接收数量==1的时候,使用 spi_i2s_rxfifo_plevel_get 判断 RxFIFO 是否仅剩最后一字节数据,如果是,则使用spi_i2s_data_receive 手动取回。此后的 DMA 接收将跳过这一字节数据,所以需要控制好手动取回的时机,否则数据顺序将出问题。

4、由于是“半自动”控制 DMA 的接收,可能会对数据吞吐量造成影响。但根据实际测试发现 SPI 时钟跑上 80MHz 没有问题。

至此便解决了SPI+循环DMA出现0数据的情况,但需要特殊处理缓存在 RxFIFO 里的最后一字节数据。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

5

主题

25

帖子

0

粉丝
快速回复 在线客服 返回列表 返回顶部