STM32 ADC基本介绍+DMA外设到内存

时间:2024-03-10 16:38:04

  1.ADC介绍

STM32f103系列有3个ADC,精度为12位,每个ADC最多有16个外部通道。其中ADC1和ADC2都有16个外部通道,ADC3一般有8个外部通道,各通道的A/D转换可以单次、连续、扫描或间断执行,ADC转换的结果可以左对齐或右对齐储存在16位数据寄存器中。ADC的输入时钟不得超过14MHz,其时钟频率由PCLK2分频产生。

ADC功能框图:把整体框图分成若干个部分,按照顺序介绍具体的作用。

 

图1

(1)第一部分限定了ADC电压输入范围 。ADC 输入范围为: VREF- ≤ VIN ≤ VREF+。由 VREF-、 VREF+ 、 VDDA 、 VSSA、这四个外部引脚决定。我们在设计原理图的时候一般把 VSSA 和 VREF-接地,把 VREF+和 VDDA 接 3V3,得到ADC 的输入电压范围为: 0~3.3V。如果我们想让输入的电压范围变宽,去到可以测试负电压或者更高的正电压,我们可以在外部加一个电压调理电路,把需要转换的电压抬升或者降压到 0~3.3V,这样 ADC 就可以测量了。
(2)ADC是芯片内部的一个外设,用来采集外部的电压,通过芯片的IO引脚来采集电压。stm32f103zet6芯片的每个ADC有18个通道,每个通道对应一个IO引脚。具体对应关系如下:

图2(引用于野火资料) 

 (3)在第2、3部分之间有注明规则通道最多16个,注入通道最多4个。规则通道就是最常用的通道,注入通道就是一种优先级比较高的通道,注入通道可以理解为插入,中断类型的转换。比如当前正在转换一个规则通道,突然来了一个注入通道,那就要先转换注入通道,然后在继续转换规则通的。

(4)第4、5部分是注入通道数据寄存器和规则转换数据寄存器。在第3部分中。从规则通道或者注入通道采集到模拟电压后,在第3部分中的模拟至数字转化器中进行转换,转换后的数字信号存放到对应的数据寄存器和规则转换数据寄存器。可以看到注入通道数据寄存器有4个,正好对应前面说的注入通道最多有4个,但是规则通道只有1个。也就是说如果ADC有多个通道在同时采集模拟信号,这些通道的最后的转换结果都会存放在同一个规则通道数据寄存器中,这样会发生数据覆盖,解决办法就是使用DMA。后面有DMA处理多个通道的程序。

 关于转换顺序:知道了ADC的转换通道后,如果ADC只使用一个通道来转换,那就很简单,但如果是使用多个通道进行转换就涉及到一个先后顺序了,毕竟规则转换通道只有一个数据寄存器。多个通道的使用顺序分为俩种情况:规则通道的转换顺序和注入通道的转换顺序。

规则通道转换顺序:
规则通道中的转换顺序由三个寄存器控制:SQR1、SQR2、SQR3,它们都是32位寄存器。SQR寄存器控制着转换通道的数目和转换顺序,只要在对应的寄存器位SQx中写入相应的通道,这个通道就是第x个转换。具体的对应关系如下:

ADC规则序列寄存器ADC_SQR1:

 

在设置多个规则通道时,需要在SQR1寄存器的位23:20中写入总共有多少个规则通道需要被转换,然后再SQR1、SQR2、SQR3中设置每个通道的顺序。

注入通道转换顺序
和规则通道转换顺序的控制一样,注入通道的转换也是通过注入寄存器来控制,只不过只有一个JSQR寄存器来控制,控制关系如下:

需要注意的是,只有当JL=4的时候,注入通道的转换顺序才会按照JSQ1、JSQ2、JSQ3、JSQ4的顺序执行。当JL<4时,注入通道的转换顺序恰恰相反,也就是执行顺序为:JSQ4、JSQ3、JSQ2、JSQ1。这个在设置的时候需要注意下。

(5)第6部分,通过地址数据总线从第4、5部分的数据寄存器读取转换后的数据。或者通过DMA请求,不经过CPU直接转移数据。
  (6)第7部分时关于中断的。关于ADC转换有3个中断:规则转换结束中断、注入转换结束中断、看门狗比较中断,既然产生中断了就要涉及到NVIC了。

 (7)在设置好上面的部分,就可以开始转换了,但是什么时候开始转换,可以手动开始转换,也可以使用其他事件触发开始转换。由图1中红色框图8、9、10选择。事件触发又分成2种,一种是触发规则通道的转换,另一种时触发注入通道的转换。

 手动触发转换:11.12.3 ADC控制寄存器2(ADC_CR2)

 

 其他事件触发转换:11.12.3 ADC控制寄存器2(ADC_CR2)

   触发规则转换:

         

 

  触发注入转换:

         

 需要注意的是:还有两个位EXTTRIG和JEXTTRIG用来作为开关,是否打开其他事件能够触发ADC的转换。

