第75章 STM32H7的SPI总线应用之驱动DAC8501(双路输出,16bit分辨率,0-5V)
本章节为大家讲解标准SPI接线方式驱动模数转换器DAC8501,制作了中断和DMA两种驱动方式。
75.1 初学者重要提示
75.2 DAC结构分类和技术术语
75.3 DAC8501硬件设计
75.4 DAC8501关键知识点整理(重要)
75.5 DAC8501驱动设计(中断更新方式)
75.5 DAC8501驱动设计(SPI DMA更新方式)
75.6 SPI总线板级支持包(bsp_spi_bus.c)
75.7 DAC8501支持包中断方式(bsp_spi_dac8501.c)
75.8 DAC8501支持包DMA方式(bsp_spidma_dac8501.c)
75.9 DAC8501驱动移植和使用(中断更新方式)
75.10 DAC8501驱动移植和使用(SPI DMA更新方式)
75.11 实验例程设计框架
75.12 实验例程说明(MDK)
75.13 实验例程说明(IAR)
75.14 总结
75.1 初学者重要提示
1、 学习本章节前,务必优先学习第72章。
2、 DAC8501模块上带了两片8501,每片是单通道DAC,带片上输出缓冲运放,轨到轨输出,16bit分辨率,支持30MHz的SPI时钟速度。
3、 本章涉及到的知识点比较多,需要大家掌握STM32H7的SPI , DMA,TIM,DMAMUX和DAC8501的一些细节用法。
4、 H7的SPI + DMA驱动这类外设的灵活度,绝对可以媲美FPGA去控制:
H7的SPI外设比F4系列的灵活性强太多了,主要表现在两个方面:数据的传输支持了4-32bit,特别是那个NSS片选引脚,超强劲,可以做各种时间插入,灵活应对了市场上这类芯片的需求。
DMA这块相比F4系列,有了质的飞跃,支持了DMAMUX,这个DMAMUX除了带来灵活的触发源选择,还支持了各种触发事件和同步触发功能。本章配套例子的触发周期控制就是利用了DMAMUX的同步触发功能。
5、 本章配套了中断和DMA两种更新方式的案例,DMA实现方式与中断更新方式完全不同,因为DMA方式要使用硬件SPI1 NSS片选引脚驱动DAC8501。而中断更新方式使用公共的总线驱动文件bsp_spi_bus.c,片选是通过通用IO方式控制,支持串行FLASH、TSC2046、VS1053、AD7705、ADS1256等SPI设备。大家在看例子的时候要注意。
6、 对于本章教程配套例子的SPI DMA方式,这里特别注意一点,定时器触发一次,就会让SPI以DMA方式传输24bit数据。
7、 DAC8501数据手册,模块原理图和接线图都已经放到本章教程配置例子的Doc文件里。
75.2 DAC结构分类和技术术语
在本教程的第74章进行了详细说明。
75.3 DAC8501硬件设计
DAC的原理图下载:
75.3.1 DAC8501模块规格
产品规格:
1、供电电压: 2.7 - 5.5V【3.3V供电时,输出电压也可以到5V】。
2、通道数: 2路 (通过2片DAC8501E实现)。
3、输出电压范围 : 0 - 5V【零位 < 0.020V, 满位 > 4.970V】。
4、分辨率: 16位。
5、功耗 : 小于10mA。
6、MCU接口 :高速 SPI (30M) 支持 3.3V和5V单片机。
7、DAC输出模拟带宽:350KHz。
8、DAC输出响应: 10uS 到 0.003% FSR。
产品特点:
1、输出和供电电压无关;模块内带升压电路和5V基准。
2、自适应单片机的电平(2.7 - 5V 均可以)。
3、输出电压轨到轨,最高电压可以到 4.970V 以上。
产品效果:
75.3.2 DAC8501硬件接口
V7板子上DAC8501模块的插座的原理图如下:
实际对应开发的位置如下:
75.4 DAC8501关键知识点整理(重要)
驱动DAC8501需要对下面这些知识点有个认识。
75.4.1 DAC8501基础信息
单通道DAC,带片上输出缓冲运放,轨到轨输出,16bit分辨率,支持30MHz的SPI时钟速度。
模拟输出带宽350KHz。
供电范围2.7V到5.5V。
具有低功耗特性。
上电复位输出0V。
75.4.2 DAC8501每个引脚的作用
DAC8501的封装形式:
Vdd
供电范围2.7-5.5V。
Vref
稳压基准输入。
Vfb
输出运放的反馈。
Vout
模拟输出电压,输出运放具有轨到轨特性。
SYNC (片选)
低电平有效,当SYNC变为低电平时,它使能输入移位寄存器,并且数据采样在随后的时钟下降沿。 DAC输出在第24个时钟下降沿之后更新。 如果SYNC在第23个时钟沿之前变高,SYNC的上升沿将充当中断,并且DAC8501将忽略写序列。
SCLK
时钟输入端,支持30MHz。
Din
串行时钟输入,每个时钟下降沿将数据写到的24bit的输入移位寄存器。
GND
接地端。
75.4.3 DAC8501输出电压计算公式
DAC8501的计算公式如下:
D
配置DAC8501数据输出寄存器的数值,范围0 到2^16 – 1,即0到65535。
VREF
使用外部参考电压,由VREFIN引脚的输入决定。
Vout
输出电压。
75.4.4 DAC8501时序图
DAC8501的时序图如下:
这个时序里面有三个参数尤其重要,后面时序配置要用到。
f(1)
供电2.7到3.6V时,最高时钟20MHz。
供电3.6到5.5V时,最高时钟30MHz。
t(4)
SYNC低电平有效到SCLK第1个上降沿信号的时间没有最小值限制,可以为0。
t(8)
每传输24bit数据后,SYNC要保持一段时间的高电平。
供电2.7到3.6V时,最小要求50ns。
供电3.6到5.5V时,最小要求33ns。
75.4.5 DAC8501寄存器配置
DAC8501的寄存器配置是24bit格式:
控制DAC8501每次要传输24bit数据,高8bit控制位 + 16bit数据位。
控制位的PD1和PD0定义如下:
PD1 PD0 决定4种工作模式
0 0 ---> 正常工作模式
0 1 ---> 输出接1K欧到GND
1 0 ---> 输出100K欧到GND
1 1 ---> 输出高阻
75.5 DAC8501驱动设计(中断更新方式)
DAC8501的程序驱动框架设计如下:
有了这个框图,程序设计就比较好理解了。
75.5.1 第1步:SPI总线配置
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引脚在空闲状态处于高电平
*
* 返 回 值: 无
**********************************************************************************************************/
voidbsp_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.CRCLength= SPI_CRC_LENGTH_8BIT; /*禁止CRC后,此位无效*/hspi.Init.NSS= SPI_NSS_SOFT; /*使用软件方式管理片选引脚*/hspi.Init.FifoThreshold= SPI_FIFO_THRESHOLD_01DATA; /*设置FIFO大小是一个数据项*/hspi.Init.NSSPMode= SPI_NSS_PULSE_DISABLE; /*禁止脉冲输出*/hspi.Init.MasterKeepIOState= SPI_MASTER_KEEP_IO_STATE_ENABLE; /*禁止SPI后,SPI相关引脚保持当前状态*/hspi.Init.Mode= SPI_MODE_MASTER; /*SPI工作在主控模式*/
/*复位配置*/
if (HAL_SPI_DeInit(&hspi) !=HAL_OK)
{
Error_Handler(__FILE__, __LINE__);
}/*初始化配置*/
if (HAL_SPI_Init(&hspi) !=HAL_OK)
{
Error_Handler(__FILE__, __LINE__);
}
}
关于这两个函数有以下两点要做个说明:
函数bsp_InitSPIBus里面的配置是个初始设置。实际驱动芯片时,会通过函数bsp_InitSPIParam做再配置。
函数bsp_InitSPIParam提供了时钟分频,时钟相位和时钟极性配置。驱动不同外设芯片时,基本上调整这三个参数就够。当SPI接口上接了多个不同类型的芯片时,通过此函数可以方便的切换配置。
75.5.2 第2步:SPI总线的查询,中断和DMA方式设置
注:对于DAC8501,请使用查询方式。
SPI驱动的查询,中断和DMA方式主要通过函数bsp_spiTransfer实现数据传输:
/**********************************************************************************************************
* 选择DMA,中断或者查询方式
**********************************************************************************************************/
//#define USE_SPI_DMA /* DMA方式 *///#define USE_SPI_INT /* 中断方式 */
#define USE_SPI_POLL /* 查询方式 */
/*查询模式*/
#if defined (USE_SPI_POLL)uint8_t g_spiTxBuf[SPI_BUFFER_SIZE];
uint8_t g_spiRxBuf[SPI_BUFFER_SIZE];/*中断模式*/
#elif defined (USE_SPI_INT)uint8_t g_spiTxBuf[SPI_BUFFER_SIZE];
uint8_t g_spiRxBuf[SPI_BUFFER_SIZE];/*DMA模式使用的SRAM4*/
#elif defined (USE_SPI_DMA)
#if defined ( __CC_ARM ) /* IAR *******/__attribute__((section (".RAM_D3"))) uint8_t g_spiTxBuf[SPI_BUFFER_SIZE];
__attribute__((section (".RAM_D3"))) uint8_t g_spiRxBuf[SPI_BUFFER_SIZE];#elif defined (__ICCARM__) /* MDK ********/
#pragma location = ".RAM_D3"uint8_t g_spiTxBuf[SPI_BUFFER_SIZE];#pragma location = ".RAM_D3"uint8_t g_spiRxBuf[SPI_BUFFER_SIZE];#endif
#endif
/**********************************************************************************************************
* 函 数 名: 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_POLLif(HAL_SPI_TransmitReceive(&hspi, (uint8_t*)g_spiTxBuf, (uint8_t *)g_spiRxBuf, g_spiLen, 1000000) !=HAL_OK)
{
Error_Handler(__FILE__, __LINE__);
}#endif}
通过开头宏定义可以方便的切换中断,查询和DMA方式。其中查询和中断方式比较好理解,而DMA方式要特别注意两点:
通过本手册第26章的内存块超方便使用方式,将DMA缓冲定义到SRAM4上。因为本工程是用的DTCM做的主RAM空间,这个空间无法使用通用DMA1和DMA2。
由于程序里面开启了数据Cache,会造成DMA和CPU访问SRAM4数据不一致的问题,特此将SRAM4空间关闭Cache。
/*配置SRAM4的MPU属性为Non-cacheable*/MPU_InitStruct.Enable=MPU_REGION_ENABLE;
MPU_InitStruct.BaseAddress= 0x38000000;
MPU_InitStruct.Size=MPU_REGION_SIZE_64KB;
MPU_InitStruct.AccessPermission=MPU_REGION_FULL_ACCESS;
MPU_InitStruct.IsBufferable=MPU_ACCESS_NOT_BUFFERABLE;
MPU_InitStruct.IsCacheable=MPU_ACCESS_NOT_CACHEABLE;
MPU_InitStruct.IsShareable=MPU_ACCESS_NOT_SHAREABLE;
MPU_InitStruct.Number=MPU_REGION_NUMBER2;
MPU_InitStruct.TypeExtField=MPU_TEX_LEVEL1;
MPU_InitStruct.SubRegionDisable= 0x00;
MPU_InitStruct.DisableExec=MPU_INSTRUCTION_ACCESS_ENABLE;
HAL_MPU_ConfigRegion(&MPU_InitStruct);
75.5.3 第3步:DAC8501的时钟极性和时钟相位配置
首先回忆下STM32H7支持的4种时序配置。
当CPOL = 1, CPHA = 1时
SCK引脚在空闲状态处于低电平,SCK引脚的第2个边沿捕获传输的第1个数据。
当CPOL = 0, CPHA = 1时
SCK引