ADDA详解

ADC基本概念

  1. 分辨率(Resolution)

    • 分辨率决定了 ADC 能将模拟电压"切"成多少份,也就是它能区分多么细微的电压变化。通常用"位" (bits) 来表示。

      例如,一个 12 位的 ADC,能表示 2(12次方)=4096 个不同的数字级别。如果参考电压是 3.3V,那么它能分辨的最小电压变化大约是 3.3V/4096≈0.0008V 或 0.8mV。

    • 分辨率越高,测量越精确,但转换速度可能越慢,成本也越高

  2. 参考电压(Reference Voltage, Vref)

    • 参考电压是 ADC 进行测量的"标尺"。它定义了 ADC 能够转换的模拟电压的最大值。输入的模拟电压不能超过参考电压。

    • 参考电压是外部需要AD转换的最高电压

    • 数字值=(输入电压/Vref)×(2n−1) 数字值 = (输入电压 / Vref) × (2^n - 1) 数字值=(输入电压/Vref)×(2n1)

      假设Vref是3.3V,输入电压是1.65V,ADC是12位的:

      数字值 = (1.65 / 3.3) × 4095 = 0.5 × 4095 = 2047.5 ≈ 2048

  3. 采样率(Samping Rating)

    • 采样率表示 ADC 每秒钟进行多少次模数转换。单位通常是 SPS (Samples Per Second) 或 Hz。
    • 根据奈奎斯特采样定理,采样率必须至少是被测模拟信号最高频率的两倍,才能无失真地还原原始信号。采样率越高,越能捕捉到快速变化的模拟信号。
    • 假设你测一个声音信号,最高频率是20kHz(人类能听到的最高音)。根据奈奎斯特采样定理,采样率得至少是40kHz,才能无失真地录下这个声音。
      现实中,CD音乐用44.1kHz的采样率,就是为了保证能完美捕捉20kHz以下的声音。
  4. 模拟量与数字量

    • 模拟 (Analog): 像音量旋钮,可以在一个范围内连续变化,有无限多个可能的音量值。现实世界的物理量大多是模拟的。
    • 数字 (Digital): 像计算器的按键,只有有限的、离散的状态(0, 1, 2…)。计算机和单片机处理的是数字信号。

