最新教程下载:http://www.armbbs.cn/forum.php?mod=viewthread&tid=93255
本章节为大家讲解标准SPI接线方式驱动模数转换器DAC856X。
目录
第33章 STM32F407的SPI总线应用之驱动DAC8563(双通道,16bit分辨率,正负10V)
33.1 初学者重要提示
33.2 DAC结构分类
33.2.1 R2R型MDAC
33.2.2 R2R型backDAC
33.2.3 String型DAC
33.3 DAC技术术语
33.3.1 单位ppm℃(ppm/℃)
33.3.2 毛刺脉冲(Glitch impulse)
33.3.3 偏移误差(Offset Error)
33.3.4 增益误差(Gain Error)
33.3.5 差分非线性误差(DNL)
33.3.6 积分非线性误差(INL)
33.3.7 绝对精度误差(Absolute Accuracy Error)
33.4 DAC856X硬件设计
33.4.1 DAC856X模块规格
33.4.2 DAC856X硬件接口
33.5 DAC856X关键知识点整理(重要)
33.5.1 DAC856X基础信息
33.5.2 DAC856X每个引脚的作用
33.5.3 DAC856X输出电压计算公式
33.5.4 DAC856X时序图
33.5.5 DAC856X寄存器配置
33.6 DAC856X驱动设计
33.6.1 第1步:SPI总线配置
33.6.2 第2步:SPI总线的查询,中断和DMA方式设置
33.6.3 第3步:DAC856X的时钟极性和时钟相位配置
33.6.4 第4步:单SPI接口管理多个SPI设备的切换机制
33.6.5 第5步:DAC856X的数据更新
33.7 SPI总线板级支持包(bsp_spi_bus.c)
33.7.1 函数bsp_InitSPIBus
33.7.2 函数bsp_InitSPIParam
33.7.3 函数bsp_spiTransfer
33.8 DAC856X支持包中断方式(bsp_spi_dac8562.c)
33.8.1 函数bsp_InitDAC8562
33.8.2 函数DAC8562_SetCS
33.8.3 函数DAC8562_WriteCmd
33.8.4 函数DAC8562_SetDacData
33.8.5 函数DAC8562_DacToVoltage
33.8.6 函数DAC8562_VoltageToDac
33.9 DAC856X驱动移植和使用
33.10 实验例程设计框架
33.11 实验例程说明(MDK)
33.12 实验例程说明(IAR)
33.13 总结
这里将三种DAC结构为大家做个普及:R2R型MDAC,R2R型backDAC和Srting型DAC。
注,这些知识翻译自TI的英文技术手册。
自动测试设备或仪器通常使用R2R MDAC。MDAC型制造商能够设计具有±1 LSB的高分辨率积分非线性(INL)和差分非线性(DNL)DAC。通过使用合适的外部放大器,MDAC能够实现较短的建立时间(<0.3 ms)和大于10 MHz的带宽。并且通过为MDAC的外部运算放大器提供不同电源电压和高输出电流可以增强DAC功能。
MDAC产生的电流与用户设置的数字编码,外部放大器以及RFB(在MDAC内部)将DAC的电流输出信号转换为可用的电压。
这类DAC的缺点是会有稳定性问题。
通常在工业应用中使用R2R backDAC。其它一些应用还包括仪器和数字控制校准。使用这类DAC,每次新更新会将2R支路切换到参考电压高(VREF-H)或参考电压低(VREF-L)。注意R-2R梯子的布置与MDAC相比是倒置的。这就是名字backDAC的由来,这种架构很容易制造。
这类DAC的缺点是毛刺脉冲问题(注,此贴有详细解释:链接):
String型DAC最适合便携式仪器,闭环伺服控制和过程控制。下图显示了一个3bit String DAC的模型,数字输入代码101b被解码为5/8 VREF。String DAC的输出级放大器隔离了来自输出负载的内部电阻元件。
String DAC是一种低功耗解决方案,可确保单调性在整个输入代码中具有良好的DNL(差分非线性)性能范围。毛刺能量通常低于其它DAC类型。
但是,INL(积分非线性)通常较大,具体取决于电阻式片上匹配,另一方面,控制回路中的DAC可减轻线性度影响。String DAC的噪声也相对较大,因为电阻串的阻抗很高,所以该值很高。但String DAC功耗低且非常小的故障能量。
一些常见的DAC技术术语需要大家见到了,大概了解是什么意思。
这个参数是专门用来定义温飘的,ppm全称是parts per million,即百万分之一。比如2ppm℃就是2 x 10^-6 ,反映到DAC8563上,定义如下:
Input or 2.5-V Output 4-ppm°C Temperature Drift (Typ)
也就是说,当输出2.5V时,每变化一度,输出电压的变化是2.5V x (4 x 10^-6) = 10uV
类似的定义还有很多:
ppb,ppt,ppq所代表的含义:
使用DAC进行设计时,您期望输出从一个值单调移至下一个值,但实际电路并非总是如此。在某些代码范围内,出现过冲或下冲(量化为毛刺脉冲)并不少见。主要以下面两种形式呈现:
具体原因分析在这个帖子里面进行了讲解(内容较多,就不整理到教程里面了):链接。
偏移误差为标称偏移点与实际偏移点之间的差。此错误以相同的数量影响所有代码,通常可以通过修正来补偿处理。如果无法修正,则该误差称为零刻度误差。
增益误差定义为传输时标称增益点与实际增益点之差。
DNL全称Differential Nonlinearity。
差分非线性误差为实际步长宽度(对于ADC)或步长高度(对于DAC)与1 LSB的理想值之间的差值。 因此,如果阶跃宽度或高度恰好为1 LSB,则差分非线性误差为零。 如果DNL超过1 LSB,转换器可能变得非单调。这意味着增加了输入的幅度但输出的大小可能变小。
INL全称Integral Nonlinearity
积分非线性误差是从一个传输点到相对应的理想传输曲线的最大偏差距离,不考虑偏置误差和增益误差。 这个参数对最佳传输函数或端点传输函数有一定参考意义。
绝对精度误差是包括偏移,增益,积分线性等误差的总体误差。
DAC的输出量可以为0到2.5V或者0到5V,通过外置运放,实现了±10V输出。原理图下载:
http://www.armbbs.cn/forum.php?mod=viewthread&tid=97082 。
产品规格:
1、供电电压 : 2.7 - 5.5V 【3.3V供电时,输出电压也可以到正负10V】
2、通道数: 2路 (通过1片DAC8563实现)
3、输出电压范围 : -10V ~ +10V 【客户可以自己更改为 0-10V输出范围。使用烙铁切换2个焊点即可,无需更换元器件】
4、输出驱动能力:带运放驱动,最大输出电流10mA,负载电阻>1K欧姆
5、分辨率: 16位
6、功耗 : 小于20mA
7、MCU接口 :高速 SPI (50M) 支持 3.3V和5V单片机
8、DAC输出模拟带宽:350KHz
9、DAC输出响应: 10uS 到 0.003% FSR
产品特点:
1、输出和供电电压无关;模块内带正负12V升压电路
2、自适应单片机的电平(2.7 - 5V 均可以)
3、输出电压可抵达正负10V
4、上电时缺省输出0V (在软件未启动时)
5、引出正负12V电源排针,方便客户使用
重要提示:
1、DAC8562和DAC8563完全兼容,区别仅仅在于CLR引脚有效时,DAC8562数据设置为0, DAC8563数据设置为32767。注意这是DAC的内部数据,不表示输出电压。 对于-10 ~ +10V输出的模块,DAC8562输出-10V, DAC8563输出0V。
2、无论是用DAC8562还是DAC8563芯片,只要软件不启动,本模块输出电压缺省状态都是0V。
3、CLR脚悬浮时,电压在1.9V左右,容易受到干扰导致输出被清零。因此即使不用CLR控制功能,这个CLR脚也需要接固定电平(推荐接GND)。CLR是边沿触发,仅在下降沿信号出现执行清零。
产品效果:
V5板子上DAC856X模块的插座的原理图如下(NRF24L01,AD9833,DAC8563和TM7705都是用的而这个插座):
实际对应开发的位置如下:
驱动DAC856X需要对下面这些知识点有个认识。
DAC856X主要有下面两种封装形式:
供电范围2.7-5.5V
异步清除输入,下降沿有效,触发后,DAC8562输出最低电压值,DAC8563输出中间值。用户写入操作的的第24个时钟下降沿将退出清除模式,激活清除模式将终止写操作。
串行时钟输入,每个时钟下降沿将数据写到的24bit的输入移位寄存器。
接地端。
同步模式下,数据更新发生在第24个SCLK周期的下降沿,之后伴随着SYNC的下降沿。 这种同步更新不需要LDAC,而LDAC必须永久接地,或者将命令发送到设备时保持低电平。异步模式下,LDAC是低电平触发,用于同步DAC更新,可以编写多个单通道命令进行设置,然后在LDAC引脚上产生一个下降沿将同步更新DAC输出寄存器。
时钟输入端,支持50MHz。
低电平有效,当SYNC变为低电平时,它使能输入移位寄存器,并且数据采样在随后的时钟下降沿。 DAC输出在第24个时钟下降沿之后更新。 如果SYNC在第23个时钟沿之前变高,SYNC的上升沿将充当中断,并且DAC756x,DAC816x和DAC856x器件将忽略写序列。
模拟电压输出A。
模拟电压输出B。
双向电压参考引脚,如果使用内部电压基准,此引脚是输出2.5V。
DAC856X的计算公式如下:
配置DAC856X数据输出寄存器的数值,范围0 到2^16 – 1,即0到65535。
对于DAC856X来说,n是16。
如果使用内部参考电压,那么此数值是2.5V,如果使用外部参考电压,由VREFIN引脚的输入决定。
增益设置。禁止内部电压基准后,默认增益是1。如果使能内部电压基准后,默认增益是2。具体增益是1还是2,可以通过DAC856X的寄存器设置。
DAC856X的时序图如下:
这个时序里面有三个参数尤其重要(对于F4系列主要是第1个参数,H7系列这三个都要用的)。
支持最高的串行时钟是50MHz。
每传输24bit数据后,SYNC要保持一段时间的高电平,DAC856X要求至少要80ns。
SYNC低电平有效到SCLK第1个下降沿信号的时间,最小值13ns。
DAC856X的寄存器配置看下面的图表即可,一目了然(X表示为0或者为1均可):
控制DAC856X每次要传输24bit数据,高8bit控制位 + 16bit数据位。
比如Power up DAC-A and DAC-B:
DAC8562_WriteCmd((4 << 19) | (0 << 16) | (3 << 0))
DAC856X的程序驱动框架设计如下:
有了这个框图,程序设计就比较好理解了。
spi总线配置通过如下两个函数实现:
/* ********************************************************************************************************* * 函 数 名: bsp_InitSPIBus * 功能说明: 配置SPI总线。 * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */ void bsp_InitSPIBus(void) { g_spi_busy = 0; bsp_InitSPIParam(SPI_BAUDRATEPRESCALER_8, SPI_PHASE_1EDGE, SPI_POLARITY_LOW); } /* ********************************************************************************************************* * 函 数 名: bsp_InitSPIParam * 功能说明: 配置SPI总线参数,时钟分频,时钟相位和时钟极性。 * 形 参: _BaudRatePrescaler SPI总线时钟分频设置,支持的参数如下: * SPI_BAUDRATEPRESCALER_2 2分频 * SPI_BAUDRATEPRESCALER_4 4分频 * SPI_BAUDRATEPRESCALER_8 8分频 * SPI_BAUDRATEPRESCALER_16 16分频 * SPI_BAUDRATEPRESCALER_32 32分频 * SPI_BAUDRATEPRESCALER_64 64分频 * SPI_BAUDRATEPRESCALER_128 128分频 * SPI_BAUDRATEPRESCALER_256 256分频 * * _CLKPhase 时钟相位,支持的参数如下: * SPI_PHASE_1EDGE SCK引脚的第1个边沿捕获传输的第1个数据 * SPI_PHASE_2EDGE SCK引脚的第2个边沿捕获传输的第1个数据 * * _CLKPolarity 时钟极性,支持的参数如下: * SPI_POLARITY_LOW SCK引脚在空闲状态处于低电平 * SPI_POLARITY_HIGH SCK引脚在空闲状态处于高电平 * * 返 回 值: 无 ********************************************************************************************************* */ void bsp_InitSPIParam(uint32_t _BaudRatePrescaler, uint32_t _CLKPhase, uint32_t _CLKPolarity) { /* 提高执行效率,只有在SPI硬件参数发生变化时,才执行HAL_Init */ if (s_BaudRatePrescaler == _BaudRatePrescaler && s_CLKPhase == _CLKPhase && s_CLKPolarity == _CLKPolarity) { return; } s_BaudRatePrescaler = _BaudRatePrescaler; s_CLKPhase = _CLKPhase; s_CLKPolarity = _CLKPolarity; /* 设置SPI参数 */ hspi.Instance = SPIx; /* 例化SPI */ hspi.Init.BaudRatePrescaler = _BaudRatePrescaler; /* 设置波特率 */ hspi.Init.Direction = SPI_DIRECTION_2LINES; /* 全双工 */ hspi.Init.CLKPhase = _CLKPhase; /* 配置时钟相位 */ hspi.Init.CLKPolarity = _CLKPolarity; /* 配置时钟极性 */ hspi.Init.DataSize = SPI_DATASIZE_8BIT; /* 设置数据宽度 */ hspi.Init.FirstBit = SPI_FIRSTBIT_MSB; /* 数据传输先传高位 */ hspi.Init.TIMode = SPI_TIMODE_DISABLE; /* 禁止TI模式 */ hspi.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE; /* 禁止CRC */ hspi.Init.CRCPolynomial = 7; /* 禁止CRC后,此位无效 */ hspi.Init.NSS = SPI_NSS_SOFT; /* 使用软件方式管理片选引脚 */ hspi.Init.Mode = SPI_MODE_MASTER; /* SPI工作在主控模式 */ /* 复位SPI */ if(HAL_SPI_DeInit(&hspi) != HAL_OK) { Error_Handler(__FILE__, __LINE__); } if (HAL_SPI_Init(&hspi) != HAL_OK) { Error_Handler(__FILE__, __LINE__); } }
关于这两个函数有以下两点要做个说明:
注:推荐使用查询方式。
SPI驱动的查询,中断和DMA方式主要通过函数bsp_spiTransfer实现数据传输:
/* ********************************************************************************************************* * 选择DMA,中断或者查询方式 ********************************************************************************************************* */ //#define USE_SPI_DMA /* DMA方式 */ //#define USE_SPI_INT /* 中断方式 */ #define USE_SPI_POLL /* 查询方式 */ uint8_t g_spiTxBuf[SPI_BUFFER_SIZE]; uint8_t g_spiRxBuf[SPI_BUFFER_SIZE]; /* ********************************************************************************************************* * 函 数 名: bsp_spiTransfer * 功能说明: 启动数据传输 * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */ void bsp_spiTransfer(void) { if (g_spiLen > SPI_BUFFER_SIZE) { return; } /* DMA方式传输 */ #ifdef USE_SPI_DMA wTransferState = TRANSFER_WAIT; if(HAL_SPI_TransmitReceive_DMA(&hspi, (uint8_t*)g_spiTxBuf, (uint8_t *)g_spiRxBuf, g_spiLen) != HAL_OK) { Error_Handler(__FILE__, __LINE__); } while (wTransferState == TRANSFER_WAIT) { ; } #endif /* 中断方式传输 */ #ifdef USE_SPI_INT wTransferState = TRANSFER_WAIT; if(HAL_SPI_TransmitReceive_IT(&hspi, (uint8_t*)g_spiTxBuf, (uint8_t *)g_spiRxBuf, g_spiLen) != HAL_OK) { Error_Handler(__FILE__, __LINE__); } while (wTransferState == TRANSFER_WAIT) { ; } #endif /* 查询方式传输 */ #ifdef USE_SPI_POLL if(HAL_SPI_TransmitReceive(&hspi, (uint8_t*)g_spiTxBuf, (uint8_t *)g_spiRxBuf, g_spiLen, 1000000) != HAL_OK) { Error_Handler(__FILE__, __LINE__); } #endif }
通过开头宏定义可以方便的切换中断,查询和DMA方式。
首先回忆下STM32F4支持的4种时序配置。
SCK引脚在空闲状态处于高电平,SCK引脚的第2个边沿捕获传输的第1个数据。
SCK引脚在空闲状态处于低电平,SCK引脚的第2个边沿捕获传输的第1个数据。
SCK引脚在空闲状态处于高电平,SCK引脚的第1个边沿捕获传输的第1个数据。
SCK引脚在空闲状态处于低电平,SCK引脚的第1个边沿捕获传输的第1个数据。
有了H7支持的时序配置,再来看下DAC856X的时序图:
首先DAC856X是下降升沿做数据采集,所以STM32F4的可选的配置就是:
CHOL = 0, CPHA = 1
CHOL = 1, CPHA = 0
对于这两种情况的主要区别是空闲状态下SCLK时钟选择高电平还是低电平,根据上面的时序图和DAC856X的数据手册,两种情况下都可以正常运行。经过实际测试,STM32F4使用这两个配置确实都可以正常运行。程序里面默认是选择CHOL = 0, CPHA = 1。
单SPI接口管理多个SPI设备最麻烦的地方是不同设备的时钟分配,时钟极性和时钟相位并不相同。对此的解决解决办法是在片选阶段配置切换,比如DAC856X的片选:
/* ********************************************************************************************************* * 函 数 名: DAC8562_SetCS * 功能说明: DAC8562 片选控制函数 * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */ void DAC8562_SetCS(uint8_t _Level) { if (_Level == 0) { bsp_SpiBusEnter(); /* 占用SPI总线 */ bsp_InitSPIParam(SPI_BAUDRATEPRESCALER_2, SPI_PHASE_2EDGE, SPI_POLARITY_LOW); CS_0(); } else { CS_1(); bsp_SpiBusExit(); /* 释放SPI总线 */ } }
通过这种方式就有效的解决了单SPI接口管理多设备的问题。因为给每个设备都配了一个独立的片选引脚,这样就可以为每个设备都配置这么一个片选配置。
但是频繁配置也比较繁琐,所以函数bsp_InitSPIParam里面做了特别处理。当前配置与之前配置相同的情况下无需重复配置。
DAC856X的双通道数据更新通过下面的函数实现:
/* ********************************************************************************************************* * 函 数 名: DAC8562_SetDacData * 功能说明: 设置DAC输出,并立即更新。 * 形 参: _ch, 通道, 0 , 1 * _data : 数据 * 返 回 值: 无 ********************************************************************************************************* */ void DAC8562_SetDacData(uint8_t _ch, uint16_t _dac) { if (_ch == 0) { /* Write to DAC-A input register and update DAC-A; */ DAC8562_WriteCmd((3 << 19) | (0 << 16) | (_dac << 0)); } else if (_ch == 1) { /* Write to DAC-B input register and update DAC-A; */ DAC8562_WriteCmd((3 << 19) | (1 << 16) | (_dac << 0)); } }
函数实现比较简单,每次更新发送24bit数据即可。
SPI总线驱动文件bsp_spi_bus.c主要实现了如下几个API供用户调用:
函数原型:
void bsp_InitSPIBus(void)
函数描述:
此函数主要用于SPI总线的初始化,在bsp.c文件调用一次即可。
函数原型:
void bsp_InitSPIParam(uint32_t _BaudRatePrescaler, uint32_t _CLKPhase, uint32_t _CLKPolarity)
函数描述:
此函数用于SPI总线的配置。
函数参数:
SPI_BAUDRATEPRESCALER_2 2分频
SPI_BAUDRATEPRESCALER_4 4分频
SPI_BAUDRATEPRESCALER_8 8分频
SPI_BAUDRATEPRESCALER_16 16分频
SPI_BAUDRATEPRESCALER_32 32分频
SPI_BAUDRATEPRESCALER_64 64分频
SPI_BAUDRATEPRESCALER_128 128分频
SPI_BAUDRATEPRESCALER_256 256分频
SPI_PHASE_1EDGE SCK引脚的第1个边沿捕获传输的第1个数据
SPI_PHASE_2EDGE SCK引脚的第2个边沿捕获传输的第1个数据
SPI_POLARITY_LOW SCK引脚在空闲状态处于低电平
SPI_POLARITY_HIGH SCK引脚在空闲状态处于高电平
函数原型:
void bsp_spiTransfer(void)
函数描述:
此函数用于启动SPI数据传输,支持查询,中断和DMA方式传输。
DAC856X驱动文件bsp_spi_dac8562.c主要实现了如下几个API供用户调用:
函数原型:
void bsp_InitDAC8562(void)
函数描述:
主要用于DAC856X的初始化,调用前务必先调用函数bsp_InitSPIBus初始化SPI外设。
函数原型:
void DAC8562_SetCS(uint8_t _Level)
函数描述:
此函数用于片选DAC8562。
函数参数:
函数原型:
void DAC8562_WriteCmd(uint32_t _cmd)
函数描述:
此函数用于向SPI总线发送24个bit数据。
函数参数:
函数原型:
void DAC8562_SetDacData(uint8_t _ch, uint16_t _dac)
函数描述:
此函数用于设置DAC输出,并立即更新。
函数参数:
函数原型:
int32_t DAC8562_DacToVoltage(uint16_t _dac)
函数描述:
此函数用于将DAC值换算为电压值,单位0.1mV。
函数参数:
函数原型:
uint32_t DAC8562_VoltageToDac(int32_t _volt)
函数描述:
此函数用于将电压值转换为DAC值。
函数参数:
DAC856X移植步骤如下:
/* ********************************************************************************************************* * 时钟,引脚,DMA,中断等宏定义 ********************************************************************************************************* */ #define SPIx SPI1 #define SPIx_CLK_ENABLE() __HAL_RCC_SPI1_CLK_ENABLE() #define DMAx_CLK_ENABLE() __HAL_RCC_DMA2_CLK_ENABLE() #define SPIx_FORCE_RESET() __HAL_RCC_SPI1_FORCE_RESET() #define SPIx_RELEASE_RESET() __HAL_RCC_SPI1_RELEASE_RESET() #define SPIx_SCK_CLK_ENABLE() __HAL_RCC_GPIOB_CLK_ENABLE() #define SPIx_SCK_GPIO GPIOB #define SPIx_SCK_PIN GPIO_PIN_3 #define SPIx_SCK_AF GPIO_AF5_SPI1 #define SPIx_MISO_CLK_ENABLE() __HAL_RCC_GPIOB_CLK_ENABLE() #define SPIx_MISO_GPIO GPIOB #define SPIx_MISO_PIN GPIO_PIN_4 #define SPIx_MISO_AF GPIO_AF5_SPI1 #define SPIx_MOSI_CLK_ENABLE() __HAL_RCC_GPIOB_CLK_ENABLE() #define SPIx_MOSI_GPIO GPIOB #define SPIx_MOSI_PIN GPIO_PIN_5 #define SPIx_MOSI_AF GPIO_AF5_SPI1 #define SPIx_TX_DMA_CHANNEL DMA_CHANNEL_3 #define SPIx_TX_DMA_STREAM DMA2_Stream3 #define SPIx_RX_DMA_CHANNEL DMA_CHANNEL_3 #define SPIx_RX_DMA_STREAM DMA2_Stream0 #define SPIx_IRQn SPI1_IRQn #define SPIx_IRQHandler SPI1_IRQHandler #define SPIx_DMA_TX_IRQn DMA2_Stream3_IRQn #define SPIx_DMA_RX_IRQn DMA2_Stream0_IRQn #define SPIx_DMA_TX_IRQHandler DMA2_Stream3_IRQHandler #define SPIx_DMA_RX_IRQHandler DMA2_Stream0_IRQHandler
/* ********************************************************************************************************* * 函 数 名: DAC8562_SetCS * 功能说明: DAC8562 片选控制函数 * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */ void DAC8562_SetCS(uint8_t _Level) { if (_Level == 0) { bsp_SpiBusEnter(); /* 占用SPI总线 */ bsp_InitSPIParam(SPI_BAUDRATEPRESCALER_2, SPI_PHASE_2EDGE, SPI_POLARITY_LOW); CS_0(); } else { CS_1(); bsp_SpiBusExit(); /* 释放SPI总线 */ } }
/* SYNC, 也就是CS片选 */ #define CS_CLK_ENABLE() __HAL_RCC_GPIOF_CLK_ENABLE() #define CS_GPIO GPIOF #define CS_PIN GPIO_PIN_7 #define CS_1() CS_GPIO->BSRR = CS_PIN #define CS_0() CS_GPIO->BSRR = ((uint32_t)CS_PIN << 16U) /* CLR */ #define CLR_CLK_ENABLE() __HAL_RCC_GPIOH_CLK_ENABLE() #define CLR_GPIO GPIOH #define CLR_PIN GPIO_PIN_7 #define CLR_1() CLR_GPIO->BSRR = CLR_PIN #define CLR_0() CLR_GPIO->BSRR = ((uint32_t)CLR_PIN << 16U) /* LDAC */ #define LDAC_CLK_ENABLE() __HAL_RCC_GPIOA_CLK_ENABLE() #define LDAC_GPIO GPIOA #define LDAC_PIN GPIO_PIN_4 #define LDAC_1() LDAC_GPIO->BSRR = CLR_PIN #define LDAC_0() LDAC_GPIO->BSRR = ((uint32_t)CLR_PIN << 16U)
/* 针对不同的应用程序,添加需要的底层驱动模块初始化函数 */ bsp_InitSPIBus(); /* 配置SPI总线 */ bsp_InitDAC8562(); /* 初始化配置DAC8562/8563 */
通过程序设计框架,让大家先对配套例程有一个全面的认识,然后再理解细节,本次实验例程的设计框架如下:
第1阶段,上电启动阶段:
第2阶段,进入main函数:
配套例子:
V5-014_DAC856x简易信号发生器(双通道,16bit分辨率, 正负10V输出)
实验目的:
实验内容:
根据增益设置不同,DAC的输出量可以为0到2.5V或者0到5V。
实验操作:
上电后串口打印的信息:
波特率 115200,数据位 8,奇偶校验位无,停止位 1。
波形效果:
模块插入位置:
程序设计:
系统栈大小分配:
硬件外设初始化
硬件外设的初始化是在 bsp.c 文件实现:
/* ********************************************************************************************************* * 函 数 名: bsp_Init * 功能说明: 初始化所有的硬件设备。该函数配置CPU寄存器和外设的寄存器并初始化一些全局变量。只需要调用一次 * 形 参:无 * 返 回 值: 无 ********************************************************************************************************* */ void bsp_Init(void) { /* STM32F407 HAL 库初始化,此时系统用的还是F407自带的16MHz,HSI时钟: - 调用函数HAL_InitTick,初始化滴答时钟中断1ms。 - 设置NVIV优先级分组为4。 */ HAL_Init(); /* 配置系统时钟到168MHz - 切换使用HSE。 - 此函数会更新全局变量SystemCoreClock,并重新配置HAL_InitTick。 */ SystemClock_Config(); /* Event Recorder: - 可用于代码执行时间测量,MDK5.25及其以上版本才支持,IAR不支持。 - 默认不开启,如果要使能此选项,务必看V5开发板用户手册第8章 */ #if Enable_EventRecorder == 1 /* 初始化EventRecorder并开启 */ EventRecorderInitialize(EventRecordAll, 1U); EventRecorderStart(); #endif bsp_InitKey(); /* 按键初始化,要放在滴答定时器之前,因为按钮检测是通过滴答定时器扫描 */ bsp_InitTimer(); /* 初始化滴答定时器 */ bsp_InitUart(); /* 初始化串口 */ bsp_InitExtIO(); /* 初始化扩展IO */ bsp_InitLed(); /* 初始化LED */ BEEP_InitHard(); /* 初始化蜂鸣器 */ /* 针对不同的应用程序,添加需要的底层驱动模块初始化函数 */ bsp_InitSPIBus(); /* 配置SPI总线 */ bsp_InitDAC8562(); /* 初始化配置DAC8562/8563 */ }
主功能:
主程序实现如下操作:
/* ********************************************************************************************************* * 函 数 名: main * 功能说明: c程序入口 * 形 参: 无 * 返 回 值: 错误代码(无需处理) ********************************************************************************************************* */ int main(void) { bsp_Init(); /* 硬件初始化 */ PrintfLogo(); /* 打印例程名称和版本等信息 */ DemoSpiDac(); /* SPI DAC测试 */ } /* ********************************************************************************************************* * 函 数 名: DemoSpiDac * 功能说明: DAC8562测试 * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */ void DemoSpiDac(void) { uint8_t i=0; uint8_t ucKeyCode; /* 按键代码 */ sfDispMenu(); /* 打印命令提示 */ bsp_StartAutoTimer(0, 200); /* 启动1个100ms的自动重装的定时器 */ /* 生成方波数据 */ for(i =0; i< 50; i++) { ch1buf[i] = 0; } for(i =50; i< 100; i++) { ch1buf[i] = 65535; } /* 生成正弦波数据 */ MakeSinTable(ch2buf, 100, 0, 65535); /* 配置个TIM6中断,频率DAC_OUT_FREQ */ bsp_SetTIMforInt(TIM6, DAC_OUT_FREQ, 2, 0); DAC8562_SetDacData(0, 65535); /* 改变第1通道 DAC输出电压 */ while(1) { bsp_Idle(); /* 这个函数在bsp.c文件。用户可以修改这个函数实现CPU休眠和喂狗 */ /* 判断定时器超时时间 */ if (bsp_CheckTimer(0)) { /* 每隔100ms 进来一次 */ bsp_LedToggle(4); } /* 按键滤波和检测由后台systick中断服务程序实现,我们只需要调用bsp_GetKey读取键值即可。 */ ucKeyCode = bsp_GetKey(); /* 读取键值, 无键按下时返回 KEY_NONE = 0 */ if (ucKeyCode != KEY_NONE) { switch (ucKeyCode) { case KEY_DOWN_K1: /* K1键按下,双通道输出,通道1输出方波,通道2输出正弦波 */ /* 生成方波数据 */ for(i =0; i< 50; i++) { ch1buf[i] = 0; } for(i =50; i< 100; i++) { ch1buf[i] = 65535; } /* 生成正弦波数据 */ MakeSinTable(ch2buf, 100, 0, 65535); break; case KEY_DOWN_K2: /* K2键按下,双通道输出方波 */ /* 生成方波数据 */ for(i =0; i< 50; i++) { ch1buf[i] = 0; ch2buf[i] = 0; } for(i =50; i< 100; i++) { ch1buf[i] = 65535; ch2buf[i] = 65535; } break; case KEY_DOWN_K3: /* K3键按下,双通道输出正弦波 */ MakeSinTable(ch1buf, 100, 0, 65535); MakeSinTable(ch2buf, 100, 0, 65535); break; case JOY_DOWN_OK: /* 摇杆OK键按下,双通道输出直流 */ /* 通道1输出-10V */ for(i = 0; i < 100; i++) { ch1buf[i] = 0; } /* 通道2输出 10V */ for(i = 0; i < 100; i++) { ch2buf[i] = 65535; } /* 输出直流的话,仅设置一次输出寄存器也是可以的 */ //DAC8562_SetDacData(0, 0); //DAC8562_SetDacData(1, 65535); break; default: /* 其它的键值不处理 */ break; } } } }
配套例子:
V5-014_DAC856x简易信号发生器(双通道,16bit分辨率, 正负10V输出)
实验目的:
实验内容:
根据增益设置不同,DAC的输出量可以为0到2.5V或者0到5V。
实验操作:
上电后串口打印的信息:
波特率 115200,数据位 8,奇偶校验位无,停止位 1。
波形效果:
模块插入位置:
程序设计:
系统栈大小分配:
硬件外设初始化
硬件外设的初始化是在 bsp.c 文件实现:
/* ********************************************************************************************************* * 函 数 名: bsp_Init * 功能说明: 初始化所有的硬件设备。该函数配置CPU寄存器和外设的寄存器并初始化一些全局变量。只需要调用一次 * 形 参:无 * 返 回 值: 无 ********************************************************************************************************* */ void bsp_Init(void) { /* STM32F407 HAL 库初始化,此时系统用的还是F407自带的16MHz,HSI时钟: - 调用函数HAL_InitTick,初始化滴答时钟中断1ms。 - 设置NVIV优先级分组为4。 */ HAL_Init(); /* 配置系统时钟到168MHz - 切换使用HSE。 - 此函数会更新全局变量SystemCoreClock,并重新配置HAL_InitTick。 */ SystemClock_Config(); /* Event Recorder: - 可用于代码执行时间测量,MDK5.25及其以上版本才支持,IAR不支持。 - 默认不开启,如果要使能此选项,务必看V5开发板用户手册第8章 */ #if Enable_EventRecorder == 1 /* 初始化EventRecorder并开启 */ EventRecorderInitialize(EventRecordAll, 1U); EventRecorderStart(); #endif bsp_InitKey(); /* 按键初始化,要放在滴答定时器之前,因为按钮检测是通过滴答定时器扫描 */ bsp_InitTimer(); /* 初始化滴答定时器 */ bsp_InitUart(); /* 初始化串口 */ bsp_InitExtIO(); /* 初始化扩展IO */ bsp_InitLed(); /* 初始化LED */ BEEP_InitHard(); /* 初始化蜂鸣器 */ /* 针对不同的应用程序,添加需要的底层驱动模块初始化函数 */ bsp_InitSPIBus(); /* 配置SPI总线 */ bsp_InitDAC8562(); /* 初始化配置DAC8562/8563 */ }
主功能:
主程序实现如下操作:
/* ********************************************************************************************************* * 函 数 名: main * 功能说明: c程序入口 * 形 参: 无 * 返 回 值: 错误代码(无需处理) ********************************************************************************************************* */ int main(void) { bsp_Init(); /* 硬件初始化 */ PrintfLogo(); /* 打印例程名称和版本等信息 */ DemoSpiDac(); /* SPI DAC测试 */ } /* ********************************************************************************************************* * 函 数 名: DemoSpiDac * 功能说明: DAC8562测试 * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */ void DemoSpiDac(void) { uint8_t i=0; uint8_t ucKeyCode; /* 按键代码 */ sfDispMenu(); /* 打印命令提示 */ bsp_StartAutoTimer(0, 200); /* 启动1个100ms的自动重装的定时器 */ /* 生成方波数据 */ for(i =0; i< 50; i++) { ch1buf[i] = 0; } for(i =50; i< 100; i++) { ch1buf[i] = 65535; } /* 生成正弦波数据 */ MakeSinTable(ch2buf, 100, 0, 65535); /* 配置个TIM6中断,频率DAC_OUT_FREQ */ bsp_SetTIMforInt(TIM6, DAC_OUT_FREQ, 2, 0); DAC8562_SetDacData(0, 65535); /* 改变第1通道 DAC输出电压 */ while(1) { bsp_Idle(); /* 这个函数在bsp.c文件。用户可以修改这个函数实现CPU休眠和喂狗 */ /* 判断定时器超时时间 */ if (bsp_CheckTimer(0)) { /* 每隔100ms 进来一次 */ bsp_LedToggle(4); } /* 按键滤波和检测由后台systick中断服务程序实现,我们只需要调用bsp_GetKey读取键值即可。 */ ucKeyCode = bsp_GetKey(); /* 读取键值, 无键按下时返回 KEY_NONE = 0 */ if (ucKeyCode != KEY_NONE) { switch (ucKeyCode) { case KEY_DOWN_K1: /* K1键按下,双通道输出,通道1输出方波,通道2输出正弦波 */ /* 生成方波数据 */ for(i =0; i< 50; i++) { ch1buf[i] = 0; } for(i =50; i< 100; i++) { ch1buf[i] = 65535; } /* 生成正弦波数据 */ MakeSinTable(ch2buf, 100, 0, 65535); break; case KEY_DOWN_K2: /* K2键按下,双通道输出方波 */ /* 生成方波数据 */ for(i =0; i< 50; i++) { ch1buf[i] = 0; ch2buf[i] = 0; } for(i =50; i< 100; i++) { ch1buf[i] = 65535; ch2buf[i] = 65535; } break; case KEY_DOWN_K3: /* K3键按下,双通道输出正弦波 */ MakeSinTable(ch1buf, 100, 0, 65535); MakeSinTable(ch2buf, 100, 0, 65535); break; case JOY_DOWN_OK: /* 摇杆OK键按下,双通道输出直流 */ /* 通道1输出-10V */ for(i = 0; i < 100; i++) { ch1buf[i] = 0; } /* 通道2输出 10V */ for(i = 0; i < 100; i++) { ch2buf[i] = 65535; } /* 输出直流的话,仅设置一次输出寄存器也是可以的 */ //DAC8562_SetDacData(0, 0); //DAC8562_SetDacData(1, 65535); break; default: /* 其它的键值不处理 */ break; } } } }
本章节涉及到的知识点非常多,需要大家稍花点精力去研究。