当前位置:   article > 正文

【正点原子STM32连载】 第三十九章 DAC输出正弦波实验 摘自【正点原子】APM32E103最小系统板使用指南

【正点原子STM32连载】 第三十九章 DAC输出正弦波实验 摘自【正点原子】APM32E103最小系统板使用指南

1)实验平台:正点原子APM32E103最小系统板
2)平台购买地址:https://detail.tmall.com/item.htm?id=609294757420
3)全套实验源码+手册+视频下载地址: http://www.openedv.com/docs/boards/xiaoxitongban

第三十九章 DAC输出正弦波实验

本章将使用TMR7的更新事件用于TRGO触发DAC1通道1输出DMA2通道3从使用软件算法生成的正弦波数据,以输出正弦波。通过本章的学习,读者将学习到DAC、TMR、DMA的使用。
本章分为如下几个小节:
39.1 硬件设计
39.2 程序设计
39.3 下载验证

39.1 硬件设计
39.1.1 例程功能

  1. 按下KEY_UP和KEY0按键,可分别利用DAC输出高采样率和低采样率的正弦波
  2. 可通过USMART设置DAC输出正弦波的频率
  3. LED0闪烁,指示程序正在运行
    39.1.2 硬件资源
  4. LED
    LED0 - PB5
  5. 按键
    KEY0 - PE4
    KEY_UP - PA0
  6. USART1(PA9、PA10连接至板载USB转串口芯片上)
  7. 正点原子 2.8/3.5/4.3/7/10寸TFTLCD模块(仅限MCU屏,16位8080并口驱动)
  8. ADC1
    通道1 - PA1
  9. DAC
    通道1 - PA4
  10. TMR7
  11. DMA2
    通道3
    39.1.3 原理图
    本章实验使用的DAC为APM32E103的片上资源,因此没有对应的连接原理图。
    39.2 程序设计
    39.2.1 Geehy标准库的TMR驱动
    本章实验中将使用TMR7的更新事件产生TRGO事件,该TRGO事件将用于触发DAC,其具体的步骤如下:
    ①:配置TMR7的自动重装载值和预分频器数值
    ②:配置TMR7的TRGO事件的触发源为更新事件
    ③:使能TMR7
    在Geehy标准库中对应的驱动函数如下:
    ①:配置TMR
    请见第16.2.1小节中配置TMR的相关章节。
    ②:配置TMR的TRGO触发源
    该函数用于配置TMR的TRGO触发源,其函数原型如下所示:
    void TMR_SelectOutputTrigger(TMR_T* tmr, TMR_TRGO_SOURCE_T TRGOSource);
    该函数的形参描述,如下表所示:
    形参 描述
    tmr 指向TMR外设结构体的指针
    例如:TMR1、TMR2等(在apm32e10x.h文件中有定义)
    TRGOSource 指定的TRGO触发源
    例如:TMR_TRGO_SOURCE_RESET、TMR_TRGO_SOURCE_UPDATE等(在apm32e10x_tmr.h文件中有定义)
    表39.2.1.1 函数TMR_SelectOutputTrigger()形参描述
    该函数的返回值描述,如下表所示:
    返回值 描述
    无 无
    表39.2.1.2 函数TMR_SelectOutputTrigger()返回值描述
    该函数的使用示例,如下所示:
#include "apm32e10x.h"
#include "apm32e10x_tmr.h"

