DAC8830模块

本文使用的是科一电子的DAC8830模块。其他的DAC8830模块也是类似的方法。
科一电子DAC8830模块

DAC8830的SPI通讯

DAC8830 SPI时序图
DAC8830的SPI时序图如图所示,可以看到这个模块是高位先行,16位收发,CS拉低时进行通讯。
此处我使用硬件SPI3,硬件SPI3对应的各个功能引脚查数据手册即可。
STM32F407 SPI3引脚
由于该模块有两个通道,因此需要两个CS引脚,选择用软件控制CS引脚,引脚如下

单片机引脚 SPI引脚
PC10 CLK
PC12 MOSI
PC11 MISO
PD2 CS1
PD6 CS2

首先对SPI3进行配置,先设置各个SPI3对应的GPIO引脚为复用模式,然后设置单片机为主机,设置为2分频(对应SPI时钟,可以自行调整),选择用软件管理CS引脚,并且设置为16位收发。

/**
 * @brief         SPI3初始化
 * @author        Fantastic
 * @date          2025-04-11
 * @version       0.0.1
*/
void SPI3_Init(void)
{
    /*使能GPIO*/
    RCC->AHB1ENR |= 1 << 2; // 开启GPIOC时钟

    GPIOC->MODER |= 0x02 << 20; // 设置PC10为复用模式
    GPIOC->MODER |= 0x02 << 22; // 设置PC11为复用模式
    GPIOC->MODER |= 0x02 << 24; // 设置PC12为复用模式

    GPIOC->OSPEEDR |= 0x2 << 20; // 设置PC10为50MHz
    GPIOC->OSPEEDR |= 0x2 << 22; // 设置PC11为50MHz
    GPIOC->OSPEEDR |= 0x2 << 24; // 设置PC12为50MHz

    /*复用SPI3*/
    GPIOC->AFR[1] |= 0x06 << 8;  // PC10 AFIO复用功能
    GPIOC->AFR[1] |= 0x06 << 12; // PC12 AFIO复用功能
    GPIOC->AFR[1] |= 0x06 << 16; // PC12 AFIO复用功能

    /*配置SPI3*/
    SPI3->CR1 |= 0x00 << 6;           // 关闭SPI3
    RCC->APB1ENR |= 1 << 15;          // 开启SPI3时钟
    SPI3->CR1 |= 0x01 << 2;           // 设置为主机模式
    SPI3->CR1 |= 0x00 << 3;           // 设置分频为2分频
    SPI3->CR1 |= (1 << 9) | (1 << 8); // 使用软件管理CS
    SPI3->CR1 |= 0x01 << 11;          // 设置SPI3为16位收发

    SPI3->CR1 |= 0x01 << 6; // 使能SPI3
}

完成了通讯的配置我们需要完成SPI的数据发送和接受。实际上DAC8830是不会给主机返回数据的,因此我们完全不需要等待数据,直接返回一个0就行了。当然直接使用void也可以。

uint16_t SPI3_SwapData(uint16_t Byte)
{
    uint32_t i = 0;

    SPI3->DR = Byte; // 将要发送的输入放入发送寄存器
    // 只发送不返回
    while (!(SPI3->SR & (0x01 << 1)))
        ; // 等待发送缓存区为空
    while (SPI3->SR & (0x01 << 7))
        ;
    return 0;
}

接下来配置CS引脚,配置完后设置CS初始状态设置为拉高。

/**
 * @brief         DAC8830初始化
 * @author        Fantastic
 * @date          2025-04-01
 * @version       0.0.1
 */
void DAC8830_Init(void)
{
    /*初始化SPI配置*/
    SPI3_Init();

    /*初始化片选CS1 CS2引脚*/
    RCC->AHB1ENR |= 1 << 3; // 开启GPIOD时钟

    GPIOD->MODER |= 0x01 << 4;    // 设置为PD2推挽输出
    GPIOD->OSPEEDR |= 0x02 << 4;  // 设置PD2速度为50MHz
    GPIOD->PUPDR |= 0x01 << 4;    // 设置PD2上拉
    GPIOD->MODER |= 0x01 << 12;   // 设置为PD6推挽输出
    GPIOD->OSPEEDR |= 0x02 << 12; // 设置PD6速度为50MHz
    GPIOD->PUPDR |= 0x01 << 12;   // 设置PD6上拉

    GPIOD->BSRR |= 1 << 2; // CS1引脚拉高
    GPIOD->BSRR |= 1 << 6; // CS2引脚拉高
}

为了后面方便调用CS引脚,我这边用枚举类型定义了不同通道

typedef enum
{
    DAC8830_Channel1,
    DAC8830_Channel2,
    DAC8830_AllChannel,
} DAC8830_Channel_e;

接下来就写好CS引脚拉高拉低的函数,通过BSRR寄存器操作即可。对于老版本固件库提供的stn32f4xx.h中定义的是BSRRLBSRRH,可以自己根据手册修改就行。

/**
 * @brief         DAC8830 CS片选拉低,开始通讯
 * @param         [in]Channel 需要开始的通道
 * @author        Fantastic
 * @date          2025-04-01
 * @version       0.0.1
 */
void DAC8830_SPI_Start(DAC8830_Channel_e Channel)
{
    if (Channel == DAC8830_Channel1)
    {
        GPIOD->BSRR |= 1 << 18; // CS1引脚拉低,开始SPI
        return;
    }
    if (Channel == DAC8830_Channel2)
    {
        GPIOD->BSRR |= 1 << 22; // CS2引脚拉低,开始SPI
        return;
    }
    if (Channel == DAC8830_AllChannel)
    {
        GPIOD->BSRR |= 1 << 18; // CS1引脚拉低,开始SPI
        GPIOD->BSRR |= 1 << 22; // CS2引脚拉低,开始SPI
        return;
    }
}

