• stm32 硬件spi半双工三线的一些研究心得


    a7105可以使用四线spi,或者3线spi, 但是之前都是使用3线的软件模拟的三线spi的,所以不想改其它代码了,就想可以提高一个spi的读写速度,原来软件方式的读写速度,在48Mhz的03x下面,大约速度是1.6mbs,使用硬件spi之后,最终大约速度为12mbs. 

         为了令它可以稳定工作,这个还是花了不了时间了。主要的挑战是因为是使用3线spi,情况有些特殊,我们需要使用MOSI一条数据线实现主和从的双向模式通信。根据文档,我们使用进行以下设置

    1. SPIx->CR1 的 BIDIMODE 设置为1,表示启用双向模式

    2. 使用SPIX->CR1的BIDIOE 来控制方向,0表示当前为Master读数据,1表示当前为Master写数据

    3. 更换这个方向时需要把SPIx禁用, SPIX->CR1的SPE,来控制。

    这个总体思路是这样的,我实际编写完代码,发现是完全不工作的,网上也比较少这样的使用,有少数几个文章有说这个。

    根据文档,在BIDIMODE 为1,BIDIOE为0, 时钟CLK在SPE马上开启输出,同时它只会在SPE为零时才会停,这个意味着,我们要依赖于Slave的处理速度,当时也依赖而自己准确开关这个CLK信号,否则这个读出来的数据就会错乱。

            这时需要一个比较慢的速度才可以稳定,最开始我并没有注意到这个,在    spiInit.SPI_BaudRatePrescaler 小于 SPI_BaudRatePrescaler_128时,要不读取全是时,要不出现一开始是对的,读着读着,数据就是错的,最后读出来全零的情况。只能不断的降低读写速度。最后才能稳定无误。但是128分频后,明显比软件模拟的方式还慢,这个就没有价值了。这个代码如下:

    1. void spi_init()
    2. {
    3. RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);
    4. GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
    5. GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    6. GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
    7. GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
    8. GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13 | GPIO_Pin_15;
    9. GPIO_Init(GPIOB, &GPIO_InitStructure);
    10. GPIO_PinAFConfig(GPIOB, GPIO_PinSource15, GPIO_AF_0);
    11. GPIO_PinAFConfig(GPIOB, GPIO_PinSource13, GPIO_AF_0);
    12. SPI_I2S_DeInit(SPI1);
    13. spiInit.SPI_Direction = SPI_Direction_1Line_Tx;
    14. spiInit.SPI_Mode = SPI_Mode_Master;
    15. spiInit.SPI_DataSize = SPI_DataSize_8b;
    16. spiInit.SPI_CPOL = SPI_CPOL_Low;
    17. spiInit.SPI_CPHA = SPI_CPHA_1Edge;
    18. spiInit.SPI_NSS = SPI_NSS_Soft;
    19. spiInit.SPI_BaudRatePrescaler= SPI_BaudRatePrescaler_128;
    20. spiInit.SPI_FirstBit = SPI_FirstBit_MSB;
    21. spiInit.SPI_CRCPolynomial = 7;
    22. SPI_Init(SPI1, &spiInit);
    23. SPI_SSOutputCmd(SPI1,DISABLE);
    24. //NSS
    25. GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
    26. GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    27. GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
    28. GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_DOWN;
    29. GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;
    30. GPIO_Init(GPIOA, &GPIO_InitStructure);
    31. SPI_TIModeCmd(SPI1,DISABLE);
    32. SPI_NSSPulseModeCmd(SPI1,DISABLE);
    33. SPI_Cmd(SPI1,ENABLE);
    34. }
    35. void Rf_Spi_Write_Byte(uint8_t dat)
    36. {
    37. if(!g_bisTx)
    38. {
    39. SPI1->CR1 &= (uint16_t)~((uint16_t)SPI_CR1_SPE);//DISABLE SPI
    40. SPI1->CR1 |= SPI_Direction_Tx;
    41. g_bisTx=1;
    42. }
    43. *(uint8_t*)&SPI1->DR = dat;
    44. SPI1->CR1 |= SPI_CR1_SPE; //ENABLE SPI
    45. // while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_BSY) != RESET){}
    46. //SPI_SendData8(SPI1,dat);
    47. while(!(SPI1->SR & SPI_I2S_FLAG_TXE)){}
    48. while (SPI1->SR & SPI_I2S_FLAG_BSY){}
    49. SPI1->CR1 &= (uint16_t)~((uint16_t)SPI_CR1_SPE);
    50. }
    51. uint8_t Rf_Spi_Read_Byte(void)
    52. {
    53. #if USE_HARDWARE_SPI
    54. uint8_t dat =0;
    55. if(g_bisTx)
    56. {
    57. SPI1->CR1 &= (uint16_t)~((uint16_t)SPI_CR1_SPE);//DISABLE SPI
    58. SPI1->CR1 &= SPI_Direction_Rx;
    59. /* Clear FRXTH bit */
    60. SPI1->CR2 &= (uint16_t)~((uint16_t)SPI_CR2_FRXTH);
    61. /* Set new FRXTH bit value */
    62. SPI1->CR2 |= SPI_RxFIFOThreshold_QF; //SET IT 8 BIT per READ, THAT is QUETER OF 32BIT
    63. }
    64. SPI1->CR1 |= SPI_CR1_SPE; //ENABLE SPI
    65. g_bisTx=0;
    66. while (!(SPI1->SR & SPI_I2S_FLAG_RXNE) ) ; // wait data received
    67. SPI1->CR1 &= (uint16_t)~((uint16_t)SPI_CR1_SPE);//DISABLE SPI
    68. dat = *(uint8_t*)&SPI1->DR;
    69. return dat;
    70. }

            这个根据工作原理分析了一下,为什么在这样,主要是因为我们的读的时候,要是这个速度高,我们的mcu还没有去禁用SPI停时钟这个太慢了,上面代码已经在判断到RXNE时马上disable SPI了,但是明显,这个还是不够快。怎么办? 如何能提高速度? 这个折腾了很久,终于发现一个不合理的操作可以直接把速度成 DIV4,即12MB。这个就是先禁用SPI,  再判断RXNE...发现竟然是可以很稳定的工作。

    1. SPI1->CR1 |= SPI_CR1_SPE; //ENABLE SPI
    2. g_bisTx=0;
    3. SPI1->CR1 &= (uint16_t)~((uint16_t)SPI_CR1_SPE);//DISABLE SPI
    4. while (!(SPI1->SR & SPI_I2S_FLAG_RXNE) ) ; // wait data received
    5. dat = *(uint8_t*)&SPI1->DR;

    这个确实也是神奇,特别写这个文章记录一下。

    归纳的原理应该是SPI ENABLE就开始读了,这ENABLE和DISALBE的时间刚好就是12MB的速度,多一条指令都不行了。

    同时也尝试了使用DMA去加速,但是同样因为这个时钟刹车停止问题,使用DMA时实际测试需要更慢才能稳定传输,也要去到SPI_BaudRatePrescaler_128。究其原因应该也是DMA停止时操作太多,无法简化,导致时钟无法及时停止,引起数据错误。

    以下是三线SPI的DMA传输的代码,供参考。

    1. void Rfchip_Spi_Init(void)
    2. {
    3. GPIO_InitTypeDef GPIO_InitStructure;
    4. DMA_InitTypeDef DMA_InitStructure={0};
    5. RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);
    6. GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
    7. GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    8. GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
    9. GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
    10. GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13 | GPIO_Pin_15;
    11. GPIO_Init(GPIOB, &GPIO_InitStructure);
    12. GPIO_PinAFConfig(GPIOB, GPIO_PinSource15, GPIO_AF_0);
    13. GPIO_PinAFConfig(GPIOB, GPIO_PinSource13, GPIO_AF_0);
    14. SPI_I2S_DeInit(SPI1);
    15. spiInit.SPI_Direction = SPI_Direction_1Line_Tx;
    16. spiInit.SPI_Mode = SPI_Mode_Master;
    17. spiInit.SPI_DataSize = SPI_DataSize_8b;
    18. spiInit.SPI_CPOL = SPI_CPOL_Low;
    19. spiInit.SPI_CPHA = SPI_CPHA_1Edge;
    20. spiInit.SPI_NSS = SPI_NSS_Soft;
    21. spiInit.SPI_BaudRatePrescaler= SPI_BaudRatePrescaler_128;
    22. spiInit.SPI_FirstBit = SPI_FirstBit_MSB;
    23. spiInit.SPI_CRCPolynomial = 7;
    24. SPI_Init(SPI1, &spiInit);
    25. SPI_SSOutputCmd(SPI1,DISABLE);
    26. //NSS
    27. GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
    28. GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    29. GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
    30. GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_DOWN;
    31. GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;
    32. GPIO_Init(GPIOA, &GPIO_InitStructure);
    33. SPI_TIModeCmd(SPI1,DISABLE);
    34. SPI_NSSPulseModeCmd(SPI1,DISABLE);
    35. DMA_DeInit(DMA1_Channel2);
    36. DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&SPI1->DR;
    37. DMA_InitStructure.DMA_MemoryBaseAddr = 0;
    38. DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
    39. DMA_InitStructure.DMA_BufferSize = 0;
    40. DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
    41. DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
    42. DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
    43. DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
    44. DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
    45. DMA_InitStructure.DMA_Priority = DMA_Priority_High;
    46. DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
    47. DMA_Init(DMA1_Channel2, &DMA_InitStructure);
    48. DMA_DeInit(DMA1_Channel3);
    49. DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&SPI1->DR;
    50. DMA_InitStructure.DMA_MemoryBaseAddr = 0;
    51. DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;
    52. DMA_InitStructure.DMA_BufferSize = 0;
    53. DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
    54. DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
    55. DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
    56. DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
    57. DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
    58. DMA_InitStructure.DMA_Priority = DMA_Priority_High;
    59. DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
    60. DMA_Init(DMA1_Channel3, &DMA_InitStructure);
    61. SPI_Cmd(SPI1,ENABLE);
    62. //GIO
    63. GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;
    64. GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    65. GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
    66. GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_DOWN;
    67. GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
    68. GPIO_Init(GPIOA, &GPIO_InitStructure);
    69. RFChip_Disable;
    70. }
    71. void Rf_Spi_Write_Bytes(uint8_t *pbuf, uint32_t len)
    72. {
    73. if(!g_bisTx)
    74. {
    75. SPI1->CR1 &= (uint16_t)~((uint16_t)SPI_CR1_SPE);//DISABLE SPI
    76. SPI1->CR1 |= SPI_Direction_Tx;
    77. g_bisTx=1;
    78. }
    79. DMA_Cmd(DMA1_Channel3,DISABLE);
    80. DMA1_Channel3->CMAR = (uint32_t)pbuf;
    81. DMA1_Channel3->CNDTR = len;
    82. DMA_Cmd(DMA1_Channel3,ENABLE);
    83. SPI_I2S_DMACmd(SPI1,SPI_I2S_DMAReq_Tx,ENABLE);
    84. SPI1->CR1 |= SPI_CR1_SPE; //ENABLE SPI
    85. while(DMA_GetFlagStatus(DMA1_FLAG_TC3)==RESET);
    86. DMA_ClearFlag(DMA1_FLAG_TC3);
    87. SPI_I2S_DMACmd(SPI1,SPI_I2S_DMAReq_Tx,DISABLE);
    88. while (SPI1->SR & SPI_I2S_FLAG_BSY){}
    89. SPI1->CR1 &= (uint16_t)~((uint16_t)SPI_CR1_SPE);
    90. DMA_Cmd(DMA1_Channel3,DISABLE);
    91. }
    92. void Rf_Spi_Read_Bytes(uint8_t *pbuf, uint32_t len)
    93. {
    94. uint16_t Dis_SPI_CR1_SPE=(uint16_t)~((uint16_t)SPI_CR1_SPE);
    95. uint16_t dMA_DISABLE = (uint16_t)(~DMA_CCR_EN);
    96. if(g_bisTx)
    97. {
    98. SPI1->CR1 &= (uint16_t)~((uint16_t)SPI_CR1_SPE);//DISABLE SPI
    99. SPI1->CR1 &= SPI_Direction_Rx;
    100. /* Clear FRXTH bit */
    101. SPI1->CR2 &= (uint16_t)~((uint16_t)SPI_CR2_FRXTH);
    102. /* Set new FRXTH bit value */
    103. SPI1->CR2 |= SPI_RxFIFOThreshold_QF; //SET IT 8 BIT per READ, THAT is QUETER OF 32BIT
    104. }
    105. g_bisTx=0;
    106. DMA1_Channel2->CCR &= dMA_DISABLE; //DISABLE DMA CHANNEL2
    107. DMA1_Channel2->CMAR = (uint32_t)pbuf;
    108. DMA1_Channel2->CNDTR = len;
    109. SPI1->CR2 |= SPI_I2S_DMAReq_Rx; //DMA REQUEST
    110. SPI1->CR1 |= SPI_CR1_SPE; //ENABLE SPI
    111. DMA1_Channel2->CCR |= DMA_CCR_EN; //ENABLE DMA CHANNEL2
    112. while(!(DMA1->ISR & DMA1_FLAG_TC2));
    113. DMA1_Channel2->CCR &=dMA_DISABLE; //DISABLE DMA CHANNEL2
    114. SPI1->CR1 &= Dis_SPI_CR1_SPE;//DISABLE SPI
    115. SPI1->CR2 &= (uint16_t)~SPI_I2S_DMAReq_Rx;
    116. DMA_ClearFlag(DMA1_FLAG_TC2);
    117. }

  • 相关阅读:
    如何使用Docker部署Nacos服务?Nacos Docker 快速部署指南: 一站式部署与配置教程
    [国产MCU]-W801开发实例-TCP客户端与服务器实现
    通过一款插件动态观察ES分片如何分布
    亚马逊对IP的要求是什么?
    LVS集群 ----------------(直接路由 )DR模式部署 (二)
    【空间&单细胞组学】第1期:单细胞结合空间转录组研究PDAC肿瘤微环境
    前端工作小结78-点击按钮报错
    Spring Boot注册Web组件
    【附源码】计算机毕业设计SSM石家庄铁道大学影视资料管理系统
    java培训技术数据绑定流程原理
  • 原文地址:https://blog.csdn.net/aerror/article/details/126694561