基于 STM32 利用 ADC(模数转换器)测量光照程度并通过串口输出

一、硬件准备

  1. STM32 开发板。
  2. 光照传感器(通常是光敏电阻或光电二极管等),并连接到 STM32 开发板的 ADC 输入引脚。

二、软件实现

  1. 配置开发环境:

    • 安装 STM32 的开发工具,如 Keil MDK 等。
    • 创建一个新的工程。
  2. 配置 ADC:

    • 开启 ADC 时钟。在 STM32 中,不同型号的芯片开启时钟的方式可能略有不同,但通常可以通过 RCC(Reset and Clock Control)寄存器来实现。
    • 配置 ADC 的采样时间、分辨率等参数。可以根据实际需求选择合适的采样时间和分辨率。例如,如果需要更高的精度,可以选择较长的采样时间和较高的分辨率。
    • 选择 ADC 的输入通道,连接到光照传感器的引脚。
  3. 配置串口:

    • 开启串口时钟。
    • 配置串口的波特率、数据位、停止位等参数。
    • 使能串口发送和接收功能。
  4. 读取 ADC 值并转换为光照程度:

    • 启动 ADC 转换。可以通过设置相应的寄存器来启动 ADC 转换。
    • 等待 ADC 转换完成。可以通过查询标志位或使用中断的方式来等待转换完成。
    • 读取 ADC 的转换结果。转换结果通常是一个数字值,表示输入电压的大小。
    • 根据光照传感器的特性,将 ADC 值转换为光照程度。这通常需要通过实验或查阅传感器的数据手册来确定转换公式。例如,如果光照传感器的输出电压与光照程度成线性关系,可以使用线性插值的方法进行转换。
  5. 通过串口输出光照程度:

    • 将光照程度转换为字符串格式。可以使用 sprintf 等函数将数字转换为字符串。
    • 使用串口发送函数将字符串发送出去。可以使用 USART_SendData 等函数将字符串逐个字符发送出去。

例子:基于STM32F405做一个光照收集并在串口一输出的

(关于初始化的,可以看芯片手册,中文手册,权威指南)

不知道该配置的一定要多看这些资料,以及原理图(有查不到的可以私信,我帮找找看)

adc.h 中的ADC初始化:

#include "adc.h"
#include "stm32f4xx.h"                  // Device header
void adc_1_init(void)
{
    // 使能 GPIOC 的时钟(AHB1 总线)
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC, ENABLE);
    // 使能 ADC1 的时钟(APB2 总线)
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);

    GPIO_InitTypeDef GPIO_InitStruct = {0};
    // 设置 GPIO 模式为模拟模式,用于连接光敏传感器
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AN;
    // 选择引脚 PC0
    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0;
    // 不使用上拉或下拉电阻
    GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL;
    // 初始化 GPIOC 的引脚配置
    GPIO_Init(GPIOC, &GPIO_InitStruct);

    ADC_InitTypeDef ADC_InitStruct = {0};
    // 禁用连续转换模式
    ADC_InitStruct.ADC_ContinuousConvMode = DISABLE;
    // 设置数据对齐方式为右对齐
    ADC_InitStruct.ADC_DataAlign = ADC_DataAlign_Right;
    // 设置转换序列长度为 1,即只进行一次转换
    ADC_InitStruct.ADC_NbrOfConversion = 1;
    // 设置 ADC 分辨率为 12 位
    ADC_InitStruct.ADC_Resolution = ADC_Resolution_12b;
    // 禁用扫描模式,只对一个通道进行转换
    ADC_InitStruct.ADC_ScanConvMode = DISABLE;
    // 初始化 ADC1 的参数
    ADC_Init(ADC1, &ADC_InitStruct);

    ADC_CommonInitTypeDef ADC_CommonInitStruct = {0};
    // 设置 ADC 预分频器为 4,调整 ADC 时钟频率
    ADC_CommonInitStruct.ADC_Prescaler = ADC_Prescaler_Div4;
    // 设置两个采样之间的延迟为 5 个 ADC 时钟周期
    ADC_CommonInitStruct.ADC_TwoSamplingDelay = ADC_TwoSamplingDelay_5Cycles;
    // 初始化 ADC 的通用参数
    ADC_CommonInit(&ADC_CommonInitStruct);

    // 以下是中断相关配置,这里注释掉了,暂时不需要中断功能,开了中断反而有点输出不顺畅
    // NVIC_InitTypeDef NVIC_InitStruct = {0};
    // 设置中断通道为 18,对应 ADC1 的中断
    // NVIC_InitStruct.NVIC_IRQChannel = 18;
    // 使能中断通道
    // NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
    // 设置中断优先级为 2,子优先级为 2
    // NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 2;
    // NVIC_InitStruct.NVIC_IRQChannelSubPriority = 2;
    // 初始化中断控制器
    // NVIC_Init(&NVIC_InitStruct);

    // 使能 ADC1,准备进行转换
    ADC_Cmd(ADC1, ENABLE);
}

adc.h中的获取数据的服务函数:

int adc_1_GetFlagStatus(void)
{

    int i = 0;
    float a[10];
    float temp;

    // 进行 10 次采样
    for (i = 0; i < 10; i++)
    {
        // 配置 ADC1 的规则通道为通道 10,采样顺序为 1,采样时间为 480 个 ADC 时钟周期
        ADC_RegularChannelConfig(ADC1, ADC_Channel_10, 1, ADC_SampleTime_480Cycles);
        // 软件启动 ADC1 的转换
        ADC_SoftwareStartConv(ADC1);
        // 等待转换结束标志位 ADC_FLAG_EOC 被置位,表示转换完成
        while (!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC)) {}
        // 将转换结果存储到数组 a 中
        a[i] = ADC_GetConversionValue(ADC1);
    }

    // 对采样结果进行冒泡排序
    for (int i = 0; i < 10 - 1; i++)
    {
        for (int j = 0; j < 10 - i - 1; j++)
        {
            if (a[j] > a[j + 1])
            {
                temp = a[j];
                a[j] = a[j + 1];
                a[j + 1] = temp;
            }
        }
    }

    // 返回排序后的中间两个值的平均值
    return (a[4] + a[5]) / 2;
}

