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

1)实验平台:正点原子stm32f103战舰开发板V4

2)平台购买地址:https://detail.tmall.com/item.htm?id=609294757420

3)全套实验源码+手册+视频下载地址: http://www.openedv.com/thread-340252-1-1.html##

第三十九章 DAC输出实验

本章介绍使用APM32F407的DAC输出指定的电压值。通过本章的学习。读者将学习到DAC的使用。

本章分为如下几个小节:

39.1 硬件设计

39.2 程序设计

39.3 下载验证

39.1 硬件设计

39.1.1 例程功能

  1. DAC的输出作为ADC的输入,并在LCD上显示DAC输出电压的数字量和模拟量和ADC采集到电压的模拟量
  2. 按下KEY_UP和KEY0按键,可分别增加和减少DAC的输出
  3. 可通过USMART直接设置DAC输出的电压值
  4. LED0闪烁,指示程序正在运行
    39.1.2 硬件资源
  5. LED
    LED0 - PF9
  6. 按键
    KEY0 - PE4
    KEY_UP - PA0
  7. USART1(PA9、PA10连接至板载USB转串口芯片上)
  8. 正点原子 2.8/3.5/4.3/7/10寸TFTLCD模块(仅限MCU屏,16位8080并口驱动)
  9. ADC1
    通道1 - PA1
  10. DAC
    通道1 - PA4
    39.1.3 原理图
    本章实验使用的DAC为APM32F407的片上资源,因此没有对应的连接原理图。
    39.2 程序设计
    39.2.1 Geehy标准库的DAC驱动
    本章实验主要使用DAC的通道1(PA4引脚)输出指定的电压值(0V~3.3V),其具体的步骤如下:
    ①:配置DAC的通道1
    ②:使能DAC的通道1
    ③:配置DAC通道1的保持寄存器,以输出指定电压
    在Geehy标准库中对应的驱动函数如下:
    ①:配置DAC通道
    该函数用于配置DAC通道的相关参数,其函数原型如下所示:
    void DAC_Config(uint32_t channel, DAC_Config_T* dacConfig);
    该函数的形参描述,如下表所示:
    形参 描述
    channel 指定DAC的通道
    例如:DAC_CHANNEL_1、DAC_CHANNEL_2(在apm32f4xx_dac.h文件中有定义)
    dacConfig 指向DAC通道配置结构体的指针
    需自行定义,并根据DAC通道的配置参数填充结构体中的成员变量
    表39.2.1.1 函数DAC_Config()形参描述
    该函数的返回值描述,如下表所示:
    返回值 描述
    无 无
    表39.2.1.2 函数DAC_Config()返回值描述
    该函数使用的DAC_Config_T类型的结构体变量传入DAC通道的配置参数,该结构体的定义如下所示:
c 复制代码
typedef enum
{
    DAC_TRIGGER_NONE					= 0x00000000,	/* 禁用DAC通道触发 */
    DAC_TRIGGER_TMR6_TRGO			= 0x00000004,	/* 定时器6的TRGO事件 */
    DAC_TRIGGER_TMR8_TRGO			= 0x0000000C,	/* 定时器8的TRGO事件 */
    DAC_TRIGGER_TMR7_TRGO			= 0x00000014,	/* 定时器7的TRGO事件 */
    DAC_TRIGGER_TMR5_TRGO			= 0x0000001C,	/* 定时器5的TRGO事件 */
    DAC_TRIGGER_TMR2_TRGO			= 0x00000024,	/* 定时器2的TRGO事件 */
    DAC_TRIGGER_TMR4_TRGO			= 0x0000002C,	/* 定时器4的TRGO事件 */
    DAC_TRIGGER_EINT9				= 0x00000034,	/* 外部中断线9 */
    DAC_TRIGGER_SOFT					= 0x0000003C	/* 软件触发 */
} DAC_TRIGGER_T;

typedef enum
{
    DAC_OUTPUT_BUFFER_ENBALE		= 0x00000000,	/* 禁用输出缓存 */
    DAC_OUTPUT_BUFFER_DISABLE		= 0x00000002	/* 使能输出缓存 */
} DAC_OUTPUT_BUFFER_T;

