Программирование STM32. Часть 12: Прерывания DMA

Итак, мы рассмотрели обмен с периферийными устройствами посредством DMA на примере передачи данных из памяти в интерфейс SPI и обратно, а так же с помощью DMA копировали один массив данных в другой. В этой части мы рассмотрим прерывания, которые может генерировать контроллер DMA. Предыдущая статья здесь, все статьи цикла можно посмотреть тут: https://dimoon.ru/category/obuchalka/stm32f1.

Прерывания DMA

В stm32f103c8 каждый канал DMA может генерировать 3 типа прерываний:

  1. Передача данных завершена
  2. Завершена передача половины данных
  3. Ошибка передачи данных (возникает при обращении к зарезервированной области данных)

Для каждого канала нужные прерывания можно включить индивидуально в регистре конфигурации канала DMA_CCRx. За это отвечают биты TCIE (завершение передачи), HTIE ( передана половина буфера) и TEIE (ошибка передачи):

DMA channel x configuration register (DMA_CCRx)

 

Кроме того, есть еще 2 специальных регистра, один из которых содержит информацию об активных флагах прерываний всех каналов DMA (регистр DMA_ISR), а с помощью другого можно сбросить нужные флаги прерываний выбранных каналов (регистр DMA_IFCR):

Регистр статуса прерываний DMA_ISR

DMA interrupt flag clear register (DMA_IFCR)

Регистр сброса флагов прерываний 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(;;)
  {
  }
}

На этом пока все, спасибо за внимание!!! 😉

Продолжение

Метки: , , , . Закладка Постоянная ссылка.

1 комментарий: Программирование STM32. Часть 12: Прерывания DMA

  1. Лео пишет:

    Зачем эти сдвиги:
    DMA1_Channel3->CCR &= ~(1 <CCR &= ~DMA_CCR_EN;

Добавить комментарий