STM32F407 learning DMA double buffer mode HAL library implementation

The first training of this semester requires the use of a single-chip microcomputer to achieve a single-step variable-frequency sine wave output from 1 to 40KHz. At the beginning, this dish was used to change the output wave table frequency and modify the timing by modifying the timer prescaler coefficient and automatic reload value . There are two methods of device + DDS , but the effect is not ideal, not only the waveform is not good-looking, but the frequency of 37KHz is not accurate. After checking the information and discussing with friends, I finally decided to try to use DMA double buffering + DDS to realize the waveform generator. The effect is not only good-looking waveform, but also relatively accurate frequency.

There is not much information about stm32's DMA double buffering implementation on the Internet. Here I write down the two methods of my own implementation. On the one hand, I can find it quickly when I use it in the future. On the other hand, I hope that I can help and want to use double buffering. netizen.

(Thank you in advance for the passionate help of Brother Han, Guozi and other friends (*^▽^*) )

The two methods are as follows:

1. DMA half-full and full-full interrupt implementation

@Yogurt

The first is configuration, configuring the DMA of the DAC.

Here I tied the DMA to TIM4, you can change it according to your needs.

Here, in order to output a higher frequency, the clock frequency is pulled to the maximum.

 PS:

Let me explain here that when it comes to interrupts, it is best to set the DMA interrupt priority of the DAC a little higher, so as to avoid any strange problems.

Next is the code, the code is relatively simple, mainly using the two callback functions that come with the HAL library in the dma interrupt:

void HAL_DAC_ConvHalfCpltCallbackCh1(DAC_HandleTypeDef *hdac); //half full interrupt callback

void HAL_DAC_ConvCpltCallbackCh1(DAC_HandleTypeDef *hdac); //full full interrupt callback

Half-full, as the name implies, if you use the statement HAL_DAC_Start_DMA(&hdac, DAC1_CHANNEL_1, (uint32_t*)dacBuf, 4000, DAC_ALIGN_12B_R); then when the DMA transfers data to 4000/2=2000 which is half the length of dacBuf, it will enter a half-full Interrupt callback, when the transmission data reaches the 4000th, that is, the entire transmission is completed, it will enter the full-full interrupt callback.

The two callback functions are weak functions, you need to rewrite them yourself, and then write your own code such as DDS into them. These two callback functions are inside the HAL_DAC_Start_DMA function, you can find them yourself.

 Here is a reminder: once HAL_DAC_Start_DMA is called, the half-full, full-full, and error interrupts of the DMA will be automatically enabled. When you enable DMA without interrupting, it is best to stop it manually, just like this in the it.c file below Comment it out (the author does not know what problems will be caused by directly commenting like this, if anyone in the know, please contact the author to correct it).

The approximate code flow is as follows:

1. Main function

int main(void)
{
  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_USART1_UART_Init();
  MX_DMA_Init();
  MX_DAC_Init();
  MX_TIM3_Init();
  MX_TIM4_Init();
  MX_TIM6_Init();
  MX_USART2_UART_Init();
  /* USER CODE BEGIN 2 */
//  delay_init(168);

  HAL_TIM_Base_Start(&htim4);  
  HAL_DAC_Start_DMA(&hdac, DAC1_CHANNEL_1, (uint32_t*)dacBuf,  SINN, DAC_ALIGN_12B_R);  //¿ªÆôdac1£¬Êä³öÕýÏÒ²¨
  
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

That's right, only HAL_TIM_Base_Start(&htim4);  
  HAL_DAC_Start_DMA(&hdac, DAC1_CHANNEL_1, (uint32_t*)dacBuf, SINN, DAC_ALIGN_12B_R); two sentences are needed in the main function.

It should be noted here that the DMA initialization needs to be placed before the DAC initialization, otherwise there will be problems.

2. Own half-full and full-full interrupts

#define   SINN   4000

double    scrb=0.5;   //幅度参数
uint16_t  ftw=10;     //步进
uint16_t  oudress;    //地址
uint16_t  sinI;       //输出正弦波数组索引

void HAL_DAC_ConvHalfCpltCallbackCh1(DAC_HandleTypeDef *hdac)  //半满中断回调
{
  u16 i=0;
  
  for(i=0; i<2000; i++)  //在半满回调中更改前半个周期的数据
  {
    dacBuf[sinI] = (sinBuf[oudress]-1862)*scrb+2062;     //循环里边的是DDS
    sinI++;                                              //大家可以自己去了解DDS原理及应用

    oudress+=ftw;
    if(oudress>4000 || oudress==4000) oudress%=4000;
    if(sinI==4000) sinI=0;
  }
}

void HAL_DAC_ConvCpltCallbackCh1(DAC_HandleTypeDef *hdac)  //全满中断回调
{
  u16 i=0;
  
  for(i=0; i<2000; i++)  //在全满回调中更改后半个周期的数据
  {
    dacBuf[sinI] = (sinBuf[oudress]-1862)*scrb+2062;
    sinI++;

    oudress+=ftw;
    if(oudress>4000 || oudress==4000) oudress%=4000;
    if(sinI==4000) sinI=0;
  }
}

 To change the frequency, the author modifies the frequency conversion by modifying the value of the step ftw in the timer interrupt after a certain period of time.

The above is the first method to achieve DMA double buffering, which is relatively simple.

2. Relatively authentic double buffering

@果子

The configuration of the cube in this method is the same as the first one, just configure the DMA and timer.

I won’t talk too much about the principles here, but just talk about the implementation methods. The author will put the links to the articles of the big guys who I think are good about the relevant principles. You can read them if you need them.

1. Main function

int main(void)
{
  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_USART1_UART_Init();
  MX_DMA_Init();
  MX_DAC_Init();
  MX_TIM2_Init();
  MX_TIM3_Init();
  /* USER CODE BEGIN 2 */

  __HAL_DAC_ENABLE(&hdac, DAC_CHANNEL_1);  //提前使能DAC外设
	HAL_NVIC_SetPriority(DMA1_Stream5_IRQn,2,0);
	HAL_NVIC_EnableIRQ(DMA1_Stream5_IRQn);
	MY_Buffer_Init();     //初始化两个缓冲区的数据
  
  HAL_DAC_Start_DMA(&hdac,DAC_CHANNEL_2,(uint32_t *) MY_M0_Buffer,50,DAC_ALIGN_12B_R);  //开启DAC
  HAL_DMAEx_MultiBufferStart_IT(&hdma_dac2, (u32)&DAC1->DHR12R1, (u32)Pm0, (u32)Pm1, 100);    //开启DMA双缓冲模式中断
  HAL_TIM_Base_Start(&htim2);
  
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
    LED1_T;
    HAL_Delay(1000);
    LED0_T;
  }
  /* USER CODE END 3 */
}

