STM32硬件SPI时钟频率与时钟解析(基于逻辑分析仪的抓包试验)

首先粘贴出我们CubeMX生成的时钟配置:
在这里插入图片描述
然后启用SPI3的功能,这里因为博主的逻辑分析仪比较low,所以把SPI的波特率设置成最大分频,即256分频,此时CubeMX工具计算出来的时钟频率为1.5625MBits/s
在这里插入图片描述
我们都知道,SPI3挂载在APB1总线上,受到总线的最大时钟120M的限制,由前面的时钟图可以知道,APB1总线时钟速度为100M,那么经过256分频应该是390.625KHz才对。1.5625M/390.625K=4,这里的4倍频,是CubeMX软件计算的问题,还是真的哪里有了4倍频?
在这里插入图片描述
先研究一下手册里关于APB1寄存器的相关说明:
在这里插入图片描述
其中SPI3的时钟包含了给spi_ker_ck 输入的内核时钟,以及 rcc_pclk1总线接口时钟。
在这里插入图片描述
先看一下SPI的结构图:
在这里插入图片描述
对于SPI外设来说,有两个时钟域,一个输入到寄存器、另外一个输入到时钟发生器,从上面的图可以简单看出来,spi_pclk给SPI寄存器提供访问时钟,而spi_ker_ck则是给SPI从设备提供SCK信号输出。
再回到CubeMX的软件配置界面,发现这里新激活了一块:
在这里插入图片描述
多了一个SPI时钟矩阵,难道这个就是spi_ker_ck?
假设这个最大再来分析一波,对于STM32的SPI协议来说,Data Size最小为4bit,最大为32bit;
当传输的数据位为最小4bit时:
受到APB1总线速度的限制,spi_pclk最大也就120MHz,因为PLLQ最大也只能是480MHz,假设这个SPI Clock MUX就是spi_ker_ck,那么最大也就是480MHz,刚好接收完4个bit,寄存器的时钟脉冲也到了。
话不多说验证一波,根据CubeMX生成的系统时钟配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
/**
  * @brief  System Clock 配置
  *         system Clock 配置如下:
    *            System Clock source  = PLL (HSE)
    *            SYSCLK(Hz)           = 400000000 (CPU Clock)
    *            HCLK(Hz)             = 200000000 (AXI and AHBs Clock)
    *            AHB Prescaler        = 2
    *            D1 APB3 Prescaler    = 2 (APB3 Clock  100MHz)
    *            D2 APB1 Prescaler    = 2 (APB1 Clock  100MHz)
    *            D2 APB2 Prescaler    = 2 (APB2 Clock  100MHz)
    *            D3 APB4 Prescaler    = 2 (APB4 Clock  100MHz)
    *            HSE Frequency(Hz)    = 25000000
    *            PLL_M                = 5
    *            PLL_N                = 160
    *            PLL_P                = 2
    *            PLL_Q                = 4
    *            PLL_R                = 2
    *            VDD(V)               = 3.3
    *            Flash Latency(WS)    = 4
  * @param  None
  * @retval None
  */
