首页 > 其他分享 >【STM32】RTT-Studio中HAL库开发教程四:DAC+DMA输出波形

【STM32】RTT-Studio中HAL库开发教程四:DAC+DMA输出波形

时间:2024-07-17 20:56:18浏览次数:16  
标签:duty DMA HAL DAC void Init

文章目录

一、DAC介绍

1.DAC作用

  • DAC(Digital-to-Analog Converter),即为数字/模拟转换模块,又称D/A转换器;
  • 作用就是把输入的数字编码,转换成对应的模拟电压输出,它的功能与ADC 相反。即为输出波形和输出固定电压

2.DAC介绍

  • 在常见的数字信号系统中,大部分传感器信号被化成电压信号,而ADC 把电压模拟信号转换成易于计算机存储、处理的数字编码,由计算机处理完成后,再由DAC 输出电压模拟信号,该电压模拟信号常常用来驱动某些执行器件,使人类易于感知。
  • STM32 具有片上DAC 外设,它的分辨率可配置为8 位或12 位的数字输入信号,具有两个DAC输出通道,这两个通道互不影响,每个通道都可以使用DMA 功能,都具有出错检测能力,可外部触发。
  • 在双DAC模式下,2个通道可以独立地进行转换,也可以同时进行转换并同步地更新2个通道的输出。DAC可以通过引脚输入参考电压VREF+ 以获得更精确的转换结果。
  • DAC工作在12位模式时,数据可以设置成左对齐或右对齐

3.DAC功能框图
在这里插入图片描述
(1)整个DAC 模块围绕框图下方的“数字至模拟转换器x”展开,它的左边分别是参考电源的引脚:VDDA、VSSA 及Vref+,其中STM32 的DAC 规定了它的参考电压Vref+ 输入范围为2.4——3.3V。
(2)“数字至模拟转换器x”的输入为DAC 的数据寄存器“DORx”的数字编码,经过它转换得的模拟信号由图中右侧的“DAC_OUTx”输出。而数据寄存器“DORx”又受“控制逻辑”支配,它可以控制数据寄存器加入一些伪噪声信号或配置产生三角波信号。
(3)图中的左上角为DAC 的触发源,DAC根据触发源的信号来进行DAC 转换,其作用就相当于DAC 转换器的开关,它可以配置的触发源为外部中断源触发、定时器触发或软件控制触发

4.参考电压
与ADC 外设类似,DAC 也使用VREF+ 引脚作为参考电压,在设计原理图的时候一般把VSSA 接地,把VREF+ 和VDDA 接3.3V,可得到DAC 的输出电压范围为:0~3.3V。

如果想让输出的电压范围变宽,可以在外部加一个电压调理电路,把0~3.3V 的DAC 输出抬升到特定的范围即可。

5.数模转换及输出通道
框图中的“数字至模拟转换器x”是核心部件,整个DAC 外设都围绕它而展开。它以左边的VREF+作为参考电源,以DAC 的数据寄存器“DORx”的数字编码作为输入,经过它转换得的模拟信号由右侧的“DAC_OUTx”通道输出。

其中各个部件中的“x”是指设备的标号,在STM32 中具有2 个这样的DAC 部件,每个DAC 有1 个对应的输出通道连接到特定的引脚,即:PA4-通道1,PA5-通道2,为避免干扰,使用DAC 功能时,DAC 通道引脚需要被配置成模拟输入功能(AIN)。

6.触发源及DHRx 寄存器
在使用DAC 时,不能直接对上述DORx 寄存器写入数据,任何输出到DAC 通道x 的数据都必须写入到DHRx 寄存器中(其中包含DHR8Rx、DHR12Lx 等,根据数据对齐方向和分辨率的情况写入到对应的寄存器中)。

数据被写入到DHRx 寄存器后,DAC 会根据触发配置进行处理,若使用硬件触发,则DHRx 中的数据会在3 个APB1 时钟周期后传输至DORx,DORx 随之输出相应的模拟电压到输出通道;

若DAC 设置为外部事件触发,可以使用定时器(TIMx_TRGO)、EXTI_9 信号或软件触发(SWTRIGx)这几种方式控制数据DAC 转换的时机,例如使用定时器触发,配合不同时刻的DHRx 数据,可实现DAC 输出正弦波的功能。

二、HAL库配置初始化

1.配置时钟
在这里插入图片描述
在这里插入图片描述
2.开启DAC
在这里插入图片描述

  • Output Buffer:关闭后可输出更低的电压值
  • Trigger:触发方式,这里选择TIM2定时器触发
  • Wave generation mode:关闭后可输出自定义波形

