STM32F103: странная проблема с DMA (исправлено, но не решено)

Пытаюсь заюзать DMA для пересылки данных из фиксированного массива в регистр, так же находящийся в памяти по фиксированному адресу (на него отображается шина SSD1963).

Ежели адрес источника данных не инкрементируется (т.е. чтение идёт всё время из одного и того же адреса), то всё ок - на экране видим однотонную заливку (на картинке ниже синий фон именно так и создан).

Если же адрес инкрементируется (в соответствии с флагом в соответствующем регистре контроллера DMA), то каждые первые n посылок (в терминологии референс-мануала -- "токенов") портятся, но каким-то не очень случайным образом:

Ширина испорченной зоны подозрительно похожа на 32. Посылки все 16-битные (16-битных массив пересылается в 16-битный регистр). Эффект не зависит ни от стартового адреса, ни от количества данных в одной транзакции. Скорость работы шины вряд ли виновата (т.к. равномерная заливка всё-таки работает и тактовая частота микроконтроллера более чем в два раза ниже, чем тактовая на видеочипе).

Код:

    SSD1963_SetArea (sx, ex, y, y);
    RCC->AHBENR |= RCC_AHBENR_DMA1EN; 

    DMA1_Channel1->CMAR = (uint32_t)(&buffer[0]); 
    DMA1_Channel1->CPAR = (uint32_t)(&SSD1963.Data); 
    DMA1_Channel1->CNDTR = Count;

    DMA1_Channel1->CCR = DMA_CCR1_MEM2MEM | /* memory-to-memory */
            (0*DMA_CCR1_PL_1) | (0*DMA_CCR1_PL_0) | /* 11: very high priority mode, 00: low priority */
            DMA_CCR1_MSIZE_0 | /* memory size: 16bit */
            DMA_CCR1_PSIZE_0 | /* periph size: 16bit */
            DMA_CCR1_MINC | /* memory increment enabled */
            DMA_CCR1_DIR ; /* direction: read from memory */

    SSD1963.Cmd = (SSD1963_WRITE_MEMORY_START);

    DMA1_Channel1->CCR |= DMA_CCR1_EN; 
    while (!(DMA1->ISR & DMA_ISR_TCIF1)) {}; 
    DMA1_Channel1->CCR &= ~DMA_CCR1_EN; 
    DMA1->IFCR = DMA_IFCR_CTCIF1; 

    RCC->AHBENR &= ~RCC_AHBENR_DMA1EN;



Если я пересылаю ровно точно тот же массив в точно тот же регистр "вручную", но всё пишется нормально, просто медленнее. Если я запускаю программу под отладчиком (опции компиляции все точно те же) и смотрю на регистры, то там записанно именно то, что надо - и глюка пересылки данных НЕТ.

Вопрос - чё за хренотень и как с ней бороться?

Upd: нашёл. Виновата оптимизация -O3. Если её не так туго затягивать, то на -O1 всё работает как и должно работать. Я понял это, когда в отладчике стал смотреть инициализацию DMA по шагам. В сишном коде все операции записаны в том порядке, в котором они ДОЛЖНЫ быть - сначала устанавливаем регистры адресов, затем настраиваем транзакцию, затем включаем... а оптимизатор решил, что всё это неправильно и в итоге получилось так: сначала блок DMA включается на передачу, успевает что-то передать (тот самый мусор!), а затем уже исполняются инструкции, которые его настраивают и дальше высылаются корректные данные.
s -O3 тоже должно работать, только нужно пометить регистры пересылаемые через дма (которые могут изменитьтся сами собой) kak volatile
дык они так и помечены
оптимизатору пофиг, он их всё равно перемешал

проблему решила обёртка функций атрибутом
void __attribute__((optimize("O0"))) SSD1963_WritePixelsLine (uint16_t sx, uint16_t ex, uint16_t y, const uint16_t *buffer)

теперь внутри обёрнутых функций код "как есть", в том же порядке
Не, ну это хрень.
Если -O3 ломает код - 99% у вас таки undefined behavior или типа того (не факт, что именно в вашем коде - возможно, в используемых хидерах). Компилятор, конечно, не безбажен, но лучше б найти, в чём дело.

Классический случай (не ваш, но удобен в качестве примера) - использование volatile bool в качестве флага для блокирования доступа к переменным (не volatile) из других потоков. Компилятор с чистой совестью меняет местами обращения к volatile и non volatile переменным, и в итоге что-то оказывается без защиты.
А делать volatile всё - отстой и тормоза. Лечится использованием compiler fence/memory fence (для gcc можно asm volatile("" ::: "memory"); - это значит, что все операции с переменными, которые есть в коде до этого места - отработают до него, а все, что после - действительно после).
Думаю, один или два таких барьера исправят вам ситуацию не хуже отключения оптимизации - и без пачки volatile (которые опять же её ломают).
с т.з. компилятора это был доступ к регистрам, побочных эффектов он не обнаружил и решил, что раз так, то давай-ка я их переставлю (для чего - х.з, факт в том, что таки переставил). Откуда ему знать, что побочный эффект на самом деле есть? Это в самом деле Undefined behavior, ибо информация о том, что происходит ВНЕ процессора и его памяти компилятору в принципе недоступна
Вроде memory barrier как раз для таких целей, отучить все от оптимизации - компилятор, сильно умный проц, итп.
privedite uzhe kod...
esli pomechaete peremennije kak volatile, togda kompiljatov voobshe ne dolzhen ih trogatj. esli dve peremennije volatile, to obe ne budet trogatj, i perestavitj mestami on ih nikak ne mozhet.
kod priveden v tekste zapisi

kompilator ne trogal peremennie. On izemnil poyadok obrasheniya k nim. Perestavit' mozhet, ochevidno - perestavil zhe.
странно это, может изменилось чего, сейчас и компиляторы изменились, давно я под гцц не писал, кроме драйверов ядра...
насколько я знаю, volatile обязывает компилер обращать внимание на переменную, даже если обращение не имеет явных побочных эффектов - т.е. он не может ВЫКИНУТЬ его из кода. Про переставить местами.. видимо, это уже не так обязательно
в листинге нету ничего про волатайл
вот это помечено как волатайл?
DMA1_Channel1 ?

просто лучше наверное данные демаркировать, покрайней мере раньше так было,
про asm volatile я вообще первый раз слышу.
Собственно, в применении к dma - в вашем случае (dma на вывод) строго обязателен memory fence перед запуском dma. Для dma на ввод - перед чтением полученных данных.
В либах это должно бы быть из коробки...
я решил, что пользоваться готовыми либами буду лишь тогда, когда пойму, что мне экстремально лень делать всё то же самое. Например, я сначала попробовал написать свою реализацию FAT, а затем взял FatFs. И с USB так же - поковырявшись с неделю, плюнул и допилил полуготовый код.

Ну, тогда сделайте себе макрос, дёргающий asm volatile (на x86/x64 он же процессору должен пинка давать) и пользуйтесь, чтобы сообщить компилятору о том, что происходит обращение к памяти из волшебного мира.
Потом расскажите, помогло ли... Должно бы - это стандартный путь.
я параноик. Обставил теперь все критичные куски кода всякими там прагмами (там, где я не хочу, чтобы компилер менял порядок операций, а то снова решит, что не нужно перед данными команду слать, можно и потом), атрибутами функций И этими самыми "заборами".

Век живи, век учись - ни разу не нужно было, а вот теперь без них не обойтись