本帖最后由 正点原子运营 于 2023-8-10 11:34 编辑
第三十四章 DAC实验 1)实验平台:正点原子探索者STM32F407开发板
2) 章节摘自【正点原子】STM32F407开发指南 V1.1
3)购买链接:https://detail.tmall.com/item.htm?id=609294673401
4)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/stm32/zdyz_stm32f407_explorerV3.html
5)正点原子官方B站:https://space.bilibili.com/394620890
6)STM32技术交流QQ群:151941872
本章,我们将介绍STM32F407的DAC(Digital -to- analog converters,数模转换器)功能。我们通过三个实验来学习DAC,分别是DAC输出实验、DAC输出三角波实验和DAC输出正弦波实验。 本章分为如下几个小节: 34.1 DAC简介 34.2 DAC输出实验 34.3 DAC输出三角波实验 34.4 DAC输出正弦波实验
34.1 DAC简介STM32F407的DAC模块(数字/模拟转换模块)是12位数字输入,电压输出型的DAC。DAC可以配置为8位或12位模式,也可以与DMA控制器配合使用。DAC工作在12位模式时,数据可以设置成左对齐或右对齐。DAC模块有2个输出通道,每个通道都有单独的转换器。在双DAC模式下,2个通道可以独立地进行转换,也可以同时进行转换并同步地更新2个通道的输出。DAC可以通过引脚输入参考电压Vref+(通ADC共用)以获得更精确的转换结果。 DAC通道框图如图34.1.1所示,DAC模块主要特点有: ① 2个DAC转换器:每个转换器对应1个输出通道 ② 8位或者12位单调输出 ③ 12位模式下数据左对齐或者右对齐 ④ 同步更新功能 ⑤ 噪声波形生成 ⑥ 三角波形生成 ⑦ 双DAC双通道同时或者分别转换 ⑧ 每个通道都有DMA功能
图中VDDA和VSSA为DAC模块模拟部分的供电,而VREF+则是DAC模块的参考电压。DAC_OUT1/2就是DAC的两个输出通道了(对应PA4或者PA5引脚)。ADC的这些输入/输出引脚信息如下表所示: 从图34.1.1可以看出,DAC输出是受DORx(x=1/2,下同)寄存器直接控制的,但是我们不能直接往DORx寄存器写入数据,而是通过DHRx间接的传给DORx寄存器,实现对DAC输出的控制。 前面我们提到,STM32F407的DAC支持8/12位模式,8位模式的时候是固定的右对齐的,而12位模式又可以设置左对齐/右对齐。DAC单通道模式下的数据寄存器对齐方式,总共有3种情况,如下图所示: 图34.1.2 DAC单通道模式下的数据寄存器对齐方式 ① 8位数据右对齐:用户将数据写入DAC_DHR8Rx[7:0]位(实际存入DHRx[11:4]位)。 ② 12位数据左对齐:用户将数据写入DAC_DHR12Lx[15:4]位(实际存入DHRx[11:0]位)。 ③ 12位数据右对齐:用户将数据写入DAC_DHR12Rx[11:0]位(实际存入DHRx[11:0]位)。
我们本章实验中使用的都是单通道模式下的DAC通道1,采用12位右对齐格式,所以采用第③种情况。另外DAC还具有双通道转换功能。
对于 DAC 双通道(可用时),也有三种可能的方式,如下图所示: 图34.1.3 DAC双通道模式下的数据寄存器对齐方式 ① 8位数据右对齐:用户将DAC通道1的数据写入DAC_DHR8RD[7:0]位(实际存入DHR1 [11:4]位),将DAC通道2的数据写入DAC_DHR8RD[15:8]位(实际存入DHR2 [11:4]位)。 ② 12位数据左对齐:用户将DAC通道1的数据写入DAC_DHR12LD [15:4]位(实际存入DHR1[11:0]位),将DAC通道2的数据写入DAC_DHR12LD [31:20]位(实际存入DHR2[11:0]位)。 ③ 12位数据右对齐:用户将DAC通道1的数据写入DAC_DHR12RD [11:0]位(实际存入DHR1[11:0]位),将DAC通道2的数据写入DAC_DHR12RD [27:16]位(实际存入DHR2[11:0]位)。
DAC可以通过软件或者硬件触发转换,通过配置TENx控制位来决定。
如果没有选中硬件触发(寄存器DAC_CR1的TENx位置0),存入寄存器DAC_DHRx的数据会在1个APB1时钟周期后自动传至寄存器DAC_DORx。如果选中硬件触发(寄存器DAC_CR1的TENx位置1),数据传输在触发发生以后3个APB1时钟周期后完成。一旦数据从DAC_DHRx寄存器装入DAC_DORx寄存器,在经过时间tSETTLING之后,输出即有效,这段时间的长短依电源电压和模拟输出负载的不同会有所变化。我们可以从《STM32F407ZGT6.pdf》数据手册查到tSETTLING的典型值为3us,最大是6us,所以DAC的转换速度最快是333K左右。
不使用硬件触发(TEN=0),其转换的时间框图如图34.1.4所示: 图34.1.4 TEN=0时DAC模块转换时间框图 当DAC的参考电压为VREF+的时候,DAC的输出电压是线性的从0~VREF+,12位模式下DAC输出电压与VREF +以及DORx的计算公式如下: DACx输出电压= VREF *(DORx/4096) 如果使用硬件触发(TEN=1),可通过外部事件(定时计数器、外部中断线)触发DAC转换。由TSELx[2:0]控制位来决定选择8个触发事件中的一个来触发转换。这8个触发事件如下表所示:
原表见《STM32F4xx参考手册_V4(中文版).pdf》第292页表58。
每个DAC通道都有DMA功能,两个DMA通道分别用于处理两个DAC通道的DMA请求。如果DMAENx位置1时,如果发生外部触发(而不是软件触发),就会产生一个DMA请求,然后DAC_DHRx寄存器的数据被转移到DAC_NORx寄存器。
34.2 DAC输出实验本实验我们来学习DAC输出实验。
34.2.1 DAC寄存器下面,我们介绍要实现DAC的通道1输出,需要用到的一些DAC寄存器。
l DACx控制寄存器(DACx_CR) DACx控制寄存器描述如图34.2.1.1所示: EN1位:用于DAC通道1的使能,我们需要用到DAC通道1的输出,该位必须设置为1。 BOFF1位:用于DAC输出缓存控制,这里STM32的DAC输出缓存做的有些不好,如果使能的话,虽然输出能力强一点,但是输出没法到0,这是个很严重的问题。所以该位我们设置为0。 TEN1位:用于DAC通道1的触发使能,我们设置该位为0,不使用硬件触发。写入DHR1的值会在1个APB1周期后传送到DOR1,然后输出到PA4口上。 TSEL1[2:0]位,用于选择DAC通道1的触发方式,这里我们没有用到外部触发,所以这几位设置为0即可。 WAVE1[1:0]位,用于控制DAC通道1的噪声/波形输出功能,默认设置为00,不使能噪声/波形输出。 DMAEN1位,用于控制DAC通道1的DMA使能,本实验不使能,设置该位为0即可。 MAMP[3:0]位,用于在噪声生成模式下选择屏蔽位,在三角波生成模式下选择波形值。本章没有用到波形发生器,所以设置为0即可。
l DACx通道1 12位右对齐数据保持寄存器(DACx_DHR12R1) DACx通道1 12位右对齐数据保持寄存器描述如图34.2.1.3所示: 该寄存器用来设置DAC输出,通过写入12位数据到该寄存器,就可以在DAC输出通道1(PA4)得到我们所要的结果。
34.2.2 硬件设计
1. 例程功能使用KEY1/KEY_UP两个按键,控制STM32内部DAC的通道1输出电压大小,然后通过ADC1的通道5采集DAC输出的电压,在LCD模块上面显示ADC采集到的电压值以及DAC的设定输出电压值等信息。也可以通过usmart调用dac_set_voltage函数,来直接设置DAC输出电压。LED0闪烁,提示程序运行。
2. 硬件资源1)LED灯 LED0 – PF9 2)串口1(PA9/PA10连接在板载USB转串口芯片CH340上面) 3)正点原子2.8/3.5/4.3/7寸TFTLCD模块(仅限MCU屏,16位8080并口驱动) 4)独立按键 :KEY1 – PE3、WK_UP – PA0 5)ADC1 :通道5 – PA5 6)DAC1 :通道1 – PA4
3. 原理图我们来看看原理图上ADC1通道5(PA5)和DAC1通道1(PA4)引出来的引脚,如下图所示: 图34.2.2.1 ADC和DAC在开发板上的连接关系原理图 P11是多功能端口,我们只需要通过跳线帽连接P1的ADC和DAC,就可以使得ADC1通道5(PA5)和DAC1通道1(PA4)连接起来。对应的硬件连接如图34.2.2.2所示: 34.2.3 程序设计
34.2.3.1 DAC的HAL库驱动DAC在HAL库中的驱动代码在stm32f4xx_hal_dac.c和stm32f4xx_hal_dac_ex.c文件(及其头文件)中。
1.HAL_DAC_Init函数 DAC的初始化函数,其声明如下: - HAL_StatusTypeDefHAL_DAC_Init(DAC_HandleTypeDef *hdac);
复制代码l 函数描述: 用于初始化DAC。
l 函数形参: 形参1是DAC_HandleTypeDef结构体类型指针变量,其定义如下: - typedef struct
- {
- DAC_TypeDef *Instance; /* DAC寄存器基地址 */
- __IOHAL_DAC_StateTypeDef State; /* DAC 工作状态 */
- HAL_LockTypeDef Lock; /* DAC锁定对象 */
- DMA_HandleTypeDef *DMA_Handle1; /* 通道1的DMA处理句柄指针 */
- DMA_HandleTypeDef *DMA_Handle2; /* 通道2的DMA处理句柄指针 */
- __IO uint32_t ErrorCode; /* DAC错误代码 */
- }DAC_HandleTypeDef;
复制代码从该结构体看到该函数并没有设置任何DAC相关寄存器,即没有对DAC进行任何配置,它只是HAL库提供用来在软件上初始化DAC,为后面HAL库操作DAC做好准备。
l 函数返回值: HAL_StatusTypeDef枚举类型的值。
l 注意事项: DAC的MSP初始化函数HAL_DAC_MspInit,该函数声明如下: - voidHAL_DAC_MspInit(DAC_HandleTypeDef* hdac);
复制代码2.HAL_DAC_ConfigChannel函数 DAC 的通道参数初始化函数,其声明如下: - HAL_StatusTypeDefHAL_DAC_ConfigChannel(DAC_HandleTypeDef *hdac,
- DAC_ChannelConfTypeDef *sConfig, uint32_t Channel);
复制代码l 函数描述: 该函数用来配置DAC通道的触发类型以及输出缓冲。
l 函数形参: 形参1是DAC_HandleTypeDef结构体类型指针变量。 形参2是DAC_ChannelConfTypeDef结构体类型指针变量,其定义如下: - typedef struct
- {
- uint32_t DAC_Trigger; /* DAC触发源的选择 */
- uint32_t DAC_OutputBuffer; /* 启用或者禁用DAC通道输出缓冲区 */
- }DAC_ChannelConfTypeDef;
复制代码形参3用于选择要配置的通道,可选择DAC_CHANNEL_1或者DAC_CHANNEL_2。
l 函数返回值: HAL_StatusTypeDef枚举类型的值。
3.HAL_DAC_Start函数 使能启动DAC转换通道函数,其声明如下: - HAL_StatusTypeDefHAL_DAC_Start(DAC_HandleTypeDef *hdac, uint32_t Channel);
复制代码
l 函数描述: 使能启动DAC转换通道。
l 函数形参: 形参1是DAC_HandleTypeDef结构体类型指针变量。 形参2用于选择要启动的通道,可选择DAC_CHANNEL_1或者DAC_CHANNEL_2。
l 函数返回值: HAL_StatusTypeDef枚举类型的值。
4.HAL_DAC_SetValue函数 DAC的通道输出值函数,其声明如下: - HAL_StatusTypeDefHAL_DAC_SetValue(DAC_HandleTypeDef *hdac, uint32_t Channel,uint32_t Alignment, uint32_t Data);
复制代码
l 函数描述: 配置DAC的通道输出值。
l 函数形参: 形参1是DAC_HandleTypeDef结构体类型指针变量。 形参2用于选择要输出的通道,可选择DAC_CHANNEL_1或者DAC_CHANNEL_2。 形参3用于指定数据对齐方式。 形参4设置要加载到选定数据保存寄存器中的数据。
l 函数返回值: HAL_StatusTypeDef枚举类型的值。
5.HAL_DAC_GetValue函数 DAC读取通道输出值函数,其声明如下: - uint32_t HAL_DAC_GetValue(DAC_HandleTypeDef *hdac, uint32_t Channel);
复制代码
l 函数描述: 获取所选DAC通道的最后一个数据输出值。
l 函数形参: 形参1是DAC_HandleTypeDef结构体类型指针变量。 形参2用于选择要读取的通道,可选择DAC_CHANNEL_1或者DAC_CHANNEL_2。
l 函数返回值: 获取到的输出值。
DAC输出配置步骤1)开启DAC和通道输出的GPIO时钟,配置该IO口的复用功能输出 首先开启DAC的时钟,然后配置GPIO为复用功能输出。本实验我们默认用到DAC1通道1,对应IO是PA4,它们的时钟开启方法如下: - __HAL_RCC_DAC_CLK_ENABLE (); /* 使能DAC1时钟 */
- __HAL_RCC_GPIOA_CLK_ENABLE(); /* 开启GPIOA时钟 */
复制代码IO口复用功能是通过函数HAL_GPIO_Init来配置的。
2)初始化DAC 通过HAL_DAC_Init函数来设置需要初始化的DAC。该函数并没有设置任何DAC相关寄存器,也就是说没有对DAC进行任何配置,它只是HAL库提供用来在软件上初始化DAC。 注意:该函数会调用:HAL_DAC_MspInit回调函数来完成对DAC底层以及其输入通道IO的初始化,包括:ADC及GPIO时钟使能、GPIO模式设置等。这里我们没有重定义该回调函数,而是直接在dac_init函数中完成这些设置。
3)配置DAC通道并启动DA转换器 在HAL库中,通过HAL_DAC_ConfigChannel函数来设置配置DAC的通道,根据需求设置触发类型以及输出缓冲。 配置好DAC通道之后,通过HAL_DAC_Start函数启动DA转换器。
4)设置DAC的输出值 通过HAL_DAC_SetValue函数设置DAC的输出值。
34.2.3.2 程序流程图
34.2.3.3 程序解析这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。DAC驱动源码包括两个文件:dac.c和dac.h。
dac.h文件只有一些声明,下面直接开始介绍dac.c的程序,首先是DAC初始化函数。 - /**
- *@brief DAC初始化函数
- * @note 本函数支持DAC1_OUT1/2通道初始化
- * DAC的输入时钟来自APB1, 时钟频率=42MHz=23.8ns
- * DAC在输出buffer关闭的时候, 输出建立时间最小值: tSETTLING = 3us
- * 因此DAC输出的最高速度约为:333Khz, 以10个点为一个周期, 最大能输出33Khz左
- * 右的波形
- *
- *@param outx: 要初始化的通道
- * @arg 1, 初始化DAC通道1
- * @arg 2, 初始化DAC通道2
- *@retval 无
- */
- void dac_init(uint8_t outx)
- {
- __HAL_RCC_DAC_CLK_ENABLE(); /* 使能DAC1的时钟 */
- __HAL_RCC_GPIOA_CLK_ENABLE();/* 使能DAC OUT1/2的IO口时钟(都在PA口,PA4/PA5) */
- GPIO_InitTypeDef gpio_init_struct;
- /*STM32单片机, 总是PA4=DAC1_OUT1,PA5=DAC1_OUT2 */
- gpio_init_struct.Pin = (outx==1) ? GPIO_PIN_4 : GPIO_PIN_5;
- gpio_init_struct.Mode = GPIO_MODE_ANALOG;
- gpio_init_struct.Pull = GPIO_NOPULL;
- HAL_GPIO_Init(GPIOA, &gpio_init_struct);
- g_dac1_handle.Instance = DAC;
- HAL_DAC_Init(&g_dac1_handle); /* 初始化DAC */
- DAC_ChannelConfTypeDef dac_ch_conf;
- dac_ch_conf.DAC_Trigger = DAC_TRIGGER_NONE; /* 不使用触发功能 */
- dac_ch_conf.DAC_OutputBuffer = DAC_OUTPUTBUFFER_DISABLE;/* DAC1输出缓冲关闭 */
-
- switch(outx)
- {
- case 1: /* DAC通道1配置 */
- HAL_DAC_ConfigChannel(&g_dac1_handle, &dac_ch_conf, DAC_CHANNEL_1);
- HAL_DAC_Start(&g_dac1_handle, DAC_CHANNEL_1); /* 开启DAC通道1 */
- break;
- case 2: /* DAC通道2配置 */
- HAL_DAC_ConfigChannel(&g_dac1_handle, &dac_ch_conf, DAC_CHANNEL_2);
- HAL_DAC_Start(&g_dac1_handle, DAC_CHANNEL_2); /* 开启DAC通道1 */
- break;
- default : break;
- }
- }
复制代码该函数主要调用HAL_DAC_Init、HAL_DAC_ConfigChannel和HAL_DAC_Start函数三个函数对DAC进行初始化。一般我们是使用HAL_DAC_Init的MSP初始化回调函数HAL_DAC_MspInit用来编写存放时钟使能和IO口配置,这里我们为了使dac_init支持DAC1的OUT1/2两个通道初始化,就直接在dac_init里面初始化IO配置等。
下面是设置DAC通道1/2输出电压函数,其定义如下: - /**
- *@brief 设置通道1/2输出电压
- *@param outx: 1,通道1; 2,通道2
- *@param vol : 0~3300,代表0~3.3V
- *@retval 无
- */
- voiddac_set_voltage(uint8_t outx, uint16_t vol)
- {
- double temp = vol;
- temp /= 1000;
- temp = temp * 4096 / 3.3;
- if (temp >= 4096)temp = 4095; /* 如果值大于等于4096, 则取4095 */
- if (outx == 1) /* 通道1 */
- {
- /* 12位右对齐数据格式设置DAC值 */
- HAL_DAC_SetValue(&g_dac_handle, DAC_CHANNEL_1, DAC_ALIGN_12B_R, temp);
- }
- else /* 通道2 */
- {
- /* 12位右对齐数据格式设置DAC值 */
- HAL_DAC_SetValue(&g_dac_handle, DAC_CHANNEL_2, DAC_ALIGN_12B_R, temp);
- }
- }
复制代码该函数实际就是将电压值转换为DAC输入值,形参1用于设置通道,形参2设置要输出的电压值,设置的范围:0~3300,代表0~3.3V。
最后在main函数里面编写如下代码: - int main(void)
- {
- uint16_t adcx;
- float temp;
- uint8_t t = 0;
- uint16_t dacval = 0;
- uint8_t key;
- HAL_Init(); /* 初始化HAL库 */
- sys_stm32_clock_init(336, 8, 2, 7); /* 设置时钟, 168Mhz */
- delay_init(168); /* 延时初始化 */
- usart_init(115200); /* 串口初始化为115200 */
- usmart_dev.init(84); /* 初始化USMART */
- led_init(); /* 初始化LED */
- lcd_init(); /* 初始化LCD */
- key_init(); /* 初始化按键 */
- adc_init(); /* 初始化ADC */
- dac_init(1); /* 初始化DAC1_OUT1通道 */
- lcd_show_string(30, 50, 200, 16, 16, "STM32", RED);
- lcd_show_string(30, 70, 200, 16, 16, "DACTEST", RED);
- lcd_show_string(30, 90, 200, 16, 16, "ATOM@ALIENTEK", RED);
- lcd_show_string(30, 110, 200, 16, 16, "WK_UP:+ KEY1:-", RED);
-
- lcd_show_string(30, 150, 200, 16, 16, "DACVAL:", BLUE);
- lcd_show_string(30, 170, 200, 16, 16, "DACVOL:0.000V", BLUE);
- lcd_show_string(30, 190, 200, 16, 16, "ADCVOL:0.000V", BLUE);
- /* DAC初始值为0 */
- HAL_DAC_SetValue(&g_dac_handle, DAC_CHANNEL_1, DAC_ALIGN_12B_R, 0);
- while (1)
- {
- t++;
- key = key_scan(0); /* 按键扫描 */
- if (key == WKUP_PRES)
- {
- if (dacval < 4000)dacval += 200; /* DAC输出增大200 */
- HAL_DAC_SetValue(&g_dac_handle, DAC_CHANNEL_1, DAC_ALIGN_12B_R,
- dacval);
- }
- else if (key == KEY1_PRES)
- {
- if (dacval > 200)dacval -= 200; /* DAC输出减少200 */
- else dacval = 0;
- HAL_DAC_SetValue(&g_dac_handle, DAC_CHANNEL_1, DAC_ALIGN_12B_R,
- dacval);
- }
- /* WKUP/KEY1按下了,或者定时时间到了 */
- if (t == 10 || key == KEY1_PRES || key == WKUP_PRES)
- {
- /* 读取前面设置DAC1_OUT1的值 */
- adcx =HAL_DAC_GetValue(&g_dac_handle, DAC_CHANNEL_1);
- lcd_show_xnum(94, 150, adcx, 4, 16, 0, BLUE); /* 显示DAC寄存器值 */
- temp = (float)adcx * (3.3 / 4096); /* 得到DAC电压值 */
- adcx = temp;
- lcd_show_xnum(94, 170, temp, 1, 16, 0, BLUE); /* 显示电压值整数部分 */
- temp -= adcx;
- temp *= 1000;
- lcd_show_xnum(110, 170, temp, 3, 16, 0X80, BLUE);/*显示电压值的小数部分*/
- /* 得到ADC通道19的转换结果 */
- adcx = adc_get_result_average(ADC_ADCX_CHY, 10);
- temp = (float)adcx * (3.3 / 65536); /* 得到ADC电压值(adc是16bit的) */
- adcx = temp;
- lcd_show_xnum(94, 190, temp, 1, 16, 0, BLUE); /* 显示电压值整数部分 */
- temp -= adcx;
- temp *= 1000;
- lcd_show_xnum(110, 190, temp, 3, 16, 0X80, BLUE);/*显示电压值的小数部分*/
- LED0_TOGGLE(); /* LED0闪烁 */
- t = 0;
- }
- delay_ms(10);
- }
- }
复制代码此部分代码,我们通过KEY_UP(WKUP按键)和KEY1(也就是上下键)来实现对DAC输出的幅值控制。按下KEY_UP增加,按KEY1减小。同时在LCD上面显示DHR12R1寄存器的值、DAC设置输出电压以及ADC采集到的DAC输出电压。
34.2.4 下载验证下载代码后,可以看到LED0不停的闪烁,提示程序已经在运行了。LCD显示如下图所示: 验证试验前记得先通过跳线帽连接P11的ADC和DAC,然后我们可以通过按WK_UP按键,增加DAC输出的电压,这时ADC采集到的电压也会增大,通过按KEY1减小DAC输出的电压,这时ADC采集到的电压也会减小。
除此之外,我们还可以通过usmart调用dac_set_voltage函数,来直接设置DAC输出电压,如下图34.2.4.2所示:
34.3 DAC输出三角波实验本实验我们来学习使用如何让DAC输出三角波,DAC初始化部分还是用DAC输出实验的,所以做本实验的前提是先学习DAC输出实验。
34.3.1 DAC寄存器本实验用到的寄存器在DAC输出实验都有介绍。
34.3.2 硬件设计
1. 例程功能使用DAC输出三角波,通过KEY0/KEY1两个按键,控制DAC1的通道1输出两种三角波,需要通过示波器接PA4进行观察。也可以通过usmart调用dac_triangular_wave函数,来控制输出哪种三角波。LED0闪烁,提示程序运行。
2. 硬件资源1)LED灯 LED0 – PF9 2)串口1(PA9/PA10连接在板载USB转串口芯片CH340上面) 3)正点原子2.8/3.5/4.3/7寸TFTLCD模块(仅限MCU屏,16位8080并口驱动) 4)独立按键 : KEY0 – PE4 KEY1 – PE3 5)DAC1 :通道1 - PA4
3. 原理图我们只需要把示波器的探头接到DAC1通道1(PA4)引脚,就可以在示波器上显示DAC输出的波形。PA4在P11多功能端口的DAC标志排针已经引出,硬件连接如图34.3.2.1所示: 34.3.3 程序设计本实验用到的DAC的HAL库API函数前面都介绍过,具体调用情况请看程序解析部分。下面介绍DAC输出三角波的配置步骤。
DAC输出三角波配置步骤1)开启DAC和通道输出的GPIO时钟,配置该IO口的复用功能输出 首先开启DAC的时钟,然后配置GPIO为复用功能输出。本实验我们默认用到DAC1通道1,对应IO是PA4,它们的时钟开启方法如下: - __HAL_RCC_DAC_CLK_ENABLE(); /* 使能DAC1时钟 */
- __HAL_RCC_GPIOA_CLK_ENABLE(); /* 开启GPIOA时钟 */
复制代码IO口复用功能是通过函数HAL_GPIO_Init来配置的。
2)初始化DAC 通过HAL_DAC_Init函数来设置需要初始化的DAC。该函数并没有设置任何DAC相关寄存器,也就是说没有对DAC进行任何配置,它只是HAL库提供用来在软件上初始化DAC。 注意:该函数会调用:HAL_DAC_MspInit回调函数来完成对DAC底层以及其输入通道IO的初始化,包括:ADC及GPIO时钟使能、GPIO模式设置等。这里我们没有重定义该回调函数,而是直接在dac_init函数中完成这些设置。
3)配置DAC通道并启动DA转换器 在HAL库中,通过HAL_DAC_ConfigChannel函数来设置配置DAC的通道,根据需求设置触发类型以及输出缓冲。 配置好DAC通道之后,通过HAL_DAC_Start函数启动DA转换器。
4)设置DAC的输出值 通过HAL_DAC_SetValue函数设置DAC的输出值。这里我们根据三角波的特性,创建了dac_triangular_wave函数用于控制输出三角波。
34.3.3.1 程序流程图
图34.3.3.1.1DAC输出三角波实验程序流程图 34.3.3.2 程序解析这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。DAC驱动源码包括两个文件:dac.c和dac.h。
dac.h文件只有一些声明,下面直接开始介绍dac.c的程序,本实验的DAC初始化我们还是用到dac_init函数,就添加了一个设置DAC_OUT1输出三角波函数,其定义如下: - /**
- *@brief 设置DAC_OUT1输出三角波
- * @note 输出频率 ≈ 1000 / (dt * samples) Khz, 不过在dt较小的时候,比如
- 小于5us时, 由于delay_us本身就不准了(调用函数,计算等都需要时间,延时
- 很小的时候,这些时间会影响到延时), 频率会偏小.
- *
- *@param maxval : 最大值(0 <maxval < 4096), (maxval + 1)必须大于等于samples/2
- *@param dt : 每个采样点的延时时间(单位: us)
- *@param samples: 采样点的个数, samples必须小于等于(maxval + 1) * 2 ,
- 且maxval不能等于0
- *@param n : 输出波形个数,0~65535
- *
- *@retval 无
- */
- voiddac_triangular_wave(uint16_t maxval, uint16_t dt, uint16_t samples,
- uint16_t n)
- {
- uint16_t i, j;
- float incval; /* 递增量 */
- float Curval; /* 当前值 */
-
- if((maxval + 1) <= samples)return ; /* 数据不合法 */
- incval = (maxval + 1) / (samples / 2); /* 计算递增量 */
- for(j = 0; j < n; j++)
- {
- /* 先输出0 */
- HAL_DAC_SetValue(&g_dac_handle, DAC_CHANNEL_1, DAC_ALIGN_12B_R, Curval);
- for(i = 0; i < (samples / 2); i++) /* 输出上升沿 */
- {
- Curval += incval; /* 新的输出值 */
- /* 用寄存器操作波形会更稳定 */
- HAL_DAC_SetValue(&g_dac_handle,DAC_CHANNEL_1,DAC_ALIGN_12B_R,Curval);
- delay_us(dt);
- }
- for(i = 0; i < (samples / 2); i++) /* 输出下降沿 */
- {
- Curval -= incval; /* 新的输出值 */
- /* 用寄存器操作波形会更稳定 */
- HAL_DAC_SetValue(&g_dac_handle,DAC_CHANNEL_1,DAC_ALIGN_12B_R,Curval);
- delay_us(dt);
- }
- }
- }
复制代码该函数用于设置DAC通道1输出三角波,输出频率≈ 1000 / (dt * samples) Khz,形参意义在源码已经有详细注释。该函数中,我们使用HAL_DAC_SetValue函数来设置DAC的输出值,这样得到的三角波在示波器上可以看到,会有跳动现象(不平稳),是正常的;如果直接像寄存器例程一样使用寄存器DHR12R1来操作,得到的波形就比较稳定。由于使用HAL库的函数,CPU花费的时间会更长(因为指令变多了),对于对时间精度要求比较高的应用,就不适合用HAL库函数来操作了,这一点希望大家明白。所示学STM32不是只是会HAL库就可以了,对寄存器也是需要有一定的理解,最好是熟悉。这里用HAL库操作只是为了演示怎么使用HAL库的相关函数。
最后在main.c里面编写如下代码: - int main(void)
- {
- uint8_t t = 0;
- uint8_t key;
- HAL_Init(); /* 初始化HAL库 */
- sys_stm32_clock_init(336, 8, 2, 7); /* 设置时钟, 168Mhz */
- delay_init(168); /* 延时初始化 */
- usart_init(115200); /* 串口初始化为115200 */
- usmart_dev.init(84); /* 初始化USMART */
- led_init(); /* 初始化LED */
- lcd_init(); /* 初始化LCD */
- key_init(); /* 初始化按键 */
- dac_init(1); /* 初始化DAC1_OUT1通道 */
- lcd_show_string(30, 50, 200, 16, 16, "STM32", RED);
- lcd_show_string(30, 70, 200, 16, 16, "DACTriangular WAVE TEST", RED);
- lcd_show_string(30, 90, 200, 16, 16, "ATOM@ALIENTEK", RED);
- lcd_show_string(30, 110, 200, 16, 16, "KEY0:Wave1 KEY1:Wave2", RED);
- lcd_show_string(30, 130, 200, 16, 16, "DACNone", BLUE); /* 提示无输出 */
- while (1)
- {
- t++;
- key = key_scan(0); /* 按键扫描 */
- if (key == KEY0_PRES) /* 高采样率, 约100Hz波形 */
- {
- lcd_show_string(30, 130, 200, 16, 16, "DAC Wave1", BLUE);
- /* 幅值4095, 采样点间隔5us, 200个采样点, 100个波形 */
- dac_triangular_wave(4095, 5, 2000, 100);
- lcd_show_string(30, 130, 200, 16, 16, "DACNone ", BLUE);
- }
- else if (key == KEY1_PRES) /* 低采样率, 约100Hz波形 */
- {
- lcd_show_string(30, 130, 200, 16, 16, "DAC Wave2", BLUE);
- /* 幅值4095, 采样点间隔500us, 20个采样点, 100个波形 */
- dac_triangular_wave(4095, 500, 20, 100);
- lcd_show_string(30, 130, 200, 16, 16, "DACNone ", BLUE);
- }
- if (t == 10 ) /* 定时时间到了 */
- {
- LED0_TOGGLE(); /* LED0闪烁 */
- t = 0;
- }
- delay_ms(10);
- }
- }
复制代码该部分代码功能是,按下KEY0后,DAC输出三角波1,按下KEY1后,DAC输出三角波2,将dac_triangular_wave的形参代入公式:输出频率 ≈ 1000 / (dt * samples) Khz,得到三角波1和三角波2的频率都是100Hz。
34.3.4 下载验证下载代码后,可以看到LED0不停的闪烁,提示程序已经在运行了。LCD显示如图34.3.4.1所示: 没有按下任何按键之前,LCD屏显示DAC None,当按下KEY0后,DAC输出三角波1,LCD屏显示DAC Wave1 ,三角波1输出完成后LCD屏继续显示DAC None,当按下KEY1后,DAC输出三角波2,LCD屏显示DAC Wave2,三角波2输出完成后LCD屏继续显示DAC None。
其中三角波1和三角波2在示波器的显示情况如下图所示: 由上面两副测试图可以知道,三角波1的频率是96.3Hz,三角波2的频率是99.9Hz,基本都接近我们算出来的结果100Hz。
34.4 DAC输出正弦波实验本实验我们来学习使用如何让DAC输出正弦波。实验将用定时器7来触发DAC进行转换输出正弦波,以DMA传输数据的方式。
34.4.1 DAC寄存器本实验用到的寄存器在前面的实验都有介绍。
34.4.2 硬件设计
1. 例程功能使用DAC输出正弦波,通过KEY0/KEY1两个按键,控制DAC1的通道1输出两种正弦波,需要通过示波器接PA4进行观察。TFTLCD显示DAC转换值、电压值和ADC的电压值。LED0闪烁,提示程序运行。
2. 硬件资源1)LED灯 LED0 – PF9 2)串口1(PA9/PA10连接在板载USB转串口芯片CH340上面) 3)正点原子2.8/3.5/4.3/7寸TFTLCD模块(仅限MCU屏,16位8080并口驱动) 4)独立按键 : KEY0 – PE4 KEY1 – PE3 5)ADC1 :通道5 - PA5 6)DAC1 :通道1 - PA4 7)DMA(DMA1 数据流5 通道7) 8)定时器7
3. 原理图我们只需要把示波器的探头接到DAC1通道1(PA4)引脚,就可以在示波器上显示DAC输出的波形。PA4在P11多功能端口的DAC标志排针已经引出,硬件连接如图34.4.2.1所示:
34.4.3 程序设计
34.4.3.1 DAC的HAL库驱动本实验用到的HAL库API函数前面大都介绍过,下面将介绍本实验用到且没有介绍过的。
1.HAL_DAC_Start_DMA函数 启动DAC使用DMA方式传输函数,其声明如下: - HAL_StatusTypeDefHAL_DAC_Start_DMA(DAC_HandleTypeDef *hdac, uint32_t Channel,uint32_t *pData, uint32_t Length, uint32_t Alignment);
复制代码l 函数描述: 用于启动DAC使用DMA的方式。
l 函数形参: 形参1是DAC_HandleTypeDef结构体类型指针变量。 形参2用于选择要启动的通道,可选择DAC_CHANNEL_1或者DAC_CHANNEL_2。 形参3是使用DAC输出数据缓冲区的指针。 形参4是DAC输出数据的长度。 形参5是指定DAC通道的数据对齐方式,有:DAC_ALIGN_8B_R(8位右对齐)、DAC_ALIGN_12B_L(12位左对齐)和DAC_ALIGN_12B_R(12位右对齐)三种方式。
l 函数返回值: HAL_StatusTypeDef枚举类型的值。
2.HAL_DAC_Stop_DMA函数 停止DAC的DMA方式函数,其声明如下: - HAL_StatusTypeDefHAL_DAC_Stop_DMA(DAC_HandleTypeDef *hdac, uint32_t Channel);
复制代码l 函数描述: 用于停止DAC的DMA方式。
l 函数形参: 形参1是DAC_HandleTypeDef结构体类型指针变量。 形参2用于选择要启动的通道,可选择DAC_CHANNEL_1或者DAC_CHANNEL_2。
l 函数返回值: HAL_StatusTypeDef枚举类型的值。
3.HAL_TIMEx_MasterConfigSynchronization函数 配置主模式下的定时器触发输出选择函数,其声明如下: - HAL_StatusTypeDefHAL_TIMEx_MasterConfigSynchronization(TIM_HandleTypeDef *htim, TIM_MasterConfigTypeDef*sMasterConfig);
复制代码l 函数描述: 用于配置主模式下的定时器触发输出选择。
l 函数形参: 形参1是TIM_HandleTypeDef结构体类型指针变量。 形参2是TIM_MasterConfigTypeDef结构体类型指针变量,用于配置定时器工作在主/从模式,以及触发输出(TRGO和TRGO2)的选择。
l 函数返回值: HAL_StatusTypeDef枚举类型的值。
DAC输出正弦波配置步骤1)开启DACx和通道输出的GPIO时钟,配置该IO口的复用功能输出 首先开启DACx的时钟,然后配置GPIO为复用功能输出。本实验我们默认用到DAC1通道1,对应IO是PA4,它们的时钟开启方法如下: - __HAL_RCC_DAC_CLK_ENABLE (); /* 使能DAC1时钟 */
- __HAL_RCC_GPIOA_CLK_ENABLE(); /* 开启GPIOA时钟 */
复制代码IO口复用功能是通过函数HAL_GPIO_Init来配置的。
2)初始化DACx 通过HAL_DAC_Init函数来设置需要初始化的DAC。该函数并没有设置任何DAC相关寄存器,也就是说没有对DAC进行任何配置,它只是HAL库提供用来在软件上初始化DAC。 注意:该函数会调用:HAL_DAC_MspInit回调函数来完成对DAC底层以及其输入通道IO的初始化,包括:ADC及GPIO时钟使能、GPIO模式设置等。这里我们没有重定义该回调函数,而是直接在dac_dma_wave_init函数中完成这些设置。
3)配置DAC通道 在HAL库中,通过HAL_DAC_ConfigChannel函数来设置配置DAC的通道,根据需求设置触发类型以及输出缓冲等。
4)配置DMA并关联DAC 通过HAL_DMA_Init函数初始化DMA,包括配置通道,外设地址,存储器地址,传输数据量等。 HAL库为了处理各类外设的DMA请求,在调用相关函数之前,需要调用一个宏定义标识符,来连接DMA和外设句柄。这个宏定义为__HAL_LINKDMA。
5)配置定时器控制触发DAC 通过HAL_TIM_Base_Init函数设置定时器溢出频率。 通过HAL_TIMEx_MasterConfigSynchronization函数配置定时器溢出事件用做触发器。 通过HAL_TIM_Base_Start函数启动计数。
6)使能DMA对应数据流中断,配置DMA中断优先级,使能ADC,使能并启动DMA 通过HAL_DAC_Stop_DMA函数先停止之前的DMA传输以及DAC输出。 再通过HAL_DAC_Start_DMA函数启动DMA传输以及DAC输出。
34.4.3.2 程序流程图
图34.4.3.2.1DAC输出正弦波实验程序流程图 34.4.3.3 程序解析这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。DAC驱动源码包括两个文件:dac.c和dac.h。
dac.h文件只有一些声明,下面直接开始介绍dac.c的程序,本实验的DAC以及DMA的初始化,我们用到dac_dma_wave_init函数,其定义如下: - /**
- *@brief DAC DMA输出波形初始化函数
- * @note 本函数支持DAC1_OUT1/2通道初始化
- * DAC的输入时钟来自APB1, 时钟频率=42MHz=23.8ns
- * DAC在输出buffer关闭的时候, 输出建立时间最小值:
- * tSETTLING = 3us(F407数据手册有写)
- * 因此DAC输出的最高速度约为:333KHz, 以10个点为一个周期,
- * 最大能输出33Khz左右的波形
- *
- *@param outx : 要初始化的通道. 1,通道1; 2,通道2
- *@retval 无
- */
- voiddac_dma_wave_enable(uint8_t outx)
- {
- DAC_ChannelConfTypeDef dac_ch_conf={0};
- GPIO_InitTypeDef gpio_init_struct;
- __HAL_RCC_GPIOA_CLK_ENABLE(); /* DAC通道引脚端口时钟使能 */
- __HAL_RCC_DAC_CLK_ENABLE(); /* DAC外设时钟使能 */
- __HAL_RCC_DMA1_CLK_ENABLE(); /* DMA时钟使能 */
- gpio_init_struct.Pin = (outx == 1) ? GPIO_PIN_4 : GPIO_PIN_5; /* PA4/5 */
- gpio_init_struct.Mode = GPIO_MODE_ANALOG; /* 模拟 */
- gpio_init_struct.Pull = GPIO_NOPULL; /* 不带上下拉 */
- HAL_GPIO_Init(GPIOA, &gpio_init_struct); /* 初始化DAC引脚 */
- g_dac_dma_handle.Instance = DAC1;
- HAL_DAC_Init(&g_dac_dma_handle); /* DAC初始化 */
- /* 使用的DAM1Stream5/6 */
- g_dma_dac_handle.Instance = (outx == 1) ? DMA1_Stream5 : DMA1_Stream6;
- g_dma_dac_handle.Init.Channel = DMA_CHANNEL_7;
- g_dma_dac_handle.Init.Direction = DMA_MEMORY_TO_PERIPH; /* 存储器到外设模式 */
- g_dma_dac_handle.Init.PeriphInc = DMA_PINC_DISABLE; /* 外设地址禁止自增 */
- g_dma_dac_handle.Init.MemInc = DMA_MINC_ENABLE; /* 存储器地址自增 */
- /* 外设数据长度:16位 */
- g_dma_dac_handle.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;
- /* 存储器数据长度:16位 */
- g_dma_dac_handle.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;
- g_dma_dac_handle.Init.Mode = DMA_CIRCULAR; /* 循环模式 */
- g_dma_dac_handle.Init.Priority = DMA_PRIORITY_MEDIUM; /* 中等优先级 */
- g_dma_dac_handle.Init.FIFOMode = DMA_FIFOMODE_DISABLE; /* 不使用FIFO */
- HAL_DMA_Init(&g_dma_dac_handle); /* 初始化DMA */
- /* DMA句柄与DAC句柄关联 */
- __HAL_LINKDMA(&g_dac_dma_handle, DMA_Handle1, g_dma_dac_handle);
- dac_ch_conf.DAC_Trigger = DAC_TRIGGER_T7_TRGO; /* 采用定时器7触发 */
- dac_ch_conf.DAC_OutputBuffer = DAC_OUTPUTBUFFER_ENABLE; /* 使能输出缓冲 */
- /* DAC通道输出配置 */
- HAL_DAC_ConfigChannel(&g_dac_dma_handle, &dac_ch_conf, DAC_CHANNEL_1);
- }
复制代码该函数用于初始化DAC用DMA的方式输出正弦波。本函数用到的API函数起前面都介绍过,请结合前面介绍过的相关内容来理解源码。这里值得注意的是我们是采用定时器7触发DAC进行转换输出的。
下面介绍DAC DMA使能波形输出函数,其定义如下:
- /**
- *@brief DAC DMA使能波形输出
- * @note TIM7的输入时钟频率(f)来自APB1, f = 42* 2 = 84Mhz.
- * DAC触发频率 ftrgo = f / ((psc + 1) * (arr + 1))
- * 波形频率 = ftrgo / ndtr;
- *@param outx : DAC通道1/2
- *@param ndtr : DMA通道单次传输数据量
- *@param arr : TIM7的自动重装载值
- *@param psc : TIM7的分频系数
- * @retval 无
- */
- voiddac_dma_wave_enable(uint8_t outx, uint16_t ndtr, uint16_t arr, uint16_t psc)
- {
- TIM_HandleTypeDef tim7_handle = {0};
- TIM_MasterConfigTypeDef master_config = {0};
- __HAL_RCC_TIM7_CLK_ENABLE(); /* TIM7时钟使能 */
- tim7_handle.Instance = TIM7; /* 选择定时器7 */
- tim7_handle.Init.Prescaler = psc; /* 分频系数 */
- tim7_handle.Init.CounterMode = TIM_COUNTERMODE_UP; /* 向上计数 */
- tim7_handle.Init.Period = arr; /* 重装载值 */
- tim7_handle.Init.AutoReloadPreload=TIM_AUTORELOAD_PRELOAD_ENABLE;/*自动重装*/
- HAL_TIM_Base_Init(&tim7_handle); /* 初始化定时器7 */
- master_config.MasterOutputTrigger = TIM_TRGO_UPDATE;
- master_config.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
- /*配置TIM7 TRGO */
- HAL_TIMEx_MasterConfigSynchronization(&tim7_handle, &master_config);
- HAL_TIM_Base_Start(&tim7_handle); /* 使能定时器7 */
- /*先停止之前的传输 */
- HAL_DAC_Stop_DMA(&g_dac_dma_handle,(outx==1)?DAC_CHANNEL_1:DAC_CHANNEL_2);
- HAL_DAC_Start_DMA(&g_dac_dma_handle, (outx == 1) ? DAC_CHANNEL_1 :
- DAC_CHANNEL_2, (uint32_t *)g_dac_sin_buf, ndtr, DAC_ALIGN_12B_R);
- }
复制代码该函数用于使能波形输出,利用定时器7的更新事件来触发DAC转换输出。使能定时器7的时钟后,调用HAL_TIMEx_MasterConfigSynchronization函数配置TIM7选择更新事件作为触发输出 (TRGO),然后调用HAL_DAC_Stop_DMA函数停止DAC转换以及DMA传输,最后再调用HAL_DAC_Start_DMA函数重新配置并启动DAC和DMA。
最后在main.c里面编写如下代码: - uint16_t g_dac_sin_buf[4096]; /* 发送数据缓冲区 */
- /**
- *@brief 产生正弦波序列
- * @note 需保证: maxval> samples/2
- *
- *@param maxval : 最大值(0 <maxval < 2048)
- *@param samples: 采样点的个数
- *
- *@retval 无
- */
- voiddac_creat_sin_buf(uint16_t maxval, uint16_t samples)
- {
- uint8_t i;
- float inc = (2 * 3.1415962) / samples; /* 计算增量(一个周期DAC_SIN_BUF个点)*/
- float outdata = 0;
- for (i = 0; i < samples; i++)
- {
- /* 计算以dots个点为周期的每个点的值,放大maxval倍,并偏移到正数区域 */
- outdata = maxval * (1 + sin(inc * i));
- if (outdata > 4095)
- outdata = 4095; /* 上限限定 */
- //printf("%f\r\n",outdata);
- g_dac_sin_buf = outdata;
- }
- }
- /**
- *@brief 通过USMART设置正弦波输出参数,方便修改输出频率.
- *@param arr : TIM7的自动重装载值
- *@param psc : TIM7的分频系数
- *@retval 无
- */
- voiddac_dma_sin_set(uint16_t arr, uint16_t psc)
- {
- dac_dma_wave_enable(100, arr, psc);
- }
- int main(void)
- {
- uint16_t adcx;
- float temp;
- uint8_t t = 0;
- uint8_t key;
- HAL_Init(); /* 初始化HAL库 */
- sys_stm32_clock_init(336, 8, 2, 7); /* 设置时钟, 168Mhz */
- delay_init(168); /* 延时初始化 */
- usart_init(115200); /* 串口初始化为115200 */
- usmart_dev.init(84); /* 初始化USMART */
- led_init(); /* 初始化LED */
- lcd_init(); /* 初始化LCD */
- key_init(); /* 初始化按键 */
- adc_init(); /* 初始化ADC */
- dac_dma_wave_init(); /* 初始化DAC通道1 DMA波形输出 */
- lcd_show_string(30, 50, 200, 16, 16, "STM32", RED);
- lcd_show_string(30, 70, 200, 16, 16, "DAC DMASine WAVE TEST", RED);
- lcd_show_string(30, 90, 200, 16, 16, "ATOM@ALIENTEK", RED);
- lcd_show_string(30, 110, 200, 16, 16, "KEY0:3Khz KEY1:30Khz", RED);
- lcd_show_string(30, 130, 200, 16, 16, "DACVAL:", BLUE);
- lcd_show_string(30, 150, 200, 16, 16, "DACVOL:0.000V", BLUE);
- lcd_show_string(30, 170, 200, 16, 16, "ADC VOL:0.000V", BLUE);
- dac_creat_sin_buf(2048, 100);
- dac_dma_wave_enable(1, 100, 10 - 1, 84 - 1);
- while (1)
- {
- t++;
- key = key_scan(0); /* 按键扫描 */
- if (key == KEY0_PRES) /* 高采样率 */
- {
- dac_creat_sin_buf(2048, 100); /* 产生正弦波函序列 */
- /* 300Khz触发频率, 100个点, 得到最高3KHz的正弦波. */
- dac_dma_wave_enable(1, 100, 10 - 1, 28 - 1);
- }
- else if (key == KEY1_PRES) /* 低采样率 */
- {
- dac_creat_sin_buf(2048, 10); /* 产生正弦波函序列 */
- /* 300Khz触发频率, 10个点, 可以得到最高30KHz的正弦波. */
- dac_dma_wave_enable(1, 10, 10 - 1, 28 - 1);
- }
- adcx = DAC1->DHR12R1; /* 获取DAC1_OUT1的输出状态 */
- lcd_show_xnum(94, 130, adcx, 4, 16, 0, BLUE);/* 显示DAC寄存器值 */
- temp = (float)adcx * (3.3 / 4096); /* 得到DAC电压值 */
- adcx = temp;
- lcd_show_xnum(94, 150, temp, 1, 16, 0, BLUE);/* 显示电压值整数部分 */
- temp -= adcx;
- temp *= 1000;
- lcd_show_xnum(110, 150, temp, 3, 16, 0X80, BLUE); /* 显示电压值的小数部分 */
- adcx =adc_get_result_average(ADC_ADCX_CHY,20);/*得到ADC通道19的转换结果*/
- temp = (float)adcx * (3.3 / 65536); /* 得到ADC电压值(adc是16bit的) */
- adcx = temp;
- lcd_show_xnum(94, 170, temp, 1, 16, 0, BLUE); /* 显示电压值整数部分 */
- temp -= adcx;
- temp *= 1000;
- lcd_show_xnum(110, 170, temp, 3, 16, 0X80, BLUE); /* 显示电压值的小数部分 */
- if (t == 10) /* 定时时间到了 */
- {
- LED0_TOGGLE(); /* LED0闪烁 */
- t = 0;
- }
- delay_ms(5);
- }
- }
复制代码其中dac_creat_sin_buf函数用于产生正弦波序列,并保存在g_dac_sin_buf数组中,供给DAC转换。dac_dma_sin_set函数可以通过USMART设置正弦波输出参数,方便修改输出频率。
main函数经过一系列初始化后,默认输出100KHz触发频率,100个点,得到1KHz的正弦波。我们重点看看这些参数是怎么计算出来的。
100KHz触发频率,其实就是定时器7的事件更新频率,不记得怎么计算定时器的中断频率,请回顾基本定时器的相关内容,这里直接把公式列出: Tout= ((arr+1)*(psc+1))/Tclk 看到dac_dma_wave_enable(100, 100 - 1,24 - 1),这个语句,第二个形参是自动重装载值,第三个形参是分频系数,那么代入公式,可得: Tout= ((arr+1)*(psc+1))/Tclk=((9+1)*(83+1))/ 84MHz= 0.00001s 得到定时器的事件更新周期是0.00001秒,即事件更新频率为100KHz,也就得到DAC输出触发频率为100KHz。
再结合总一个正弦波共有100个采样点,就可以得到正弦波的频率为100KHz/100 = 1KHz。
我们知道了正弦波的这些参数的计算,后面的代码就很好理解了,下面直接下载验证。
34.4.4 下载验证下载代码后,可以看到LED0不停的闪烁,提示程序已经在运行了。LCD显示如图34.4.4.1所示: 上图是将跳线帽连接多功能端口P11的ADC和DAC两个排针,可以看到ADC VOL的值随着DAC的输出变化而变化,即ADC采集到的值是不停变化的。变化太快了,这样看不出采集到值形成什么波形,下面我们借用示波器来进行观察,首先将探头接到DAC的排针上。
没有按下任何按键之前,默认是输出100KHz触发频率,100个点,得到1KHz的正弦波,如下图所示: 当按下KEY0后,DAC输出300KHz触发频率,100个点,得到3KHz的正弦波,如下图所示: 图34.3.4.3 按下KEY0,DAC输出的的正弦波 当按下KEY1后,DAC输出300KHz触发频率,10个点,得到30KHz的正弦波,如下图所示: 图34.3.4.4 按下KEY1,DAC输出的的正弦波 |