В данном уроке мы попытаемся поработать с периферией DMA контроллера STM32F1. Мы попробуем скопировать некоторое количество информации из одной области памяти в другую (MEM2MEM).
Подобную процедуру мы с вами уже проводили в уроке 155 и уроке 156. Только в данных уроках мы использовали для этого библиотеки HAL и LL. А в данном уроке ту же самую процедуру мы проделаем с применением уже возможностей другой библиотеки — CMSIS.
Периферия DMA нами в предыдущих занятиях уже полностью изучена на аппаратном уровне, причём мы изучили и инициализацию данной периферии, хотя сами мы её не писали, нам в этом любезно помогал проектогенератор Cube MX. Тем не менее нам знаком порядок инициализации, а также порядок работы с периферией. Поэтому, в принципе, мы можем сразу же приступить к проекту.
Прежде чем приступим непосредственно к проекту, скажу ещё, что схему мы оставим без изменений и возьмём схему прошлого урока, например, для устройства SLAVE, хотя вполне можно взять и для MASTER, так как они абсолютно одинаковые. Логический анализатор сегодня не используем
Проект сделан из будет из проекта прошлого урока для ведущего устройства с именем CMSIS_SPI_MASTER и назван был CMSIS_DMA_MEM2MEM.
Откроем проект в Keil и из файла main.c удалим пока вот эти глобальные макросы, касающиеся модуля SPI1
#define CS1_RESET() SET_BIT(GPIOA->ODR,GPIO_ODR_ODR4)
#define CS1_SET() CLEAR_BIT(GPIOA->ODR,GPIO_ODR_ODR4)
…
#define SPI1_ENABLE() SET_BIT(SPI1->CR1, SPI_CR1_SPE);
Функцию SPI1_Init также удалим вместе с телом.
В функции main() удалим вот эти строки
SPI1_Init();
…
CS1_RESET();
…
SPI1_ENABLE();
…
Number_7219(87654321);
delay_ms(2000);
Также удалим всё из бесконечного цикла и удалим объявление локальной переменной r
uint16_t i, r;
Выше функции SPI2_Init добавим функцию инициализации периферии DMA1
1 2 3 4 5 |
//---------------------------------------------------------- static void DMA1_Init(void) { } //---------------------------------------------------------- |
Добавим в тело данной функции вот этот код
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
static void DMA1_Init(void) { //DMA controller clock enable SET_BIT(RCC->AHBENR, RCC_AHBENR_DMA1EN); tmpreg = READ_BIT(RCC->AHBENR, RCC_AHBENR_DMA1EN); (void)tmpreg; //Set transfer direction MODIFY_REG(DMA1_Channel1->CCR, DMA_CCR1_DIR, DMA_CCR1_MEM2MEM); //Set priority level CLEAR_BIT(DMA1_Channel1->CCR, DMA_CCR1_PL); //Set DMA mode CLEAR_BIT(DMA1_Channel1->CCR, DMA_CCR1_CIRC); //Set peripheral increment mode SET_BIT(DMA1_Channel1->CCR, DMA_CCR1_PINC); //Set memory increment mode SET_BIT(DMA1_Channel1->CCR, DMA_CCR1_MINC); //Set peripheral data width MODIFY_REG(DMA1_Channel1->CCR, DMA_CCR1_PSIZE_1, DMA_CCR1_PSIZE_0); //Set memory data width MODIFY_REG(DMA1_Channel1->CCR, DMA_CCR1_MSIZE_1, DMA_CCR1_MSIZE_0); NVIC_EnableIRQ(DMA1_Channel1_IRQn); |
Думаю, здесь объяснять особо нечего, так как те же самые действия проводятся и в LL при автогенерации, тем не менее кратко поясню.
Сначала мы настроили направление передачи — из памяти в память посредством включения бита CCR1_MEM2MEM, затем выбрали приоритет, режим DMA, также настроили инкрементирование адреса и источника и приёмника, ширину данных (16 бит) и для приёмника и для источника, а также включили глобальные прерывания от модуля DMA1.
Вызовем данную функцию в main()
1 2 |
GPIO_Init(); DMA1_Init(); |
Добавим два буфера, один для передачи в периферию, другой для приёма из неё
1 2 3 |
__IO uint32_t SysTick_CNT = 0; uint16_t src_dma_buf[1024] = {0}; uint16_t dst_dma_buf[1024] = {0}; |
В функции main() заполним буфер передачи декрементированными значениями от 1024 до 1
1 2 3 4 5 6 |
Clear_7219(); //fill src buffer for(i=0;i<1024;i++) { src_dma_buf[i] = 1024-i; } |
Далее добавим вот такой код
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
src_dma_buf[i] = 1024-i; } //Disable DMA channel CLEAR_BIT(DMA1_Channel1->CCR, DMA_CCR1_EN); //Clear Channel 1 transfer complete flag WRITE_REG(DMA1->IFCR, DMA_IFCR_CTCIF1); //Clear Channel 1 transfer error flag WRITE_REG(DMA1->IFCR, DMA_IFCR_CTEIF1); //Set Number of data to transfer MODIFY_REG(DMA1_Channel1->CNDTR, DMA_CNDTR1_NDT, 1024); //Configure the Source and Destination addresses WRITE_REG(DMA1_Channel1->CPAR, (uint32_t)&src_dma_buf); WRITE_REG(DMA1_Channel1->CMAR, (uint32_t)&dst_dma_buf); //Enable Transfer complete interrupt SET_BIT(DMA1_Channel1->CCR, DMA_CCR1_TCIE); //Enable Transfer error interrupt SET_BIT(DMA1_Channel1->CCR, DMA_CCR1_TEIE); //Enable DMA channel SET_BIT(DMA1_Channel1->CCR, DMA_CCR1_EN); |
То же самое мы делали и когда работали с LL, только сейчас это всё переписано в свете требований CMSIS. Сначала мы отключили канал 1 канал DMA1, затем очистили флаги окончания передачи и ошибки, также занесли в регистр CNDTR1_NDT количество передаваемых данных — у нас будет 1024 полуслова. Затем мы занесли адрес приёмника и источника в соответствующие регистры канала 1, потом включили прерывания по событию окончания передачи пакета данных и по возникновению ошибки, и в конце включили канал, что и является сигналом к началу передачи по шине DMA1.
Добавим глобальную переменную для флага окончания передачи
1 2 |
uint16_t dst_dma_buf[1024] = {0}; uint8_t fl=0; |
В самом низу файла добавим функцию-обработчик прерываний от DMA1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
//---------------------------------------------------------- void DMA1_Channel1_IRQHandler(void) { if(READ_BIT(DMA1->ISR, DMA_ISR_TCIF1) == (DMA_ISR_TCIF1)) { //Clear Channel 1 global interrupt flag WRITE_REG(DMA1->IFCR, DMA_IFCR_CGIF1); fl = 1; } else if(READ_BIT(DMA1->ISR, DMA_ISR_TEIF1) == (DMA_ISR_TEIF1)) { __NOP(); } } //---------------------------------------------------------- |
Думаю, что здесь тоже всё ясно.
Если у нас произошло прерывание, мы попадаем в данную функцию. Если в этот момент взведен флаг полного окончания передачи, то мы его очищаем и устанавливаем наш пользовательский флаг fl, а если прочитался флаг ошибки, то можно предпринять какие-то действия по выявлению данной ошибки, но мы ничего не будем предпринимать, мы лишь вставим здесь инструкцию nop. И если у нас всё же что-то пойдёт не так, то мы сюда поставим точку останова и узнаем то, что мы попали именно сюда, а дальше уж будем разбираться, что у нас за ошибка.
Вернёмся в функцию main(), подождём установки нашего пользовательского флага, сбросим его и отобразим по очереди элементы приёмного буфера на индикаторе. Если через DMA1 передача пройдёт нормально, то мы увидим убавляющиеся числа от 1024 до 1
1 2 3 4 5 6 7 8 |
SET_BIT(DMA1_Channel1->CCR, DMA_CCR1_EN); while (!fl) {} fl=0; for(i=0;i<1024;i++) { NumberR_7219(dst_dma_buf[i]); delay_ms(50); } |
Соберём проект прошьём контроллер и посмотрим, как убавляются числа на нашем индикаторе
Если немного подождать, то счётчик наш досчитает в обратную сторону до 1.
Итак, на данном занятии мы научились управлять периферией DMA в контроллере STM32F1 в режиме MEM2MEM (передачи из памяти в память), воспользовавшись при этом функционалом библиотеки CMSIS.
Всем спасибо за внимание!
Предыдущий урок Программирование МК STM32 Следующий урок
Отладочную плату STM32F103C8T6 можно приобрести здесь STM32F103C8T6
Программатор недорогой можно купить здесь ST-Link V2
Индикатор светодиодный семиразрядный с драйвером MAX7219
Смотреть ВИДЕОУРОК (нажмите на картинку)
Добавить комментарий