首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >在STM32中,内存的循环DMA周期将如何在传输结束时表现出来?

在STM32中,内存的循环DMA周期将如何在传输结束时表现出来?
EN

Stack Overflow用户
提问于 2020-02-08 23:13:34
回答 3查看 10.2K关注 0票数 4

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

我只是在想如何通过STM32将连续的数据流SPI传输到PC,因为我认为每隔一段时间用USB传输一次96字节的数据比通过STM32实时传输SPI要容易吗?我不知道这是可能的

EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2020-02-08 23:43:53

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

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

如果每个传输实际上需要96个字节,那么只需使缓冲区192个字节长(2x96)。

在伪码中:

代码语言:javascript
复制
#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,这可能就足够了。

票数 5
EN

Stack Overflow用户

发布于 2020-02-09 06:39:06

在我的一个项目中,我遇到了一个类似的问题。任务是将来自外部ADC芯片(与SPI连接)的数据通过全速USB传输到PC机。数据为(8 chx16位),我被要求达到最快的采样频率。

最后,我得到了一个三缓冲区解决方案。缓冲区有4种可能的状态:

  1. READY:缓冲区中满是数据,可以通过USB
  2. SENT:缓冲区发送,outdated
  3. IN_USE: DMA (由SPI请求)目前正在填充此buffer
  4. NEXT: --该缓冲区被认为是空的,在IN_USE已满时将使用。

由于USB请求的时间不能与SPI进程同步,我相信双缓冲解决方案是行不通的。如果没有下一个缓冲区,则在决定发送就绪缓冲区时,DMA可能会完成填充IN_USE缓冲区并开始损坏就绪缓冲区。但是在三重缓冲解决方案中,准备好的缓冲区发送到USB上是安全的,因为即使当前的IN_USE缓冲区已经满了,它也不会被填满。

因此,随着时间的推移,缓冲区状态看起来是这样的:

代码语言:javascript
复制
Buf0     Buf1      Buf2
====     ====      ====
READY    IN_USE    NEXT
SENT     IN_USE    NEXT
NEXT     READY     IN_USE
NEXT     SENT      IN_USE
IN_USE   NEXT      READY

当然,如果PC没有足够快地启动USB请求,当它变成下一个(在被发送之前),您可能仍然会释放一个现成的缓冲区。PC异步发送USB请求,不提供有关当前缓冲区状态的信息。如果没有就绪缓冲区(处于发送状态),则STM32用ZLP (零长度包)进行响应,PC在延迟1ms后再次尝试。

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

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

票数 3
EN

Stack Overflow用户

发布于 2020-02-09 09:00:55

通常,循环DMA只在触发半满/半空的情况下才能工作,否则就没有足够的时间从缓冲区复制信息。

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

如果在中断中执行副本,则在复制期间阻塞其他优先级较低的中断。在STM32上,48字节的简单简单的简单字节副本可能需要额外的48*6 ~300个时钟周期。

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

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

我这样做的一个USART在460 k波特率,接收异步可变长度的数据包。如果确保生产者只更新写入指针,而使用者只更新读指针,则可以避免在大多数情况下进行数据竞争。注意,皮质m3/m4上对齐的<=32位变量的读和写是原子的。

包含的代码是我使用的带有DMA支持的循环缓冲区的简化版本。它仅限于2^n的缓冲区大小,并使用模板和C++11功能,因此根据您的开发/平台约束,它可能不合适。

使用缓冲区调用getDmaReadBlock()或getDMAwriteBlock()并获取DMA内存地址和块长度。DMA完成后,使用skipRead() / skipWrite()将读或写指针增加到实际传输量。

代码语言:javascript
复制
 /**
   * 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)};
    }
  }
`
票数 3
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/60132112

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档