STM32-ADC多通道输入实验

news2024/10/6 12:25:33

之前已经介绍了几个ADC的笔记和实验了,链接如下:

关于ADC的笔记1_Mr_rustylake的博客-CSDN博客

STM32-ADC单通道采集实验_Mr_rustylake的博客-CSDN博客

STM32-单通道ADC采集(DMA读取)实验_Mr_rustylake的博客-CSDN博客

接下来介绍这次的实验要求:

通过ADC1通道0/1/2/3/4/5(PA0/1/2/3/4/5)采集测试电压,并现实ADC转换的数字量和换算后的电压值。

首先确定我们的最小刻度,Vref = 3.3V,所以0V <= Vin <= 3.3V,所以最小刻度是3.3V / 4096(2^12)。

接下来确定转换时间。采样时间239.5个ADC时钟周期为例,可以得到转换时间为21us。

时间转换公式参考如下公式:Tcvtmin=(12.5+X)周期=(12.5 + X)/(12MHz)=21us。

 下图是对应的通道表:

这里的模式考虑到需要多通道扫描,所以启动扫描模式,并启动连续模式。通道数记得改为6,注意设置通道数目和对应的转换顺序。

接下来编写函数的代码:

先编写函数文件adc.h:

#include "./BSP/ADC/adc.h"
 
ADC_HandleTypeDef g_adc_nch_handle;
DMA_HandleTypeDef g_dma_nch_handle;
uint8_t g_adc_dma_sta; //标志DMA的传输是否完成
 
void adc_nch_dam_init(uint32_t mar){
 
    ADC_ChannelConfTypeDef adc_ch_conf;
 
    __HAL_RCC_DMA1_CLK_ENABLE();
 
    g_dma_nch_handle.Instance = DMA1_Channel1;
    g_dma_nch_handle.Init.Direction = DMA_PERIPH_TO_MEMORY;  //外设到内存
    g_dma_nch_handle.Init.PeriphInc = DMA_PINC_DISABLE;  //因为选取的是DMA1的数据寄存器,选择不增量
    g_dma_nch_handle.Init.MemInc = DMA_MINC_ENABLE;  //对于存储器需要存储多个数据,所以选择增量模式
    g_dma_nch_handle.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD; //外设数据位宽,我们选择16位半字(全字可以理解为全角中文字符)
    g_dma_nch_handle.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;  //存储器数据位宽,我们也选择16位半字
    g_dma_nch_handle.Init.Mode = DMA_NORMAL;   //选择普通模式,因为在传输完成之后我们需要进行进一步操作现实我们获取到的值,所以选择normal
    g_dma_handle.Init.Priority = DMA_PRIORITY_MEDIUM;   //只有1个DMA随便选
 
    HAL_DMA_Init(&g_dma_nch_handle);
    //联系DMA和ADC的句柄
    __HAL_LINKDMA(&g_adc_nch_handle, DMA_Handle, &g_dma_nch_handle);  //第二个参数为第一个ADC句柄的第三个成员,指向对应的DMA句柄
 
    g_adc_nch_handle.Instance = ADC1;
    g_adc_nch_handle.Init.DataAlign = ADC_DATAALIGN_RIGHT; //右对齐
    g_adc_nch_handle.Init.ScanConvMode = ADC_SCAN_ENABLE; //扫描
    g_adc_nch_handle.Init.ContinuousConvMode = ENABLE; //连续模式
    g_adc_nch_handle.Init.NbrOfConversion = 6; //转换通道数为6,6通道
    g_adc_nch_handle.Init.DiscontinuousConvMode = DISABLE; //不用间断模式
    g_adc_nch_handle.Init.NbrOfDiscConversion = 0; //无间断模式则无间断通道
    g_adc_nch_handle.Init.ExternalTrigConv = ADC_SOFTWARE_START; //外部软件触发

    HAL_ADC_Init(&g_adc_handle);
 
    adc_ch_conf.Channel = ADC_CHANNEL_0;
    adc_ch_conf.Rank = ADC_REGULAR_RANK_1; //转换顺序
    adc_ch_conf.SamplingTime = ADC_SMAPLINGTIME_239CYCLES_5; //设置为最大值
    HAL_ADC_ConfigChannel(&g_adc_nch_handle, &adc_ch_conf);

    adc_ch_conf.Channel = ADC_CHANNEL_1;
    adc_ch_conf.Rank = ADC_REGULAR_RANK_2; //转换顺序
    adc_ch_conf.SamplingTime = ADC_SMAPLINGTIME_239CYCLES_5; //设置为最大值
    HAL_ADC_ConfigChannel(&g_adc_nch_handle, &adc_ch_conf);

    adc_ch_conf.Channel = ADC_CHANNEL_2;
    adc_ch_conf.Rank = ADC_REGULAR_RANK_3; //转换顺序
    adc_ch_conf.SamplingTime = ADC_SMAPLINGTIME_239CYCLES_5; //设置为最大值
    HAL_ADC_ConfigChannel(&g_adc_nch_handle, &adc_ch_conf);

    adc_ch_conf.Channel = ADC_CHANNEL_3;
    adc_ch_conf.Rank = ADC_REGULAR_RANK_4; //转换顺序
    adc_ch_conf.SamplingTime = ADC_SMAPLINGTIME_239CYCLES_5; //设置为最大值
    HAL_ADC_ConfigChannel(&g_adc_nch_handle, &adc_ch_conf);

    adc_ch_conf.Channel = ADC_CHANNEL_4;
    adc_ch_conf.Rank = ADC_REGULAR_RANK_5; //转换顺序
    adc_ch_conf.SamplingTime = ADC_SMAPLINGTIME_239CYCLES_5; //设置为最大值
    HAL_ADC_ConfigChannel(&g_adc_nch_handle, &adc_ch_conf);

    adc_ch_conf.Channel = ADC_CHANNEL_5;
    adc_ch_conf.Rank = ADC_REGULAR_RANK_6; //转换顺序
    adc_ch_conf.SamplingTime = ADC_SMAPLINGTIME_239CYCLES_5; //设置为最大值
    HAL_ADC_ConfigChannel(&g_adc_nch_handle, &adc_ch_conf);

    HAL_NVIC_SetPriority(DMA1_Channel1_IRQn, 2, 3);
    HAL_NVIC_EnableIRQ(DMA1_Channel1_IRQn);
 
    HAL_ADCEx_Calibration_Start(&g_adc_nch_handle);

    HAL_DMA_Start_IT(&g_dma_nch_handle, (uint32_t)&ADC1->DR, mar, 0);
    HAL_ADC_Start_IT(&g_adc_nch_handle, &mar, 0);
}
 
