一、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 /* __ADC_H */

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; // 参与转换的通道数为1

ADC_Init(ADC_x, &ADC_InitStructure);
RCC_ADCCLKConfig(RCC_PCLK2_Div8); // f(ADC) = PCLK2 / 8,其中PCLK2 = 72MHz

// 配置ADC规则通道的参数,最后两个参数分别是:规则通道的转换顺序 和 采样时间
ADC_RegularChannelConfig(ADC_x, ADC_CHANNEL, 1, ADC_SampleTime_55Cycles5);
ADC_ITConfig(ADC_x, ADC_IT_EOC, ENABLE); // 开启EOC中断
ADC_Cmd(ADC_x, ENABLE); // 使能ADC,但此时未使能触发方式,ADC不工作

ADC_StartCalibration(ADC_x); // ADC开始校准
while(ADC_GetCalibrationStatus(ADC_x) == SET); // 等待ADC校准完成

ADC_SoftwareStartConvCmd(ADC_x, ENABLE); // 软件触发ADC使其开始工作
}

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 ) // 如果 EOC 中断标志位为 1
{
ADC_ConversionValue = ADC_GetConversionValue(ADC_x); // 读取测量数据
}
ADC_ClearITPendingBit(ADC_x, ADC_IT_EOC); // 清除 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; // 提示:最小分辨率为 3.30V / 2^12
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 /* __ADC_H */

(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_DMA请求

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)); // 外设基地址为ADC数据寄存器
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t) (&ADC_ConversionValue); // 存储器基地址为变量地址
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; // 传输源为外设(数据寄存器)

DMA_InitStructure.DMA_BufferSize = 1; // 每次传输1个数据
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; // 不使用增量模式
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Disable; // 不使用增量模式
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; // 数据宽度:16位
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; // 数据宽度:16位

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; // 使用 4 个通道
1
2
3
4
5
// 分别对 4 个规则通道配置转换顺序和采样时间
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; // 传输 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"

/*******双ADC规则同步实验*******/

#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 /* __ADC_H */

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; // PC1
GPIO_Init(ADC_PORT, &GPIO_InitStructure);

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2; // PC2
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_DMACmd(ADC2, ENABLE); // ADC2与DMA不相连
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; // 每次采集 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; // 高16位数据处理
ADC_Result2 = (float) (ADC_ConversionValue & 0xFFFF) / 4096 * 3.30; // 低16位数据处理
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);
}
}