普冉(PUYA)单片机开发笔记(7): ADC-轮询式多路采样

概述

应用中经常会有使用单片机进行模数转换的需求。PY32F003 具有 1 个 12 位的模拟数字转换器(ADC),今天我们一起来使用一下这个 ADC。

数据手册中对 ADC 简介如下。

SAR ADC:逐次逼近式 ADC,原理参见“参考链接:什么是SAR ADC? - 知乎”。12位采样值的最大值4095。数据手册上标明的最大可用通道数量是 8 个外部通道,但对照 PY32F003F18P 的管脚复用表,如果应用中还要使用 GPIO,LED,定时器 和 UART 的话,可使用的外部 ADC 通道数最多不超过 6 个。对比于 PY32F003F18P 的 20 脚封装和低廉的芯片价格,这样的 MCU 可以在应用中采样 6 个外部模拟量通道也是相当可观的数量了。

PY32F003 可以在不使用外部晶振的情况下完成数模转换,但其采样精度还需要验证。今天先尝试着把 ADC 的功能跑通先。

实现代码

参考在 STM32F103 上实现 ADC 的思路,在 PY32F003 上完成一下看。大致的步骤如下:

  1. 为 ADC1 指定 GPIO 管脚,并设置其复用功能
  2. 对 ADC1 进行初始化
  3. 在主循环中进行采样和打印输出

在 main.h 中增加和 ADC 相关的函数声明

/** ----------------------------------------------------------------------------
* @name   : void ADC_Init(void)
* @brief  : ADC 初始化
* @param  : [in] None
* @retval : [out] void
* @remark :
*** ----------------------------------------------------------------------------
*/
void ADC_Init(void);/** ----------------------------------------------------------------------------
* @name   : HAL_StatusTypeDef ADC_Sample(char * sampleResult)
* @brief  : 获取 ADC 的采样结果,结果存放在 sampleResult 字符串中
* @param  : [in] None
* @retval : [out] HAL_HandleTypeDef. 操作成功返回 HAL_OK, 错误返回错误码。
* @remark : sampleResult 是格式化的字符串,需要解析
*** ----------------------------------------------------------------------------
*/
HAL_StatusTypeDef ADC_Sample(char * sampleResult);

在 app_adc.c 文件中实现函数功能

在 Application/User 组增加 app_adc.c 文件,完整代码如下。

