第二十六章 DAC——输出正弦波

单芯片解决方案,开启全新体验——W55MH32 高性能以太网单片机

W55MH32是WIZnet重磅推出的高性能以太网单片机,它为用户带来前所未有的集成化体验。这颗芯片将强大的组件集于一身,具体来说,一颗W55MH32内置高性能Arm® Cortex-M3核心,其主频最高可达216MHz;配备1024KB FLASH与96KB SRAM,满足存储与数据处理需求;集成TOE引擎,包含WIZnet全硬件TCP/IP协议栈、内置MAC以及PHY,拥有独立的32KB以太网收发缓存,可供8个独立硬件socket使用。如此配置,真正实现了All-in-One解决方案,为开发者提供极大便利。

在封装规格上,W55MH32 提供了两种选择:QFN100和QFN68。

W55MH32L采用QFN100封装版本,尺寸为12x12mm,其资源丰富,专为各种复杂工控场景设计。它拥有66个GPIO、3个ADC、12通道DMA、17个定时器、2个I2C、5个串口、2个SPI接口(其中1个带I2S接口复用)、1个CAN、1个USB2.0以及1个SDIO接口。如此丰富的外设资源,能够轻松应对工业控制中多样化的连接需求,无论是与各类传感器、执行器的通信,还是对复杂工业协议的支持,都能游刃有余,成为复杂工控领域的理想选择。 同系列还有QFN68封装的W55MH32Q版本,该版本体积更小,仅为8x8mm,成本低,适合集成度高的网关模组等场景,软件使用方法一致。更多信息和资料请进入网站或者私信获取。

此外,本W55MH32支持硬件加密算法单元,WIZnet还推出TOE+SSL应用,涵盖TCP SSL、HTTP SSL以及 MQTT SSL等,为网络通信安全再添保障。

为助力开发者快速上手与深入开发,基于W55MH32L这颗芯片,WIZnet精心打造了配套开发板。开发板集成WIZ-Link芯片,借助一根USB C口数据线,就能轻松实现调试、下载以及串口打印日志等功能。开发板将所有外设全部引出,拓展功能也大幅提升,便于开发者全面评估芯片性能。

若您想获取芯片和开发板的更多详细信息,包括产品特性、技术参数以及价格等,欢迎访问官方网页,我们期待与您共同探索W55MH32的无限可能。

wKgZPGgbOfaANhwzACodXd3sVzg463.png

第二十六章 DAC——输出正弦波

本章参考资料:《W55MH32参考手册》DAC章节。

学习本章时,配合《W55MH32参考手册》DAC章节一起阅读,效果会更佳,特别是涉及到寄存器说明的部分。

1 DAC简介

DAC为数字/模拟转换模块,故名思议,它的作用就是把输入的数字编码,转换成对应的模拟电压输出,它的功能与ADC相反。 在常见的数字信号系统中,大部分传感器信号被化成电压信号,而ADC把电压模拟信号转换成易于计算机存储、处理的数字编码, 由计算机处理完成后,再由DAC输出电压模拟信号,该电压模拟信号常常用来驱动某些执行器件,使人类易于感知。如音频信号的采集及还原就是这样一个过程。

W55MH32具有片上DAC外设,它的分辨率可配置为8位或12位的数字输入信号,具有两个DAC输出通道,这两个通道互不影响, 每个通道都可以使用DMA功能,都具有出错检测能力,可外部触发。

2 DAC功能框图剖析

W55MH32的DAC模块框图如下:

整个DAC模块围绕框图下方的“数字至模拟转换器x”展开,它的左边分别是参考电源的引脚:VDDA、 VSSA及VREF+, 其中W55MH32的DAC规定了它的参考电压:math:V_{ref +}输入范围为2.4——3.3V。 “数字至模拟转换器x”的输入为DAC的数据寄存器“DORx”的数字编码,经过它转换得的模拟信号由图中右侧的“DAC_OUTx”输出。 而数据寄存器“DORx”又受“控制逻辑”支配,它可以控制数据寄存器加入一些伪噪声信号或配置产生三角波信号。图中的左上角为DAC的触发源, DAC根据触发源的信号来进行DAC转换,其作用就相当于DAC转换器的开关,它可以配置的触发源为外部中断源触发、定时器触发或软件控制触发。 如本章实验中需要控制正弦波的频率,就需要定时器定时触发DAC进行数据转换。

