STM32
直播中

学电超人

12年用户 756经验值
擅长:微处理器/微控制 微处理器/微控制 微处理器/微控制 微处理器/微控制 微处理器/微控制 微处理器/微控制 微处理器/微控制 微处理器/微控制 微处理器/微控制 微处理器/微控制 微处理器/微控制 微处理器/微控制 微处理器/微控制 微处理器/微控制 微处理器/微控制 微处理器/微控制
私信 关注
[问答]

如何对DAC的定时器触发和DMA进行配置呢

怎样去解决DAC输出阻抗的问题呢?

如何对DAC的定时器触发和DMA进行配置呢?

回帖(2)

杨秀英

2021-11-15 11:39:27
  DAC输出阻抗的问题:
  DAC集成了2个输出缓存,可以用来减少输出阻抗,无须外部运放即可直接驱动外部负载。每个DAC通道输出缓存可以通过设置DAC_CR寄存器的BOFFx位来使能或者关闭,如果带载能力还不行,后面就需要接一个电压跟随器,选择运放一定要选择电流大的型号。
  DAC使能输出缓冲后,DAC的最小输出电压为0.2V。最大电压为Vref±0.2(会造成削顶问题)。而未使能输出缓冲则可达到0V。
  
  输出缓冲和外接负载时的框图。
  
  DAC驱动实现
  我们这里使用了DAC1,驱动中还需要用到TIM6和DMA,方便我们配置不同的的频率,占空比和幅值。
  1. 引脚配置和DAC配置
  /*
  *********************************************************************************************************
  * 函 数 名: bsp_InitDAC1
  * 功能说明: 配置PA4/DAC1
  * 形 参: 无
  * 返 回 值: 无
  *********************************************************************************************************
  */
  void bsp_InitDAC1(void)
  {
  /* 配置GPIO */
  {
  GPIO_InitTypeDef GPIO_InitStructure;
  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
  /* 配置DAC引脚为模拟模式 PA4 / DAC_OUT1 */
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AN;
  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL ;
  GPIO_Init(GPIOA, &GPIO_InitStructure);
  }
  /* DAC通道1配置 */
  {
  DAC_InitTypeDef DAC_InitStructure;
  /* 使能DAC时钟 */
  RCC_APB1PeriphClockCmd(RCC_APB1Periph_DAC, ENABLE);
  DAC_InitStructure.DAC_Trigger = DAC_Trigger_None; /* 选择软件触发, 软件修改DAC数据寄存器 */
  DAC_InitStructure.DAC_WaveGeneration = DAC_WaveGeneration_None;
  DAC_InitStructure.DAC_LFSRUnmask_TriangleAmplitude = DAC_LFSRUnmask_Bit0;
  //DAC_InitStructure.DAC_OutputBuffer = DAC_OutputBuffer_Enable;
  DAC_InitStructure.DAC_OutputBuffer = DAC_OutputBuffer_Disable;
  DAC_Init(DAC_Channel_1, &DAC_InitStructure);
  DAC_Cmd(DAC_Channel_1, ENABLE);
  }
  }
  特别注意。程序中关闭了DAC输出缓冲,即DAC参数成员DAC_InitStructure.DAC_OutputBuffer。
  2. DAC的定时器触发和DMA配置:
  /*
  *********************************************************************************************************
  * 函 数 名: dac1_InitForDMA
  * 功能说明: 配置PA4 为DAC_OUT1, 启用DMA2
  * 形 参: _BufAddr : DMA数据缓冲区地址
  * _Count : 缓冲区样本个数
  * _DacFreq : DAC样本更新频率
  * 返 回 值: 无
  *********************************************************************************************************
  */
  void dac1_InitForDMA(uint32_t _BufAddr, uint32_t _Count, uint32_t _DacFreq)
  {
  uint16_t usPeriod;
  uint16_t usPrescaler;
  uint32_t uiTIMxCLK;
  TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
  DMA_Cmd(DMA1_Stream5, DISABLE);
  DAC_DMACmd(DAC_Channel_1, DISABLE);
  TIM_Cmd(TIM6, DISABLE);
  /* TIM6配置 */
  {
  RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM6, ENABLE);
  uiTIMxCLK = SystemCoreClock / 2;
  if (_DacFreq 《 100)
  {
  usPrescaler = 10000 - 1; /* 分频比 = 10000 */
  usPeriod = (uiTIMxCLK / 10000) / _DacFreq - 1; /* 自动重装的值 */
  }
  else if (_DacFreq 《 3000)
  {
  usPrescaler = 100 - 1; /* 分频比 = 100 */
  usPeriod = (uiTIMxCLK / 100) / _DacFreq - 1; /* 自动重装的值 */
  }
  else /* 大于4K的频率,无需分频 */
  {
  usPrescaler = 0; /* 分频比 = 1 */
  usPeriod = uiTIMxCLK / _DacFreq - 1; /* 自动重装的值 */
  }
  TIM_TimeBaseStructure.TIM_Period = usPeriod;
  TIM_TimeBaseStructure.TIM_Prescaler = usPrescaler;
  TIM_TimeBaseStructure.TIM_ClockDivision = 0;
  TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
  TIM_TimeBaseStructure.TIM_RepetitionCounter = 0x0000; /* TIM1 和 TIM8 必须设置 */
  TIM_TimeBaseInit(TIM6, &TIM_TimeBaseStructure);
  /* 选择TIM6做DAC的触发时钟 */
  TIM_SelectOutputTrigger(TIM6, TIM_TRGOSource_Update);
  }
  /* DAC通道1配置 */
  {
  DAC_InitTypeDef DAC_InitStructure;
  /* 使能DAC时钟 */
  RCC_APB1PeriphClockCmd(RCC_APB1Periph_DAC, ENABLE);
  DAC_InitStructure.DAC_Trigger = DAC_Trigger_T6_TRGO;
  DAC_InitStructure.DAC_WaveGeneration = DAC_WaveGeneration_None;
  DAC_InitStructure.DAC_LFSRUnmask_TriangleAmplitude = DAC_LFSRUnmask_Bit0;
  //DAC_InitStructure.DAC_OutputBuffer = DAC_OutputBuffer_Enable;
  DAC_InitStructure.DAC_OutputBuffer = DAC_OutputBuffer_Disable;
  DAC_Init(DAC_Channel_1, &DAC_InitStructure);
  DAC_Cmd(DAC_Channel_1, ENABLE);
  }
  /* DMA1_Stream5配置 */
  {
  DMA_InitTypeDef DMA_InitStructure;
  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1, ENABLE);
  /* 配置DMA1 Stream 5 channel 7用于DAC1 */
  DMA_InitStructure.DMA_Channel = DMA_Channel_7;
  DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&DAC-》DHR12R1;
  DMA_InitStructure.DMA_Memory0BaseAddr = _BufAddr;
  DMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToPeripheral;
  DMA_InitStructure.DMA_BufferSize = _Count;
  DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
  DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
  DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
  DMA_InitStructure.DMA_MemoryDataSize = DMA_PeripheralDataSize_HalfWord;
  DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; //循环模式
  DMA_InitStructure.DMA_Priority = DMA_Priority_High;
  DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;
  DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull;
  DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;
  DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
  DMA_Init(DMA1_Stream5, &DMA_InitStructure);
  DMA_Cmd(DMA1_Stream5, ENABLE);
  /* 使能DAC通道1的DMA */
  DAC_DMACmd(DAC_Channel_1, ENABLE);
  }
  /* 使能定时器 */
  TIM_Cmd(TIM6, ENABLE);
  }
  通过这个函数可以方便的计算DAC的输出波形频率。 计算方法如下:
  输出波形频率 = 配置的定时器触发频率 / DMA的缓冲个数 。
  其中,DMA缓冲数据的个数就是输出波形一个周期的采样点数。程序中统一将其配置为128个点代表一个周期的波形。实际应用中,配置的点数不要太少, 否则波形不够漂亮。
  比如,我们需要输出 10KHz的波形,这个函数的配置就是:
  dac1_InitForDMA((uint32_t)&g_Wave1,128, 10000 * 128)
  数组g_Wave1里面是128个波形采样点。通过修改触发频率来实现波形频率。
  关于这个驱动代码,要注意TIM6的配置。F4的定时器从TIM1到TIM14的主频如下:
  /*
  ********************************************************************************
  system_stm32f4xx.c 文件中 voidSetSysClock(void) 函数对时钟的配置如下:
  HCLK = SYSCLK / 1 (AHB1Periph)
  PCLK2 = HCLK / 2 (APB2Periph)
  PCLK1 = HCLK / 4 (APB1Periph)
  因为APB1 prescaler != 1, 所以 APB1上的TIMxCLK = PCLK1 x 2 = SystemCoreClock/ 2;
  因为APB2 prescaler != 1, 所以 APB2上的TIMxCLK = PCLK2 x 2 =SystemCoreClock;
  APB1 定时器有 TIM2, TIM3,TIM4, TIM5, TIM6, TIM7, TIM12, TIM13, TIM14
  APB2 定时器有 TIM1, TIM8,TIM9, TIM10, TIM11
  TIM 更新周期是 = TIMCLK / (TIM_Period + 1)/(TIM_Prescaler+ 1)
  ********************************************************************************
  */
  由此可知,TIM6的主频是SystemCoreClock / 2。当主频是168MHz时,TIM6的时钟就是84MHz,TIM6更新周期 = TIM6CLK / (TIM_Period + 1)/(TIM_Prescaler+ 1),其中
  TIM_Period就是定时器结构体成员TIM_TimeBaseStructure.TIM_Period。
  TIM_Prescaler就是定时器结构体成员TIM_TimeBaseStructure.TIM_Prescaler。
  另外还有非常重要的一点,TIM6是16位定时器,这两个参范围是0-65535,切不要超过65535。正是因为这个原因,程序中对不同的输出频率做了范围区分。
  3. 正弦波输出配置:
  /*
  *********************************************************************************************************
  * 函 数 名: dac1_SetSinWave
  * 功能说明: DAC1输出正弦波
  * 形 参: _vpp : 幅度 0-4095;
  * _freq : 频率
  * 返 回 值: 无
  *********************************************************************************************************
  */
  void dac1_SetSinWave(uint16_t _vpp, uint32_t _freq)
  {
  uint32_t i;
  uint32_t dac;
  TIM_Cmd(TIM6, DISABLE);
  /* 调整正弦波幅度 */
  for (i = 0; i 《 128; i++)
  {
  dac = (g_SineWave128[i] * _vpp) / 4095;
  if (dac 》 4095)
  {
  dac = 4095;
  }
  g_Wave1[i] = dac;
  }
  dac1_InitForDMA((uint32_t)&g_Wave1, 128, _freq * 128);
  }
  正弦波输出128个采样点代表一个周期,同时程序里面增加了一个幅值设置功能,范围0到4095。实际DAC输出的波形频率由前面第2步函数 dac1_InitForDMA实现。比如我们要实现频率10KHz,幅值4095正弦波,那么配置就是:dac1_SetSinWave(4095, 10000)。
  生成正弦波数据表
  要输出正弦波,实质是要控制 DAC 以 v=sin(t)的正弦函数关系输出电压,其中 v 为电压输出,t 为时间。 而由于模拟信号连续而数字信号是离散的,所以使用 DAC 产生正弦波时,只能按一定时间间隔输出正弦曲线上的点,在该时间段内输出相同的电压值,若缩短时间间隔,提高单个周期内的输出点数,可以得到逼近连续正弦波的图形,见下图 37-3,若在外部电路加上适当的电容滤波,可得到更完美的图形。
  
  由于正弦曲线是周期函数,所以只需要得到单个周期内的数据后按周期重复即可,而单个周期内取样输出的点数又是有限的,所以为了得到呈 v=sin(t)函数关系电压值的数据通常不会实时计算获取,而是预先计算好函数单个周期内的电压数据表,并且转化成以 DAC 寄存器表示的值。 如 sin 函数值的范围为[-1: +1],而 STM32 的 DAC 输出电压范围为[0~3.3]V,按 12 位 DAC 分辨率表示的方法,可写入寄存器的最大值为 212 = 4096,即范围为[0:4096]。所以,实际输出时,会进行如下处理:
  抬升 sin 函数的输出为正值:v = sin(t)+1 ,此时,v 的输出范围为[0:2];
  扩展输出至 DAC 的全电压范围: v = 3.3*(sin(t)+1)/2 ,此时,v 的输出范围为[0:3.3], 正是 DAC 的电压输出范围,扩展至全电压范围可以充分利用 DAC 的分辨率;
  把电压值以 DAC 寄存器的形式表示:Reg_val = 212/3.3 * v = 211*(sin(t)+1),此时,存储到 DAC 寄存器的值范围为[0:4096];
  实践证明,在 sin(t)的单个周期内,取 32 个点进行电压输出已经能较好地还原正弦波形,所以在 t∈[0:2π]区间内等间距根据上述 Reg_val 公式运算得到 32 个寄存器值,即可得到正弦波表;
  控制 DAC 输出时,每隔一段相同的时间从上述正弦波表中取出一个新数据进行输出,即可输出正弦波。改变间隔时间的单位长度,可以改变正弦波曲线的周期。
  生成的正弦波数据表:
  /* 正弦波数据,12bit,1个周期128个点, 0-4095之间变化 */
  const uint16_t g_SineWave128[] = {
  2047 , 2147 , 2248 , 2347 , 2446 , 2544 , 2641 , 2737 ,
  2830 , 2922 , 3012 , 3099 , 3184 , 3266 , 3346 , 3422 ,
  3494 , 3564 , 3629 , 3691 , 3749 , 3803 , 3852 , 3897 ,
  3938 , 3974 , 4006 , 4033 , 4055 , 4072 , 4084 , 4092 ,
  4094 , 4092 , 4084 , 4072 , 4055 , 4033 , 4006 , 3974 ,
  3938 , 3897 , 3852 , 3803 , 3749 , 3691 , 3629 , 3564 ,
  3494 , 3422 , 3346 , 3266 , 3184 , 3099 , 3012 , 2922 ,
  2830 , 2737 , 2641 , 2544 , 2446 , 2347 , 2248 , 2147 ,
  2047 , 1947 , 1846 , 1747 , 1648 , 1550 , 1453 , 1357 ,
  1264 , 1172 , 1082 , 995 , 910 , 828 , 748 , 672 ,
  600 , 530 , 465 , 403 , 345 , 291 , 242 , 197 ,
  156 , 120 , 88 , 61 , 39 , 22 , 10 , 2 ,
  0 , 2 , 10 , 22 , 39 , 61 , 88 , 120 ,
  156 , 197 , 242 , 291 , 345 , 403 , 465 , 530 ,
  600 , 672 , 748 , 828 , 910 , 995 , 1082 , 1172 ,
  1264 , 1357 , 1453 , 1550 , 1648 , 1747 , 1846 , 1947
  };
  
