二十二、ADC+DMA实验
1. 模块来源
2. 规格参数
工作电压: 3.3-5V
工作电流: 1MA
模块尺寸: 31.1475 x 14.097mm
输出方式: DO接口为数字量输出 AO接口为模拟量输出
读取方式: ADC
管脚数量: 4 Pin(2.54mm间距排针)
3. 引脚分配接线图
光敏电阻 | 开发板 |
---|---|
AO | PA5 |
DO | PA2 |
GND | GND |
VCC | 3V3 |
4. 配置流程
4.1 首先定义数组
C
uint16_t ADC_Value[10] = {0}; // DMA搬运的目标地址
1
4.2 使能时钟
厂家已经很方便的给我们提供了宏,直接使用。
C
__RCC_GPIOA_CLK_ENABLE(); // 使能GPIOA时钟
__RCC_ADC_CLK_ENABLE(); // 使能ADC时钟
__RCC_DMA_CLK_ENABLE(); // 使能DMA时钟
1
2
3
2
3
4.3 GPIO配置
C
PA05_ANALOG_ENABLE() ; //设定PA05(AIN5)为模拟输入
1
4.4 ADC初始化配置
我们使用该结构体进行初始化
我们配置相应的参数:
C
ADC_InitTypeDef ADC_InitStruct; // ADC初始化结构体
/* ADC初始化 */
ADC_InitStruct.ADC_OpMode = ADC_SingleChOneMode; //单通道单次转换模式
ADC_InitStruct.ADC_ClkDiv = ADC_Clk_Div128; //PCLK分频
ADC_InitStruct.ADC_SampleTime = ADC_SampTime5Clk; //5个ADC时钟周期
ADC_InitStruct.ADC_VrefSel = ADC_Vref_VDDA; //VDDA参考电压
ADC_InitStruct.ADC_InBufEn = ADC_BufEnable; //开启跟随器
ADC_InitStruct.ADC_TsEn = ADC_TsDisable; //内置温度传感器禁用
ADC_InitStruct.ADC_DMAEn = ADC_DmaEnable; //ADC转换完成触发DMA传输
ADC_InitStruct.ADC_Align = ADC_AlignRight; //ADC转换结果右对齐
ADC_InitStruct.ADC_AccEn = ADC_AccDisable; //转换结果累加不使能
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
参数说明
- ADC_InitStruct.ADC_OpMode = ADC_SingleChOneMode;: 设置ADC的工作模式为单通道单次转换模式。这意味着在每次转换时,ADC只会对一个指定的通道进行一次采样转换。
- ADC_InitStruct.ADC_ClkDiv = ADC_Clk_Div128;: 配置ADC的时钟分频因子为128。ADC的时钟频率由PCLK(外设时钟)经过这个分频得到,分频系数的选择会影响ADC的转换速率和精度。较大的分频系数通常会使采样时间延长,但能提高转换精度。
- ADC_InitStruct.ADC_SampleTime = ADC_SampTime5Clk;: 设置ADC的采样时间为5个ADC时钟周期。采样时间指的是ADC在真正开始转换之前对输入信号进行稳定采样的持续时间,更长的采样时间有助于减少采样误差,但会增加总的转换时间。
- ADC_InitStruct.ADC_VrefSel = ADC_Vref_VDDA;: 选择ADC的参考电压为VDDA,即模拟电源电压。这意味着ADC的满刻度范围将与VDDA的电压相同。
- ADC_InitStruct.ADC_InBufEn = ADC_BufEnable;: 启用ADC的输入缓冲器(跟随器)。输入缓冲器可以提高输入阻抗,减少对信号源的影响,并可能提供一定的信号调理作用,适用于驱动能力较弱的信号源。
- ADC_InitStruct.ADC_TsEn = ADC_TsDisable;: 禁用内置温度传感器。这表示不会使用ADC来测量片上温度传感器的温度。
- ADC_InitStruct.ADC_DMAEn = ADC_DmaEnable;: 启用DMA(直接内存访问)功能。这意味着ADC在每次转换完成后,会自动通过DMA将转换结果传输到内存,而无需CPU干预,提高数据传输效率。
- ADC_InitStruct.ADC_Align = ADC_AlignRight;: 设置ADC转换结果的对齐方式为右对齐。这意味着转换结果的最高有效位(MSB)将位于数据字的最右侧,这对于大多数处理操作来说是最常见的格式。
- ADC_InitStruct.ADC_AccEn = ADC_AccDisable;: 禁用结果累加功能。ADC不会将新的转换结果累加到之前的转换结果上,每次转换都是独立的值。
4.5 ADC初始化
C
ADC_Init(&ADC_InitStruct); //初始化ADC配置
1
4.6 配置ADC通道
C
CW_ADC->CR1_f.DISCARD = FALSE; //配置数据覆盖更新,不包含在ADC结构体中
CW_ADC->CR1_f.CHMUX = ADC_ExInputCH5; //配置ADC输入通道5,不包含在ADC结构体中
1
2
2
参数说明
- FALSE:配置这个参数的意思是,允许数据覆盖更新。
- ADC_ExInputCH5:ADC通道5的意思,也就对应引脚PA05。
4.7 配置ADC中断
C
ADC_ClearITPendingBit(ADC_IT_EOC); // 清除中断标志
ADC_ITConfig(ADC_IT_EOC, ENABLE); // 使能采集完成中断
ADC_EnableNvic(ADC_INT_PRIORITY); // 使能NVIC中的ADC中断
1
2
3
2
3
4.8 编写ADC中断服务函数
C
/******************************************************************
* 函 数 名 称:ADC_IRQHandler
* 函 数 说 明:ADC中断服务函数,在中断里开启下一次软件转换
* 函 数 形 参:无
* 函 数 返 回:无
* 作 者:LC
* 备 注:无
******************************************************************/
void ADC_IRQHandler(void)
{
if (ADC_GetITStatus(ADC_IT_EOC) != RESET)
{
ADC_ClearITPendingBit(ADC_IT_EOC);
ADC_SoftwareStartConvCmd(ENABLE); //启动下一次ADC转换
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
4.9 DMA初始化配置
使用结构体进行初始化配置
C
DMA_InitTypeDef DMA_InitStruct; // DMA初始化结构体
DMA_StructInit(&DMA_InitStruct); // 使用默认参数填充DMA初始化结构体
DMA_InitStruct.DMA_Mode = DMA_MODE_BLOCK; // 使用BLOCK触发
DMA_InitStruct.DMA_TransferWidth = DMA_TRANSFER_WIDTH_16BIT; // 数据宽度为12Bit
DMA_InitStruct.DMA_SrcInc = DMA_SrcAddress_Fix; // ADC外设地址固定
DMA_InitStruct.DMA_DstInc = DMA_DstAddress_Increase; // 目标地址自动增加
DMA_InitStruct.DMA_TransferCnt = 10; // 自动转换10次
DMA_InitStruct.DMA_SrcAddress = (uint32_t)(&(CW_ADC->RESULT0)); // ADC数据寄存器地址(源地址)
DMA_InitStruct.DMA_DstAddress = (uint32_t)ADC_Value; // 目标地址
DMA_InitStruct.TrigMode = DMA_HardTrig; // 硬件触发
DMA_InitStruct.HardTrigSource = DMA_HardTrig_ADC_TRANSCOMPLETE; // ADC触发
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
参数说明
- DMA_StructInit(&DMA_InitStruct);: 调用此函数来使用默认参数初始化DMA_InitStruct结构体,为接下来的自定义配置奠定基础。
- DMA_InitStruct.DMA_Mode = DMA_MODE_BLOCK;: 设置DMA的工作模式为BLOCK模式。在这种模式下,DMA在接收到触发信号后,会连续传输指定数量的数据块。
- DMA_InitStruct.DMA_TransferWidth = DMA_TRANSFER_WIDTH_16BIT;: 配置DMA的数据传输宽度为16位。
- DMA_InitStruct.DMA_SrcInc = DMA_SrcAddress_Fix;: 指定DMA源地址不变。这里表示ADC的数据寄存器地址在连续传输过程中保持固定,即每次传输都从同一个寄存器读取数据。
- DMA_InitStruct.DMA_DstInc = DMA_DstAddress_Increase;: 设置目标地址递增。这意味着每次DMA传输完成后,目标地址(内存地址)会自动增加,以便连续写入多组转换结果。
- DMA_InitStruct.DMA_TransferCnt = 10;: 配置DMA要执行的传输次数为10。这表示DMA将连续从ADC读取数据并写入内存10次,之后停止,除非再次触发。
- DMA_InitStruct.DMA_SrcAddress = (uint32_t)(&(CW_ADC->RESULT0));: 指定了DMA的源地址为ADC数据寄存器RESULT0的地址。这是ADC转换结果存放的地方,DMA将从此处读取数据。
- DMA_InitStruct.DMA_DstAddress = (uint32_t)ADC_Value;: 设置DMA的目标地址为数组ADC_Value的首地址。转换得到的数据将被直接写入这个数组。
- DMA_InitStruct.TrigMode = DMA_HardTrig;: 选择硬件触发模式。这意味着DMA传输是由硬件事件触发的,而不是软件指令。
- DMA_InitStruct.HardTrigSource = DMA_HardTrig_ADC_TRANSCOMPLETE;: 指定硬件触发源为ADC转换完成事件。这意味着每当ADC完成一次转换,就会触发DMA开始传输数据。
4.10 DMA初始化
C
DMA_Init(CW_DMACHANNEL1, &DMA_InitStruct); // 初始化DMA
1
4.11 配置DMA中断
C
DMA_ClearITPendingBit(DMA_IT_ALL); // 清除DMA所有中断标志位
DMA_ITConfig(CW_DMACHANNEL1, DMA_IT_TC, ENABLE); // 使能DMA_CHANNEL1发送完成中断
//使能DMA_CHANNEL1中断
__disable_irq();
NVIC_ClearPendingIRQ(DMACH1_IRQn);
NVIC_EnableIRQ(DMACH1_IRQn);
__enable_irq();
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
代码解析
- DMA_ClearITPendingBit(DMA_IT_ALL);: 这行代码用于清除所有DMA中断的挂起标志位。DMA_IT_ALL是一个宏,表示清除所有类型的DMA中断标志。这通常在初始化阶段使用,以确保没有遗留的中断标志影响新的中断处理流程,保证从一个干净的状态开始。
- DMA_ITConfig(CW_DMACHANNEL1, DMA_IT_TC, ENABLE);: 此函数用于配置特定DMA通道的中断。在这里,针对CW_DMACHANNEL1启用了传输完成中断(DMA_IT_TC)。这意味着当DMA通道1完成数据传输时,将触发一个中断。ENABLE参数表明中断被激活。
- __disable_irq(); 和 __enable_irq();: 这两个函数用于临时禁用和重新启用中断。在修改中断相关的设置(如NVIC配置)之前禁用中断是一种常见的做法,可以防止在配置过程中被其他中断打断,导致配置不完整或不一致的问题。
- NVIC_ClearPendingIRQ(DMACH1_IRQn);: 清除挂起的DMA通道1中断请求。这一步是预防性的,确保在使能中断之前,即使之前有中断请求挂起,也不会立即触发中断处理,让系统从一个已知的、干净的状态开始。
- NVIC_EnableIRQ(DMACH1_IRQn);: 该函数用于使能DMA通道1对应的中断向量。在NVIC(嵌套向量中断控制器)中激活这个中断,意味着当DMA通道1完成传输时,CPU将响应并执行相应的中断服务例程(ISR),进行必要的后处理工作。
4.12 编写DMA中断服务函数
C
/******************************************************************
* 函 数 名 称:DMACH1_IRQHandler
* 函 数 说 明:DMA通道1中断服务函数,完成一次DMA传输,在中断里重新使能DMA
* 函 数 形 参:无
* 函 数 返 回:无
* 作 者:LC
* 备 注:无
******************************************************************/
void DMACH1_IRQHandler(void)
{
if( DMA_GetITStatus(DMA_IT_TC1) )
{
DMA_ClearITPendingBit(DMA_IT_TC1);
CW_DMACHANNEL1->CNT = 0x10010; //重置CNT计数
CW_DMACHANNEL1->DSTADDR = (uint32_t)ADC_Value; //重置目的地址
DMA_Cmd(CW_DMACHANNEL1, ENABLE); //使能DMA
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
主要是在中断里面重新使能DMA。
4.13 使能ADC和DMA
C
DMA_Cmd(CW_DMACHANNEL1, ENABLE); //使能DMA
ADC_Enable(); //ADC使能
1
2
3
2
3
4.14 开启第一次软件转换
C
ADC_SoftwareStartConvCmd(ENABLE); // 软件转换开始
1
我们只需要在初始化的时候开启第一次软件转换,后面的操作全部都有中断服务函数中进行。
5. 获取ADC数据平均值
ADC采集完毕之后DMA会自动帮我们把数据搬运到ADC_Value这个数组里面,我们每次进行10次ADC转换,也就是采集十次,将这十次数据依次放入ADC_Value[0] 到 ADC_Value[9] 中。
我们要将十次数据求和然后算平均值。
C
/******************************************************************
* 函 数 名 称:getAverage
* 函 数 说 明:计算并返回ADC_Value数组中的数据平均值
* 函 数 形 参:无
* 函 数 返 回:平均值
* 作 者:LC
* 备 注:无
******************************************************************/
uint32_t getAverage(void)
{
uint64_t averageValue = 0;
uint32_t Count = 10;
for(int i = 0; i < Count; i++)
{
uint32_t temp = ADC_Value[i];
if(temp == 0) // 如果采集的数据是0
{
Count--; // 将本次剔除不计算在内
continue;
}
averageValue += temp;
}
return (averageValue/Count);
}
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
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
6. 实验现象
烧写我们的代码之后,在串口助手中发送对应的数据,将会在串口助手中显示出来。
说明
关于这一章节的代码,在立创·地文星CW32F030C8T6开发板资料/第03章软件资料/代码例程/010ADC+DMA数据采集。