一、工具
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