static void SystemClock_Config(void)
{
    RCC_ClkInitTypeDef RCC_ClkInitStruct;
    RCC_OscInitTypeDef RCC_OscInitStruct;
    HAL_StatusTypeDef ret = HAL_OK;

    /*使能供电配置更新 */
    MODIFY_REG(PWR->CR3, PWR_CR3_SCUEN, 0);

    /* 当器件的时钟频率低于最大系统频率时,电压调节可以优化功耗,
       关于系统频率的电压调节值的更新可以参考产品数据手册。  */
    __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);

    while (!__HAL_PWR_GET_FLAG(PWR_FLAG_VOSRDY)) {}

    /* 启用HSE振荡器并使用HSE作为源激活PLL */
    RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
    RCC_OscInitStruct.HSEState = RCC_HSE_ON;
    RCC_OscInitStruct.HSIState = RCC_HSI_OFF;
    RCC_OscInitStruct.CSIState = RCC_CSI_OFF;
    RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
    RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;

    RCC_OscInitStruct.PLL.PLLM = 1;
    RCC_OscInitStruct.PLL.PLLN = 100;
    RCC_OscInitStruct.PLL.PLLP = 2;
    RCC_OscInitStruct.PLL.PLLR = 2;
    RCC_OscInitStruct.PLL.PLLQ = 4;

    RCC_OscInitStruct.PLL.PLLVCOSEL = RCC_PLL1VCOWIDE;
    RCC_OscInitStruct.PLL.PLLRGE = RCC_PLL1VCIRANGE_2;
    ret = HAL_RCC_OscConfig(&RCC_OscInitStruct);
    if (ret != HAL_OK)
    {
        while (1) { ; }
    }

    /* 选择PLL作为系统时钟源并配置总线时钟分频器 */
    RCC_ClkInitStruct.ClockType = (RCC_CLOCKTYPE_SYSCLK  | \
                                   RCC_CLOCKTYPE_HCLK    | \
                                   RCC_CLOCKTYPE_D1PCLK1 | \
                                   RCC_CLOCKTYPE_PCLK1   | \
                                   RCC_CLOCKTYPE_PCLK2   | \
                                   RCC_CLOCKTYPE_D3PCLK1);
    RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
    RCC_ClkInitStruct.SYSCLKDivider = RCC_SYSCLK_DIV1;
    RCC_ClkInitStruct.AHBCLKDivider = RCC_HCLK_DIV2;
    RCC_ClkInitStruct.APB3CLKDivider = RCC_APB3_DIV2;
    RCC_ClkInitStruct.APB1CLKDivider = RCC_APB1_DIV2;
    RCC_ClkInitStruct.APB2CLKDivider = RCC_APB2_DIV2;
    RCC_ClkInitStruct.APB4CLKDivider = RCC_APB4_DIV2;
    ret = HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_4);
    if (ret != HAL_OK)
    {
        while (1) { ; }
    }

    RCC_PeriphCLKInitTypeDef PeriphClkInitStruct = {0};
    PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_ADC | RCC_PERIPHCLK_CKPER;
    PeriphClkInitStruct.CkperClockSelection = RCC_CLKPSOURCE_HSE;
    PeriphClkInitStruct.AdcClockSelection = RCC_ADCCLKSOURCE_CLKP;
    if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct) != HAL_OK)
    {
        while (1) { ; }
    }

}

对应SPI3的配置函数如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
SPI_HandleTypeDef SPI3_Handler;  //SPI2句柄

void SPI3_Init(void)
{
    SPI3_Handler.Instance = SPI3;
    SPI3_Handler.Init.Mode = SPI_MODE_MASTER;
    SPI3_Handler.Init.Direction = SPI_DIRECTION_2LINES;
    SPI3_Handler.Init.DataSize = SPI_DATASIZE_8BIT;
    SPI3_Handler.Init.CLKPolarity = SPI_POLARITY_LOW;
    SPI3_Handler.Init.CLKPhase = SPI_PHASE_1EDGE;
    SPI3_Handler.Init.NSS = SPI_NSS_SOFT;
    SPI3_Handler.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_256;
    SPI3_Handler.Init.FirstBit = SPI_FIRSTBIT_MSB;
    SPI3_Handler.Init.TIMode = SPI_TIMODE_DISABLE;
    SPI3_Handler.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
    SPI3_Handler.Init.CRCPolynomial = 7;
    SPI3_Handler.Init.CRCLength = SPI_CRC_LENGTH_DATASIZE;
    SPI3_Handler.Init.NSSPMode = SPI_NSS_PULSE_DISABLE;
    HAL_SPI_Init(&SPI3_Handler);

    __HAL_SPI_ENABLE(&SPI3_Handler);              
}


//SPI2 读写一个字节
//TxData:要写入的字节
//返回值:读取到的字节
uint8_t SPI3_ReadWriteByte(uint8_t TxData)
{
    uint8_t Rxdata;
    HAL_SPI_TransmitReceive(&SPI3_Handler, &TxData, &Rxdata, 1, 1000);
    return Rxdata;                      //返回收到的数据
}

