What is the difference between these features on STM32G0 "Scan Conversion mode" and "Continuous conversion mode" and "DMA continuous request" features?
When you go on CubeIDE you will see these features on ADC configuration.
Electrical – DMA Continuous Request functionality
dmastm32g
Related Solutions
How will the DMAC know when to wrap around when it reaches the end of the array? -- i.e. how can I ensure that the DMA won't transfer data past the last array index? (Is this size limit defined by the DMA_BufferSize?)
Yes, it will either stop at the limit you set, or if you enable circular mode, it will start over and overwrite data at the start of the array. It will never go past the limit.
I would read & erase the data out of one array whilst the others are being filled by the DMA, and it would cycle around. Is it smart and/or possible to change the DMA destination constantly like this?
On the STM32, you can allocate one big array, and use the two halves as double buffer. There is a half-transfer interrupt (when enabled) which tells you that the first half is filled and ready for processing, and the second half is being modified. Then you'll get a transfer complete interrupt when the second half is ready and (in circular mode) it goes on updating the first half again.
So, there is no need to change the DMA destination (you can't do it anyway while DMA is running), but you'll always be able to tell which half is ready by examining the DMA status bits.
On higher-end controllers (STM32F4, STM32F7), there are actually two memory addresses for each DMA channel, the buffers halves do not need to be contiguous, and you can even change the address of the inactive buffer on the fly.
So, I've actually managed to get the SPI DMA running. Posting my working code below:
#include "stm32h7xx.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static void InitializeMCO(void);
static void ConfigureHSI(void);
static void InitializeDMA(void);
static void InitializeMasterTxSPI(void);
const uint8_t aTxBuffer[] = "Simple SPI message";
int main()
{
ConfigureHSI();
InitializeMCO();
InitializeDMA();
InitializeMasterTxSPI();
while (1)
{
/* Delay added to distinguish between the SPI messages: */
while(DMA2_Stream4->NDTR != 0) asm("nop");
for(uint32_t i=0; i<0xBF; i++) asm("nop");
//DMA2_Stream4->CR &= ~DMA_SxCR_EN;
DMA2->HIFCR |= DMA_HIFCR_CTCIF4 | DMA_HIFCR_CHTIF4 | DMA_HIFCR_CTEIF4 | DMA_HIFCR_CDMEIF4 | DMA_HIFCR_CFEIF4;
//DMA2_Stream4->PAR = (uint32_t) &(SPI1->TXDR);
DMA2_Stream4->M0AR = (uint32_t ) &(aTxBuffer[0]);
DMA2_Stream4->NDTR = 0x12;
DMA2_Stream4->CR |= DMA_SxCR_EN;
};
}
/* Initializes the MCU clock */
static void ConfigureHSI(void)
{
PWR->CR3 |= PWR_CR3_SCUEN;
PWR->D3CR |= (PWR_D3CR_VOS_1 | PWR_D3CR_VOS_0);
while ((PWR->D3CR & PWR_D3CR_VOSRDY) != PWR_D3CR_VOSRDY)
{
};
FLASH->ACR = FLASH_ACR_LATENCY_2WS;
RCC->CR |= RCC_CR_HSION;
while ((RCC->CR & RCC_CR_HSIRDY) != RCC_CR_HSIRDY)
{
};
RCC->PLLCKSELR = (4u << RCC_PLLCKSELR_DIVM1_Pos) |
(32u << RCC_PLLCKSELR_DIVM2_Pos) |
(32u << RCC_PLLCKSELR_DIVM3_Pos) |
RCC_PLLCKSELR_PLLSRC_HSI;
RCC->PLLCFGR = RCC_PLLCFGR_DIVR1EN |
RCC_PLLCFGR_DIVQ1EN |
RCC_PLLCFGR_DIVP1EN |
(2u << RCC_PLLCFGR_PLL1RGE_Pos) |
(1u << RCC_PLLCFGR_PLL1VCOSEL_Pos);
RCC->PLL1DIVR = ((2u - 1u) << RCC_PLL1DIVR_R1_Pos) |
((2u - 1u) << RCC_PLL1DIVR_Q1_Pos) |
((2u - 1u) << RCC_PLL1DIVR_P1_Pos) |
((10u - 1u) << RCC_PLL1DIVR_N1_Pos) // Reducing the clock rate so I can probe it with my slow USB scope
;
RCC->D1CFGR = RCC_D1CFGR_D1CPRE_DIV1;
RCC->D1CFGR = RCC_D1CFGR_HPRE_DIV2 | RCC_D1CFGR_D1PPRE_DIV2;
RCC->D2CFGR = RCC_D2CFGR_D2PPRE1_DIV2 | RCC_D2CFGR_D2PPRE2_DIV2;
RCC->D3CFGR = RCC_D3CFGR_D3PPRE_DIV2;
RCC->CR |= RCC_CR_PLL1ON;
while (!(RCC->CR & RCC_CR_PLLRDY))
{
};
RCC->CFGR |= (1u << 25);
RCC->CFGR |= RCC_CFGR_SW_PLL1;
while (!(RCC->CFGR & RCC_CFGR_SWS_PLL1))
{
};
}
/* Displays MCO on PC9 */
static void InitializeMCO(void)
{
RCC->CFGR |= RCC_CFGR_MCO2;
RCC->CFGR |= (15 << 25); // Reducing the output so I can probe it with my slow USB scope
RCC->AHB4ENR &= ~RCC_AHB4ENR_GPIOCEN;
RCC->AHB4ENR |= RCC_AHB4ENR_GPIOCEN;
GPIOC->MODER &= ~GPIO_MODER_MODER9;
GPIOC->MODER |= GPIO_MODER_MODER9_1;
GPIOC->OTYPER &= ~GPIO_OTYPER_OT_9;
GPIOC->PUPDR &= ~GPIO_PUPDR_PUPDR9;
GPIOC->OSPEEDR &= ~GPIO_OSPEEDER_OSPEEDR9;
GPIOC->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR9;
GPIOC->AFR[0] &= ~GPIO_AFRL_AFRL0;
}
static void InitializeDMA()
{
RCC->AHB2ENR |= (RCC_AHB2ENR_D2SRAM1EN | RCC_AHB2ENR_D2SRAM2EN | RCC_AHB2ENR_D2SRAM3EN); // Enable the SRAM
RCC->AHB1ENR |= RCC_AHB1ENR_DMA2EN; // DMA2 clock enable;
// Set the peripheral and memory addresses:
DMA2_Stream4->PAR = (uint32_t) &(SPI1->TXDR);
DMA2_Stream4->M0AR = (uint32_t ) &(aTxBuffer[0]);
DMA2_Stream4->CR = 0;
DMA2_Stream4->CR |= (1u << DMA_SxCR_DIR_Pos); // Memory to peripheral
DMA2_Stream4->CR |= DMA_SxCR_MINC; // Memory increment mode
DMA2_Stream4->CR |= (3u << DMA_SxCR_PL_Pos); // Very high priority
DMA2_Stream4->NDTR = 0x12; //DMA transfer length
DMA2_Stream4->CR |= DMA_SxCR_EN; // Enable DMA stream
DMAMUX1_Channel12->CCR = 0x26;
}
static void InitializeMasterTxSPI(void)
{
RCC->AHB4ENR |= RCC_AHB4ENR_GPIOAEN; // Enable usage of GPIOA
GPIOA->MODER &= ~GPIO_MODER_MODER5;
GPIOA->MODER |= GPIO_MODER_MODER5_1; // Alternate function for SPI1 SCK on PA5
GPIOA->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR5; // High Speed on PA5
GPIOA->AFR[0] |= (0x05 << 5 * 4); // AFRL selected AF5 (SPI1 SCK) for PA5
GPIOA->MODER &= ~GPIO_MODER_MODER6;
GPIOA->MODER |= GPIO_MODER_MODER6_1; // Alternate function for SPI1 MISO on PA6
GPIOA->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR6; // High Speed on PA6
GPIOA->AFR[0] |= (0x05 << 6 * 4); // AFRL selected AF5 (SPI1 MISO) for PA6
GPIOA->MODER &= ~GPIO_MODER_MODER7;
GPIOA->MODER |= GPIO_MODER_MODER7_1; // Alternate function for SPI1 MOSI on PA7
GPIOA->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR7; // High Speed on PA7
GPIOA->AFR[0] |= (0x05 << 7 * 4); // AFRL selected AF5 (SPI1 MOSI) for PA7
GPIOA->MODER &= ~GPIO_MODER_MODER4;
GPIOA->MODER |= GPIO_MODER_MODER4_1; // Alternate function for SPI1 NSS on PA4
GPIOA->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR4; // High Speed on PA4
GPIOA->AFR[0] |= (0x05 << 4 * 4); // AFRL selected AF5 (SPI1 NSS) for PA4
GPIOA->PUPDR |= GPIO_PUPDR_PUPDR4; // Ensure all pull up pull down resistors are enabled
GPIOA->PUPDR &= ~GPIO_PUPDR_PUPDR5; // Ensure all pull up pull down resistors are disabled
GPIOA->PUPDR &= ~GPIO_PUPDR_PUPDR6; // Ensure all pull up pull down resistors are disabled
GPIOA->PUPDR &= ~GPIO_PUPDR_PUPDR7; // Ensure all pull up pull down resistors are disabled
RCC->APB2ENR |= RCC_APB2ENR_SPI1EN;
SPI1->CR1 = SPI_CR1_SSI;
SPI1->CFG1 = (2u << SPI_CFG1_MBR_Pos) |
(7u << SPI_CFG1_CRCSIZE_Pos) |
SPI_CFG1_TXDMAEN | // SPI_CFG1_RXDMAEN |
(7u << SPI_CFG1_FTHLV_Pos) |
(7u << SPI_CFG1_DSIZE_Pos);
SPI1->CFG2 = SPI_CFG2_SSM | SPI_CFG2_MASTER;
SPI1->CR1 |= SPI_CR1_SPE;
SPI1->CR1 |= SPI_CR1_CSTART;
}
Now the basic functionality of the DMAMUX is not too hard, all things considered. The manual states that:
- DMAMUX1 channels 0 to 7 are connected to DMA1 channels 0 to 7
- DMAMUX1 channels 8 to 15 are connected to DMA2 channels 0 to 7
- DMAMUX2 channels 0 to 7 are connected to BDMA channels 0 to 7
These, along with the assignment of multiplexer inputs to resources tables, are the keys to get DMA running (at least running the same way as in the older series of MCUS). For example, SPI1_TX is on the 38th DMA request MUX input of DMAMUX1 (see table 110 in the reference manual). This means that I can employ either DMA1 or DMA2 (and not BDMA, as it is linked to DMAMUX2). I can choose any stream I want, they only need to follow the rule:
- DMA1_Stream_x -> DMAMUX1_Channel_x
- DMA2_Stream_x -> DMAMUX1_Channel_(x+8)
So this is how you essentially link a peripheral to a DMA stream via a particular channel of the DMAMUX.
A couple of thing to note as well:
- Do not forget to set the
SPI_CR1_CSTART
bit (this is a new thing for the H7's). - Be careful with the
SPI->CR2
register. If you write a value to it, the SPI transfer will stop after the predefined number of data transfers has commenced. An infinite loop, as presented in my example, will not work if CR2 is set (we will only get a single full SPI transfer).
While the entire thing seem a bit obvious to me now, I will still say that the information in the reference is somewhat lacking. The DMA operation in the older series manuals was described slightly better (at least in my opinion). For example, I still do not know how (and when) to utilize the remaining functionality of the DMAMUX (like the request generators and whatnot). Also, I am not quite sure how memort-to-memory transfers are implemented (I will probably have to learn that when propertime comes).
I hope this helps anyone who wants to delve deeper into ARM programming.
Cheers.
Best Answer
I haven't checked the output of the CubeIDE if you select that option, but I think that they are referring to the feature which is described on page 325 of the reference manual (Section 14.5.5 Managing converted data using the DMA.
Basically the ADC can run in two different modes when coupled to the DMA. It can run in a one shot mode, which will stop the ADC as soon as all transfers which were configured in the DMA are finished. So you set the DMA transfer count to 10 and the ADC will stop after 10 samples during the 11th conversion.
The second mode is the circular mode. In this mode the ADC will continuously generate DMA request even if the last DMA transfer is done. This is because you can setup the DMA to work in a circular way resetting to the first position after the last transfer and start again automatically. This generates a ring buffer of continuously updating ADC samples.
The corresponding
DMACFG
bit is located in theADC_CFGR1
register.