以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输出分辨率的方法,若有不妥之处或优化建议,欢迎大家指出。