ADC的配置

  1. 分辨率(Resolution)

    • 决定ADC能分辨的最小电压变化,通常以bits表示。12bits 10bits 8bits等等
    • 分辨率越高,精度越高,但转换时间可能越长。
  2. 数据对齐(Data Alignment)

    • ADC转换的结果是一个数字值,但它储存再16位或32位的数据寄存器中。
    • 有左对齐和右对齐,一般选择右对齐。
  3. 转换模式

    1. 单次转换(Single Conversion Mode):每次触发(软件或硬件)只进行一次转换,然后停止。需要再次触发才能进行下一次转换。常用于轮询法或简单的中断法。
    2. 连续转换(Continuous Conversion Mode):首次触发后,ADC 会自动连续不断地进行转换,每次转换完成后立即开始下一次转换,直到手动停止。常用于配合 DMA 进行高速连续采样。(在cubemax中使能Continuous Conversion Mode)
  4. 时钟来源与分频(Clock Source & Prescaler)

    • ADC 模块通常有一个独立的可配置时钟源。这个时钟源通常来自 APB2 总线时钟 (PCLK2) 或其他专用时钟 (如 HSI, PLL 输出等),然后经过一个预分频器 (Prescaler) 进一步分频后,才作为 ADC 的工作时钟 (ADCCLK)。
    • 在时钟分频中通常选择4 还有 2 6 8 等等
  5. 采样时间(Sampling Time)

    • 指 ADC 在转换前对输入信号进行采样保持的时间长度。这个时间需要足够长,以确保 ADC 内部的采样保持电容 (Csh) 完全充电到输入引脚的电压
    • 采样时间以ADCCLK周期来表示。1.5cycles 7.5 13.5 28.5 等等
  6. 扫描模式(Scan Conversion Mode)

    • 当需要测量多个模拟输入通道时,需要配置 ADC 的扫描模式。这些设置决定了 ADC 如何依次转换多个通道。
    • Disable:单通道 adc只转换配置的第一个通道Rank1
    • Enable:adc会按照配置Rank的顺序,依次转换多个通道
  7. 在规则通道配置(Regular Conversion Launch)

    • Number Of Conversion:定义在一次扫描转换序列中转换的通道总数

    • Rank 配置:

      对于序列中的每一个 Rank (位置),需要指定:

      • Channel: 选择要在此 Rank 转换的 ADC 通道 (e.g., Channel 0, Channel 1, Temp Sensor, Vbat)。
      • Sampling Time: 为此通道设置采样时间(可以每个通道不同)。
  8. 扫描结束选择(End of Conversion Selection - EOC)

    • 这个设置决定了何时产生 EOC (转换结束) 标志或中断。
    • End of single conversion: 每个通道转换完成后都会产生 EOC 标志。
    • End of sequence conversion: DMA常用 只有当整个扫描序列中的所有通道都转换完成后,才会产生 EOC 标志。这在使用 DMA 读取整个序列结果时非常有用
  9. 不连续转换(Discontiunous Conversion Mode)

    • 这是一个可选的高级模式,允许将扫描序列分成几个小组进行转换。每次外部触发只会转换一个小组。
    • 如果启用,需要设置 “Number Of Discontinuous Conversions” 来指定每个小组包含的通道数量。例如,扫描 6 个通道,分成 3 组,每次触发转换 2 个通道。
  10. ADC外部触发配置

    • 定时器触发(Timer Trigger)
      • Timer x Trigger Out event (TRGO): 定时器的主要触发输出信号,通常配置为由定时器的更新事件 (Update Event - UEV) 产生。这是实现固定频率采样的关键。
      • Timer x Capture Compare x event: 定时器的捕获/比较匹配事件。
    • 软件出发(Software Trigger):通过调用 HAL_ADC_Start() 等函数来手动启动转换。
    • 外部引脚触发:由外部 GPIO 引脚上的边沿事件触发
  11. 触发边沿(External Trigger Conversion Edge)

    • 上升沿 下降沿 双边沿 disable(软件触发)
  12. 中断配置(需要打开中断选项)

    1. ADC自身中断

      • 需要在 CubeMX 的 “NVIC Settings” 标签页中启用 ADCx global interrupt

        • EOC (End of Conversion): 中断法使用 单次或序列转换结束。在非 DMA 的中断驱动方式中使用。在 DMA 模式下,通常不直接使用 ADC 的 EOC 中断来处理数据。

        • AWD (Analog Watchdog): 模拟看门狗事件。当 ADC 转换结果超出预设的上下阈值时触发。

        • OVR (Overrun): 重要 数据覆盖错误。当新的转换结果在旧结果被读取前就绪时发生。通常指示 CPU 处理速度跟不上 ADC 转换速度(尤其是在中断模式或未使用 DMA 时)。

          ADC 自身中断 (ADC Global Interrupt) / EOC (End of Conversion) 中断回调函数:

          • 对于单个ADC转换结束或序列转换结束,中断服务程序(ISR)中会调用一个回调函数。在HAL库中,这个函数通常是:

            void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc);
            

            这个函数在ADC完成一次转换(对于单次转换)或完成整个序列转换(对于连续或扫描模式)后被调用。

          • 此外,如果启用了不连续模式(Discontinuous mode),当每个组的转换完成时,可能会调用:

            void HAL_ADC_ConvHalfCpltCallback(ADC_HandleTypeDef* hadc); // 仅在DMA模式下,当一半数据传输完成时触发
            

          ADC 模拟看门狗中断回调函数(如果启用):

          • 如果配置了模拟看门狗(Analog Watchdog),当模拟输入电压超出预设阈值时,会触发中断,并调用:

            void HAL_ADC_LevelOutOfWindowCallback(ADC_HandleTypeDef* hadc);
            

          ADC 错误中断回调函数(如果发生错误):

          • 当ADC操作中发生错误时(例如溢出),会调用:

            void HAL_ADC_ErrorCallback(ADC_HandleTypeDef *hadc);
            
    2. DMA通道中断

      • 需要在 CubeMX 的 “NVIC Settings” 标签页中启用 ADC 使用的那个 DMA 通道的中断 (例如 DMA1 Stream 0 global interrupt)。
        • TC (Transfer Complete): DMA常用 DMA 完成了整个缓冲区的传输(在 Circular 模式下,传输了 Length 个数据)。常用于触发数据处理任务(如 Ping-Pong 缓冲的切换,或通知主循环处理整个缓冲区)。
        • HT (Half Transfer): DMA可选 DMA 完成了一半缓冲区的传输。可用于实现 Ping-Pong 缓冲,在传输后半段时处理前半段数据。
        • TE (Transfer Error): 错误处理 DMA 传输过程中发生错误(如总线错误)。
  13. DMA Continuous Requests 的用处

    • DMA Continuous Requests 启用时,ADC 完成一次转换后,DMA 会自动将转换结果传输到内存,并且 ADC 会在下一个转换完成时继续触发 DMA 传输,而无需软件再次启动 ADC 或 DMA。如果不使能则需要在每一次接受数据后手动用软件进行启动。

