【正点原子】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 等平台的异构核协同处理,不妨试试这种方案!有问题欢迎评论区一起探讨交流!

相关推荐
笑远35 分钟前
不同服务器架构(x86、ARM、Power、SPARC)对数据库使用的影响
运维·服务器·数据库·架构
season_zhu2 小时前
iOS开发:关于导航控制器
ios·架构·swift
数据智能老司机2 小时前
使用Python和PyTorch的生成式AI——开源大语言模型(LLMs)
pytorch·架构·llm
AronTing2 小时前
01-Spring Cloud Alibaba 微服务架构总览与核心组件详解
java·微服务·架构
austin流川枫3 小时前
线程池深入分析:参数设计优化和避坑指南
java·后端·架构
向哆哆3 小时前
Java 架构设计:从单体架构到微服务的转型之路
java·微服务·架构
渣渣灰95873 小时前
Keil创建自定义的STM32标准库工程
stm32·单片机·嵌入式硬件
weisian1514 小时前
中间件--ClickHouse-1--基础介绍(列式存储,MPP架构,分布式计算,SQL支持,向量化执行,亿万级数据秒级查询)
clickhouse·中间件·架构
GoldenaArcher4 小时前
[React] 如何用 Zustand 构建一个响应式 Enum Store?附 RTKQ 实战与 TS 架构落地
react.js·架构·状态模式
星仔_X5 小时前
STM32 HAL库之USART示例代码
arm开发·stm32·单片机·嵌入式硬件