//读取芯片ID
//返回值如下:                 
//0XEF13,表示芯片型号为W25Q80  
//0XEF14,表示芯片型号为W25Q16    
//0XEF15,表示芯片型号为W25Q32  
//0XEF16,表示芯片型号为W25Q64
//0XEF17,表示芯片型号为W25Q128    
uint16_t W25QXX_ReadID(void)
{
    uint16_t Temp = 0;   
    HAL_GPIO_WritePin(GPIOA,GPIO_PIN_4,GPIO_PIN_RESET);    
    SPI3_ReadWriteByte(0x90);//发送读取ID命令    
    SPI3_ReadWriteByte(0x00);      
    SPI3_ReadWriteByte(0x00);      
    SPI3_ReadWriteByte(0x00);                  
    Temp|=SPI3_ReadWriteByte(0xFF)<<8;  
    Temp|=SPI3_ReadWriteByte(0xFF);  
    HAL_GPIO_WritePin(GPIOA,GPIO_PIN_4,GPIO_PIN_SET);  
    return Temp;
}  

void W25QXX_Init(void)
{
    MX_GPIO_Init();
    HAL_GPIO_WritePin(GPIOA,GPIO_PIN_4,GPIO_PIN_SET);
    SPI3_Init();
    uint16_t W25QXX_TYPE=W25QXX_ReadID();//读取FLASH ID.  
    printf("W25QXX ID:0x%4x\r\n",W25QXX_TYPE);
}

void HAL_SPI_MspInit(SPI_HandleTypeDef* spiHandle)
{

  GPIO_InitTypeDef GPIO_InitStruct = {0};
   if(spiHandle->Instance==SPI3)
  {
    __HAL_RCC_SPI3_CLK_ENABLE();
    __HAL_RCC_GPIOB_CLK_ENABLE();
    GPIO_InitStruct.Pin = GPIO_PIN_2;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
    GPIO_InitStruct.Alternate = GPIO_AF7_SPI3;
    HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

    GPIO_InitStruct.Pin = GPIO_PIN_3|GPIO_PIN_4;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
    GPIO_InitStruct.Alternate = GPIO_AF6_SPI3;
    HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
  }
}

先直接调用函数W25QXX_Init,得到输出:
在这里插入图片描述
连接逻辑分析仪:
在这里插入图片描述
复位一次抓包:
在这里插入图片描述
对照着我们读ID的程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
uint16_t W25QXX_ReadID(void)
{
    uint16_t Temp = 0;   
    HAL_GPIO_WritePin(GPIOA,GPIO_PIN_4,GPIO_PIN_RESET);    
    SPI3_ReadWriteByte(0x90);//发送读取ID命令    
    SPI3_ReadWriteByte(0x00);      
    SPI3_ReadWriteByte(0x00);      
    SPI3_ReadWriteByte(0x00);                  
    Temp|=SPI3_ReadWriteByte(0xFF)<<8;  
    Temp|=SPI3_ReadWriteByte(0xFF);  
    HAL_GPIO_WritePin(GPIOA,GPIO_PIN_4,GPIO_PIN_SET);  
    return Temp;
}

其中0x90=144,0xEF=239,0x14=20可以看出,读写过程是一个全双工的过程。
在这里插入图片描述
放大来看,左边有个ERROR是因为我是抓的片选信号的下降沿,而在上电初始化IO口的时候默认拉低,随后手动拉高了。
而右边的数据,时钟周期是1.25us,即800KHz,虽然逻辑分析仪比较垃圾,但是800KHz和签名算出来的1.5625MBits/s的波特率,好像还是差了个二倍关系呀,emm……
心中掠过千万种不合实际的想法:比如SPI可以根据时钟极性配置成上升沿和下降沿各采集一次?还是全双工的波特率要x2?但串口人家也没x2呀……

再次想着是不是逻辑分析仪的问题,准备把SCK速率调低,不过最大也就256分频了,再调就只能改PLL的DIV1Q了,说改就改,突然发现,写的代码里居然和时钟树不一样……(因为这里偷懒,直接把SPI的配置放在了一个常用的工程里,时钟配置直接用就没改,平时也没注意PLLQ……)
在这里插入图片描述
所以这里的实际计算的波特率应该是1.5625/2=781.25KHz,和上图里的800KHz接近。

总结两点:

  • HAL_SPI_TransmitReceive的传输是全双工的,如果硬件是单双工的收(发送端悬空),SPI3_ReadWriteByte可以传入任意参数;如果硬件是单双工的发,那么返回值可以不要。
  • SPI的有两个速率,APB1的速率只与处理器访问APB1总线上的SPI寄存器有关,不影响通信;而SPI的SCK速率由单独的时钟矩阵选择输入源,在经过SPI外设的分配器产生波特率。

希望这篇博文能够帮助大家更好的理解SPI协议。