【STM32】ADC

目录

  • [1 ADC介绍](#1 ADC介绍)
    • [1.1 什么是ADC?](#1.1 什么是ADC?)
    • [1.2 ADC工作原理(逐次逼近型)](#1.2 ADC工作原理(逐次逼近型))
    • [1.3 ADC特性参数](#1.3 ADC特性参数)
  • [2 ADC框图](#2 ADC框图)
  • [3 ADC的一些细节](#3 ADC的一些细节)
    • [3.1 输入通道](#3.1 输入通道)
    • [3.2 规则组/注入组](#3.2 规则组/注入组)
    • [3.3 转换顺序](#3.3 转换顺序)
    • [3.4 触发转换方法](#3.4 触发转换方法)
    • [3.5 转换时间](#3.5 转换时间)
    • [3.6 中断及事件](#3.6 中断及事件)
    • [3.7 校准](#3.7 校准)
    • [3.8 单次转换和连续转换](#3.8 单次转换和连续转换)
    • [3.9 扫描模式](#3.9 扫描模式)
  • [4. ADC寄存器及库函数介绍](#4. ADC寄存器及库函数介绍)
  • 小实验1:ADC单通道采集实验
  • 小实验2:ADC单通道采集实验+DMA读取
  • 小实验3:ADC多通道采集实验+DMA读取
  • 项目:吸烟室管控系统

1 ADC介绍

1.1 什么是ADC?

全称:Analog-to-Digital Converter,指模拟/数字转换器。

ADC可以将引脚上连续变化的模拟电压转换为内存中存储的数字变量,建立模拟电路到数字电路的桥梁。

12 位 ADC 是一种逐次逼近型模拟数字转换器(0~4095)。它有多达 18 个通道,可测量 16 个外部和 2 个内部信号源。各通道的 A/D 转换可以单次、连续、扫描或间断模式执行。 ADC 的结果可以左对齐或右对齐方式存储在 16 位数据寄存器中。

模拟看门狗特性允许应用程序检测输入电压是否超出用户定义的高/低阀值。

ADC 的输入时钟不得超过 14MHz,它是由 PCLK2 经分频产生。

STM32F103C8T6 ADC资源:ADC1、ADC2,10 个外部输入通道。

1.2 ADC工作原理(逐次逼近型)

1.3 ADC特性参数

ADC的性能指标

  • 量程:能测量的电压范围
  • 分辨率:ADC能辨别的最小模拟量,通常以输出二进制数的位数表示,比如:8、10、12、16位等;位数越多,分辨率越高,一般来说分辨率越高,转化时间越长
  • 转化时间(采样时间):从转换开始到获得稳定的数字量输出所需要的时间称为转换时间。转换时间越长,转换结果相对越准确,但是转换速度就越慢

ADC特性

  • 12 位精度下转换速度可高达1MHZ
  • 供电电压:VSSA :0V,VDDA :2.4V~3.6V
  • ADC输入范围:VREF- ≤ VIN ≤ VREF+,0~3.3V
  • ADC 的结果可以左对齐或右对齐方式存储在 16 位数据寄存器中

2 ADC框图

3 ADC的一些细节

3.1 输入通道

总共 2 个 ADC(ADC1,ADC2),每个 ADC 有 18 个转换通道: 16 个外部通道、 2 个内部通道(温度传感器、内部参考电压)。

外部的16个通道在转换时又分为规则通道和注入通道,其中规则通道最多有16路,注入通道最多有4路。

3.2 规则组/注入组

3.3 转换顺序

每个 ADC 规则通道只有一个数据寄存器,16个通道一起共用这个寄存器,所以需要指定规则转换通道的转换顺序。

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

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

注入序列的转换顺序是从JSQx[ 4 : 0 ](x=4-JL[1:0])开始。只有当JL=4的时候,注入通道的转换顺序才会按照JSQ1、JSQ2、JSQ3、JSQ4的顺序执行。

3.4 触发转换方法

  1. 通过向控制寄存器 ADC-CR2 的 ADON 位写 1 来开启 ADC ,再将 SWSTART 位置 1 ,启动规则通道转换。
  2. 也可以通过外部事件(如定时器)进行转换。

3.5 转换时间

ADC 是挂载在 APB2 总线(PCLK2)上的,经过分频器得到 ADC 时钟(ADCCLK),最高 14 MHz。

c 复制代码
转换时间=采样时间+12.5个周期

12.5个周期是固定的,一般我们设置 PCLK2=72M,经过 ADC 预分频器能分频到最大的时钟只能是 12M,采样周期设置为 1.5 个周期,算出最短的转换时间为 1.17us。

3.6 中断及事件

DMA请求(只适用于规则组)

规则组每个通道转换结束后,除了可以产生中断外,还可以产生DMA请求,我们利用DMA及时把转换好的数据传输到指定的内存里,防止数据被覆盖。

3.7 校准

ADC有一个内置自校准模式。校准可大幅减小因内部电容器组的变化而造成的准精度误差。在校准期间,在每个电容器上都会计算出一个误差修正码(数字值),这个码用于消除在随后的转换中每个电容器上产生的误差。

通过设置ADC_CR2寄存器的CAL位启动校准。一旦校准结束, CAL位被硬件复位,可以开始正常转换。建议在上电时执行一次ADC校准。校准阶段结束后,校准码储存在ADC_DR中。

建议在每次上电后执行一次校准。

3.8 单次转换和连续转换

单次转换:只转换一次

连续转换:转换一次之后,立马进行下一次转换

3.9 扫描模式

关闭扫描模式:只转换ADC_SQRx或ADC_JSQR选中的第一个通道

打开扫描模式:扫描所有被ADC_SQRx或ADC_JSQR选中的所有通道

4. ADC寄存器及库函数介绍





只列举部分,具体请查看STM32中文参考手册。

小实验1:ADC单通道采集实验

实验目的

使用 ADC1 采集通道 1 的电压值,通道 1 连接光敏电阻传感器。

流程

代码

main.c

c 复制代码

adc.c

c 复制代码

adc.h

c 复制代码

小实验2:ADC单通道采集实验+DMA读取

实验目的

使用 ADC1 采集通道 1 的电压值+DMA读取,通道 1 连接光敏电阻传感器。

流程

代码

main.c

c 复制代码
#include "sys.h"
#include "uart1.h"
#include "delay.h"
#include "led.h"
#include "uart1.h"
#include "adc.h"

uint16_t adc_result = 0;

int main(void)
{
    HAL_Init();                         /* 初始化HAL库 */
    stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */      
    //LED初始化
    led_init();
    //串口1初始化
    uart1_init(115200);
    //adc相关初始化
    adc_dma_init((uint32_t *)&adc_result);
    printf("打印测试:hello world\r\n");
    
    while(1)
    {
        //打印ADC转换结果
        printf("adc result: %f\r\n",(float)adc_result/4096 * 3.3);
        delay_ms(500);
    }
}

adc.c

c 复制代码
#include "adc.h"

//ADC句柄
ADC_HandleTypeDef adc_handle = {0};
//DMA句柄
DMA_HandleTypeDef dma_handle = {0};

//ADC配置
void adc_config(void)
{
    adc_handle.Instance = ADC1;                            //选择ADC1
    adc_handle.Init.DataAlign = ADC_DATAALIGN_RIGHT;       //数据右对齐
    adc_handle.Init.ScanConvMode = ADC_SCAN_DISABLE;       //不扫描
    adc_handle.Init .ContinuousConvMode = ENABLE;          //连续转换
    adc_handle.Init.NbrOfConversion = 1;                   //转换个数为1
    adc_handle.Init.DiscontinuousConvMode = DISABLE;       //不采用间断模式           
    adc_handle.Init.NbrOfDiscConversion = 0;               //间断模式个数为0
    adc_handle.Init.ExternalTrigConv = ADC_SOFTWARE_START; //采用软件触发
    
    HAL_ADC_Init(&adc_handle);  //调用初始化函数
    
    //ADC校准
    HAL_ADCEx_Calibration_Start(&adc_handle);
}
//DMA配置
void dma_config()
{
    __HAL_RCC_DMA1_CLK_ENABLE();
    
    dma_handle.Instance = DMA1_Channel1;                //DMA1通道5
    dma_handle.Init.Direction = DMA_PERIPH_TO_MEMORY;   //从外设到内存
    //内存相关配置
    dma_handle.Init   .MemDataAlignment = DMA_MDATAALIGN_HALFWORD;    //数据对齐方式
    dma_handle.Init.MemInc = DMA_MINC_ENABLE;                         //数据增长方式
    //外设相关配置
    dma_handle.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD; //数据对齐方式
    dma_handle.Init.PeriphInc = DMA_PINC_DISABLE;                  //数据增长方式
    //优先级和模式
    dma_handle.Init.Priority =DMA_PRIORITY_MEDIUM;
    dma_handle.Init.Mode = DMA_CIRCULAR;    //循环搬运
    //初始化函数
    HAL_DMA_Init(&dma_handle);
    
    //链接串口和DMA
    __HAL_LINKDMA(&adc_handle, DMA_Handle, dma_handle);
}
//ADC相关硬件配置
void HAL_ADC_MspInit(ADC_HandleTypeDef* hadc)
{
    //确认是否为ADC1
    if(hadc->Instance == ADC1){
        RCC_PeriphCLKInitTypeDef  adc_clk_init = {0}; 
        GPIO_InitTypeDef gpio_init_struct = {0};
        //开启ADC1时钟
        __HAL_RCC_ADC1_CLK_ENABLE();
        //使能GPIOA时钟
        __HAL_RCC_GPIOA_CLK_ENABLE();
        //设置PIN1口,模拟输入模式
        gpio_init_struct.Pin = GPIO_PIN_1;
        gpio_init_struct.Mode = GPIO_MODE_ANALOG;
        //GPIO初始化
        HAL_GPIO_Init(GPIOA,&gpio_init_struct);
        //设置外设时钟选择为RCC外设ADC时钟
        adc_clk_init.PeriphClockSelection = RCC_PERIPHCLK_ADC;
        //设置adc时钟分频因子 为6分频
        adc_clk_init.AdcClockSelection = RCC_ADCPCLK2_DIV6;
        //配置外设时钟
        HAL_RCCEx_PeriphCLKConfig(&adc_clk_init);
    }
} 
//ADC通道配置
void adc_channel_config(ADC_HandleTypeDef* hadc, uint32_t ch,uint32_t rank, uint32_t stime)
{ 
    ADC_ChannelConfTypeDef adc_ch_config = {0};
    //设置adc通道参数
    adc_ch_config.Channel = ch;     //设置通道
    adc_ch_config.Rank = rank;      //设置通道次序
    adc_ch_config.SamplingTime = stime;     //设置采样时间
    //通道配置
    HAL_ADC_ConfigChannel(hadc, &adc_ch_config);
}
//获取ADC的值
uint32_t adc_get_result(uint32_t ch)
{
    //配置ADC通道
    adc_channel_config(&adc_handle, ch, ADC_REGULAR_RANK_1, ADC_SAMPLETIME_239CYCLES_5);
    //开始ADC转换
    HAL_ADC_Start(&adc_handle);
    //轮询方式等待ADC转换完成
    HAL_ADC_PollForConversion(&adc_handle, 10);
    //获取ADC转换结果
    return (uint16_t)HAL_ADC_GetValue(&adc_handle);
}
//ADC和DMA初始化
void adc_dma_init(uint32_t *mar)
{
    //ADC配置
    adc_config();
    //配置ADC通道
    adc_channel_config(&adc_handle, ADC_CHANNEL_1, ADC_REGULAR_RANK_1, ADC_SAMPLETIME_239CYCLES_5);
    //DMA配置
    dma_config();
    //开始ADC-DMA转换
    HAL_ADC_Start_DMA(&adc_handle, mar, 1);
}

adc.h

c 复制代码
#ifndef __ADC_H__
#define __ADC_H__

#include "sys.h"

//ADC和DMA初始化
void adc_dma_init(uint32_t *mar);

#endif

小实验3:ADC多通道采集实验+DMA读取

实验目的

使用 ADC1 采集通道 0 ~3 的电压值+DMA读取,通道 1 连接光敏电阻传感器

代码

main.c

c 复制代码
#include "sys.h"
#include "uart1.h"
#include "delay.h"
#include "led.h"
#include "uart1.h"
#include "adc.h"

uint16_t adc_result[4] = {0};

int main(void)
{
    HAL_Init();                         /* 初始化HAL库 */
    stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */      
    //LED初始化
    led_init();
    //串口1初始化
    uart1_init(115200);
    //adc相关初始化
    adc_dma_init((uint32_t *)&adc_result);
    printf("打印测试:hello world\r\n");
    
    while(1)
    {
        //打印ADC转换结果
        printf("通道0电压: %f\r\n",(float)adc_result[0]/4096 * 3.3);
        printf("通道1电压: %f\r\n",(float)adc_result[1]/4096 * 3.3);
        printf("通道2电压: %f\r\n",(float)adc_result[2]/4096 * 3.3);
        printf("通道3电压: %f\r\n\r\n",(float)adc_result[3]/4096 * 3.3);
        delay_ms(1000);
    }
}

adc.c

c 复制代码
#include "adc.h"

//ADC句柄
ADC_HandleTypeDef adc_handle = {0};
//DMA句柄
DMA_HandleTypeDef dma_handle = {0};

//ADC配置
void adc_config(void)
{
    adc_handle.Instance = ADC1;                            //选择ADC1
    adc_handle.Init.DataAlign = ADC_DATAALIGN_RIGHT;       //数据右对齐
    adc_handle.Init.ScanConvMode = ADC_SCAN_ENABLE;        //扫描
    adc_handle.Init .ContinuousConvMode = ENABLE;          //连续转换
    adc_handle.Init.NbrOfConversion = 4;                   //转换个数为1
    adc_handle.Init.DiscontinuousConvMode = DISABLE;       //不采用间断模式           
    adc_handle.Init.NbrOfDiscConversion = 0;               //间断模式个数为0
    adc_handle.Init.ExternalTrigConv = ADC_SOFTWARE_START; //采用软件触发
    
    HAL_ADC_Init(&adc_handle);  //调用初始化函数
    
    //ADC校准
    HAL_ADCEx_Calibration_Start(&adc_handle);
}
//DMA配置
void dma_config()
{
    __HAL_RCC_DMA1_CLK_ENABLE();
    
    dma_handle.Instance = DMA1_Channel1;                //DMA1通道5
    dma_handle.Init.Direction = DMA_PERIPH_TO_MEMORY;   //从外设到内存
    //内存相关配置
    dma_handle.Init   .MemDataAlignment = DMA_MDATAALIGN_HALFWORD;    //数据对齐方式
    dma_handle.Init.MemInc = DMA_MINC_ENABLE;                         //数据增长方式
    //外设相关配置
    dma_handle.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD; //数据对齐方式
    dma_handle.Init.PeriphInc = DMA_PINC_DISABLE;                  //数据增长方式
    //优先级和模式
    dma_handle.Init.Priority =DMA_PRIORITY_MEDIUM;
    dma_handle.Init.Mode = DMA_CIRCULAR;    //循环搬运
    //初始化函数
    HAL_DMA_Init(&dma_handle);
    
    //链接串口和DMA
    __HAL_LINKDMA(&adc_handle, DMA_Handle, dma_handle);
}
//ADC相关硬件配置
void HAL_ADC_MspInit(ADC_HandleTypeDef* hadc)
{
    //确认是否为ADC1
    if(hadc->Instance == ADC1){
        RCC_PeriphCLKInitTypeDef  adc_clk_init = {0}; 
        GPIO_InitTypeDef gpio_init_struct = {0};
        //开启ADC1时钟
        __HAL_RCC_ADC1_CLK_ENABLE();
        //使能GPIOA时钟
        __HAL_RCC_GPIOA_CLK_ENABLE();
        //设置PIN1口,模拟输入模式
        gpio_init_struct.Pin = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3;
        gpio_init_struct.Mode = GPIO_MODE_ANALOG;
        //GPIO初始化
        HAL_GPIO_Init(GPIOA,&gpio_init_struct);
        //设置外设时钟选择为RCC外设ADC时钟
        adc_clk_init.PeriphClockSelection = RCC_PERIPHCLK_ADC;
        //设置adc时钟分频因子 为6分频
        adc_clk_init.AdcClockSelection = RCC_ADCPCLK2_DIV6;
        //配置外设时钟
        HAL_RCCEx_PeriphCLKConfig(&adc_clk_init);
    }
} 
//ADC通道配置
void adc_channel_config(ADC_HandleTypeDef* hadc, uint32_t ch,uint32_t rank, uint32_t stime)
{ 
    ADC_ChannelConfTypeDef adc_ch_config = {0};
    //设置adc通道参数
    adc_ch_config.Channel = ch;     //设置通道
    adc_ch_config.Rank = rank;      //设置通道次序
    adc_ch_config.SamplingTime = stime;     //设置采样时间
    //通道配置
    HAL_ADC_ConfigChannel(hadc, &adc_ch_config);
}
//获取ADC的值
uint32_t adc_get_result(uint32_t ch)
{
    //配置ADC通道
    adc_channel_config(&adc_handle, ch, ADC_REGULAR_RANK_1, ADC_SAMPLETIME_239CYCLES_5);
    //开始ADC转换
    HAL_ADC_Start(&adc_handle);
    //轮询方式等待ADC转换完成
    HAL_ADC_PollForConversion(&adc_handle, 10);
    //获取ADC转换结果
    return (uint16_t)HAL_ADC_GetValue(&adc_handle);
}
//ADC和DMA初始化
void adc_dma_init(uint32_t *mar)
{
    //ADC配置
    adc_config();
    //配置ADC通道
    adc_channel_config(&adc_handle, ADC_CHANNEL_1, ADC_REGULAR_RANK_1, ADC_SAMPLETIME_239CYCLES_5);
    adc_channel_config(&adc_handle, ADC_CHANNEL_2, ADC_REGULAR_RANK_2, ADC_SAMPLETIME_239CYCLES_5);
    adc_channel_config(&adc_handle, ADC_CHANNEL_3, ADC_REGULAR_RANK_3, ADC_SAMPLETIME_239CYCLES_5);
    adc_channel_config(&adc_handle, ADC_CHANNEL_4, ADC_REGULAR_RANK_4, ADC_SAMPLETIME_239CYCLES_5);
    //DMA配置
    dma_config();
    //开始ADC-DMA转换
    HAL_ADC_Start_DMA(&adc_handle, mar, 4);
}

adc.h

c 复制代码
#ifndef __ADC_H__
#define __ADC_H__

#include "sys.h"

//ADC和DMA初始化
void adc_dma_init(uint32_t *mar);

#endif

项目:吸烟室管控系统

项目需求

  1. 使用 mq-2 获取环境烟雾值,并显示在 LCD1602 上;
  2. 按键修改阈值,并显示在 LCD1602 上;
  3. 烟雾值超过阈值时,蜂鸣器长响,风扇打开;烟雾值小于阈值时,蜂鸣器响,风扇关闭;

硬件清单

LCD1602

mq-2

蜂鸣器

继电器(模拟风扇)

按键

杜邦线

STM32

ST-Link

USB转TTL

硬件接线

项目框图

完整代码

main.c

c 复制代码
#include "sys.h"
#include "uart1.h"
#include "delay.h"
#include "led.h"
#include "uart1.h"
#include "adc.h"
#include "beep.h"
#include "fan.h"
#include "key.h"
#include "lcd1602.h"

int main(void)
{
    float smoke_value = 0;
    float limit_value = 1.0;
    uint8_t key_num = 0;
    
    
    HAL_Init();                         /* 初始化HAL库 */
    stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */      
    //LED初始化
    led_init();
    //串口1初始化
    uart1_init(115200);
    //ADC初始化
    adc_dma_init();
    //蜂鸣器初始化
    beep_init();
    //风扇初始化
    fan_init();
    //按键初始化
    key_init();
    //LCD1602初始化
    lcd1602_init();
    printf("打印测试:hello world\r\n");
    //测试
    //lcd1602_show_line(1, 1, "Smoking:   0.0 V");
    //lcd1602_show_line(1, 2, "Limit: - 0.0 + ");
    
    while(1)
    {
        //获取按下键的值
        key_num = key_scan();
        //比较判断
        if(key_num == 1){
            limit_value += 0.1;
        }else if(key_num == 2){
            limit_value -= 0.1;
        }
        //屏幕显示烟雾阈值
        lcd1602_display_limit(limit_value);
        //获取烟雾值
        smoke_value = adc_get_smoke();
        //屏幕显示烟雾值
        lcd1602_display_smoke(smoke_value);
        //比较判断
        if(smoke_value > limit_value){
            beep1_on();
            fan_on();
        }else{
            beep1_off();
            fan_off();
        }
    }
}

adc.c

c 复制代码
#include "adc.h"

//定义ADC值
uint16_t adc_value = 0;

//ADC句柄
ADC_HandleTypeDef adc_handle = {0};
//DMA句柄
DMA_HandleTypeDef dma_handle = {0};

//ADC配置
void adc_config(void)
{
    adc_handle.Instance = ADC1;                            //选择ADC1
    adc_handle.Init.DataAlign = ADC_DATAALIGN_RIGHT;       //数据右对齐
    adc_handle.Init.ScanConvMode = ADC_SCAN_DISABLE;       //不扫描
    adc_handle.Init .ContinuousConvMode = ENABLE;          //连续转换
    adc_handle.Init.NbrOfConversion = 1;                   //转换个数为1
    adc_handle.Init.DiscontinuousConvMode = DISABLE;       //不采用间断模式           
    adc_handle.Init.NbrOfDiscConversion = 0;               //间断模式个数为0
    adc_handle.Init.ExternalTrigConv = ADC_SOFTWARE_START; //采用软件触发
    
    HAL_ADC_Init(&adc_handle);  //调用初始化函数
    
    //ADC校准
    HAL_ADCEx_Calibration_Start(&adc_handle);
}
//DMA配置
void dma_config()
{
    __HAL_RCC_DMA1_CLK_ENABLE();
    
    dma_handle.Instance = DMA1_Channel1;                //DMA1通道1
    dma_handle.Init.Direction = DMA_PERIPH_TO_MEMORY;   //从外设到内存
    //内存相关配置
    dma_handle.Init   .MemDataAlignment = DMA_MDATAALIGN_HALFWORD;    //数据对齐方式
    dma_handle.Init.MemInc = DMA_MINC_ENABLE;                         //数据增长方式
    //外设相关配置
    dma_handle.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD; //数据对齐方式
    dma_handle.Init.PeriphInc = DMA_PINC_DISABLE;                  //数据增长方式
    //优先级和模式
    dma_handle.Init.Priority =DMA_PRIORITY_MEDIUM;
    dma_handle.Init.Mode = DMA_CIRCULAR;    //循环搬运
    //初始化函数
    HAL_DMA_Init(&dma_handle);
    
    //链接串口和DMA
    __HAL_LINKDMA(&adc_handle, DMA_Handle, dma_handle);
}
//ADC相关硬件配置
void HAL_ADC_MspInit(ADC_HandleTypeDef* hadc)
{
    //确认是否为ADC1
    if(hadc->Instance == ADC1){
        RCC_PeriphCLKInitTypeDef  adc_clk_init = {0}; 
        GPIO_InitTypeDef gpio_init_struct = {0};
        //开启ADC1时钟
        __HAL_RCC_ADC1_CLK_ENABLE();
        //使能GPIOB时钟
        __HAL_RCC_GPIOB_CLK_ENABLE();
        //设置PIN1口,模拟输入模式
        gpio_init_struct.Pin = GPIO_PIN_0;
        gpio_init_struct.Mode = GPIO_MODE_ANALOG;
        //GPIO初始化
        HAL_GPIO_Init(GPIOB,&gpio_init_struct);
        //设置外设时钟选择为RCC外设ADC时钟
        adc_clk_init.PeriphClockSelection = RCC_PERIPHCLK_ADC;
        //设置adc时钟分频因子 为6分频
        adc_clk_init.AdcClockSelection = RCC_ADCPCLK2_DIV6;
        //配置外设时钟
        HAL_RCCEx_PeriphCLKConfig(&adc_clk_init);
    }
} 
//ADC通道配置
void adc_channel_config(ADC_HandleTypeDef* hadc, uint32_t ch,uint32_t rank, uint32_t stime)
{ 
    ADC_ChannelConfTypeDef adc_ch_config = {0};
    //设置adc通道参数
    adc_ch_config.Channel = ch;     //设置通道
    adc_ch_config.Rank = rank;      //设置通道次序
    adc_ch_config.SamplingTime = stime;     //设置采样时间
    //通道配置
    HAL_ADC_ConfigChannel(hadc, &adc_ch_config);
}
//获取ADC的值
uint32_t adc_get_result(uint32_t ch)
{
    //配置ADC通道
    adc_channel_config(&adc_handle, ch, ADC_REGULAR_RANK_1, ADC_SAMPLETIME_239CYCLES_5);
    //开始ADC转换
    HAL_ADC_Start(&adc_handle);
    //轮询方式等待ADC转换完成
    HAL_ADC_PollForConversion(&adc_handle, 10);
    //获取ADC转换结果
    return (uint16_t)HAL_ADC_GetValue(&adc_handle);
}
//ADC和DMA初始化
void adc_dma_init(void)
{
    //ADC配置
    adc_config();
    //配置ADC通道
    adc_channel_config(&adc_handle, ADC_CHANNEL_8, ADC_REGULAR_RANK_1, ADC_SAMPLETIME_239CYCLES_5);
    //DMA配置
    dma_config();
    //开始ADC-DMA转换
    HAL_ADC_Start_DMA(&adc_handle, (uint32_t *)&adc_value, 1);
}
//ADC烟雾值获取
float adc_get_smoke(void)
{
    return (float)adc_value /4096 * 3.3;
}

adc.h

c 复制代码
#ifndef __ADC_H__
#define __ADC_H__

#include "sys.h"

//ADC和DMA初始化
void adc_dma_init(void);
//ADC烟雾值获取
float adc_get_smoke(void);
    
#endif

beep.c

c 复制代码
#include "beep.h"
#include "sys.h"

//初始化GBIO口函数
void beep_init(void)
{
    GPIO_InitTypeDef gpio_initstruct;
    
    //使能GPIOB时钟
    __HAL_RCC_GPIOB_CLK_ENABLE();
    
    //调用GPIO初始化函数
    gpio_initstruct.Pin = GPIO_PIN_5;               //beep1对应引脚
    gpio_initstruct.Mode = GPIO_MODE_OUTPUT_PP;     //推挽输出
    gpio_initstruct.Speed = GPIO_SPEED_FREQ_HIGH;   //上拉
    gpio_initstruct.Pull = GPIO_PULLUP;             //高速
    HAL_GPIO_Init(GPIOB, &gpio_initstruct);
    
    //关闭beep1
    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_5, GPIO_PIN_SET);
}

//开启beep1的函数
void beep1_on(void)
{
    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_5, GPIO_PIN_RESET);   //拉低beep1引脚,开启beep1
}


//关闭beep1的函数

void beep1_off(void)
{
    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_5, GPIO_PIN_SET);     //拉高beep1引脚,关闭beep1
}

//翻转beep1的函数
void beep1_toggle(void)
{
    HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_5);                   //翻转beep1引脚电平
}

beep.h

c 复制代码
#ifndef __BEEP_H__
#define __BEEP_H__

//初始化GBIO口函数
void beep_init(void);

//开启beep1的函数
void beep1_on(void);

//关闭beep1的函数

void beep1_off(void);

//翻转beep1的函数
void beep1_toggle(void);

#endif 

fan.c

c 复制代码
#include "fan.h"
#include "sys.h"

//初始化GBIO口函数
void fan_init(void)
{ 
    GPIO_InitTypeDef gpio_initstruct;
    
    //使能GPIOB时钟
    __HAL_RCC_GPIOB_CLK_ENABLE();
    
    //调用GPIO初始化函数
    gpio_initstruct.Pin = GPIO_PIN_6;               //fan对应引脚
    gpio_initstruct.Mode = GPIO_MODE_OUTPUT_PP;     //推挽输出
    gpio_initstruct.Speed = GPIO_SPEED_FREQ_HIGH;   //上拉
    gpio_initstruct.Pull = GPIO_PULLUP;             //高速
    HAL_GPIO_Init(GPIOB, &gpio_initstruct);
    
    //关闭plugin
    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_6, GPIO_PIN_SET);
}

//打开fan的函数
void fan_on(void)
{
    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_6, GPIO_PIN_RESET);   //拉低fan引脚,关闭fan
}


//关闭fan的函数

void fan_off(void)
{
    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_6, GPIO_PIN_SET);     //拉高fan引脚,关闭fan
}

//翻转fan的函数
void fan_toggle(void)
{
    HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_6);                   //翻转fan引脚电平
}

//返回fan的状态
uint8_t fan_status_get(void)
{
    return (uint8_t)HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_6);
}

fan.h

c 复制代码
#ifndef __FAN_H__
#define __FAN_H__

#include "stdint.h"

#define FAN_STATUS_ON 0
#define FAN_STATUS_OFF 0

//初始化GBIO口函数
void fan_init(void);

//打开fan的函数
void fan_on(void);

//关闭fan的函数
void fan_off(void);

//翻转fan的函数
void fan_toggle(void);

//返回继电器的状态
uint8_t fan_status_get(void);

#endif 

lcd1602.c

c 复制代码
#include "lcd1602.h"
#include "sys.h"
#include "delay.h"
#include "stdio.h"

//RS引脚定义
#define RS_GPIO_Port    GPIOB
#define RS_GPIO_Pin     GPIO_PIN_1
#define RS_HIGH     HAL_GPIO_WritePin(RS_GPIO_Port,RS_GPIO_Pin,GPIO_PIN_SET);
#define RS_LOW      HAL_GPIO_WritePin(RS_GPIO_Port,RS_GPIO_Pin,GPIO_PIN_RESET);

//RW引脚定义
#define RW_GPIO_Port    GPIOB
#define RW_GPIO_Pin     GPIO_PIN_2
#define RW_HIGH     HAL_GPIO_WritePin(RW_GPIO_Port,RW_GPIO_Pin,GPIO_PIN_SET);
#define RW_LOW      HAL_GPIO_WritePin(RW_GPIO_Port,RW_GPIO_Pin,GPIO_PIN_RESET);

//EN引脚定义
#define EN_GPIO_Port    GPIOB
#define EN_GPIO_Pin     GPIO_PIN_10
#define EN_HIGH     HAL_GPIO_WritePin(EN_GPIO_Port,EN_GPIO_Pin,GPIO_PIN_SET);
#define EN_LOW      HAL_GPIO_WritePin(EN_GPIO_Port,EN_GPIO_Pin,GPIO_PIN_RESET);

//LCD1602初始化
void lcd1602_init(void)
{
    //初始化GPIO
    lcd1602_gpio_init();
    //上电初始化
    lcd1602_start();
}
//LCD1602GPIO初始化
void lcd1602_gpio_init(void)
{
    GPIO_InitTypeDef gpio_initstruct;
    
    //使能GPIOA时钟
    __HAL_RCC_GPIOA_CLK_ENABLE();
    //使能GPIOB时钟
    __HAL_RCC_GPIOA_CLK_ENABLE();
    
    //调用GPIO初始化函数
    gpio_initstruct.Pin = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3 |
                          GPIO_PIN_4 | GPIO_PIN_5 | GPIO_PIN_6 | GPIO_PIN_7;               //D0-D7对应引脚
    gpio_initstruct.Mode = GPIO_MODE_OUTPUT_PP;     //推挽输出
    gpio_initstruct.Speed = GPIO_SPEED_FREQ_HIGH;   //高速
    gpio_initstruct.Pull = GPIO_PULLUP;             //上拉
    HAL_GPIO_Init(GPIOA, &gpio_initstruct);
    
    gpio_initstruct.Pin = GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_10;              //RS、RW、EN对应引脚
    gpio_initstruct.Mode = GPIO_MODE_OUTPUT_PP;     //推挽输出
    gpio_initstruct.Speed = GPIO_SPEED_FREQ_HIGH;   //高速
    gpio_initstruct.Pull = GPIO_PULLUP;             //上拉
    HAL_GPIO_Init(GPIOB, &gpio_initstruct);
}
//LCD1602启动
void lcd1602_start(void)
{
//(1)延时 15ms
    delay_ms(15);
//(2)写指令 38H(不检测忙信号)
    lcd1602_write_cmd(0x38);
//(3)延时 5ms
    delay_ms(5);
//(4)以后每次写指令,读/写数据操作均需要检测忙信号
//(5)写指令 38H:显示模式设置
    lcd1602_write_cmd(0x38);
//(6)写指令 08H:显示关闭
    lcd1602_write_cmd(0x08);
//(7)写指令 01H:显示清屏
    lcd1602_write_cmd(0x01);
//(8)写指令 06H:显示光标移动设置
    lcd1602_write_cmd(0x06);
//(9)写指令 0CH:显示开及光标设置
    lcd1602_write_cmd(0x0C);
}
//LCD1602写指令
void lcd1602_write_cmd(char cmd)
{
    RS_LOW;
    RW_LOW;
    EN_LOW;
    GPIOA->ODR = cmd;
    delay_ms(5);
    EN_HIGH;
    delay_ms(5);
    EN_LOW;
}
//LCD1602写数据
void lcd1602_write_data(char data)
{
    RS_HIGH;
    RW_LOW;
    EN_LOW;
    GPIOA->ODR = data;
    delay_ms(5);
    EN_HIGH;
    delay_ms(5);
    EN_LOW;
}
//LCD1602显示一个字符
void lcd1602_show_char(char row,char col,char data)
{
    //显示位置
    if(row == 1){
        lcd1602_write_cmd(0x00+0x80+col-1);
    }else if(row ==2){
        lcd1602_write_cmd(0x40+0x80+col-1);
    }
    
    //显示数据
    lcd1602_write_data(data);
}
//LCD1602显示一行
void lcd1602_show_line(char row,char col,char *string)
{
    switch(row){
        case 1:
            lcd1602_write_cmd(0x00+0x80+col-1);
            while(*string){
                lcd1602_write_data(*string);
                string++;
            }
            break;
        case 2:
            lcd1602_write_cmd(0x40+0x80+col-1);
            while(*string){
                lcd1602_write_data(*string);
                string++;
            }
            break;
    }
}
//LCD1602显示烟雾值
void lcd1602_display_smoke(float smoke_value)
{
    char msg[16] = {0};
    
    sprintf(msg, "Smoke:   %0.2f V",smoke_value);
    lcd1602_show_line(1, 1, msg);
}
//LCD1602显示烟雾阈值
void lcd1602_display_limit(float limit_value)
{
    char msg[16] = {0};
    
    sprintf(msg, "Limit: - %0.2f +",limit_value);
    lcd1602_show_line(2, 1, msg);
}

lcd1602.h

c 复制代码
#ifndef __LCD1602_H__
#define __LCD1602_H__

//LCD1602初始化
void lcd1602_init(void);
//LCD1602GPIO初始化
void lcd1602_gpio_init(void);
//LCD1602启动
void lcd1602_start(void);
//LCD1602写指令
void lcd1602_write_cmd(char cmd);
//LCD1602写数据
void lcd1602_write_data(char data);
//LCD1602显示一个字符
void lcd1602_show_char(char row,char col,char data);
//LCD1602显示一行
void lcd1602_show_line(char row,char col,char *string);
//LCD1602显示烟雾值
void lcd1602_display_smoke(float smoke_value);
//LCD1602显示烟雾阈值
void lcd1602_display_limit(float limit_value);

#endif

key.c

c 复制代码
#include "key.h"
#include "delay.h"

//初始化GPIO
void key_init(void)
{
    GPIO_InitTypeDef gpio_initstruct;
    
    //使能GPIOB时钟
    __HAL_RCC_GPIOA_CLK_ENABLE();
    
    //调用GPIO初始化函数
    gpio_initstruct.Pin = GPIO_PIN_13 | GPIO_PIN_14;  //KEY1,KEY2对应引脚
    gpio_initstruct.Mode = GPIO_MODE_INPUT;         //输入
    gpio_initstruct.Pull = GPIO_PULLUP;             //上拉
    gpio_initstruct.Speed = GPIO_SPEED_FREQ_HIGH;   //高速
    HAL_GPIO_Init(GPIOB, &gpio_initstruct);
}

//按键扫描函数
uint8_t key_scan(void)
{
    //检测按键1是否按下
    if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_13) == GPIO_PIN_RESET){
        //消抖
        delay_ms(10);
        //再次判断按键是否按下
        if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_13) == GPIO_PIN_RESET){
            //如果确实按下,那么等待按键松开
            while(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_13) == GPIO_PIN_RESET);
            //返回按键值
            return 1;
        }
    }
    
    //检测按键2是否按下
    if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_14) == GPIO_PIN_RESET){
        //消抖
        delay_ms(10);
        //再次判断按键是否按下
        if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_14) == GPIO_PIN_RESET){
            //如果确实按下,那么等待按键松开
            while(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_14) == GPIO_PIN_RESET);
            //返回按键值
            return 2;
        }
    }
    
    //返回默认值
    return 0;
}

