【正点原子】STM32MP257 同构多核架构下的 ADC 电压采集与处理应用开发实战

在嵌入式系统中,ADC模拟电压的读取是常见的需求。如何高效、并发、且可控地完成数据采集与处理?本篇文章通过双线程分别绑定在 Linux 系统的不同 CPU 核心上,采集 /sys/bus/iio 接口的 ADC 原始值与缩放系数 scale,并在另一个核上计算真实电压值,适用于高性能、低延迟的工业控制场景。

在这里插入图片描述

正点原子 STM32MP257 同构多核架构下的 ADC 电压采集与处理应用开发实战

  • 一、背景介绍:为什么要用多核并发读取ADC?
  • 二、系统架构与源码解析
    • 1、数据采集线程(CPU0)
    • 2、数据处理线程(CPU1)
    • 3、两线程同步机制
    • 4、完整代码及使用方法
      • 1.完整代码展示
      • 2.使用方法
  • 三、应用场景与实际部署建议
    • 1、工业自动化控制
    • 2、边缘AI与信号预处理
    • 3、多任务实时系统调度
  • 四、测试效果与输出示例
  • 五、总结与拓展建议

一、背景介绍:为什么要用多核并发读取ADC?

在嵌入式 Linux 平台(如 STM32MP257、i.MX93 等)中,我们常使用工业级 ADC 进行传感器数据采集。通过内核 IIO 子系统,用户可以在 /sys/bus/iio/devices/iio:deviceX/ 目录下读取原始电压值和电压缩放因子(scale),从而计算出真实电压。

而本项目的设计目标,是实现 采集线程 + 处理线程分核运行,充分利用 A核多核系统的资源,提高数据采集实时性,降低主线程阻塞风险。

二、系统架构与源码解析

该项目通过两个线程分别运行在 CPU0 和 CPU1,线程间通过互斥锁和条件变量进行数据同步:

1、数据采集线程(CPU0)

  • 绑定在 CPU0
  • 定时读取:
    • 原始 ADC值:/sys/bus/iio/devices/iio:device0/in_voltage15_raw
    • 缩放系数:/sys/bus/iio/devices/iio:device0/in_voltage_scale
  • 通过共享内存区 shared_data 和 shared_scale,将数据传给处理线程
int val = read_sysfs_int(SYSFS_ADC_PATH);
float scale = read_sysfs_float(SYSFS_ADC_SCALE);
shared_data = val;
shared_scale = scale;

2、数据处理线程(CPU1)

  • 绑定在 CPU1
  • 阻塞等待数据更新信号
  • 计算真实电压:voltage = val × scale × 0.001
  • 可拓展滤波、特征提取、阈值报警等算法处理
float voltage = val * scale * 0.001;
printf("处理线程: 处理 %d × %.6f x 0.001 = %.2f V\n", val, scale, voltage);

3、两线程同步机制

  • 使用 pthread_mutex_t 和 pthread_cond_t 进行数据同步,确保线程安全。
  • data_ready 标志位控制数据更新通知。

4、完整代码及使用方法

