C++ 为什么在Raspberry Pi(主)和Nucleo l432kc板(从)之间的SPI通信中接收移位和重复数据

C++ 为什么在Raspberry Pi(主)和Nucleo l432kc板(从)之间的SPI通信中接收移位和重复数据,c++,raspberry-pi,stm32,spi,mbed,C++,Raspberry Pi,Stm32,Spi,Mbed,​我在ST Q&A论坛和Raspberry Pi论坛上问过同样的问题,但我没有得到任何回答。我希望这里有人能帮助我 我想让两个核子l432kc(从机)通过SPI协议与树莓Pi(主机)通信。在Raspberry上,我使用的是Spidev API,而在Nucleo上,我使用的是带有DMA的HAL SPI接口 允许稳定传输的唯一配置是Spidev的SPI_模式_2,对应于HAL的SPI_极性_高和SPI_相位_2边缘配置 对于上述配置,我有两个问题: 主设备发送给从设备的消息始终正确到达,但从设备发送

​我在ST Q&A论坛和Raspberry Pi论坛上问过同样的问题,但我没有得到任何回答。我希望这里有人能帮助我

我想让两个核子l432kc(从机)通过SPI协议与树莓Pi(主机)通信。在Raspberry上,我使用的是Spidev API,而在Nucleo上,我使用的是带有DMA的HAL SPI接口

允许稳定传输的唯一配置是Spidev的SPI_模式_2,对应于HAL的SPI_极性_高和SPI_相位_2边缘配置

