PWM 会出波形了。今天把电压变成数字。板上这组转换器也被用户键用了两路,读数前先认准设备名字。
往外部端子灌 2 V,页面上的毫伏数不动。第一反应是「ADC 坏了」。打开 IIO,读的却是 iio:device0/in_voltage0_raw。device0 的名字是 ad9833,那是波形芯片抢走的编号,根本没有 in_voltage*。SARADC 的名字是 fe720000.saradc ,运行时多半是 iio:device1 。通道 0/1 还被板载 KEY3/KEY4 占用。外部端子是 VIN2~VIN7 ,对应 in_voltage2_raw ... in_voltage7_raw。
前端不是把 ±5 V 直灌进 SoC。端子先经过运放和分压,缩到 SARADC 的 0~1.8 V。你在页面上读到的是 脚上的 raw,不是端子上的伏特。2 V 灌进去不会显示 2000。要两点标定。
产品 rk3568_evb:
bash
device/board/rk/rk3568_evb/kernel/rk3568-evb-linux.dts


官方连线图:信号、地、参考。这块板参考是 vcca_1v8,不是 3.3 V。流程图:打开设备 → 选通道 → 读 raw → 换算。开源鸿蒙用户态走 IIO sysfs,不走 HDF ADC 那套,除非你另外配了 HCS。两边不要同时绑同一颗 SARADC。
1. 通道怎么分
RK3568 SARADC:10 bit(有的镜像报 12 bit,以 in_voltage_scale 为准),8 通道,控制器 saradc@fe720000。
| 通道 | sysfs | 接到哪 |
|---|---|---|
| 0 | in_voltage0_raw | KEY3,VIN0 |
| 1 | in_voltage1_raw | KEY4,VIN1 |
| 2~7 | in_voltage2_raw ... 7 | 外部端子 ADCINB0~5,经运放 |
按 KEY3,通道 0 的 raw 掉到接近 0------说明你找对了 SARADC 这颗 IIO。不要用通道 0/1 当外部电压输入,按键会乱报,外部端子也不会变。
bash
hdc shell "cat /sys/bus/iio/devices/iio:device*/name"
典型:
bash
ad9833
fe720000.saradc
max30100
hdc1080
veml6040
mlx90614
adc_linux_virtual_iio_driver
按名字找:
bash
for d in /sys/bus/iio/devices/iio:device*; do
echo -n "$d "
cat $d/name
done
name 含 saradc 或等于 fe720000.saradc 的那颗,才读 in_voltageN_raw。
bash
hdc shell "cat /sys/bus/iio/devices/iio:device1/in_voltage_scale"
hdc shell "cat /sys/bus/iio/devices/iio:device1/in_voltage2_raw"
脚电压 mV ≈ raw × scale。1.8 V / 1024 ≈ 1.76 mV/LSB。满量程 raw≈1023 对应约 1800 mV。超了削顶,一直 1023 不是「2 V 读成了 1023 毫伏」,是已经顶格。
2. 前端运放:±5 V 进端子,1.8 V 进芯片
外部六路经过两块运放(常见 TLC2274 一类,0.8 V 偏置 + 电阻分压,独立 3.3 V 给模拟前端)。端子允许大约 ±5 V(以原理图耐压为准),缩到 SARADC 脚的 0~1.8 V。
bash
端子 VIN (±5 V)
│ 分压 + 0.8 V 偏置
▼
运放
│ 0~1.8 V
▼
SARADC VIN2~VIN7
│ raw 0~1023
▼
sysfs in_voltageN_raw
负电压也能采:偏置让 0 V 端子落在 raw 中间附近,负端往下、正端往上。不要用「raw=0 就是 0 V」去套端子。
两点标定(推荐):
- 端子接地,记 raw0、脚电压 V0 = raw0 × scale
- 端子接已知 Vref(例如 2.500 V 基准),记 raw1、V1
- 增益 g = (2.500 - 0) / (V1 - V0),偏置 b = 0 - V0 × g
- 之后端子电压 ≈ (raw × scale) × g + b
应用里把 g、b 做成可填系数,不要写死在固件里。换模块、换电阻,系数就变。
前端没工作的症状:通道 2~7 全是同一个值,扫 0~5 V 都不动。查运放供电(丝印附近 3.3 V)、0.8 V 基准、端子有没有接到你以为的那一路。通道 0/1 会跟着 KEY3/KEY4 动,那只说明 SARADC 本身活着。
设备树:
bash
&saradc {
status = "okay";
vref-supply = <&vcca_1v8>;
};
内核 CONFIG_ROCKCHIP_SARADC=y、CONFIG_IIO=y。公版 adc_keys 挂通道 0,板载按键那篇已经把它 disabled。这里不要再打开。
3. 和波形芯片回环
没有示波器时,把 AD9833 的模拟输出接到某一路外部 ADC(不要接到通道 0/1)。使能正弦 1 kHz,raw 会在偏置附近晃。enable=0 之后交流分量下去,留下运放的直流偏置(常见大约 0.8 V 对应的 raw)。
采样率:sysfs 一次 cat 只有一个点,不是示波器。1 kHz 正弦用 shell 循环读,只能看见乱跳的数,不能还原波形。验收标准是「enable 前后 raw 的峰峰值明显不同」,不是画出漂亮的正弦。要画图,用下面的 C 尽量快地采几百个点,再在主机上画。
量错座:波形座和 ADC 端子是两排。黑表笔 AGND。交流大约零点几伏峰峰值,万用表 AC 档大约 0.15~0.2 V RMS 是有输出;DC 档看到偏置是正常的。
4. 完整 C:按名字找 SARADC,打 raw
bash
/* saradc_dump.c
* armv7-unknown-linux-ohos-clang --target=arm-linux-ohos \
* --sysroot=${OHOS_NATIVE}/sysroot \
* -o saradc_dump saradc_dump.c \
* -Wl,--dynamic-linker=/system/lib/ld-musl-arm.so.1
*
* ./saradc_dump # 打 ch0..7 一次
* ./saradc_dump 4 200 # 通道 4,采 200 点
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dirent.h>
#include <fcntl.h>
#include <unistd.h>
static int read_int(const char *path, int *out)
{
char buf[64];
int fd = open(path, O_RDONLY);
if (fd < 0) {
return -1;
}
int n = (int)read(fd, buf, sizeof(buf) - 1);
close(fd);
if (n <= 0) {
return -1;
}
buf[n] = 0;
*out = atoi(buf);
return 0;
}
static int find_saradc(char *out, int len)
{
DIR *d = opendir("/sys/bus/iio/devices");
struct dirent *e;
if (!d) {
return -1;
}
while ((e = readdir(d)) != NULL) {
char p[256], name[64];
int fd, n;
if (strncmp(e->d_name, "iio:device", 10) != 0) {
continue;
}
snprintf(p, sizeof(p), "/sys/bus/iio/devices/%s/name", e->d_name);
fd = open(p, O_RDONLY);
if (fd < 0) {
continue;
}
n = (int)read(fd, name, sizeof(name) - 1);
close(fd);
if (n <= 0) {
continue;
}
name[n] = 0;
if (strstr(name, "saradc")) {
snprintf(out, len, "/sys/bus/iio/devices/%s", e->d_name);
closedir(d);
return 0;
}
}
closedir(d);
return -1;
}
int main(int argc, char **argv)
{
char base[256], p[300];
int ch, n, i, raw;
if (find_saradc(base, sizeof(base)) < 0) {
fprintf(stderr, "没有 saradc。查 &saradc 和名字 fe720000.saradc\n");
return 1;
}
printf("IIO %s\n", base);
if (argc < 2) {
for (ch = 0; ch < 8; ch++) {
snprintf(p, sizeof(p), "%s/in_voltage%d_raw", base, ch);
if (read_int(p, &raw) == 0) {
printf("ch%d raw=%d%s\n", ch, raw,
(ch <= 1) ? " (KEY)" : " (外部)");
} else {
printf("ch%d 读失败\n", ch);
}
}
printf("外部看 ch2..7。灌电压时 ch0/1 会跟着按键跳,不是端子。\n");
return 0;
}
ch = atoi(argv[1]);
n = (argc > 2) ? atoi(argv[2]) : 100;
snprintf(p, sizeof(p), "%s/in_voltage%d_raw", base, ch);
for (i = 0; i < n; i++) {
if (read_int(p, &raw) < 0) {
perror(p);
return 2;
}
printf("%d\n", raw);
}
return 0;
}
把 200 个点拷回主机画散点,比在板上用 shell 循环 cat 干净。
5. ArkTS:标定系数放页面上
bash
import evbio from 'libevbio.so';
@Entry
@Component
struct SarAdc {
@State ch: number = 2;
@State raw: number = -1;
@State mv: string = '';
@State vin: string = '';
@State log: string = '按名字找 saradc,不要写死 device0';
@State gain: string = '1';
@State offset: string = '0';
private base: string = '';
private timer: number = -1;
private scale: number = 1.76;
build() {
Column() {
Text('SARADC VIN2~7').fontSize(22).fontWeight(FontWeight.Bold)
Row() {
ForEach([2, 3, 4, 5, 6, 7], (c: number) => {
Button('ch' + c.toString())
.onClick(() => { this.ch = c; })
.margin(4)
})
}
Text('raw ' + this.raw.toString() + ' 脚 ' + this.mv + ' mV').margin({ top: 8 })
Text('端子(标定后) ' + this.vin + ' V').fontSize(20)
Row() {
Text('gain').margin(4)
TextInput({ text: this.gain }).width(80).onChange((v: string) => { this.gain = v; })
Text('offset').margin(4)
TextInput({ text: this.offset }).width(80).onChange((v: string) => { this.offset = v; })
}.margin({ top: 8 })
Text(this.log).fontSize(14).margin({ top: 8 })
}.padding(16)
}
aboutToAppear() {
this.base = evbio.findIio('saradc');
if (this.base.length === 0) {
this.log = '没有 fe720000.saradc。device0 若是 ad9833,那是波形芯片';
return;
}
let s: string = evbio.readText(this.base + '/in_voltage_scale');
let sc: number = parseFloat(s);
if (sc > 0) {
this.scale = sc;
}
this.log = this.base + ' scale=' + this.scale.toString();
this.timer = setInterval(() => { this.tick(); }, 200);
}
tick() {
let t: string = evbio.readText(this.base + '/in_voltage' + this.ch.toString() + '_raw');
this.raw = parseInt(t, 10);
if (isNaN(this.raw)) {
return;
}
let pin: number = this.raw * this.scale;
this.mv = pin.toFixed(1);
let g: number = parseFloat(this.gain);
let o: number = parseFloat(this.offset);
if (isNaN(g)) { g = 1; }
if (isNaN(o)) { o = 0; }
this.vin = (pin / 1000.0 * g + o).toFixed(3);
}
aboutToDisappear() {
if (this.timer >= 0) {
clearInterval(this.timer);
}
}
}
HAP 读 sysfs 要 chmod in_voltage*_raw 和 name、in_voltage_scale。
通道 0/1 可以在诊断页只读显示「KEY3/KEY4」,不要标成「外部 1、外部 2」。
6. 现象对照
| 现象 | 原因 | 处理 |
|---|---|---|
| device0 没有 in_voltage | device0 是 ad9833 | 按 name=saradc 找,多半 device1 |
| 灌 2 V raw 不变 | 读的 ch0/1,或前端没电 | 改 ch2~7;量运放 3.3 V |
| raw 一直满量程 | 端子超过 1.8 V 映射后削顶 | 降电压或看分压 |
| 显示 2000 mV | 把 raw 当毫伏,或没标定 | raw×scale 是脚电压;端子要增益 |
| 负电压 raw 不翻 | 没把偏置算进去 | 两点标定 |
| 回环不晃 | 接到 KEY 通道或波形没 enable | 外部通道 + AD9833 enable=1 |
| KEY3 乱报 | 外部信号灌进 VIN0 | 外部只用 2~7 |
bash
flowchart TD
A[灌电压 raw 不动] --> B{name 是 saradc?}
B -->|否, ad9833| C[换设备, 按名字找]
B -->|是| D{通道是 2..7?}
D -->|0/1| E[那是 KEY3/KEY4]
D -->|是| F{ch2-7 全不变?}
F -->|是| G[运放供电 / 0.8V 基准 / 端子]
F -->|只有满量程| H[削顶, 降电压]
SARADC 叫 fe720000.saradc,编号常是 iio:device1。外部走通道 2~7,前端运放把 ±5 V 缩到 0~1.8 V。C 和 ArkTS 都读 raw,端子伏特靠两点标定。波形芯片可以回环,当没有示波器时的活证据。
读 raw 是第一件事。换算、标定、回环都排在名字找对之后。
系列第 29 篇 · 芯片:瑞芯微 RK3568 · OpenHarmony 4.1(API 11) · Linux 5.10