void HAL_ADC_MspInit(ADC_HandleTypeDef *hadc){
 
    if(hadc->Instance == ADC1){
        GPIO_InitTypeDef gpio_init_struct;
        RCC_PeriphCLKInitTypeDef adc_clk_init = {0};
 
        __HAL_RCC_GPIOA_CLK_ENABLE();  //使能ADC时钟
        __HAL_RCC_ADC1_CLK_ENABLE();   //使能GPIO时钟
 
        gpio_init_struct.Pin = GPIO_PIN_0;
        gpio_init_struct.Mode = GPIO_MODE_ANALOG; //模拟模式
        HAL_GPIO_Init(GPIOA, &gpio_init_struct);

        gpio_init_struct.Pin = GPIO_PIN_1;
        HAL_GPIO_Init(GPIOA, &gpio_init_struct);

        gpio_init_struct.Pin = GPIO_PIN_2;
        HAL_GPIO_Init(GPIOA, &gpio_init_struct);

        gpio_init_struct.Pin = GPIO_PIN_3;
        HAL_GPIO_Init(GPIOA, &gpio_init_struct);

        gpio_init_struct.Pin = GPIO_PIN_4;
        HAL_GPIO_Init(GPIOA, &gpio_init_struct);

        gpio_init_struct.Pin = GPIO_PIN_5;
        HAL_GPIO_Init(GPIOA, &gpio_init_struct);
 
        adc_clk_init.PeriphClockSelection = RCC_PERIPHCLK_ADC; //选择ADC外设时钟设置
        adc_clk_init.AdcClockSelection = RCC_ADCPCLK2_DIV6; //选择6分频,72/6=12MHz
 
        HAL_RCCEx_PeriphCLKConfig(&adc_clk_init, &g_adc_nch_handle);
    }
}
 
