Embedded 在STM32中传输结束时,循环DMA外围到内存的行为如何?

Embedded 在STM32中传输结束时,循环DMA外围到内存的行为如何?,embedded,usb,stm32,spi,dma,Embedded,Usb,Stm32,Spi,Dma,我想问,在下面的情况下,DMA SPI rx在STM32中的表现如何。 我有一个指定的(例如)96字节数组,名为a,用于存储从SPI接收的数据。我打开循环SPI DMA,它在每个字节上运行,配置为96字节。 在循环DMA开始写入A(并销毁保存在B中的数据)之前,当DMA将填充我的96字节数组时,传输完成中断是否可能关闭,以快速将96字节数组复制到另一个-B? 我想通过USB将数据从B中的A快速传输到PC(每次我从B中的A获取新数据时) 我只是在想如何通过USB将STM32的连续数据流SPI传输到

我想问,在下面的情况下,DMA SPI rx在STM32中的表现如何。 我有一个指定的(例如)96字节数组,名为a,用于存储从SPI接收的数据。我打开循环SPI DMA,它在每个字节上运行,配置为96字节。 在循环DMA开始写入A(并销毁保存在B中的数据)之前,当DMA将填充我的96字节数组时,传输完成中断是否可能关闭,以快速将96字节数组复制到另一个-B? 我想通过USB将数据从B中的A快速传输到PC(每次我从B中的A获取新数据时)


我只是在想如何通过USB将STM32的连续数据流SPI传输到PC,因为一个96字节的数据块每特定时间通过USB传输一次比通过STM32将实时SPI传输到USB更容易?我甚至不知道这是否可能

要使其工作,您必须能够保证在接收到下一个SPI字节并将其传输到缓冲区开始之前复制所有数据。这是否可能取决于处理器的时钟速度和SPI的速度,并且能够保证不会发生可能延迟传输的更高优先级中断。为了安全起见,它需要非常慢的SPI速度,在这种情况下,可能根本不需要使用DMA

总而言之,这是个坏主意,完全没有必要。DMA控制器有一个“半传输”中断正好用于此目的。传输前48个字节时,您将获得HT中断,而DMA将在您复制下半缓冲区时继续传输剩余的48个字节。当你完成转移时,你转移上半部分。这将您传输数据的时间从单个字节的接收时间延长到48字节的接收时间

如果每次传输都需要96字节,那么只需将缓冲区设置为192字节长(2x96)

在伪代码中:

#define BUFFER_LENGTH 96
char DMA_Buffer[2][BUFFER_LENGTH] ;

void DMA_IRQHandler()
{
    if( DMA_IT_Flag(DMA_HT) == SET )
    {
        memcpy( B, DMA_Buffer[0], BUFFER_LENGTH ) ;
        Clear_IT_Flag(DMA_HT) ;
    }
    else if( DMA_IT_Flag(DMA_TC) == SET )
    {
        memcpy( B, DMA_Buffer[1], BUFFER_LENGTH ) ;
        Clear_IT_Flag(DMA_TC) ;
    }
}

关于通过USB将数据传输到PC,首先需要确保USB传输速率至少与SPI传输速率相同或更快。USB传输可能不太确定(因为它由PC主机控制-也就是说,只有当主机明确要求时,您才能在USB上输出数据),因此即使平均传输速率足够,也可能存在需要进一步缓冲的延迟,因此,与简单地从DMA缓冲区A复制到USB缓冲区B不同,您可能需要一个循环缓冲区或FIFO队列来为USB提供数据。另一方面,如果您已经有了缓冲区
DMA_buffer[0]
DMA_buffer[1]
B
,那么实际上您已经有了三个96字节的FIFO块,这可能就足够了。在我的一个项目中,我遇到了类似的问题。任务是通过全速USB将来自外部ADC芯片(与SPI连接)的数据传输到PC。数据是(8通道x 16位),我被要求实现尽可能快的采样频率

