在嵌入式系統中,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 等平臺的異構核協同處理,不妨試試這種方案!有問題歡迎評論區一起探討交流!