uint32_t adc_get_result(void){
 
    HAL_ADC_Start(&g_adc_nch_handle);
    HAL_ADC_PollForConversion(&g_adc_nch_handle, 10); //第二个参数比1大就行
    return (uint16_t)HAL_ADC_GetValue(&g_adc_nch_handle);
}
 
uint32_t adc_get_result_average(uint32_t ch, uint8_t times){
 
    uint32_t temp_val = 0;
    uint8_t t;
 
    for(t = 0; t < times; t++){
        temp_val += adc_get_result();
        delay_ms(5);
    }
 
    return temp_val / times;
}
 
void adc_dma_enable(uint16_t cndtr){
    /*
    ADC1->CR2 &= ~(1 << 0); //关闭ADC
    DMA1_Channel1->CCR &= ~(1 << 0);//关闭DMA
    while(DMA1_Channel1->CCR & (1 << 0));
    DMA1_Channel1->CNDTR = cndtr;
    DMA1_Channel1->CCR |= (1 << 0); //开启DMA
    ADC1->CR2 |= (1 << 0);  //开启ADC
 
    ADC1->CR2 |= (1 << 22);  //触发规则组转换
    */

    //hal库法
    __HAL_ADC_DISABLE(&g_adc_nch_handle);

    __HAL_DNA_DISABLE(&g_dma_nch_handle);
    while(__HAL_DMA_GET_FLAG(&g_dma_nch_handle, __HAL_DMA_GET_FLAG_INDEX(&g_dma_nch_handle)));
    DMA1_Channel1->CNDTR = cndtr;
    __HAL_DMA_ENABEL(&g_dma_nch_handle);

    __HAL_ADC_ENABLE(&g_adc_nch_handle);
    HAL_ADC_Start(&g_adc_nch_handle);
}
 
void DMA1_Channel1_IRQHandle(void){
 
    if(DMA1->ISR & (1 << 1)){
        g_adc_dma_sta = 1;
        DMA1->IECR |= 1 << 1;
    }
}

接下来在编写函数文件的头文件adc.h:

#ifndef __ADC_H
#define __ADC_H
 
#include "SYSTEM/sys/sys.h"
#include "BSP/DMA/dma.h"
 
extern ADC_HandleTypeDef g_adc_handle;
 
void adc_nch_dam_init(uint32_t mar);
void HAL_ADC_MspInit(ADC_HandleTypeDef *hadc);
uint32_t adc_get_result(void);
uint32_t adc_get_result_average(uint32_t ch, uint8_t times);
void adc_dma_enable(uint16_t cndtr);
void DMA1_Channel1_IRQHandle(void);
 
#endif

最后编写主函数代码main.c:

#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/usart/usart.h"
#include "./SYSTEM/delay/delay.h"
#include "./USMART/usmart.h"
#include "./BSP/LED/led.h"
#include "./BSP/LCD/lcd.h"
#include "./BSP/ADC/adc.h"


#define ADC_DMA_BUF_SIZE        50 * 6      /* ADC DMA采集 BUF大小, 应等于ADC通道数的整数倍,50表示转换50次 */
uint16_t g_adc_dma_buf[ADC_DMA_BUF_SIZE];   /* ADC DMA BUF */

extern uint8_t g_adc_dma_sta;               /* DMA传输状态标志, 0,未完成; 1, 已完成 */

