TinyOS 用户程序 API 参考(goc32)
这篇是 TinyOS 用户程序 API 参考,以 user/lib.h 为准,适用于 goc32 编译的 user/*.c。
所有声明都在:
它会把 types.h 和 tos.h 一起带进来,所以 size_t、uint32_t、TOS_PIN_*、GPIO_OP_* 等都能直接用。
1. 程序入口与镜像笔记
- 用户程序是 PIE,由内核在 exec 时重定位到任务窗口。
argv来自 shell/exec();argv[0]是命令名。- 返回值就是退出码;也可以调用
exit(code)。
TOS_STACK(bytes)
覆盖程序默认栈大小。放在文件作用域,只在程序里写一次:
- 必须是 2 的幂、至少一页,且不超过板子的任务窗口粒度(C3/RP2040 为 16 KiB,PC/QEMU 为 64 KiB)。
- 内核在 exec 前读取这个 note,用它决定任务窗口大小。
TOS_HEAP(bytes)
声明程序需要的堆:
- 不写这个 note 时,堆只有任务窗口对齐后剩下的“边角料”,小工具里经常刚好是 0,
malloc()会全部返回 NULL。 - 只要程序调用
malloc,就应该声明。 - 它是下限,不是上限;窗口向上取整多出来的部分仍然归堆。
2. 进程与系统
| 函数 | 说明 |
|---|---|
int getpid(void) | 当前任务 pid |
void exit(int code) | 立即退出,code 是退出码 |
int exec(const char *path, char *const argv[], int argc) | 启动一个程序,返回新任务 pid(不是替换当前进程) |
int wait(int pid) | 等待任务结束,返回退出码 |
int yield(void) | 主动让出 CPU |
int ps(void) | 打印任务表,返回任务数 |
int setprio(int pid, int prio) | 设置优先级,0 最高、2 最低;pid=0 表示自己 |
int getcpu(void) | 当前运行在哪个 CPU 上 |
void reboot(void) | 重启,不返回 |
exec 的路径是文件系统里的路径,例如 bin/hello:
3. 时间
| 函数 | 说明 |
|---|---|
int sleep(int ms) | 睡眠毫秒,让出 CPU |
int uptime(void) | 开机以来的毫秒数(调度器时钟) |
int clockinfo(struct clockinfo *c) | 填充高精度时钟信息 |
uint64_t clock_ns(void) | 单调纳秒 |
uint32_t clock_ms(void) | 毫秒(低 32 位) |
clock_ns/ms 来自自由运行的硬件计数器,不是 10 ms 调度 tick;适合测耗时。
4. 控制台与字符 I/O
| 函数 | 说明 |
|---|---|
int write(int fd, const void *buf, int len) | 写 fd |
int write_all(int fd, const void *buf, int len) | 循环写直到写完;管道满时会短写,输出大块数据用它 |
int read(int fd, void *buf, int len) | 读 fd |
int ttyraw(int on) | 切换控制台原始模式:返回上一个模式,1=raw |
int getline(char *buf, int max) | 从 fd 0 读一行,去掉换行,返回长度 |
int hist_count(void) | shell 历史条数 |
int hist_get(int newest_index, char *buf, int cap) | 取第 N 条历史(1=最近一条),buf 至少 256 字节 |
约定:0 标准输入,1 标准输出,2 标准错误(内核/管道重定向使用)。
ttyraw(1) 后 read(0) 会原样返回字节(方向键等),不再回显;进程退出时内核会恢复 cooked 模式。
5. 文件与目录
| 函数 | 说明 |
|---|---|
int open(const char *path) | 以只读方式打开,返回 fd;失败 <0 |
int create(const char *path) | 创建/截断文件,返回 fd |
int close(int fd) | 关闭 |
int unlink(const char *path) | 删除普通文件 |
int read(int fd, void *buf, int len) | 读,返回字节数,0=EOF |
int write(int fd, const void *buf, int len) | 写 |
int stat(const char *path, struct stat *st) | 文件信息 |
int readdir(int index, char *name, int len) | 按索引读目录项 |
int chdir(const char *path) | 切换当前目录 |
int getcwd(char *buf, int len) | 取当前目录,返回长度或 -1 |
int dfinfo(struct dfinfo *d) | littlefs 使用量 |
open() 没有 flags 参数;需要新建/清空用 create()。目录项用索引遍历:
6. 管道与重定向
| 函数 | 说明 |
|---|---|
int pipe(int fds[2]) | 创建管道,fds[0] 读端、fds[1] 写端 |
int dup2(int oldfd, int newfd) | 把 oldfd 复制到 newfd(用于重定向 0/1/2) |
典型用法:
read() 在空管道且还有写端时会阻塞;所有写端关闭后返回 0(EOF)。
7. 内存
| 函数 | 说明 |
|---|---|
void *malloc(size_t n) | 分配;失败返回 NULL |
void free(void *p) | 释放 |
void *sbrk(int bytes) | 直接移动 break,返回旧 break 或 -1 |
int meminfo(struct meminfo *m) | 内核堆统计 |
再次提醒:调用 malloc 的程序要写 TOS_HEAP(...)。
8. 字符串与格式化
输出:
printf/tos_vsnprintf 支持的格式是 %d %u %x %c %s %%,可带零/空格宽度;没有 %p、%ld、浮点格式。需要把文本写进缓冲区时用 tos_vsnprintf(没有公开的 snprintf 包装)。
9. GPIO 与 ADC
模式常量:
| 常量 | 含义 |
|---|---|
TOS_PIN_IN | 输入 |
TOS_PIN_OUT | 输出 |
TOS_PIN_IN_PULLUP | 输入 + 上拉 |
TOS_PIN_IN_PULLDOWN | 输入 + 下拉 |
示例:
通过 /dev/gpioN
同样的事情也可以走设备文件:
GPIO_OP_MODE / GPIO_OP_WRITE / GPIO_OP_READ / GPIO_OP_COUNT 定义在 tos.h。
ADC 通过 /dev/adcN 时使用 ADC_OP_*。
10. 屏幕与帧缓冲
如果板子有 LCD,设备节点是 /dev/lcd0(文本/字符模式,/dev/screen 是它的别名)和 /dev/fb0(RGB565 像素模式)。
字符屏:
ioctl(fd, ...) | 参数 / 返回 |
|---|---|
LCD_OP_INFO | 返回 (cols << 16) | rows |
LCD_OP_CLEAR | 参数 RGB565,清屏 |
LCD_OP_CURSOR | (row << 16) | col |
LCD_OP_BACKLIGHT | 0/1,返回旧状态 |
LCD_OP_MIRROR / LCD_OP_MIRROR_GET | 镜像开关/查询 |
LCD_OP_INVERT | 反色 |
LCD_OP_FG / LCD_OP_BG | 前景/背景 RGB565 |
LCD_OP_PIXELS | 返回 (width << 16) | height |
LCD_OP_DEVS | 绘制设备表 |
对 /dev/lcd0 的 write() 按 UTF-8-ish ASCII 文本渲染,处理 \n、\r、\b。
像素屏:
ioctl(fd, ...) | 作用 |
|---|---|
FB_OP_INFO | 返回 (width << 16) | height |
FB_OP_RECT | 用 FB_RECT(x,y,w,h) 选择下一块写入区域 |
FB_OP_SYNC | 等待 DMA 完成 |
FB_OP_CLEAR | 用 RGB565 填满 |
FB_RECT(x,y,w,h) 把四个字节压进一个 int;FB_RECT_X/Y/W/H 取出。write() 的数据是两个字节一个像素(高字节在前)。
11. 设备文件
| 路径 | 说明 |
|---|---|
/dev/gpioN | GPIO N |
/dev/adcN | ADC 通道 N |
/dev/ledN | 板载 LED;write “0”/“1”,ioctl(0/1) 设置/读取 |
/dev/lcd0, /dev/screen | 字符屏 |
/dev/fb0 | 像素帧缓冲 |
/proc/ps | 任务表 |
/proc/dev | 设备表 |
/proc/dev 和 ls /dev 可以查看当前板子实际暴露了哪些节点。
12. 错误约定
- 系统调用风格:失败返回负值(通常是
-1),成功返回0、字节数、fd 或 pid。 - 没有用户可见的
errno;需要判断返回值。 read()返回0表示 EOF;read()在管道上可能阻塞。malloc()失败返回NULL。
13. 语言与编译限制(goc32)
- 不支持浮点、
long double、atomics、TLS、内联汇编。 - 支持整数/指针/数组/结构体/函数/可变参数。
- 后端仍是栈机 + 局部优化;没有全函数寄存器分配。
- 单调用点内联只在单输入文件、RV32/ARMv7-M/v8-M 上启用,且函数不超过 40 条 IR 指令。
- ARM 上的
user/smp.c使用 GCC 寄存器变量和裸svc内联汇编,暂时不能编译。
14. 最小示例索引
| 需求 | 组合 |
|---|---|
| 点灯/闪灯 | gpio_count + gpio_mode + gpio_write + sleep |
| 读按键 | gpio_mode(pin, TOS_PIN_IN_PULLUP) + gpio_read |
| 读模拟量 | adc_count + adc_read + adc_vref_mv |
| 复制文件 | open + read + write_all + close |
| 列目录 | readdir 循环 |
| 运行子程序 | exec + wait |
| 管道重定向 | pipe + dup2 + exec |
| 申请内存 | TOS_HEAP(...) + malloc/free |
| 画字符屏 | open("/dev/lcd0") + write + ioctl |
完整的可运行例子可以直接看 TinyOS 的 user/ 目录:gpio.c、cat.c、ls.c、mem.c、wheap.c、sh.c。