2. Some definitions

/* USER CODE BEGIN 0 */

void MY_Buffer_Init(void);   //初始化两个缓冲区的数据
volatile float MY_U=3.3;     //用volatile是防止寄存器优化使程序出问题

uint16_t   outadress=0;  //地址
uint16_t   sinBuf[4000]={2047,2050,2053,2057,2060,2063,.....}; //随便一个正弦波表,这里太长就不放了
uint16_t   MY_M0_Buffer[50];     //DMA第一个缓冲区
uint16_t   MY_M1_Buffer[50];     //DMA第二个缓冲区
uint16_t   *Pm0=MY_M0_Buffer, *Pm1=MY_M1_Buffer;  
uint16_t   MY_F=40;      //步进 
   
/* USER CODE END 0 */

3. Related functions

/* USER CODE BEGIN 4 */

void dma_m0_rxcplt_callback(DMA_HandleTypeDef *hdma)  //自己写的第一个回调函数,相当于半满
{
	u8 i=0;
	for(i=0;i<50;i++)  //DDS
	{
        MY_M0_Buffer[i] = sinBuf[outadress];
		outadress = (outadress + MY_F)%4000;
	}

}
void dma_m1_rxcplt_callback(DMA_HandleTypeDef *hdma)  //自己写的第二个回调函数,相当于全满
{
	u8 i=0;
	for(i=0;i<50;i++)
	{
        MY_M1_Buffer[i] = sinBuf[outadress];
		outadress = (outadress + MY_F)%4000;
	}
}

void MY_Buffer_Init(void)       //初始化缓冲区数据
{
	u8 i=0;                
	for(i=0;i<50;i++)
	{
        MY_M0_Buffer[i] = sinBuf[outadress];
		outadress = (outadress + MY_F)%4000;
	}
	for(i=0;i<50;i++)
	{
        MY_M1_Buffer[i] = sinBuf[outadress];
		outadress = (outadress + MY_F)%4000;
	}	
}

/* USER CODE END 4 */

4. Here comes the important point, please pay attention to the following, some internal configurations of the hal library need to be changed

(1) First enter the function definition of HAL_DMAEx_MultiBufferStart_IT, which can be found in the file below

(2) Add your own callback function definition

(3) Add these two sentences at the appropriate place

(4) You have to be disabled first

Overall as follows

/**
  * @brief  Starts the multi_buffer DMA Transfer with interrupt enabled.
  * @param  hdma       pointer to a DMA_HandleTypeDef structure that contains
  *                     the configuration information for the specified DMA Stream.  
  * @param  SrcAddress The source memory Buffer address
  * @param  DstAddress The destination memory Buffer address
  * @param  SecondMemAddress The second memory Buffer address in case of multi buffer Transfer  
  * @param  DataLength The length of data to be transferred from source to destination
  * @retval HAL status
  */

extern void dma_m0_rxcplt_callback(DMA_HandleTypeDef *hdma);
extern void dma_m1_rxcplt_callback(DMA_HandleTypeDef *hdma);

