ADC和DAC——跟我一起写STM32(第九期)

11 ADC

这里的ADC不是射手(雾),这里的ADC是Analog-to-Digital Converter,翻译一下就是模拟到数字的意思。
总所周知,模拟信号是指用连续变化的物理量所表达的信息,如温度、湿度、压力、长度、电流、电压等等,我们通常又把模拟信号称为连续信号,它在一定的时间范围内可以有无限多个不同的取值。而数字信号是指在取值上是离散的、不连续的信号,如在计算机中,数字信号的大小常用有限位的二进制数表示。
具体可以参考:
模拟信号
数字信号

而ADC和DAC这样的物件,就能构起模拟与数字之间的大桥。

11.1 走进ADC

推荐补课视频:

传感器采集模拟量,通过ADC得到数字量,并交给单片机处理。

ADC将会采样、保持、量化、编码,把模拟量电压转化为数字量

就比如一个3位ADC,采样的范围就是0到2的3次方,即0~8
若它的参考电压为3.3v,GND为0,
就等于0-3.3v映射为了0-8的数字。
比如数字1就是 (1/8)*3.3V的模拟值

拿小蜜蜂老师的经典例题为例:

详情参考:
b站:【小蜜蜂笔记】基于STM32CubeMX的嵌入式开发基础教程——ADC模数转换器基本工作原理
好文推荐:一篇易懂的模数转换器(ADC)学习笔记1

最基本的ADC类型:

并联比较型:

这里只是一个3位ADC,用了2的3次方个电阻和比较器。
每增加ADC分辨率(分辨率表示ADC能辨别的最小模拟量,用二进制位数表示,比如:8、10、12、16位),都会增加2的次方倍的比较器和电阻。

逐次逼近型:

每次比较一位,三次比较下来才能得到3位ADC的数字值,是拿速度换分辨率的方法。

STM32f103系列有3个ADC(都是逐次逼近型)精度为12位,每个ADC最多有16个外部通道。其中ADC1和ADC2都有16个外部通道,ADC3一般有8个外部通道,各通道的A/D转换可以单次、连续、扫描或间断执行,ADC转换的结果可以左对齐或右对齐储存在16位数据寄存器中。ADC的输入时钟不得超过14MHz,其时钟频率由PCLK2分频产生。

11.2 ADC配置

11.2.1 排队问题

A/D转换被组织为两组:规则组(常规转换组)和注入组(注入转换组)
规则组最多可以有16个转换,注入组最多有4个转换

顾名思义,规则组按照规矩依次办事,注入组会插队,大体如图:

11.2.2 触发源

软件触发、定时器触发…

当然也可以EXTI触发ADC:
STM32复习笔记(十二) —— ADC(EXTI事件触发)采集电压

11.2.3 转换时间

ADC时钟

笔者的配置为:

配置好了时钟,那我们就能计算ADC的转化时间

ADC转换时间: T = 采样时间 + 12.5个周期

采样时间我们可以自行配置:

时间越久精度越高

11.2.4 数据对齐

采集数据是存放在寄存器里

但是寄存器都是32位或者16位,而笔者ADC仅仅12位,这样存放数据时就有讲究了。
可以选择存放在高12位或者低12位,即左对齐或者右对齐

11.2.5 ADC模式

这个就比较重要了,大体很多人ADC出问题,都是模式没调好。

转化模式

CONT位01
转化模式单次转化模式连续转化模式

单次转换模式
只触发一次转换

连续转换模式
自动触发下一次转换
注意:只有规则组才能触发该模式

可以参考:
HAL ADC连续转换模式 Continuous Conversion Mode

扫描模式

SCAN位01
扫描模式

关闭扫描模式

ADC只转换选中的第一个通道进行转换

使用扫描模式
ADC会扫描所有选中的所有通道

间断模式——不连续采样模式
这个笔者用的比较少,这里不做介绍,大家可以自行了解。

不同模式组合的作用


具体例子:

总而言之,若开启连续转化,就不用自己再手动再次使能,反之,若没开启,再次想使用时就一定自己手动打开。
连续转化的时间就是我们刚刚计算的转化时间。
一般,用定时器触发时,就会关闭连续转化。