key.h

c 复制代码
#ifndef __KEY_H__
#define __KEY_H__

#include "sys.h"

void key_init(void);
uint8_t key_scan(void);

#endif 

项目实物图

相关推荐
纳祥科技3 小时前
方案分享:一款基于高性能单片机的落日灯方案
单片机·嵌入式硬件
三佛科技-134163842124 小时前
智能保温杯方案,保温杯MCU控制方案设计
单片机·嵌入式硬件·智能家居·pcb工艺
Wave8456 小时前
STM32_标准库转hal库
stm32·单片机·嵌入式硬件
智者知已应修善业7 小时前
【51单片机:两边向中间流水:即两边先点亮然后熄灭,次边的点亮再熄灭,直到最中间的两个点亮再熄灭,然后重复动作。】2023-3-4
c语言·c++·经验分享·笔记·嵌入式硬件·算法·51单片机
嵌入式软硬件攻城狮7 小时前
6.单片机回调函数
单片机·嵌入式硬件
就是蠢啊8 小时前
单片机入门实验——点灯
单片机·嵌入式硬件
@曾记否8 小时前
瑞控FPVF405飞控开发板硬件功能整理
stm32·嵌入式硬件
oshan20128 小时前
小华HC32L136K8TA 单片机LED(三)
单片机·嵌入式硬件
小齐勇闯天涯8 小时前
STM32--PWM原理机制
stm32·单片机·嵌入式硬件