完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
实验目的
当我们看到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%dVn",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:rn",adcx); LED2 = !LED2; printf("OKrn"); 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 temp_val+=Get_Adc(ch); delay_ms(5); } return temp_val/times; } |
|
|
|
只有小组成员才能发言,加入小组>>
调试STM32H750的FMC总线读写PSRAM遇到的问题求解?
1632 浏览 1 评论
X-NUCLEO-IHM08M1板文档中输出电流为15Arms,15Arms是怎么得出来的呢?
1559 浏览 1 评论
985 浏览 2 评论
STM32F030F4 HSI时钟温度测试过不去是怎么回事?
688 浏览 2 评论
ST25R3916能否对ISO15693的标签芯片进行分区域写密码?
1605 浏览 2 评论
1869浏览 9评论
STM32仿真器是选择ST-LINK还是选择J-LINK?各有什么优势啊?
655浏览 4评论
STM32F0_TIM2输出pwm2后OLED变暗或者系统重启是怎么回事?
525浏览 3评论
540浏览 3评论
stm32cubemx生成mdk-arm v4项目文件无法打开是什么原因导致的?
512浏览 3评论
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2024-11-25 18:05 , Processed in 0.767251 second(s), Total 49, Slave 42 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191 工商网监 湘ICP备2023018690号