举报

李国东

2021-11-15 11:39:38
4. 方波输出配置:
  /*
  *********************************************************************************************************
  * 函 数 名: dac1_SetRectWave
  * 功能说明: DAC1输出方波
  * 形 参: _low : 低电平时DAC,
  * _high : 高电平时DAC
  * _freq : 频率 Hz
  * _duty : 占空比 2% - 98%, 调节步数 1%
  * 返 回 值: 无
  *********************************************************************************************************
  */
  void dac1_SetRectWave(uint16_t _low, uint16_t _high, uint32_t _freq, uint16_t _duty)
  {
  uint16_t i;
  TIM_Cmd(TIM6, DISABLE);
  for (i = 0; i 《 (_duty * 128) / 100; i++)
  {
  g_Wave1[i] = _high;
  }
  for (; i 《 128; i++)
  {
  g_Wave1[i] = _low;
  }
  dac1_InitForDMA((uint32_t)&g_Wave1, 128, _freq * 128);
  }
  方波也是输出128个采样点代表一个周期,同时支持幅值和占空比的配置,其中占空比可以配置2%到98%,直接填数值2到98就可以了。实际DAC输出的波形频率由前面第2步函数dac1_InitForDMA实现。比如我们要实现频率10KHz,幅值4095,占空比50%的方波,那么配置就是:dac1_SetRectWave(0, 4095, 10000, 50)。
  5. 三角波输出配置:
  /*
  *********************************************************************************************************
  * 函 数 名: dac1_SetTriWave
  * 功能说明: DAC1输出三角波
  * 形 参: _low : 低电平时DAC,
  * _high : 高电平时DAC
  * _freq : 频率 Hz
  * _duty : 占空比
  * 返 回 值: 无
  *********************************************************************************************************
  */
  void dac1_SetTriWave(uint16_t _low, uint16_t _high, uint32_t _freq, uint16_t _duty)
  {
  uint32_t i;
  uint16_t dac;
  uint16_t m;
  TIM_Cmd(TIM6, DISABLE);
  /* 构造三角波数组,128个样本,从 _low 到 _high */
  m = (_duty * 128) / 100;
  if (m == 0)
  {
  m = 1;
  }
  if (m 》 127)
  {
  m = 127;
  }
  for (i = 0; i 《 m; i++)
  {
  dac = _low + ((_high - _low) * i) / m;
  g_Wave1[i] = dac;
  }
  for (; i 《 128; i++)
  {
  dac = _high - ((_high - _low) * (i - m)) / (128 - m);
  g_Wave1[i] = dac;
  }
  dac1_InitForDMA((uint32_t)&g_Wave1, 128, _freq * 128);
  }
  三角波也是输出128个采样点代表一个周期,同时支持幅值和占空比的配置,其中占空比可以配置0%到100%,不过程序中对0%和100%做了一个特殊处理。实际DAC输出的波形频率由前面第2步函数dac1_InitForDMA实现。比如我们要实现频率10KHz,幅值4095,占空比50%的三角波,那么配置就是:dac1_SetTriWave (0, 4095, 10000, 50)。
举报

更多回帖

发帖
×
20
完善资料,
赚取积分