STM32学习笔记详解-ADC&DAC

针对STM32F103RC学习过程中遇到的较为复杂、难以理解的地方,此处对其进行详细分析,以避免学习笔记中容易出错、混淆、看不懂的地方

基于正点原子ALIENTEKmini版,由于其手册对于部分模块的讲解有些模糊,此处记录一些个人理解,便于日后重温

概念

模拟信号:

在时间上连续,在数值上也连续的信号,其伴随的信息不仅与数值有关,也和时间有关

数字信号:

在时间上离散,在数值上只能取得几个固定值,其伴随的信息不仅与数值有关,也和时间有关

相比于模拟信号,数字信号不仅在时间上采样,在数值上也采样

对于单片机的对外交互,模拟信号可理解为电压信号,数字信号理解为每一个时刻、与之对应的寄存器静态数据

 ADC模拟-数字转换:

将端口的输入模拟信号,转换为数字信号,便于计算、显示。

可用于测量电压值、传感器数据采集与处理等

转换通道:总20,按照规律顺序进行转换称为规则16,插入打断规则组进行转换称为注入组4

启动方式:规则组既可以中断触发也可以软件触发,注入组仅中断触发

ADCx_INn为被检测通道n

ADC输入模拟电压Ur=(ADC_DR/2^ADC位数N)*标准电压Vcc

DAC数字-模拟转换:

根据数字信号,将数字信号转换为模拟信号,从端口输出,便于外设控制

可用于波形生成,产生目标电平等

转换通道:仅2

学习目的

了解ADC和DAC原理,学习如何使用ADC和DAC达到具体目的 

 接下来以一个小实验为例,实验目标如下:

    1、温度传感器获取温度信息,并在LCD显示 

    2、DAC产生数字信号,该数字信号由按键WKUP与KEY0控制,WKUP增大电压值,KEY0减小电压值,在LCD显示输出电压

    3、ADC接受上述数字信号,并在LCD显示测量电压

    4、usmar串口调试工具可以直接通过串口助手,向DAC设定输出电压值

分析过程

原理分析

 理清数字信号、模拟信号在本实验中的关系,ADC、DAC在各个环节的具体作用,如图所示

 

需要理解的东西:

    1)信号的作用域:

            模拟信号主要用于单片机与外界的交互,例如获取传感器电压、发送信号、控制外设等;

            数字信号主要用于单片机内CPU的计算,例如处理输入数据,设计输出数据等;

    2)检测信号:

            检测即获取传感器数据,由CPU处理,将数字信号可视化的过程。

            需要用到ADC模块将模拟信号转化为数字信号,交由单片机处理即可,如有需要则可以再将数据可视化(例如串口发送、LED、LCD等)

    3)控制外设:

            控制即对于已知原理的外设,给予自定义信号,保证外设按照目标执行。

            已知需要的电压值,根据模拟信号近似等于数字信号的原则(位数越大精度越高),根据DAC内数字信号与寄存器数据的关系,由程序设计出相应的寄存器数据,再通过DAC转化为需要的模拟信号进行外设控制

    4)实验原理

            本实验设计理想模拟信号的产生,和模拟输入的检测,其中DAC负责产生模拟信号,ADC负责检测信号,两者对应的寄存器数据,均可以由CPU计算得到一个响应的电压值,理论上只要精度够高,两者应该一致,但是由于经历了数字信号转换为模拟信号的过程,会带来微小误差

    5)误差:

            无论是数-模-数,还是模-数-模,经历了ADC/DAC后的信号复现均存在误差,这是由于采样周期和转换位数决定,转换位数越高误差越小,采样周期太长在时间上会有信号滞后失真,采样周期也不能太短,必须等待CPU处理完上一个数据才能继续向CPU传值

实验内容:

    1)温度传感:

            对于外界输入模拟信号进行处理,得到寄存器数据(数字信号),根据传感器物理量纲关系,计算得到量纲数值

    2)信号复现:

            由单片机设置/设计一个寄存器数据(数字信号),由DAC转换为模拟信号至端口,再由端口发送至ADC接受,将其再转换为数字信号。此过程中,最初的DAC寄存器数据(原始数字信号),与ADC寄存器数据(接受数字信号)均可换算为电压数值。因此可对比两者,发现两者数值相近

    3)数字信号设计:

            按键(外设IO口)、串口助手(串口)改变寄存器数据,便可以以此改变相应的数字信号,也就改变了对应的模拟信号。这样就可以实现软件程序设计/设置向外输出的模拟信号电压值

