文章目录
- DAC
- DAC_CTL 控制寄存器
- 定时器
- TIMERx_CTL1 控制寄存器
- 改变波形频率
- DMA
- DMA和外设配合
- DMA_CHxCNT计数寄存器
- 波形曲线
- 总结
- 源码
DAC
如下面框图所示,使能外部触发后(通过设置 DAC_CTL 寄存器的 DTENx 位), 当已经选择的触发事件发
生, DAC 保持数据(DACx_DH)会被转移到 DAC 数据输出寄存器(DACx_DO),经过一段时间之后,模拟输出变得有效。DAC引脚上的模拟输出电压DACoutput=VREF×DAC_DO/4096。
对于12位的DAC保持数据(DACx_DH),可以通过对DACx_R12DH、DACx_L12DH和DACx_R8DH中的任意一个寄存器写入数据来配置。
在外部触发使能的情况下,通过设置DAC_CTL寄存器的DDMAENx位来使能DMA请求。当有外部硬件触发的时候(不是软件触发),则产生一个DMA请求。
DAC_CTL 控制寄存器
名称 | 描述 |
---|---|
DWBW0[3:0] | DAC0 噪声波位宽 |
DDMAEN0 | DAC0 DMA 使能 |
DWM0[1:0] | DAC0 噪声波模式 |
DTSEL0[2:0] | DAC0 触发选择 |
DBOFF0 | DAC0 输出缓冲区 |
DEN0 | DAC0 使能 |
DTEN0 | DAC0 触发使能 |
配置DAC其实还是配置的上面这些东西,只不过,代码里面是调用了固件库函数来配的。
其中在DAC0外部触发使能(DTEN0=1)的情况下,DAC0外部触发源选择TIMER6 TRGO。
/*DAC通道输出配置,定时器6触发,不开启输出缓存*/
static void sin_adc_channel_config(void)
{//DAC外设复位dac_deinit();/* DAC触发禁能 */dac_trigger_disable(SIN_DAC);//DAC噪声波模式选择dac_wave_mode_config(SIN_DAC, DAC_WAVE_DISABLE);//DAC输出缓冲区禁能dac_output_buffer_disable(SIN_DAC);/* DAC触发源的选择*/dac_trigger_source_config(SIN_DAC,DAC_TRIGGER_T6_TRGO);/* DAC的DMA功能使能 */dac_dma_enable(SIN_DAC);/* DAC触发使能 */dac_trigger_enable(SIN_DAC);/* DAC使能 */dac_enable(SIN_DAC);
}
定时器
选用基本定时器TIM6触发DAC。
如下面框图所示,TIMER_CK驱动计数器预分频器,预分频值由TIMERx_PSC寄存器确定,TIMER_CK经过预分频器产生PSC_CLK。向上计数模式下,计数器从0开始向上连续计数到自动加载值(在TIMERx_CAR寄存器中),然后,重新从0开始向上计数并产生上溢事件。此时,如果TIMERx_SWEVG寄存器的UPG位置1,计数值会被清0,并产生更新事件。发生更新事件时,所有的寄存器(重复计数器,自动重载寄存器,预分频寄存器)都将被更新。
TIMERx_CTL1 控制寄存器
其中MMC[2:0]位控制TRGO信号的选择,使用timer_master_output_trigger_source_select函数,主模式控制器选择更新事件作为TRGO。
使用timer_update_event_enable函数,将TIMERx_SWEVG寄存器的UPG位置1,那么定时器产生上溢事件时会产生更新事件,然后触发TIMERx_TRGO。进而触发DAC。
//定时器主输出触发源选择timer_master_output_trigger_source_select(SIN_TIM,TIMER_TRI_OUT_SRC_UPDATE);//定时器更新事件使能timer_update_event_enable(SIN_TIM);
改变波形频率
这里面,设置不同的定时周期,会改变输出波形的频率。
按照下面这样设置,最终会得到4KHZ的正弦波。
/*TIMx触发配置*/
void sin_timx_trigger_function_config(void)
{timer_parameter_struct timer_initpara;rcu_periph_clock_enable(SIN_TIM_CLK);timer_deinit(SIN_TIM);/* TIMER6 configuration */timer_initpara.prescaler = 1-1;//预分频timer_initpara.counterdirection = TIMER_COUNTER_UP;//向上计数timer_initpara.period = 225-1;//定时周期timer_init(SIN_TIM,&timer_initpara);//定时器主输出触发源选择timer_master_output_trigger_source_select(SIN_TIM,TIMER_TRI_OUT_SRC_UPDATE);//定时器更新事件使能timer_update_event_enable(SIN_TIM);
}
DMA
DMA控制器提供了一种硬件的方式,在外设和存储器之间或者存储器和存储器之间传输数据。无需CPU的介入,使CPU可以专注在处理其他系统功能上。
DMA传输 ,从源地址读取数据后,将读取的数据存储到目的地址。
AHB从接口配置DMA,AHB主接口进行数据传输,仲裁器进行DMA请求的优先级管理。
dma_parameter_struct结构体:
名称 | 描述 |
---|---|
periph_addr | 外设基地址 |
periph_width | 外设数据传输宽度 |
memory_addr | 存储器基地址 |
memory_width | 存储器数据传输宽度 |
number | DMA通道数据传输数量 |
priority | DMA通道传输软件优先级 |
periph_inc | 外设地址生成算法模式 |
memory_inc | 存储器地址生成算法模式 |
direction | DMA通道数据传输方向 |
初始化,设定上面结构体的值,其实还是对DMA_CHxCTL寄存器的一些设置。设置传输方向为从存储器读出并写入外设。
存储器和外设都独立的支持两种地址生成算法:固定模式和增量模式。寄存器DMA_CHxCTL的PNAGA和MNAGA位用来设置存储器和外设的地址生成算法,固定模式中,地址一直固定为初始化的基地址,增量模式中,下一次传输数据的地址是当前地址加1(或2、4,取决于数据传输宽度) ,前面波形数组uint16_t Sine[POINT_NUM2]定义的是U16,所以设置的宽度也是16bit。
//DMA配置
static void sin_dma_config(void)
{dma_parameter_struct dma_init_struct;/* enable DMA CLK */rcu_periph_clock_enable(SIN_DMA_CLK);/* deinitialize DMA channel3(USART0 tx) */dma_deinit(SIN_DMA, SIN_DMA_CHANNEL);dma_init_struct.direction = DMA_MEMORY_TO_PERIPHERAL;
// dma_init_struct.memory_addr = ;dma_init_struct.memory_inc = DMA_MEMORY_INCREASE_ENABLE;dma_init_struct.memory_width = DMA_MEMORY_WIDTH_16BIT;
// dma_init_struct.number = ;
// dma_init_struct.periph_addr = ;dma_init_struct.periph_inc = DMA_PERIPH_INCREASE_DISABLE;dma_init_struct.periph_width = DMA_PERIPHERAL_WIDTH_16BIT;dma_init_struct.priority = DMA_PRIORITY_ULTRA_HIGH;dma_init(SIN_DMA, SIN_DMA_CHANNEL, &dma_init_struct);/* configure DMA mode 存储器到存储器DMA传输禁能*/dma_memory_to_memory_disable(SIN_DMA, SIN_DMA_CHANNEL);
}
DMA和外设配合
循环模式用来处理连续的外设请求,循环模式中,当每次DMA传输完成后,CNT值会被重新载入,且传输完成标志位会被置1。
DMA会一直响应外设的请求,直到通道使能位(DMA_CHxCTL寄存器的CHEN位)被清0。
当DMA控制器在同一时间接收到多个外设请求时,仲裁器将根据外设请求的优先级来决定响应哪一个外设请求。当通道具有相同的软件优先级时,编号低的通道优先级高。
多个外设请求被映射到同一个DMA 通道。这些请求信号在经过逻辑或后进入DMA。通过配置对应外设的寄存器,每个外设
的请求均可以独立的开启或关闭。DMA1各通道请求表如下,由于外设是DAC_CH0,所以选用DMA1的Channel2通道。
为了保证数据的有效传输, DMA控制器中引入了外设和存储器的握手机制。请求信号由外设发出,表明外设已经准备好发送或接收数据,应答信号由DMA控制器响应,表明DMA控制器已经发送AHB命令去访问外设。
DMA_CHxCNT计数寄存器
CNT[15:0],该寄存器表明还有多少数据等待被传输。一旦通道使能,该寄存器为只读的,并在每个DMA传输之后值减1。如果该寄存器的值为0,无论通道开启与否,都不会有数据传输。如果该通道工作在循环模式下,一旦通道的传输任务完成,该寄存器会被自动重装载为初始设置值。程序中,用dma_transfer_number_config函数配置该寄存器的值。
#define ARRAYNUM(arr_name) (uint32_t)(sizeof(arr_name)/sizeof(*(arr_name)))
#define DAC0_R12DH_ADDRESS ((uint32_t)0x40007408)
void sin_dma_function_config(void)
{ //DMAx的通道y的存储器基地址配置dma_memory_address_config(SIN_DMA,SIN_DMA_CHANNEL,(uint32_t)Sine);//配置DMAx通道y还有多少数据要传输dma_transfer_number_config(SIN_DMA,SIN_DMA_CHANNEL,ARRAYNUM(Sine));//DMAx的通道y的外设基地址配置dma_periph_address_config(SIN_DMA,SIN_DMA_CHANNEL,DAC0_R12DH_ADDRESS);//DMA循环模式开启dma_circulation_enable(SIN_DMA, SIN_DMA_CHANNEL);//DMA的通道使能dma_channel_enable(SIN_DMA, SIN_DMA_CHANNEL);
}
外设基地址配置:
DAC0_R12DH地址偏移: 0x08
DAC 基地址: 0x4000 7400
DAC0_R12DH地址:0x4000 7408
波形曲线
其中可以修改点数,使波形变得平滑;也可以修改波形幅值;也可以修改波形形状。
#! python3
#coding=utf-8import matplotlib.pyplot as plt
import numpy as np
import math#修改本变量可以更改点数,如16、32、64等
POINT_NUM = 120pi = math.pi#一个周期 POINT_NUM 个点
n = np.linspace(0,2*pi,POINT_NUM)#计算POINT_NUM个点的正弦值
a = map(math.sin,n)r =[]
for i in a:#调整幅值至在0~1区间i+=1 #按3.3V电压调整幅值#i*= 3.3/2 #求取dac数值,12位dac LSB = 3.3V/2**12 ri = round(i*2**12/3.3) #检查参数if ri >= 4095:ri = 4095#得到dac数值序列r.append( ri )print(list(map(int,r)))#写入序列到文件
with open("py_dac_sinWav.c",'w',encoding= 'gb2312') as f:print(list(map(int,r)),file= f)#绘图
plt.plot(n,r,"-o")
plt.show()
如果得到的值为a,那么(a/4096)*3.3就是实际输出的电压值。
uint16_t Sine[POINT_NUM2] = {
1241, 1307, 1372, 1437, 1501, 1565, 1628, 1690, 1750, 1809, 1867, 1922, 1976, 2028, 2077, 2125, 2169, 2212, 2251, 2288, 2322, 2352, 2380, 2404, 2426, 2444, 2458, 2469, 2477, 2481, 2482, 2480, 2474, 2464, 2451, 2435, 2415, 2393, 2367, 2337, 2305, 2270, 2232, 2191, 2147, 2101, 2053, 2002, 1949, 1895, 1838, 1780, 1720, 1659, 1597, 1533, 1469, 1405, 1339, 1274, 1208, 1143, 1078, 1013, 949, 886, 824, 762, 703, 644, 588, 533, 480, 430, 381, 335, 292, 251, 213, 177, 145, 116, 90, 67, 47, 31, 18, 9, 3, 0, 1, 5, 13, 24, 39, 57, 78, 102, 130, 161, 195, 231, 271, 313, 358, 405, 455, 506, 560, 616, 673, 732, 793, 855, 917, 981, 1045, 1110, 1176, 1241
};
效果如下。
总结
1.使能TIM6触发DAC,其中,将TIMERx_SWEVG寄存器的UPG位置1,定时器产生上溢事件时会产生更新事件,主模式控制器选择更新事件作为TRGO。由于设置了DAC的DAC_CTL寄存器的DDMAENx位来使能DMA请求,当有外部硬件触发的时候,DAC则产生一个DMA请求。
3.使用DMA传输数据,从存储器读出数据并写入外设,也就是说,DMA从Sine数组的地址读数据,写到DAC0_R12DH寄存器里面。
2.触发事件发生, DAC保持数据(DACx_DH)会被转移到DAC数据输出寄存器(DACx_DO),经过一段时间之后,DAC引脚上的输出电压DACoutput=VREF×DAC_DO/4096。
4.总而言之,定时器从0开始向上连续计数到自动加载值,然后,重新从0开始向上计数并产生上溢事件,TIMERx_SWEVG寄存器的UPG位置1,计数值会被清0,并产生更新事件,主模式控制器选择更新事件作为TRGO,触发DAC,产生DMA请求,搬运数组的数据。由于DMA设置为循环模式,当每次DMA传输完成后,CNT值(待传输的数据个数)会被重新载入,DMA会一直响应外设的请求,如此循环往复,把波形曲线的值写到DAC0_R12DH寄存器里面,然后在DAC引脚上输出电压,DACoutput=VREF×DAC_DO/4096。
5.使用一个外设,看用户手册,可以知道外设大概都有哪些功能,要使用外设的某些功能都需要怎么配置寄存器。写程序的话,可能提供库函数,库函数就是把配置寄存器的那些代码给封装起来了,然后使用库函数把外设用起来就好了。
源码
GD32F307DAC输出波形-单片机文档类资源-CSDN文库
bsp_dac.c文件如下
main.c调用void sin_app(void)函数即可。
#include "systick.h"
#include "bsp_dac.h"
#define POINT_NUM 32
#define POINT_NUM2 120#define ARRAYNUM(arr_name) (uint32_t)(sizeof(arr_name)/sizeof(*(arr_name)))
#define DAC0_R12DH_ADDRESS ((uint32_t)0x40007408)#define DAC_OUT_VAL (0x09B3)#define SIN_TIM TIMER6
#define SIN_TIM_CLK RCU_TIMER6#define SIN_DAC DAC0
#define SIN_DAC_CLK RCU_DAC#define SIN_PIN GPIO_PIN_4
#define SIN_GPIO_PORT GPIOA
#define SIN_GPIO_CLK RCU_GPIOA#define SIN_DMA DMA1
#define SIN_DMA_CLK RCU_DMA1
#define SIN_DMA_CHANNEL DMA_CH2uint16_t Sine12bit[POINT_NUM] = {1241, 1491, 1731, 1950, 2141, 2295, 2405, 2468, 2481, 2443, 2356, 2223, 2050, 1844, 1613, 1367, 1116,
870, 639, 433, 260, 127, 40, 2, 14, 77, 188, 342, 532, 752, 991, 1241
};
uint16_t Sine[POINT_NUM2] = {
1241, 1307, 1372, 1437, 1501, 1565, 1628, 1690, 1750, 1809, 1867, 1922, 1976, 2028, 2077, 2125, 2169, 2212, 2251, 2288, 2322, 2352, 2380, 2404, 2426, 2444, 2458, 2469, 2477, 2481, 2482, 2480, 2474, 2464, 2451, 2435, 2415, 2393, 2367, 2337, 2305, 2270, 2232, 2191, 2147, 2101, 2053, 2002, 1949, 1895, 1838, 1780, 1720, 1659, 1597, 1533, 1469, 1405, 1339, 1274, 1208, 1143, 1078, 1013, 949, 886, 824, 762, 703, 644, 588, 533, 480, 430, 381, 335, 292, 251, 213, 177, 145, 116, 90, 67, 47, 31, 18, 9, 3, 0, 1, 5, 13, 24, 39, 57, 78, 102, 130, 161, 195, 231, 271, 313, 358, 405, 455, 506, 560, 616, 673, 732, 793, 855, 917, 981, 1045, 1110, 1176, 1241
};/*DAC引脚配置*/
static void sin_dac_gpio_config(void)
{/* enable the clock of peripherals */rcu_periph_clock_enable(SIN_GPIO_CLK);rcu_periph_clock_enable(SIN_DAC_CLK);rcu_periph_clock_enable(RCU_AF);/* once enabled the DAC, the corresponding GPIO pin is connected to the DAC converter automatically */gpio_init(SIN_GPIO_PORT, GPIO_MODE_AIN, GPIO_OSPEED_50MHZ, SIN_PIN);}/*DAC通道输出配置,定时器6触发,不开启输出缓存*/
static void sin_adc_channel_config(void)
{dac_deinit();/* configure the DAC0 */dac_trigger_disable(SIN_DAC);dac_wave_mode_config(SIN_DAC, DAC_WAVE_DISABLE);dac_output_buffer_disable(SIN_DAC);/* DAC触发源的选择*/dac_trigger_source_config(SIN_DAC,DAC_TRIGGER_T6_TRGO);/* DAC的DMA功能使能 */dac_dma_enable(SIN_DAC);/* DAC触发使能 */dac_trigger_enable(SIN_DAC);/* enable DAC0 */dac_enable(SIN_DAC);}
//DMA配置
static void sin_dma_config(void)
{dma_parameter_struct dma_init_struct;/* enable DMA CLK */rcu_periph_clock_enable(SIN_DMA_CLK);/* deinitialize DMA channel3(USART0 tx) */dma_deinit(SIN_DMA, SIN_DMA_CHANNEL);dma_init_struct.direction = DMA_MEMORY_TO_PERIPHERAL;
// dma_init_struct.memory_addr = ;dma_init_struct.memory_inc = DMA_MEMORY_INCREASE_ENABLE;dma_init_struct.memory_width = DMA_MEMORY_WIDTH_16BIT;
// dma_init_struct.number = ;
// dma_init_struct.periph_addr = ;dma_init_struct.periph_inc = DMA_PERIPH_INCREASE_DISABLE;dma_init_struct.periph_width = DMA_PERIPHERAL_WIDTH_16BIT;dma_init_struct.priority = DMA_PRIORITY_ULTRA_HIGH;dma_init(SIN_DMA, SIN_DMA_CHANNEL, &dma_init_struct);/* configure DMA mode */dma_memory_to_memory_disable(SIN_DMA, SIN_DMA_CHANNEL);
}void sin_config(void)
{//引脚配置sin_dac_gpio_config();//通道配置sin_adc_channel_config();//DMA配置sin_dma_config();
}void sin_dma_function_config(void)
{ //DMAx的通道y的存储器基地址配置dma_memory_address_config(SIN_DMA,SIN_DMA_CHANNEL,(uint32_t)Sine);//配置DMAx通道y还有多少数据要传输dma_transfer_number_config(SIN_DMA,SIN_DMA_CHANNEL,ARRAYNUM(Sine));//DMAx的通道y的外设基地址配置dma_periph_address_config(SIN_DMA,SIN_DMA_CHANNEL,DAC0_R12DH_ADDRESS);//DMA循环模式开启dma_circulation_enable(SIN_DMA, SIN_DMA_CHANNEL);//DMA的通道使能dma_channel_enable(SIN_DMA, SIN_DMA_CHANNEL);
}/*TIMx触发配置*/
void sin_timx_trigger_function_config(void)
{timer_parameter_struct timer_initpara;rcu_periph_clock_enable(SIN_TIM_CLK);timer_deinit(SIN_TIM);/* TIMER6 configuration */timer_initpara.prescaler = 1-1;//预分频timer_initpara.counterdirection = TIMER_COUNTER_UP;//向上计数timer_initpara.period = 225-1;//定时周期timer_init(SIN_TIM,&timer_initpara);//定时器主输出触发源选择timer_master_output_trigger_source_select(SIN_TIM,TIMER_TRI_OUT_SRC_UPDATE);//定时器更新事件使能timer_update_event_enable(SIN_TIM);
}/*操作函数*/
void sin_app(void)
{//*配置*/sin_config();//DMA功能配置sin_dma_function_config();// TIMER触发功能配置sin_timx_trigger_function_config();// TIMER启动timer_enable(SIN_TIM);}
bsp_dac.h文件
#include "gd32f30x.h"void sin_app(void);