1.完整代码展示

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#include <sched.h>#define SYSFS_ADC_PATH "/sys/bus/iio/devices/iio:device0/in_voltage15_raw"
#define SYSFS_ADC_SCALE "/sys/bus/iio/devices/iio:device0/in_voltage_scale"#define ACQ_INTERVAL_US 500000  // 500 msstatic int shared_data = 0;
static float shared_scale = 0.0f;
static int data_ready = 0;static pthread_mutex_t data_lock = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t data_cond = PTHREAD_COND_INITIALIZER;static void bind_thread_to_cpu(pthread_t tid, int cpu)
{cpu_set_t cpuset;CPU_ZERO(&cpuset);CPU_SET(cpu, &cpuset);if (pthread_setaffinity_np(tid, sizeof(cpuset), &cpuset) != 0) {fprintf(stderr, "警告:无法将线程绑定到 CPU%d: %s\n",cpu, strerror(errno));}
}static int read_sysfs_int(const char *path)
{int fd = open(path, O_RDONLY);if (fd < 0) return -1;char buf[32];ssize_t len = read(fd, buf, sizeof(buf)-1);close(fd);if (len <= 0) return -1;buf[len] = '\0';return atoi(buf);
}static float read_sysfs_float(const char *path)
{int fd = open(path, O_RDONLY);if (fd < 0) return -1.0f;char buf[32];ssize_t len = read(fd, buf, sizeof(buf)-1);close(fd);if (len <= 0) return -1.0f;buf[len] = '\0';return atof(buf);
}static void *acquisition_thread(void *arg)
{pthread_t tid = pthread_self();bind_thread_to_cpu(tid, 0);printf("采集线程绑定到 CPU0\n");while (1) {int val = read_sysfs_int(SYSFS_ADC_PATH);float scale = read_sysfs_float(SYSFS_ADC_SCALE);if (val < 0 || scale <= 0) {perror("读取ADC或Scale失败");usleep(ACQ_INTERVAL_US);continue;}pthread_mutex_lock(&data_lock);shared_data = val;shared_scale = scale;data_ready = 1;pthread_cond_signal(&data_cond);pthread_mutex_unlock(&data_lock);printf("采集线程: 原始值=%d, scale=%.6f\n", val, scale);usleep(ACQ_INTERVAL_US);}return NULL;
}static void *processing_thread(void *arg)
{pthread_t tid = pthread_self();bind_thread_to_cpu(tid, 1);printf("处理线程绑定到 CPU1\n");while (1) {pthread_mutex_lock(&data_lock);while (!data_ready) {pthread_cond_wait(&data_cond, &data_lock);}int val = shared_data;float scale = shared_scale;data_ready = 0;pthread_mutex_unlock(&data_lock);float voltage = val * scale * 0.001;printf("处理线程: 处理 %d × %.6f x 0.001 = %.2f V\n", val, scale, voltage);}return NULL;
}int main(int argc, char *argv[])
{pthread_t tid_acq, tid_proc;int ret;ret = pthread_create(&tid_acq, NULL, acquisition_thread, NULL);if (ret) {fprintf(stderr, "创建采集线程失败: %s\n", strerror(ret));return 1;}ret = pthread_create(&tid_proc, NULL, processing_thread, NULL);if (ret) {fprintf(stderr, "创建处理线程失败: %s\n", strerror(ret));return 1;}pthread_join(tid_acq, NULL);pthread_join(tid_proc, NULL);return 0;
}

2.使用方法

将以上代码编辑为 adc_app.c 文件,在 ubuntu 系统里使用以下命令交叉编译为可执行文件即可:

source /opt/st/stm32mp2/5.0.3-snapshot/environment-setup-cortexa35-ostl-linux
${CC} -o adc_app adc_app.c

在这里插入图片描述
最终生成的 adc_app 文件就是我们需要放到 STM32MP257 文件系统里的可执行文件。

注意事项:在STM32MP257的百度资料网盘里已经提供了交叉编译工具链的安装脚本,文件路径是 “STM32MP257开发板\05、开发工具\01、出厂系统交叉编译器” ,请大家可以自行去下载使用。

atk-image-openstlinux-weston-stm32mp2.rootfs-x86_64-toolchain-5.0.3-snapshot-20250115-v1.0

在这里插入图片描述

三、应用场景与实际部署建议

本方案适用于以下典型场景:

1、工业自动化控制

  • 实时读取传感器电压信号(如压力、温湿度、光强等)
  • 多核处理确保主线程响应不中断
  • 电压计算后可直接用于闭环 PID 控制逻辑

2、边缘AI与信号预处理

  • 采集模拟数据后可直接进行数字滤波、傅里叶变换等前处理
  • 数据处理线程也可通过 RPMsg 发送到 Cortex-M33 协处理核做进一步处理

3、多任务实时系统调度

  • 多核绑定可防止线程“漂移”,适用于带有调度器的 RT-PREEMPT 系统
  • 强化线程的确定性和性能隔离

四、测试效果与输出示例

运行后,终端将周期性打印如下信息:
在这里插入图片描述

说明:

  • in_voltage15_raw = 4095 表示ADC原始数值
  • scale = 0.439453 mV/LSB 是 ADC 的电压精度
  • 最终电压 = 4095* 0.439453 * 0.001 ≈ 1.8V

执行 adc_app 可执行文件后,我们用 ssh 打开 STM32MP257 的新终端,用以下指令可以查看 这个例程的调用 cpu 使用情况:

top -H -p $(pidof adc_app)

在这里插入图片描述
通过终端显示的消息,可以看到 adc_app 主线程在 CPU1 里使用,采集数据 和 处理数据的线程 分别在 CPU0 和 CPU1 里分别使用。