DAC配置

待检测端口时钟使能、模拟输入配置

外设时钟使能寄存器RCC_APB2ENR设置ADC时钟使能、接口复位

外设复位寄存器RCC_APB2RSTR让ADC复位,然后恢复该位(否则一直复位)

时钟配置寄存器RCC->CFGR设置ADC预分频,为保证ADC准确性,频率不可超过14MHz

控制寄存器ADC_CR,设置触发方式、对齐方式

规则序列寄存器ADC_SQR设置转换规则中的序列数

采样时间寄存器ADC_SMPR设置通道的采样时间

控制寄存器ADC_CR,开启AD转换,复位校准及AD校准,等待校准完成硬件复位

规则序列寄存器ADC_SQR匹配规则序列和通道值

状态寄存器ADC_SR判断转换是否结束

规则数据寄存器ADC_DR获得转换结果

ADC配置

使能待输入端口使能,模拟输入配置

外设时钟使能寄存器RCC_APB2ENR设置DAC时钟使能

控制寄存器DAC_CR,设置触发方式、波形产生、输出缓存等,使能DAC

通道n的m位右对齐数据保持寄存器DAC_DHRmRn清空DAC通道,向其写入DAC值

通道n对应IO口模拟电压

DAC输出模拟电压Ur=(DAC_DHRmRn/2^DAC位数m)*标准电压Vcc

代码编写

DAC部分

#include <dac.h>
#include <sys.h>

//DAC初始化
void DAC_Init(void)
{
	RCC->APB2ENR|=1<<2;     //使能PA时钟
	RCC->APB1ENR|=1<<29;    //使能DAC时钟
	GPIOA->CRL&=0XFFF0FFFF;
	GPIOA->CRL|=0X00000000; //PA4模拟输入
	
	DAC->CR|=3<<0;          //不使用屏蔽发生、触发功能、DMA及输出缓存,使能DAC
	DAC->DHR12R1=0;
}

//设置通道1输出电压
//mvol 0~3300mV
void DAC_Set_Vol(u16 mvol)
{
	float temp=mvol;
	temp=temp*4096/3300;
	DAC->DHR12R1=temp;
}

ADC部分

#include <adc.h>
#include <sys.h>
#include <delay.h>

//初始化ADC
//仅开启规则通道1
void ADC_Init(void)
{
	RCC->APB2ENR|=1<<2;       //被检测IO口时钟使能
	GPIOA->CRL&=0XFFFFFF0F;   //IO口模拟输入
	RCC->APB2ENR|=1<<9;       //ADC时钟使能
	RCC->APB2RSTR|=1<<9;      //ADC接口复位
	RCC->APB2RSTR&=~(1<<9);   //复位一次后手动恢复,避免一直复位
	RCC->CFGR&=0XFFFF3FFF;    //预分频,不超过14MHz
	RCC->CFGR|=0X00008000;
	
	ADC1->CR2|=0X1E0000;      //软件触发SWSTART
	ADC1->CR2|=1<<23;         //开启温度传感器
    ADC1->SQR1&=0X0FFFFF;     //1个转换在规则序列中(只转换规则序列1)
	
	ADC1->SMPR2&=~(7<<3);
	ADC1->SMPR2|=7<<3;        //通道1,ADC采样时间(越大输出越准确)
	ADC1->SMPR1&=~(7<<18);
	ADC1->SMPR1|=7<<18;       //通道16设置采样时间
	
	ADC1->CR2|=1<<0;          //开启AD转换
	ADC1->CR2|=1<<3;          //复位校准
	while(ADC1->CR2&1<<3);    //等待复位校准完成
	ADC1->CR2|=1<<2;          //AD校准
	while(ADC1->CR2&1<<2);    //等待AD校准完成
}

//获取ADC值
//ch通道值 0~16
u16 Get_ADC(u8 ch)
{
	ADC1->SQR3&=0XFFFFFFE0;
	ADC1->SQR3|=ch<<0;        //匹配规则序列和通道
	ADC1->CR2|=1<<22;         //启用规则转换通道
	while((ADC1->SR&1<<1)==0);//等待转换结束
	return ADC1->DR;
}

