Page 1 of 1

STM32 Programming. Part 12: DMA Interrupts

Posted: 17 Oct 2023, 04:42
by Oleg
So, we have considered the exchange with peripherals via DMA on the example of data transfer from memory to SPI interface and back, as well as using DMA to copy one data array to another. In this part we will look at interrupts that can be generated by the DMA controller.

DMA interrupts

In the stm32f103c8, each DMA channel can generate 3 types of interrupts:

Data transfer complete
Half data transfer completed
Data transfer error (occurs when a reserved data area is accessed)
For each channel the required interrupts can be enabled individually in the DMA_CCRx channel configuration register. The bits TCIE (transfer complete), HTIE (half buffer transferred) and TEIE (transfer error) are responsible for this:
image.png
image.png (12.7 KiB)
Viewed 3549 times
In addition, there are 2 more special registers, one of which contains information about active interrupt flags of all DMA channels (DMA_ISR register), and the other one can be used to reset the desired interrupt flags of selected channels (DMA_IFCR register):
DMA_ISR Interrupt Status Register
image.png (15.42 KiB)
DMA_ISR Interrupt Status Register Viewed 3549 times
DMA_IFCR Interrupt Flag Reset Register
image.png (18.58 KiB)
DMA_IFCR Interrupt Flag Reset Register Viewed 3549 times
Using DMA interrupts

Let's move on to practice. As a basis, let's take the code from the article STM32 Programming. Part 10: SPI + DMA, where we sent an array of data to SPI via DMA. SPI initialization is done in the same way, but we will add 3 lines to the end:

Code: Select all

void SPIInit(void)
{
  RCC->APB2ENR |= RCC_APB2ENR_SPI1EN; //Enable SPI1 clocking
  RCC->APB2ENR |= RCC_APB2ENR_IOPAEN; //enable GPIOA port clocking
  RCC->AHBENR |= RCC_AHBENR_DMA1EN; //Enable DMA1 clocking
  
  
  //Customize GPIO
  
  //PA7 - MOSI
  //PA6 - MISO
  //PA5 - SCK
  //First reset all configuration bits to zeros
  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);
  
  //Customize
  //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);
  
  
  //Set SPI
  SPI1->CR1 = 0<<SPI_CR1_DFF_Pos //Frame size 8 bits
    | 0<<SPI_CR1_LSBFIRST_Pos //MSB first
    | 1<<SPI_CR1_SSM_Pos //SSS program control
    | 1<<SPI_CR1_SSI_Pos //SS in high state
    | 0x04<<SPI_CR1_BR_Pos //Baud rate: F_PCLK/32
    | 1<<SPI_CR1_MSTR_Pos //Master mode (master)
    | 0<<SPI_CR1_CPOL_Pos | 0<<SPI_CR1_CPHA_Pos; // SPI operating mode: 0
  
  
  SPI1->CR2 |= 1<<SPI_CR2_TXDMAEN_Pos;
  SPI1->CR2 |= 1<<SPI_CR2_RXDMAEN_Pos;
  SPI1->CR1 |= 1<<SPI_CR1_SPE_Pos; //Enable SPI
  
  DMA1->IFCR = 1<<DMA_IFCR_CTCIF3_Pos; //reset interrupt flag
  NVIC_EnableIRQ(DMA1_Channel3_IRQn); //enable interrupts
  //from DMA1 channel 3
}
Using the string

Code: Select all

DMA1->IFCR = 1<<DMA_IFCR_CTCIF3_Pos; //reset the interrupt flag
we reset the interrupt flag about the end of data transfer just in case. Then in the interrupt controller we enable the interrupt from DMA channel 3:

Code: Select all

NVIC_EnableIRQ(DMA1_Channel3_IRQn); //enable interrupts
//from DMA1 channel 3
Next, let's consider the function of sending data to SPI via DMA:

Code: Select all

void SPI_Send(uint8_t *data, uint16_t len)
{
  //disable the DMA channel after the previous data transfer
  DMA1_Channel3->CCR &= ~(1 << DMA_CCR_EN_Pos);
  
  DMA1_Channel3->CPAR = (uint32_t)(&SPI1->DR); //add DR register address to CPAR
  DMA1_Channel3->CMAR = (uint32_t)data; //add data address to CMAR register
  DMA1_Channel3->CNDTR = len; //number of transmitted data
  
  //DMA channel setup
  DMA1_Channel3->CCR = 0 << DMA_CCR_MEM2MEM_Pos //MEM2MEM mode disabled
    | 0x00 << DMA_CCR_PL_Pos //priority low
    | 0x00 << DMA_CCR_MSIZE_Pos //memory data size 8 bits
    | 0x01 << DMA_CCR_PSIZE_Pos //data register size 16 bits
    | 1 << DMA_CCR_MINC_Pos // Enable increment of memory address
    | 0 << DMA_CCR_PINC_Pos // Peripheral address increment disabled
    | 0 << DMA_CCR_CIRC_Pos //ring mode disabled
    | 1 << DMA_CCR_DIR_Pos //1 - from memory to periphery
    | 1 << DMA_CCR_TCIE_Pos; //Interrupt at transfer completion
  
  DMA1_Channel3->CCR |= 1 << DMA_CCR_EN_Pos; //enable data transfer
}
Here we have added the line

Code: Select all

1 << DMA_CCR_TCIE_Pos; //Interrupt on completion of transfer
with the help of which we enable the interrupt in the 3rd DMA channel after the transfer is completed.

The interrupt handler remains. It will look like this:

Code: Select all

void DMA1_Channel3_IRQHandler(void)
{
  DMA1->IFCR = 1<<DMA_IFCR_CTCIF3_Pos; //reset interrupt flag
  //add processing code
  //...
}
Notice DMA1->IFCR = 1<<DMA_IFCR_CTCIF3_Pos.
With this line we reset the interrupt flag about the end of data transfer in DMA channel 3. Without this line, the DMA1_Channel3_IRQHandler() interrupt will be called an infinite number of times, which will stall the execution of the main program of the MCU.

To check this main():

Code: Select all

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(;;)
  {
  }
}
That's all for now, thanks for your attention!