一、ADC 中断实验
【实现功能】使用 ADC 的一个通道进行测量,并通过中断读取测量结果。
【基本思路】本次实验我们使用 ADC2 的通道 11,查看电路原理图可知,通道 11 对应的是 PC1 口。
结合之前我们所学的内容,因为使用的是是单通道测量,因此工作模式应选择独立模式;连续扫描模式是用于多通道扫描的,因此不使用;由于需要不停地重复采集,因此应使用连续转换模式;不使用外部触发源,而是使用软件触发方式;最后,测量数据使用右对齐方式,以方便程序读取。
除此之外,我们还需要配置 ADC 转换时钟、通道 11 的转换顺序、采样时间,最后开启中断。别忘了每次测量之前,应该对 ADC 进行一次校准。
每测量完一次,便会产生 EOC 中断标志。在中断服务函数内部,即可读取测量结果,并且记得要清除中断标志位,为下一次中断做准备。
【编程要点】
- 初始化 GPIO:PC1 引脚;
- 初始化 ADC 结构体;
- 配置 NVIC 结构体;
- 配置 ADC 时钟;
- 配置 ADC 规则通道(转换顺序+采样时间);
- 使能 ADC 中断、使能 ADC;
- 校准 ADC,并等待校准完成;
- 软件触发 ADC 开始工作;
- 中断服务函数读取测量结果,并清除中断标志位。
部分代码如下:
1. adc.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| #ifndef __ADC_H #define __ADC_H
#include "stm32f10x.h"
#define ADC_PORT GPIOC #define ADC_PORT_CLK RCC_APB2Periph_GPIOC #define ADC_PIN GPIO_Pin_1
#define ADC_x ADC2 #define ADC_CLK RCC_APB2Periph_ADC2 #define ADC_CHANNEL ADC_Channel_11
#define ADC_IRQ ADC1_2_IRQn #define ADC_IRQHandler ADC1_2_IRQHandler
void ADC_Init_Config(void);
#endif
|
2. adc.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
| #include "adc.h"
static void ADC_GPIO_Config(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(ADC_PORT_CLK, ENABLE); GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; GPIO_InitStructure.GPIO_Pin = ADC_PIN; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(ADC_PORT, &GPIO_InitStructure); }
static void ADC_Mode_Config(void) { ADC_InitTypeDef ADC_InitStructure; RCC_APB2PeriphClockCmd(ADC_CLK, ENABLE); ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; ADC_InitStructure.ADC_ScanConvMode = DISABLE; ADC_InitStructure.ADC_ContinuousConvMode = ENABLE; ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; ADC_InitStructure.ADC_NbrOfChannel = 1; ADC_Init(ADC_x, &ADC_InitStructure); RCC_ADCCLKConfig(RCC_PCLK2_Div8); ADC_RegularChannelConfig(ADC_x, ADC_CHANNEL, 1, ADC_SampleTime_55Cycles5); ADC_ITConfig(ADC_x, ADC_IT_EOC, ENABLE); ADC_Cmd(ADC_x, ENABLE); ADC_StartCalibration(ADC_x); while(ADC_GetCalibrationStatus(ADC_x) == SET); ADC_SoftwareStartConvCmd(ADC_x, ENABLE); }
static void ADC_NVIC_Config(void) { NVIC_InitTypeDef NVIC_InitStructure; NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1); NVIC_InitStructure.NVIC_IRQChannel = ADC_IRQ; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); }
void ADC_Init_Config(void) { ADC_Mode_Config(); ADC_GPIO_Config(); ADC_NVIC_Config(); }
|
3. stm32f10x_it.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| #include "stm32f10x_it.h" #include "adc.h"
__IO uint16_t ADC_ConversionValue = 0;
void ADC_IRQHandler(void) { if( ADC_GetITStatus(ADC_x, ADC_IT_EOC) == SET ) { ADC_ConversionValue = ADC_GetConversionValue(ADC_x); } ADC_ClearITPendingBit(ADC_x, ADC_IT_EOC); }
|
4. main.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| #include "stm32f10x.h" #include "usart.h" #include "adc.h"
extern uint16_t ADC_ConversionValue;
void Delay(__IO uint32_t nCount) { for(; nCount != 0; nCount--); }
int main(void) { float ADC_Result; ADC_Init_Config(); USART_Config(); printf("\r\n开始测试!!!\r\n"); while(1) { ADC_Result = (float)ADC_ConversionValue / 4096 * 3.30; printf("\r\n测试结果:%f V (0x%04X)\r\n", ADC_Result, ADC_ConversionValue); Delay(0xffffee); } }
|
大家只需要用杜邦线将 PC1 引脚与待测量引脚连接在一起就行了,数据会在上位机上显示。
二、ADC_DMA 实验
很多情况下,我们不使用中断读取测量数据,主要原因是从中断发起到响应需要一定的滞后时间,并且频繁的响应会拖慢 CPU 的运行速度。使用 DMA 请求则可以解决这个问题。
以下我们分为两个部分:单通道测量和多通道测量。
1. ADC_DMA 单通道实验
【实现功能】使用 DMA 存储 单通道ADC 测量的数据。继续使用 通道11。
【基本思路】回顾一下我们之前所学的 DMA 知识:STM32 有 2 个 DMA ,DMA1 有 7 个通道,DMA2 有 5 个通道,每个通道专门用来管理来自于一个或多个外设对存储器访问的请求。DMA1 的通道 1 对应的是 ADC1,DMA2 的通道 5 对应的是 ADC3,没有专门的通道对应至 ADC2。 因此我们选择使用 ADC1。
现在来解决一下 DMA 的结构体配置问题:
- 由于数据来源于 ADC_DR 寄存器,因此外设基地址(PeripheralBaseAddr) 应为 ADC_DR 寄存器;
- 我们用一个变量来存储测量结果,因此存储基地址(MemoryBaseAddr) 应为该变量地址;
- DMA 的传输方向显然是从外设到存储(DMA_DIR_PeripheralSRC) ;
- 显然,由于我们使用的是单通道 ADC,因此均不开启外设和存储基地址的增量模式(PeripheralInc & MemoryInc) ;
- 因为在单通道情况下,只使用 ADC_DR 寄存器的低 16 位,所以外设的数据宽度(PeripheralDataSize)和存储的数据宽度(MemoryDataSize) 均为 16 位,即半字(Half Word);
- 每次采集传输数据只有 1 个,因此传输数据个数(BufferSize) 为 1;
- 因为是不断采集,工作模式选择循环模式(DMA_Mode_Circular);
在配置完 DMA 后,需要通过函数 ADC_DMACmd 开启 ADC_DMA 请求。
【编程要点】
- 初始化 GPIO:PC1 引脚;
- 初始化 ADC 结构体;
- 初始化 DMA 结构体;
- 配置 ADC 时钟;
- 配置 ADC 规则通道(转换顺序+采样时间);
- 使能 ADC、使能 ADC_DMA;
- 校准 ADC,并等待校准完成;
- 软件触发 ADC 开始工作。
部分代码如下:
(1)adc.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| #ifndef __ADC_H #define __ADC_H
#include "stm32f10x.h"
#define ADC_PORT GPIOC #define ADC_PORT_CLK RCC_APB2Periph_GPIOC #define ADC_PIN1 GPIO_Pin_1 #define ADC_PIN2 GPIO_Pin_2 #define ADC_PIN3 GPIO_Pin_3 #define ADC_PIN4 GPIO_Pin_4
#define ADC_x ADC1 #define ADC_CLK RCC_APB2Periph_ADC1 #define ADC_CHANNEL1 ADC_Channel_11 #define ADC_CHANNEL2 ADC_Channel_12 #define ADC_CHANNEL3 ADC_Channel_13 #define ADC_CHANNEL4 ADC_Channel_14
#define ADC_DMA_CLK RCC_AHBPeriph_DMA1 #define ADC_DMA_CHANNEL DMA1_Channel1
#define ADC_IRQ ADC1_2_IRQn #define ADC_IRQHandler ADC1_2_IRQHandler
void ADC_Init_Config(void);
#endif
|
(2)adc.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71
| #include "adc.h"
__IO uint16_t ADC_ConversionValue = 0;
static void ADC_GPIO_Config(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(ADC_PORT_CLK, ENABLE); GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Pin = ADC_PIN1; GPIO_Init(ADC_PORT, &GPIO_InitStructure); }
static void ADC_Mode_Config(void) { ADC_InitTypeDef ADC_InitStructure; RCC_APB2PeriphClockCmd(ADC_CLK, ENABLE); ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; ADC_InitStructure.ADC_ScanConvMode = DISABLE; ADC_InitStructure.ADC_ContinuousConvMode = ENABLE; ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; ADC_InitStructure.ADC_NbrOfChannel = 1; ADC_Init(ADC_x, &ADC_InitStructure); RCC_ADCCLKConfig(RCC_PCLK2_Div8); ADC_RegularChannelConfig(ADC_x, ADC_CHANNEL1, 1, ADC_SampleTime_55Cycles5); ADC_Cmd(ADC_x, ENABLE); ADC_DMACmd(ADC_x, ENABLE); ADC_StartCalibration(ADC_x); while(ADC_GetCalibrationStatus(ADC_x) == SET); ADC_SoftwareStartConvCmd(ADC_x, ENABLE); }
static void ADC_DMA_Config(void) { DMA_InitTypeDef DMA_InitStructure; RCC_AHBPeriphClockCmd(ADC_DMA_CLK, ENABLE); DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t) (&(ADC_x->DR)); DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t) (&ADC_ConversionValue); DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; DMA_InitStructure.DMA_BufferSize = 1; DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Disable; DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; DMA_InitStructure.DMA_Priority = DMA_Priority_High; DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; DMA_Init(ADC_DMA_CHANNEL, &DMA_InitStructure); DMA_Cmd(ADC_DMA_CHANNEL, ENABLE); }
void ADC_Init_Config(void) { ADC_Mode_Config(); ADC_GPIO_Config(); ADC_DMA_Config(); }
|
2. ADC_DMA 多通道实验
现在我们将上面程序修改一下,变成可以同时测量 4 个通道的功能。需要修改的地方其实很少,其实只要理解了结构体每一项的功能,就知道要修改哪里了。
首先当然需要使能 4 个 GPIO(PC1、PC2、PC3、PC4)了,很简单,这里就不放出代码了。然后需要定义一个数组:
1
| __IO uint16_t ADC_ConversionValue[4] = {0, 0, 0, 0};
|
(1)ADC 通道数、规则通道的配置修改
1
| ADC_InitStructure.ADC_ScanConvMode = ENABLE;
|
1
| ADC_InitStructure.ADC_NbrOfChannel = 4;
|
1 2 3 4 5
| ADC_RegularChannelConfig(ADC_x, ADC_CHANNEL1, 1, ADC_SampleTime_55Cycles5); ADC_RegularChannelConfig(ADC_x, ADC_CHANNEL2, 2, ADC_SampleTime_55Cycles5); ADC_RegularChannelConfig(ADC_x, ADC_CHANNEL3, 3, ADC_SampleTime_55Cycles5); ADC_RegularChannelConfig(ADC_x, ADC_CHANNEL4, 4, ADC_SampleTime_55Cycles5);
|
(2)DMA 部分修改
1
| DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t) (ADC_ConversionValue);
|
1
| DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
|
1
| DMA_InitStructure.DMA_BufferSize = 4;
|
三、双 ADC 规则同步
【实现功能】使用 ADC1 和 ADC2(不能使用 ADC3)的同步规则模式,同时测量两个引脚(PC1 和 PC2)的电压值。
【基本思路】回顾一下同步规则模式:ADC1 和 ADC2 同时转换一个规则通道组,其中 ADC1 为主,ADC2 为从,ADC1 转换的结果放在ADC_DR低16位,ADC2 转换的结果放在ADC_DR的高16位。
ADC1 测量 PC1 引脚,ADC2 测量 PC2 引脚,每个 ADC 都是单通道测量。由于 ADC2 没有 DMA 功能,所以不需要对 ADC2 使能 DMA 功能。ADC1 采用软件触发方式,ADC2 采用外部触发方式,触发源是 ADC1。
【编程要点】
- 初始化 GPIO:PC1 和 PC2 引脚;
- 初始化 ADC 结构体;
- 初始化 DMA 结构体;
- 配置 ADC1/2 时钟;
- 配置 ADC1/2 规则通道(转换顺序+采样时间);
- 使能 ADC1/2、使能 ADC1_DMA;
- 校准 ADC1/2,并等待校准完成;
- 外部触发 ADC2、软件触发 ADC1 开始工作。
1. adc.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| #ifndef __ADC_H #define __ADC_H
#include "stm32f10x.h"
#define ADC_PORT GPIOC #define ADC_PORT_CLK RCC_APB2Periph_GPIOC #define ADC_PIN1 GPIO_Pin_1 #define ADC_PIN2 GPIO_Pin_2 #define ADC_PIN3 GPIO_Pin_3 #define ADC_PIN4 GPIO_Pin_4
#define ADC_DMA_CLK RCC_AHBPeriph_DMA1 #define ADC_DMA_CHANNEL DMA1_Channel1
void ADC_Init_Config(void);
#endif
|
2. adc.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82
| #include "adc.h"
__IO uint32_t ADC_ConversionValue = 0;
static void ADC_GPIO_Config(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE); GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1; GPIO_Init(ADC_PORT, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2; GPIO_Init(ADC_PORT, &GPIO_InitStructure); }
static void ADC_Mode_Config(void) { ADC_InitTypeDef ADC_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC2, ENABLE); RCC_ADCCLKConfig(RCC_PCLK2_Div8); ADC_InitStructure.ADC_Mode = ADC_Mode_RegSimult; ADC_InitStructure.ADC_ScanConvMode = DISABLE; ADC_InitStructure.ADC_ContinuousConvMode = ENABLE; ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; ADC_InitStructure.ADC_NbrOfChannel = 1; ADC_RegularChannelConfig(ADC1, ADC_Channel_11, 1, ADC_SampleTime_55Cycles5); ADC_Init(ADC1, &ADC_InitStructure); ADC_Cmd(ADC1, ENABLE); ADC_DMACmd(ADC1, ENABLE); ADC_StartCalibration(ADC1); while(ADC_GetCalibrationStatus(ADC1) == SET);
ADC_RegularChannelConfig(ADC2, ADC_Channel_12, 1, ADC_SampleTime_55Cycles5); ADC_Init(ADC2, &ADC_InitStructure); ADC_Cmd(ADC2, ENABLE); ADC_StartCalibration(ADC2); while(ADC_GetCalibrationStatus(ADC2) == SET); ADC_SoftwareStartConvCmd(ADC1, ENABLE); ADC_ExternalTrigConvCmd (ADC2, ENABLE); }
static void ADC_DMA_Config(void) { DMA_InitTypeDef DMA_InitStructure; RCC_AHBPeriphClockCmd(ADC_DMA_CLK, ENABLE); DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t) (&(ADC1->DR)); DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t) (&ADC_ConversionValue); DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; DMA_InitStructure.DMA_BufferSize = 1; DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Disable; DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word; DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Word; DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; DMA_InitStructure.DMA_Priority = DMA_Priority_High; DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; DMA_Init(ADC_DMA_CHANNEL, &DMA_InitStructure); DMA_Cmd(ADC_DMA_CHANNEL, ENABLE); }
void ADC_Init_Config(void) { ADC_Mode_Config(); ADC_GPIO_Config(); ADC_DMA_Config(); }
|
3. main.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| #include "stm32f10x.h" #include "usart.h" #include "adc.h"
extern uint32_t ADC_ConversionValue;
void Delay(__IO uint32_t nCount) { for(; nCount != 0; nCount--); }
int main(void) { float ADC_Result1 = 0, ADC_Result2 = 0; ADC_Init_Config(); USART_Config(); printf("\r\nADC_DMA开始测试!!!\r\n"); while(1) { ADC_Result1 = (float)( (ADC_ConversionValue & 0xFFFF0000) >> 16) / 4096 * 3.30; ADC_Result2 = (float) (ADC_ConversionValue & 0xFFFF) / 4096 * 3.30; printf("\r\n测试结果1:%f V (0x%04X)\r\n", ADC_Result1, ADC_ConversionValue); printf("\r\n测试结果2:%f V (0x%04X)\r\n", ADC_Result2, ADC_ConversionValue); printf("\r\n"); Delay(0xffffee); } }
|