12下一页
返回列表 发新帖我要提问本帖赏金: 150.00元(功能说明)

[技术问答] 对比ST和新塘SPI外设,记录一场畅快的SPI开发新体验!

[复制链接]
31684|23
 楼主| 呐咯密密 发表于 2021-8-3 10:07 | 显示全部楼层 |阅读模式
#申请原创# @21小跑堂
废话开篇:
帖子题目纯属吐槽,再次被这个逻辑分析仪拉进坑里,本来无比畅快的SPI体验,就因为这个玩意我把标题都改了,原来准备用该标题的,这里用作副标题不过分吧:
如果不是这个逻辑分析仪,这将是一场畅快的SPI新体验。
先看一下ST和新塘的SPI外设对比
此次对比的均为M0内核主频48M,此处不考虑超频。MCU为ST的STM32F031,新塘的是M031。
STM32F031:
133346107b1ced2cc8.png
新塘M031:
787846107b1ee7f6b2.png
通过对比可以看出,新塘的M031可以理解为在STM32F031基础上进行升级改造,总线时钟新塘M031在主机模式下可以达到24M,此时钟是MCU的PCLK二分频得来。STM32F031最大只支持18M的频率。传输的位长新塘可以在8-32自由配置,STM32F031为4-16位自由配置,可以看得出新塘在高位数传输时更好用,但是低于8位的场合还是ST更好。如果概括起来说,8-32位的广域自由配置还是要更好用。这也是我畅快的原因。
测试这两个芯片的SPI是因为我们的产品在使用SPI通信时发送的数据都是24位,在之前使用STM32F031时只能通过将24位数据拆分为三个8位数据依次发送。此操作容易造成SPI时钟不连续,或者发8位的数据,却产生16和时钟信号,造成数据发送错误或者时间过长。具体解决方案我在GD32E230的SPI帖子里有介绍过,有遇到这个问题的可以跳转查看。GD32E230SPI+DMA
现在转了新塘的平台,8-32位的自由配置位长可就帮了大忙。我的24位数据便可一次性全送出。
代码对比:
在STM32F031中配置SPI为8位字长:
  1. /************************************************
  2. 函数名称 : SPI_GPIO_Configuration
  3. 功    能 : SPI GPIO初始化
  4. 参    数 : 无
  5. 返 回 值 : 无
  6. 作    者 : Mico
  7. *************************************************/
  8. void SPI_GPIO_Configuration(void)
  9. {
  10.   GPIO_InitTypeDef GPIO_InitStructure;
  11.         SPI_InitTypeDef  SPI_InitStructure;        
  12.          
  13.           /* 使能AHB时钟 */
  14.   RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOB, ENABLE);
  15.         RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE);
  16.                   /* 使能APB2时钟 */
  17.   RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);

  18.   /*定义 SPI复用引脚 */
  19.   GPIO_InitStructure.GPIO_Pin = PIN_SPI_SCK | PIN_SPI_MISO | PIN_SPI_MOSI;
  20.   GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;                       //复用模式
  21.   GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;                  //高速输出
  22.   GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;                     //推完输出
  23.   GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;                       //上拉
  24.   GPIO_Init(PORT_SPI_SCK, &GPIO_InitStructure);
  25.         
  26.   GPIO_PinAFConfig(GPIOB, GPIO_PinSource3, GPIO_AF_0);
  27.   GPIO_PinAFConfig(GPIOB, GPIO_PinSource4, GPIO_AF_0);
  28.   GPIO_PinAFConfig(GPIOB, GPIO_PinSource5, GPIO_AF_0);
  29.         
  30.   /* 片选CS */
  31.   GPIO_InitStructure.GPIO_Pin = PIN_SPI_CS;
  32.   GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;                      //输出模式
  33.         GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;                     //推完输出
  34.         GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;                  //高速输出
  35.         GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;                       //上拉
  36.   GPIO_Init(PORT_SPI_CS, &GPIO_InitStructure);
  37.         GPIO_ResetBits(GPIOB, GPIO_Pin_3 | GPIO_Pin_4 | GPIO_Pin_5);//拉低片选
  38.   /* SPI 初始化定义 */
  39.   SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; //SPI设置为双线双向全双工
  40.   SPI_InitStructure.SPI_Mode = SPI_Mode_Master;                      //设置为主 SPI
  41.   SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;                  //SPI发送接收 8 位帧结构
  42.   SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;                        //时钟悬空低
  43.   SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;                       //数据捕获于第二个时钟沿
  44.   SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;                          //软件控制 NSS 信号
  45.   SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_8; //波特率预分频值为8
  46.   SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;                 //数据传输从 MSB 位开始
  47.   SPI_InitStructure.SPI_CRCPolynomial = 8;                           //定义了用于 CRC值计算的多项式
  48.   SPI_Init(SPI1, &SPI_InitStructure);

  49.   SPI_RxFIFOThresholdConfig(SPI1, SPI_RxFIFOThreshold_QF);           //区别于STM32F103
  50.   SPI_Cmd(SPI1, ENABLE);
  51. }