int main(void)
{
    uint16_t i,j;
    uint16_t adcx;
    uint32_t sum;
    float temp;

    HAL_Init();                                 /* 初始化HAL库 */
    sys_stm32_clock_init(RCC_PLL_MUL9);         /* 设置时钟, 72Mhz */
    delay_init(72);                             /* 延时初始化 */
    usart_init(115200);                         /* 串口初始化为115200 */
    led_init();                                 /* 初始化LED */
    lcd_init();                                 /* 初始化LCD */

    adc_nch_dma_init((uint32_t)&g_adc_dma_buf); /* 初始化ADC DMA采集 */

    lcd_show_string(30,  50, 200, 16, 16, "STM32", RED);
    lcd_show_string(30,  70, 200, 16, 16, "ADC 6CH DMA TEST", RED);
    lcd_show_string(30,  90, 200, 16, 16, "ATOM@ALIENTEK", RED);

    lcd_show_string(30, 110, 200, 12, 12, "ADC1_CH0_VAL:", BLUE);
    lcd_show_string(30, 122, 200, 12, 12, "ADC1_CH0_VOL:0.000V", BLUE); /* 先在固定位置显示小数点 */
    
    lcd_show_string(30, 140, 200, 12, 12, "ADC1_CH1_VAL:", BLUE);
    lcd_show_string(30, 152, 200, 12, 12, "ADC1_CH1_VOL:0.000V", BLUE); /* 先在固定位置显示小数点 */

    lcd_show_string(30, 170, 200, 12, 12, "ADC1_CH2_VAL:", BLUE);
    lcd_show_string(30, 182, 200, 12, 12, "ADC1_CH2_VOL:0.000V", BLUE); /* 先在固定位置显示小数点 */

    lcd_show_string(30, 200, 200, 12, 12, "ADC1_CH3_VAL:", BLUE);
    lcd_show_string(30, 212, 200, 12, 12, "ADC1_CH3_VOL:0.000V", BLUE); /* 先在固定位置显示小数点 */

    lcd_show_string(30, 230, 200, 12, 12, "ADC1_CH4_VAL:", BLUE);
    lcd_show_string(30, 242, 200, 12, 12, "ADC1_CH4_VOL:0.000V", BLUE); /* 先在固定位置显示小数点 */

    lcd_show_string(30, 260, 200, 12, 12, "ADC1_CH5_VAL:", BLUE);
    lcd_show_string(30, 272, 200, 12, 12, "ADC1_CH5_VOL:0.000V", BLUE); /* 先在固定位置显示小数点 */

    adc_dma_enable(ADC_DMA_BUF_SIZE);   /* 启动ADC DMA采集 */

    while (1)
    {
        if (g_adc_dma_sta == 1)
        {
            /* 循环显示通道0~通道5的结果 */
            for(j = 0; j < 6; j++)  /* 遍历6个通道 */
            {
                sum = 0; /* 清零 */
                for (i = 0; i < ADC_DMA_BUF_SIZE / 6; i++)  /* 每个通道采集了50次数据,进行50次累加 */
                {
                    sum += g_adc_dma_buf[(6 * i) + j];      /* 相同通道的转换数据累加 */
                }
                adcx = sum / (ADC_DMA_BUF_SIZE / 6);        /* 取平均值 */
                
                /* 显示结果 */
                lcd_show_xnum(108, 110 + (j * 30), adcx, 4, 12, 0, BLUE);   /* 显示ADCC采样后的原始值 */

                temp = (float)adcx * (3.3 / 4096);  /* 获取计算后的带小数的实际电压值,比如3.1111 */
                adcx = temp;                        /* 赋值整数部分给adcx变量,因为adcx为u16整形 */
                lcd_show_xnum(108, 122 + (j * 30), adcx, 1, 12, 0, BLUE);   /* 显示电压值的整数部分,3.1111的话,这里就是显示3 */

                temp -= adcx;                       /* 把已经显示的整数部分去掉,留下小数部分,比如3.1111-3=0.1111 */
                temp *= 1000;                       /* 小数部分乘以1000,例如:0.1111就转换为111.1,相当于保留三位小数。 */
                lcd_show_xnum(120, 122 + (j * 30), temp, 3, 12, 0X80, BLUE);/* 显示小数部分(前面转换为了整形显示),这里显示的就是111. */
            }
 
            g_adc_dma_sta = 0;                      /* 清除DMA采集完成状态标志 */
            adc_dma_enable(ADC_DMA_BUF_SIZE);       /* 启动下一次ADC DMA采集 */
        }

        LED0_TOGGLE();
        delay_ms(100);
    }
}

到这里我们的实验代码就写完了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/529985.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

