75.6 DAC8501驱动设计(SPI DMA更新方式)
DAC8501的DMA驱动方式略复杂,跟中断更新方式完全不同,要使用硬件SPI1 NSS引脚驱动DAC8501的片选,所有专门做了一个驱动文件来实现,程序驱动框架设计如下:
有了这个框图,程序设计就比较好理解了。
75.6.1 第1步:SPI总线配置
spi总线配置通过如下两个函数实现:
- /*
- *********************************************************************************************************
- * 函 数 名: bsp_InitDAC8501
- * 功能说明: 配置GPIO并初始化DAC8501寄存器
- * 形 参: 无
- * 返 回 值: 无
- *********************************************************************************************************
- */
- void bsp_InitDAC8501(void)
- {
- s_SpiDmaMode = 0;
- /*##-1- 配置SPI DMA ############################################################*/
- bsp_InitSPIParam(SPI_BAUDRATEPRESCALER_8, SPI_PHASE_2EDGE, SPI_POLARITY_LOW);
- /*##-2- 默认输出0V ############################################################*/
- DAC8501_SetDacData(0, 0); /* CH1输出0 */
- }
- /*
- *********************************************************************************************************
- * 函 数 名: 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参数 */
- hspi.Instance = SPIx; /* 例化SPI */
- hspi.Init.BaudRatePrescaler = _BaudRatePrescaler; /* 设置波特率 */
- hspi.Init.Direction = SPI_DIRECTION_2LINES_TXONLY; /* 全双工 */
- hspi.Init.CLKPhase = _CLKPhase; /* 配置时钟相位 */
- hspi.Init.CLKPolarity = _CLKPolarity; /* 配置时钟极性 */
- hspi.Init.DataSize = SPI_DATASIZE_24BIT; /* 设置数据宽度 */
- 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.FifoThreshold = SPI_FIFO_THRESHOLD_05DATA; /* 设置FIFO大小是一个数据项 */
- hspi.Init.NSS = SPI_NSS_HARD_OUTPUT; /* 使用软件方式管理片选引脚 */
- hspi.Init.NSSPMode = SPI_NSS_PULSE_ENABLE; /* 使能脉冲输出 */
- hspi.Init.NSSPolarity = SPI_NSS_POLARITY_LOW; /* 低电平有效 */
- /* MSS, 插入到NSS有效边沿和第一个数据开始之间的额外延迟,单位SPI时钟周期个数 */
- hspi.Init.MasterSSIdleness = SPI_MASTER_SS_IDLENESS_00CYCLE;
- /* MIDI, 两个连续数据帧之间插入的最小时间延迟,单位SPI时钟周期个数 */
- hspi.Init.MasterInterDataIdleness = SPI_MASTER_INTERDATA_IDLENESS_02CYCLE;
- 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__);
- }
- }
复制代码
这两个配置函数里面最重要的是置红的几个配置选项,这里依次为大家做个说明:
SPI_DIRECTION_2LINES_TXONLY
驱动DAC856X仅需要SPI写操作。
SPI_DATASIZE_24BIT
STM32H7的SPI支持4-32bit数据传输,由于DAC856X需要24bit数据,所以这里配置为24即可。
SPI_FIFO_THRESHOLD_05DATA
对于SPI1来说,里面的FIFO大小是16字节,那么SPI数据传输配置为24bit的话,FIFO最多可以存储5个24bit,因此这个fifo阀值要设置为5。
SPI_NSS_HARD_OUTPUT
我们这里要使用SPI的硬件片选引脚SPI_NSS。
SPI_MASTER_SS_IDLENESS_00CYCLE
插入到NSS有效边沿和第一个数据开始之间的额外延迟,单位SPI时钟周期个数。
根据本章75.4.4小节里面的t(4)要求,片选有效到SCLK第1个下降沿信号的时间,最小值为0。所以这里配置为0即可,也就是无需插入时间。
SPI_MASTER_INTERDATA_IDLENESS_10CYCLE
两个连续数据帧之间插入的最小时间延迟,单位SPI时钟周期个数。
根据本章75.4.4小节里面的t(5)要求,每传输24bit数据后,片选要保持一段时间的高电平,DAC856X要求至少要33ns(供电3.6到5.5V时),也是说,如果我们以25MHz驱动DAC856X,这里至少要配置为1个时钟周期,推荐值为2及其以上即可,我们这里直接配置为2个时钟周期(配置为1也没问题的)。
75.6.2 第2步:TIM12周期性触发配置
这里特别注意一点,定时器触发一次,就会让SPI以DMA方式传输24bit输出。
TIM12的触发配置如下:
- /*
- *********************************************************************************************************
- * 函 数 名: TIM12_Config
- * 功能说明: 配置TIM12,用于触发DMAMUX的请求发生器
- * 形 参: _ulFreq 触发频率,推荐范围100Hz - 1MHz
- * 返 回 值: 无
- *********************************************************************************************************
- */
- TIM_HandleTypeDef htim ={0};
- TIM_MasterConfigTypeDef sMasterConfig = {0};
- TIM_OC_InitTypeDef sConfig = {0};
- void TIM12_Config(uint32_t _ulFreq)
- {
- uint16_t usPeriod;
- uint16_t usPrescaler;
- uint32_t uiTIMxCLK;
- /* 使能时钟 */
- __HAL_RCC_TIM12_CLK_ENABLE();
- /*-----------------------------------------------------------------------
- bsp.c 文件中 void SystemClock_Config(void) 函数对时钟的配置如下:
- System Clock source = PLL (HSE)
- SYSCLK(Hz) = 400000000 (CPU Clock)
- HCLK(Hz) = 200000000 (AXI and AHBs Clock)
- AHB Prescaler = 2
- D1 APB3 Prescaler = 2 (APB3 Clock 100MHz)
- D2 APB1 Prescaler = 2 (APB1 Clock 100MHz)
- D2 APB2 Prescaler = 2 (APB2 Clock 100MHz)
- D3 APB4 Prescaler = 2 (APB4 Clock 100MHz)
- 因为APB1 prescaler != 1, 所以 APB1上的TIMxCLK = APB1 x 2 = 200MHz; 不含这个总线下的LPTIM1
- 因为APB2 prescaler != 1, 所以 APB2上的TIMxCLK = APB2 x 2 = 200MHz;
- APB4上面的TIMxCLK没有分频,所以就是100MHz;
- APB1 定时器有 TIM2, TIM3 ,TIM4, TIM5, TIM6, TIM7, TIM12, TIM13, TIM14,LPTIM1
- APB2 定时器有 TIM1, TIM8 , TIM15, TIM16,TIM17
- APB4 定时器有 LPTIM2,LPTIM3,LPTIM4,LPTIM5
- ----------------------------------------------------------------------- */
- uiTIMxCLK = SystemCoreClock / 2;
- if (_ulFreq < 100)
- {
- usPrescaler = 10000 - 1; /* 分频比 = 10000 */
- usPeriod = (uiTIMxCLK / 10000) / _ulFreq - 1; /* 自动重装的值 */
- }
- else if (_ulFreq < 3000)
- {
- usPrescaler = 100 - 1; /* 分频比 = 100 */
- usPeriod = (uiTIMxCLK / 100) / _ulFreq - 1;/* 自动重装的值 */
- }
- else /* 大于4K的频率,无需分频 */
- {
- usPrescaler = 0; /* 分频比 = 1 */
- usPeriod = uiTIMxCLK / _ulFreq - 1; /* 自动重装的值 */
- }
- htim.Instance = TIM12;
- htim.Init.Period = usPeriod;
- htim.Init.Prescaler = usPrescaler;
- htim.Init.ClockDivision = 0;
- htim.Init.CounterMode = TIM_COUNTERMODE_UP;
- htim.Init.RepetitionCounter = 0;
- if(HAL_TIM_Base_DeInit(&htim) != HAL_OK)
- {
- Error_Handler(__FILE__, __LINE__);
- }
- if(HAL_TIM_Base_Init(&htim) != HAL_OK)
- {
- Error_Handler(__FILE__, __LINE__);
- }
- sConfig.OCMode = TIM_OCMODE_PWM1;
- sConfig.OCPolarity = TIM_OCPOLARITY_LOW;
- sConfig.Pulse = usPeriod / 2; /* 占空比50% */
- if(HAL_TIM_OC_ConfigChannel(&htim, &sConfig, TIM_CHANNEL_1) != HAL_OK)
- {
- Error_Handler(__FILE__, __LINE__);
- }
- /* 启动OC1 */
- if(HAL_TIM_OC_Start(&htim, TIM_CHANNEL_1) != HAL_OK)
- {
- Error_Handler(__FILE__, __LINE__);
- }
- /* TIM12的TRGO用于触发DMAMUX的请求发生器 */
- sMasterConfig.MasterOutputTrigger = TIM_TRGO_OC1REF;
- sMasterConfig.MasterOutputTrigger2 = TIM_TRGO2_RESET;
- sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
- HAL_TIMEx_MasterConfigSynchronization(&htim, &sMasterConfig);
- }
- #endif
复制代码
这个函数支持的触发频率很宽,对于DAC856X来说,如果样本点设置为100个的话,此函数推荐的触发频率是100Hz到1MHz,具体可以支持到最高触发速度计算看本章4.7.7小节即可。
75.6.3 第3步:DMAMUX同步触发SPI DMA传输
DMA和DMAMUX的配置如下:
- /*
- *********************************************************************************************************
- * 函 数 名: bsp_spiDamStart
- * 功能说明: 启动SPI DMA传输
- * 形 参: _ulFreq 范围推荐100Hz-1MHz
- * 返 回 值: 无
- *********************************************************************************************************
- */
- void bsp_spiDamStart(uint32_t _ulFreq)
- {
- /* 设置模式,要切换到DMA CIRCULAR模式 */
- s_SpiDmaMode = 1;
- bsp_InitSPIParam(SPI_BAUDRATEPRESCALER_4, SPI_PHASE_2EDGE, SPI_POLARITY_LOW);
- /* 使能DMA时钟 */
- DMAx_CLK_ENABLE();
- /* SPI DMA发送配置 */
- hdma_tx.Instance = SPIx_TX_DMA_STREAM; /* 例化使用的DMA数据流 */
- hdma_tx.Init.FIFOMode = DMA_FIFOMODE_ENABLE; /* 使能FIFO */
- hdma_tx.Init.FIFOThreshold = DMA_FIFO_THRESHOLD_FULL; /* 用于设置阀值 */
- hdma_tx.Init.MemBurst = DMA_MBURST_SINGLE; /* 用于存储器突发 */
- hdma_tx.Init.PeriphBurst = DMA_PBURST_SINGLE; /* 用于外设突发 */
- hdma_tx.Init.Request = SPIx_TX_DMA_REQUEST; /* 请求类型 */
- hdma_tx.Init.Direction = DMA_MEMORY_TO_PERIPH; /* 传输方向是从存储器到外设 */
- hdma_tx.Init.PeriphInc = DMA_PINC_DISABLE; /* 外设地址自增禁止 */
- hdma_tx.Init.MemInc = DMA_MINC_ENABLE; /* 存储器地址自增使能 */
- hdma_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_WORD; /* 外设数据传输位宽选择字节,即8bit */
- hdma_tx.Init.MemDataAlignment = DMA_MDATAALIGN_WORD; /* 存储器数据传输位宽选择字节,即8bit */
- hdma_tx.Init.Mode = DMA_CIRCULAR; /* 正常模式 */
- hdma_tx.Init.Priority = DMA_PRIORITY_LOW; /* 优先级低 */
- /* 复位DMA */
- if(HAL_DMA_DeInit(&hdma_tx) != HAL_OK)
- {
- Error_Handler(__FILE__, __LINE__);
- }
- /* 初始化DMA */
- if(HAL_DMA_Init(&hdma_tx) != HAL_OK)
- {
- Error_Handler(__FILE__, __LINE__);
- }
- /* 关联DMA句柄到SPI */
- __HAL_LINKDMA(&hspi, hdmatx, hdma_tx);
- /* 关闭DMA发送中断 */
- HAL_NVIC_SetPriority(SPIx_DMA_TX_IRQn, 1, 0);
- HAL_NVIC_DisableIRQ(SPIx_DMA_TX_IRQn);
- /* 关闭SPI中断 */
- HAL_NVIC_SetPriority(SPIx_IRQn, 1, 0);
- HAL_NVIC_DisableIRQ(SPIx_IRQn);
- /* 同步触发配置 */
- dmamux_syncParams.EventEnable = ENABLE;
- dmamux_syncParams.SyncPolarity = HAL_DMAMUX_SYNC_RISING;
- dmamux_syncParams.RequestNumber = 1;
- dmamux_syncParams.SyncSignalID = HAL_DMAMUX1_SYNC_TIM12_TRGO;
- dmamux_syncParams.SyncEnable = ENABLE;
- HAL_DMAEx_ConfigMuxSync(&hdma_tx, &dmamux_syncParams);
- //LPTIM_Config(_ulFreq);
- TIM12_Config(_ulFreq);
- /* 启动DMA传输 */
- if(HAL_SPI_Transmit_DMA(&hspi, (uint8_t*)g_spiTxBuf, g_spiLen/4)!= HAL_OK)
- {
- Error_Handler(__FILE__, __LINE__);
- }
- }
复制代码
这段程序里面最关键的就是置红的部分。作用是配置DMAMUX的同步触发功能,触发周期由TIM12控制。
75.6.4 第4步:24bit数据的DMA传输解决办法
由于通用DMA1和DMA2仅支持8bit,16bit和32bit数据传输,我们这里要传输24bit数据,解决的关键就是配置DMA为传输宽度为32bit,并将传输的数据由24bit再补一个8bit的任意值组成32bit即可,实际的传输会由SPI完成。
- /*
- *********************************************************************************************************
- * 函 数 名: DAC8501_SetDacDataDMA
- * 功能说明: DAC8501数据发送,DMA方式
- * 形 参: _ch 1表示通道1输出
- * _pbufch1 通道1数据缓冲地址
- * _sizech1 通道1数据大小
- * _ulFreq 触发频率,推荐范围100Hz- 1MHz,注意这个参数是触发频率,并不是波形周期。
- * 这里触发一次,SPI DMA传输一次24bit数据。
- * 返 回 值: 无
- *********************************************************************************************************
- */
- void DAC8501_SetDacDataDMA(uint8_t _ch, uint16_t *_pbufch1, uint32_t _sizech1, uint32_t _ulFreq)
- {
- uint32_t i;
- uint32_t _cmd;
- g_spiLen = 0;
- switch (_ch)
- {
- /*
- DAC8501.pdf page 12 有24bit定义
- DB24:18 = xxxxx 保留
- DB17: PD1
- DB16: PD0
- DB15:0 16位数据
- 其中 PD1 PD0 决定4种工作模式
- 0 0 ---> 正常工作模式
- 0 1 ---> 输出接1K欧到GND
- 1 0 ---> 输出100K欧到GND
- 1 1 ---> 输出高阻
- */
- /* 通道1数据发送 */
- case 1:
- for(i = 0; i < _sizech1; i++)
- {
- /* 更新需要配置PD1和PD0,当前是选择的正常工作模式 */
- _cmd = (0 << 16) | (_pbufch1 << 0);
- g_spiTxBuf[g_spiLen++] = (uint8_t)(_cmd);
- g_spiTxBuf[g_spiLen++] = (uint8_t)(_cmd >> 8);
- g_spiTxBuf[g_spiLen++] = (uint8_t)(_cmd >> 16);
- g_spiTxBuf[g_spiLen++] = 0;
- }
- break;
- default:
- break;
- }
- bsp_spiDamStart(_ulFreq);
- }
复制代码
|