2.1 参考电压

与ADC外设类似,DAC也使用VREF+引脚作为参考电压, 在设计原理图的时候一般把VSSA接地,把VREF+和VDDA 接3.3V, 可得到DAC的输出电压范围为:0~3.3V。

如果想让输出的电压范围变宽,可以在外部加一个电压调理电路,把0~3.3V的DAC输出抬升到特定的范围即可。

2.2 数模转换及输出通道

框图中的“数字至模拟转换器x”是核心部件,整个DAC外设都围绕它而展开。它以左边的VREF+作为参考电源, 以DAC的数据寄存器“DORx”的数字编码作为输入,经过它转换得的模拟信号由右侧的“DAC_OUTx”通道输出。其中各个部件中的“x”是指设备的标号, 在W55MH32中具有2个这样的DAC部件,每个DAC有1个对应的输出通道连接到特定的引脚,即:PA4-通道1,PA5-通道2,为避免干扰,使用DAC功能时, DAC通道引脚需要被配置成模拟输入功能(AIN)。

2.3 触发源及DHRx寄存器

在使用DAC时,不能直接对上述DORx寄存器写入数据,任何输出到DAC通道x的数据都必须写入到DHRx寄存器中(其中包含DHR8Rx、DHR12Lx等, 根据数据对齐方向和分辨率的情况写入到对应的寄存器中)。

数据被写入到DHRx寄存器后,DAC会根据触发配置进行处理,若使用硬件触发,则DHRx中的数据会在3个APB1时钟周期后传输至DORx, DORx随之输出相应的模拟电压到输出通道;若DAC设置为外部事件触发,可以使用定时器(TIMx_TRGO)、 EXTI_9信号或软件触发(SWTRIGx)这几种方式控制数据DAC转换的时机,例如使用定时器触发,配合不同时刻的DHRx数据,可实现DAC输出正弦波的功能。

3 DAC初始化结构体详解

在W55MH32的标准库中,把控制DAC相关的各种配置封装到了结构体DAC_InitTypeDef中, 它主要包含了DAC_CR控制寄存器的各寄存器位的配置,见代码清单:DAC-1 :

代码清单:DAC-1 DAC_InitTypeDef结构体

typedef struct {
    /*DAC触发方式 */
    uint32_t DAC_Trigger;

    /*是否自动输出噪声或三角波 */
    uint32_t DAC_WaveGeneration;

    /*选择噪声生成器的低通滤波或三角波的幅值 */
    uint32_t DAC_LFSRUnmask_TriangleAmplitude;

    /*选择是否使能输出缓冲器 */
    uint32_t DAC_OutputBuffer;

} DAC_InitTypeDef;

各个结构体成员的介绍如下,解说中各模式后括号内的英文为该模式在标准库中使用宏:

DAC_Trigger

本成员用于配置DAC的触发模式,当DAC产生相应的触发事件时,才会把DHRx寄存器的值转移到DORx寄存器中进行转换。 本结构体成员可以选择的触发模式如下:硬件触发模式(DAC_Trigger_None),DHRx寄存器内的数据会在3个APB1时钟周期内自动转换至DORx进行转换; 定时器触发模式(DAC_Trigger_T2/4/5/6/7_TRGO),使用定时器2、4、5、6、7控制DHRx寄存器的数据按时间转移到DORx中进行转换, 利用这种方式可以输出特定的波形;EXTI_9触发方式(DAC_Trigger_Ext_IT9),当产生EXTI_9事件时(如GPIO中断事件), 触发转换;软件触发模式(DAC_Trigger_Software),在本模式下,向DAC_SWTRIGR寄存器写入配置即可触发信号进行转换。

DAC_WaveGeneration