在定时器的框图中,下图红色框2中,就是用来触发ADC转换的事件。

 

2.ADC结构体配置

typedef struct
 {
 uint32_t ADC_Mode; // ADC 工作模式选择
 FunctionalState ADC_ScanConvMode; // ADC 扫描(多通道)或者单次(单通道)模式选择 
 FunctionalState ADC_ContinuousConvMode; // ADC 单次转换或者连续转换选择
 uint32_t ADC_ExternalTrigConv; // ADC 转换触发信号选择
 uint32_t ADC_DataAlign; // ADC 数据寄存器对齐格式
 uint8_t ADC_NbrOfChannel; // ADC 采集通道数
 } ADC_InitTypeDef;

  1.工作模式:Configures the ADC to operate in independent or dual mode 独立模式(只用一个ADC)还是双ADC模式。

  2.扫描或者单次:This parameter can be set to ENABLE or DISABLE 

       3.单次转换或者多次转换:This parameter can be set to ENABLE or DISABLE

  4. ADC 转换触发信号选择:可以选择ADC_ExternalTrigConv_None代表不适用外部事件触发,或者选择要触发的具体事件

  5.对齐格式:左对齐或者右对齐。因为是16位寄存器,转换结果是12位的,

  6.采集通道数量:总共需要采集的通道数量(区分规则还是注入,不混合计算)

关于独立模式、连续转换、扫描转换可能有些模糊,举个例子具体说明一下区别:

用ADC1 规则通道的顺序为CH0,CH1,CH2,CH3,

不启动扫描模式

在单次转换模式下:

启动ADC1,则

1.开始转换CH1(ADCx_SQR的第一通道)

转换完成后停止,等待ADC的下一次启动,继续从第一步开始转换

在连续转换模式下:

启动ADC1,则

1.开始转换CH0(ADC_SQR的第一通道)

转换完成后回到第一步。

启动扫描模式下

在单次转换模式下:

启动ADC1,则

1.开始转换CH0、

2.转换完成后自动开始转换CH1

3.转换完成后自动开始转换CH2

4.转换完成后自动开始转换CH3

5.转换完成后停止,等待ADC的下一次启动下一次ADC启动后从第一步开始转换

在连续转换模式下:

启动ADC1,则

1.开始转换CH0

2.转换完成后自动开始转换CH1

3.转换完成后自动开始转换CH2

4.转换完成后自动开始转换CH3

5.转换完成后返回第一步继续转换CH0
  

3.结合实验理解ADC的使用方法

3.1ADC采集电压-单规则通道-中断

使用ADC2,CH11,IO为PC1

 1 static void ADCx_GPIO_Config(void)//PC1配置
 2 {
 3     GPIO_InitTypeDef GPIO_InitStructure;
 4     
 5     // 打开 ADC IO端口时钟
 6     ADC_GPIO_APBxClock_FUN ( ADC_GPIO_CLK, ENABLE );
 7     
 8     // 配置 ADC IO 引脚模式
 9     // 必须为模拟输入
10     GPIO_InitStructure.GPIO_Pin = ADC_PIN;
11     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
12     
13     // 初始化 ADC IO
14     GPIO_Init(ADC_PORT, &GPIO_InitStructure);                
15 }
16 
17 /**
18   * @brief  配置ADC工作模式
19   * @param  无
20   * @retval 无
21   */
22 static void ADCx_Mode_Config(void)
23 {
24     ADC_InitTypeDef ADC_InitStructure;    
25 
26     // 打开ADC时钟
27     ADC_APBxClock_FUN ( ADC_CLK, ENABLE );
28     
29     // ADC 模式配置
30     // 只使用一个ADC,属于独立模式
31     ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
32     
33     // 禁止扫描模式,多通道才要,单通道不需要
34     ADC_InitStructure.ADC_ScanConvMode = DISABLE ; 
35 
36     // 连续转换模式
37     ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;
38 
39     // 不用外部触发转换,软件开启即可
40     ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
41 
42     // 转换结果右对齐
43     ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
44     
45     // 转换通道1个
46     ADC_InitStructure.ADC_NbrOfChannel = 1;    
47         
48     // 初始化ADC
49     ADC_Init(ADCx, &ADC_InitStructure);
50     
51     // 配置ADC时钟为PCLK2的8分频,即9MHz
52     RCC_ADCCLKConfig(RCC_PCLK2_Div8); 
53     
54     // 配置 ADC 通道转换顺序和采样时间
55     ADC_RegularChannelConfig(ADCx, ADC_CHANNEL, 1, 
56                              ADC_SampleTime_55Cycles5);
57     
58     // ADC 转换结束产生中断,在中断服务程序中读取转换值
59     ADC_ITConfig(ADCx, ADC_IT_EOC, ENABLE);
60     
61     // 开启ADC ,并开始转换
62     ADC_Cmd(ADCx, ENABLE);
63     
64     // 初始化ADC 校准寄存器  
65     ADC_ResetCalibration(ADCx);
66     // 等待校准寄存器初始化完成
67     while(ADC_GetResetCalibrationStatus(ADCx));
68     
69     // ADC开始校准
70     ADC_StartCalibration(ADCx);
71     // 等待校准完成
72     while(ADC_GetCalibrationStatus(ADCx));
73     
74     // 由于没有采用外部触发,所以使用软件触发ADC转换 
75     ADC_SoftwareStartConvCmd(ADCx, ENABLE);
76 }
77 
78 static void ADC_NVIC_Config(void)//中断配置
79 {
80   NVIC_InitTypeDef NVIC_InitStructure;
81     // 优先级分组
82     NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
83 
84   // 配置中断优先级
85   NVIC_InitStructure.NVIC_IRQChannel = ADC_IRQ;
86   NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
87   NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
88   NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
89   NVIC_Init(&NVIC_InitStructure);
90 }

