STM32'у кольцевой буфер не нужен :), т.к. у него существует
аппаратное прерывание в середине DMA-буфера. А потому возможно
использовать непрерывный/циклический режим приема/передачи, успевая
в обработчике этого прерывания заполнять первую половину DMA-буфера
новыми данными или забирать их оттуда (в зависимости от того, на
прием или передачу тот буфера работает). Что касается HAL'а, то там
DMA-прерывания уже имплементированы в функции работы с периферией.
Например: ConvCpltCallback(...); // ADC conversion complete callback
ConvHalfCpltCallback(...); // ADC conversion DMA half-transfer callback
В таких случаях никого программирования DMA-прерываний от программиста не требуется, а достаточно лишь употребить эти функции в программном коде (строго соблюдая синтаксис их параметров), чтобы нужные прерывания были разрешены и эти функции вызывали. Скажем, если вы использовали в своей программе функцию ConvHalfCpltCallback, то и прерывание на середине DMA-буфера будет автоматически включено при инициализации ADC. Аналогично этому, использование функции ConvCpltCallback разрешает прерывание по концу DMA-буфера. При использовании обеих этих функций тактика такая: в первой функции забираю оцифрованные ADC'ом данные из 1-ой половины буфера, а во второй функции забираю данные из 2-ой половины буфера. Причем оцифровка данных идет непрерывно в циклическом режиме без останова на отсос данных.
Рассказываю это на примере ADC, поскольку сама это делала, но полагаю, что в случае SPI и UART механизм работает точно так же. Например, для UART'а в HAL'е тоже имеются зарезервированные функции с именами:
RxCpltCallback
RxHalfCpltCallback
- для приема и
TxCpltCallback
TxHalfCpltCallback
- для передачи.