typedef enum
{
    DAC_WAVE_GENERATION_NONE		= 0x00000000,	/* 不产生波形 */
    DAC_WAVE_GENERATION_NOISE		= 0x00000040,	/* 产生噪声波形 */
    DAC_WAVE_GENERATION_TRIANGLE	= 0x00000080	/* 产生三角波波形 */
} DAC_WAVE_GENERATION_T;

typedef enum
{
    DAC_LFSR_MASK_BIT11_1			= 0x00000000,	/* 屏蔽LFSP[11:1] */
    DAC_LFSR_MASK_BIT11_2			= 0x00000100,	/* 屏蔽LFSP[11:2] */
    DAC_LFSR_MASK_BIT11_3			= 0x00000200,	/* 屏蔽LFSP[11:3] */
    DAC_LFSR_MASK_BIT11_4			= 0x00000300,	/* 屏蔽LFSP[11:4] */
    DAC_LFSR_MASK_BIT11_5			= 0x00000400,	/* 屏蔽LFSP[11:5] */
    DAC_LFSR_MASK_BIT11_6			= 0x00000500,	/* 屏蔽LFSP[11:6] */
    DAC_LFSR_MASK_BIT11_7			= 0x00000600,	/* 屏蔽LFSP[11:7] */
    DAC_LFSR_MASK_BIT11_8			= 0x00000700,	/* 屏蔽LFSP[11:8] */
    DAC_LFSR_MASK_BIT11_9			= 0x00000800,	/* 屏蔽LFSP[11:9] */
    DAC_LFSR_MASK_BIT11_10			= 0x00000900,	/* 屏蔽LFSP[11:10] */
    DAC_LFSR_MASK_BIT11				= 0x00000A00,	/* 屏蔽LFSP[11] */
    DAC_LFSR_MASK_NONE				= 0x00000B00,	/* 不屏蔽LFSP */

    DAC_TRIANGLE_AMPLITUDE_1		= 0x00000000,	/* 三角波幅值等于1 */
    DAC_TRIANGLE_AMPLITUDE_3		= 0x00000100,	/* 三角波幅值等于3 */
    DAC_TRIANGLE_AMPLITUDE_7		= 0x00000200,	/* 三角波幅值等于7 */
    DAC_TRIANGLE_AMPLITUDE_15		= 0x00000300,	/* 三角波幅值等于15 */
    DAC_TRIANGLE_AMPLITUDE_31		= 0x00000400,	/* 三角波幅值等于31 */
    DAC_TRIANGLE_AMPLITUDE_63		= 0x00000500,	/* 三角波幅值等于63 */
    DAC_TRIANGLE_AMPLITUDE_127		= 0x00000600,	/* 三角波幅值等于127 */
    DAC_TRIANGLE_AMPLITUDE_255		= 0x00000700,	/* 三角波幅值等于255 */
    DAC_TRIANGLE_AMPLITUDE_511		= 0x00000800,	/* 三角波幅值等于511 */
    DAC_TRIANGLE_AMPLITUDE_1023		= 0x00000900,	/* 三角波幅值等于1023 */
    DAC_TRIANGLE_AMPLITUDE_2047		= 0x00000A00,	/* 三角波幅值等于2047 */
    DAC_TRIANGLE_AMPLITUDE_4095		= 0x00000B00	/* 三角波幅值等于4095 */
} DAC_MASK_AMPLITUDE_SEL_T;

typedef struct
{
    DAC_TRIGGER_T				trigger;				/* 触发 */
    DAC_OUTPUT_BUFFER_T			outputBuffer;			/* 输出缓存 */
    DAC_WAVE_GENERATION_T		waveGeneration;			/* 产生波形 */
    DAC_MASK_AMPLITUDE_SEL_T	maskAmplitudeSelect;	/* 屏蔽LFSR位/三角波幅 */
} DAC_Config_T;
该函数的使用示例,如下所示:
#include "apm32f4xx.h"
#include "apm32f4xx_dac.h"