对于这个冒泡排序是希望数据不要出现偶然性,比如,突然出现一个很大的数据反而影响我们想要的数据,当然,这个冒泡取中值我感觉也不算完美,期待大家改进。

串口一的初始化以及服务函数:

#include "usart1.h"

void usart1_init(u32 baud)
{
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
	//初始化配置
	GPIO_InitTypeDef GPIO_InitStruct={0};
	GPIO_InitStruct.GPIO_Mode  = GPIO_Mode_AF;//通用输入模式
	GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;//推挽
	GPIO_InitStruct.GPIO_Pin   = GPIO_Pin_9 |GPIO_Pin_10;//引脚
	GPIO_InitStruct.GPIO_PuPd  = GPIO_PuPd_NOPULL;//无上下拉模式
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_2MHz;//速度
	
  GPIO_Init( GPIOA, & GPIO_InitStruct);//IO口初始化
  
	GPIO_PinAFConfig(GPIOA, GPIO_PinSource9,  GPIO_AF_USART1);
	GPIO_PinAFConfig(GPIOA, GPIO_PinSource10,  GPIO_AF_USART1);//映射

	USART_InitTypeDef USART_InitStruct   ={0};
	//波特率
	USART_InitStruct.USART_BaudRate      =baud;//最高不能超过2M
	USART_InitStruct.USART_Mode          =USART_Mode_Rx |USART_Mode_Tx;//接收和发送
	//帧格式
	USART_InitStruct.USART_Parity        =USART_Parity_No ;//无校验
	USART_InitStruct.USART_StopBits      =USART_StopBits_1;//1位 停止
	USART_InitStruct.USART_WordLength    =USART_WordLength_8b;//数据8位
	//硬件流控
	//
	//选择接收和空闲中断源
	USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
  USART_ITConfig(USART1, USART_IT_IDLE, ENABLE);
	//选择中断和开中断
	NVIC_InitTypeDef NVIC_InitStruct={0};
	NVIC_InitStruct.NVIC_IRQChannel                     =37; //中断编号(向量表),主控头文件
	NVIC_InitStruct.NVIC_IRQChannelCmd							    =ENABLE;      //开中断
	NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority =2 ;  //抢占优先级
	NVIC_InitStruct.NVIC_IRQChannelSubPriority				=2;
	NVIC_Init(& NVIC_InitStruct);

	USART_Init(USART1, &USART_InitStruct);//USART口初始化
  //开启串口一--USART1
	USART_Cmd(USART1, ENABLE);
}

/*中断服务函数--函数名固定的--启动文件 .s*/
char sp[256]={0};
int USART1_Flag=0;//接收完成标记位
int USART1_XB=0;//数组下标
void USART1_IRQHandler(void)
{
  //判断是接收中断-USART
	if(USART_GetITStatus(USART1, USART_IT_RXNE)==SET)
	{
	sp[USART1_XB++]=	USART_ReceiveData(USART1);//读取数据,并清接收标记位
  if(USART1_XB>255) USART1_XB=0;//数组下标不能超过最值
	}
	//判断是空闲中断-USART
	else if(USART_GetITStatus(USART1, USART_IT_IDLE)==SET)
	{
		sp[USART1_XB]='\0';//转成字符串数组
		USART1_Flag=1;//标记接收完成
		USART_ReceiveData(USART1);//清空闲中断标记
	}
}

/*发送接收*/
void usart1_send(char sp)//发送一个字符函数
{
	//等待上次发送完成
	while(USART_GetFlagStatus(USART1, USART_FLAG_TC)==RESET);//TC
	USART_SendData(USART1, sp);
}

void usart1_send_str(char *sp)//芯片发送一个指针,达到连续输入字符效果
{
  while(*sp!='\0')//循环
		usart1_send(*sp++);
}
//接收一个字符函数
char usart1_receive(void)
{
	//等待接收完成完成--USART_GetFlagStatus
	//return USART_ReceiveData()
  while(USART_GetFlagStatus(USART1,USART_FLAG_RXNE)==RESET);
	return USART_ReceiveData(USART1);
}

void usart1_receive_str(char *sp)//接收电脑发送的字符串并存入到字符指针
{
	int i=0;
  while(1)
	{
	sp[i]=usart1_receive();//接收一个字符
		if(sp[i]=='\r'||sp[i]=='\n')//判断是否接收完毕,串口发送完毕会最后发送回车和换行
		{
			sp[i]='\0';//跳出上面芯片发送的循环
			return ;//跳出当前循环
		}
		i++;//循环接收
		if(i>255) i=0;
	}
}

主函数:


int main()
{
	systick_init(168);//系统滴答初始化,我的延迟函数在里面
	usart1_init(115200);//115200是波特率
	adc_1_init();
    while(1)
    {
 
        printf("光照强度:%.2f%%\r\n",(1-adc_1_GetFlagStatus()/4095.0)*100); //获取的数据转化 
                                                                            //成百分比
        delay_ms(200);   
    }
}

串口收集到的数据:

到此就结束咯,如果有还觉得不过瘾的友友,还可以拿温度传感器或者火焰传感器来练练手,打印输出收集到的数据!操作都差不多的!

今天就到这啦!文中若有哪些有误的,恳请友友们能在评论区指出!感激不尽!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值