一、工具
1、硬件:STM32F429BI单片机(HAL库)
2、编译环境:Atollic TrueSTUDIO for STM32 9.3.0
3、辅助工具:STM32CubeMX
二、需求分析
现有以下需求,需要单片机能够同时输出一个方波和三角波,并且使方波的高电平的中间与三角波的波峰对齐,方波的低电平中间与三角波的波谷对齐,于此同时还必须能够在任意时刻更改两个波形的频率以及三角波的幅值,效果如下图所示:
首先三角波必须得使用单片机的DAC来控制输出,只需要和一个定时器配合工作,即可实现不同频率的三角波输出;方波可以使用DAC输出也可以使用定时器输出,如果使用DAC输出方波,这就需要单片的DAC具备至少两个通道;如果使用定时器输出方波就得考虑同时启动的问题。这里我使用单片机DAC的双通道实现。
1、通过查阅对应的芯片手册,可以看到关于DAC生成三角波的介绍,具体内容如下图所示:
这里我总结下来就是:DAC有一个用于计数的寄存器DOR,而这个寄存器不会自己自加或者自减,自加和自减需要借助定时器产生的事件来完成(通常定时器会在一个周期内产生一个事件),而[DOR寄存器的值+DHRx寄存器的值]就是当前DAC输出的电压AD值,幅值就是[DHRx寄存的值+MAMPx寄存器的值]。尽管单片机具备输出三角波的功能,但在使用中发现不够灵活,于是乎我舍弃该方式,使用DAC+DMA的方式输出三角波和方波。
三、单片机系统时钟配置
1、系统时钟配置(没有显示的默认),这里选择的是外部的高速时钟(HSE)作为时钟源,系统时钟频率配置到96MHz。
四、触发源定时器配置
这里选择定时器6作为触发源,该定时器主要是为DAC提供更新事件,具体配置如下图所示。
五、DAC配置
DAC选择通道1和通道2配置相同,触发源选择的是定时器6,具体配置如下图所示:
补充:这里配置DAC的时候需要设置“Output Buffer”为“DISABLE”,否则三角波的波谷会出现不正常的波形。
DAC的两个通道都开启DMA且配置相同,具体配置如下图所示:
七、生成工程并进行完善
1、生成工程设置
2、添加个人代码
DAC_HandleTypeDef hdac;
DMA_HandleTypeDef hdma_dac1;
DMA_HandleTypeDef hdma_dac2;
TIM_HandleTypeDef htim6;
WAVEFORM_POINT_NUM的值要根据定时器时钟源来灵活设定,首先做到能被4整除,再者使定时器的时钟源能被(定时器频率*WAVEFORM_POINT_NUM)整除,比如当前我的定时器的时钟源为48MHz,如果输出1KHz频率的三角波和方波,则有(1KHz*WAVEFORM_POINT_NUM) = 48MHz/(psc*per)。
#define WAVEFORM_POINT_NUM 800 uint32_t DAC_ch1_value[WAVEFORM_POINT_NUM]; uint32_t DAC_ch2_value[WAVEFORM_POINT_NUM]; /** * @brief 初始化三角波数组值 * @param amplitude 三角波幅值对应的DA值 * @param p 被初始化的数组起始地址 * @param length 被初始化的数组长度(长度值要能被4整除) * @retval none */ void triangle_array_init(uint16_t amplitude, uint32_t *p, uint32_t length) { uint32_t i; float unit_value = 0; unit_value = (amplitude/(length/2.0)); for( i = 0; i < length; i++) { if(i <= (length/2)) p[i] = (i*unit_value); else p[i] = ((length - i)*unit_value); } } /** * @brief 初始化方波数组值 * @param p 被初始化的数组起始地址 * @param length 被初始化的数组长度(长度值要能被4整除) * @retval none */ void square_array_init(uint32_t *p, uint32_t length) { uint32_t i; for( i = 0; i < length; i++) { if(i < (length/4)) p[i] = 0; else if(i < ((length * 3)/4)) p[i] = 4095; else p[i] = 0; } } /** * @brief 波形输出停止 * @retval none */ void waveform_stop(void) { HAL_TIM_Base_Stop(&htim6); HAL_DAC_Stop_DMA(&hdac, DAC_CHANNEL_1); HAL_DAC_Stop_DMA(&hdac, DAC_CHANNEL_2); } /** * @brief 波形输出开启 * @retval none */ void waveform_start(void) { HAL_DAC_Start_DMA(&hdac, DAC_CHANNEL_1, DAC_ch1_value, WAVEFORM_POINT_NUM, DAC_ALIGN_12B_R); HAL_DAC_Start_DMA(&hdac, DAC_CHANNEL_2, DAC_ch2_value, WAVEFORM_POINT_NUM, DAC_ALIGN_12B_R); HAL_TIM_Base_Start(&htim6); } /** * @brief 波形设置 * @param freq 波形频率 * @param voltage 三角波幅值电压(单位mV) * @retval none */ void waveform_set(uint32_t freq, float voltage) { triangle_array_init((uint16_t)((voltage*4095)/3300) ,DAC_ch1_value, WAVEFORM_POINT_NUM); square_array_init(DAC_ch2_value, WAVEFORM_POINT_NUM); __HAL_TIM_SET_AUTORELOAD(&htim6, (48000000/(freq * WAVEFORM_POINT_NUM) - 1)); }
3、不用修改的代码
定时器6初始化函数的周期值会在开启波形输出前被重新配置,这里的值并无意义,具体内容看代码:
/** * @brief TIM6 Initialization Function * @param None * @retval None */ static void MX_TIM6_Init(void) { TIM_MasterConfigTypeDef sMasterConfig = {0}; htim6.Instance = TIM6; htim6.Init.Prescaler = 0; htim6.Init.CounterMode = TIM_COUNTERMODE_UP; htim6.Init.Period = 0; htim6.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE; if (HAL_TIM_Base_Init(&htim6) != HAL_OK) { Error_Handler(); } sMasterConfig.MasterOutputTrigger = TIM_TRGO_UPDATE; sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE; if (HAL_TIMEx_MasterConfigSynchronization(&htim6, &sMasterConfig) != HAL_OK) { Error_Handler(); } }
/** * @brief TIM_Base MSP Initialization * This function configures the hardware resources used in this example * @param htim_base: TIM_Base handle pointer * @retval None */ void HAL_TIM_Base_MspInit(TIM_HandleTypeDef* htim_base) { if(htim_base->Instance==TIM6) { /* Peripheral clock enable */ __HAL_RCC_TIM6_CLK_ENABLE(); } } /** * @brief TIM_Base MSP De-Initialization * This function freeze the hardware resources used in this example * @param htim_base: TIM_Base handle pointer * @retval None */ void HAL_TIM_Base_MspDeInit(TIM_HandleTypeDef* htim_base) { if(htim_base->Instance==TIM6) { /* Peripheral clock disable */ __HAL_RCC_TIM6_CLK_DISABLE(); } }
DMA初始化,具体内容看代码:
/** * Enable DMA controller clock */ static void MX_DMA_Init(void) { /* DMA controller clock enable */ __HAL_RCC_DMA1_CLK_ENABLE(); /* DMA interrupt init */ /* DMA1_Stream5_IRQn interrupt configuration */ HAL_NVIC_SetPriority(DMA1_Stream5_IRQn, 0, 0); HAL_NVIC_EnableIRQ(DMA1_Stream5_IRQn); /* DMA1_Stream6_IRQn interrupt configuration */ HAL_NVIC_SetPriority(DMA1_Stream6_IRQn, 0, 0); HAL_NVIC_EnableIRQ(DMA1_Stream6_IRQn); }
DMA中断函数,具体内容看代码:
/** * @brief This function handles DMA1 stream5 global interrupt. */ void DMA1_Stream5_IRQHandler(void) { HAL_DMA_IRQHandler(&hdma_dac1); } /** * @brief This function handles DMA1 stream6 global interrupt. */ void DMA1_Stream6_IRQHandler(void) { HAL_DMA_IRQHandler(&hdma_dac2); }
DAC引脚初始化,及使能时钟,具体内容看代码:
/** * @brief DAC Initialization Function * @param None * @retval None */ static void MX_DAC_Init(void) { DAC_ChannelConfTypeDef sConfig = {0};
/** DAC Initialization */ hdac.Instance = DAC; if (HAL_DAC_Init(&hdac) != HAL_OK) { Error_Handler(); } /** DAC channel OUT1 config */ sConfig.DAC_Trigger = DAC_TRIGGER_T6_TRGO; sConfig.DAC_OutputBuffer = DAC_OUTPUTBUFFER_DISABLE; if (HAL_DAC_ConfigChannel(&hdac, &sConfig, DAC_CHANNEL_1) != HAL_OK) { Error_Handler(); } /** DAC channel OUT2 config */ if (HAL_DAC_ConfigChannel(&hdac, &sConfig, DAC_CHANNEL_2) != HAL_OK) { Error_Handler(); } }
/** * @brief DAC MSP Initialization * This function configures the hardware resources used in this example * @param hdac: DAC handle pointer * @retval None */ void HAL_DAC_MspInit(DAC_HandleTypeDef* hdac) { GPIO_InitTypeDef GPIO_InitStruct = {0}; if(hdac->Instance==DAC) { /* Peripheral clock enable */ __HAL_RCC_DAC_CLK_ENABLE(); __HAL_RCC_GPIOA_CLK_ENABLE(); /**DAC GPIO Configuration PA4 ------> DAC_OUT1 PA5 ------> DAC_OUT2 */ GPIO_InitStruct.Pin = GPIO_PIN_4|GPIO_PIN_5; GPIO_InitStruct.Mode = GPIO_MODE_ANALOG; GPIO_InitStruct.Pull = GPIO_NOPULL; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); /* DAC DMA Init */ /* DAC1 Init */ hdma_dac1.Instance = DMA1_Stream5; hdma_dac1.Init.Channel = DMA_CHANNEL_7; hdma_dac1.Init.Direction = DMA_MEMORY_TO_PERIPH; hdma_dac1.Init.PeriphInc = DMA_PINC_DISABLE; hdma_dac1.Init.MemInc = DMA_MINC_ENABLE; hdma_dac1.Init.PeriphDataAlignment = DMA_PDATAALIGN_WORD; hdma_dac1.Init.MemDataAlignment = DMA_MDATAALIGN_WORD; hdma_dac1.Init.Mode = DMA_CIRCULAR; hdma_dac1.Init.Priority = DMA_PRIORITY_LOW; hdma_dac1.Init.FIFOMode = DMA_FIFOMODE_DISABLE; if (HAL_DMA_Init(&hdma_dac1) != HAL_OK) { Error_Handler(); } __HAL_LINKDMA(hdac,DMA_Handle1,hdma_dac1); /* DAC2 Init */ hdma_dac2.Instance = DMA1_Stream6; hdma_dac2.Init.Channel = DMA_CHANNEL_7; hdma_dac2.Init.Direction = DMA_MEMORY_TO_PERIPH; hdma_dac2.Init.PeriphInc = DMA_PINC_DISABLE; hdma_dac2.Init.MemInc = DMA_MINC_ENABLE; hdma_dac2.Init.PeriphDataAlignment = DMA_PDATAALIGN_WORD; hdma_dac2.Init.MemDataAlignment = DMA_MDATAALIGN_WORD; hdma_dac2.Init.Mode = DMA_CIRCULAR; hdma_dac2.Init.Priority = DMA_PRIORITY_LOW; hdma_dac2.Init.FIFOMode = DMA_FIFOMODE_DISABLE; if (HAL_DMA_Init(&hdma_dac2) != HAL_OK) { Error_Handler(); } __HAL_LINKDMA(hdac,DMA_Handle2,hdma_dac2); } } /** * @brief DAC MSP De-Initialization * This function freeze the hardware resources used in this example * @param hdac: DAC handle pointer * @retval None */ void HAL_DAC_MspDeInit(DAC_HandleTypeDef* hdac) { if(hdac->Instance==DAC) { /* Peripheral clock disable */ __HAL_RCC_DAC_CLK_DISABLE(); /**DAC GPIO Configuration PA4 ------> DAC_OUT1 PA5 ------> DAC_OUT2 */ HAL_GPIO_DeInit(GPIOA, GPIO_PIN_4|GPIO_PIN_5); /* DAC DMA DeInit */ HAL_DMA_DeInit(hdac->DMA_Handle1); HAL_DMA_DeInit(hdac->DMA_Handle2); } }
4、主函数
/** * @brief The application entry point. * @retval int */ int main(void) { /* MCU Configuration--------------------------------------------------------*/ /* Reset of all peripherals, Initializes the Flash interface and the Systick. */ HAL_Init(); /* Configure the system clock */ SystemClock_Config(); /* Initialize all configured peripherals */ MX_GPIO_Init(); MX_DMA_Init(); MX_DAC_Init(); MX_TIM6_Init(); /* USER CODE BEGIN 2 */ waveform_set(1000, 200); waveform_start(); HAL_Delay(20000); /* USER CODE END 2 */ /* Infinite loop */ /* USER CODE BEGIN WHILE */ while (1) { /* USER CODE END WHILE */ waveform_stop(); waveform_set(200, 800); waveform_start(); HAL_Delay(20000); waveform_stop(); waveform_set(1000, 1200); waveform_start(); HAL_Delay(20000); waveform_stop(); waveform_set(4000, 200); waveform_start(); HAL_Delay(20000); /* USER CODE BEGIN 3 */ } /* USER CODE END 3 */ }