TIM定时器设置

Mode (模式) 部分

这部分定义了定时器的基本工作方式和时钟来源。

  • Slave Mode (从模式)

    • 作用: 决定定时器是否作为“从机”运行,即其计数操作是否由外部事件(如另一个定时器的输出、外部引脚信号等)触发。

    • 选项: Disable(禁用)表示定时器独立运行;如果启用,你可以选择不同的从模式(如门控模式、触发模式、外部时钟模式等),使定时器受控于外部信号。

  • Trigger Source (触发源)

    • 作用: 当定时器处于从模式时,此选项用于指定是哪个具体的外部事件或内部信号将作为其触发源。

    • 常见触发源: 可以是GPIO引脚上的外部信号(TIx)、其他定时器的输出(如TIM1的TRGO)、内部复位信号等。

  • Clock Source (时钟源)

    • 作用: 决定了定时器计数的基础时钟信号来源。

    • 选项:

      • Internal Clock (内部时钟): 最常用选项。定时器使用微控制器内部提供的APB总线时钟作为其输入时钟。这是软件控制下最灵活和普遍的时钟源。
      • External Clock Mode 1/2: 使用外部引脚上的信号作为时钟源。这允许定时器直接计数外部脉冲。
      • Internal Trigger Output (ITR): 使用另一个定时器的TRGO(触发输出)作为本定时器的时钟源,实现定时器间的级联。
  • Channel1, Channel2, Channel3, Channel4 (通道)

    • 作用: TIM3通常有四个独立的通用通道,每个通道都可以配置为不同的功能,如:

      • Output Compare (OC): 用于在计数器达到特定值时生成输出脉冲或触发中断。
      • Input Capture (IC): 用于测量输入信号的脉冲宽度、周期或频率。
      • PWM Generation (PWM): 用于生成脉冲宽度调制信号,控制电机速度、LED亮度等。
      • One Pulse Mode (OPM): 生成一个单一的脉冲。
      • Forced Output: 强制输出为高或低电平。
    • 选项: 每个通道都可以独立设置为上述模式或Disable

  • Combined Channels (组合通道)

    • 作用: 某些高级定时器功能(如互补PWM输出)可能需要组合使用两个通道。当启用此功能时,通常会将一个通道配置为主通道,另一个配置为互补通道。

Counter Settings (计数器设置) 部分

