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:
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):
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
}Code: Select all
DMA1->IFCR = 1<<DMA_IFCR_CTCIF3_Pos; //reset the interrupt flagCode: Select all
NVIC_EnableIRQ(DMA1_Channel3_IRQn); //enable interrupts
//from DMA1 channel 3Code: 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
}Code: Select all
1 << DMA_CCR_TCIE_Pos; //Interrupt on completion of transferThe 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
  //...
}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(;;)
  {
  }
}