我最终得到了一个三重缓冲解决方案。缓冲区可能处于4种状态:

  • 准备就绪:缓冲区已满,可以通过USB发送数据
  • 已发送:缓冲区已发送且过期
  • 正在使用:DMA(由SPI请求)当前正在填充此缓冲区
  • 下一步:此缓冲区被视为空缓冲区,将在使用满时使用
  • 由于USB请求的时间不能与SPI进程同步,我认为双缓冲区解决方案不起作用。如果您没有下一个缓冲区,在您决定发送就绪缓冲区时,DMA可能会完成填充IN_USE缓冲区并开始损坏就绪缓冲区。但在三缓冲区解决方案中,READY buffer可以通过USB安全发送,因为即使当前使用的缓冲区已满,它也不会被填充

    随着时间的推移,缓冲区状态如下所示:

    Buf0     Buf1      Buf2
    ====     ====      ====
    READY    IN_USE    NEXT
    SENT     IN_USE    NEXT
    NEXT     READY     IN_USE
    NEXT     SENT      IN_USE
    IN_USE   NEXT      READY
    
    当然,如果电脑启动USB请求的速度不够快,在进入下一个(发送前)时,您可能仍然会丢失一个就绪缓冲区。PC异步发送USB IN请求,而不提供有关当前缓冲区状态的信息。如果没有就绪缓冲区(它处于发送状态),STM32将以ZLP(零长度软件包)响应,PC将在延迟1毫秒后重试

    对于STM32上的实现,我使用双缓冲模式,并修改DMA传输完成ISR中的M0AR和M1AR寄存器,以寻址3个缓冲区


    顺便说一句,我使用了(3 x 4000)字节缓冲区,并在最后实现了32 kHz的采样频率。USB被配置为特定于供应商的类,并使用批量传输。

    通常,只有在半满/半空触发时,使用循环DMA才有效,否则您没有足够的时间将信息复制出缓冲区

    我建议不要在中断期间将数据从缓冲区中复制出来。而是直接从缓冲区使用数据,而不需要额外的复制步骤

    如果在中断中进行复制,则在复制过程中会阻止其他低优先级的中断。在STM32上,48字节的简单原始字节拷贝可能需要额外的48*6~300个时钟周期

    如果独立地跟踪缓冲区的读写位置,则只需更新单个指针并向缓冲区的使用者发出延迟通知调用

    如果您想要更长的周期,那么不要使用循环DMA,而是在48字节块中使用普通DMA,并实现循环字节缓冲区作为数据结构

    我这样做是为了一个460k波特的USART,它接收异步可变长度的数据包。如果您确保生产者只更新写指针,消费者只更新读指针,则可以避免大多数情况下的数据争用。请注意,read和wr
     /**
       * Creates a circular buffer. There is a read pointer and a write pointer
       * The buffer is full when the write pointer is = read pointer -1
       */
     template<uint16_t SIZE=256>
      class CircularByteBuffer {
        public:
          struct MemBlock {
              uint8_t  *blockStart;
              uint16_t blockLength;
          };
    
        private:
          uint8_t *_data;
          uint16_t _readIndex;
          uint16_t _writeIndex;
    
          static constexpr uint16_t _mask = SIZE - 1;
    
          // is the circular buffer a power of 2
          static_assert((SIZE & (SIZE - 1)) == 0);
    
        public:
          CircularByteBuffer &operator=(const CircularByteBuffer &) = default;
    
          CircularByteBuffer(uint8_t (&data)[SIZE]);
    
          CircularByteBuffer(const CircularByteBuffer &) = default;
    
          ~CircularByteBuffer() = default;
    
        private:
          static uint16_t wrapIndex(int32_t index);
    
        public:
          /*
           * The number of byte available to be read. Writing bytes to the buffer can only increase this amount.
           */
          uint16_t readBytesAvail() const;
    
          /**
           * Return the number of bytes that can still be written. Reading bytes can only increase this amount.
           */
          uint16_t writeBytesAvail() const;
    
          /**
           * Read a byte from the buffer and increment the read pointer
           */
          uint8_t readByte();
    
          /**
           * Write a byte to the buffer and increment the write pointer. Throws away the byte if there is no space left.
           * @param byte
           */
          void writeByte(uint8_t byte);
    
          /**
           * Provide read only access to the buffer without incrementing the pointer. Whilst memory accesses outside the
           * allocated memeory can be performed. Garbage data can still be read if that byte does not contain valid data
           * @param pos the offset from teh current read pointer
           * @return the byte at the given offset in the buffer.
           */
          uint8_t operator[](uint32_t pos) const;
    
          /**
           * INcrement the read pointer by a given amount
           */
          void skipRead(uint16_t amount);
          /**
           * Increment the read pointer by a given amount
           */
          void skipWrite(uint16_t amount);
    
    
          /**
           * Get the start and lenght of the memeory block used for DMA writes into the queue.
           * @return
           */
          MemBlock getDmaWriteBlock();
    
          /**
           * Get the start and lenght of the memeory block used for DMA reads from the queue.
           * @return
           */
          MemBlock getDmaReadBlock();
    
      };
    
      // CircularByteBuffer
      // ------------------
      template<uint16_t SIZE>
      inline CircularByteBuffer<SIZE>::CircularByteBuffer(uint8_t (&data)[SIZE]):
          _data(data),
          _readIndex(0),
          _writeIndex(0) {
      }
    
      template<uint16_t SIZE>
      inline uint16_t CircularByteBuffer<SIZE>::wrapIndex(int32_t index){
        return static_cast<uint16_t>(index & _mask);
      }
    
      template<uint16_t SIZE>
      inline uint16_t CircularByteBuffer<SIZE>::readBytesAvail() const {
        return wrapIndex(_writeIndex - _readIndex);
      }
    
      template<uint16_t SIZE>
      inline uint16_t CircularByteBuffer<SIZE>::writeBytesAvail() const {
        return wrapIndex(_readIndex - _writeIndex - 1);
      }
    
      template<uint16_t SIZE>
      inline uint8_t CircularByteBuffer<SIZE>::readByte() {
        if (readBytesAvail()) {
          uint8_t result = _data[_readIndex];
          _readIndex = wrapIndex(_readIndex+1);
          return result;
        } else {
          return 0;
        }
      }
    
      template<uint16_t SIZE>
      inline void CircularByteBuffer<SIZE>::writeByte(uint8_t byte) {
        if (writeBytesAvail()) {
          _data[_writeIndex] = byte;
          _writeIndex = wrapIndex(_writeIndex+1);
        }
      }
    
      template<uint16_t SIZE>
      inline uint8_t CircularByteBuffer<SIZE>::operator[](uint32_t pos) const {
        return _data[wrapIndex(_readIndex + pos)];
      }
    
      template<uint16_t SIZE>
      inline void CircularByteBuffer<SIZE>::skipRead(uint16_t amount) {
        _readIndex = wrapIndex(_readIndex+ amount);
      }
    
      template<uint16_t SIZE>
      inline void CircularByteBuffer<SIZE>::skipWrite(uint16_t amount) {
        _writeIndex = wrapIndex(_writeIndex+ amount);
      }
    
      template <uint16_t SIZE>
      inline typename CircularByteBuffer<SIZE>::MemBlock  CircularByteBuffer<SIZE>::getDmaWriteBlock(){
        uint16_t len = static_cast<uint16_t>(SIZE - _writeIndex);
       // full is  (write == (read -1)) so on wrap around we need to ensure that we stop 1 off from the read pointer.
        if( _readIndex == 0){
          len = static_cast<uint16_t>(len - 1);
        }
        if( _readIndex > _writeIndex){
          len = static_cast<uint16_t>(_readIndex - _writeIndex - 1);
        }
        return {&_data[_writeIndex], len};
      }
    
      template <uint16_t SIZE>
      inline typename CircularByteBuffer<SIZE>::MemBlock  CircularByteBuffer<SIZE>::getDmaReadBlock(){
        if( _readIndex > _writeIndex){
          return {&_data[_readIndex], static_cast<uint16_t>(SIZE- _readIndex)};
        } else {
          return {&_data[_readIndex], static_cast<uint16_t>(_writeIndex - _readIndex)};
        }
      }
    `