这部分定义了定时器计数方式的核心参数。

  • Prescaler (PSC) (预分频器)

    • 作用: 对定时器的输入时钟进行分频,以降低计数器的计数速度。

    • 计算: 定时器的实际计数频率 = 定时器时钟频率 / (PSC + 1)。

    • : 0到65535(16位)。PSC为0表示不分频。

  • Counter Mode (计数模式)

    • 作用: 定义计数器如何进行计数。

    • 选项:

      • Up (向上计数): 计数器从0开始向上计数,达到Counter Period值后溢出,然后从0重新开始计数。这是最常用模式。
      • Down (向下计数): 计数器从Counter Period值开始向下计数,达到0后溢出,然后从Counter Period重新开始计数。
      • Center Aligned Mode 1/2/3 (中央对齐模式): 计数器先向上计数到Counter Period,然后向下计数到0,形成一个对称的波形。常用于高级PWM应用。
  • Counter Period (AutoReload Register) (计数周期 / 自动重载寄存器 ARR)

    • 作用: 定义了计数器达到此值后会溢出并重新开始计数(或改变计数方向)。这个值决定了定时器的周期。
    • 计算: 定时器溢出频率 = 定时器实际计数频率 / (ARR + 1)。
    • : 0到65535(16位)。
  • Internal Clock Division (CKD) (内部时钟分频)

    • 作用: 用于选择定时器内部数字滤波器、死区生成器等使用的时钟分频系数。它通常与PWM的死区时间生成和输入捕获的数字滤波有关,不直接影响主计数器的时钟。

    • 选项: No Division(不分频)、Div2(2分频)、Div4(4分频)。

  • Auto-reload preload (自动重载预加载)

    • 作用: 决定AutoReload Register (ARR)的值是否会预加载。
    • 选项: Enable(启用)或 Disable(禁用)。
      • Enable: 当ARR值改变时,新的值会立即加载到影子寄存器中,但只有在下一次计数器溢出时才真正生效。这有助于平滑地改变定时器周期,避免毛刺。

Trigger Output (TRGO) Parameters (触发输出参数) 部分

这部分配置定时器作为主模式时如何生成触发信号。

  • Master/Slave Mode (MSM bit) (主/从模式)

    • 作用: 通常指的是TIMx_CR2寄存器中的MSM(Master/Slave Mode)位。当设置为Enable时,可以延迟触发事件的效果,直到定时器内部的同步事件发生。对于简单的TRGO输出,通常设置为Disable
  • Trigger Event Selection (触发事件选择)

    • 作用: 定义什么事件会触发TRGO输出信号。这个输出信号可以连接到其他定时器作为触发源或时钟源,也可以连接到ADC、DAC等外设。
    • 常见选项:
      • Reset (复位): 当定时器计数器复位(通常是溢出或UG位设置时)时产生触发。
      • Enable (使能): 定时器使能时产生触发。
      • Update (更新): 当计数器溢出/下溢,或者ARR、PSC值更新时产生触发。
      • Compare Pulse (比较脉冲): 当OCREF(输出比较参考)信号发生变化时产生触发。
      • Output Compare Ref signals (OCREF): 某个输出比较通道的参考信号作为触发源。
      • TIMx_ITR0/1/2/3: 其他定时器的内部触发信号。

ADC读取

  1. ADC轮询读取
// 在需要读取 ADC 的地方调用,比如一个任务函数内
void adc_read_by_polling(void) 
{
    // 1. 启动 ADC 转换
    HAL_ADC_Start(&hadc1); // hadc1 是你的 ADC 句柄

    // 2. 等待转换完成 (阻塞式)
    //    参数 1000 表示超时时间 (毫秒)
    if (HAL_ADC_PollForConversion(&hadc1, 1000) == HAL_OK) 
    {
        // 3. 转换成功,读取数字结果 (0-4095 for 12-bit)
        adc_val = HAL_ADC_GetValue(&hadc1);

        // 4. (可选) 将数字值转换为实际电压值
        //    假设 Vref = 3.3V, 分辨率 12 位 (4096)
        voltage = (float)adc_val * 3.3f / 4096.0f; 

        // (这里可以加入你对 voltage 或 adc_val 的处理逻辑)
        // my_printf(&huart1, "ADC Value: %lu, Voltage: %.2fV\n", adc_val, voltage);

    } 
    else 
    {
        // 转换超时或出错处理
        // my_printf(&huart1, "ADC Poll Timeout!\n");
    }
    
    // 5. (重要)如果 ADC 配置为单次转换模式,通常不需要手动停止。
    //    如果是连续转换模式,可能需要 HAL_ADC_Stop(&hadc1);
    // HAL_ADC_Stop(&hadc1); // 根据你的 CubeMX 配置决定是否需要
}
  • HAL_ADC_PollForConversion(&hadc1, 1000):参数1000是检测ADC转换的时间,当超过1000没有转换成功是函数返回超时
