二十、 ADC光敏电阻实验
模块来源
采购链接:
资料下载链接:
https://pan.baidu.com/s/1VMFN1fVo5jxB80IYTsY67A
资料提取码:y8jw
规格参数
工作电压:3.3-5V
工作电流:1MA
模块尺寸:31.1475 x 14.097mm
输出方式: DO接口为数字量输出 AO接口为模拟量输出
读取方式:ADC
管脚数量:4 Pin(2.54mm间距排针)
1. 配置流程
接线图:
1.1 关闭相关寄存器写保护
HC32F4A0有很多寄存器是不能直接修改写入的,我们先解除相关寄存器写保护。
// 关闭相关的寄存器写保护
LL_PERIPH_WE(LL_PERIPH_GPIO | LL_PERIPH_FCG | LL_PERIPH_PWC_CLK_RMU);
2
1.2 配置时钟
本案例使用ADC1进行操作,需要开启ADC1的时钟。 不过需要注意的是HC32F4A0的用户手册写出,ADC的最大时钟频率为60MHz 。V因此我们使用ADC时,需要将挂载ADC的时钟总线频率分频到60MHz以下。
我们在board.c文件中已经将数字接口时钟PCLK4和模拟电路时钟PCLK2设定好了。
4分频满足了我们的要求。
// 使能 ADC 时钟
FCG_Fcg3PeriphClockCmd(FCG3_PERIPH_ADC1, ENABLE);
2
1.3 配置引脚
HC32F4A0一共有16个外部ADC通道,我们将光敏电阻模块的AO引脚连接在PA5引脚上,查找数据手册45页的引脚定义可知,PA5的附加功能有ADC12的通道5。
因为我们之前配置时钟的章节开启的是ADC1的时钟,所以这里选择PA5的附加功能,ADC1的通道5进行操作。要操作 GPIO 引脚,必不可少的就是对 GPIO 进行配置。 GPIO配置:
stc_gpio_init_t stcGpioInit;
// ADC引脚初始化
(void)GPIO_StructInit(&stcGpioInit);
stcGpioInit.u16PinAttr = PIN_ATTR_ANALOG; // 设置引脚属性为模拟输入
stcGpioInit.u16PinState = PIN_STAT_RST; // 引脚状态为复位状态
stcGpioInit.u16PinDir = PIN_DIR_IN; // 引脚方向为输入
stcGpioInit.u16PinDrv = PIN_LOW_DRV; // 引脚驱动能力为低驱动力
stcGpioInit.u16Latch = PIN_LATCH_OFF; // 关闭锁存器
stcGpioInit.u16PullUp = PIN_PU_OFF; // 关闭上拉电阻
stcGpioInit.u16Invert = PIN_INVT_OFF; // 不反相输入信号
stcGpioInit.u16ExtInt = PIN_EXTINT_OFF; // 关闭外部中断功能
stcGpioInit.u16PinOutputType = PIN_OUT_TYPE_CMOS; // 输出类型为CMOS
stcGpioInit.u16PinInputType = PIN_IN_TYPE_SMT;
(void)GPIO_Init(GPIO_PORT_A, GPIO_PIN_05, &stcGpioInit);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
这段代码初始化了一个名为 stcGpioInit
的 GPIO 结构体,并将其用于配置 GPIO 引脚:
u16PinAttr
: 将引脚属性设置为模拟输入,这意味着该引脚将被用作模拟信号的输入。u16PinState
: 将引脚状态设置为复位状态。在这里,PIN_STAT_RST
表示将引脚状态设置为默认状态,可能是低电平、高电平或浮空状态,具体取决于硬件设计。u16PinDir
: 设置引脚方向为输入。这意味着该引脚将接收外部信号作为输入,而不是输出信号。u16PinDrv
: 设置引脚的驱动能力为低驱动力。这表示引脚的输出能力较低。u16Latch
: 关闭锁存器。锁存器用于锁定引脚的当前状态,这里将其关闭,使得引脚状态不被锁定。u16PullUp
: 关闭上拉电阻。上拉电阻用于将输入引脚拉到高电平,这里将其关闭,表示不使用上拉电阻。u16Invert
: 不反相输入信号。这表示输入信号不会被反相处理。u16ExtInt
: 关闭外部中断功能。这意味着该引脚不会触发外部中断。u16PinOutputType
: 设置输出类型为 CMOS。CMOS 输出具有较低的功耗和较高的速度。 最后,调用GPIO_Init
函数,将以上配置应用到 GPIO Port A 的第 5 个引脚上。 以上代码需要注意的是使用ADC功能时,引脚必须设置为模拟输入模式,如设置为上拉或下拉模式,则采集的电压是不准确的。
1.4 配置ADC
这里ADC我们配置为单次扫描模式,这样就可以根据自己的需要,配置采集通道进行依次扫描采集对应引脚的模拟信号。
stc_adc_init_t stcAdcInit;
// 使用基本参数初始化ADC结构体
(void)ADC_StructInit(&stcAdcInit);
stcAdcInit.u16ScanMode = ADC_MD_SEQA_SINGLESHOT; // 设置ADC为单次扫描模式
stcAdcInit.u16Resolution = ADC_RESOLUTION_12BIT; // 设置ADC分辨率为12位
stcAdcInit.u16DataAlign = ADC_DATAALIGN_RIGHT; // 设置ADC数据右对齐
// 初始化ADC
(void)ADC_Init(CM_ADC1, &stcAdcInit);
2
3
4
5
6
7
8
9
10
这段代码初始化了一个名为 stcAdcInit
的 ADC 结构体,并用基本参数初始化了该结构体,接着设置了一些 ADC 的工作模式:
u16ScanMode
: 设置 ADC 为单次扫描模式 (ADC_MD_SEQA_SINGLESHOT
)。在单次扫描模式下,ADC 只会执行一次转换,然后停止转换。u16Resolution
: 设置 ADC 分辨率为 12 位 (ADC_RESOLUTION_12BIT
)。这意味着 ADC 将会以 12 位的精度进行转换,即输出的数字范围为 0 到 4095。u16DataAlign
: 设置 ADC 数据右对齐。在右对齐模式下,ADC 输出的数据将从右边开始,最高有效位对齐到数据寄存器的最高位。 最后,调用ADC_Init
函数,将以上配置应用到 ADC1 上。
1.5 通道重映射
将 ADC1 的通道 5 与 PA5 引脚重新映射
函数原型: void ADC_ChRemap(CM_ADC_TypeDef *ADCx, uint8_t u8Ch, uint8_t u8AdcPin)
ADCx
:ADC 实例的寄存器基址,可以是CM_ADC
或CM_ADCx
,表示 ADC 实例的不同寄存器基址。u8Ch
:ADC 通道号,可以是ADC_Channel
中定义的值。u8AdcPin
:ADC 输入引脚的重新映射值,可以是ADC_Remap_Pin
中定义的值。 重映射我们可以举一个小小的例子,让大家明白什么是重映射: 想象一下你家的电视遥控器。假设你经常使用遥控器上的“音量+”按钮来调整电视的音量。但是有一天,你发现这个按钮有些损坏了,无法正常工作。这时,你决定将另一个不常用的按钮,比如“静音”按钮,重新映射到“音量+”功能上,这样就可以使用它来调节音量了。这就是通道重映射的概念,你重新调整了按钮的功能,以适应新的需求。 代码:
// 通道重映射
ADC_ChRemap(CM_ADC1, ADC_CH5, ADC12_PIN_PA5);
2
1.6 使能ADC
ADC配置好之后并不能开始工作,还需要去使能,就是相当于有一个开关可以打开关闭。需要注意的是要先使能了ADC之后,才能开始进行自校准。
// ADC使能
ADC_ChCmd(CM_ADC1, ADC_SEQ_A, ADC_CH5, ENABLE);
2
2. 采集转换
采集数据函数:
/********************************\*\*********************************
- 函 数 名 称:Adc_Get_Value
- 函 数 说 明:采集一次adc数据
- 函 数 形 参:无
- 函 数 返 回:ADC采集的数值
- 作 者:LCKFB-LP
- 备 注:
********************************\*\*********************************/
uint16_t Adc_Get_Value(void)
{
static uint16_t adcValue;
\_\_IO uint32_t TimeOut = 0UL;
/* 只能启动序列 A 的转换。
序列 B 需要硬件触发才能启动转换。*/
ADC_Start(CM_ADC1);
// 等待ADC转换完成
while(SET != ADC_GetStatus(CM_ADC1, ADC_FLAG_EOCA))
{
// 如果超时
if(TimeOut > 500)
{
// 清除标志位
ADC_ClearStatus(CM_ADC1, ADC_FLAG_EOCA);
// 停止ADC转换
ADC_Stop(CM_ADC1);
// 打印错误信息
printf("ERROR = ADC 等待序列 A 转换【超时】!!\r\n");
// 返回0
return 0;
}
// 每次循环是延时1ms
TimeOut++;
delay_ms(1);
}
// 如果转换完成,清除标志位。
ADC_ClearStatus(CM_ADC1, ADC_FLAG_EOCA);
// 采集一次数据
adcValue = ADC_GetValue(CM_ADC1, ADC_CH5);
// 返回数据
return adcValue;
}
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
这个函数的主要目的是从 ADC(模数转换器)中采集一次数据。让我们逐步解释这个函数的主要步骤:
- 首先,函数定义了一个静态变量
adcValue
,用于存储 ADC 采集到的数值。这个变量被定义为静态是为了保留上一次采集的数值,以便在函数下次调用时使用。 - 接着,定义了一个名为
TimeOut
的变量,用于记录等待 ADC 转换完成的时间。这个变量初始化为0,会在等待转换完成的循环中递增。 - 调用
ADC_Start(CM_ADC1)
启动 ADC 转换。这里使用的是序列 A,因为序列 B 需要硬件触发才能启动转换。 - 进入一个循环,检查 ADC 转换是否完成。循环会持续等待,直到转换完成或者超时。如果超过了设定的时间(这里是500ms),就会认为转换超时。
- 如果转换超时,函数会执行相应的错误处理操作。首先会清除 ADC 转换完成标志位,然后停止 ADC 转换。最后,会打印错误信息并返回0。
- 如果转换成功完成,会清除 ADC 转换完成标志位,并调用 ADC_GetValue 函数获取 ADC 通道 5 的采样值。
- 最后,返回获取到的 ADC 采集数值。
总的来说,这个函数主要用于等待 ADC 转换完成,并返回采集到的数据。在等待转换完成的过程中,加入了超时处理,以避免程序长时间阻塞。 数据转换函数:
/********************************\*\*********************************
- 函 数 名 称:Get_Adc_Average
- 函 数 说 明:采集 \_\_Count 次adc数据,然后求平均值。
- 函 数 形 参:\_\_Count:采集次数
- 函 数 返 回:ADC采集的平均数值
- 作 者:LCKFB-LP
- 备 注:
********************************\*\*********************************/
uint16_t Get_Adc_Average(uint8_t **Count)
{
uint32_t adcValue = 0;
for(int i = 0; i < **Count; i++)
{
// 获取一次 ADC 数据
uint16_t tmp = Adc_Get_Value();
// 如果获取到的数据为0,说明采集失败,跳过本次循环
if(tmp == 0)
{
\_\_Count--;
continue;
}
// 将采集到的数据累加到 adcValue 变量中
adcValue += tmp;
}
// 计算平均值
return ( adcValue / __Count );
}
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
- 创建一个名为
adcValue
的无符号32位整型变量,用于累加采集到的 ADC 数据的总和。 - 使用
for
循环,循环次数由参数 __Count 指定,表示需要采集的次数。 - 在循环中,调用
Adc_Get_Value
函数获取一次 ADC 数据,并将其保存在名为tmp
的临时变量中。 - 检查临时变量
tmp
的值是否为0,如果为0,则说明 ADC 采集失败,此时减少循环次数\_\_Count
,并跳过本次循环。 - 如果临时变量
tmp
的值不为0,则将其累加到adcValue
变量中。 - 循环结束后,计算平均值,即将
adcValue
变量的值除以采集次数\_\_Count
,并返回计算结果。
3. 实验现象
在main.c中调用 uint16_t Get_Adc_Average(uint8_t __Count) ;函数,采集PA5引脚的电压,并通过串口输出。
int32_t main(void)
{
board_init(); // 初始化板载设备
uart1_init(115200U); // 初始化串口1,波特率为115200
Adc_Init(); // 初始化ADC
while(1)
{
printf("value = %d\r\n", Get_Adc_Average(20)); // 打印ADC采集的数值
delay_ms(1000); // 延时1秒
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
在灯光充足的情况下,ADC转换的值大约为4000左右,转换为实际电压接近3.3V。
当用手挡住光敏电阻后,采集到的值大约在60左右,转换为实际电压接近0V。
如果没有光敏电阻,则可以连接到3.3V或者GND进行测试。当接入3.3V时采集到的值接近4095;当接入GND时,采集到的值接近0。