3.开启DMA
在这里插入图片描述

  • Mode:选择循环模式
  • Data Width:发送数据长度,半字即可

4.配置定时器
在这里插入图片描述

  • 初始化定时器触发频率为1:84MHZ /(84 * 20) =50000HZ
  • 触发事件选择“更新触发事件”

5.配置中断优先级
在这里插入图片描述

三、RTT中初始化

1.board.h
在board.h中添加宏定义,使用DAC的驱动函数

#define BSP_USING_DAC1

2.board.c
在board.c中添加如下初始化代码,进行DAC引脚初始化。

/**
 * @brief DAC初始化
 * @param dacHandle
 */
void HAL_DAC_MspInit(DAC_HandleTypeDef* dacHandle)
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    if (dacHandle->Instance == DAC)
    {
        /* DAC clock enable */
        __HAL_RCC_DAC_CLK_ENABLE();
        __HAL_RCC_GPIOA_CLK_ENABLE();

        /**DAC GPIO Configuration
         PA5     ------> DAC_OUT2
         */
        GPIO_InitStruct.Pin = GPIO_PIN_5;
        GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
        GPIO_InitStruct.Pull = GPIO_NOPULL;
        HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

        /* DAC DMA Init */
        /* 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_HALFWORD;
        hdma_dac2.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;
        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(dacHandle, DMA_Handle2, hdma_dac2);
    }
}

3.dac.c

#include "dac.h"

// 方波缓冲波形
uint16_t signalSquareBuffer[DAC_SAMPLE_POINT] = {0};

/*======================================================##### 外部调用 #####==================================================*/
/**
 * @brief DAC输出DMA初始化
 */
void DAC_TIME_DMA_Init(void)
{
    DAC_ChannelConfTypeDef sConfig = {0};
    TIM_ClockConfigTypeDef sClockSourceConfig = {0};
    TIM_MasterConfigTypeDef sMasterConfig = {0};

    /* DMA controller clock enable */
    __HAL_RCC_GPIOA_CLK_ENABLE();
    __HAL_RCC_DMA1_CLK_ENABLE();

    /** DAC Initialization
     */
    hdac.Instance = DAC;
    if (HAL_DAC_Init(&hdac) != HAL_OK)
    {
        Error_Handler();
    }

    /** DAC channel OUT2 config
     */
    sConfig.DAC_Trigger = DAC_TRIGGER_T2_TRGO;
    sConfig.DAC_OutputBuffer = DAC_OUTPUTBUFFER_DISABLE;
    if (HAL_DAC_ConfigChannel(&hdac, &sConfig, DAC_CHANNEL_2) != HAL_OK)
    {
        Error_Handler();
    }

    htim2.Instance = TIM2;
    htim2.Init.Prescaler = 20 - 1;
    htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
    htim2.Init.Period = 84 - 1;
    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();
    }

    /* DMA interrupt init */
    /* DMA1_Stream6_IRQn interrupt configuration */
    HAL_NVIC_SetPriority(DMA1_Stream6_IRQn, 0, 0);
    HAL_NVIC_EnableIRQ(DMA1_Stream6_IRQn);
}

/**
 * @brief 启动DAC的定时器和DMA输出
 */
void Start_Dac_Time_DMA(void)
{
    HAL_TIM_Base_Start(&htim2);
    HAL_DAC_Start_DMA(&hdac, DAC_CHANNEL_2, (uint32_t *)signalSquareBuffer, DAC_SAMPLE_POINT, DAC_ALIGN_12B_R);
}

/**
 * @brief 关闭DAC的定时器和DMA输出
 */
void Stop_Dac_Time_DMA(void)
{
    HAL_TIM_Base_Stop(&htim2);
    HAL_DAC_Stop_DMA(&hdac, DAC_CHANNEL_2);
}

/**
 * @brief 方波信号缓存填充
 * @param duty: 占空比设置0 ~ 100
 * @param vol : 电压设置 0-2.5V
 */
void signalSquare (uint16_t  duty, float vol)
{
    if (duty < 0)
        duty = 0;
    else if (duty > DAC_SAMPLE_POINT)
        duty = DAC_SAMPLE_POINT;

    if (vol > DAC_OUTPUT_MAX)
        vol = DAC_OUTPUT_MAX;
    else if (vol < 0)
        vol = 0;

    for (int i = 0; i < duty; i++)
    {
        signalSquareBuffer[i] = vol * DAC_REFERENCE_VALUE / DAC_OUTPUT_MAX;
    }

    for (int i = duty; i < DAC_SAMPLE_POINT; i++)
    {
        signalSquareBuffer[i] = 0;
    }
}