// --- 全局变量 --- 
#define ADC_DMA_BUFFER_SIZE 32 // DMA缓冲区大小,可以根据需要调整
uint32_t adc_dma_buffer[ADC_DMA_BUFFER_SIZE]; // DMA 目标缓冲区
__IO uint32_t adc_val;  // 用于存储计算后的平均 ADC 值
__IO float voltage; // 用于存储计算后的电压值

// --- 初始化 (通常在 main 函数或外设初始化函数中调用一次) ---
void adc_dma_init(void)
{
    // 启动 ADC 并使能 DMA 传输
    // hadc1: ADC 句柄
    // (uint32_t*)adc_dma_buffer: DMA 目标缓冲区地址 (HAL库通常需要uint32_t*)
    // ADC_DMA_BUFFER_SIZE: 本次传输的数据量 (缓冲区大小)
    HAL_ADC_Start_DMA(&hadc1, (uint32_t*)adc_dma_buffer, ADC_DMA_BUFFER_SIZE);
}

// --- 处理任务 (在主循环或定时器回调中定期调用) ---
void adc_task(void)
{
    uint32_t adc_sum = 0;
    
    // 1. 计算 DMA 缓冲区中所有采样值的总和
    //    注意:这里直接读取缓冲区,可能包含不同时刻的采样值
    for(uint16_t i = 0; i < ADC_DMA_BUFFER_SIZE; i++)
    {
        adc_sum += adc_dma_buffer[i];
    }
    
    // 2. 计算平均 ADC 值
    adc_val = adc_sum / ADC_DMA_BUFFER_SIZE; 
    
    // 3. (可选) 将平均数字值转换为实际电压值
    voltage = ((float)adc_val * 3.3f) / 4096.0f; // 假设12位分辨率, 3.3V参考电压

    // 4. 使用计算出的平均值 (adc_val 或 voltage)
    // my_printf(&huart1, "Average ADC: %lu, Voltage: %.2fV\n", adc_val, voltage);
}
  • ADC为循环模式,DMA Continuous Requestion为使能状态,所以不需要再调用 HAL_ADC_Start_DMA使能DMA
  • 在配置DMA的缓冲区时长配置为32位,因为 HAL_ADC_Start_DMA这个函数接收的参数就是32位的
  • 为什么HAL_ADC_Start_DMA(&hadc1, (uint32_t*)adc_dma_buffer, ADC_DMA_BUFFER_SIZE);这个函数的第三个参数不能填sizeif(adc_dma_buffer)?
    • HAL_ADC_Start_DMA 函数的第三个参数是传输数据项的数量,而不是整个缓冲区的大小(以字节为单位)。
    • ADC_DMA_BUFFER_SIZE 的值是 32。这告诉 DMA 要传输 32 个 ADC 采样值。
    • sizeof(adc_dma_buffer) 的值是 32 * sizeof(uint16_t) = 32 * 2 = 64 字节。如果你把 64 传给 HAL_ADC_Start_DMA,DMA 会认为要传输 64 个 ADC 采样值,但这会超出你的缓冲区实际可容纳的元素数量,导致缓冲区溢出或不可预测的行为。
  1. 这用定时器进行定时采样,ADC循环模式关闭,DMA连续请求关闭
// --- 宏定义和外部变量 ---
#define BUFFER_SIZE 1000        // DMA 缓冲区大小 (总点数)

extern DMA_HandleTypeDef hdma_adc1; // 假设这是 ADC1 对应的 DMA 句柄
extern ADC_HandleTypeDef hadc1;    // ADC1 句柄
extern UART_HandleTypeDef huart1; // 用于 my_printf 的 UART 句柄

// --- 全局变量 ---
uint32_t dac_val_buffer[BUFFER_SIZE / 2]; // 用于存储处理后的 ADC 数据
__IO uint32_t adc_val_buffer[BUFFER_SIZE]; // DMA 目标缓冲区 (存储原始 ADC 数据)