void example_fun(void)
{
    DAC_Config_T dac_init_struct;
    
    /* 配置DAC通道1 */
    dac_init_struct.trigger				= DAC_TRIGGER_NONE;
    dac_init_struct.outputBuffer		= DAC_OUTPUT_BUFFER_DISABLE;
    dac_init_struct.waveGeneration		= DAC_WAVE_GENERATION_NONE;
    dac_init_struct.maskAmplitudeSelect	= DAC_LFSR_MASK_BIT11_1;
    DAC_Config(DAC_CHANNEL_1, &dac_init_struct);
}

②:使能DAC通道

该函数用于使能DAC通道,其函数原型如下所示:

void DAC_Enable(DAC_CHANNEL_T channel);

该函数的形参描述,如下表所示:

形参 描述

channel 指定DAC的通道

例如:DAC_CHANNEL_1、DAC_CHANNEL_2(在apm32f4xx_dac.h文件中有定义)

表39.2.1.3 函数DAC_Enable()形参描述

该函数的返回值描述,如下表所示:

返回值 描述

无 无

表39.2.1.4 函数DAC_Enable()返回值描述

该函数的使用示例,如下所示:

c 复制代码
#include "apm32f4xx.h"
#include "apm32f4xx_dac.h"

void example_fun(void)
{
    /* 使能DAC通道1 */
    DAC_Enable(DAC_CHANNEL_1);
}

③:配置DAC通道数据保持寄存器

该函数用于配置DAC通道的数据保持寄存器,其函数原型如下所示:

void DAC_ConfigChannel1Data(DAC_ALIGN_T align, uint16_t data);

void DAC_ConfigChannel2Data(DAC_ALIGN_T align, uint16_t data);

该函数的形参描述,如下表所示:

形参 描述

align 数据对齐方式

例如:DAC_ALIGN_12BIT_L、DAC_ALIGN_8BIT_R等(在apm32f4xx_dac.h文件中有定义)

data 写入数据保持寄存器的数据

表39.2.1.5 函数DAC_ConfigChannelnData()形参描述

该函数的返回值描述,如下表所示:

返回值 描述

无 无

表39.2.1.6 函数DAC_ConfigChannelnData()返回值描述

该函数的使用示例,如下所示:

c 复制代码
#include "apm32f4xx.h"
#include "apm32f4xx_dac.h"

void example_fun(void)
{
    /* 配置DAC通道1的保持寄存器 */
    DAC_ConfigChannel1Data(DAC_ALIGN_12BIT_R, 1024);
}

39.2.2 DAC驱动

本章实验的DAC驱动主要负责向应用层提供DAC的初始化和配置其输出指定电压的函数。本章实验中,DAC的驱动代码包括dac.c和dac.h两个文件。

DAC驱动中,DAC的初始化函数,如下所示:

c 复制代码
/**
 * @brief	初始化DAC
 * @param	outx: 待初始化的DAC通道
 * @arg1: 	DAC通道1
 * @arg2: 	DAC通道2
 * @retval	无
 */
void dac_init(uint8_t outx)
{
    GPIO_Config_T gpio_init_struct;
    DAC_Config_T dac_init_struct;
    
    /* 使能时钟 */
    RCM_EnableAPB1PeriphClock(RCM_APB1_PERIPH_DAC);		/* 使能DAC时钟 */
    RCM_EnableAHB1PeriphClock(RCM_AHB1_PERIPH_GPIOA);	/* 使能DAC输出引脚端口时钟 */
    
    /* 配置DAC输出引脚 */
    gpio_init_struct.pin	= (outx == 1) ? GPIO_PIN_4 : GPIO_PIN_5;
    gpio_init_struct.mode	= GPIO_MODE_AN;
    gpio_init_struct.pupd	= GPIO_PUPD_NOPULL;
    GPIO_Config(GPIOA, &gpio_init_struct);
    
    /* 配置DAC */
    dac_init_struct.trigger					= DAC_TRIGGER_NONE;
    dac_init_struct.outputBuffer			= DAC_OUTPUT_BUFFER_DISABLE;
    dac_init_struct.waveGeneration			= DAC_WAVE_GENERATION_NONE;
    dac_init_struct.maskAmplitudeSelect		= DAC_LFSR_MASK_BIT11_1;
    DAC_Config((outx == 1) ? DAC_CHANNEL_1 : DAC_CHANNEL_2, &dac_init_struct);
    
    /* 使能DAC */
    DAC_Enable((outx == 1) ? DAC_CHANNEL_1 : DAC_CHANNEL_2);
}

