STM32-使用定时器+DAC输出对齐的方波和三角波
一、工具
1、硬件:STM32L072KB单片机(HAL库)
2、编译环境:Atollic TrueSTUDIO for STM32 9.3.0
3、辅助工具:STM32CubeMX
二、需求分析
现有以下需求,需要单片机能够同时输出一个方波和三角波,并且使方波的高电平的中间与三角波的波峰对齐,方波的低电平中间与三角波的波谷对齐,于此同时还必须能够在任意时刻更改两个波形的频率以及三角波的幅值,效果如下图所示:
首先三角波必须得使用单片机的DAC来控制输出,只需要和一个定时器配合工作,即可实现不同频率的三角波输出;方波可以使用DAC输出也可以使用定时器输出,如果使用DAC输出方波,这就需要单片的DAC具备至少两个通道(因为条件限制我这里就不采用这种方式),如果使用定时器输出方波就得考虑同时启动的问题。
同时启动问题目前我想到的只有两种:一种是外部因素,即如果做到DAC和定时器同时打开;一种是内部因素,即DAC或者定时器在启动后输出波形的响应速度(因为速度比较快可以先不考虑)。
考虑到输出三角波的DAC和输出方波的定时器同时启动问题,首先要分析下DAC输出三角波的原理(为了便于理解下面均是采用STM32F429系列芯片的中文手册截图)。
1、通过查阅对应的芯片手册,可以看到关于DAC生成三角波的介绍,具体内容如下图所示:
这里我总结下来就是:DAC有一个用于计数的寄存器DOR,而这个寄存器不会自己自加或者自减,自加和自减需要借助定时器产生的事件来完成(通常定时器会在一个周期内产生一个事件),而[DOR寄存器的值+DHRx寄存器的值]就是当前DAC输出的电压AD值,幅值就是[DHRx寄存的值+MAMPx寄存器的值]。
2、分析完DAC产生三角波的原理,可以得出三角波最终的产生是由为它提供事件的定时器来决定的。这时候就可以想到只要让给DAC提供事件的定时器和产生方波的定时器同时启动,理论上就应该可以做到所谓的三角波和方波同时产生。既然涉及到两个定时器,我想到了定时器的同步功能。继续查阅芯片手册,找到定时器的同步功能介绍,如下图所示:
从上图中,我发现当两个定时器关联在一起使用的时钟源是同一个,前一个定时器(主定时器)的预分频器会影响到后面定时器(从定时器)的输入时钟源,也正如文中描述的那样“将一个定时器用作另一个定时器的预分频器”。鉴于手册上文字描述信息过少,通过测试发现,主定时器的预分频器值不仅会影响到从定时器,自动重装载寄存器ARR的值同样会影响到从定时器,从定时器的时钟源就是主定时器的频率。
于是乎我就有以下猜想:在用户使用“将一个定时器用作另一个定时器的预分频器”这个功能时,主定时器每一个周期会产生一个事件,而从定时器每接收到主定时器的一个事件后计数器就会加1,感觉和前面说的DAC产生三角波的原理相似。
通过以上分析,只要让一个定时器产生事件同时作用于DAC和另一个定时器,这样的话基本就可以解决三角波和方波同步启动问题。顺便再补充一句,DAC的计数器DOR寄存器值是定时器每一个周期加1,所以一个完整的三角波周期应该包含多个定时器周期,要想使产生的方波能够与三角波对齐必须方波的每个周期也要包含多个主定时器的周期,而包含的定时器周期个数与三角波的幅值有关。
三、单片机系统时钟配置
1、系统时钟配置(没有显示的默认),这里选择的是内部的高速时钟(HSI)作为时钟源,系统时钟频率配置到24MHz。
四、触发源定时器配置
这里选择定时器2作为触发源,该定时器主要是为DAC和其它定时器提供更新事件,具体配置如下图所示。
五、输出三角波的DAC配置
DAC选择通道1输出三角波,触发源选择的是定时器2,具体配置如下图所示:
补充:这里配置DAC的时候需要设置“Output Buffer”为“DISABLE”,否则三角波的波谷会出现不正常的波形。
六、输出方波的定时器配置
方波由定时器3的通道4通过输出比较模式产生,设置为从模式,触发源由以下表中得到:
自动重装载值为DAC幅值*2,即(511*2);通道比较模式为反转,上电默认起始为低电平,至于占空比值,交给DMA去加载,具体配置如下图所示:
打开并设置定时器的DMA,具体配置如下图所示:
七、生成工程并进行完善
1、生成工程设置
2、完善代码
在DAC的初始化最后要进行DAC基值的设置。
/** * @brief DAC Initialization Function * @param None * @retval None */ static void MX_DAC_Init(void) { /* USER CODE BEGIN DAC_Init 0 */ /* USER CODE END DAC_Init 0 */ DAC_ChannelConfTypeDef sConfig = {0}; /* USER CODE BEGIN DAC_Init 1 */ /* USER CODE END DAC_Init 1 */ /** DAC Initialization */ hdac.Instance = DAC; if (HAL_DAC_Init(&hdac) != HAL_OK) { Error_Handler(); } /** DAC channel OUT1 config */ sConfig.DAC_Trigger = DAC_TRIGGER_T2_TRGO; sConfig.DAC_OutputBuffer = DAC_OUTPUTBUFFER_DISABLE; /* 补充:这里必须关闭 */ if (HAL_DAC_ConfigChannel(&hdac, &sConfig, DAC_CHANNEL_1) != HAL_OK) { Error_Handler(); } /** Configure Triangle wave generation on DAC OUT1 */ if (HAL_DACEx_TriangleWaveGenerate(&hdac, DAC_CHANNEL_1, DAC_TRIANGLEAMPLITUDE_511) != HAL_OK) { Error_Handler(); } /* USER CODE BEGIN DAC_Init 2 */ HAL_DAC_SetValue(&hdac, DAC_CHANNEL_1, DAC_ALIGN_12B_R, 100); /* USER CODE END DAC_Init 2 */ }
编写两个开始和停止波形输出的函数,这里有几个地方需要注意:
- 如果再关闭前不把方波的电平强制拉高,会导致三角波和方波产生半个周期的偏移;
- 三角波的幅值只能在DAC开启之前配置;
- 注意清零计数器的值。
具体内容看代码:
uint32_t oc_ccr4_value[3] = {255, 765, 1022}; /* 该数组需要根据不同的DAC幅值作出相应的改变,值按照 1/4、3/4、4/4设置 */
void triangle_wave_start(uint32_t freq) { TIM_OC_InitTypeDef sConfigOC; /* 恢复方波输出配置 */ sConfigOC.OCMode = TIM_OCMODE_TOGGLE; sConfigOC.Pulse = 0; sConfigOC.OCPolarity = TIM_OCPOLARITY_LOW; sConfigOC.OCFastMode = TIM_OCFAST_DISABLE; HAL_TIM_OC_ConfigChannel(&htim3, &sConfigOC, TIM_CHANNEL_4); /* 设置频率 */ __HAL_TIM_SET_AUTORELOAD(&htim2, (HAL_RCC_GetPCLK1Freq()/(freq*1022) - 1)); /* 打开三角波输出 */ DAC->CR |= (0x3 << 6); /* 开启所有波形输出 */ HAL_DAC_Start(&hdac, DAC_CHANNEL_1); HAL_TIM_OC_Start_DMA(&htim3, TIM_CHANNEL_4, oc_ccr4_value, 3); HAL_TIM_Base_Start(&htim2); } void triangle_wave_stop(void) { TIM_OC_InitTypeDef sConfigOC; /* 关闭三角波输出 */ DAC->CR &= ~(0x3 << 6); /* 强制使方波变成高电平 */ HAL_TIM_OC_Stop_DMA(&htim3, TIM_CHANNEL_4); sConfigOC.OCMode = TIM_OCMODE_FORCED_ACTIVE; sConfigOC.Pulse = 1022; sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH; sConfigOC.OCFastMode = TIM_OCFAST_DISABLE; HAL_TIM_OC_ConfigChannel(&htim3, &sConfigOC, TIM_CHANNEL_4); HAL_TIM_OC_Start_DMA(&htim3, TIM_CHANNEL_4, oc_ccr4_value, 3); /* 关闭所有波形输出 */ HAL_DAC_Stop(&hdac, DAC_CHANNEL_1); HAL_TIM_OC_Stop_DMA(&htim3, TIM_CHANNEL_4); HAL_TIM_Base_Stop(&htim2); /* 定时器计数器清零 */ __HAL_TIM_SET_COUNTER(&htim3, 0); __HAL_TIM_SET_COUNTER(&htim2, 0); }
3、不用修改的代码
定时器2初始化函数,只用了定时器2的定时器功能,周期值会在开启波形输出前被重新配置,这里的值并无意义,具体内容看代码:
/** * @brief TIM2 Initialization Function * @param None * @retval None */ static void MX_TIM2_Init(void) { /* USER CODE BEGIN TIM2_Init 0 */ /* USER CODE END TIM2_Init 0 */ TIM_ClockConfigTypeDef sClockSourceConfig = {0}; TIM_MasterConfigTypeDef sMasterConfig = {0}; /* USER CODE BEGIN TIM2_Init 1 */ /* USER CODE END TIM2_Init 1 */ htim2.Instance = TIM2; htim2.Init.Prescaler = 0; htim2.Init.CounterMode = TIM_COUNTERMODE_UP; htim2.Init.Period = 239; htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE; if (HAL_TIM_Base_Init(&htim2) != HAL_OK) { Error_Handler(); } sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL; if (HAL_TIM_ConfigClockSource(&htim2, &sClockSourceConfig) != HAL_OK) { Error_Handler(); } sMasterConfig.MasterOutputTrigger = TIM_TRGO_UPDATE; sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE; if (HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig) != HAL_OK) { Error_Handler(); } /* USER CODE BEGIN TIM2_Init 2 */ /* USER CODE END TIM2_Init 2 */ }
定时器3的初始化函数,这里的周期值与DAC的幅值有关,如果需要随时更改DAC幅值,这里也要改变,占空比值交给了DMA去处理,这里默认为0,具体内容看代码:
/** * @brief TIM3 Initialization Function * @param None * @retval None */ static void MX_TIM3_Init(void) { /* USER CODE BEGIN TIM3_Init 0 */ /* USER CODE END TIM3_Init 0 */ TIM_SlaveConfigTypeDef sSlaveConfig = {0}; TIM_MasterConfigTypeDef sMasterConfig = {0}; TIM_OC_InitTypeDef sConfigOC = {0}; /* USER CODE BEGIN TIM3_Init 1 */ /* USER CODE END TIM3_Init 1 */ htim3.Instance = TIM3; htim3.Init.Prescaler = 0; htim3.Init.CounterMode = TIM_COUNTERMODE_UP; htim3.Init.Period = 1021; htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; htim3.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE; if (HAL_TIM_Base_Init(&htim3) != HAL_OK) { Error_Handler(); } if (HAL_TIM_OC_Init(&htim3) != HAL_OK) { Error_Handler(); } sSlaveConfig.SlaveMode = TIM_SLAVEMODE_EXTERNAL1; sSlaveConfig.InputTrigger = TIM_TS_ITR0; if (HAL_TIM_SlaveConfigSynchro(&htim3, &sSlaveConfig) != HAL_OK) { Error_Handler(); } sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET; sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE; if (HAL_TIMEx_MasterConfigSynchronization(&htim3, &sMasterConfig) != HAL_OK) { Error_Handler(); } sConfigOC.OCMode = TIM_OCMODE_TOGGLE; sConfigOC.Pulse = 0; sConfigOC.OCPolarity = TIM_OCPOLARITY_LOW; sConfigOC.OCFastMode = TIM_OCFAST_DISABLE; if (HAL_TIM_OC_ConfigChannel(&htim3, &sConfigOC, TIM_CHANNEL_4) != HAL_OK) { Error_Handler(); } /* USER CODE BEGIN TIM3_Init 2 */ /* USER CODE END TIM3_Init 2 */ HAL_TIM_MspPostInit(&htim3); }
DMA初始化,具体内容看代码:
/** * Enable DMA controller clock */ static void MX_DMA_Init(void) { /* DMA controller clock enable */ __HAL_RCC_DMA1_CLK_ENABLE(); /* DMA interrupt init */ /* DMA1_Channel2_3_IRQn interrupt configuration */ HAL_NVIC_SetPriority(DMA1_Channel2_3_IRQn, 0, 0); HAL_NVIC_EnableIRQ(DMA1_Channel2_3_IRQn); }
DAC引脚初始化,及使能时钟,具体内容看代码:
/** * @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) { /* USER CODE BEGIN DAC_MspInit 0 */ /* USER CODE END DAC_MspInit 0 */ /* Peripheral clock enable */ __HAL_RCC_DAC_CLK_ENABLE(); __HAL_RCC_GPIOA_CLK_ENABLE(); /**DAC GPIO Configuration PA4 ------> DAC_OUT1 */ GPIO_InitStruct.Pin = GPIO_PIN_4; GPIO_InitStruct.Mode = GPIO_MODE_ANALOG; GPIO_InitStruct.Pull = GPIO_NOPULL; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); /* USER CODE BEGIN DAC_MspInit 1 */ /* USER CODE END DAC_MspInit 1 */ } }
定时器时钟使能,及DMA初始化,具体内容看代码:
/** * @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==TIM2) { /* USER CODE BEGIN TIM2_MspInit 0 */ /* USER CODE END TIM2_MspInit 0 */ /* Peripheral clock enable */ __HAL_RCC_TIM2_CLK_ENABLE(); /* USER CODE BEGIN TIM2_MspInit 1 */ /* USER CODE END TIM2_MspInit 1 */ } else if(htim_base->Instance==TIM3) { /* USER CODE BEGIN TIM3_MspInit 0 */ /* USER CODE END TIM3_MspInit 0 */ /* Peripheral clock enable */ __HAL_RCC_TIM3_CLK_ENABLE(); /* TIM3 DMA Init */ /* TIM3_CH4_UP Init */ hdma_tim3_ch4_up.Instance = DMA1_Channel3; hdma_tim3_ch4_up.Init.Request = DMA_REQUEST_10; hdma_tim3_ch4_up.Init.Direction = DMA_MEMORY_TO_PERIPH; hdma_tim3_ch4_up.Init.PeriphInc = DMA_PINC_DISABLE; hdma_tim3_ch4_up.Init.MemInc = DMA_MINC_ENABLE; hdma_tim3_ch4_up.Init.PeriphDataAlignment = DMA_PDATAALIGN_WORD; hdma_tim3_ch4_up.Init.MemDataAlignment = DMA_MDATAALIGN_WORD; hdma_tim3_ch4_up.Init.Mode = DMA_CIRCULAR; hdma_tim3_ch4_up.Init.Priority = DMA_PRIORITY_LOW; if (HAL_DMA_Init(&hdma_tim3_ch4_up) != HAL_OK) { Error_Handler(); } /* Several peripheral DMA handle pointers point to the same DMA handle. Be aware that there is only one channel to perform all the requested DMAs. */ __HAL_LINKDMA(htim_base,hdma[TIM_DMA_ID_CC4],hdma_tim3_ch4_up); __HAL_LINKDMA(htim_base,hdma[TIM_DMA_ID_UPDATE],hdma_tim3_ch4_up); /* USER CODE BEGIN TIM3_MspInit 1 */ /* USER CODE END TIM3_MspInit 1 */ } }
定时器引脚初始化,具体内容看代码:
void HAL_TIM_MspPostInit(TIM_HandleTypeDef* htim) { GPIO_InitTypeDef GPIO_InitStruct = {0}; if(htim->Instance==TIM3) { /* USER CODE BEGIN TIM3_MspPostInit 0 */ /* USER CODE END TIM3_MspPostInit 0 */ __HAL_RCC_GPIOB_CLK_ENABLE(); /**TIM3 GPIO Configuration PB1 ------> TIM3_CH4 */ GPIO_InitStruct.Pin = GPIO_PIN_1; GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; GPIO_InitStruct.Alternate = GPIO_AF2_TIM3; HAL_GPIO_Init(GPIOB, &GPIO_InitStruct); /* USER CODE BEGIN TIM3_MspPostInit 1 */ /* USER CODE END TIM3_MspPostInit 1 */ } }
DMA中断函数,具体内容看代码:
/** * @brief This function handles DMA1 channel 2 and channel 3 interrupts. */ void DMA1_Channel2_3_IRQHandler(void) { /* USER CODE BEGIN DMA1_Channel2_3_IRQn 0 */ /* USER CODE END DMA1_Channel2_3_IRQn 0 */ HAL_DMA_IRQHandler(&hdma_tim3_ch4_up); /* USER CODE BEGIN DMA1_Channel2_3_IRQn 1 */ /* USER CODE END DMA1_Channel2_3_IRQn 1 */ }
4、主函数
/** * @brief The application entry point. * @retval int */ 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_DMA_Init(); MX_TIM2_Init(); MX_TIM3_Init(); MX_DAC_Init(); /* USER CODE BEGIN 2 */ triangle_wave_start(250); /* USER CODE END 2 */ /* Infinite loop */ /* USER CODE BEGIN WHILE */ while (1) { /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ HAL_Delay(10000); triangle_wave_stop(); triangle_wave_start(1000); HAL_Delay(11000); triangle_wave_stop(); triangle_wave_start(3750); HAL_Delay(12000); triangle_wave_stop(); triangle_wave_start(250); } /* USER CODE END 3 */ }
八、波形显示
刚上电的波形输出
刚切换频率后波形输出
不切换频率波形输出
#end