用单片机内部12bit DAC实现 20bit DAC效果

    以stm32f103为例,单片机内部DAC为12位,参考电压3.3V,则输出电压最小刻度为3.3/4095=0.8mV,已满足大部分应该需求,如需更高精度的模拟输出,一般会选择专用的DAC,但高精度的DAC价格都很贵,不差钱的氪金族可以提前退场了[狗头],那么下面就介绍一种低成本的解决方案,用单片机内部12位 DAC实现20位DAC的效果。
    先讲一下原理,PWM大家应该都很熟(能刷到这篇文章,相信大家都是有基础的),通常单片机PWM输出低电平为0,高电平为3.3V,调节不同的占空比,再对PWM信号进行简单的RC滤波,即可得到0V到3.3V之间的直流电压信号,具体信号幅值调整刻度和信号质量取决于PWM计数器值和滤波器效果,可以参考这篇文章   STM32学习之PWM DAC实验中的滤波电路部分元件参数的由来问题_正点原子pwm_dac_AI3-TMAC1的博客-CSDN博客

    那么,用PWM占空比的方式去控制DAC输出值的最后1bit,即可将DAC输出的最后1bit再次细分,若PWM计数器值为256,则DAC的输出最小刻度将会变为 0.8/256=0.003125mV,即达到了20位DAC的分辨率!!
    废话少说,上代码实测!手上正好有一块stm32f103的核心板,配置PA4为DAC输出,配置TIM2 CH1为PWM。用GXA3801高精度采集卡采集DAC输出信号。GXA3801是一款单通道数模混合高精度数据采集卡:ADC分辨率为32位,±20V输入范围,最高采样率38.4k,支持交流、直流、单端、差分、IEPE输入模式,所有模式输入阻抗均为1M欧;而数字部分则支持CAN、UART、PWM、IO等多种功能。哦,扯远了,回到正题....

 下图为实物接线,PA4引出经过RC滤波,通过GXA3801采集卡采集:

/* USER CODE BEGIN 0 */

#define PWM_DAC_MODE  1   // 0 普通模式, 1 PWM调整模式

int count = 1000;
uint16_t _outBit = 1000;
uint16_t _pulse  = 0;
uint16_t Sine12bit[32] = {
	2048	, 2460	, 2856	, 3218	, 3532	, 3786	, 3969	, 4072	,
	4093	, 4031	, 3887	, 3668	, 3382	, 3042	,2661	, 2255	, 
	1841	, 1435	, 1054	, 714	, 428	, 209	, 65	, 3		,
	24		, 127	, 310	, 564	, 878	, 1240	, 1636	, 2048
};

/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_DAC_Init();
  MX_TIM2_Init();
  /* USER CODE BEGIN 2 */
	HAL_DAC_Start(&hdac, DAC_CHANNEL_1);
	
	//普通DAC模式时不开启中断!!
	#if(PWM_DAC_MODE)
	HAL_TIM_IC_Start_IT(&htim2, TIM_CHANNEL_1);    //启动输入捕获
	HAL_TIM_Base_Start_IT(&htim2);
	#endif
	
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
	  HAL_Delay(1000);
	  //--------PWM DAC输出模式-------------
	  #if(PWM_DAC_MODE)
	  _pulse++;
	  if(_pulse == 256) 
	  {
		  _pulse = 0;
		  _outBit++;
	  }
	  TIM2->CCR1 = _pulse;
	  #endif
	  //--------PWM DAC输出模式-------------
	  
	  //--------普通DAC输出模式-------------
	  #if(!PWM_DAC_MODE)
	  HAL_DAC_SetValue(&hdac,DAC_CHANNEL_1,DAC_ALIGN_12B_R,_outBit++);
	  #endif
	  //--------普通DAC输出模式-------------
	  
	  if(_outBit == 2000) _outBit = 1000;
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}


//中断里输出DAC值
/**
  * @brief This function handles TIM2 global interrupt.
  */