/**
 * @brief 输出方波参数设置
 * @param duty:占空比设置
 * @param vol :输出电压
 * @param fre :输出频率
 */
void I_DAC_Output(uint16_t duty, float vol, uint32_t fre)
{
    signalSquare(duty, vol);

    //计算PWM频率,所对应的自动重装载值   ---> ARR = 主频 / (预分频+1) / 预期PWM频率(Hz) - 1
    float prescaler = (TIMER_FREQ * 1.0) / (84 * DAC_SAMPLE_POINT * fre) - 1;

    //配置PSC预分频值
    __HAL_TIM_SET_PRESCALER(&htim2, prescaler);
}

/**
 * @brief 实时输出方波数据
 */
void I_DAC_Output_Signal(void)
{
    uint16_t pwr, pt, it;

    // 数据改变后再输出
    if (g_laser.param_flag == 1)
    {
        g_laser.param_flag = 0;
        pwr = g_laser.laser_pwr;
        pt = g_laser.laser_pt;
        it = g_laser.laser_it;
        I_DAC_Output(100.0 * pt / (pt + it), pwr * I_DAC_MAX / I_PWR_MAX, 1000.0 / (pt + it));
    }
}
/*=====================================================#######  END  #######=================================================*/

4.dac.h

#ifndef APPLICATIONS_SYSTEM_INC_DAC_H_
#define APPLICATIONS_SYSTEM_INC_DAC_H_

#include <rtthread.h>

/**====================================================###### 宏定义 ######==================================================*/
#define  DAC_OUTPUT_MAX         (3.3f)          // DAC输出最大值
#define  I_DAC_MAX              (2.5f)          // I_DAC输出最大电压
#define  I_PWR_MAX              850             // I_DAC输出功率最大值
#define  DAC_REFERENCE_VALUE    4095            // DAC参考值
#define  TIMER_FREQ             84000000        // 定时器主频
#define  DAC_SAMPLE_POINT       100             // 采样点个数
/**====================================================#######  END  #######=================================================*/

/**=================================================##### 函数及变量声明 #####===============================================*/
extern void DAC_TIME_DMA_Init(void);                                    // DAC输出DMA初始化
extern void Start_Dac_Time_DMA(void);                                   // 启动DAC的定时器和DMA输出
extern void Stop_Dac_Time_DMA(void);                                    // 关闭DAC的定时器和DMA输出
extern void signalSquare (uint16_t  duty, float vol);                   // 方波信号缓存填充
extern void I_DAC_Output(uint16_t duty, float vol, uint32_t pscVal);    // 输出方波参数设置
extern void I_DAC_Output_Signal(void);                                  // 实时输出方波数据
/**====================================================#######  END  #######=================================================*/

#endif /* APPLICATIONS_SYSTEM_INC_DAC_H_ */

5.其他波形函数

//   正弦波信号缓存填充
void signalSin(void)
{
    for (int i = 0; i < 100; i++)
    {
        signalSinBuffer[i] = ((sin(i * 2 * PI / 100) + 1) * (4096 / 2));
    }
}

//   三角波信号缓存填充,duty: 对称性设置 0 ~ 100
void signalRamp(uint16_t duty)
{
    if(duty < 0)    duty = 0;
    if (duty > 100) duty = 100;

    for (int i = 0; i < duty; i++)
    {
        signalRampBuffer[i] = i * 4095 / duty;
    }

    for (int i = duty; i < 100; i++)
    {
        signalRampBuffer[i] = (100 - i) * 4095 / (100 - duty);
    }
}

四、测试验证

1.main.c测试

int main(void)
{
    DAC_TIME_DMA_Init();        // DAC输出DMA初始化
    Start_Dac_Time_DMA();       // 启动DAC的定时器和DMA输出

    while (1)
    {
    	I_DAC_Output(50, 5, 500);
        rt_thread_mdelay(1);
    }

    return RT_EOK;
}

总结:通过使用示波器测试输出的波形,可以正常观察到方波信号,并且可以更改方波的幅度、频率和占空比。同时上面还附带了其他波形生成的函数以供使用。

标签:duty,DMA,HAL,DAC,void,Init
From: https://blog.csdn.net/Hei_se_meng_yan/article/details/140503492

