实验目的
当我们看到ADC的时候是不是想起来躲在辅助后面输出的ADC,但是此ADC非彼ADC。那么我们来看看32的ADC吧。
STM32集成有ADC模数转换器,本章学习利用其采集电压,通过串口打印出来,数据手册请参看第11章。
实验简介
ADC(Analog to Digital Converter),模/数转换器。在模拟信号需要以数字形式处理,存储或传输时,模/数转换器几乎必不可少。
STM32在片上集成的ADC外设非常强大。在STM32F103xC,STM32F103xD和STM32F103xE增强型产品,内嵌3个12位的ADC,每个ADC共用多达21个外部通道,可以实现单次或多次扫描转换。星光STM32开发板用的是STM32F103VET6,属于增强型的CPU,它有18个通道,可测量16个外部和2个内部信号源。各通道的A/D转换可以单次,连续,扫描或间断模式执行。ADC的结果可以左对齐或右对齐方式存储在16位数据寄存器中。
模拟看门狗特性允许应用程序检测输入电压是否超出用户定义的高/低阈值。
STM32的ADC最大的转换速率为1MHz,也就是转换时间为1us(在 ADCCLK = 14M,采样周期为1.5个ADC时钟下得到),不要让ADC的时钟超过14M,否则将导致结果准确度下降。
STM32将ADC的转换分为2个通道组:规则通道组和注入通道组。规则通道相当于你正常运行的程序,而注入通道呢,就相当于中断。在你程序正常执行的时候,中断是可以打断你的执行的,同这个类似,注入通道的转换可以打断规则通道的转换,在注入通道被转换完成之后,规则通道才得以继续转换。
通过一个形象的例子可以说明:假如你在家里的院子内放了5个温度探头,室内放了3个温度探头;你需要时刻监视室外温度即可,但偶尔你想看看室内的温度;因此你可以使用规则通道组循环扫描室外的5个探头并显示AD转换结果,当你想看室内的温度时,通过一个按钮启动注入转换组(3个室内探头)并暂时显示室内温度,当你放开这个按钮后,系统又会回到规则通道组继续检测室外温度。从系统设计上,测量并显示室内温度的过程中断了测量并显示室外温度的过程,但程序设计上可以在初始化阶段分别设置好不同的转换组,系统运行中不必再变更循环转换的配置,从而达到两个任务互不干扰和快速切换的结果。可以设想一下,如果没有规则和注入组的规划,当你按下按钮后,需要从新配置AD循环扫描通道,然后在释放按钮后需再次配置AD循环扫描的通道。
从图中可知,它的参考电压负极是要接地的,即VREF- = 0V。而参考电压正常的范围为 2.4 <= VREF+ <=3.6V,所以STM32的ADC是不能直接测量负电压的,而且其输入的电压信号的范围为: VREF- <= VIN <= VREF+.当需要测量负电压或测量的电压信号超出范围时,要先经过运算电路进行平移或利用电阻分压。
电路设计
星光STM32F103开发板板载1个可调电阻,连接到STM32的PA7引脚,对应ADC的IN7,实现AD转换进行电压采集,电路如下图所示:
代码
main.c
#include "MyIncludes.h"
char buff[100];
//用来存放电位器电压值的数组
uint16_t Vol;
//电位器电压的变量
u16 sys_cnt = 0;
void systick_isr(void)
{
if(sys_cnt < 1000)
{
sys_cnt++;
}
else
{
sys_cnt = 0;
HAL_GPIO_TogglePin(GPIOC,GPIO_PIN_4|GPIO_PIN_5);
}
}
int main(void)
{
System_Init();
LED_Init();
SysTick_Init(systick_isr);
USART1_Init(115200,NULL,NULL);
STM32_ADC_Init(ADC1,ADC_CHANNEL_7,NULL);
//ADC初始化(ADC外设1,ADC输入通道7,中断处理回调函数NULL)
delay_ms(1000);
while(1)
{
Vol = Vol_Sample();
//电压采样函数
sprintf(buff,"VOL: %d.%d%dV\n",Vol/1000,Vol%1000/100,Vol%100/10);
printf(buff);
delay_ms(1000);
}
}
adc.h
#ifndef __ADC_H_
#define __ADC_H_
#include "stm32f1xx.h"
#include "stm32_types.h"
#include "stm32f1xx_hal.h"
typedef struct
{
void(*isr_op)(void);
//ADC中断处理
}_ADC_ISR_;
extern ADC_HandleTypeDef AdcHandle;
void STM32_ADC_Init(ADC_TypeDef *ADCx,uint32_t Channel,void(*ISR)(void));
//ADC初始化 (ADC外设,ADC输入通道,中断处理回调指针)
uint32_t Vol_Sample(void);
//电压采样
#endif
adc.c
#include "adc.h"
ADC_HandleTypeDef AdcHandle;
//ADC句柄结构变量声明
DMA_HandleTypeDef hdma_adc;
//DMA句柄结构变量声明
_ADC_ISR_ adc_isr;
//ADC中断结构变量声明
#ifdef ADC_DMA_ENABLE
//使能DMA;
uint32_t aADCxConvertedValues;
#endif
//在HAL_ADC_Init中调用
void HAL_ADC_MspInit(ADC_HandleTypeDef *hadc)
{
GPIO_InitTypeDef GPIO_InitStruct;
//GPIO初始化结构变量声明
RCC_PeriphCLKInitTypeDef PeriphClkInit;
//RCC扩展时钟结构变量声明
PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_ADC;
//要配置的扩展时钟 ADC外围时钟
PeriphClkInit.AdcClockSelection = RCC_ADCPCLK2_DIV6;
//ADC时钟源,可以是预分频器的值 PCLK(IO接口时钟)2/6
HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit);
//RCC外围时钟源配置
__ADC1_CLK_ENABLE();
//使能ADC1时钟
__GPIOA_CLK_ENABLE();
//使能GPIOA时钟
GPIO_InitStruct.Pin = GPIO_PIN_7;
GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
//模拟输入模式
HAL_GPIO_Init(GPIOA,&GPIO_InitStruct);
//GPIO初始化
#ifdef ADC_DMA_ENABLE
__HAL_RCC_DMA1_CLK_ENABLE();
//使能DMA1时钟
hdma_adc.Instance = DMA1_Channel1;
//寄存器基址
hdma_adc.Init.Direction = DMA_PERIPH_TO_MEMORY;
//外设到内存方向
hana_adc.Init.PeriphInc = DMA_PINC_DISABLE;
//外围增量模式禁用
hdma_adc.Init.MemInc = DMA_MINC_ENABLE;
//内存增量模式启用
hdma_adc.Init.PeriphDataAlignment = DMA_PDATAALING_HALFWORD;
//外围数据对其 半字
hdma_adc.Init.MemDataAlignment = DMA_PDATAALINg_HALFWORD;
//外围数据对其 半字
hdma_adc.Init.Mode = DMA_CIRCULAR;
//圆形模式
hdma_adc.Init.Priority = DMA_PRIORITY_HIGH;
// 优先级 高
HAL_DMA_Init(&hdma_adc);
//DMA初始化
__HAL_LINKDMA(hadc,DMA_Handle,hdma_adc);
//将初始化的DMA句柄与ADC句柄关联
HAL_NVIC_SetPriority(DMA1_Channel1_IRQn,0,0);
//设置优先级
HAL_NVIC_EnableIRQ(DMA1_Channel1_IRQn)
//设置中断源
#endif
}
void STM32_ADC_Init(ADC_TypeDef *ADCx,uint32_t Channel,void (*ISR)(void))
{
ADC_ChannelConfTypeDef sConfig;
//ADC通道结构变量声明
adc_isr.isr_op = ISR;
//挂载中断处理函数
//配置ADC外设
AdcHandle.Instance = ADCx;
//寄存器基址 ADC1
AdcHandle.Init.ScanConvMode = ADC_SCAN_DISABLE;
//禁止扫描模式
AdcHandle.Init.ContinuousConvMode = ENABLE;
//使能连续转换
AdcHandle.Init.DiscontinuousConvMode = DISABLE;
//禁止常规通道的不连续采样模式
//AdcHandle.Init.NbrOfDiscConversion = 0;
//不连续采样模式下的转换常规通道数
AdcHandle.Init.ExternalTrigConv = ADC_SOFTWARE_START;
//软件启动
AdcHandle.Init.DataAlign = ADC_DATAALING_RIGHT;
//右对齐
AdcHandle.Init.NbrOfConversion = 1;
//常规通道序列长度,2次转换
HAL_ADC_Init(&AdcHandle);
//ADC初始化
//配置ADC通道
sConfig.Channel = Channel;
//转换通道
sConfig.Rank = 1;
//指定常规组序列器的列组
sConfig.SamplingTime = ADC_SAMPLETIME_55CYCLES_5;
//要为选定通道设置的采样时间值
HAL_ADC_ConfigChannel(&AdcHandle,&sConfig);
//ADC配置通道
HAL_ADCEx_Calibration_Start(&AdcHandle);
//ADC校准
#ifdef ADC_DMA_ENABLE//使能DMA
HAL_ADC_Start_DMA(&AdcHandle,&aADCxConvertedValues,sizeof(aADCxConvertedValues));
#else
HAL_ADC_Start_IT(&AdcHandle);
//使能ADC中断,启动ADC
#endif
}
void ADC_IRQHandle(void)
//ADC中断服务函数
{
HAL_ADC_IRQHandler(&AdcHandle);
}
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* AdcHandle)
//转换完成回调,在HAL_ADC_IRQHandler中调用
{
if(adc_isr.isr_op != NULL )
adc_isr.isr_op();
}
//转换完成一半回调
void HAL_ADC_ConvHalfCpltCallback(ADC_HandleTypeDef* hadc)
{
}
//ADC错误回调
void HAL_ADC_ErrorCallback(ADC_HandleTypeDef *hadc)
{
}
#ifdef ADC_DMA_ENABLE
//使能DMA
void DMA1_Channel1_IRQHandler(void)
DMA中断
{
HAL_DMA_IRQHandler(&hdma_adc);
}
#endif
//电压采样
uint32_t Vol_Sample(void)
{
#ifdef ADC_DMA_ENABLE
//使能DMA
return (aADCxConvertedValues&0xfff)*3300/4096;
#else
uint32_t Vol_ADC_Val;
Vol_ADC_Val = HAL_ADC_GetValue(&AdcHandle);
return Vol_ADC_Val*3300/4096;
//返回电压值扩大1000倍
#endif
}
实验现象
D5 D6 LED灯闪烁
寄存器代码
test.c
#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "adc.h"
int main(void)
{
u16 adcx;
Stm32_Clock_Init(9);
uart_init(72,115200);
delay_init(72);
LED_Init();
Adc_Init();
while(1)
{
LED3 = !LED3;
adcx = Get_Adc_Average(ADC_CH7,10);
LED1 = !LED1;
printf("%d:\r\n",adcx);
LED2 = !LED2;
printf("OK\r\n");
delay_ms(250);
}
}
adc.h
#ifndef __ADC_H
#define __ADC_H
#include "sys.h"
#define ADC_CH1 1
#define ADC_CH7 7 //通道7(连接在PA1)
void Adc_Init(void);
//ADC通道初始化
u16 Get_Adc(u8 ch);
//获得某个通道值
u16 Get_Adc_Average(u8 ch,u8 times);
//得到某个通道采样的平均值
#endif
adc.c
#include "adc.h"
#include "delay.h"
void Adc_Init(void)
{
//初始化IO口
RCC->APB2ENR|=1<<2;
// 使能PORTA时钟
GPIOA->CRL&=0X0FFFFFFF;
//PA7 输入
RCC->APB2ENR|=1<<9;
//ADC1时钟使能
RCC->APB2RSTR|=1<<9;
//ADC1复位
RCC->APB2RSTR&=~(1<<9);
//复位结束
RCC->CFGR&=~(3<<14);
//分频因子清零
//SYSCLK/DIV2=12M ADC时钟设置为12M
//ADC最大时钟不能超过14M,否则准确度下降
RCC->CFGR|=2<<14;
ADC1->CR1&=0XF0FFFF;
//工作模式清零
ADC1->CR1|=0<<16;
//独立工作模式
ADC1->CR1&=~(1<<8);
//非扫描模式
ADC1->CR2&=~(1<<1);
//单次转换模式
ADC1->CR2&=~(7<<17);
ADC1->CR2|=7<<17;
//软件控制转换
ADC1->CR2|=1<<20;
//使用用外部触发,必须使用一个事件来触发
ADC1->CR2&=~(1<<11);
//右对齐
ADC1->SQR1&=~(0XF<<20);
ADC1->SQR1|=0<<20;
//1个转换在规则序列中,也就是只转换规则序列1
//设置通道1的采样时间
ADC1->SMPR2&=~(3*1);
//通道1采样时间清空
ADC1->SMPR2|=7<<(3*1);
//通道1 239.5周期,提高采样时间可以提高准确度
ADC1->CR2|=1<<0;
//开启AD转换器
ADC1->CR2|=1<<3;
//使能复位校准
while(ADC1->CR2&1<<3);
//等待校准结束
//该位由软件设置并由硬件清除,在校准寄存器
//被初始化后该位也被清除
ADC1->CR2|=1<<2;
//开启AD校准
while(ADC1->CR2&1<<2);
//等待校准结束
//该位由软件设置以开始校准,并在校准结束时由硬件清除。
}
u16 Get_Adc(u8 ch)
{
//设置转换序列
ADC1->SQR3&=0XFFFFFFE0;
//规则序列1 通道ch
ADC1->SQR3|=ch;
ADC1->CR2|=1<<22;
//启动规则转换通道
while(!(ADC1->SR&1<<1));
//等待转换结束
return ADC1->DR;
//返回adc值
}
//获取通道ch的转换值,然后平均
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;
}