通常只有在半满/半空时触发下,使用循环DMA才有效,否则您就没有足够的时间将信息从缓冲区复制出来。建议您不要在中断期间复制缓冲区中的数据,而是直接从缓冲区中使用数据,避免额外的复制步骤。
如果在中断中进行复制,则会在复制期间阻塞其他低优先级中断。在STM32上,简单的幼稚字节复制48字节可能需要额外的48 * 6〜300个时钟周期。
如果您独立跟踪缓冲区的读写位置,则只需要更新单个指针并向缓冲区的使用者发送延迟通知调用即可。
如果您想要一个更长的周期,则不要使用循环DMA,而是使用48字节块的普通DMA,并将循环字节缓冲区实现为数据结构。
我曾经在460k波特率的USART上异步接收可变长度的数据包。如果确保生产者仅更新写指针,使用者仅更新读指针,则可以避免大部分数据竞争。请注意,在cortex m3 / m4上对齐的<=32位变量的读取和写入是原子的。
附带的代码是我使用的带DMA支持的循环缓冲区的简化版本。它仅限于2^n的缓冲区大小,并使用模板和C++11功能,因此根据您的开发/平台约束可能不适用。
要使用缓冲区,请调用getDmaReadBlock()或getDMAwriteBlock(),并获取DMA内存地址和块长度。一旦DMA完成,请使用skipRead()/skipWrite()将读或写指针增加实际传输的量。
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;
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:
uint16_t readBytesAvail() const;
uint16_t writeBytesAvail() const;
uint8_t readByte();
void writeByte(uint8_t byte);
uint8_t operator[](uint32_t pos) const;
void skipRead(uint16_t amount);
void skipWrite(uint16_t amount);
MemBlock getDmaWriteBlock();
MemBlock getDmaReadBlock();
};
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);
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)};
}
}
`