因为SPI是收发一体,所以在需要发送24位数据时SPI使用该函数进行三次收发,代码显得十分臃肿。
  1. /************************************************
  2. 函数名称 : SPI_WriteReadByte
  3. 功    能 : 对SPI写读字节数据
  4. 参    数 : 无
  5. 返 回 值 : 32位角度值
  6. 作    者 : Mico
  7. *************************************************/
  8. /***************************************************
  9. DR寄存器是16位的,如果你直接SPI1->DR = 0x9F ;
  10. 这样的操作是不正确的,你的数据会变成0x009F之后赋值给DR寄存器,
  11. 也就是操作了16位,所以STM32会输出16个时钟脉冲

  12. 我们先找到DR寄存器的地址,再用一个八位的指针指向这个地址,
  13. 现在指向的是DR寄存器的开头,那么指针+1,指针指向了DR寄存器的低八位
  14. 这时候给指针指向的地址赋值0x85,那么这个字节就会放入DR低八位的空间内,
  15. 而不是操作整个16位DR寄存器
  16. ***************************************************/
  17. uint32_t SPI_WriteRead(void)
  18. {
  19.                 uint16_t num1,num2,num3;

  20.                 uint32_t AngelData;
  21.                 GPIO_ResetBits(GPIOA, GPIO_Pin_15);//拉低片选

  22.                 *((uint8_t*)&(SPI1->DR) + 1 ) = 0x50;
  23.                 num1 = SPI1->DR;        
  24.                 while(SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_TXE) == RESET);        
  25.                 while(SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_BSY) == RESET);        
  26.         
  27.                 *((uint8_t*)&(SPI1->DR) + 1 ) = 0x35;
  28.                 num2 = SPI1->DR;
  29.                 while(SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_TXE) == RESET);
  30.                 while(SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_BSY) == RESET);
  31.         
  32.                 *((uint8_t*)&(SPI1->DR) + 1 ) = 0x0F;
  33.                 num3 = SPI1->DR;
  34.                 while(SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_TXE) == RESET);
  35.                 while(SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_BSY) == RESET);
  36.                 GPIO_SetBits(GPIOA, GPIO_Pin_15);//拉高片选
  37.                 AngelData = ((num2&0xFF)<<16 |(num3&0xFF)<<8 | (num1&0xFF));

  38.                 return AngelData ;
  39. }