本成员用于设置是否使用DAC输出伪噪声或三角波(DAC_WaveGeneration_None/Noise/Triangle),使用伪噪声和三角波输出时, DAC都会把LFSR寄存器的值叠加到DHRx数值上,产生伪噪声和三角波,若希望产生自定义的输出时,直接配置为DAC_WaveGeneration_None即可。

DAC_LFSRUnmask_TriangleAmplitude

本成员通过控制DAC_CR的MAMP2位设置LFSR寄存器位的数据,即当使用伪噪声或三角波输出时要叠加到DHRx的值,非噪声或三角波输出模式下, 本配置无效。使用伪噪声输出时LFSR=0xAAA,MAMP2寄存器位可以屏蔽LFSR的某些位, 这时把本结构体成员赋值为DAC_LFSRUnmask_Bit0~DAC_LFSRUnmask_Bit11_0等宏即可;使用三角波输出时,本结构体设置三角波的最大幅值, 可选择为DAC_TriangleAmplitude_1~ DAC_TriangleAmplitude_4096等宏,见下图,DAC输出三角波。DAC在DHRx值的基础上升,幅值达到MAMPx设置的最大幅度时下降,形成三角波的输出。

wKgZPGgxfmeAe1EBAACTlSrRBq4420.png

DAC_OutputBuffer

本结构体成员用于控制是否使能DAC的输出缓冲(DAC_OutputBuffer_Enable/Disable), 使能了DAC的输出缓冲后可以减小输出阻抗,适合直接驱动一些外部负载。

4 DAC输出正弦波实验

这段代码是基于 W55MH32 微控制器编写的,其主要功能是通过 DAC(数模转换器)输出双声道的正弦波信号,并且配置了串口通信以便输出系统时钟信息。

4.1 代码解析

1. 宏定义与全局变量

#define DAC1_DHR12RD_ADDRESS (DAC_BASE + 0x00000008 + DAC_Align_12b_R)
#define DAC2_DHR12RD_ADDRESS (DAC_BASE + 0x00000014 + DAC_Align_12b_R)
#define POINT_NUM 32

uint16_t Sine12bit[POINT_NUM] = {
    2048, 2460, 2856, 3218, 3532, 3786, 3969, 4072,
    4093, 4031, 3887, 3668, 3382, 3042, 2661, 2255,
    1841, 1435, 1054, 714, 428, 209, 65, 3,
    24, 127, 310, 564, 878, 1240, 1636, 2048};

uint32_t DualSine12bit[POINT_NUM];

DAC1_DHR12RD_ADDRESS 和 DAC2_DHR12RD_ADDRESS:分别定义了 DAC1 和 DAC2 的 12 位右对齐数据寄存器地址。

POINT_NUM:定义了正弦波一个周期内的采样点数,这里为 32 个点。

Sine12bit:存储了一个周期正弦波的 12 位采样数据。

DualSine12bit:用于存储双声道的正弦波数据。

2. 函数声明

void UART_Configuration(uint32_t bound);
void GPIO_Configuration(void);
void DAC_Configuration(void);
void TIM_Configuration(void);
void DMA_Configuration(void);

声明了用于配置串口、GPIO、DAC、定时器和 DMA 的函数。

3. main()函数

int main(void)
{
    uint32_t i;

    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_CRC, ENABLE);
    delay_init();
    UART_Configuration(115200);
    RCC_GetClocksFreq(&clocks);

    printf("n");
    printf("SYSCLK: %3.1fMhz, HCLK: %3.1fMhz, PCLK1: %3.1fMhz, PCLK2: %3.1fMhz, ADCCLK: %3.1fMhzn",
           (float)clocks.SYSCLK_Frequency / 1000000, (float)clocks.HCLK_Frequency / 1000000,
           (float)clocks.PCLK1_Frequency / 1000000, (float)clocks.PCLK2_Frequency / 1000000, (float)clocks.ADCCLK_Frequency / 1000000);

    printf("DAC Out Software Sine Wave Test.n");

    GPIO_Configuration();
    DAC_Configuration();
    TIM_Configuration();

    for (i = 0; i < POINT_NUM; i++)
    {
        DualSine12bit[i] = (Sine12bit[i] < < 16) + (Sine12bit[i]);
    }

    DMA_Configuration();

    while (1);
}