五、总结与拓展建议

通过绑定线程至特定 CPU 核心,并使用条件变量进行线程同步,我们实现了一个 低延迟、高稳定性 的 ADC 电压采集处理方案。它可轻松适配到任意支持 Linux 的 ARM 多核平台,推荐用于工业控制、信号处理、边缘AI等高实时场景。

后续可以拓展:

  • 将数据通过 Socket/UDP/CanOpen 发送
  • 写入共享内存供 GUI 使用
  • 增加多通道采集
  • 与 Cortex-M 核通信(RPMsg)

如果你也在做 STM32MP257 / i.MX93 / RK3588 等平台的异构核协同处理,不妨试试这种方案!有问题欢迎评论区一起探讨交流!

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

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

相关文章

电商用户购物行为分析:基于K-Means聚类与分类验证的完整流程

随着电商行业的快速发展,用户行为分析成为企业优化营销策略、提升用户体验的重要手段。通过分析用户的购物行为数据,企业可以挖掘出用户群体的消费特征和行为模式,从而制定更加精准的营销策略。本文将详细介绍一个基于Python实现的电商用户购物行为分析系统,涵盖数据预处理…

AMGCL库的Backends及使用示例

AMGCL库的Backends及使用示例 AMGCL是一个用于解决大型稀疏线性方程组的C库&#xff0c;它提供了多种后端(backends)实现&#xff0c;允许用户根据不同的硬件和性能需求选择合适的计算后端。 AMGCL支持的主要Backends 内置Backends: builtin - 默认的纯C实现block - 支持块状…

Express中间件(Middleware)详解:从零开始掌握(3)