从上面的代码中可以看出,DAC的初始化函数会根据传入的参数outx自动配置并使能DAC的通道1或通道2,同时将DAC通道对应的输出引脚配置为模拟模式。

DAC驱动中,配置DAC通道输出指定电压的函数,如下所示:

c 复制代码
/**
 * @brief	设置DAC输出电压
 * @param	outx: 指定输出电压的DAC通道
 * @arg1: 	DAC通道1
 * @arg2: 	DAC通道2
 * @param	vol: DAC输出电压值(扩大100倍),范围0~3300
 * @retval	无
 */
void dac_set_voltage(uint8_t outx, uint16_t vol)
{
    uint32_t dacdata;
    
    dacdata = vol * 4095 / 3300;	/* 将电压值转换为数字量 */
    if (dacdata > 4095)				/* 限制最大值 */
    {
    		dacdata = 4095;
    }
    
    if (outx == 1)
    {
    		/* DAC通道1输出的电压 */
    		DAC_ConfigChannel1Data(DAC_ALIGN_12BIT_R, dacdata);
    }
    else
    {
    		/* DAC通道2输出的电压 */
    		DAC_ConfigChannel2Data(DAC_ALIGN_12BIT_R, dacdata);
    }
}

该函数将输入的电压模拟量转换为DAC输出的数字量后,将该值写入指定DAC通道的数据保持寄存器。

39.2.3 实验应用代码

本章实验的应用代码,如下所示:

c 复制代码
int main(void)
{
    uint8_t key;
    uint16_t dacdata = 0;
    uint8_t t = 0;
    uint16_t dacoutdata;
    uint16_t dac_voltage;
    uint16_t adcdata;
    uint16_t adc_voltage;
    
    NVIC_ConfigPriorityGroup(NVIC_PRIORITY_GROUP_3);	/* 设置中断优先级分组为组3 */
    sys_apm32_clock_init(336, 8, 2, 7);					/* 配置系统时钟 */
    delay_init(168);										/* 初始化延时功能 */
    usart_init(115200);									/* 初始化串口 */
    usmart_dev.init(84);								/* 初始化USMART */
    led_init();											/* 初始化LED */
    lcd_init();											/* 初始化LCD */
    adc_init();											/* 初始化ADC */
    dac_init(1);											/* 初始化DAC通道1 */
    dac_set_voltage(1, 0);								/* 设置DAC输出电压 */
    
    lcd_show_string(30, 50, 200, 16, 16, "APM32", RED);
    lcd_show_string(30, 70, 200, 16, 16, "DAC TEST", RED);
    lcd_show_string(30, 90, 200, 16, 16, "ATOM@ALIENTEK", RED);
    lcd_show_string(30, 110, 200, 16, 16, "KEY_UP:+  KEY0:-", RED);
    lcd_show_string(30, 150, 200, 16, 16, "DAC VAL:", BLUE);
    lcd_show_string(30, 170, 200, 16, 16, "DAC VOL:0.000V", BLUE);
    lcd_show_string(30, 190, 200, 16, 16, "ADC VOL:0.000V", BLUE);
    
    while (1)
    {
    		t++;
    		key = key_scan(0);/* 扫描按键 */
    		
    		if (key == WKUP_PRES)/* KEY_UP按键按下,DAC输出增加200 */
    		{
    			if (dacdata < 4000)
    			{
    				dacdata += 200;
    			}
    			DAC_ConfigChannel1Data(DAC_ALIGN_12BIT_R, dacdata);
    			}
    		else if (key == KEY0_PRES)/* KEY0按键按下,DAC输出减少200 */
    		{
    			if (dacdata > 200)
    			{
    				dacdata -= 200;
    			}
    			else
    			{
    				dacdata = 0;
    			}
    			DAC_ConfigChannel1Data(DAC_ALIGN_12BIT_R, dacdata);
    		}
    		
    		if ((t == 10) || (key == WKUP_PRES) || (key == KEY0_PRES))
    		{
    			/* 获取DAC输出电压的数字量 */
    			dacoutdata = DAC_ReadDataOutputValue(DAC_CHANNEL_1);
    			/* 显示DAC输出电压的数字量 */
    			lcd_show_xnum(94, 150, dacoutdata, 5, 16, 0, BLUE);
    			
    			/* 计算实际输出的电压值(扩大1000倍) */
    			dac_voltage = (dacoutdata * 3300) / 4095;
    			/* 显示DAC输出电压值的整数部分 */
    			lcd_show_xnum(94, 170, dac_voltage / 1000, 1, 16, 0, BLUE);
    			/* 显示DAC输出电压值的小数部分(保留三位小数) */
    			lcd_show_xnum(110, 170, dac_voltage % 1000, 3, 16, 0x80, BLUE);
    			
    			/* 获取ADC采集到电压的数字量 */
    			adcdata = adc_get_result_average(ADC_ADCX_CHY, 10);
    			/* 计算实际电压值(扩大1000倍) */
    			adc_voltage = (adcdata * 3300) / 4095;
    			/* 显示ADC采集电压值的整数部分 */
    			lcd_show_xnum(94, 190, adc_voltage / 1000, 1, 16, 0, BLUE);
    			/* 显示ADC采集电压值的小数部分(保留三位小数) */
    			lcd_show_xnum(110, 190, adc_voltage % 1000, 3, 16, 0x80, BLUE);
    			
    			LED0_TOGGLE();
    			t = 0;
    		}
    		
    		delay_ms(10);
    }
}