ADC的规则转换结束中断函数:

1 void ADC_IRQHandler(void) //ADC中断
2 {    
3     if (ADC_GetITStatus(ADCx,ADC_IT_EOC)==SET) //判断是否是转换结束
4     {
5         // 读取ADC的转换值
6         ADC_ConvertedValue = ADC_GetConversionValue(ADCx);//通过库函数把读取到的值赋值给变量 ADC_ConvertedValue
7    }
8   ADC_ClearITPendingBit(ADCx,ADC_IT_EOC);  }

对于只使用一个通道来说,使用中断也可以。下图是把3.3V接到PC1之后的结果。

 

 

 

 

3.2ADC采集电压-单规则通道-DMA

上面使用中断的方式,现在使用DMA方式,读取一个通道的数据,基本配置一样,去掉了ADC转换结束的中断,添加了DMA初始化结构体,因为要使用DMA传输,DMA也是一个外设,在使用一个外设的时候,肯定是要初始化这个外设的结构体的。这里使用DMA和ADC的时候只要注意几点既可以了:

1.传输方向:ADC的转换结果数据寄存器->内存(一个变量)

2.传输大小:16位

3.外设地址和内存地址都不自增

4.DMA传输模式:循环模式 DMA_Mode_Circular

5.注意区分使能DMA通道,和使能ADC的DMA请求

// 使能 DMA 通道  。让DMA通道就绪
 52     DMA_Cmd(ADC_DMA_CHANNEL , ENABLE);
 82     // 使能ADC DMA 请求 。让ADC去使用已经就绪的DMA通道。在这一步才开始进行DMA传输
 83     ADC_DMACmd(ADCx, ENABLE);
 98     // 由于没有采用外部触发,所以使用软件触发ADC转换  。开启ADC转换
 99     ADC_SoftwareStartConvCmd(ADCx, ENABLE);

6.这里只有一个ADC规则通道,需要对这一个通道进行配置。下个实验,多规则通道使用DMA,也要单独对每个通道设置。

 // 配置 ADC 通道转换顺序为1,第一个转换,采样时间为55.5个时钟周期
 80     ADC_RegularChannelConfig(ADCx, ADC_CHANNEL, 1, ADC_SampleTime_55Cycles5);

7.如果使用DMA的3个中断(传输一半、传输完成、传输出错),则配置NVIC。