__IO uint8_t AdcConvEnd = 0;             // ADC 转换完成标志 (一个块完成)

// --- 初始化函数 (在 main 或外设初始化后调用) ---
void adc_tim_dma_init(void)
{
    // 启动 ADC 的 DMA 传输,请求 BUFFER_SIZE 个数据点
    // 注意:这里假设 hadc1 已经配置为合适的触发模式 (定时器或软件)
    //       且 DMA 配置为 Normal 模式
    HAL_ADC_Start_DMA(&hadc1, (uint32_t *)adc_val_buffer, BUFFER_SIZE);

    // 显式禁用 DMA 半传输中断 (如果不需要处理半满事件)
    __HAL_DMA_DISABLE_IT(&hdma_adc1, DMA_IT_HT);

    // 注意:如果使用定时器触发,需要在此处或之前启动定时器
    HAL_TIM_Base_Start(&htimX); // 替换 htimX 为实际定时器句柄
}

// --- ADC 转换完成回调函数 (由 DMA TC 中断触发) ---
// 当 DMA 完成整个缓冲区的传输 (Normal 模式下传输 BUFFER_SIZE 个点) 时触发
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
{
    // 检查是否是由我们关心的 ADC (hadc1) 触发的
    if (hadc->Instance == ADC1) // 或 if(hadc == &hadc1)
    {
        HAL_ADC_Stop_DMA(hadc);

        // 设置转换完成标志,通知后台任务数据已准备好
        AdcConvEnd = 1;
    }
}

// --- 后台处理任务 (在主循环或低优先级任务中调用) ---
void adc_task(void)
{
    // 检查转换完成标志
    if (AdcConvEnd)
    {
        // 处理数据: 从原始 ADC 缓冲区提取数据到 dac_val_buffer
        // 示例逻辑:提取扫描转换中第二个通道的数据 (?)
        for(uint16_t i = 0; i < BUFFER_SIZE / 2; i++)
        {
            // 假设 adc_val_buffer[0] 是通道1, adc_val_buffer[1] 是通道2, ...
            dac_val_buffer[i] = adc_val_buffer[i * 2 + 1];
        }

        // 打印处理后的数据 (示例)
        for(uint16_t i = 0; i < BUFFER_SIZE / 2; i++)
        {
            // 注意: my_printf 是自定义函数, 需确保其存在且可用
            my_printf(&huart1, "{dac}%d", (int)dac_val_buffer[i]);
        }

        // 清理处理后的缓冲区 (可选)
        memset(dac_val_buffer, 0, sizeof(uint32_t) * (BUFFER_SIZE / 2));

        // 清除转换完成标志,准备下一次采集
        AdcConvEnd = 0;

        // 重新启动 ADC 的 DMA 传输,采集下一个数据块
        // 注意: 需要确保 ADC 状态适合重启 (例如没有错误)
        HAL_ADC_Start_DMA(&hadc1, (uint32_t *)adc_val_buffer, BUFFER_SIZE);
        // 再次禁用半传输中断 (如果 Start_DMA 会重新启用它)
        __HAL_DMA_DISABLE_IT(&hdma_adc1, DMA_IT_HT);
    }
}
  • extern 用来声明外部变量

  • HAL_ADC_ConvCpltCallback这个函数在没有配置DMA和配置了DMA的触发机制不相同

    • 在没有配置DMA时:这意味着,如果你配置了单个通道,每次转换完成都会触发;如果你配置了多个通道进行常规扫描,那么在所有通道的转换序列完成后,这个回调函数会被触发一次。

    • 配置了DMA时:

      DMA Normal Mode(普通模式): 当DMA传输完指定数量的数据(即填满整个缓冲区)后,DMA会产生一个传输完成中断,进而触发 HAL_ADC_ConvCpltCallback。在这种模式下,DMA传输完成后会停止,你需要再次调用 HAL_ADC_Start_DMA() 来启动下一次传输。

      DMA Circular Mode(循环模式): 当DMA传输完整个缓冲区的数据后,它会循环回到缓冲区的起始位置继续传输。HAL_ADC_ConvCpltCallback 函数在循环模式下,通常在DMA传输完整个缓冲区的数据时触发。此外,很多HAL库也提供了 HAL_ADC_ConvHalfCpltCallback 函数,用于在DMA传输完成一半缓冲区数据时触发。