可以看到,应用代码中不仅初始化了DAC的通道1(PA4引脚)同时还使能了ADC的通道1(PA1引脚)用于采集DAC通道1的输出电压。在完成初始化后,便不断地扫描按键并将DAC输出电压的模拟量和数字量以及ADC采集到电压的模拟量在LCD上进行显示,此时若扫描到KEY0按键,则减少DAC输出电压的数字量,若扫描到KEY_UP按键,则增加DAC输出电压的数字量。

39.3 下载验证

在完成编译和烧录操作后,可以看到LCD上实时刷新显示着DAC输出电压的模拟量和数字量以及ADC采集到电压的模拟量,此时可以将PA4引脚(DAC通道1输出引脚)和PA4引脚(ADC通道1采集引脚)通过杜邦线相连,在按下KEY0或KEY_UP按键来调整DAC通道1的输出电压,可以看到LCD上显示的DAC输出电压的模拟量和数字量以及ADC采集到电压的模拟量也随之变化,并且ADC采集到电压的模拟量也十分接近DAC输出电压的模拟量。

相关推荐
嵌入式详谈2 小时前
基于STM32的智能风扇控制系统设计
stm32·单片机·嵌入式硬件
小小怪大梦想2 小时前
RTC实时时钟
stm32·单片机·嵌入式硬件
水饺编程6 小时前
【英特尔IA-32架构软件开发者开发手册第3卷:系统编程指南】2001年版翻译,1-2
linux·嵌入式硬件·fpga开发
电子科技圈6 小时前
IAR全面支持国科环宇AS32X系列RISC-V车规MCU
人工智能·嵌入式硬件·mcu·编辑器
SZPU领跑7 小时前
第十二届蓝桥杯嵌入式省赛程序设计题解析(基于HAL库)(第一套)
stm32·单片机·算法·职场和发展·蓝桥杯
逢生博客7 小时前
Rust 语言开发 ESP32C3 并在 Wokwi 电子模拟器上运行(esp-hal 非标准库、LCD1602、I2C)
开发语言·后端·嵌入式硬件·rust
Tlog嵌入式9 小时前
蓝桥杯【物联网】零基础到国奖之路:十六. 扩展模块之矩阵按键
arm开发·stm32·单片机·mcu·物联网·蓝桥杯·iot
打地基的小白10 小时前
UART通信—基于江科大源码基础进行的改进和解析
单片机·嵌入式硬件·uart通信·代码详解
黄小美32111 小时前
STM32(五)GPIO输入硬件电路及C语言知识复习
stm32·单片机·嵌入式硬件
py.鸽鸽11 小时前
STM32
stm32·单片机·嵌入式硬件