相关文章

  • AndroidQ 打通应用层到HAL层---(HIDL服务实现)
    什么是HIDLHIDL全称为HALinterfacedefinitionlanguage(发音为“hide-l”)是用于指定HAL和其用户之间的接口的一种接口描述语言(IDL),AndroidO开始引入了HIDL这个概念,HIDL和应用层AIDL差不多,AIDL常用于连接App和Framework,HIDL则是用来连接Framework和HAL,AIDL使用Binder通信,HI......
  • JNI和HAL 的区别
    JNI(JavaNativeInterface)和HAL(HardwareAbstractionLayer)在Android系统中都扮演着与本地代码交互的重要角色,但它们的功能和应用场景有所不同。以下是它们之间的关系和区别:JNI(JavaNativeInterface)用途:JNI是一种框架,允许Java代码调用本地(Native)C/C++代......
  • Datawhale AI 夏令营 task2
    Task1的baseline我们是基于经验模型(使用均值作为结果数据)来解决的问题,Task2版本教程将使用机器学习模型解决本次问题,模型使用简单,数据不需要过多预处理;使用机器学习方法一般主要需要从获取数据&增强、特征提取和模型三个方面下手。一般的使用机器学习模型解决问题的主要步骤......
  • 【Datawhale AI夏令营】 Task1 学习笔记
    目录一、baseline二、NLP模型自然语言处理的主要任务自然语言处理的技术和方法自然语言处理的应用自然语言处理的挑战 三、赛题理解 赛题背景赛事任务术语词典干预术语词典干预的主要特点术语词典干预的实施方法四、实操 步骤体会感想   学习目标:跑......
  • RDMA 高性能架构基本原理与设计方案
    RDMA的主要优点包括低延迟、高吞吐量、减少CPU负担和支持零拷贝网络。它允许数据直接在网络接口卡(NIC)和内存之间传输,减少了数据传输过程中的中间环节,从而显著降低了延迟。RDMA技术能够实现高速的数据传输,适用于需要大量数据交换的应用场景。由于数据传输不需要CPU的参与,CPU可......
  • HAL库源码移植与使用之FSMC (例子加思路与理解,万字良心保证你能听懂)
    FMC和FSMC是一样的东西,只是FMC更可控地址更多又可以驱动SDRAM,用法都一样!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!总结:其实fsmc更像是一个有着特定转换时序功能的寄存器,每个fsmc映射在芯片内存里的地址都有一个这样的寄存器,你往这个映射的地址里赋值,这个赋值信息先到达对应fsmc寄存器,他不会像普通寄存器一样直接控制......
  • HAL库源码移植与使用之驱动LCD屏
    LCD屏会有烧屏残影的风险,但因其价格便宜他非常适合用于单片机显示显示屏分为以下几种:他的组成部包含玻璃基板、背光、驱动IC等LCD接口的种类MCU很简单,连51单片机都能驱动,但无法频繁刷新,一般有着20几个引脚引出的就是MCU接口我们常用的是就是MCU,下面讲的也是LCD屏幕MCU驱动......
  • HAL库源码移植与使用之正点原子OLED使用解析
    正点原子的OLED是使用SSD1306来驱动的,并设计了多种通讯方式,通过背后的焊点来选择这里以正点原子开发板最常用的8080并口通讯来讲引脚定义各正点原子开发板对OLED的接线8080时序图发送数据示例代码voidoled_wr_byte(uint8_tdata,uint8_tcmd){ OLED_RS(cmd); /......
  • 电力需求预测挑战赛——Datawhale AI 夏令营第2期【从零入门AI竞赛之机器学习】
    赛事官网https://challenge.xfyun.cn/h5/detail?type=electricity-demand&ch=dw24_uGS8Gs学习者手册https://exn8g66dnwu.feishu.cn/docx/T7WGd7goqowRvFxwoApclo9Pn0bTask1——传统时序模型(2024/7/14)阶段要求根据文档跑通提交并拿下第一个分数;学习相关知识点,理解赛题。......
  • 毕业设计 基于机器视觉的PCB焊接缺陷检测系统(Halcon+C#)
    毕业设计基于机器视觉的PCB焊接缺陷检测系统一、功能需求检测PCB板的焊接缺陷:漏焊、虚焊等二、开发环境1、Halcon2、C#三、运行效果处理图片:运行视频:毕业设计基于机器视觉的PCB焊接缺陷检测系统毕业设计资料(C#软件源码+Halcon算法源码+开题报告+毕业设计+......