对于上述配置,我有两个问题:

  • 主设备发送给从设备的消息始终正确到达,但从设备发送给主设备的所有消息始终向右移位1位(例如,如果我发送两个字节
    0b00000011 0b00000001
    我接收
    0b00000001 0b10000000
    )。看起来MISO信号的采样延迟了,但我不知道为什么
  • 无论是否设置了芯片选择,我都会在两个从机上接收数据。在从机的响应中,我没有碰撞,因此我认为当NSS高时,只有MISO总线在从机中有效地设置为高阻抗模式 为了测试目的,我尝试使用mbed框架的SPISlave类, 这个类不能使用DMA,所以我不能在实际程序中使用它。我用它解决了第一个问题,但第二个问题仍然存在

    另一件可能有用的事情是,对于SPISlave类,我可以使用SPI协议的所有4种模式,当然,从机和主机必须使用相同的模式,但不知道使用哪种模式。 正如我之前所说,对于HAL接口,我只能使用SPI_模式_2

    这是在两个从机上运行的配置代码:

    // SPI
    __HAL_RCC_SPI1_CLK_ENABLE();
    
    /* SPI1 parameter configuration*/
    hspi.Instance = SPI1; 
    hspi.Init.Mode = SPI_MODE_SLAVE;
    hspi.Init.Direction = SPI_DIRECTION_2LINES;   // full duplex mode 
    hspi.Init.DataSize = SPI_DATASIZE_8BIT;       // dimension of 1 byte
    hspi.Init.CLKPolarity = SPI_POLARITY_HIGH;    // start and idle clk value
    hspi.Init.CLKPhase = SPI_PHASE_2EDGE;         // edge of sampling of data both on miso and mosi        
    hspi.Init.FirstBit = SPI_FIRSTBIT_MSB;        // bit order
    hspi.Init.TIMode = SPI_TIMODE_DISABLE;        // disabling the TI mode
    hspi.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE; // disable crc calc
    hspi.Init.NSSPMode = SPI_NSS_PULSE_DISABLE;           // disable NSS puls value
    
    if (HAL_SPI_Init(&hspi) != HAL_OK)
      return false;
    
      /* SPI1 interrupt Init */
    HAL_NVIC_SetPriority(SPI1_IRQn, 0, 0);
    HAL_NVIC_EnableIRQ(SPI1_IRQn);
    
    // GPIO 
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    __HAL_RCC_GPIOA_CLK_ENABLE();
    __HAL_RCC_GPIOB_CLK_ENABLE();
    /**SPI1 GPIO Configuration    
    PA1     ------> SPI1_SCK
    PA11     ------> SPI1_MISO
    PA12     ------> SPI1_MOSI 
    PB0     ------> SPI1_NSS 
    */
    GPIO_InitStruct.Pin = GPIO_PIN_1|GPIO_PIN_11|GPIO_PIN_12;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
    GPIO_InitStruct.Alternate = GPIO_AF5_SPI1;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
    GPIO_InitStruct.Pin = GPIO_PIN_0;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
    GPIO_InitStruct.Alternate = GPIO_AF5_SPI1;
    HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
    
    
    // DMA 
    /* DMA controller clock enable */
    __HAL_RCC_DMA1_CLK_ENABLE();
    
        /* SPI1 DMA Init */
    /* SPI1_RX Init */
    hdma_spi_rx.Instance = DMA1_Channel2;
    hdma_spi_rx.Init.Request = DMA_REQUEST_1;
    hdma_spi_rx.Init.Direction = DMA_PERIPH_TO_MEMORY;
    hdma_spi_rx.Init.PeriphInc = DMA_PINC_DISABLE;
    hdma_spi_rx.Init.MemInc = DMA_MINC_ENABLE;
    hdma_spi_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
    hdma_spi_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
    hdma_spi_rx.Init.Mode = DMA_NORMAL;
    hdma_spi_rx.Init.Priority = DMA_PRIORITY_LOW;
    
    if (HAL_DMA_Init(&hdma_spi_rx) != HAL_OK) return false;
    
     __HAL_LINKDMA(&hspi,hdmarx,hdma_spi_rx);
    /* DMA interrupt init */
    /* DMA1_Channel2_IRQn interrupt configuration */
    HAL_NVIC_SetPriority(DMA1_Channel2_IRQn, 0, 0);
    HAL_NVIC_EnableIRQ(DMA1_Channel2_IRQn);
    
    /* SPI1 DMA Init */
    /* SPI1_TX Init */
    hdma_spi_tx.Instance = DMA1_Channel3;
    hdma_spi_tx.Init.Request = DMA_REQUEST_1;
    hdma_spi_tx.Init.Direction = DMA_MEMORY_TO_PERIPH;
    hdma_spi_tx.Init.PeriphInc = DMA_PINC_DISABLE;
    hdma_spi_tx.Init.MemInc = DMA_MINC_ENABLE;
    hdma_spi_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
    hdma_spi_tx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
    hdma_spi_tx.Init.Mode = DMA_NORMAL;
    hdma_spi_tx.Init.Priority = DMA_PRIORITY_LOW;
    
    if (HAL_DMA_Init(&hdma_spi_tx) != HAL_OK) return false;
    
    __HAL_LINKDMA(&hspi,hdmatx,hdma_spi_tx);
    
    /* DMA interrupt init */
    /* DMA1_Channel3_IRQn interrupt configuration */
    HAL_NVIC_SetPriority(DMA1_Channel3_IRQn, 0, 0);
    HAL_NVIC_EnableIRQ(DMA1_Channel3_IRQn);
    
    这是在主机上运行的代码:

    unsigned int bitsPerByte = 8u;
    unsigned int delay =  0u; 
    unsigned int speed = 100; // hz
    unsigned int cs_change   = 0u;  // false in C
    
    // initialization
    fd = ::open("/dev/spidev0.0", O_RDWR);
    auto mode = SPI_MODE_2;  // clock polarity low, clock phase second edge
    
    if (fd == -1)
        throw std::runtime_error("Can't open the spi device");
    
    if (::ioctl(fd, SPI_IOC_WR_MODE, &mode) == -1) 
        throw std::runtime_error("Can't set the spi mode"); 
    
     /*
     * bits per word
     */
    auto bits = bitsPerByte;
    auto ret = ioctl(fd, SPI_IOC_WR_BITS_PER_WORD, &bits);
    if (ret == -1)
        throw std::runtime_error("can't set bits per word");
    
    ret = ioctl(fd, SPI_IOC_RD_BITS_PER_WORD, &bits);
    if (ret == -1)
        throw std::runtime_error("can't get bits per word");
    
    /*
     * max speed hz
     */
    auto maxSpeed = speed;
    ret = ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, &maxSpeed);
    if (ret == -1)
        throw std::runtime_error("can't set max speed hz");
    
    ret = ioctl(fd, SPI_IOC_RD_MAX_SPEED_HZ, &maxSpeed);
    if (ret == -1)
        throw std::runtime_error("can't get max speed hz");
    
    // code used for sending messages
    void transfer(uint8_t *tx, uint8_t *rx, size_t size) {
        struct spi_ioc_transfer tr = {  
            (unsigned long)tx,  // .tx_buf 
            (unsigned long)rx,  // .rx_buf        
            size,               // .len  
            speed,              // .speed_hz
            delay,              // .delay_usecs
            bitsPerByte,        // .bits_per_word
            cs_change,          // .cs_change
            0,                  // .tx_nbits
            0,                  // .rx_nbits
            0,                  // .pad
        };
    
        if (::ioctl(fd, SPI_IOC_MESSAGE(1), &tr) == -1)
            throw std::runtime_error("Can't send data throught the spi");
    }
    
    在从机的主要功能中,我实际做的唯一一件事就是发回我收到的确切数据包

    编辑 在覆盆子论坛上,有人告诉我使用piscope查看发送到PIN上的数字值。我已经这样做了,我看到CE0和CE1引脚总是很低。我不明白为什么味噌上没有碰撞

    编辑2 我已经解决了第二个问题(重复数据),这是主配置上的一个错误,我使用的是
    cs_change=1
    ,但它必须等于0


    随着我发现从机正确发送数据,是主机没有很好地读取数据

    断开为1是典型的错误时钟边缘,或者SCK的空闲状态在激活和非激活SPI之间不同(如果激活SPI,将产生时钟滴答声。您需要为SCK使用上拉/下拉电阻器)。好的,我应该在从机或主机上使用下拉菜单?通常的方法是靠近主机,如果其他线路也有下拉菜单(这是个好主意),则将它们放在一起。但这并不重要。您只需要定义一个值。如果您愿意,您甚至可以将它们放在两端。我尝试过上拉和下拉(先上拉然后下拉),没有任何变化。关闭一个是错误时钟边缘的典型情况,或者SCK的空闲状态在活动和非活动SPI之间是不同的(如果你激活SPI,会产生时钟滴答声。你需要为SCK使用上拉/下拉电阻)。好的,我应该在从机或主机上使用下拉电阻?通常的方法是靠近主机,如果你对其他线路也有下拉电阻(这是个好主意)把它们放在一起。但这并不重要。你只想有一个定义的值。如果你愿意,你甚至可以把它们放在两端。我尝试过上拉和下拉(先上拉然后下拉),没有任何变化。