/**
 * @brief         DAC8830 CS片选拉高,停止通讯
 * @param         [in]Channel 需要停止的通道
 * @author        Fantastic
 * @date          2025-04-01
 * @version       0.0.1
 */
void DAC8830_SPI_Stop(DAC8830_Channel_e Channel)
{
    if (Channel == DAC8830_Channel1)
    {
        GPIOD->BSRR |= 1 << 2; // CS1引脚拉高,结束SPI
        return;
    }
    if (Channel == DAC8830_Channel2)
    {
        GPIOD->BSRR |= 1 << 6; // CS2引脚拉高,结束SPI
        return;
    }
    if (Channel == DAC8830_AllChannel)
    {
        GPIOD->BSRR |= 1 << 2; // CS1引脚拉高,结束SPI
        GPIOD->BSRR |= 1 << 6; // CS2引脚拉高,结束SPI
        return;
    }
}

DAC8830并不会发送数据,所以我们写F4发送数据就可以了

/**
 * @brief         DAC8830 写数据
 * @param         [in]Data 需要发送的数据
 * @param         [in]Channel 需要发送的通道
 * @author        Fantastic
 * @date          2025-04-01
 * @version       0.0.1
 */
void DAC8830_WriteData(uint16_t Data, DAC8830_Channel_e Channel)
{
    DAC8830_SPI_Start(Channel);
    SPI3_SwapData((uint16_t)Data);
    Delay_us(1);
    DAC8830_SPI_Stop(Channel);
}

电压输出

完成通讯的部分,接下来就要发送数据输出电压了。发送的数据和输出的电压模拟量之间有一个转换关系,由于我使用的科一电子的模块,提供的数据如图所示
科一电子DAC8830数字量与模拟量的对应公式
如果直接使用的是TI的芯片,在TI的手册中也有提供
TI芯片计算公式
为方便调用,可以将计算公式也封装成一个函数

/**
 * @brief         DAC8830 模拟电压转数字电压
 * @param         [in]AnalogVal 模拟电压,单位mV
 * @return        int32_t 数字电压
 * @author        Fantastic
 * @date          2025-04-01
 * @version       0.0.1
 */
int32_t DAC8830_Analog2Digtal(int32_t AnalogVal)
{
    int32_t DigtalVal = 0;
    DigtalVal = ((double)AnalogVal + 10000.f) / 20000.f * 65535.f; // 参考手册公式,模拟量转数字量
    return DigtalVal;
}

因此最终输出电压的函数为

/**
 * @brief         DAC8830输出用模拟值电压
 * @param         [in]AnalogVal 模拟电压,单位mV
 * @param         [in]Channel 需要输出的通道
 * @author        Fantastic
 * @date          2025-04-01
 * @version       0.0.1
 */
void DAC8830_OutputVoltage_Analog(int32_t AnalogVal, DAC8830_Channel_e Channel)
{
    DAC8830_WriteData(DAC8830_Analog2Digtal(AnalogVal), Channel); // 输出电压
}

上述代码组成了DAC8830模块的相关.c文件和.h文件,在main函数中调用为

int main()
{
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); // 优先级分组2
    LED_Init();                                     // 初始化LED
    SPI_LCD_Init();
    Delay_Init();
    DAC8830_Init();
    USART1_Init(9600);
    LCD_DisplayNumber(0, 0, SystemCoreClock, 8); // 显示数字
    while (1)
    {
        printf("Hello World!\r\n"); // 打印字符串
        Delay_ms(1000);             // 延时1秒
    }
}

实测效果还是不错的。

波形输出

如果要波形输出的话,那就需要不断的修改寄存器中的值。下面这个代码用于输出一个正弦波,不过实测效果不是特别好,最快只能达到400Hz的水平,后续还需要进一步优化。

#define SINEWAVE_SAMPLES_MAX 256 // 采样点数
int32_t Array_SineWave[SINEWAVE_SAMPLES_MAX];
#define SYS_CLOCK_FREQ 72000000 // 系统时钟频率 72MHz

void WaveGenerate_CalculateSine(int32_t VoltagePeak)
{
    for (int i = 0; i < SINEWAVE_SAMPLES_MAX; i++)
    {
        Array_SineWave[i] = DAC8830_Analog2Digtal((int32_t)(VoltagePeak * sin(2 * 3.1415926 * i / SINEWAVE_SAMPLES_MAX)));
    }
}

void TIM6_DAC_IRQHandler(void)
{
    static uint8_t i = 0;
    if (TIM6->SR & (1 << 0))
    {
        TIM6->SR &= ~(1 << 0);
        DAC8830_OutputVoltage(Array_SineWave[i++], DAC8830_Channel1);
    }
}

main函数中调用为

int main()
{
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); // 优先级分组2
    LED_Init();                                     // 初始化LED
    SPI_LCD_Init();
    Delay_Init();
    DAC8830_Init();
    WaveGenerate_CalculateSine(2500);
    USART1_Init(9600);
    LCD_DisplayNumber(0, 0, SystemCoreClock, 8); // 显示数字
    TIM6_Init(1, 524);
    while (1)
    {
        printf("Hello World!\r\n"); // 打印字符串
        Delay_ms(1000);             // 延时1秒
    }
}
Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