DAC参数

与 ADC 相反,DAC (Digital-to-Analog Converter) 的任务是将单片机内部的数字值转换为外部世界连续变化的模拟电压或电流信号。它就像一位"乐谱演奏家",将数字化的乐谱 (数字值) 转换成实际的声波 (模拟信号)。

  1. 分辨率(Resolution)

    • 与 ADC 类似,DAC 的分辨率也决定了其输出精度的细腻程度,同样用"位" (bits) 表示。

      一个 12 位的 DAC,可以将输入的数字值 (0 到 212−1=4095) 映射到输出电压范围内的 4096 个不同的模拟电压等级。分辨率越高,输出的模拟信号越平滑,越接近理想的模拟波形。

  2. 参考电压(Vref)

    • DAC 的参考电压定义了其输出模拟电压的最大值
    • 默认情况下,VREF+通常连接到微控制器的VDDA(模拟电源电压)。在许多STM32微控制器中,VDDA通常是3.3V。因此,在没有特殊配置的情况下,DAC的参考电压通常默认为3.3V。
    • 例如,对于 12 位 DAC,Vref = 3.3V:
      • 输入数字值为 0 时,输出电压 ≈ 0V。
      • 输入数字值为 4095 时,输出电压 ≈ 3.3V。
      • 输入数字值为 2048 时,输出电压 ≈ 3.3V * (2048 / 4096) = 1.65V。
  3. 转换速率与建立时间(Settling Time)

    • DAC将数字值转换成模拟电压需要一定的时间
    • 建立时间是指从数字输入改变到模拟输出稳定在目标电压的一小段误差范围内所需的时间。这限制了 DAC 能够产生的模拟信号的最大频率。
  4. 输出缓冲(Output Buffer)

    • 许多DAC内部集成一个运算放大器
    • 提高驱动能力: 使 DAC 能够驱动一定的负载(比如直接驱动小阻抗的负载或后续电路),而不会导致电压下降。
    • 降低输出阻抗: 提供更稳定的输出电压。

DAC配置(DAC+DMA+定时器正弦波)

  1. 配置定时器(TIM6)
    • 启动定时器,选择时钟源
    • 参照时钟树的总线频率进行设置分频(Prescaler)和周期(Period)以获得所需的采样点输出频率 (注意:这不是最终的正弦波频率)。
  2. 配置DAC
    • 启用DAC通道
    • 设置Output Buffer位Enable
    • 设置Trigger为触发DAC的那个定时器的 TRGO 事件
  3. 配置DMA
    • 为 DAC 通道添加 DMA 请求 (Add DMA Request),选择一个 DMA 通道。
    • 设置 Direction 为 Memory to Peripheral (数据从内存流向外设)。
    • 设置 Mode 为 Circular (循环读取查找表)。
    • 设置 Peripheral 和 Memory 的 Data Width:
      • Peripheral 通常是 Half Word (16位),因为 DAC 数据寄存器通常只需要写入 12 位或 8 位。
      • Memory 通常也设置为 Half Word (16位),以匹配我们 uint16_t SineWave[] 数组的元素大小。
    • 确保 Memory 地址是递增的 (Increment Address: Memory)。
    • Peripheral 地址不递增 (Increment Address: Peripheral - Disabled)。
  4. NVIC 配置: 对于纯 DAC 输出,通常不需要启用 DAC 或 DMA 的中断

  1. 生成正弦波数组
// --- 全局变量 --- 
#define SINE_SAMPLES 100    // 一个周期内的采样点数
#define DAC_MAX_VALUE 4095 // 12 位 DAC 的最大数字值 (2^12 - 1)

uint16_t SineWave[SINE_SAMPLES]; // 存储正弦波数据的数组

