Итак, мы рассмотрели обмен с периферийными устройствами посредством 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;