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

相关推荐
free慢26 分钟前
Docker组件详解:核心技术与架构分析
docker·eureka·架构
DIY机器人工房1 小时前
[6-1] TIM定时中断 江协科技学习笔记(45个知识点)
笔记·科技·stm32·单片机·学习
亚里随笔2 小时前
StreamRL:弹性、可扩展、异构的RLHF架构
人工智能·架构·大语言模型·rlhf·推理加速
602寝室长3 小时前
RT-THREAD RTC组件中Alarm功能驱动完善
stm32·单片机·rt-thread·rtos
憧憬一下4 小时前
stm32之USART
stm32·单片机·嵌入式硬件·串口·嵌入式·usart
what_20186 小时前
集群/微服务/分布式
运维·微服务·架构
shinelord明6 小时前
【计算机主板架构】ATX架构
架构·计算机外设·硬件架构·硬件工程
pccai-vip6 小时前
系分论文《论多云架构治理的分析和应用》
架构·软考论文
又熟了7 小时前
WDG看门狗(独立看门狗和窗口看门狗)
c语言·stm32·单片机·嵌入式硬件
AWS官方合作商17 小时前
AWS VPC架构师指南:从零设计企业级云网络隔离方案
安全·架构·aws