实用中间件模式25例 1. 基础增强模式 请求属性扩展 function extendRequest() {return (req, res, next) > {req.getClientLanguage () > {return req.headers[accept-language]?.split(,)[0] || en;};next();}; } 响应时间头 function responseTime() {return (r…

05--MQTT物联网协议

一、MQTT的概念 MQTT 协议快速入门 2025&#xff1a;基础知识和实用教程 | EMQ 1.MQTT&#xff08;Message Queuing Telemetry Transport&#xff09;是一种轻量级、基于发布-订阅模式的消息传输协议&#xff0c;适用于资源受限的设备和低带宽、高延迟或不稳定的网络环境。它…

数据结构与算法——链表OJ题详解(2)

文章目录 一、前言二、OJ续享2.1相交链表2.2环形链表12.2环形链表2 三、总结 一、前言 哦了兄弟们&#xff0c;咱们上次在详解链表OJ题的时候&#xff0c;有一部分OJ题呢up并没有整理完&#xff0c;这一个星期呢&#xff0c;up也是在不断的学习并且沉淀着&#xff0c;也是终于…

SQL Server AlwaysOn (SQL 查询数据详解及监控用途)

修正后的完整查询 SELECT ar.replica_server_name AS [副本名称],ar.availability_mode_desc AS [同步模式],DB_NAME(dbr.database_id) AS [数据库名称],dbr.database_state_desc AS [数据库状态],dbr.synchronization_state_desc AS [同步状态],dbr.synchronization_health_d…

力扣热题100刷题day63|49.字母异位词分组

目录 一、哈希表相关理论 二、思路 核心思路 三、相关题目 四、总结 一、哈希表相关理论 代码随想录刷题day15|&#xff08;哈希表篇&#xff09;242.有效的字母异位词、383.赎金信-CSDN博客 二、思路 首先&#xff0c;创建一个map集合&#xff0c;遍历字符串数组&…

爱普生可编程晶振SG8201CJ和SG8200CJ在胃镜机器人发挥重要作用

在医疗机器人技术高速发展的今天&#xff0c;胃镜机器人作为胃肠道疾病诊断与治疗的创新设备&#xff0c;正逐渐改变传统诊疗模式。其复杂精密的系统需要精准的时间同步与稳定的信号输出&#xff0c;胃镜机器人是一种先进的医疗设备&#xff0c;用于无创性地检查胃部疾病。与传…

Ubuntu22环境下,Docker部署阿里FunASR的gpu版本

番外: 随着deepseek的爆火,人工智能相关的开发变得异常火爆,相关的大模型开发很常见的agent智能体需要ASR语音识别的功能,阿里开源的FunASR几乎是把一个商业的项目放给我们使用了。那么我们项目中的生产环境怎么部署gpu版本的语音识别服务呢?经过跟deepseek的一上午的极限…

图解Java设计模式

1、设计模式面试题 2、设计模式的重要性 3、7大设计原则介绍 3.1、单一职责原则

transformers的 pipeline是什么:将模型加载、数据预处理、推理等步骤进行了封装

transformers的 pipeline是什么:将模型加载、数据预处理、推理等步骤进行了封装 pipe = pipeline("text-generation", model=model, tokenizer=tokenizer, max_new_tokens=50 )pipeline :这是 transformers 库中一个非常实用的工具函数。它可以基于预训练模型快速构…

jmeter插件安装

1、下载 下载地址&#xff1a; Documentation :: JMeter-Plugins.org 然后复制到D:\apache-jmeter-5.6.3\lib\ext 复制后 2、重启jmeter 在菜单【选项】找到“Plugins Manager” 在 Plugins Manager 界面上&#xff0c;点击“Available Plugins”标签页&#xff0c;可以浏览所…

VSCode CMake调试CPP程序

文章目录 1 安装C与CMake插件2 配置CMakeLists.txt3 使用CMake编译调试3.1 编译3.2 调试 4 自定义构建调试参考 1 安装C与CMake插件 C插件 CMake插件 2 配置CMakeLists.txt 编写测试程序 #include<iostream>int main(int argc, char const *argv[]) {int a 1, b 2;i…

【前端】【css】flex布局详解

Flex 布局&#xff08;Flexible Box Layout&#xff0c;弹性盒子布局&#xff09;是 CSS3 中的一种布局模式&#xff0c;用于在容器中更高效地分配空间并对齐内容&#xff0c;即使它们的大小是动态未知的。它非常适用于响应式设计。 一、Flex 布局的基本概念 1. 启用 Flex 布局…

LEARNING DYNAMICS OF LLM FINETUNING【论文阅读笔记】

LEARNING DYNAMICS OF LLM FINETUNING 一句话总结 作者将LLM的学习动力机制拆解成AKG三项&#xff0c;并分别观察了SFT和DPO训练过程中​​正梯度信号​​和​​负梯度信号​​的变化及其带来的影响&#xff0c;并得到以下结论&#xff1a; ​​SFT通过梯度相似性间接提升无关…

Mac 下载 PicGo 的踩坑指南

Mac 下载 PicGo 的踩坑指南 一、安装问题 下载地址&#xff1a;https://github.com/Molunerfinn/PicGo/releases 下载之后直接安装即可&#xff0c;此时打开会报错&#xff1a;Picgo.app 文件已损坏&#xff0c;您应该将它移到废纸篓。 这是因为 macOS 为了保护用户不受恶意…

Element UI 设置 el-table-column 宽度 width 为百分比无效

问题描述&#xff1a; 想要每列宽度不同&#xff0c;不想使用 px 固定值&#xff0c;将 width 设置成百分比&#xff0c;但是每一列还是很窄 原因&#xff1a; el-table 组件会被 vue 解析成 html&#xff0c;vue 直接把百分号去掉把数值当做列宽来呈现&#xff0c;所以&#x…

第五篇:Python面向对象编程(OOP)深度教程

1. 类与对象 1.1 基本概念 ​​类​​是创建对象的蓝图,定义了对象的​​属性​​(数据)和​​方法​​(行为)。​​对象​​是类的实例化实体,每个对象拥有独立的属性值和共享的类方法 ​​示例​​:定义Dog类 class Dog:species = "Canis familiaris" …

【数据结构】2.顺序表实现通讯录

文章目录 一、通讯录的要求二、通讯录的具体实现0、 准备工作1、通讯录的初始化2、通讯录的销毁3、通讯录的展示4、通讯录添加数据5、通讯录删除数据6、通讯录的查找7、通讯录的修改8、保存通讯录数据到文件9、读取文件内容到通讯录 三、 通讯录的完整实现 一、通讯录的要求 通…

程序化广告行业(79/89):技术革新与行业发展脉络梳理

程序化广告行业&#xff08;79/89&#xff09;&#xff1a;技术革新与行业发展脉络梳理 大家好&#xff01;一直以来&#xff0c;我都热衷于在技术领域不断探索&#xff0c;也深知知识共享对于进步的重要性。写这篇博客&#xff0c;就是希望能和大家一起深入研究程序化广告行业…