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

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

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

一、背景介绍:为什么要用多核并发读取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,将数据传给处理线程
c 复制代码
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
  • 可拓展滤波、特征提取、阈值报警等算法处理
c 复制代码
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.完整代码展示

c 复制代码
#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 ms

static 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 系统里使用以下命令交叉编译为可执行文件即可:

bash 复制代码
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、出厂系统交叉编译器" ,请大家可以自行去下载使用。

bash 复制代码
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 使用情况:

bash 复制代码
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 等平台的异构核协同处理,不妨试试这种方案!有问题欢迎评论区一起探讨交流!

相关推荐
yunteng52113 小时前
通用架构(同城双活)(单点接入)
架构·同城双活·单点接入
麦聪聊数据13 小时前
Web 原生架构如何重塑企业级数据库协作流?
数据库·sql·低代码·架构
程序员侠客行14 小时前
Mybatis连接池实现及池化模式
java·后端·架构·mybatis
bobuddy16 小时前
射频收发机架构简介
架构·射频工程
桌面运维家16 小时前
vDisk考试环境IO性能怎么优化?VOI架构实战指南
架构
xuxg200516 小时前
4G 模组 AT 命令解析框架课程正式发布
stm32·嵌入式·at命令解析框架
一个骇客18 小时前
让你的数据成为“操作日志”和“模型饲料”:事件溯源、CQRS与DataFrame漫谈
架构
CODECOLLECT18 小时前
京元 I62D Windows PDA 技术拆解:Windows 10 IoT 兼容 + 硬解码模块,如何降低工业软件迁移成本?
stm32·单片机·嵌入式硬件
鹏北海-RemHusband18 小时前
从零到一:基于 micro-app 的企业级微前端模板完整实现指南
前端·微服务·架构
BackCatK Chen18 小时前
STM32+FreeRTOS:嵌入式开发的黄金搭档,未来十年就靠它了!
stm32·单片机·嵌入式硬件·freertos·低功耗·rtdbs·工业控制