HAL_StatusTypeDef HAL_DMAEx_MultiBufferStart_IT(DMA_HandleTypeDef *hdma, uint32_t SrcAddress, uint32_t DstAddress, uint32_t SecondMemAddress, uint32_t DataLength)
{
  HAL_StatusTypeDef status = HAL_OK;
  
  /* Check the parameters */
  assert_param(IS_DMA_BUFFER_SIZE(DataLength));
  
  /* Memory-to-memory transfer not supported in double buffering mode */
  if (hdma->Init.Direction == DMA_MEMORY_TO_MEMORY)
  {
    hdma->ErrorCode = HAL_DMA_ERROR_NOT_SUPPORTED;
    return HAL_ERROR;
  }

//  /*Current memory buffer used is Memory 1 callback*/
	  hdma->XferCpltCallback=dma_m0_rxcplt_callback;    //第一个缓冲区填满后调用
//	/*Current memory buffer used is Memory 0 callback*/
	  hdma->XferM1CpltCallback=dma_m1_rxcplt_callback;  //第二个缓冲区填满后调用

  /* Check callback functions */
  if ((NULL == hdma->XferCpltCallback) || (NULL == hdma->XferM1CpltCallback) || (NULL == hdma->XferErrorCallback))
  {
    hdma->ErrorCode = HAL_DMA_ERROR_PARAM;
    return HAL_ERROR;
  }
  
  /* Process locked */
  __HAL_LOCK(hdma);
  
	/*Enable the peripheral*/
   __HAL_DMA_DISABLE(hdma);  //先失能DMA后边的设置才会生效
  
  if(HAL_DMA_STATE_READY == hdma->State)
  {
    /* Change DMA peripheral state */
    hdma->State = HAL_DMA_STATE_BUSY;
    
    /* Initialize the error code */
    hdma->ErrorCode = HAL_DMA_ERROR_NONE;
    
    /* Enable the Double buffer mode */
    hdma->Instance->CR |= (uint32_t)DMA_SxCR_DBM;
    
    /* Configure DMA Stream destination address */
    hdma->Instance->M1AR = SecondMemAddress;
    
    /* Configure the source, destination address and the data length */
    DMA_MultiBufferSetConfig(hdma, SrcAddress, DstAddress, DataLength); 
    
    /* Clear all flags */
    __HAL_DMA_CLEAR_FLAG (hdma, __HAL_DMA_GET_TC_FLAG_INDEX(hdma));
    __HAL_DMA_CLEAR_FLAG (hdma, __HAL_DMA_GET_HT_FLAG_INDEX(hdma));
    __HAL_DMA_CLEAR_FLAG (hdma, __HAL_DMA_GET_TE_FLAG_INDEX(hdma));
    __HAL_DMA_CLEAR_FLAG (hdma, __HAL_DMA_GET_DME_FLAG_INDEX(hdma));
    __HAL_DMA_CLEAR_FLAG (hdma, __HAL_DMA_GET_FE_FLAG_INDEX(hdma));

    /* Enable Common interrupts*/
    hdma->Instance->CR  |= DMA_IT_TC | DMA_IT_TE | DMA_IT_DME;
    hdma->Instance->FCR |= DMA_IT_FE;
    
    if((hdma->XferHalfCpltCallback != NULL) || (hdma->XferM1HalfCpltCallback != NULL))
    {
      hdma->Instance->CR  |= DMA_IT_HT;
    }
    
    /* Enable the peripheral */
    __HAL_DMA_ENABLE(hdma); 
  }
  else
  {     
    /* Process unlocked */
    __HAL_UNLOCK(hdma);	  
    
    /* Return error status */
    status = HAL_BUSY;
  }  
  return status; 
}

 

 3. Summary

The above are the two double-buffering implementation methods that I have practiced. Uu who need the source program can send a private message.

And this is the DDS principle blog push:

DDS principle and implementation_Azad_Walden's blog-CSDN blog_dds principle 1. DDS system structure DDS is a direct digital frequency synthesizer (Direct Digital Synthesizer), the system structure can be divided into the following parts, in which the phase control word can be adjusted The phase of the output sine wave, the frequency control word can adjust the frequency of the output sine wave. The DAC converts the digital output of the FPGA into an analog signal, because there are a lot of high-frequency signals in the signal, and adding a low-pass filter can make the signal smoother. ... https://blog.csdn.net/persistlzy/article/details/115218899?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522165099155716782390513538%2522%252C%2522 scm% 2522%253A%252220140713.130102334..% 2522%257D&request_id=165099155716782390513538&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-115218899-null-null-2~all~top_click~default-2-115218899.142 %5Ev9%5Econtrol,157%5Ev4%5Econtrol&utm_term=DDS&spm=1018.2226. 3001.4187 and the principle of double buffering push:

https://www.cnblogs.com/puyu9495/archive/2022/02/19/15914090.html

Everyone needs to pick it up. come on! Come on!

Guess you like

Origin blog.csdn.net/qq_51368339/article/details/124439407