/*
多次转换取平均,提高精度
ch 通道值
times 获取次数
此函数应添加到usmart_config.c的func_table内,允许串口助手调用
*/
u16 Get_ADC_Average(u8 ch,u8 times)    
{
	u32 temp_val=0;
	u8 t;
	for(t=0;t<times;t++)
	{
		temp_val+=Get_ADC(ch);
		delay_ms(5);
	}
	return temp_val/times;
}

 主函数部分

#include <sys.h>
#include <delay.h>
#include <usart.h>
#include <led.h>
#include <lcd.h>
#include <adc.h>
#include <dac.h>
#include <key.h>
#include <usmart.h>

int main()
{
	u16 adc1;
	u16 adc16;
	u16 dac1;
	float temp;
	float temperate;
	Stm32_Clock_Init(9);
	delay_init(72);
	uart_init(72,9600);
	LED_Init();
	KEY_Init();
	LCD_Init();
	ADC_Init();
	DAC_Init();
	usmart_dev.init(72);
	
	LCD_Clear(WHITE);
	POINT_COLOR=BLACK;
	
	//固定文字部分显示
	LCD_ShowString(100,240,200,24,24,"WKUP+ KEY0-");
	LCD_ShowString(100,270,200,24,24,"DAC VAL:");
	LCD_ShowString(100,300,200,24,24,"DAC VOL:0.000V");
	LCD_ShowString(100,330,250,24,24,"ADC VOL:0.000V");
	LCD_ShowString(100,360,250,24,24,"TEMPERATE:00.00C");
	
	while(1)
	{
		
		switch(KEY_Scan(0))
		{
			case WKUP_PRES:
				if(dac1<4000)dac1+=200;
				DAC->DHR12R1=dac1;
				break;
			case KEY0_PRES:
				if(dac1>200)dac1-=200;
				else dac1=0;
				DAC->DHR12R1=dac1;
				break;
			default:break;
		}
		
		//DAC电压显示
		dac1=DAC->DHR12R1;
		LCD_ShowxNum(196,270,dac1,4,24,0);     //得到dac值
		temp=(float)dac1*(3.3/4096);
		LCD_ShowxNum(196,300,(u16)temp,1,24,0);//展示整数部分
		temp-=(u16)temp;
		temp*=1000;
		LCD_ShowxNum(220,300,temp,3,24,0x80);  //展示3位小数
		
		//ADC电压显示
		adc1=Get_ADC_Average(1,10);		
		temp=(float)adc1*(3.3/4096);           //得到电压值(浮点型),但是LCD无法直接展示小数
		adc1=temp;                          
		LCD_ShowxNum(196,330,adc1,1,24,0);     //展示整数部分
		temp-=adc1;
		temp*=1000;
		LCD_ShowxNum(220,330,temp,3,24,0x80);  //展示3位小数
				
		//温度计算显示
		adc16=Get_ADC_Average(16,10);
		temp=(float)adc16*(3.3/4096);
		temperate=(1.43-temp)/0.0043+25;      
		LCD_ShowxNum(220,360,(u8)temperate,2,24,0);
		temperate-=(u8)temperate;
		temperate*=100;
		LCD_ShowxNum(256,360,temperate,2,24,0);
		
		LED0=0;
		
		delay_ms(10);
	}
}

实验结果

首先注意,此实验硬件配置中,ADC1_IN1与PA1接通,DAC_OUT1与PA4接通,PA1与PA4接通

 DAC产生的电压,与ADC测得的电压,数值上几乎保持相等;如图:

                          

 

按下KWUP则电压值对应增加约0.161V,相应的,按下KEY0则电压值对应减小约0.161V

串口调试助手调用电压设置函数并传入数据,发送给单片机,观察电压值变为设定的电压,如图

                          

 

可以发现此处出现了一个小问题,温度小数位显示有缺失,这是由于此时小数首位为0,由于我们处理小数显示时,只是将小数部分倍乘100,并未考虑首位为零的情况,例如温度为30.04时,小数乘100为4,整数部分为30,因此显示为30.  4。

这个问题可通过在显示时加入一个判断,对数据进一步处理即可,不是本章重点,此处不予细究

对于程序中的按键、LED、LCD、串口等配置,见        STM32学习笔记(未更新)

  • 13
    点赞
  • 42
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值