NodeJs基础之NRM与NPM

nrm nrm can help you easy and fast switch between different npm registries, now include: npm, cnpm, taobao, nj(nodejitsu). 译文&#xff1a;nrm可以帮助您在不同的npm注册表之间轻松快速地切换&#xff0c;现在包括&#xff1a;npm、cnpm、taobao、nj&#xff08;no…

编译安装及yum安装

一、编译安装 源码包&#xff1a;是由程序员按照特定格式和语法编写的包 二进制包:源码包经过成功编译之后产生的包 1.tar -xf httpd-2.4.29.tar.bz #解压源码包 2.安装依赖环境 3.配置安装路径 4.编译make并安装 5.关闭防火墙&#xff0c;和安全机制 6.开启服务器 7.…

全电发票时代,记账凭证不用再打印了!

—政策通告— 为进一步推进电子发票应用和推广实施工作&#xff0c;助力国家数字经济发展&#xff0c;国家档案局会同财政部、商务部、国家税务总局总结三批增值税电子发票电子化报销、入账、归档试点经验&#xff0c;依据国家相关法律法规和标准规范&#xff0c;编制形成了《…

KMP匹配算法

目录 一、暴力匹配法动画演示代码实现 二、KMP算法的概念三、KMP算法的应用题目代码实现 一、暴力匹配法 动画演示 时间复杂度为&#xff1a; O ( m ∗ n ) O(m * n) O(m∗n) 代码实现 #define _CRT_SECURE_NO_WARNINGS #include <iostream> using namespace std;int…

Revit API:ErrorHandling

前言 本文介绍 Revit 的错误处理机制。 内容 程序员对错误处理的定义和理解 程序的错误处理机制可以分为两种类型&#xff1a;错误返回码和异常捕捉。 错误返回码是指在程序中遇到错误时&#xff0c;通过函数返回值来表明错误的类型和信息。错误返回码可以在程序中被预测和…

分段存储管理方式

目录 一、分段存储管理方式的引入的需求: 1.方便编程 2.信息共享 3.信息保护 4.动态增长 5.动态链接 二、分段系统的基本原理 1.分段 2.段表 3.地址变换机构 4.分页与分段的主要区别 三、信息共享 四、段页式存储管理方式 1.基本原理 2.地址变换过程 分段与分页…

Spring实现IOC和DI入门案例(XML版)

文章目录 1 IOC入门案例(XML版)1.1 思路分析1.2 代码实现步骤1:创建Maven项目步骤2:添加Spring的依赖jar包步骤3:添加案例中需要的类步骤4:添加spring配置文件步骤5:在配置文件中完成bean的配置步骤6:获取IOC容器步骤7:从容器中获取对象进行方法调用步骤8:运行程序 2 DI入门案例…

AltTab for Mac 像Windows一样的窗口快速切换工具

AltTab for Mac AltTab for Mac 是一款非常好用的窗口快速切换工具&#xff0c;AltTab将Windows的 “Alt-Tab” 窗口切换器的功能引入到了macOS。可以让您更快的在各个程序之间自由切换&#xff0c;大大提高您的工作效率。 AltTab for Mac下载 AltTab for Mac AltTab for Ma…

哈工大软件过程与工具作业2

云原生技术云原生技术 哈尔滨工业大学 计算机科学与技术学院/国家示范性软件学院 2022年秋季学期 《软件过程与工具》课程 作业报告 作业 2&#xff1a;需求分析UML建模 姓名 学号 联系方式 石卓凡 120L021011 944613709qq.com/18974330318 目 录 1 需求概述...........…

Vue3——简易版个人空间(上半部分)

创建项目 使用vue 的图形化界面创建一个新的vue3项目如下图所示 装两个新的插件——router和vuex插件 该过程的可能有点久&#xff0c;需要耐心等待。 再装一些需要的依赖 需要用到的依赖: boostrap和poperjs/core&#xff08;bootstrap是提供给不会做美工的程序员的一个新的…

物联网|uart串口相关寄存器|波特率设置及计算|发送处理代码|串口接收中断处理函数|物联网之蓝牙4.0 BLE基础-学习笔记(7)