新塘M031 SPI代码:
因为新塘时钟的初始化是写在一个函数中的,我沿用了这个习惯,初始化系统以及各个外设时钟,这个必不可少。
  1. void SYS_Init(void)
  2. {

  3.     /*---------------------------------------------------------------------------------------------------------*/
  4.     /* Init System Clock                                                                                       */
  5.     /*---------------------------------------------------------------------------------------------------------*/
  6.     /* Unlock protected registers */
  7.     SYS_UnlockReg();

  8.     /* Enable HIRC clock (Internal RC 48MHz) */
  9.     CLK_EnableXtalRC(CLK_PWRCTL_HIRCEN_Msk);

  10.     /* Wait for HIRC clock ready */
  11.     CLK_WaitClockReady(CLK_STATUS_HIRCSTB_Msk);

  12.     /* Select HCLK clock source as HIRC and HCLK source divider as 1 */
  13.     CLK_SetHCLK(CLK_CLKSEL0_HCLKSEL_HIRC, CLK_CLKDIV0_HCLK(1));

  14.     /* Set both PCLK0 and PCLK1 as HCLK */
  15.     CLK->PCLKDIV = CLK_PCLKDIV_APB0DIV_DIV1 | CLK_PCLKDIV_APB1DIV_DIV1;

  16.     /* Select IP clock source */
  17.     /* Select UART0 clock source is HIRC */
  18.     CLK_SetModuleClock(UART0_MODULE, CLK_CLKSEL1_UART0SEL_HIRC, CLK_CLKDIV0_UART0(1));
  19.     /* Select UART1 clock source is HIRC */
  20.     CLK_SetModuleClock(UART1_MODULE, CLK_CLKSEL1_UART1SEL_HIRC, CLK_CLKDIV0_UART1(1));
  21.     /* Select PCLK1 as the clock source of SPI0 */
  22.     CLK_SetModuleClock(SPI0_MODULE, CLK_CLKSEL2_SPI0SEL_PCLK1, MODULE_NoMsk);
  23.         
  24.     /* Enable UART0 peripheral clock */
  25.     CLK_EnableModuleClock(UART0_MODULE);
  26.     /* Enable UART1 peripheral clock */
  27.     CLK_EnableModuleClock(UART1_MODULE);
  28.     /* Enable PDMA module clock */
  29.     CLK_EnableModuleClock(PDMA_MODULE);
  30.     /* Enable SPI0 peripheral clock */
  31.     CLK_EnableModuleClock(SPI0_MODULE);
  32.         
  33.     /* Update System Core Clock */
  34.     /* User can use SystemCoreClockUpdate() to calculate PllClock, SystemCoreClock and CycylesPerUs automatically. */
  35.     SystemCoreClockUpdate();

  36.     /*---------------------------------------------------------------------------------------------------------*/
  37.     /* Init I/O Multi-function                                                                                 */
  38.     /*---------------------------------------------------------------------------------------------------------*/

  39.     /* Set PB multi-function pins for UART0 RXD=PB.12 and TXD=PB.13 */
  40.     SYS->GPB_MFPH = (SYS->GPB_MFPH & ~(SYS_GPB_MFPH_PB12MFP_Msk | SYS_GPB_MFPH_PB13MFP_Msk))    |       \
  41.                     (SYS_GPB_MFPH_PB12MFP_UART0_RXD | SYS_GPB_MFPH_PB13MFP_UART0_TXD);

  42.     /* Set PB multi-function pins for UART1 RXD(PB.2) and TXD(PB.3) */
  43.     SYS->GPB_MFPL = (SYS->GPB_MFPL & ~(SYS_GPB_MFPL_PB2MFP_Msk | SYS_GPB_MFPL_PB3MFP_Msk))    |       \
  44.                     (SYS_GPB_MFPL_PB2MFP_UART1_RXD | SYS_GPB_MFPL_PB3MFP_UART1_TXD);

  45.     /* Setup SPI0 multi-function pins */
  46.     /* PA.3 is SPI0_SS,   PA.2 is SPI0_CLK,
  47.        PA.1 is SPI0_MISO, PA.0 is SPI0_MOSI*/
  48.     SYS->GPA_MFPL = (SYS->GPA_MFPL & ~(SYS_GPA_MFPL_PA3MFP_Msk |
  49.                                        SYS_GPA_MFPL_PA2MFP_Msk |
  50.                                        SYS_GPA_MFPL_PA1MFP_Msk |
  51.                                        SYS_GPA_MFPL_PA0MFP_Msk)) |
  52.                     (SYS_GPA_MFPL_PA3MFP_SPI0_SS |
  53.                      SYS_GPA_MFPL_PA2MFP_SPI0_CLK |
  54.                      SYS_GPA_MFPL_PA1MFP_SPI0_MISO |
  55.                      SYS_GPA_MFPL_PA0MFP_SPI0_MOSI);
  56.                                          
  57.     /* Lock protected registers */
  58.     SYS_LockReg();
  59. }
超级简洁的SPI初始化:
  1. void SPI_Init(void)
  2. {

  3.     SPI_Open(SPI0, SPI_MASTER, SPI_MODE_1, 24, 8000000);
  4.         SPI_EnableAutoSS(SPI0, SPI_SS, SPI_SS_ACTIVE_LOW);
  5. }
此处将SPI0设置为主模式,24位字长和8M的速度。
在主函数中测试:
  1.     while(1)
  2.         {
  3.                 SPI_SET_DATA_WIDTH(SPI0,24);
  4.         
  5.                 /* Write to TX register */
  6.                 SPI_WRITE_TX(SPI0, 0x50650F);
  7.                 /* Check SPI0 busy status */
  8.                 while(SPI_IS_BUSY(SPI0));
  9.                 SPI_SET_DATA_WIDTH(SPI0,32);
  10.                                 /* Write to TX register */
  11.                 SPI_WRITE_TX(SPI0, 0x50650F55);
  12.                 /* Check SPI0 busy status */
  13.                 while(SPI_IS_BUSY(SPI0));
  14.         }
首先将发送的字长设定为24位,然后输出,完成后将输出字长改为32,再次输出。在这里我遇到了麻烦事,输出的数据根本对不上,在使用逻辑分析仪查看数据如下:
48166107b9175784c.png
无论是数据还是时钟均对不上,然后将时钟降低到2M,输出如下:
285346107b9587990c.png
可以很明显的看到数据完全正确,时钟连续性很好,我怀疑是SPI的时钟线配置有问题,可能是主频分频过来的的时钟太低,达不到8M,但是在时钟配置中检查不到任何问题,于是在这里转圈圈,折腾了好久,后来想到去年在调试STM32F031的SPI时也遇到莫名其妙的问题,当时发现是逻辑分析仪的最大速度只有2M,高于2M显示就不准确。
果然,在2M和8M的频率下接上示波器,观察两个波形其实是相同的
2M
7309661089c0296610.png
8M
8907861089c1aa7401.png
问题分析:逻辑分析仪只能抓取分析波形好的逻辑,在高速情况下,加上逻辑分析仪的线较长,可能导致波形变差,导致逻辑分析仪无法抓取全部波形。另外一个就是速度太快,逻辑分析仪的响应速度达不到那么高。
同一个坑,踩了两次,真的有够烦人的,以后一定要记住逻辑分析仪遇到问题考虑一下示波器?


