在现代电子系统中,信号的生成与处理是极为重要的环节。正弦波作为一种基本的周期性信号,在通信、音频、电力等众多领域有着广泛的应用。例如在通信领域,正弦波可作为载波信号用于调制信息;在音频领域,它是构成各种复杂声音的基础。
STM32F407 是 ST 公司推出的一款高性能微控制器,具备丰富的外设资源,其中数模转换器(DAC)能将数字信号转换为模拟信号。借助 STM32 HAL 库,开发者可以更便捷地操作硬件,实现各种功能。本文将深入探讨如何基于 STM32F407 HAL 库利用 DAC 生成正弦波,从原理分析、硬件设计到软件实现进行全面阐述。
数模转换器(DAC)的核心功能是将离散的数字信号转换为连续的模拟信号。其工作原理基于加权求和的思想,通过内部的电阻网络或电容网络,将输入的数字代码按照不同的权重进行组合,从而得到对应的模拟电压输出。
以常见的二进制加权电阻网络 DAC 为例,对于一个 n 位的 DAC,其输入的二进制数字代码为 \(D = d_{n - 1}2^{n - 1}+d_{n - 2}2^{n - 2}+\cdots +d_12^1 + d_02^0\),其中 \(d_i\) 为第 i 位的二进制值(0 或 1)。每个位对应一个特定的权重电阻,通过开关控制是否将该电阻接入电路。当数字代码输入后,相应的开关闭合,各电阻上的电流根据其权重进行叠加,最终在输出端得到与数字代码对应的模拟电压。
正弦波是一种周期性的连续信号,其数学表达式为 (y(t)=Asin(2πft+b),其中:
正弦波具有许多重要的特性,如周期性、对称性等。在信号处理中,正弦波是最基本的信号形式,许多复杂的信号都可以分解为多个不同频率、振幅和相位的正弦波的叠加。
根据奈奎斯特采样定理,为了能够从采样信号中无失真地恢复出原始连续信号,采样频率 fs必须大于等于原始信号最高频率 fmax 的两倍.
在生成正弦波的过程中,我们需要对连续的正弦波进行采样,得到一系列离散的采样点。采样频率的选择直接影响到生成的正弦波的质量。如果采样频率过低,会导致信号失真,出现混叠现象;如果采样频率过高,则会增加系统的处理负担和存储需求。
STM32F407 是基于 ARM Cortex - M4 内核的 32 位微控制器,具有高性能、低功耗等特点。它集成了丰富的外设,如定时器、串口、SPI、I2C、DAC 等,适用于各种工业控制、消费电子等领域。
DAC 的两个通道分别对应特定的引脚:
在实际应用中,需要将这些引脚连接到外部电路,以获取模拟输出信号。
STM32F407 的最小系统包括电源电路、时钟电路、复位电路和调试接口。
DAC 输出电路的设计需要考虑输出信号的驱动能力、滤波和负载匹配等问题。
以下是一个简单的基于 STM32F407 的 DAC 正弦波生成硬件连接图:
+---------------------+
| |
| STM32F407 |
| |
| PA4 (DAC1_OUT) ----+---> 低通滤波器 ----> 负载
| |
| VDD --------------+---> 3.3V电源
| GND --------------+---> 地
| |
| SWDIO ------------+---> 调试器
| SWCLK ------------+---> 调试器
| |
+---------------------+
打开 STM32CubeMX,选择 STM32F407VG 芯片。
在 RCC 选项中,配置外部晶振和系统时钟。将系统时钟设置为 168MHz,以提高系统的运行速度。
完成配置后,点击 “Generate Code” 生成初始化代码。
#include
#define SAMPLE_SIZE 32 // 正弦波采样点数
#define DAC_MAX_VALUE 4095 // 12位DAC最大值
uint16_t sine_wave[SAMPLE_SIZE];
void generate_sine_wave_table() {
for (int i = 0; i < SAMPLE_SIZE; i++) {
float angle = 2 * M_PI * i / SAMPLE_SIZE;
sine_wave[i] = (uint16_t)((sin(angle) + 1) * DAC_MAX_VALUE / 2);
}
}
这段代码通过循环计算正弦波在每个采样点的值,并将其转换为 12 位的数字代码存储在 sine_wave
数组中。
#include "main.h"
#include "stm32f4xx_hal.h"
#include
#define SAMPLE_SIZE 32 // 正弦波采样点数
#define DAC_MAX_VALUE 4095 // 12位DAC最大值
// 正弦波数据表
uint16_t sine_wave[SAMPLE_SIZE];
// 定时器句柄
TIM_HandleTypeDef htim6;
// DAC句柄
DAC_HandleTypeDef hdac;
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_DAC_Init(void);
static void MX_TIM6_Init(void);
void generate_sine_wave_table(void);
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_DAC_Init();
MX_TIM6_Init();
// 生成正弦波数据表
generate_sine_wave_table();
// 启动DAC
HAL_DAC_Start(&hdac, DAC_CHANNEL_1);
// 启动定时器触发DAC转换
HAL_TIM_Base_Start(&htim6);
while (1)
{
// 主循环可以处理其他任务
}
}
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
/** 初始化RCC振荡器
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLM = 8;
RCC_OscInitStruct.PLL.PLLN = 336;
RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
RCC_OscInitStruct.PLL.PLLQ = 7;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
/** 初始化RCC时钟
*/
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5) != HAL_OK)
{
Error_Handler();
}
}
static void MX_DAC_Init(void)
{
DAC_ChannelConfTypeDef sConfig = {0};
/** 初始化DAC
*/
hdac.Instance = DAC;
if (HAL_DAC_Init(&hdac) != HAL_OK)
{
Error_Handler();
}
/** 配置DAC通道1
*/
sConfig.DAC_Trigger = DAC_TRIGGER_T6_TRGO;
sConfig.DAC_OutputBuffer = DAC_OUTPUTBUFFER_ENABLE;
if (HAL_DAC_ConfigChannel(&hdac, &sConfig, DAC_CHANNEL_1) != HAL_OK)
{
Error_Handler();
}
}
static void MX_TIM6_Init(void)
{
TIM_MasterConfigTypeDef sMasterConfig = {0};
htim6.Instance = TIM6;
htim6.Init.Prescaler = 167; // 定时器分频系数
htim6.Init.CounterMode = TIM_COUNTERMODE_UP;
htim6.Init.Period = 99; // 定时器周期
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();
}
}
static void MX_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
/* GPIO Ports Clock Enable */
__HAL_RCC_GPIOA_CLK_ENABLE();
/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_RESET);
/*Configure GPIO pin : PA5 */
GPIO_InitStruct.Pin = GPIO_PIN_5;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
}
void generate_sine_wave_table(void)
{
for (int i = 0; i < SAMPLE_SIZE; i++) {
float angle = 2 * M_PI * i / SAMPLE_SIZE;
sine_wave[i] = (uint16_t)((sin(angle) + 1) * DAC_MAX_VALUE / 2);
}
}
void Error_Handler(void)
{
while(1)
{
}
}
#ifdef USE_FULL_ASSERT
/**
* @brief Reports the name of the source file and the source line number
* where the assert_param error has occurred.
* @param file: pointer to the source file name
* @param line: assert_param error line source number
* @retval None
*/
void assert_failed(uint8_t *file, uint32_t line)
{
/* User can add his own implementation to report the file name and line number,
ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
}
#endif
主函数中首先进行系统初始化,包括时钟、GPIO、DAC 和定时器的初始化。然后调用 generate_sine_wave_table
函数生成正弦波数据表,启动 DAC 和定时器,开始周期性的 DAC 转换。
void TIM6_DAC_IRQHandler(void)
{
static uint8_t index = 0;
if (__HAL_TIM_GET_FLAG(&htim6, TIM_FLAG_UPDATE) != RESET) {
if (__HAL_TIM_GET_IT_SOURCE(&htim6, TIM_IT_UPDATE) != RESET) {
// 输出正弦波数据
HAL_DAC_SetValue(&hdac, DAC_CHANNEL_1, DAC_ALIGN_12B_R, sine_wave[index]);
index = (index + 1) % SAMPLE_SIZE;
__HAL_TIM_CLEAR_IT(&htim6, TIM_IT_UPDATE);
}
}
}
定时器中断服务函数在定时器溢出时被调用,每次中断将正弦波数据表中的下一个数据输出到 DAC 通道 1,并更新索引值。
sin
计算正弦波在每个采样点的值,并将其转换为适合 DAC 输入的数字代码。采样点数 SAMPLE_SIZE
决定了正弦波的平滑度,采样点数越多,正弦波越接近理想的连续正弦波。HAL_DAC_Init
函数初始化 DAC 模块,配置 DAC 通道 1 的触发方式和输出缓冲。HAL_TIM_Base_Init
函数初始化定时器,配置定时器的时钟源、分频系数和自动重载值。定时器的溢出频率决定了正弦波的采样频率。