STM32 DAC实战教程:从入门到精通的全方位指导
目录
前言:
1、实验要求
任务1:配置DAC,确保输出值精确达至1.49V,同时在显示屏上清晰展示或通过串口打印输出,实现精准控制与可视化监测的完美结合。
任务 2:在任务一的基础上面,PA10 复用为 DAC0_OUT,定时 1ms,每毫秒将 DAC0 数据转换输出值自加 1,数据转换值大于 1023 后,将数据转换值清 0,如此循环。使用示波器观察 PA10 输出波形。
2、理论知识储备
2.1 了解DAC的工作原理以及作用
2.2清楚如何设置 DAC 参考电压,输出电压
2.3清楚 DAC 特性参数
2.4DAC的计算公式(十位)
同理,以下是8位和12位的计算公式
2.5什么是触发源
3、软件显示
3.1任务1:DAC的配置输出
3.1.1GPIO 配置 + DAC初始化
3.1.2获取DAC的数值
3.1.3主函数
3.1.4实验效果:
3.2 任务二 DAC + 定时器
3.2.1DAC的配置输出
GPIO配置+DAC配置
3.2.2定时器配置:
3.2.3中断配置:
3.2.4定时器中断函数:
3.2.5 主函数
3.2.6实验效果:
4、总结:
前言:
本文将引领您踏上一段探寻DAC(数模转换器)奥秘的旅程,深入剖析其工作原理与配置技巧。将细致解读DAC的运作机制,并通过两个鲜活的案例——DAC的配置输出、DAC定时自加输出——来助您轻松掌握其应用精髓。旨在让读者能够更好地理解并掌握DAC的工作原理和应用技巧,为后续的STM32开发奠定坚实基础。
1、实验要求
任务1:配置DAC,确保输出值精确达至1.49V,同时在显示屏上清晰展示或通过串口打印输出,实现精准控制与可视化监测的完美结合。
任务 2:在任务一的基础上面,PA10 复用为 DAC0_OUT,定时 10ms,每毫秒将 DAC0 数据转换输出值自加 1,数据转换值
大于 1023 后,将数据转换值清 0,如此循环。使用示波器观察 PA10 输出波形。
2、理论知识储备
2.1 了解DAC的工作原理以及作用
这个DAC的框图描述了数字到模拟信号的转换过程。
首先,传感器收集环境数据,并将其转换成电信号。这些电信号经过模数转换器(ADC)转换成数字量,然后被单片机处理。接着,单片机根据需要的输出信号,通过数字到模拟转换器(DAC)将数字量转换为模拟信号。最后,这个模拟信号被用于控制模拟控制系统中的各种设备。
2.2清楚如何设置 DAC 参考电压,输出电压
DAC_InitStruct.DAC_Vref_Select = DAC_Vref_Avdd; //参考源配置
DAC_SetDac_10B_Data(DAC0, DAC_Align_10B_R, 0x1D1);
2.3清楚 DAC 特性参数
2.4DAC的计算公式(十位)
同理,以下是8位和12位的计算公式
2.5什么是触发源
3、软件显示
3.1任务1:DAC的配置输出
3.1.1GPIO 配置 + DAC初始化
void DAC0_Config()
{
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_4; // 配置PA4引脚
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AN; // 模拟输入模式
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_Level_2; // GPIO速度为10MHz
GPIO_InitStruct.GPIO_OType = GPIO_OType_PP; // 推挽输出
GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL; // 无上下拉
GPIO_Init(GPIOA, &GPIO_InitStruct); // 初始化GPIOA
GPIO_PinAFConfig(GPIOA, GPIO_PinSource4, GPIO_AF_2); // 将PA4引脚的复用功能设置为DAC0_OUT
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_5; // 配置PA5引脚
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AN; // 模拟输入模式
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_Level_2; // GPIO速度为10MHz
GPIO_InitStruct.GPIO_OType = GPIO_OType_PP; // 推挽输出
GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL; // 无上下拉
GPIO_Init(GPIOA, &GPIO_InitStruct); // 初始化GPIOA
GPIO_PinAFConfig(GPIOA, GPIO_PinSource5, GPIO_AF_2); // 将PA5引脚的复用功能设置为DAC0_OUT
DAC_InitTypeDef DAC_InitStruct;
DAC_InitStruct.DAC_EXTrigger_Edge = DAC_EXTrigger_edge_dis; // 外部触发边缘禁用
DAC_InitStruct.DAC_Trigger_Source = DAC_Trigger_Software; // 软件触发源
DAC_InitStruct.DAC_WaveGeneration = DAC_WaveGeneration_None; // 波形生成模式为无
DAC_InitStruct.DAC_LFSRUnmask_TriangleAmplitude = DAC_LFSRUnmask_Bit0; // DAC_LFSRUnmask_Bit0
DAC_InitStruct.DAC_OutputBuffer = DAC0_OutputBuffer_Enable; // 输出缓冲器使能
DAC_InitStruct.DAC_Vref_Select = DAC_Vref_Avdd; // 选择参考电压为AVDD
DAC_InitStruct.DAC_DmaMode = DISABLE; // DMA模式禁用
DAC_InitStruct.DAC_DMAUDR_IE = DISABLE; // DMA中断禁用
DAC_Init(DAC0, &DAC_InitStruct); // 初始化DAC0
DAC_Cmd(DAC0, ENABLE); // 启用DAC0
DAC_SetDac_10B_Data(DAC0, DAC_Align_10B_R, 0x200); // 设置DAC0的输出数据为0x200,10位右对齐
/* 软件触发使能;标示触发 DAC 的一次触发传输(选择软件触发条件) */
DAC_SoftwareTriggerCmd(DAC0, ENABLE); // 启用软件触发
}
以上代码是针对STM32的DAC0进行配置的函数。首先,配置了GPIO引脚(PA4和PA5)为模拟输入模式,并将其复用功能设置为DAC0输出。然后,对DAC进行初始化设置,包括设置触发源、波形生成模式、输出缓冲器使能等。接着启用DAC0,并设置输出数据为0x200,即512(10位右对齐)。最后,启用软件触发功能,以触发DAC一次传输。
3.1.2获取DAC的数值
uint16_t DAC_GetValue(void)
{
// 通过 DAC_GetDataOutputValue_10bit 函数获取 DAC 的输出值
return DAC_GetDataOutputValue_10bit(DAC0);
}
3.1.3主函数
uint16_t ADValue; //定义AD值变量
float Voltage; //定义电压变量
int main()
{
SystemInit();
SetSysClock(); //主频配置/set CPU frequency
RCC_APB1PeriphClockCmd(RCC_APB1Periph_ANACTRL, ENABLE);
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE);
DAC0_Config();
OLED_Init();
/*显示静态字符串*/
OLED_ShowString(1, 1, "DAValue:");
OLED_ShowString(2, 1, "Voltage:0.00V");
OLED_ShowNum(2, 11, (uint16_t)(Voltage * 100) % 100, 2); //显示电压值的小数部分
while (1)
{
ADValue = DAC_GetValue();
Voltage = (float)ADValue / 1024 * 3.3;
OLED_ShowNum(1, 9, ADValue, 4); //显示AD值
OLED_ShowNum(2, 9, Voltage, 1); //显示电压值的整数部分
OLED_ShowNum(2, 11, (uint16_t)(Voltage * 100) % 100, 2); //显示电压值的小数部分
}
}
这段代码首先进行了系统初始化和时钟配置,然后启用了模拟控制器和GPIOA的时钟。接着调用了
DAC0_Config()
函数对DAC进行配置初始化,并初始化了OLED显示屏。在主循环中,通过DAC_GetValue()
函数获取DAC输出的值,然后将其转换为电压值,并在OLED上显示AD值和电压值。整个代码实现了DAC输出值到电压值的转换,并在OLED上进行实时显示。
3.1.4实验效果:
根据计算公式 DAC_OUT = VREF * DOR/1024,以及给定的 DAValue = 0465,我们得到以下结果:1.49 = 3.3 * 0465 / 1024。因此,可以得出结论:任务一中配置的 DAC 输出实验效果是正确的。
3.2 任务二 DAC + 定时器
每10毫秒将 DAC0 数据转换输出值自加 1,数据转换值大于 1023 后,将数据转换值清 0
3.2.1DAC的配置输出
GPIO配置+DAC配置
配置两个GPIO引脚,分别用于连接DAC0输出。配置为模拟输入模式,速度为10MHz,推挽输出,无上拉下拉
配置DAC0,设置外部触发边缘,软件触发源,波形生成模式为三角波,波形幅值为1,输出缓冲器使能,参考电压选择AVDD。同时,禁用DMA模式和DMA中断。
void DAC0_Config()
{
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_4;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AN;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_Level_2;
GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;
GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_Init(GPIOA, &GPIO_InitStruct);
GPIO_PinAFConfig(GPIOA, GPIO_PinSource4, GPIO_AF_1); // DAC0_OUT
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AN;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_Level_2;
GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;
GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_Init(GPIOA, &GPIO_InitStruct);
GPIO_PinAFConfig(GPIOA, GPIO_PinSource10, GPIO_AF_2); // DAC0_OUT
DAC_InitTypeDef DAC_InitStruct;
DAC_InitStruct.DAC_EXTrigger_Edge = DAC_EXTrigger_edge_dis; //触发边沿
DAC_InitStruct.DAC_Trigger_Source = DAC_Trigger_Software; //触发方式
DAC_InitStruct.DAC_WaveGeneration = DAC_WaveGeneration_Triangle; //产生波形配置:无;三角波;噪声波
DAC_InitStruct.DAC_LFSRUnmask_TriangleAmplitude = DAC_TriangleAmplitude_1; //产生波形幅值
DAC_InitStruct.DAC_OutputBuffer = DAC0_OutputBuffer_Enable; // DAC驱动管使能控制
DAC_InitStruct.DAC_Vref_Select = DAC_Vref_Avdd; //参考源配置
DAC_InitStruct.DAC_DmaMode = DISABLE; // DAC DMA 使能位
DAC_InitStruct.DAC_DMAUDR_IE = DISABLE; // DAC DMA欠载中断使能配置
DAC_Init(DAC0, &DAC_InitStruct);
DAC_Cmd(DAC0, ENABLE);
DAC_SetDac_10B_Data(DAC0, DAC_Align_10B_R, 0x1D1);
DAC_SoftwareTriggerCmd(DAC0, ENABLE); //软件触发使能;标示触发 DAC 的一次触发传输(选择软件触发条件)
}
3.2.2定时器配置:
配置TIM3作为定时器,我们设定预分频系数为1599,并选择向上计数模式,确保其周期为100个计数周期。接下来,我们启用该定时器并激活其更新中断功能。
考虑到我们使用的主频为16Mhz,依据溢出时间的计算公式,
定时器的溢出时间计算为1600乘以100再除以16,得出结果为10,000微秒,即10毫秒。
这样的配置使得TIM3能够精确地以10毫秒的间隔触发更新中断,为我们的应用提供了稳定可靠的时间基准。
}
static void tim3_cfg_init()
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_TimeBaseStructure.TIM_Prescaler = 1599; //预分频系数设置位1599,即TIM_CLK=16/(1599+1)=10K;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseStructure.TIM_Period = 100;
TIM_TimeBaseStructure.TIM_ClockDivision = 0;
TIM_TimeBaseStructure.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
TIM_ARRPreloadConfig(TIM3, ENABLE);
TIM_ITConfig(TIM3, TIM_IT_Update, ENABLE);
TIM_Cmd(TIM3, ENABLE);
}
3.2.3中断配置:
配置TIM3的更新中断,并设置优先级。
void NVIC_Configuration()
{
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
3.2.4定时器中断函数:
在TIM3定时器中断处理函数中,首先检查是否发生更新事件,若有则清除中断标志位。然后每毫秒增加DAC输出值,使能软件触发DAC0。ADValue自增,若超过1023则清零。这段代码确保了定时器每毫秒产生一次中断,更新DAC输出值。(可以定义一个num值,然后检测定时器的配置是否正确)
void TIM3_Handler(void)
{
if (TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET) {
TIM_ClearITPendingBit(TIM3, TIM_IT_Update);
// 每毫秒增加 DAC 输出值
DAC_SetDac_10B_Data(DAC0, DAC_Align_10B_R, ADValue);
DAC_SoftwareTriggerCmd(DAC0, ENABLE);
ADValue++; // DAC 输出值自加 1
if (ADValue > 1023) {
ADValue = 0; // 数据转换值大于 1023 时,清零
}
}
//测试代码 判断是否可以进入定时器中断
// if (TIM_GetITStatus(TIM3, TIM_IT_Update) == SET) //判断是否是TIM3的更新事件触发的中断
// {
// Num ++; //Num变量自增,用于测试定时中断
// TIM_ClearITPendingBit(TIM3, TIM_IT_Update); //清除TIM2更新事件的中断标志位
// //中断标志位必须清除
// //否则中断将连续不断地触发,导致主程序卡死
// }
}
3.2.5 主函数
uint16_t ADValue; //定义AD值变量
float Voltage; //定义电压变量
uint16_t Num; //定义在定时器中断里自增的变量
int main()
{
SystemInit();
SetSysClock(); //主频配置
RCC_APB0PeriphClockCmd(RCC_APB0Periph_TIM3, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_EXTI, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_ANACTRL, ENABLE);
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE);
tim3_cfg_init();
DAC0_Config();
OLED_Init();
NVIC_Configuration();
// OLED_ShowString(1,1,"hello world");
// DAC_Trigger_GPIO_INIT();
// EXTI_Config();
// DAC1_Config();
OLED_Init();
/*显示静态字符串*/
OLED_ShowString(1, 1, "DAValue:");
OLED_ShowString(2, 1, "Voltage:0.00V");
/*显示静态字符串*/
OLED_ShowString(3, 1, "Num:"); //1行1列显示字符串Num:
while (1)
{
// ADValue = DAC_GetValue();
Voltage = (float)ADValue / 1024 * 3.3;
OLED_ShowNum(1, 9, ADValue, 4); //显示AD值
OLED_ShowNum(2, 9, Voltage, 1); //显示电压值的整数部分
OLED_ShowNum(2, 11, (uint16_t)(Voltage * 100) % 100, 2); //显示电压值的小数部分
OLED_ShowNum(3, 5, Num, 5); //不断刷新显示Num变量
}
}
3.2.6实验效果:
DAC+TIM
调试小技巧:在编程时,建议首先定义一个变量
num
用于计数。随后,在定时器中断服务程序中执行判断逻辑,并实时观察num
数值的变动。通过这一方式,我们能够直观判断定时器是否配置正确并正常工作,从而更有效地进行代码调试。
使用示波器观察波形,只见波形自底部缓缓升起,再逐渐回落,如此往复循环。这一景象清晰地展示了任务二实验效果的完美实现。
4、总结:
在任务1中,我们成功配置了DAC以输出精确的1.49V电压,这为我们后续的开发工作提供了稳定的模拟信号源。
而在任务2中,我们进一步利用DAC的定时自加输出功能,通过示波器观察到了波形从下到上、再从上到下的循环变化,充分展示了DAC的灵活性和实用性。
提供的代码可能不适用于所有情况,,但其逻辑结构和编程思路对于DAC的初始化、配置和使用同样具有指导意义。理解这些步骤后,您可以根据DAC的特性和您的具体需求进行相应的修改和优化。
希望本文能对读者有所帮助,欢迎大家探索和实践!
以下是优质的视频推荐,助您快速掌握DAC的理论精髓。
传送门
作者:呼呼呼呼大睡