下面是具体代码:

  1 static void ADCx_Mode_Config(void)
  2 {
  3     DMA_InitTypeDef DMA_InitStructure;
  4     ADC_InitTypeDef ADC_InitStructure;
  5     
  6     // 打开DMA时钟
  7     RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
  8     // 打开ADC时钟
  9     ADC_APBxClock_FUN ( ADC_CLK, ENABLE );
 10     
 11     // 复位DMA控制器
 12     DMA_DeInit(ADC_DMA_CHANNEL);
 13     
 14     // 配置 DMA 初始化结构体
 15     // 外设基址为:ADC 数据寄存器地址
 16     DMA_InitStructure.DMA_PeripheralBaseAddr = ( uint32_t ) ( & ( ADCx->DR ) );
 17     
 18     // 存储器地址,实际上就是一个内部SRAM的变量
 19     DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)&ADC_ConvertedValue;
 20     
 21     // 数据源来自外设
 22     DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
 23     
 24     // 缓冲区大小为1,缓冲区的大小应该等于存储器的大小
 25     DMA_InitStructure.DMA_BufferSize = 1;
 26     
 27     // 外设寄存器只有一个,地址不用递增
 28     DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
 29 
 30     // 存储器地址固定
 31     DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Disable; 
 32     
 33     // 外设数据大小为半字,即两个字节
 34     DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
 35     
 36     // 存储器数据大小也为半字,跟外设数据大小相同
 37     DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
 38     
 39     // 循环传输模式
 40     DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;    
 41 
 42     // DMA 传输通道优先级为高,当使用一个DMA通道时,优先级设置不影响
 43     DMA_InitStructure.DMA_Priority = DMA_Priority_High;
 44     
 45     // 禁止存储器到存储器模式,因为是从外设到存储器
 46     DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
 47     
 48     // 初始化DMA
 49     DMA_Init(ADC_DMA_CHANNEL, &DMA_InitStructure);
 50     
 51     // 使能 DMA 通道
 52     DMA_Cmd(ADC_DMA_CHANNEL , ENABLE);
 53     
 54     // ADC 模式配置
 55     // 只使用一个ADC,属于单模式
 56     ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
 57     
 58     // 禁止扫描模式,多通道才要,单通道不需要
 59     ADC_InitStructure.ADC_ScanConvMode = DISABLE ; 
 60 
 61     // 连续转换模式
 62     ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;
 63 
 64     // 不用外部触发转换,软件开启即可
 65     ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
 66 
 67     // 转换结果右对齐
 68     ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
 69     
 70     // 转换通道1个
 71     ADC_InitStructure.ADC_NbrOfChannel = 1;    
 72         
 73     // 初始化ADC
 74     ADC_Init(ADCx, &ADC_InitStructure);
 75     
 76     // 配置ADC时钟为PCLK2的8分频,即9MHz
 77     RCC_ADCCLKConfig(RCC_PCLK2_Div8); 
 78     
 79     // 配置 ADC 通道转换顺序为1,第一个转换,采样时间为55.5个时钟周期
 80     ADC_RegularChannelConfig(ADCx, ADC_CHANNEL, 1, ADC_SampleTime_55Cycles5);
 81     
 82     // 使能ADC DMA 请求
 83     ADC_DMACmd(ADCx, ENABLE);
 84     
 85     // 开启ADC ,并开始转换
 86     ADC_Cmd(ADCx, ENABLE);
 87     
 88     // 初始化ADC 校准寄存器  
 89     ADC_ResetCalibration(ADCx);
 90     // 等待校准寄存器初始化完成
 91     while(ADC_GetResetCalibrationStatus(ADCx));
 92     
 93     // ADC开始校准
 94     ADC_StartCalibration(ADCx);
 95     // 等待校准完成
 96     while(ADC_GetCalibrationStatus(ADCx));
 97     
 98     // 由于没有采用外部触发,所以使用软件触发ADC转换 
 99     ADC_SoftwareStartConvCmd(ADCx, ENABLE);
100 }

 把3.3V电压接到PC1上:

 

3.3ADC采集电压-多规则通道-DMA

使用ADC1的通道11-通道14,对应PC1-PC4。多通道对于单通道来说,只要注意几点即可:

1.使用4个通道要使用4个变量接收对应通道的转换数据。

2.使用一个数组,接收4个通道的数据。目的是为了内存地址自增时方便。

3.4个规则通道要进行单独配置。

1     // 配置ADC 通道的转换顺序和采样时间
2     ADC_RegularChannelConfig(ADC1, ADC_Channel_11, 1, ADC_SampleTime_55Cycles5);
3     ADC_RegularChannelConfig(ADC1, ADC_Channel_11, 2, ADC_SampleTime_55Cycles5);
4     ADC_RegularChannelConfig(ADC1, ADC_Channel_11, 3, ADC_SampleTime_55Cycles5);
5     ADC_RegularChannelConfig(ADC1, ADC_Channel_11, 4, ADC_SampleTime_55Cycles5);
6     ADC_RegularChannelConfig(ADC1, ADC_Channel_11, 5, ADC_SampleTime_55Cycles5);
7     ADC_RegularChannelConfig(ADC1, ADC_Channel_11, 6, ADC_SampleTime_55Cycles5);

3.4ADC采集电压-双ADC采集-同步规则

 上面使用的时候,都是使用单独某一个ADC。也就是模式设置为ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;如果使用双重ADC,则设置模式为其他选项

 使用双重ADC模式就是为了让2个ADC同时采集一个通道的信号,双重 ADC 模式较独立模式一个最大的优势就是提高了采样率,弥补了单个 ADC 采样不够快的缺点。暂时没用到,后面用到了再来写吧。