STM32 Programming. Part 12: DMA Interrupts
Posted: 17 Oct 2023, 04:42
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:
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:
Using the string
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:
Next, let's consider the function of sending data to SPI via DMA:
Here we have added the line
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:
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():
That's all for now, thanks for your attention!
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 flag
Code: Select all
NVIC_EnableIRQ(DMA1_Channel3_IRQn); //enable interrupts
//from DMA1 channel 3
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
}
Code: Select all
1 << DMA_CCR_TCIE_Pos; //Interrupt on completion of transfer
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
//...
}
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(;;)
{
}
}