Итак, мы рассмотрели обмен с периферийными устройствами посредством DMA на примере передачи данных из памяти в интерфейс SPI и обратно, а так же с помощью DMA копировали один массив данных в другой. В этой части мы рассмотрим прерывания, которые может генерировать контроллер DMA. Предыдущая статья здесь, все статьи цикла можно посмотреть тут: https://dimoon.ru/category/obuchalka/stm32f1.
Прерывания DMA
В stm32f103c8 каждый канал DMA может генерировать 3 типа прерываний:
- Передача данных завершена
- Завершена передача половины данных
- Ошибка передачи данных (возникает при обращении к зарезервированной области данных)
Для каждого канала нужные прерывания можно включить индивидуально в регистре конфигурации канала DMA_CCRx. За это отвечают биты TCIE (завершение передачи), HTIE ( передана половина буфера) и TEIE (ошибка передачи):
Кроме того, есть еще 2 специальных регистра, один из которых содержит информацию об активных флагах прерываний всех каналов DMA (регистр DMA_ISR), а с помощью другого можно сбросить нужные флаги прерываний выбранных каналов (регистр DMA_IFCR):
Регистр статуса прерываний DMA_ISR
Регистр сброса флагов прерываний DMA_IFCR
Более подробное описание всех регистров DMA можно посмотреть тут.
Использование прерываний DMA
Переходим к практике. В качестве основы возьмем код из статьи Программирование STM32. Часть 10: SPI + DMA, где мы через DMA отправляли массив данных в SPI. Инициализация SPI производится точно так же, только добавим в конец 3 строчки:
void SPIInit(void) { RCC->APB2ENR |= RCC_APB2ENR_SPI1EN; //Включаем тактирование SPI1 RCC->APB2ENR |= RCC_APB2ENR_IOPAEN; //включаем тактирование порта GPIOA RCC->AHBENR |= RCC_AHBENR_DMA1EN; //Включаем тактирование DMA1 //Настройка GPIO //PA7 - MOSI //PA6 - MISO //PA5 - SCK //Для начала сбрасываем все конфигурационные биты в нули GPIOA->CRL &= ~(GPIO_CRL_CNF5_Msk | GPIO_CRL_MODE5_Msk | GPIO_CRL_CNF6_Msk | GPIO_CRL_MODE6_Msk | GPIO_CRL_CNF7_Msk | GPIO_CRL_MODE7_Msk); //Настраиваем //SCK: MODE5 = 0x03 (11b); CNF5 = 0x02 (10b) GPIOA->CRL |= (0x02<<GPIO_CRL_CNF5_Pos) | (0x03<<GPIO_CRL_MODE5_Pos); //MISO: MODE6 = 0x00 (00b); CNF6 = 0x01 (01b) GPIOA->CRL |= (0x01<<GPIO_CRL_CNF6_Pos) | (0x00<<GPIO_CRL_MODE6_Pos); //MOSI: MODE7 = 0x03 (11b); CNF7 = 0x02 (10b) GPIOA->CRL |= (0x02<<GPIO_CRL_CNF7_Pos) | (0x03<<GPIO_CRL_MODE7_Pos); //Настройка SPI SPI1->CR1 = 0<<SPI_CR1_DFF_Pos //Размер кадра 8 бит | 0<<SPI_CR1_LSBFIRST_Pos //MSB first | 1<<SPI_CR1_SSM_Pos //Программное управление SS | 1<<SPI_CR1_SSI_Pos //SS в высоком состоянии | 0x04<<SPI_CR1_BR_Pos //Скорость передачи: F_PCLK/32 | 1<<SPI_CR1_MSTR_Pos //Режим Master (ведущий) | 0<<SPI_CR1_CPOL_Pos | 0<<SPI_CR1_CPHA_Pos; //Режим работы SPI: 0 SPI1->CR2 |= 1<<SPI_CR2_TXDMAEN_Pos; SPI1->CR2 |= 1<<SPI_CR2_RXDMAEN_Pos; SPI1->CR1 |= 1<<SPI_CR1_SPE_Pos; //Включаем SPI DMA1->IFCR = 1<<DMA_IFCR_CTCIF3_Pos; //сбрасываем флаг прерывания NVIC_EnableIRQ(DMA1_Channel3_IRQn); //разрешаем прерывания //от канала 3 DMA1 }
С помощью строки
DMA1->IFCR = 1<<DMA_IFCR_CTCIF3_Pos; //сбрасываем флаг прерывания
мы на всякий случай сбрасываем флаг прерывания о завершении передачи данных. Затем в контроллере прерываний разрешаем прерывание от 3-го канала DMA:
NVIC_EnableIRQ(DMA1_Channel3_IRQn); //разрешаем прерывания //от канала 3 DMA1
Далее, рассмотрим функцию отправки данных в SPI через DMA:
void SPI_Send(uint8_t *data, uint16_t len) { //отключаем канал DMA после предыдущей передачи данных DMA1_Channel3->CCR &= ~(1 << DMA_CCR_EN_Pos); DMA1_Channel3->CPAR = (uint32_t)(&SPI1->DR); //заносим адрес регистра DR в CPAR DMA1_Channel3->CMAR = (uint32_t)data; //заносим адрес данных в регистр CMAR DMA1_Channel3->CNDTR = len; //количество передаваемых данных //Настройка канала DMA DMA1_Channel3->CCR = 0 << DMA_CCR_MEM2MEM_Pos //режим MEM2MEM отключен | 0x00 << DMA_CCR_PL_Pos //приоритет низкий | 0x00 << DMA_CCR_MSIZE_Pos //разрядность данных в памяти 8 бит | 0x01 << DMA_CCR_PSIZE_Pos //разрядность регистра данных 16 бит | 1 << DMA_CCR_MINC_Pos //Включить инкремент адреса памяти | 0 << DMA_CCR_PINC_Pos //Инкремент адреса периферии отключен | 0 << DMA_CCR_CIRC_Pos //кольцевой режим отключен | 1 << DMA_CCR_DIR_Pos //1 - из памяти в периферию | 1 << DMA_CCR_TCIE_Pos; //Прерывание при завершении передачи DMA1_Channel3->CCR |= 1 << DMA_CCR_EN_Pos; //включаем передачу данных }
Здесь мы добавили строчку
1 << DMA_CCR_TCIE_Pos; //Прерывание при завершении передачи
с помощью которой включаем прерывание в 3-м канале DMA после завершения передачи.
Остался обработчик прерывания. Он будет выглядеть вот так:
void DMA1_Channel3_IRQHandler(void) { DMA1->IFCR = 1<<DMA_IFCR_CTCIF3_Pos; //сбрасываем флаг прерывания //добавить код обработки //... }
Обращаю внимание на DMA1->IFCR = 1<<DMA_IFCR_CTCIF3_Pos. С помощью этой строки мы сбрасываем флаг прерывания о завершении передачи данных в 3-м канале DMA. Без этой строки прерывание DMA1_Channel3_IRQHandler() будет вызываться бесконечное число раз, что застопорит выполнение основной программы МК.
Для проверки вот такой main():
uint8_t data[10]; void main() { for(int i=0; i<sizeof(data); i++) { data[i] = i+1; } SPIInit(); SPI_Send(data, sizeof(data)); for(;;) { } }
На этом пока все, спасибо за внимание!!! 😉
Зачем эти сдвиги:
DMA1_Channel3->CCR &= ~(1 <CCR &= ~DMA_CCR_EN;