/********************************************************************************* @file    app_adc.c* @brief   Application level Analog-Digital Conveter codes.******************************************************************************* @attention** Copyright (c) 2023 CuteModem Intelligence.* All rights reserved.** This software is licensed under terms that can be found in the LICENSE file* in the root directory of this software component.* If no LICENSE file comes with this software, it is provided AS-IS.********************************************************************************/#include "main.h"ADC_HandleTypeDef hadc;
uint32_t adc_value[3];/********************************************************************************************************
* @name   : HAL_StatusTypeDef ADC_Sample(char * sampleResult)
* @brief  : 获取 ADC 的采样结果,结果存放在 sampleResult 字符串中
* @param  : [in] None
* @retval : [out] HAL_HandleTypeDef. 操作成功返回 HAL_OK, 错误返回错误码。
* @remark : sampleResult 是格式化的字符串,需要解析
********************************************************************************************************/
HAL_StatusTypeDef ADC_Sample(char * sampleResult)
{uint8_t i=0;if(HAL_ADCEx_Calibration_Start(&hadc) != HAL_OK) return HAL_ERROR;HAL_ADC_Start(&hadc);                       //开始采样for (i = 0; i < 3; i++){HAL_ADC_PollForConversion(&hadc, 10000);  //等待ADC转换adc_value[i] = HAL_ADC_GetValue(&hadc);   //获取AD值}#if(1)// excel formatsprintf(sampleResult, "%d,%d,%d",(uint16_t)adc_value[0],(uint16_t)adc_value[1],(uint16_t)adc_value[2]);
#else// JSON formatsprintf(sampleResult, "[{\"C\":0,\"D\":%d}"",{\"C\":1,\"D\":%d}"",{\"C\":5,\"D\":%d}""]",(uint16_t)adc_value[0],(uint16_t)adc_value[1],(uint16_t)adc_value[2]);
#endif    HAL_ADC_Stop(&hadc); // 停止采样return HAL_OK;
}void ADC_Init(void)
{ADC_ChannelConfTypeDef sConfig = {0};__HAL_RCC_ADC_FORCE_RESET();__HAL_RCC_ADC_RELEASE_RESET();__HAL_RCC_ADC_CLK_ENABLE();hadc.Instance = ADC1;if (HAL_ADCEx_Calibration_Start(&hadc) != HAL_OK)                 //AD校准Error_Handler();/* Configure global features of the ADC1  */hadc.Init.ClockPrescaler        = ADC_CLOCK_SYNC_PCLK_DIV1;       //ADC_CLOCK_SYNC_PCLK_DIV2/4,分频系数hadc.Init.Resolution            = ADC_RESOLUTION_12B;             //设置采样位数hadc.Init.DataAlign             = ADC_DATAALIGN_RIGHT;            //右对齐hadc.Init.ScanConvMode          = ADC_SCAN_DIRECTION_FORWARD;     //扫描方向设置hadc.Init.EOCSelection          = ADC_EOC_SINGLE_CONV;            //ADC_EOC_SINGLE_CONV:单次采样 ; ADC_EOC_SEQ_CONV:序列采样hadc.Init.LowPowerAutoWait      = ENABLE;                         //ENABLE:读取ADC值后,开始下一次转换; DISABLE:直接转换hadc.Init.ContinuousConvMode    = DISABLE;                        //ENABLE:连续模式, DISABLE:单次模式hadc.Init.DiscontinuousConvMode = DISABLE;                        //非连续转换模式设置hadc.Init.ExternalTrigConv      = ADC_SOFTWARE_START;             //触发模式设置hadc.Init.ExternalTrigConvEdge  = ADC_EXTERNALTRIGCONVEDGE_NONE;  //外部触发沿设置hadc.Init.DMAContinuousRequests = DISABLE;                        //DMA连续模式设置hadc.Init.Overrun               = ADC_OVR_DATA_OVERWRITTEN;       //ADC_OVR_DATA_OVERWRITTEN:过载时覆盖,ADC_OVR_DATA_PRESERVED:保留旧值if (HAL_ADC_Init(&hadc) != HAL_OK) Error_Handler();               //初始化ADC/* Configure selected ADC channels  */sConfig.Channel      = ADC_CHANNEL_0;                             sConfig.Rank         = ADC_RANK_CHANNEL_NUMBER;                   sConfig.SamplingTime = ADC_SAMPLETIME_71CYCLES_5;                 if (HAL_ADC_ConfigChannel(&hadc, &sConfig) != HAL_OK)             Error_Handler();sConfig.Channel = ADC_CHANNEL_1;                                  sConfig.Rank = ADC_RANK_CHANNEL_NUMBER;                           sConfig.SamplingTime = ADC_SAMPLETIME_71CYCLES_5;                 if (HAL_ADC_ConfigChannel(&hadc, &sConfig) != HAL_OK)             Error_Handler();sConfig.Channel = ADC_CHANNEL_4;                                  sConfig.Rank = ADC_RANK_CHANNEL_NUMBER;                           sConfig.SamplingTime = ADC_SAMPLETIME_71CYCLES_5;                 if (HAL_ADC_ConfigChannel(&hadc, &sConfig) != HAL_OK)             Error_Handler();
}

在 app_adc.c 中定义了业务所需的变量,功能函数也在一个 .c 文件中全部实现。这样做是参考了面向对象的编程模式,遵循代码/变量和功能解耦的原则,ADC 所需的全局变量都在 app_adc.c 中定义,main.c 中就不用再引用 ADC 相关的变量,也不用关心实现的细节了。唯一的接口就是 ADC_Sample() 函数的 sampleResult,sampleResult 定义为一个字符串具有很好的通用性,并隐藏了实现的细节。这里例子中被注释掉的 JSON 串返回结果的代码,在实际应用中,在上一层的业务逻辑处理是很方便的。当然 MCU 编程,一般不会采用 JSON 这种富文本的格式,这里只作为一种示例。

