第七部分:1. STM32之ADC实验--单通道实验(滑动变阻器调节电压)
主要利用一个模拟量的电位器来实时改变电压值,通过STM32自带的ADC通道来采集这个数据,并打印出来!本实验是单通道实验
一句话,学完STM32,我就往南走,我的工资只有5000.~~~~Whappy
实验代码:
AD.c
#include "stm32f10x.h" // Device headervoid AD_Init(void)
{RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE); //开启ADC1的时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);RCC_ADCCLKConfig(RCC_PCLK2_Div6); //配置ADC时钟分频器 GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; //ADC模拟输入模式GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA,&GPIO_InitStructure);//配置规则组的输入通道ADC_RegularChannelConfig(ADC1,ADC_Channel_0,1,ADC_SampleTime_55Cycles5);//配置ADC1模1块初始化ADC_InitTypeDef ADC_InitStructure;ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right ;ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;ADC_InitStructure.ADC_Mode = ADC_Mode_Independent ;ADC_InitStructure.ADC_NbrOfChannel = 1;ADC_InitStructure.ADC_ScanConvMode = DISABLE;ADC_Init(ADC1,&ADC_InitStructure);/*
ADC_ContinuousConvMode = DISABLE关闭连续转换模式。ADC在每次触发时仅进行一次转换,转换完成后停止,适用于单次采样的场合。
ADC_DataAlign = ADC_DataAlign_Right数据右对齐。ADC转换结果存储时,低位对齐方式,通常选择右对齐便于读取和处理。
ADC_ExternalTrigConv = ADC_ExternalTrigConv_None外部触发关闭。ADC转换不依赖外部触发源,适用于单次软件触发或内部定时触发的场景。
ADC_Mode = ADC_Mode_Independent独立模式。ADC模块工作在独立模式下,不与其他ADC模块联动。
ADC_NbrOfChannel = 1仅采样一个通道。配置当前ADC转换序列中包含的通道数量为1,适用于单通道采样的情况。
ADC_ScanConvMode = DISABLE关闭扫描转换模式。ADC仅对一个通道进行采样,不自动扫描多个通道。
*/ //中断//模拟看门狗//开启ADC电源ADC_Cmd(ADC1,ENABLE);//ADC校准ADC_ResetCalibration(ADC1);while((ADC_GetResetCalibrationStatus(ADC1) == SET));ADC_StartCalibration(ADC1);while((ADC_GetCalibrationStatus(ADC1)) == SET);}//启动转换,获取结果uint16_t AD_GetValue(void)
{ADC_SoftwareStartConvCmd(ADC1,ENABLE);while((ADC_GetFlagStatus(ADC1,ADC_FLAG_EOC) == RESET));return ADC_GetConversionValue(ADC1);
}
这段代码实现了在STM32F10x系列单片机中初始化并配置ADC(模数转换器),并通过软件触发启动ADC转换并获取结果。下面逐行分析代码:
1. 初始化ADC相关硬件
c
复制代码
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE); // 开启ADC1的时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // 开启GPIOA的时钟 RCC_ADCCLKConfig(RCC_PCLK2_Div6); // 配置ADC时钟分频器
RCC_APB2PeriphClockCmd()
:这两个函数调用用于开启ADC1和GPIOA的时钟,使得这些外设能够正常工作。RCC_ADCCLKConfig(RCC_PCLK2_Div6)
:配置ADC的时钟源和分频器,RCC_PCLK2_Div6
表示将PCLK2时钟分频为6,用于给ADC提供时钟。ADC通常需要较低的时钟频率,避免过快的采样导致精度下降。
2. GPIO配置
c
复制代码
GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; // 设置为模拟输入模式 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; // 配置PA0(GPIOA的第0引脚) GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // 设置引脚的最大输出速度 GPIO_Init(GPIOA, &GPIO_InitStructure); // 初始化GPIOA的引脚
- 通过这些设置,配置PA0(GPIOA的第0引脚)为模拟输入模式,供ADC进行采样。模拟输入模式意味着该引脚不会输出数字信号,而是用于接收模拟信号。
3. 配置ADC的转换通道
c
复制代码
ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5);
ADC_RegularChannelConfig()
:配置ADC的常规通道(Regular
),并指定使用的通道和采样时间。ADC_Channel_0
表示使用通道0(即PA0引脚连接的模拟信号)。1
表示该通道在转换序列中的位置(第1个通道)。ADC_SampleTime_55Cycles5
表示采样时间为55.5个时钟周期,这在采样较慢的信号时可以提高精度。
4. 初始化ADC
c
复制代码
ADC_InitTypeDef ADC_InitStructure; ADC_InitStructure.ADC_ContinuousConvMode = DISABLE; ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; ADC_InitStructure.ADC_NbrOfChannel = 1; ADC_InitStructure.ADC_ScanConvMode = DISABLE; ADC_Init(ADC1, &ADC_InitStructure);
ADC_ContinuousConvMode = DISABLE
:关闭连续转换模式。每次触发时,ADC仅进行一次转换,完成后停止,适合单次采样。ADC_DataAlign = ADC_DataAlign_Right
:设置ADC数据右对齐,这样在读取时可以直接获取较低的位,便于处理。ADC_ExternalTrigConv = ADC_ExternalTrigConv_None
:关闭外部触发信号,表示ADC转换不依赖外部信号触发,而是由软件或其他方式启动。ADC_Mode = ADC_Mode_Independent
:选择独立模式,表示ADC1单独工作,不与其他ADC模块联动。ADC_NbrOfChannel = 1
:配置ADC转换序列中只有一个通道。ADC_ScanConvMode = DISABLE
:关闭扫描模式,表示ADC仅会转换一个通道。
5. ADC电源开启和校准
c
复制代码
ADC_Cmd(ADC1, ENABLE); // 启动ADC1 ADC_ResetCalibration(ADC1); // 复位ADC1的校准 while ((ADC_GetResetCalibrationStatus(ADC1) == SET)); // 等待校准复位完成 ADC_StartCalibration(ADC1); // 启动校准 while ((ADC_GetCalibrationStatus(ADC1)) == SET); // 等待校准完成
ADC_Cmd(ADC1, ENABLE)
:启用ADC1,使ADC模块开始工作。ADC_ResetCalibration(ADC1)
:复位ADC的校准寄存器,清除可能的校准数据。ADC_StartCalibration(ADC1)
:启动ADC的校准过程,确保转换结果的精确性。- 等待校准完成:
while ((ADC_GetCalibrationStatus(ADC1)) == SET)
等待校准完成,确保ADC在进行实际采样前已被校准。
6. 获取ADC值
c
复制代码
uint16_t AD_GetValue(void) { ADC_SoftwareStartConvCmd(ADC1, ENABLE); // 启动ADC转换 while ((ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET)); // 等待转换完成 return ADC_GetConversionValue(ADC1); // 返回ADC转换结果 }
ADC_SoftwareStartConvCmd(ADC1, ENABLE)
:通过软件启动ADC1的转换。while ((ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET))
:等待转换完成。ADC_FLAG_EOC
是"End of Conversion"标志,表示ADC转换结束。当该标志被置位时,转换完成。return ADC_GetConversionValue(ADC1)
:返回ADC的转换结果。
总结
- 这段代码初始化并配置了STM32F10x的ADC1,将其设置为单通道(PA0),并进行校准。
- 通过
AD_GetValue()
函数启动ADC转换并返回转换结果。 - 整个过程包括了开启时钟、配置GPIO、初始化ADC、启动ADC转换及等待转换完成,确保ADC可以用于后续的模拟信号采样。
main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "AD.h"uint16_t Value;
float Voltage;int main(void)
{OLED_Init();AD_Init();OLED_ShowString(1,1,"Value:");OLED_ShowString(2,1,"Voltge:0.00V");while (1){Value = AD_GetValue();Voltage = (float)Value / 4095 * 3.3 ;OLED_ShowNum(1,7,Value,4);OLED_ShowNum(2,8,Voltage,1);OLED_ShowNum(2,10,(uint16_t)(Voltage * 100) % 100,2);Delay_ms(100);}
}
这个主函数结合了前面分析的ADC初始化、获取ADC值并显示到OLED屏幕上的功能。具体来说,代码使用OLED显示屏来实时显示ADC的数字值以及对应的电压值。
代码解析
- 头文件包含:
c
复制代码
#include "stm32f10x.h" // Device header #include "Delay.h" // 延时函数库 #include "OLED.h" // OLED显示库 #include "AD.h" // ADC驱动库
stm32f10x.h
:STM32F10x的设备头文件,包含所有外设的定义和函数声明。Delay.h
:延时函数头文件,包含用于延时的函数,例如Delay_ms
。OLED.h
:OLED显示相关函数头文件,包含初始化、显示数字、字符串等功能。AD.h
:ADC相关函数头文件,包含ADC初始化和获取ADC值的函数。
- 全局变量声明:
c
复制代码
uint16_t Value; // 用于存储ADC转换的原始值 float Voltage; // 用于存储电压值(计算得到)
main()
函数初始化:
c
复制代码
int main(void) { OLED_Init(); // 初始化OLED显示屏 AD_Init(); // 初始化ADC OLED_ShowString(1,1,"Value:"); // 在OLED屏幕的第一行显示"Value:" OLED_ShowString(2,1,"Voltge:0.00V"); // 在OLED屏幕的第二行显示"Voltage:0.00V" while (1) { Value = AD_GetValue(); // 获取ADC的转换值 Voltage = (float)Value / 4095 * 3.3 ; // 将ADC值转换为电压值,假设参考电压是3.3V,12位ADC分辨率为4095 // 显示ADC值 OLED_ShowNum(1,7,Value,4); // 将Value以4位数字显示在OLED的第1行第7列 // 显示电压值 OLED_ShowNum(2,8,Voltage,1); // 显示电压的整数部分(1位) OLED_ShowNum(2,10,(uint16_t)(Voltage * 100) % 100,2); // 显示电压的小数部分(2位) Delay_ms(100); // 延时100ms,防止屏幕更新过快 } }
代码详细解析
-
OLED 初始化:
c
复制代码
OLED_Init();
OLED_Init()
初始化OLED显示屏,设置显示模式、清空屏幕等。
-
ADC 初始化:
c
复制代码
AD_Init();
AD_Init()
函数初始化ADC,包括时钟配置、GPIO配置、ADC配置等(前面已经详细分析过)。
-
OLED 显示字符串:
c
复制代码
OLED_ShowString(1, 1, "Value:"); OLED_ShowString(2, 1, "Voltge:0.00V");
OLED_ShowString(1, 1, "Value:")
在OLED屏幕的第1行第1列显示"Value:"
,用于显示ADC的原始值。OLED_ShowString(2, 1, "Voltge:0.00V")
在第2行第1列显示"Voltage:0.00V"
,用于显示电压值。
-
主循环:
c
复制代码
while (1) { Value = AD_GetValue(); // 获取ADC值 Voltage = (float)Value / 4095 * 3.3; // 将ADC值转换为电压值 OLED_ShowNum(1, 7, Value, 4); // 显示ADC原始值 OLED_ShowNum(2, 8, Voltage, 1); // 显示电压值的整数部分 OLED_ShowNum(2, 10, (uint16_t)(Voltage * 100) % 100, 2); // 显示电压值的小数部分 Delay_ms(100); // 延时100ms }
AD_GetValue()
函数通过ADC获取一个模拟信号的数字值,返回一个12位的ADC转换值,范围从 0 到 4095。- ADC到电压的转换:
- 由于ADC的最大值为 4095(12位分辨率),而参考电压为 3.3V,使用公式: Voltage=Value4095×3.3\text{Voltage} = \frac{\text{Value}}{4095} \times 3.3Voltage=4095Value×3.3
- 计算得到的电压值被存储在
Voltage
变量中。
- OLED显示:
OLED_ShowNum(1, 7, Value, 4)
:显示ADC值,显示位置是第1行第7列,最多显示4位数字(Value
是一个uint16_t
,即0到4095范围)。OLED_ShowNum(2, 8, Voltage, 1)
:显示电压的整数部分,显示位置是第2行第8列,显示1位数字。OLED_ShowNum(2, 10, (uint16_t)(Voltage * 100) % 100, 2)
:显示电压的小数部分,显示位置是第2行第10列,显示2位数字。
- 延时:
Delay_ms(100)
用于延时100ms,防止OLED屏幕刷新过快,使得显示的内容更容易被人眼观察。
总结
- 功能:这个主程序通过获取ADC1的值并将其转换为电压值,显示在OLED屏幕上。通过持续读取ADC值并转换成电压值,不断更新OLED显示,实时显示采样的模拟信号的数值和电压。
- OLED显示:在OLED屏幕上显示了ADC值和电压值,电压值保留两位小数,且实时更新。
- ADC的精度:假设使用的是12位ADC(分辨率为4095),通过将读取到的数字值映射到3.3V的电压范围来显示实际电压。
可改进的地方
- 电压计算:可以在电压计算中加入更高精度的浮点计算,确保显示的电压值准确。
- 数据刷新:可以增加ADC转换触发的频率,使得显示的电压值更新更快,适应不同应用场景。
- OLED显示优化:如果显示的内容较多,可以考虑增加滚动或分页显示等功能。