11.3 ADC轮询采集

配置ADC
在前面,我们已经说过了他们这些选项的具体含义,现在我们直接选择:

扫描模式无法关闭,但我们只配置一个通道(若只配置一个通道,也算是关闭了扫描模式了吧)

生成工程

因为我们没有使能连续模式,所以我们每次使用ADC前要开启ADC
我们编写开启ADC,并获取ADC值的函数:

u32 _adc_get_resule_pollfor(void)
{
    HAL_ADC_Start(&hadc1);
    HAL_ADC_PollForConversion(&hadc1, 10);//等待规则通道转换完成
    return (u16)HAL_ADC_GetValue(&hadc1);
}

这里轮询时间10ms,远远大于我们的采样时间了。

为了让我们的值更加精确,我们通常要对采样得到的值进行求平均:

u32 adc_get_resule_pollfor(u8 times)
{
    u32 temp_val = 0;
    for(int i = 0; i < times; i++)
    {
        temp_val += _adc_get_resule_pollfor();
        delay_ms(5);
    }
    return (temp_val / times);
}

我们用LCD屏幕把它显示出来就是:

 /* USER CODE BEGIN 2 */
    bsp_init();
    painter.size = LCD_FONTSIZE_1206;
    painter.color = BLACK;
    painter.back_color=LGRAY;
    lcd_clear();
    lcd_clear();
	lcd_show_string(5, 50,(u8*)"ADC1_CH1_VAL:");
    lcd_show_string(5, 62,(u8*)"ADC1_CH1_VOL:0.000V");
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
    u32 adcx;
    float temp;
    painter.color=BLUE;
    while (1)
    {
        adcx = adc_get_resule_pollfor(10);
        lcd_show_num(83, 50, adcx, 5);
        temp=(float )adcx * (3.3/4096);
        adcx = temp;
        lcd_show_num(83, 62 , adcx, 1);
        temp -= adcx;
        temp *= 1000;
        lcd_show_num(95, 62, temp, 3);
        delay_ms(100);
    /* USER CODE END WHILE */

11.4 用上DMA传输ADC

但有朋友就要说了,你这轮询采集ADC不就浪费时间了嘛。
于是,我们用上之前说过的数据的搬运工——DMA。
配置DMA

stm32是32位处理器,所以一个word就对应32位,一个half word就对应16位。根据需要传输数据的大小选择对应Data Width即可。
例如笔者使用ADC进行数据采集,ADC的精度是12位的,所以Data Width选择half word就够用了。

Mode选择Normal,所以每次传输完数据得自己手动再次开启DMA传输。

这里我们使能连续转化模式

因为如果不使能连续模式,ADC只转化一次,那我们若打算使用数组存放DMA缓存的话,DMA就一直得不到完成中断

于是,我们生成工程

编写DMA中断服务函数

u8 adc_dma_sta;
void DMA1_Channel1_IRQHandler(void)
{
    if (DMA1->ISR & (1 << 1))//通道1传输完成
    {
        adc_dma_sta = 1;//标识传输完成
        DMA1->IFCR |= (1 << 1);//清除标志位
    }
}

给IFCR寄存器对应位写1可清除标志位


当然,我们自己写了DMA中断服务函数之后就要把HAL库生成的注释掉:

这个函数负责设置DMA传输数量,当DMA传输数量为0时,视为停止

void adc_dma_enable(u16 cndtr)
{
    ADC1->CR2 &= ~(1 << 0);                 //__HAL_ADC_DISABLE(&hadc1);
    DMA1_Channel1->CCR &= ~(1 << 0);        //__HAL_DMA_DISABLE(&hdma_adc1);
    while (DMA1_Channel1->CCR & (1 << 0));  
    // while (__HAL_DMA_GET_FLAG(&hdma_adc1, __HAL_DMA_GET_TC_FLAG_INDEX(&hdma_adc1)));
    DMA1_Channel1->CNDTR = cndtr;	//设置DMA传输数量
    DMA1_Channel1->CCR |= (1 << 0);         //__HAL_DMA_ENABLE(&hdma_adc1);
    ADC1->CR2 |= (1 << 0);                  //__HAL_ADC_ENABLE(&hadc1);
    ADC1->CR2 |= (1 << 22);                 //HAL_ADC_Start(&hadc1)
}

ADC的CR2寄存器控制开关、和使能规则组通道转化


DMA的CCR控制开关


CNDTR控制DMA数据量

接着我们编写一个给DMA目标地址赋值的函数

u32 mar;
u32 adc_dam_address(u32 _mar)
{
    mar = _mar;
    return mar;
}

并使能DMA

HAL_DMA_Start_IT(&hdma_adc1, (u32)&ADC1->DR, mar,0);
HAL_ADC_Start_DMA(&hadc1, &mar, 0);

当然使能前,也可以校准一下

HAL_ADCEx_Calibration_Start(&hadc1);

于是,我们的代码就有了

生成目标地址数组

#define AVERAGE_TIMES 10
#define ADC_BUF_SIZE  AVERAGE_TIMES
u16 adc_buf[ADC_BUF_SIZE];

接着传入目标地址

adc_dam_address((u32)&adc_buf);

使能它

HAL_ADCEx_Calibration_Start(&hadc1);
HAL_DMA_Start_IT(&hdma_adc1, (u32)&ADC1->DR, mar,0);
HAL_ADC_Start_DMA(&hadc1, &mar, 0);

在判断DMA传输完成后显示出来

/* USER CODE BEGIN WHILE */
    u32 adcx;
    float temp;
    painter.color=BLUE;
    adc_dma_enable(ADC_BUF_SIZE);
    while (1)
    {
        if (adc_dma_sta)
        {
            adcx = adc_get_resule_pollfor(10);
            lcd_show_num(83, 50, adcx, 5);
            temp=(float )adcx * (3.3/4096);
            adcx = temp;
            lcd_show_num(83, 62 , adcx, 1);
            temp -= adcx;
            temp *= 1000;
            lcd_show_num(95, 62, temp, 3);

            adc_dma_sta = 0;
            adc_dma_enable(ADC_BUF_SIZE);
        }
        delay_ms(10);
    /* USER CODE END WHILE */

11.5 多通道采集

前面说过有扫描模式,那我们整个8通道ADC扫描来玩玩。

这里设置为连续模式
配置每个通道

规划每个通道的内存大小
这里笔者的配置为8个通道,每个通道读50次(用于取平均值),一次一共400的DMA传输大小
其中数据排列的顺序是123456781234567812345678…一个有400个数据。

#define ADC_N_CHANNEL (8)
#define ADC_BUF_SIZE (50 * ADC_N_CHANNEL)
u16 adc_buf[ADC_BUF_SIZE];
/* USER CODE BEGIN 2 */
    bsp_init();
    painter.size = LCD_FONTSIZE_1206;
    painter.color = BLACK;
    painter.back_color=LGRAY;
    lcd_clear();
    lcd_clear();
    lcd_show_string(5, 50,(u8*)"ADC1_CH1_VAL:");
    lcd_show_string(5, 62,(u8*)"ADC1_CH1_VOL:0.000V");
    lcd_show_string(5, 80,(u8*)"ADC1_CH2_VAL:");
    lcd_show_string(5, 92,(u8*)"ADC1_CH2_VOL:0.000V");
    lcd_show_string(5, 110,(u8*)"ADC1_CH3_VAL:");
    lcd_show_string(5, 122,(u8*)"ADC1_CH3_VOL:0.000V");
    lcd_show_string(5, 140,(u8*)"ADC1_CH4_VAL:");
    lcd_show_string(5, 152,(u8*)"ADC1_CH4_VOL:0.000V");
    lcd_show_string(5, 170,(u8*)"ADC1_CH5_VAL:");
    lcd_show_string(5, 182,(u8*)"ADC1_CH5_VOL:0.000V");
    lcd_show_string(5, 200,(u8*)"ADC1_CH6_VAL:");
    lcd_show_string(5, 212,(u8*)"ADC1_CH6_VOL:0.000V");
    lcd_show_string(5, 230,(u8*)"ADC1_CH7_VAL:");
    lcd_show_string(5, 242,(u8*)"ADC1_CH7_VOL:0.000V");
    lcd_show_string(5, 260,(u8*)"ADC1_CH8_VAL:");
    lcd_show_string(5, 272,(u8*)"ADC1_CH8_VOL:0.000V");
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
    u16 adcx;
    float temp;
    u32 sum;
    painter.color=BLUE;
    u32 colour[]={BLUE,GREEN,RED,MAGENTA,LBBLUE,BROWN,YELLOW,GRAY};
    adc_dma_enable(ADC_BUF_SIZE);
    while (1)
    {
        if(adc_dma_sta == 1)
        {
            for(int j = 0; j < ADC_N_CHANNEL; j++)
            {
                sum = 0;
                for(int i = 0; i < ADC_BUF_SIZE / ADC_N_CHANNEL; i++)/* 每个通道采集了50次数据,进行50次累加 */
                {
                    sum += adc_buf[(i * ADC_N_CHANNEL) + j];/* 相同通道的转换数据累加 */
                }
                adcx = sum / (ADC_BUF_SIZE / ADC_N_CHANNEL);
                lcd_show_num(83, 50+(j*30), adcx, 5,.color=colour[j]);
                temp=(float )adcx * (3.3/4096);
                adcx = temp;
                lcd_show_num(83, 62 + (j*30), adcx, 1,.color=colour[j]);
                temp -= adcx;
                temp *= 1000;
                lcd_show_num(95, 62 + (j*30), temp, 3,.color=colour[j]);
            }

            adc_dma_sta = 0;
            adc_dma_enable(ADC_BUF_SIZE);
        }
        delay_ms(100);
    /* USER CODE END WHILE */

11.6 过采集求均值提高分辨率

如果采样频率高于信号最高频率的两倍,这种采样被称为过采样。(奈奎斯特采样定理-香农采样定理)
即尽可能快地采样数据,比如之前是1s采1次,现在做成100ms采1次,并把缓存10次的数据求平均。

而我们就能利用过采样和求均值来提高分辨率位数。

根据要增加的分辨率位数计算过采样频率方程:

fos 是过采样频率,w是希望增加的分辨率位数,fs 是初始采样频率要求

方程推导过程

方法就是:
过采样数据累加并移位(这一步叫求均值,但不是求总数平均值的意思)

举个例子:12位分辨率的ADC提高4位分辨率,采样频率就要提高256倍
即需要256次采集才能得到一次16位分辨率的数据
然后将这256次采集结果求和,求和的结果再右移4位,就得到提高分辨率后的结果
注意:提高N 位分辨率,需要 右移N位

按照这个原理,我们改造之前的DMA单通道实验:

定义DMA目标地址数组

#define ADC_OVERSAMPLE_N 4	//过采样位数
#define N_POWER 65536       //分辨率 2^(12+ADC_OVERSAMPLE_N)
#define AVERAGE_TIMES 10	//平均次数
#define ADC_OVERSAMPLE_TIMES 256	//过采样次数
#define ADC_BUF_SIZE  AVERAGE_TIMES * ADC_OVERSAMPLE_TIMES
u16 adc_buf[ADC_BUF_SIZE];

编写核心代码:

/* USER CODE BEGIN WHILE */
    u32 adcx;
    float temp;
    u32 sum;
    painter.color=BLUE;
    while (1)
    {
        if(adc_dma_sta == 1)
        {
            sum = 0;
            for(int i = 0; i < ADC_BUF_SIZE; i++)
            {
                sum += adc_buf[i];//将256次过采样再进行10次平均值
            }
            adcx = sum / (AVERAGE_TIMES);
            adcx >>= ADC_OVERSAMPLE_N;//得到16位ADC数据
            lcd_show_num(83, 50, adcx, 5);
            temp=(float )adcx * (3.3/N_POWER);
            adcx = temp;
            lcd_show_num(83, 62 , adcx, 1);
            temp -= adcx;
            temp *= 1000;
            lcd_show_num(95, 62, temp, 3);

            adc_dma_sta = 0;
            adc_dma_enable(ADC_BUF_SIZE);
        }
        delay_ms(100);
    /* USER CODE END WHILE */

可以看见,我们的采样值已经到达了16位的分辨率了。

11.7 双ADC同步转换

双 ADC 的机制就是使用两个 ADC 同时采样一个或者多个通道。双重ADC 模式较独立模式一个最大的优势就是提高了采样率,弥补了单个 ADC 采样不够快的缺点。

这里也说了昂,需要使能DMA模式。



详情可以参考:
STM32之双ADC详解

例如:我们使用ADC1、ADC2同步采集,让ADC1采集内部参考电压、ADC2采集外部模拟电压。
这里我们选择:
Dual regular simultaneous mode only (规则同步转换模式)

我们选择用定时器触发ADC
配置好TIM3,并触发事件设置为Update Event,也就是把UEV事件信号作为TRGO信号。这样ADC在TIM3的TRGO信号每个上升沿启动一次ADC转换。

接着配置通道:

ADC2的通道和ADC1保持一致,避免不同步:

因为共用ADC通道,这里DMA要更改为32位数据宽度:

生成工程:

因为没有开启连续模式,我们的DMA缓存数组大小为1

#define ADC_BUF_SIZE  1
u32 adc_buf[ADC_BUF_SIZE];

先打开ADC2,使能ADC1同步模式,打开定时器:

HAL_ADC_Start(&hadc2);
HAL_ADCEx_MultiModeStart_DMA(&hadc1, adc_buf, ADC_BUF_SIZE);
HAL_TIM_Base_Start(&htim3);

这里笔者偷个懒,直接显示结果(求均值占时间比较大,应该采用之前的方法,在main函数里进行)

编写回调函数(DMA传输完成进入中断服务函数进而会调用ADC传输完成函数):

void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc)
{
    u32 volt;
    u32 value = adc_buf[0];
    u32 adc1_value = value & 0x0000FFFF;//低16位是ADC1
    volt = 3300 *adc1_value;
    volt >>= 12 ;//除以2^12
    lcd_show_num(100,50,volt,4);
    u32 adc2_value = value & 0xFFFF0000;//高16位是ADC2
    volt = 3300 *adc2_value;
    volt >>= 12 ;//除以2^12
    lcd_show_num(100,62,volt,4);
}

12 DAC

之前说过ADC和DAC是模拟电路与数字电路之间的桥梁
那么,有了ADC(Analog-to-Digital Converter),那就有DAC,Digital-to-Analog Converter

12.1 走进DAC

DAC能将数字量转化为模拟量,从而和ADC构成系统:

基本参数依旧是那些东西:
分辨率、参考电压、供电电压、输出通道啥的:

12.2 DAC配置


数据格式
DAC数据格式:支持8/12位模式

后面笔者将用单通道12位右对齐进行演示

单通道独立输出时用的是:DAC_DHR8Rx、DAC_DHR12Rx、DAC_DHR12Lx


双通道同时输出时,会启用共同寄存器:DAC_DHR8RD、DAC_DHR12RD、DAC_DHR12LD

触发源
三种触发转换的方式:自动触发、软件触发、外部事件触发

噪声波和三角波
DAC内部使用线性反馈移位寄存器,可以生成变振幅的伪噪声,每次发生触发时,经过3个APB1时钟周期后,LFSR生成一个随机数并移入DOR。
注意,生成噪声波和三角波,必须使用外部触发。

12.3 DAC输出电压

OutPut Buffer:设置是否使用输出缓冲器。使用的话,可以降低输出阻抗,提高输出负载能力,默认Enable。
Trigger:外部触发信号源。本次不使用外部触发,设置为None。

软件触发不等于None:
如果是None, 那么不需要其他任何的触发源,直接HAL_DAC_SetValue()设置DAC的值,就可以设定输出电压的大小。如果使用了软件触发,那么,每次在使用HAL_DAC_SetValue()修改输出电压后,还需要调用使能,目的是使能软件触发。由于:软件触发是硬件在一个APB1时钟周期后自动关断的,于是,每次修改输出电压的值后,都要调用软件使能的方法,才能生效。

可以参考:
DAC 使用软件触发,无电压输出
STM32_DAC之软件触发(Trigger)

我们生成工程:

编写电压设置函数:

选择通道,对齐方式

//vol:0-3300
void dac_set_voltage(u16 vol)
{
    double temp = vol;
    temp /= 1000;
    temp = temp * 4096 / 3.3;
    if(temp >= 4096) temp = 4095;
    HAL_DAC_SetValue(&hdac, DAC_CHANNEL_1, DAC_ALIGN_12B_R, temp);
}

接着拿之前写的ADC测一下:

/* USER CODE BEGIN WHILE */
	dac_set_voltage(1500);
    u32 adcx;
    float temp;
    u32 sum;
    painter.color=BLUE;
    while (1)
    {
        if(adc_dma_sta == 1 )
        {
            sum = 0;
            for(int i = 0; i < ADC_BUF_SIZE; i++)
            {
                sum += adc_buf[i];
            }
            adcx = sum / (AVERAGE_TIMES);
            lcd_show_num(83, 50, adcx, 5);
            temp=(float )adcx * (3.3/4096);
            adcx = temp;
            lcd_show_num(83, 62 , adcx, 1);
            temp -= adcx;
            temp *= 1000;
            lcd_show_num(95, 62, temp, 3);
            adc_dma_sta = 0;
            adc_dma_enable(ADC_BUF_SIZE);
        }
        delay_ms(100);
/* USER CODE END WHILE */

就可以自行查看结果了。

12.3 DAC直接输出三角波

输出三角波

不满足取值范围,波形不完整

编写对应代码:

//maxval:    幅值:0 < maxval < 4096, (maxval + 1) >= samples/2
//dt:        delay_us 小于5us后不精确
//samples:   采样点sample个
//n:         输出波形的次数(0~65535)
void dac_triangular_wave(u16 maxval, u16 dt, u16 samples, u16 n)
{
    float incval;
    float curval;
    if(samples > ((maxval + 1) * 2))    return;
    incval = (maxval + 1) / (samples / 2);
    for(u16 j = 0; j < n; j++)
    {
        curval = 0;
        HAL_DAC_SetValue(&DAC_HANDLE, channel, DAC_ALIGN_12B_R, curval);
        for(u16 i = 0; i < (samples/2); i++)    //上升波
        {
            curval += incval;
            HAL_DAC_SetValue(&DAC_HANDLE, channel, DAC_ALIGN_12B_R, curval);
            delay_us(dt);
        }
        for(u16 i = 0; i < (samples/2); i++)    //下降波
        {
            curval -= incval;
            HAL_DAC_SetValue(&DAC_HANDLE, channel, DAC_ALIGN_12B_R, curval);
            delay_us(dt);
        }
    }
}

我们在while里调用它:

/* USER CODE BEGIN WHILE */
    while (1)
    {
        dac_triangular_wave(4095, 5, 200, 100);
    /* USER CODE END WHILE */

12.4 用DMA让DAC输出正弦波

差不多的就是那些伎俩,这次用DMA把一个正弦波数组传给DAC,并让DAC输出这个正弦波。

选择DAC2:

选择TIM7作为DAC2的触发源,并配置TIM7

接着使能DMA

生成工程:

编写为我们生成正弦波数组的函数:

#define PAI 3.1415926
//生成正弦波数组
void dac_creat_sin_buf(u16 *buf, u16 maxval, u16 samples)
{
    float outdata = 0;
    float inc = (2 * PAI) / samples;
    if(maxval <= (samples / 2)) return;
    for(int i = 0; i < samples; i++)
    {
        //正弦函数解析式:y= Asin(wx + k) + b
        outdata = maxval * sin(inc * i) + maxval;
        if (outdata > 4095)
            outdata = 4095;
        buf[i] = outdata;
    }
}

关于DMA的源地址设置函数:

#define DMA_DAC hdma_dac_ch2
#define DAC_HANDLE hdac
extern DMA_HandleTypeDef DMA_DAC;
void dac_init_dma(u32 _channel, u32 _mar)
{
    mar = _mar;
    channel = _channel;
    if(channel == DAC_CHANNEL_1)
        HAL_DMA_Start(&DMA_DAC, mar, (u32)&DAC_HANDLE.Instance->DHR12R1, 0);
    else if(channel == DAC_CHANNEL_2)
        HAL_DMA_Start(&DMA_DAC, mar, (u32)&DAC_HANDLE.Instance->DHR12R2, 0);
}

DMA使能函数:

#define TIM_DAC htim7
void dac_dma_enable(u16 cndtr, u16 arr, u16 psc)
{
    TIM_DAC.Instance->PSC= psc;
    TIM_DAC.Instance->ARR= arr;
    HAL_TIM_Base_Start(&TIM_DAC);
    HAL_DAC_Stop_DMA(&DAC_HANDLE, channel);
    HAL_DAC_Start_DMA(&DAC_HANDLE, channel, (u32 *)mar, cndtr, DAC_ALIGN_12B_R);
}

通道设置函数:

void dac_set_channel(u32 _channel)
{
    channel = _channel;
    HAL_DAC_Start(&DAC_HANDLE, channel);
}

在主函数中开启这一切:

u16 buf[4096];
dac_creat_sin_buf(buf, 2048, 100);//生成正弦数组
dac_init_dma(DAC_CHANNEL_2, (u32)&buf);//把数组给dma
dac_dma_enable(100, 10-1, 24-1);

文末,把之前用到的一部分常用的函数整理成模块,方便我们调用,当然,大家也可以自行封装更多的函数作为自己的模块。

ADC:

adclib.h

//
// Created by Whisky on 2023/1/13.
//

#ifndef HELLOWORLD_ADCLIB_H
#define HELLOWORLD_ADCLIB_H
#include "main.h"

#define ADC_HANDLE hadc1
#define DMA_ADC    hdma_adc1


extern u8 adc_dma_sta;

u32 adc_get_resule_pollfor(u8 times);
void adc_dma_enable(u16 cndtr);
u32 adc_init_dma(u32 _mar);
#endif //HELLOWORLD_ADCLIB_H

adclib.c

//
// Created by Whisky on 2023/1/13.
//

#include "adclib.h"
#include "adc.h"

u8 adc_dma_sta;

extern DMA_HandleTypeDef DMA_ADC;

u32 adc_init_dma(u32 mar)
{
    HAL_ADCEx_Calibration_Start(&ADC_HANDLE);
    HAL_DMA_Start_IT(&DMA_ADC, (u32)&ADC_HANDLE.Instance->DR, mar,0);
    HAL_ADC_Start_DMA(&ADC_HANDLE, &mar, 0);
}

u32 _adc_get_resule_pollfor(void)
{
    HAL_ADC_Start(&ADC_HANDLE);
    HAL_ADC_PollForConversion(&ADC_HANDLE, 10);
    return (u16)HAL_ADC_GetValue(&ADC_HANDLE);
}

u32 adc_get_resule_pollfor(u8 times)
{
    u32 temp_val = 0;
    for(int i = 0; i < times; i++)
    {
        temp_val += _adc_get_resule_pollfor();
        delay_ms(5);
    }
    return (temp_val / times);
}


void DMA1_Channel1_IRQHandler(void)
{
    if (DMA1->ISR & (1 << 1))
    {
        adc_dma_sta = 1;
        DMA1->IFCR |= (1 << 1);
    }
}

void adc_dma_enable(u16 cndtr)
{
    ADC_HANDLE.Instance->CR2 &= ~(1 << 0);                 //__HAL_ADC_DISABLE(&ADC_HANDLE);
    DMA_ADC.Instance->CCR &= ~(1 << 0);        //__HAL_DMA_DISABLE(&DMA_ADC);
    while (DMA_ADC.Instance->CCR & (1 << 0));  // while (__HAL_DMA_GET_FLAG(&DMA_ADC, __HAL_DMA_GET_TC_FLAG_INDEX(&DMA_ADC)));
    DMA_ADC.Instance->CNDTR = cndtr;
    DMA_ADC.Instance->CCR |= (1 << 0);         //__HAL_DMA_ENABLE(&DMA_ADC);
    ADC_HANDLE.Instance->CR2 |= (1 << 0);                  //__HAL_ADC_ENABLE(&ADC_HANDLE);
    ADC_HANDLE.Instance->CR2 |= (1 << 22);                 //HAL_ADC_Start(&ADC_HANDLE)
}

DAC:

daclib.h

//
// Created by Whisky on 2023/1/13.
//

#ifndef HELLOWORLD_DACLIB_H
#define HELLOWORLD_DACLIB_H
#include "main.h"

#define DMA_DAC hdma_dac_ch2
#define DAC_HANDLE hdac
#define TIM_DAC htim7

#define PAI 3.1415926
void dac_init_dma(u32 _channel, u32 _mar);
void dac_set_voltage(u16 vol);
void dac_init_dma(u32 _channel, u32 _mar);
void dac_triangular_wave(u16 maxval, u16 dt, u16 samples, u16 n);
void dac_creat_sin_buf(u16 *buf, u16 maxval, u16 samples);
void dac_dma_enable(u16 cndtr, u16 arr, u16 psc);
#endif //HELLOWORLD_DACLIB_H

daclib.c

//
// Created by Whisky on 2023/1/13.
//

#include "daclib.h"
#include "dac.h"
#include "tim.h"
#include "math.h"

u32 channel ;
u32 mar;

extern DMA_HandleTypeDef DMA_DAC;
void dac_set_channel(u32 _channel)
{
    channel = _channel;
    HAL_DAC_Start(&DAC_HANDLE, channel);
}

void dac_init_dma(u32 _channel, u32 _mar)
{
    mar = _mar;
    channel = _channel;
    if(channel == DAC_CHANNEL_1)
        HAL_DMA_Start(&DMA_DAC, mar, (u32)&DAC_HANDLE.Instance->DHR12R1, 0);
    else if(channel == DAC_CHANNEL_2)
        HAL_DMA_Start(&DMA_DAC, mar, (u32)&DAC_HANDLE.Instance->DHR12R2, 0);
}

//vol:0-3300
void dac_set_voltage(u16 vol)
{
    double temp = vol;
    temp /= 1000;
    temp = temp * 4096 / 3.3;
    if(temp >= 4096) temp = 4095;
    HAL_DAC_SetValue(&DAC_HANDLE, channel, DAC_ALIGN_12B_R, temp);
}


//maxval:    幅值:0 < maxval < 4096, (maxval + 1) >= samples/2
//dt:        delay_us 小于5us后不精确
//samples:   采样点sample个
//n:         输出波形的次数(0~65535)
void dac_triangular_wave(u16 maxval, u16 dt, u16 samples, u16 n)
{
    float incval;
    float curval;
    if(samples > ((maxval + 1) * 2))    return;
    incval = (maxval + 1) / (samples / 2);
    for(u16 j = 0; j < n; j++)
    {
        curval = 0;
        HAL_DAC_SetValue(&DAC_HANDLE, channel, DAC_ALIGN_12B_R, curval);
        for(u16 i = 0; i < (samples/2); i++)    //上升波
        {
            curval += incval;
            HAL_DAC_SetValue(&DAC_HANDLE, channel, DAC_ALIGN_12B_R, curval);
            delay_us(dt);
        }
        for(u16 i = 0; i < (samples/2); i++)    //下降波
        {
            curval -= incval;
            HAL_DAC_SetValue(&DAC_HANDLE, channel, DAC_ALIGN_12B_R, curval);
            delay_us(dt);
        }
    }
}

//生成正弦波数组
void dac_creat_sin_buf(u16 *buf, u16 maxval, u16 samples)
{
    float outdata = 0;
    float inc = (2 * PAI) / samples;
    if(maxval <= (samples / 2)) return;
    for(int i = 0; i < samples; i++)
    {
        //正弦函数解析式:y= Asin(wx + k) + b
        outdata = maxval * sin(inc * i) + maxval;
        if (outdata > 4095)
            outdata = 4095;
        buf[i] = outdata;
    }
}

void dac_dma_enable(u16 cndtr, u16 arr, u16 psc)
{
    TIM_DAC.Instance->PSC= psc;
    TIM_DAC.Instance->ARR= arr;
    HAL_TIM_Base_Start(&TIM_DAC);
    HAL_DAC_Stop_DMA(&DAC_HANDLE, channel);
    HAL_DAC_Start_DMA(&DAC_HANDLE, channel, (u32 *)mar, cndtr, DAC_ALIGN_12B_R);
}
  • 6
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值