// --- 生成正弦波数据的函数 ---
/**
 * @brief 生成正弦波查找表
 * @param buffer: 存储波形数据的缓冲区指针
 * @param samples: 一个周期内的采样点数
 * @param amplitude: 正弦波的峰值幅度 (相对于中心值)
 * @param phase_shift: 相位偏移 (弧度)
 * @retval None
 */
void Generate_Sine_Wave(uint16_t* buffer, uint32_t samples, uint16_t amplitude, float phase_shift)
{
  // 计算每个采样点之间的角度步进 (2*PI / samples)
  float step = 2.0f * 3.14159f / samples; 
  
  for(uint32_t i = 0; i < samples; i++)
  {
    // 计算当前点的正弦值 (-1.0 到 1.0)
    float sine_value = sinf(i * step + phase_shift); // 使用 sinf 提高效率

    // 将正弦值映射到 DAC 的输出范围 (0 - 4095)
    // 1. 将 (-1.0 ~ 1.0) 映射到 (-amplitude ~ +amplitude)
    // 2. 加上中心值 (DAC_MAX_VALUE / 2),将范围平移到 (Center-amp ~ Center+amp)
    buffer[i] = (uint16_t)((sine_value * amplitude) + (DAC_MAX_VALUE / 2.0f));
    
    // 确保值在有效范围内 (钳位)
    if (buffer[i] > DAC_MAX_VALUE) buffer[i] = DAC_MAX_VALUE;
    // 由于浮点计算精度问题,理论上不需要检查下限,但加上更健壮
    // else if (buffer[i] < 0) buffer[i] = 0; 
  }
}
  • buffer是用户提供的数组,samples是数组的大小也是后续进行采样的点数,amlitude将标准正弦波映射到(-amplitude,amplitude)phase shift是用户移动的相位。
  • DAC_MAX_VALUE / 2.0f它将正弦波上移到合理的位置(0,4095)是DAC进行输出模拟量
  1. 进行初始化代码
// --- 初始化函数 (在 main 函数或外设初始化后调用) ---
void dac_sin_init(void)
{
    // 1. 生成正弦波查找表数据
    //     amplitude = DAC_MAX_VALUE / 2 产生最大幅度的波形 (0-4095)
    Generate_Sine_Wave(SineWave, SINE_SAMPLES, DAC_MAX_VALUE / 2, 0.0f);
    
    // 2. 启动触发 DAC 的定时器 (例如 TIM6)
    HAL_TIM_Base_Start(&htim6); // htim6 是 TIM6 的句柄
    
    // 3. 启动 DAC 通道并通过 DMA 输出查找表数据
    //    hdac: DAC 句柄
    //    DAC_CHANNEL_1: 要使用的 DAC 通道
    //    (uint32_t *)SineWave: 查找表起始地址 (HAL 库常需 uint32_t*)
    //    SINE_SAMPLES: 查找表中的点数 (DMA 传输单元数)
    //    DAC_ALIGN_12B_R: 数据对齐方式 (12 位右对齐)
    HAL_DAC_Start_DMA(&hdac, DAC_CHANNEL_1, (uint32_t *)SineWave, SINE_SAMPLES, DAC_ALIGN_12B_R);
}

// --- 无需后台处理任务 --- 
// 一旦 dac_sin_init 调用完成,硬件会自动循环输出波形
// adc_task() 中可以移除 dac 相关的处理
  • 生成正弦波表,启动定时器,启动DMA向外输送数据。DMA设置为循环模式
  1. ADCbuffer接受DAC模拟量,将模拟量转换成数字量使用my_printf函数串口进行打印
        for(uint16_t i = 0; i <1000; i++)
        {
            // 假设 adc_val_buffer[0] 是通道1, adc_val_buffer[1] 是通道2, ...
            dac_val_buffer[i] = adc_val_buffer[i];
        }

        // 打印处理后的数据 (示例)
        for(uint16_t i = 0; i < 1000; i++)
        {
            // 注意: my_printf 是自定义函数, 需确保其存在且可用
            my_printf(&huart1, "{dac}%d", (int)dac_val_buffer[i]);
        }
  • 因为my_printf函数打印时需要时间所以使用串口进行观察时会出现不连续的情况,所以在一个打印数组里面最好有多个周期的波形方便观察。
Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