【STM32】STM32F407驱动DAC8830模块
主要介绍了使用科一电子的DAC8830模块与STM32F4进行SPI通讯的方法。首先详细介绍了DAC8830模块的SPI通讯时序图和引脚连接方式,然后给出了在STM32F407单片机上配置SPI3通讯的具体代码。
DAC8830模块
本文使用的是科一电子的DAC8830模块。其他的DAC8830模块也是类似的方法。
DAC8830的SPI通讯

DAC8830的SPI时序图如图所示,可以看到这个模块是高位先行,16位收发,CS拉低时进行通讯。
此处我使用硬件SPI3,硬件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中定义的是BSRRL和BSRRH,可以自己根据手册修改就行。
/**
* @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);
}
电压输出
完成通讯的部分,接下来就要发送数据输出电压了。发送的数据和输出的电压模拟量之间有一个转换关系,由于我使用的科一电子的模块,提供的数据如图所示
如果直接使用的是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秒
}
}
更多推荐



所有评论(0)