使能 CRC 外设时钟,初始化延时函数,配置串口通信波特率为 115200,并获取系统时钟频率信息。

打印系统时钟频率信息和测试提示信息。

依次调用 GPIO_Configuration、DAC_Configuration() 和 TIM_Configuration()函数进行 GPIO、DAC 和定时器的配置。

将单声道的正弦波数据转换为双声道数据存储在 DualSine12bit 数组中。

调用 DMA_Configuration()函数配置 DMA 传输。

进入无限循环,保持程序运行。

4. DMA_Configuration()函数

void DMA_Configuration(void)
{
    DMA_InitTypeDef DMA_InitStructure;

    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA2, ENABLE);

    DMA_InitStructure.DMA_PeripheralBaseAddr = DAC2_DHR12RD_ADDRESS;
    DMA_InitStructure.DMA_MemoryBaseAddr     = (uint32_t)&DualSine12bit;
    DMA_InitStructure.DMA_DIR                = DMA_DIR_PeripheralDST;
    DMA_InitStructure.DMA_BufferSize         = POINT_NUM;
    DMA_InitStructure.DMA_PeripheralInc      = DMA_PeripheralInc_Disable;
    DMA_InitStructure.DMA_MemoryInc          = DMA_MemoryInc_Enable;
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word;
    DMA_InitStructure.DMA_MemoryDataSize     = DMA_MemoryDataSize_Word;
    DMA_InitStructure.DMA_Mode               = DMA_Mode_Circular;
    DMA_InitStructure.DMA_Priority           = DMA_Priority_High;
    DMA_InitStructure.DMA_M2M                = DMA_M2M_Disable;

    DMA_Init(DMA2_Channel4, &DMA_InitStructure);
    DMA_Cmd(DMA2_Channel4, ENABLE);

    DMA_InitStructure.DMA_PeripheralBaseAddr = DAC1_DHR12RD_ADDRESS;
    DMA_Init(DMA2_Channel3, &DMA_InitStructure);
    DMA_Cmd(DMA2_Channel3, ENABLE);
}

使能 DMA2 外设时钟。

配置 DMA2 通道 4,将双声道正弦波数据从内存传输到 DAC2 的数据寄存器。

配置 DMA2 通道 3,将双声道正弦波数据从内存传输到 DAC1 的数据寄存器。

采用循环模式,使 DMA 不断循环传输数据。

5. TIM_Configuration()函数

void TIM_Configuration(void)
{
    TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;

    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);

    TIM_TimeBaseStructure.TIM_Period        = (20 - 1);
    TIM_TimeBaseStructure.TIM_Prescaler     = 0;
    TIM_TimeBaseStructure.TIM_ClockDivision = 0x0;
    TIM_TimeBaseStructure.TIM_CounterMode   = TIM_CounterMode_Up;
    TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);

    TIM_SelectOutputTrigger(TIM2, TIM_TRGOSource_Update);

    TIM_Cmd(TIM2, ENABLE);
}

使能定时器 2 外设时钟。

配置定时器 2 的周期为 20 个时钟周期,预分频器为 0,计数器向上计数。

选择定时器 2 的更新事件作为触发输出。

使能定时器 2。

6. GPIO_Configuration()函数

void GPIO_Configuration(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

    GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_4 | GPIO_Pin_5;
    GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_AIN;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
}

使能 GPIOA 外设时钟。

配置 GPIOA 的引脚 4 和 5 为模拟输入模式,速度为 50MHz。

7. DAC_Configuration()函数

