我想问,在下面的情况下,在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要容易吗?我不知道这是可能的
发布于 2020-02-08 23:43:53
要做到这一点,您必须能够保证在接收到下一个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,这可能就足够了。
发布于 2020-02-09 06:39:06
在我的一个项目中,我遇到了一个类似的问题。任务是将来自外部ADC芯片(与SPI连接)的数据通过全速USB传输到PC机。数据为(8 chx16位),我被要求达到最快的采样频率。
最后,我得到了一个三缓冲区解决方案。缓冲区有4种可能的状态:
由于USB请求的时间不能与SPI进程同步,我相信双缓冲解决方案是行不通的。如果没有下一个缓冲区,则在决定发送就绪缓冲区时,DMA可能会完成填充IN_USE缓冲区并开始损坏就绪缓冲区。但是在三重缓冲解决方案中,准备好的缓冲区发送到USB上是安全的,因为即使当前的IN_USE缓冲区已经满了,它也不会被填满。
因此,随着时间的推移,缓冲区状态看起来是这样的:
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被配置为特定于供应商的类,它使用批量传输。
发布于 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()将读或写指针增加到实际传输量。
/**
* 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)};
}
}
`https://stackoverflow.com/questions/60132112
复制相似问题