void example_fun(void)
{
    /* 配置TMR1的TRGO触发源为更新事件 */
    TMR_SelectOutputTrigger(TMR1, TMR_TRGO_SOURCE_UPDATE);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

③:使能TMR
请见第16.2.1小节中使能TMR的相关内容。
39.2.2 Geehy标准库的DMA驱动
请见第三十二章“DMA实验”中Geehy标准库的DMA驱动的相关内容。
39.2.3 Geehy标准库的DAC驱动
本章实验与第三十八章一样,使用DAC通道1(PA4引脚)输出电压,不同之处在于,本章使用使用DMA自动将DAC通道1待输出的数据写入DAC的数据保持寄存器,以输出正弦波。其中对DAC的操作请见第三十八章中Geehy标准库的DAC驱动的相关内容,本小节仅介绍DAC使用DMA的相关步骤,其具体的步骤如下:
①:使能DAC通道1DMA
在Geehy标准库中对应的驱动函数如下:
①:使能DAC通道DMA
该函数用于使能DAC指定通道的DMA,其函数原型如下所示:
void DAC_DMA_Enable(DAC_CHANNEL_T channel);
该函数的形参描述,如下表所示:
形参 描述
channel 指定DAC的通道
例如:DAC_CHANNEL_1、DAC_CHANNEL_2(在apm32e10x_dac.h文件中有定义)
表39.2.3.1 函数DAC_DMA_Enable()形参描述
该函数的返回值描述,如下表所示:
返回值 描述
无 无
表39.2.3.2 函数DAC_DMA_Enable()返回值描述
该函数的使用示例,如下所示:

#include "apm32e10x.h"
#include "apm32e10x_dac.h"

void example_fun(void)
{
    /* 使能DAC通道1DMA */
    DAC_DMA_Enable(DAC_CHANNEL_1);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

39.2.4 DAC驱动
本章实验的DAC驱动主要负责向应用层提供DAC的初始化和使能DAC输出指定幅值、频率的正弦波。本章实验中,DAC的驱动代码包括dac.c和dac.h两个文件。
DAC驱动中,DAC的初始化函数,如下所示:

/**
 * @brief       初始化DAC输出波形
 * @param       outx: 待初始化的DAC通道
 * @arg         1: DAC通道1
 * @arg         2: DAC通道2
 * @retval      无
 */
void dac_dma_wave_init(uint8_t outx)
{
    DAC_Config_T dac_init_struct;
    GPIO_Config_T gpio_init_struct;
    DMA_Config_T dma_init_struct;
    DAC_CHANNEL_T dac_channel = (outx == 1) ? DAC_CHANNEL_1 : DAC_CHANNEL_2;
    
    /* 使能时钟 */
RCM_EnableAPB1PeriphClock(RCM_APB1_PERIPH_DAC);          /* 使能DAC时钟 */
/* 使能DAC OUT1/2的IO口时钟(都在PA口,PA4/PA5) */
    RCM_EnableAPB2PeriphClock(RCM_APB2_PERIPH_GPIOA);                       
    RCM_EnableAPB1PeriphClock(RCM_APB1_PERIPH_TMR7);         /* 使能TMR7时钟 */
    RCM_EnableAHBPeriphClock(RCM_AHB_PERIPH_DMA2);           /* 使能DMA1时钟 */
    
    /* 配置DAC引脚 */
    gpio_init_struct.pin = (outx == 1) ? GPIO_PIN_4 : GPIO_PIN_5;
    gpio_init_struct.mode = GPIO_MODE_ANALOG;
    GPIO_Config(GPIOA, &gpio_init_struct);
    
    /* 配置DAC */
    dac_init_struct.trigger = DAC_TRIGGER_TMR7_TRGO;
    dac_init_struct.outputBuffer = DAC_OUTPUT_BUFFER_ENBALE;
    dac_init_struct.waveGeneration = DAC_WAVE_GENERATION_NONE;
    dac_init_struct.maskAmplitudeSelect = DAC_LFSR_MASK_BIT11_1;
    DAC_Config(dac_channel, &dac_init_struct);
    
    /* 复位DMA */
    DMA_Reset(DMA2_Channel3);
    while (DMA2_Channel3->CHCFG_B.CHEN != RESET);
    
    /* 配置DMA */
    dma_init_struct.peripheralBaseAddr = (uint32_t)0;
    dma_init_struct.memoryBaseAddr = (uint32_t)g_dac_sin_buf;
    dma_init_struct.dir = DMA_DIR_PERIPHERAL_DST;
    dma_init_struct.bufferSize = 0;
    dma_init_struct.peripheralInc = DMA_PERIPHERAL_INC_DISABLE;
    dma_init_struct.memoryInc = DMA_MEMORY_INC_ENABLE;
    dma_init_struct.peripheralDataSize = DMA_PERIPHERAL_DATA_SIZE_HALFWORD;
    dma_init_struct.memoryDataSize = DMA_MEMORY_DATA_SIZE_HALFWORD;
    dma_init_struct.loopMode = DMA_MODE_CIRCULAR;
    dma_init_struct.priority = DMA_PRIORITY_MEDIUM;
    dma_init_struct.M2M = DMA_M2MEN_DISABLE;
    DMA_Config(DMA2_Channel3, &dma_init_struct);
    DMA_Enable(DMA2_Channel3);
}
可以看到本章实验DAC驱动中的DAC初始化函数与第三十七章“DAC输出实验”中对DAC的初始化基本一致,不过本章配置DAC通道1的触发源为TMR7的TRGO事件,同时还配置了DMA,DMA存储器的地址配置为了数组g_dac_sin_buf的地址,因此该数组将用于存放正弦波的数据。
DAC驱动中使能DAC输出指定幅值、频率的正弦波的函数,如下所示:
/**
 * @brief       使能DAC输出波形
 * @param       outx: 待初始化的DAC通道
 * @arg         1: DAC通道1
 * @arg         2: DAC通道2
 * @param       ndtr: DMA通道传输一次的数目
 * @param       arr : DAC触发定时器的自动重装载值
 * @param       psc : DAC触发定时器的预分频器数值
 * @retval      无
 */
void dac_dma_wave_enable(uint8_t outx, uint16_t ndtr, uint16_t arr, uint16_t psc)
{
    TMR_BaseConfig_T tmr_init_struct;
    DAC_CHANNEL_T dac_channel = (outx == 1) ? DAC_CHANNEL_1 : DAC_CHANNEL_2;
    uint32_t pdata = (uint32_t)((outx == 1) ? &(DAC->DH12R1) : &(DAC->DH12R2));
    
    /* 配置TMR7 */
    tmr_init_struct.period = arr;                           /* 自动重装载值 */
    tmr_init_struct.division = psc;                         /* 预分频器数值 */
    TMR_ConfigTimeBase(TMR7, &tmr_init_struct);             /* 配置TMR7 */
    TMR_SelectOutputTrigger(TMR7, TMR_TRGO_SOURCE_UPDATE);
    TMR_Enable(TMR7);                                       /* 使能TMR7 */
    
    /* 关闭DAC和DMA */
    DAC_Disable(dac_channel);                               /* 关闭DAC */
    DMA_Disable(DMA2_Channel3);                             /* 禁止DMA通道 */
    while (DMA2_Channel3->CHCFG_B.CHEN != RESET);           /* 等待DMA可配置 */
    
    /* 配置DAC和DMA */
    DAC_DMA_Enable(dac_channel);                            /* 使能DAC DMA */
    DMA_ConfigDataNumber(DMA2_Channel3, ndtr);              /* 配置传输的数目 */
    DMA2_Channel3->CHPADDR = pdata;                         /* 配置外设地址 */
    DMA_Enable(DMA2_Channel3);                              /* 使能DMA通道 */
    DAC_Enable(dac_channel);                                /* 使能DAC */
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89

该函数配置了TMR7并配置TMR7的更新事件用于TRGO,因此TMR7的溢出频率就决定了DAC输出正弦波的频率,同时也配置和使能了DAC和DMA。
39.2.5 实验应用代码
本章实验的应用代码,如下所示:

/* 正弦波数据缓冲区 */
uint16_t g_dac_sin_buf[4096];

/**
 * @brief       生成正弦数据
 * @param       maxval : 正弦的振幅
 * @param       samples: 正弦一个周期的采样点个数
 * @retval      无
 */
static void dac_creat_sin_buf(uint16_t maxval, uint16_t samples)
{
    uint16_t i;
    double w;
    uint16_t outdata;
    
    w = (2 * 3.1415926) / samples;                  /* ω=2π/T */
    
    for (i = 0; i < samples; i++)
    {
        outdata = maxval * sin(w * i + 0) + maxval; /* y=Asin(ωx+φ)+b */
        if (outdata > 4095)                         /* 限制上限 */
        {
            outdata = 4095;
        }
        g_dac_sin_buf[i] = outdata;
    }
}

/**
 * @brief       修改输出正弦波的频率
 * @note        主要用于USMART
 * @param       arr: DAC触发定时器的自动重装载值
 * @param       psc: DAC触发定时器的预分频器数值
 * @retval      无
 */
void dac_dma_sin_set(uint16_t arr, uint16_t psc)
{
    dac_dma_wave_enable(1, 100, arr, psc);
}

int main(void)
{
    uint8_t key;
    uint8_t t = 0;
    uint16_t dacdata;
    uint16_t dac_voltage;
    uint16_t adcdata;
    uint16_t adc_voltage;
    
    NVIC_ConfigPriorityGroup(NVIC_PRIORITY_GROUP_4);  /* 设置中断优先级分组为组4 */
    sys_apm32_clock_init(15);                         /* 配置系统时钟 */
    delay_init(120);                                  /* 初始化延时功能 */
    usart_init(115200);                               /* 初始化串口 */
    led_init();                                       /* 初始化LED */
    lcd_init();                                       /* 初始化LCD */
    key_init();                                       /* 初始化按键 */
    adc_init();                                       /* 初始化ADC */
dac_dma_wave_init(1);                             /* 初始化DAC输出波形 */
/* 生成正弦数据,振幅约3.3(V),100个数据 */
dac_creat_sin_buf(2048 - 1, 100);
/* 定时器触发速率100KHz,100个数据,输出约1KHz的正弦波 */
    dac_dma_wave_enable(1, 100, 10 - 1, 120 - 1);
    
    lcd_show_string(30, 50, 200, 16, 16, "APM32", RED);
    lcd_show_string(30, 70, 200, 16, 16, "DAC DMA Sine WAVE TEST", RED);
    lcd_show_string(30, 90, 200, 16, 16, "ATOM@ALIENTEK", RED);
    lcd_show_string(30, 130, 200, 16, 16, "DAC VAL:", BLUE);
    lcd_show_string(30, 150, 200, 16, 16, "DAC VOL:0.000V", BLUE);
    lcd_show_string(30, 170, 200, 16, 16, "ADC VOL:0.000V", BLUE);
    
    while (1)
    {
        t++;
        key = key_scan(0);
        
        if (key == WKUP_PRES)       /* ADC通道1输出高采样率正弦波 */
        {   /* 生成正弦数据,振幅约3.3(V),100个数据 */
            dac_creat_sin_buf(2048 - 1, 100);
/* 定时器触发速率300KHz,100个数据,输出约3KHz的正弦波 */
            dac_dma_wave_enable(1, 100, 10 - 1, 40 - 1);
        }
        else if (key == KEY0_PRES)  /* ADC通道1输出低采样率正弦波 */
        {
/* 生成正弦数据,振幅约3.3(V),100个数据 */
            dac_creat_sin_buf(2048 - 1, 10);
/* 定时器触发速率300KHz,10个数据,输出约30KHz的正弦波 */
            dac_dma_wave_enable(1, 10, 10 - 1, 40 - 1);
        }
        /* 获取并显示DAC输出电压的数字量 */
        dacdata = DAC_ReadDataOutputValue(DAC_CHANNEL_1);
        lcd_show_xnum(94, 130, dacdata, 5, 16, 0, BLUE);
        /* 计算并显示DAC输出电压的模拟量 */
        dac_voltage = (dacdata * 3300) / 4095;
        lcd_show_xnum(94, 150, dac_voltage / 1000, 1, 16, 0, BLUE);
        lcd_show_xnum(110, 150, dac_voltage % 1000, 3, 16, 0x80, BLUE);
        /* 获取、计算并显示ADC采集到电压的模拟量 */
        adcdata = adc_get_result_average(ADC_ADCX_CHY, 20);
        adc_voltage = (adcdata * 3300) / 4095;
        lcd_show_xnum(94, 170, adc_voltage / 1000, 1, 16, 0, BLUE);
        lcd_show_xnum(110, 170, adc_voltage % 1000, 3, 16, 0x80, BLUE);
        
        if (t == 10)
        {
            LED0_TOGGLE();
            t = 0;
        }
        delay_ms(5);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109

可以看到,在实验应用代码中,定义了函数dac_creat_sin_buf(),该函数用于生成正弦波数据,并保存至数组g_dac_sin_buf中。在完成DAC初始化后,便生成了一组正弦波数据,并调用函数dac_dma_wave_enable()使能DAC通道1输出指定的正弦波,随后便通过扫描到的不同按键值,输出不同频率的正弦波。同时也还使能了ADC便于观察DAC输出的电压,DAC输出电压的模拟量和数字量以及ADC采集到电压的模拟量都将被实时地在LCD上进行显示。
39.3 下载验证
在完成编译和烧录操作后,可以看到LCD上实时地显示了DAC通道1输出电压的数字量和模拟量以及ADC1通道1采集到电压的模拟量,此时可以将PA1引脚(ADC1采集引脚)和PA4引脚(DAC通道1)短接,便可以看到LCD上显示的ADC采集到电压的模拟量随DAC通道1输出电压的模拟量变化。为了更方便地观察DAC通道1输出的正弦波,可以使用示波器观察PA4引脚的输出,通过按下KEY0按键可以观察到频率约为30KHz的正弦波,而按下KEY_UP按键则可以观察到频率约为3KHz的正弦波。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/weixin_40725706/article/detail/172495
推荐阅读
相关标签
  

闽ICP备14008679号