void TIM2_IRQHandler(void)
{
  /* USER CODE BEGIN TIM2_IRQn 0 */
	uint16_t temp = 0;
	if (__HAL_TIM_GET_IT_SOURCE(&htim2, TIM_IT_CC1) != RESET)
    {
         __HAL_TIM_CLEAR_IT(&htim2, TIM_IT_CC1);
	}
	if (__HAL_TIM_GET_IT_SOURCE(&htim2, TIM_IT_UPDATE) != RESET)
    {
		__HAL_TIM_CLEAR_IT(&htim2, TIM_IT_UPDATE);
	}
	
	if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_SET)
	{
		HAL_DAC_SetValue(&hdac,DAC_CHANNEL_1,DAC_ALIGN_12B_R,_outBit);
	}
	else
	{
		HAL_DAC_SetValue(&hdac,DAC_CHANNEL_1,DAC_ALIGN_12B_R,_outBit-1);
	}

	return;
  /* USER CODE END TIM2_IRQn 0 */
  HAL_TIM_IRQHandler(&htim2);
  /* USER CODE BEGIN TIM2_IRQn 1 */

  /* USER CODE END TIM2_IRQn 1 */
}


    首先测试一下DAC的正常用法,直接输出值,每秒修改一次输出值,增加1bit,通过GXA3801采集到的数据如下图所示,垂直刻度为0.8mV/div,可以明显看到12位DAC的分辨率为0.8mV,输出为阶梯状。图中纵坐标为0.8mV/div。

    下面配置PWM中断,占空比上升沿和下降沿均产生中断,将DAC的输出放到中断函数中,若PWM计数值为2,则PWM输出占空比可以设置为0%、50%、100%,好,划重点了:在PWM上升沿输出的DAC值为x,在PWM下降沿输出DAC的值为x+1,那么经过滤波就可以得到三个直流电压,x、x+0.5、x+1,如下图所示。DAC的最小分辨刻度变为了0.4mV,即分辨率提高了1倍,实现了13位DAC效果!!图中纵坐标为0.8mV/div。

     更进一步,将PWM计数值改为4,则PWM输出占空比可以设置为0%、25%、50%、75%、100%,则DAC的最后1bit可再细分4次,最小刻度为0.2mV,即分辨率提高了2倍,实现了14位DAC效果!!图中纵坐标为0.8mV/div。

    那么干一票大的,将PWM计数值改为256,则可将DAC最后1bit细分256次,则最小刻度为0.8/256=0.003125mV,即可实现20位DAC的效果。采集信号如下图所示,由于飞线连接会引入工频干扰,采集信号末进行滤波,所以显示上已看不出阶梯状,整体上波形已相当平滑。图中纵坐标为0.8mV/div。

    当固定输出值,并对当前采集信号进行平均处理,可降低噪声和干扰的影响。开发板加飞线,先天不足,后天也只能尽量弥补...,还是无法准确显示出PWM计数增加1(幅值变动3.1uV)的变化,那么这里将PWM计数增加10,理论幅值应该增加31uV,如下图所示,当前电压值为 818.5642mV。  

    将PWM占空比输出值增加10,得到的直流电压值如下图所示,电压值为 818.6008mV,电压增加了36.6uV,比理论值偏高,误差原因为噪声、干扰以及参考电压并不是准确的3.3V等。

       当控制电压输出到uV级时需要考虑的因素就比较多了,比如温漂、参考源精度、噪声、INL、DNL等等。

    以上就是提高单片机内部12位DAC输出分辨率的方法,若有不妥之处或优化建议,欢迎大家指出。

工程下载: https://download.csdn.net/download/bingwueyi/87744279

  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
要在 MSP430 单片机实现三角波输出,可以使用定时器模块和 GPIO 模块。具体步骤如下: 1. 配置一个定时器为向上计数模式,并设置一个比较值和一个捕获值,比较值为定时器计数到多少时产生中断并反转输出口电平,捕获值为定时器计数到多少时重新从计数器的初始值开始计数。 2. 在定时器中断服务程序中,反转 GPIO 输出口电平,同时重新设置定时器计数器的初始值为捕获值,这样就可以实现定时器循环计数输出三角波的功能。 3. 调整比较值和捕获值的大小,可以控制三角波的频率和幅度。 下面是一个简单的 MSP430G2553 代码示例,实现了输出 1kHz 的三角波: ```c #include <msp430.h> #define TIMER_PERIOD 1000 #define TIMER_COMPARE (TIMER_PERIOD / 2) int main(void) { WDTCTL = WDTPW + WDTHOLD; P1DIR |= BIT2; TA0CCR0 = TIMER_PERIOD - 1; TA0CCTL0 = CCIE; TA0CCR1 = TIMER_COMPARE; TA0CCTL1 = OUTMOD_7; TA0CTL = TASSEL_2 + MC_1 + TACLR; __bis_SR_register(LPM0_bits + GIE); return 0; } #pragma vector=TIMER0_A0_VECTOR __interrupt void Timer_A (void) { TA0CCR1 = TA0CCR1 + 1; if (TA0CCR1 >= TIMER_PERIOD) { TA0CCR1 = 0; } } ``` 在这个例子中,定时器 TA0 被配置为 ACLK 时钟源,以向上计数模式工作。TA0CCR0 被设置为 1000,即定时器计数到 1000 时会产生中断。TA0CCR1 被初始化为 500,即定时器计数到 500 时会反转 P1.2 输出口电平。在定时器中断服务程序中,TA0CCR1 每次加 1,如果 TA0CCR1 大于等于 1000,就将 TA0CCR1 重置为 0。这样就可以实现循环计数输出三角波的功能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值