关于SPI的DMA
无论是STM32还是新塘,在仅仅操作SPI的时候可以在MCU允许的情况下进行自由字节数的收发,但是我们很多时候都需要使用DMA来搬运数据,以此减小CPU的工作量。这里我们看一下DMA的描述:
6289461089e31316d6.png
DMA的单次传输字节数并不是很自由,只有8位,16位和32位三种模式。比如我发送24位数据,DMA数据宽度得设置为32位,即使我的SPI定义为24位的宽度,实际DMA还是会产生32个时钟信号,最高位会填充0。所以当使用DMA通信时,还是得将SPI设置为8位宽度,DMA设置为8位,将24位数据分为3个8位送出。

打赏榜单

21小跑堂 打赏了 150.00 元 2021-08-03
理由:恭喜通过原创审核!请多多加油哦!

单片小菜 发表于 2021-8-3 12:15 | 显示全部楼层
逻辑分析仪究竟怎么用呢?一直没有搞懂的事情,需要专家帮忙看一下的。
 楼主| 呐咯密密 发表于 2021-8-3 13:35 | 显示全部楼层
单片小菜 发表于 2021-8-3 12:15
逻辑分析仪究竟怎么用呢?一直没有搞懂的事情,需要专家帮忙看一下的。
...

可以查看数据啊,一般的示波器都不带的,只能自己掐波形去数,太难。低速的场合还是很好用的,而且便宜,我这就是太便宜了,被自己坑了
dirtwillfly 发表于 2021-8-4 08:25 | 显示全部楼层
呐咯密密 发表于 2021-8-3 13:35
可以查看数据啊,一般的示波器都不带的,只能自己掐波形去数,太难。低速的场合还是很好用的,而且便宜, ...

换个高端的逻辑分析仪就好了
徐YH 发表于 2021-8-9 13:27 | 显示全部楼层
徐YH 发表于 2021-8-9 13:28 | 显示全部楼层
换一个逻辑分析仪
两只袜子 发表于 2021-8-9 13:52 来自手机 | 显示全部楼层
换个逻辑分析仪把
两只袜子 发表于 2021-8-9 13:52 来自手机 | 显示全部楼层
换个逻辑分析仪把
datouyuan 发表于 2021-8-10 11:56 | 显示全部楼层
我用的逻辑分析仪20多元的,感觉很好用。就是有点挑电脑,新买的笔记本只有4M,而10几年的老电脑却是全速24M。
kkzz 发表于 2021-8-11 17:57 | 显示全部楼层
这个硬件spi吗   
claretttt 发表于 2021-8-11 17:57 | 显示全部楼层
买的那家的逻辑分析仪器?
jonas222 发表于 2021-8-11 17:57 | 显示全部楼层
新塘SPI最大速度是多少  
macpherson 发表于 2021-8-11 17:58 | 显示全部楼层
一起觉得mcu自带的spi就是坑  
geraldbetty 发表于 2021-8-11 17:58 | 显示全部楼层
这个是触发的有延时吗  
mollylawrence 发表于 2021-8-11 17:58 | 显示全部楼层
M031怎么样   
mattlincoln 发表于 2021-8-11 17:58 | 显示全部楼层
内部集成i2s怎么样?
maudlu 发表于 2021-8-11 17:59 | 显示全部楼层
可以到18Mbit的速度吗?  
earlmax 发表于 2021-8-11 17:59 | 显示全部楼层
模拟spi效果怎么样  
sesefadou 发表于 2021-8-11 18:00 | 显示全部楼层
哈哈哈,逻辑分析有问题吗   
alxd 发表于 2021-8-17 08:32 | 显示全部楼层
挺强 讲得很细致,遇到类似问题 又可以有参考了
您需要登录后才可以回帖 登录 | 注册

本版积分规则

认证:苏州澜宭自动化科技嵌入式工程师
简介:本人从事磁编码器研发工作,负责开发2500线增量式磁编码器以及17位、23位绝对值式磁编码器,拥有多年嵌入式开发经验,精通STM32、GD32、N32等多种品牌单片机,熟练使用单片机各种外设。

568

主题

4085

帖子

56

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