Implementación de biblioteca HAL en modo de doble búfer DMA de aprendizaje STM32F407

La primera capacitación de este semestre requiere el uso de una microcomputadora de un solo chip para lograr una salida de onda sinusoidal de frecuencia variable de un solo paso de 1 a 40 KHz. Al principio, este plato se usó para cambiar la frecuencia de la tabla de ondas de salida y modificar el tiempo. modificando el coeficiente del preescalador del temporizador y el valor de recarga automática.Hay dos métodos de dispositivo + DDS , pero el efecto no es ideal, no solo la forma de onda no es atractiva, sino que la frecuencia de 37 KHz no es precisa. Después de consultar la información y discutir con amigos, finalmente decidí intentar usar el doble búfer DMA + DDS para realizar el generador de forma de onda. El efecto no solo es una forma de onda atractiva, sino también una frecuencia relativamente precisa.

No hay mucha información sobre la implementación de doble búfer DMA de stm32 en Internet. Aquí escribo los dos métodos de mi propia implementación. Por un lado, puedo encontrarlo rápidamente cuando lo use en el futuro. Por otro lado, Espero poder ayudar y quiero usar el doble almacenamiento en búfer.

(Gracias de antemano por la ayuda apasionada del hermano Han, Guozi y otros amigos (*^▽^*))

Los dos métodos son los siguientes:

1. Implementación de interrupciones DMA medio llenas y llenas llenas

@Yogur

El primero es la configuración, configurar el DMA del DAC.

Aquí vinculé el DMA a TIM4, puedes cambiarlo según tus necesidades.

Aquí, para generar una frecuencia más alta, la frecuencia del reloj se lleva al máximo.

 PD:

Permítanme explicar aquí que cuando se trata de interrupciones, es mejor configurar la prioridad de interrupción DMA del DAC un poco más alta, para evitar problemas extraños.

El siguiente es el código, el código es relativamente simple, utiliza principalmente las dos funciones de devolución de llamada que vienen con la biblioteca HAL en la interrupción dma:

void HAL_DAC_ConvHalfCpltCallbackCh1(DAC_HandleTypeDef *hdac); //Devolución de llamada de interrupción a la mitad

void HAL_DAC_ConvCpltCallbackCh1 (DAC_HandleTypeDef *hdac); // devolución de llamada de interrupción completa

Medio lleno, como su nombre lo indica, si usa la instrucción HAL_DAC_Start_DMA(&hdac, DAC1_CHANNEL_1, (uint32_t*)dacBuf, 4000, DAC_ALIGN_12B_R); luego, cuando DMA transfiere datos a 4000/2=2000, que es la mitad de la longitud de dacBuf , ingresará una devolución de llamada de interrupción medio llena, cuando los datos de transmisión alcancen el 4000, es decir, se complete toda la transmisión, ingresará la devolución de llamada de interrupción completa.

Las dos funciones de devolución de llamada son funciones débiles, debe reescribirlas usted mismo y luego escribir su propio código, como DDS, en ellas. Estas dos funciones de devolución de llamada están dentro de la función HAL_DAC_Start_DMA, puede encontrarlas usted mismo.

 Aquí hay un recordatorio: una vez que se llama a HAL_DAC_Start_DMA, las interrupciones de DMA medio lleno, lleno-lleno y de error se habilitan automáticamente. Cuando habilita DMA sin interrumpir, es mejor detenerlo manualmente, así en el .c archivo a continuación Coméntelo (el autor no sabe qué problemas se producirán al comentar directamente de esta manera, si alguien lo sabe, comuníquese con el autor para corregirlo).

El flujo de código aproximado es el siguiente:

1. Función principal

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 */
}

Así es, solo HAL_TIM_Base_Start(&htim4);  
  HAL_DAC_Start_DMA(&hdac, DAC1_CHANNEL_1, (uint32_t*)dacBuf, SINN, DAC_ALIGN_12B_R); se necesitan dos oraciones en la función principal.

Cabe señalar aquí que la inicialización de DMA debe colocarse antes de la inicialización de DAC, de lo contrario, habrá problemas.

2. Interrupciones propias medio llenas y llenas llenas

#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;
  }
}

 Para cambiar la frecuencia, el autor modifica la conversión de frecuencia modificando el valor del paso ftw en la interrupción del temporizador después de un cierto período de tiempo.

El anterior es el primer método para lograr el doble almacenamiento en búfer DMA, que es relativamente simple.

2. Almacenamiento en búfer doble relativamente auténtico

@果子

La configuración del cubo en este método es la misma que en el primero, solo configure el DMA y el temporizador.

No hablaré mucho sobre los principios aquí, solo hablaré sobre los métodos de implementación. El autor pondrá los enlaces a los artículos que creo que están bien explicados a continuación, y puede leerlos si los necesita.

1. Función principal

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. Algunas definiciones

/* 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. Funciones relacionadas

/* 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. Aquí viene el punto importante, preste atención a lo siguiente, es necesario cambiar algunas configuraciones internas de la biblioteca hal

(1) Primero ingrese la definición de función de HAL_DMAEx_MultiBufferStart_IT, que se puede encontrar en el archivo a continuación

(2) Agregue su propia definición de función de devolución de llamada

(3) Agregue estas dos oraciones en el lugar apropiado

(4) Tienes que estar deshabilitado primero

En general de la siguiente manera

/**
  * @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. Resumen

Los anteriores son los dos métodos de implementación de doble búfer que he practicado.Uu que necesite el programa fuente puede enviar un mensaje privado.

Y este es el impulso principal del blog DDS:

Principio e implementación de DDS_blog de Azad_Walden-blog de CSDN_principio de dds 1. Estructura del sistema DDS DDS es un sintetizador de frecuencia digital directo (sintetizador digital directo), la estructura del sistema se puede dividir en las siguientes partes, en las que se puede ajustar la palabra de control de fase La fase de la onda sinusoidal de salida, la palabra de control de frecuencia puede ajustar la frecuencia de la onda sinusoidal de salida. El DAC convierte la salida digital del FPGA en una señal analógica, porque hay muchas señales de alta frecuencia en la señal, y agregar un filtro de paso bajo puede suavizar la señal. ... 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 %5E v9%5Econtrol,157%5Ev4%5Econtrol&utm_term=DDS&spm=1018.2226 3001.4187 y el principio de doble impulso de almacenamiento intermedio:

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

Todo el mundo tiene que recogerlo. ¡vamos vamos!

Supongo que te gusta

Origin blog.csdn.net/qq_51368339/article/details/124439407
Recomendado
Clasificación