文章目录 13 uart串口基础开发基本电路图&#xff1a;实验相关寄存器波特率设置及计算计算过程&#xff1a;设置中断发送处理代码串口接收中断处理函数main.c 13 uart串口基础开发 基本电路图&#xff1a; 实验相关寄存器 相关寄存器UxCSR、UxCSR、UxGCR、UxBUF、UxBAUD、CLK…

大厂设计师青睐的十大设计网站

设计绝对是薪酬差距最大的职业之一&#xff0c;有些人年薪100万&#xff0c;有些人月薪3000。 你有没有想过普通人和高薪设计师之间的差距在哪里&#xff1f; 在这篇文章中&#xff0c;我们将解密大厂设计师青睐的十大设计网站。让我们看看&#xff01; 1.即时设计 即时设计…

眺望高阶智能驾驶的远方,北醒驶入新“平台”

从4月到5月&#xff0c;关于2023上海国际车展的话题热度始终不减。 这次车展既让外界感受到了车企们的“卷”&#xff0c;也把智能汽车产业发展的蓝图更加清晰地呈现了出来——智能汽车正在进入产业布局的关键期&#xff0c;产业链上下游发展不断提速。 作为推动汽车产业的“…

基于边缘的图像分割

文章目录 基于边缘的图像分割基本原理常用的算法实现步骤示例代码结论 基于边缘的图像分割 基于边缘的图像分割是数字图像处理中常用的一种方法&#xff0c;它通过检测图像中的边缘信息来实现图像的分割。边缘通常代表着图像中不同区域之间的边界或目标的轮廓&#xff0c;因此…

Java 8 腰斩,Java 17 暴涨 430% (文末惊喜福利)

文末送出惊喜赠书福利一份&#xff0c;绝对high到爆&#xff01; 预约视频号&#xff1a;《Java核心技术》新书发布会暨"Java核心技术大会2023"启动仪式 New Relic 最新发布了一份 “2023 年 Java 生态系统状况报告”&#xff0c;旨在提供有关当今 Java 生态系统状态…

51单片机(十六)AD/DA

❤️ 专栏简介&#xff1a;本专栏记录了从零学习单片机的过程&#xff0c;其中包括51单片机和STM32单片机两部分&#xff1b;建议先学习51单片机&#xff0c;其是STM32等高级单片机的基础&#xff1b;这样再学习STM32时才能融会贯通。 ☀️ 专栏适用人群 &#xff1a;适用于想要…

罗马斗兽场在古罗马时期为什么这么流行,它对罗马有何影响?

在古罗马时期&#xff0c;斗兽场可谓是当时的一大热门娱乐场所。为何斗兽场如此受欢迎&#xff0c;它又如何影响了罗马社会呢&#xff1f; 斗兽场是古罗马人民的一种独特的娱乐形式。无论贵族、平民还是奴隶&#xff0c;他们都喜欢观看这种刺激的竞技。 有人认为&#xff0c;斗…

弹性盒子的属性

display&#xff1a;指定元素使用弹性盒子布局&#xff0c;属性值为 flex 或 inline-flex。 flex-direction&#xff1a;指定弹性盒子主轴的方向&#xff0c;属性值可以是 row&#xff08;默认值&#xff0c;主轴为水平方向&#xff09;、row-reverse&#xff08;主轴为水平方…

第11章:约束

一、数据完整性与约束的分类 1.为什么需要约束constraint SQL以约束方式对表数据进行额外的条件限制。 为了保证数据的完整性&#xff0c;SQL对表数据进行条件限制 ①实体完整性&#xff1a;同一个表&#xff0c;不能存在两条相同无法区分的记录 ②域完整性&#xff1a;年…

【数据结构】入门及时间空间复杂度的介绍

&#x1f331;博客主页&#xff1a;大寄一场. &#x1f331;系列专栏&#xff1a;数据结构与算法 &#x1f618;博客制作不易欢迎各位&#x1f44d;点赞⭐收藏➕关注 目录 前言 1.什么是数据结构&#xff1f; 2.什么是算法&#xff1f; 3.数据结构和算法的重要性 4.常见的数…