void DAC_Configuration(void)
{
    DAC_InitTypeDef DAC_InitStructure;

    RCC_APB1PeriphClockCmd(RCC_APB1Periph_DAC, ENABLE);

    DAC_InitStructure.DAC_Trigger                      = DAC_Trigger_T2_TRGO;
    DAC_InitStructure.DAC_WaveGeneration               = DAC_WaveGeneration_None;
    DAC_InitStructure.DAC_LFSRUnmask_TriangleAmplitude = DAC_TriangleAmplitude_4095;
    DAC_InitStructure.DAC_OutputBuffer                 = DAC_OutputBuffer_Enable;
    DAC_Init(DAC_Channel_1, &DAC_InitStructure);
    DAC_Init(DAC_Channel_2, &DAC_InitStructure);

    DAC_Cmd(DAC_Channel_1, ENABLE);
    DAC_Cmd(DAC_Channel_2, ENABLE);

    DAC_DMACmd(DAC_Channel_1, ENABLE);
    DAC_DMACmd(DAC_Channel_2, ENABLE);
}

使能 DAC 外设时钟。

配置 DAC 通道 1 和通道 2 的触发源为定时器 2 的触发输出。

不生成波形,启用输出缓冲。

使能 DAC 通道 1 和通道 2,并使能 DMA 传输。

8. UART_Configuration()函数

void UART_Configuration(uint32_t bound)
{
    GPIO_InitTypeDef  GPIO_InitStructure;
    USART_InitTypeDef USART_InitStructure;

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

    GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_9;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_AF_PP;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_10;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    USART_InitStructure.USART_BaudRate            = bound;
    USART_InitStructure.USART_WordLength          = USART_WordLength_8b;
    USART_InitStructure.USART_StopBits            = USART_StopBits_1;
    USART_InitStructure.USART_Parity              = USART_Parity_No;
    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
    USART_InitStructure.USART_Mode                = USART_Mode_Rx | USART_Mode_Tx;

    USART_Init(USART_TEST, &USART_InitStructure);
    USART_Cmd(USART_TEST, ENABLE);
}

使能 USART1 和 GPIOA 外设时钟。

配置 GPIOA 的引脚 9 为复用推挽输出,引脚 10 为浮空输入。

配置 USART1 的波特率、数据位、停止位、奇偶校验等参数。

使能 USART1。

9. SER_PutChar() 和 fputc() 函数

int SER_PutChar(int ch)
{
    while (!USART_GetFlagStatus(USART_TEST, USART_FLAG_TC));
    USART_SendData(USART_TEST, (uint8_t)ch);

    return ch;
}

int fputc(int c, FILE *f)
{
    /* Place your implementation of fputc here */
    /* e.g. write a character to the USART */
    if (c == 'n')
    {
        SER_PutChar('r');
    }
    return (SER_PutChar(c));
}

SER_PutChar():向 USART 发送一个字符,等待发送完成标志位。

fputc():重定向标准输出函数,将字符发送到 USART,遇到换行符时先发送回车符。

综上所述,这段代码通过配置定时器、DMA 和 DAC,实现了双声道正弦波信号的输出,并通过串口输出系统时钟信息。

为您推荐

第十九章 ADC——电压采集

第十九章 ADC——电压采集

单芯片解决方案,开启全新体验——W55MH32 高性能以太网单片机 W55MH32是WIZnet重磅推出的高性能以...

第十八章 I2C通信测试

第十八章 I2C通信测试

单芯片解决方案,开启全新体验——W55MH32 高性能以太网单片机 W55MH32是WIZnet重磅推出的高性能以...

CMOS的逻辑门如何应用在电路中

CMOS的逻辑门如何应用在电路中

CMOS的逻辑门如何应用在电路中 前言 在如今的电子电路中,CMOS逻辑门有着接近零静态功耗和超高集成度的特点...

2025-06-25 标签:逻辑门cmos电路光伏热点
攀登者 | 全球首颗RISC-V内核超级SIM芯片的创新突围

攀登者 | 全球首颗RISC-V内核超级SIM芯片的创新突围

在安全芯片的“珠峰”上,有一群“攀登者”。面对内核自主可控与芯片安全的关键挑战,他们的目标不是跟随既有的技术路径,而是...

响应式数据可视化大屏解决方案,重构工业交互体验

响应式数据可视化大屏解决方案,重构工业交互体验

在工业互联网与 5G 技术深度融合的当下,数据可视化已从单纯的信息展示升级为业务决策的核心载体。传统大屏常受限于分辨率...

当前非电脑浏览器正常宽度,请使用移动设备访问本站!