ADC_Sample() 函数中每次采样之前都对 ADC 进行了校准,校准完成后开始采样,采样完毕后停止 ADC。

在 py32f0xx_hal_msp.c 文件中指定 GPIO 及其复用功能

/*** -----------------------------------------------------------------------* @name   : void HAL_ADC_MspInit(ADC_HandleTypeDef *hadc)* @brief  : 初始化 ADC 相关 MSP* @param  : [in] *hadc, ADC handler pointer* @retval : void* @remark :* -----------------------------------------------------------------------
*/
void HAL_ADC_MspInit(ADC_HandleTypeDef *hadc)
{GPIO_InitTypeDef GPIO_InitStruct = {0};/*=============PA0/1/4初始化=============*/if (hadc->Instance == ADC1){__HAL_RCC_ADC_CLK_ENABLE();   /* Peripheral clock enable */__HAL_RCC_GPIOA_CLK_ENABLE(); /*ADC GPIO ConfigurationPA0     ------> ADC_IN0PA1     ------> ADC_IN1PA4     ------> ADC_IN5*/GPIO_InitStruct.Pin = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_4; // 指定 PA0/1/4GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;                    // 设置为模拟端口GPIO_InitStruct.Pull = GPIO_PULLDOWN;                       // 下拉:无输入时采样值接近零HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);                     // 执行初始化}
}

按照厂家例程的文件组织,所有的 HAL_xxx_MspInit() 集中在 py32_f0xx_hal_msp.c 文件中,由于在 ADC_Init() 函数中调用了 HAL_ADC_Init() 函数,要调用 HAL_ADC_MspInit(),这个函数在 HAL 库中的原型是 weak 类型的,并且是一个空函数,因此需要在实用中重写。

当然,把 HAL_ADC_MspInit() 函数在 app_adc.c 文件中实现也是可以的。

修改 DEBUG 口的管脚映射

PY32F003 ADC1 的通道 0/1/5 复用了 PA0/1/4,之前的实验中,PA1/0 被用作了 DEBUG 口 UART2,和 ADC1 的通道是冲突的,所以需要把 DEBUG 口对应的管脚挪走。查了数据手册,AF4 组的 PA2/3 可以用作 UART2,修改 UART_Config() 如下。

除了修改管脚映射以外,中断优先级等的不做修改。

HAL_StatusTypeDef USART_Config(void)
{// Using PA2/PA3 (TX/RX)HAL_StatusTypeDef conf_res = HAL_OK;GPIO_InitTypeDef GPIO_InitStruct;gUartInited = 0;    //====================// USART2初始化//====================__HAL_RCC_USART2_CLK_ENABLE();__HAL_RCC_GPIOA_CLK_ENABLE();UartHandle.Instance = USART2;UartHandle.Init.BaudRate = 115200;UartHandle.Init.WordLength = UART_WORDLENGTH_8B;UartHandle.Init.StopBits = UART_STOPBITS_1;UartHandle.Init.Parity = UART_PARITY_NONE;UartHandle.Init.HwFlowCtl = UART_HWCONTROL_NONE;UartHandle.Init.Mode = UART_MODE_TX_RX;conf_res = HAL_UART_Init(&UartHandle);if(conf_res != HAL_OK) return conf_res;/**USART2 GPIO ConfigurationPA2     ------> USART2_TXPA3     ------> USART2_RX*/GPIO_InitStruct.Pin = GPIO_PIN_2 | GPIO_PIN_3;GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;GPIO_InitStruct.Pull = GPIO_PULLUP;GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;GPIO_InitStruct.Alternate = GPIO_AF4_USART2;HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);HAL_NVIC_SetPriority(USART2_IRQn, 0, 3);    // 使能NVICHAL_NVIC_EnableIRQ(USART2_IRQn);            // 使能USART2中断gUartInited = 1;return conf_res;
}

在 main.c 的主循环中采样

int main(void)
{HAL_Init();             // systick初始化SystemClock_Config();   // 配置系统时钟GPIO_Config();if(USART_Config() != HAL_OK) Error_Handler();         printf("[SYS_INIT] Debug port initilaized.\r\n");ADC_Init();printf("[SYS_INIT] ADC initilaized.\r\n");printf("\r\n+---------------------------------------+""\r\n|        PY32F003 MCU is ready.         |""\r\n+---------------------------------------+""\r\n         10 digits sent to you!          ""\r\n+---------------------------------------+""\r\n");if (DBG_UART_Start() != HAL_OK) Error_Handler();char sres[64]={0};uint8_t sIndex = 0;while (1){ BSP_LED_Toggle(LED3);if(sIndex % 2 == 0){if(ADC_Sample(sres) == HAL_OK){printf("%s\r\n", sres);}else{printf("Sample error.\r\n");}}sIndex ++;}HAL_Delay(500);
}

代码中,主循环每 0.5s 翻转一次 LED,每 1s 采样一次。

实验结果

初次跑通

按照上述步骤编写好代码,编译烧录,在 XCOM 上得到的结果如图。初次运行,PA0/1/4 出于悬空状态,得到的采样值是随机的。

注意到在 HAL_ADC_MspInt() 函数中,将 PA0/1/4 这三个管脚的 PULL 属性都设置成了 PULLDOWN,本想着即使悬空的话仍可得到接近 0 的采样值。但实验结果中,PA0 的悬空状态采样值仍在 1480 多的值,折合成电压为

  1480/4096*3.3 = 1.192V

这个值挺高的,而 PA1/4 管脚换算得到的电压值分别为 0.661/0.524V,这两个值也不低。这说明 PY32F003 的内部下拉应该是“弱下拉”——或许,在 HAL_ADC_Init() 函数中又对这几个管脚做了什么配置?这个问题留着以后关注。

基于此,在实际项目中用到 PY32F003 进行 ADC 时,在信号管脚接入前,要使用一个(或一组)运放做一下电压跟随才好。

采样时长

在 HAL_ADC_ConfigChannel() 中,设置了采样周期均为 71.5,加上转换的耗费 12.5 周期,合计84 个时钟周期,计算得到采样时间为 3.5us 一次,也挺快了了。

对 GND 和 VCC 的采样值

将 PA0 接地,然后再观察其采样值,得到了全“0”的采样结果。

将 PA0 接 3.3V 管脚,50次采样得到的平均值是 4087.22,换算得到 3.293V,也还好。

PULLUP 还是 PULLDOWN,还是 NOPULL?

把 PA0/1/4 都设置为内部上拉/下拉/无上下拉状态时,PA0 接地,测得 PA1/4 的采用值分别是:

PULLUP:2.159/2.191V,PULLDOWN:0.242/0.322V,NOPULL:1.990/3.061V

PA1和PA4的特性略有不同。

PA0 得到的采样值均为0,这说明管脚的 PULL 被初始化的状态不会对采样的测量值产生影响。

在 PA0 接 VCC 时,不论其 PULL 属性如何,对采样值也没有影响。

总结

  • 根据厂家例程移植,跑通 ADC 的轮询式采样是比较简单的。如果熟悉对 STM32 的 ADC 配置,可以照搬 STM32 的步骤。
  • 分配 ADC1 的采样通道时,要把开发板默认的 UART2 管脚和 ADC1 的采样通道管脚错开。
  • 当某一管脚配置为模拟信号时,其管脚的 PULL 属性对测量结果无影响。
  • 实用中,ADC1 的采样输入管脚最好使用运放做一个电压跟随器。
  • 0~VCC 中间值的采样精度如何,尚未验证,留待后续实验完成。

后续还会继续尝试使用 DMA 的 ADC,敬请期待。

谬误之处,恳请指正。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Hive】——DDL(CREATE TABLE)

1 CREATE TABLE 建表语法 2 Hive 数据类型 2.1 原生数据类型 2.2 复杂数据类型 2.3 Hive 隐式转换 2.4 Hive 显式转换 2.5 注意 3 SerDe机制 3.1 读写文件机制 3.2 SerDe相关语法 3.2.1 指定序列化类&#xff08;ROW FORMAT SERDE ‘’&#xff09; 3.2.2 指定分隔符&#xff0…

Python数据科学视频讲解:数据清洗、特征工程和数据可视化的注意事项

1.6 数据清洗、特征工程和数据可视化的注意事项 视频为《Python数据科学应用从入门到精通》张甜 杨维忠 清华大学出版社一书的随书赠送视频讲解1.6节内容。本书已正式出版上市&#xff0c;当当、京东、淘宝等平台热销中&#xff0c;搜索书名即可。内容涵盖数据科学应用的全流程…

ubuntu20.04安装完没有连接wifi的选项,ubuntu网卡驱动

目录 一.前言 二.关闭安全模式 三.安装网卡驱动 参考 一.前言 ubuntu20.04安装完没法连wifi&#xff0c;可能有如下两种原因,因为这两种原因都排除了后成功获得联网功能&#xff0c;所以安安也不清楚具体是那个原因:1.启动了安全模式。2.没有安装网卡驱动 二.关闭安全模式…

Debian 系统镜像下载

最近在看一些网络相关的文章需要用到 debian 11.x 的系统网上找了好多都发下载&#xff0c;在官网看一下 有个 11.8 的版本我无法下载&#xff0c;提示被最新的 debian-12.4.0 所代替&#xff0c;于是找到了这个链接 Index of /cdimage/unofficial/non-free/cd-including-fi…

Qt生成动态链接库并使用动态链接库

项目结构 整个工程由一个主程序构成和一个模块构成(dll)。整个工程的结构目录如下 Define.priMyProject.proMyProject.pro.user ---bin ---MainProgrammain.cppMainProgram.proMainProgram.pro.userwidget.cppwidget.hwidget.ui ---MathDllMathDll.proMathDll.pro.userMyMath.…

qt 标准对话框的简单介绍

qt常见的标准对话框主要有,标准文件对话框QFileDialog,标准颜色对话框QColorDialog,标准字体对话框QFontDialog,标准输入对话框QInputDialog,标准消息框QMessageBox...... 1. 标准文件对话框QFileDialog,使用函数getOpenFileName()获取用户选择的文件. //qt 函数getOpenFileN…

RabbitMQ-学习笔记(初识 RabbitMQ)

本篇文章学习于 bilibili黑马 的视频 (狗头保命) 同步通讯 & 异步通讯 (RabbitMQ 的前置知识) 同步通讯&#xff1a;类似打电话&#xff0c;只有对方接受了你发起的请求,双方才能进行通讯, 同一时刻你只能跟一个人打视频电话。异步通讯&#xff1a;类似发信息&#xff0c…

【Linux】使用Bash和GNU Parallel并行解压缩文件

介绍 在本教程中&#xff0c;我们将学习如何使用Bash脚本和GNU Parallel实现高效并行解压缩多个文件。这种方法在处理大量文件时可以显著加快提取过程。 先决条件 确保系统上已安装以下内容&#xff1a; BashGNU Parallel 你可以使用以下命令在不同Linux系统上安装它们&am…

Pytorch-CNN轴承故障一维信号分类(二)

目录 前言 1 数据集制作与加载 1.1 导入数据 1.2 数据加载&#xff0c;训练数据、测试数据分组&#xff0c;数据分batch 2 CNN-2D分类模型和训练、评估 2.1 定义CNN-2d分类模型 2.2 定义模型参数 2.3 模型结构 2.4 模型训练 2.5 模型评估 3 CNN-1D分类模型和训练、评…

swing快速入门(八)

注释很详细&#xff0c;直接上代码 上一篇 新增内容 cardLayout布局管理器 事件监听器的创建与绑定 多种布局与容器的结合使用 import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener;public class swing_test_6 {public static v…

Geek challenge 2023 EzHttp

打开链接需要使用post请求提交username和password 查看源码得到提示&#xff0c;爬虫想到robots协议 访问robots.txt 访问得到的路径&#xff1a;/o2takuXXs_username_and_password.txt 拿到用户名和密码&#xff1a; username:admin password:dm1N123456r00t# 进行post传参…

展望2024年供应链安全

2023年是开展供应链安全&#xff0c;尤其是开源治理如火如荼的一年&#xff0c;开源治理是供应链安全最重要的一个方面&#xff0c;所以我们从开源治理谈起。我们先回顾一下2023的开源治理情况。我们从信通院《2023年中国企业开源治理全景观察》发布的信息。信通院调研了来自七…

解决Eslint和Prettier关于三元运算符的冲突问题

万能大法 // eslint加入配置indent: 0三元运算符Prettier的格式化 三元运算符Eslint的格式要求 解决办法 // eslint加入配置&#xff0c;屏蔽标红报错indent: [error, 2, { ignoredNodes: [ConditionalExpression] }]效果

利用机器学习实现客户细分的实战

前言&#xff1a; Hello大家好&#xff0c;我是Dream。 今天来学习一下机器学习实战中的案例&#xff1a;创建客户细分&#xff0c;在此过程中也会补充很多重要的知识点&#xff0c;欢迎大家一起前来探讨学习~ 一、导入数据 在此项目中&#xff0c;我们使用 UCI 机器学习代码库…

C语言第四十六弹---最快方法找到杨氏矩阵中的数下标

C语言实现最快方法找到杨氏矩阵中数下标。 定义&#xff1a;杨氏矩阵是一种用于描述Young 表和表示论的工具&#xff0c;它在代数几何和组合数学中有广泛的应用。一个杨氏矩阵是一个以若干个正整数构成的矩形表格&#xff0c;且每行和每列的元素单调递增。 从定义中可获得条件…

可视化 Java 项目

有一定规模的 IT 公司&#xff0c;只要几年&#xff0c;必然存在大量的代码&#xff0c;比如腾讯&#xff0c;2019 年一年增加 12.9 亿行代码&#xff0c;现在只会更多。不管是对于公司&#xff0c;还是对于个人&#xff0c;怎么低成本的了解这些代码的对应业务&#xff0c;所提…

道路坑洞数据集(坑洞目标检测)VOC+YOLO格式650张

路面坑洞的形成原因是由于设计、施工、养护处理不当、控制不适和受气候、环境、地质、水文等自然因素影响&#xff0c;以及车辆的运行和车辆超载运行导致路面破损&#xff0c;出现坑洞的现象。 路面坑洞的分类&#xff1a; &#xff08;1&#xff09;路面混凝土板中坑洞&…

Node.js创建一个简单的WebSocket接口,实现通信交互

Node.js创建一个简单的WebSocket接口&#xff0c;实现通信交互 一、为什么使用WebSocket&#xff1f; WebSocket&#xff0c;最大特点就是&#xff0c;服务器可以主动向客户端推送信息&#xff0c;客户端也可以主动向服务器发送信息&#xff0c;是真正的双向平等对话&#xf…

基于CNN+数据增强+残差网络Resnet50的少样本高准确度猫咪种类识别—深度学习算法应用(含全部工程源码)+数据集+模型(一)

系列文章目录 基于CNN数据增强残差网络Resnet50的少样本高准确度猫咪种类识别—深度学习算法应用(含全部工程源码)数据集模型&#xff08;一&#xff09; 基于CNN数据增强残差网络Resnet50的少样本高准确度猫咪种类识别—深度学习算法应用(含全部工程源码)数据集模型&#xf…

【原创】【一类问题的通法】【真题+李6卷6+李4卷4(+李6卷5)分析】合同矩阵A B有PTAP=B,求可逆阵P的策略

【铺垫】二次型做的变换与相应二次型矩阵的对应&#xff1a;二次型f&#xff08;x1&#xff0c;x2&#xff0c;x3&#xff09;xTAx&#xff0c;g&#xff08;y1&#xff0c;y2&#xff0c;y3&#xff09;yTBy ①若f在可逆变换xPy下化为g&#xff0c;即P为可逆阵&#xff0c;有P…