☰
RT-Thread Audio 设备驱动框架实战:播放与录音 API 使用详解
2026/10/4 15:21:52 网站建设 项目流程
  • 操作系统
  • 嵌入式
  • 物联网
  • 嵌入式OS
  • RTOS

【免费下载链接】rt-thread

RT-Thread is an open source IoT Real-Time Operating System (RTOS). https://rt-thread.github.io/rt-thread/

项目地址:https://gitcode.com/gh_mirrors/rt/rt-thread
点击查看免费下载

RT-Thread 的 Audio 设备框架为嵌入式系统提供了一套统一的音频数据采样与输出接口,应用层通过标准的open/close/read/control设备接口即可驱动 Codec 完成播放与录音,无需关心底层硬件差异。本文以仓库文档 audio.md 为主线,结合驱动框架源码 dev_audio.h 与 dev_audio.c,系统讲解 Audio 设备的查找、打开、参数配置、音量控制、读写与关闭全流程,并给出可直接运行在 MSH 命令行上的 WAV 播放与录音完整示例,帮助读者快速在 RT-Thread 上落地音频应用。

Audio 设备简介

Audio 设备是嵌入式系统中负责音频数据采样和输出的关键组件。一个典型的 Audio 设备由以下几部分组成:

  • 数据总线接口(Data Bus):负责把音频 PCM 数据在应用与 Codec 之间搬运,通常基于 I2S / PCM / TDM 等串行音频总线;
  • 控制总线接口(Control Bus):负责配置 Codec 内部的寄存器,如采样率、声道、音量等,通常基于 I2C / SPI;
  • 音频编解码器(Codec):负责数字音频信号与模拟信号之间的转换(DAC/ADC);
  • 扬声器(Speaker)与麦克风(Microphone):音频输出与输入的物理终端。

从软件分层看,RT-Thread 的 Audio 设备驱动框架位于 Audio 体系(如 wavplayer 等上层播放组件)的底层,负责原始音频数据的采集/输出、音频流控制、设备管理、音量调节以及硬件/Codec 抽象。

Audio 设备特性

框架具备以下核心特性(见 dev_audio.h 中的group_drivers_audio定义):

  • 标准设备接口:遵循 RT-Thread 标准设备模型,提供open/close/read/control等统一接口;
  • 同步访问模式:read/write均为同步阻塞调用,数据不足或缓冲满时自动等待;
  • 支持播放与录音:输出设备(Playback)与输入设备(Record)独立管理,可同时注册为sound0、mic0等名称;
  • 音频参数管理:通过rt_audio_caps配置采样率、声道数、采样位数;
  • 音量控制:通过 Mixer 类型控制项调节主音量与麦克风增益。

启用 Audio 设备驱动

Audio 驱动框架默认不编译,需要在 menuconfig 中开启。相关配置位于 components/drivers/audio/Kconfig:

  • RT_USING_AUDIO:总开关(Using Audio device drivers,默认关闭)。启用后,SConscript 会编译dev_audio.c与dev_audio_pipe.c;
  • RT_AUDIO_REPLAY_MP_BLOCK_SIZE:播放内存池块大小,默认4096字节。即播放数据按块切割、按块排队搬运的单位;
  • RT_AUDIO_REPLAY_MP_BLOCK_COUNT:播放内存池块数量,默认2,决定了播放队列最多可暂存的音频块数;
  • RT_AUDIO_RECORD_PIPE_SIZE:录音环形管道(Pipe)大小,默认2048字节,决定录音缓冲深度。

上层组件(如 WAV 播放器 wavplayer)通常依赖该框架,同时需要挂载文件系统(RT_USING_DFS)以便读写音频文件。

查找 Audio 设备

应用通过设备名获取设备句柄,查找函数为:

rt_device_t rt_device_find(const char* name);
参数描述
nameAudio 设备名称
返回——
设备句柄找到时返回句柄
RT_NULL未找到设备

示例:

#define SOUND_DEVICE_NAME "sound0" /* Audio 设备名称 */ static rt_device_t snd_dev; /* Audio 设备句柄 */ /* 根据名称查找 Audio 设备并获取句柄 */ snd_dev = rt_device_find(SOUND_DEVICE_NAME);

设备名由底层驱动注册时指定,播放设备通常为sound0,录音设备通常为mic0(在驱动测试用例 tc_audio_common.h 中同样以SOUND_PLAYER_DEVICE_NAME = "sound0"、SOUND_MIC_DEVICE_NAME = "mic0"约定)。

打开 Audio 设备

应用使用设备句柄打开/关闭设备:

rt_err_t rt_device_open(rt_device_t dev, rt_uint16_t oflags);
参数描述
dev设备句柄
oflags设备模式标志
返回——
RT_EOK设备打开成功
-RT_EBUSY以RT_DEVICE_FLAG_STANDALONE注册的设备不能重复打开
-RT_EINVAL不支持的打开标志
其他错误设备打开失败

支持的oflags值:

#define RT_DEVICE_OFLAG_WRONLY 0x002 /* 只写模式,用于播放设备 */ #define RT_DEVICE_FLAG_RDONLY 0x001 /* 只读模式,用于录音设备 */

Audio 设备分为播放(输出)和录音(输入)两类:播放设备使用只写标志打开,录音设备使用只读标志打开。

打开播放设备:

rt_device_open(snd_dev, RT_DEVICE_OFLAG_WRONLY)

打开录音设备:

rt_device_open(mic_dev, RT_DEVICE_FLAG_RDONLY)

从实现看,dev_audio.c 的_audio_dev_open会校验打开标志与设备注册标志是否匹配(只读设备用只写标志打开会返回-RT_EIO),并在打开时完成播放/录音内部结构的初始化,例如播放路径会清零write_index/read_index/pos,录音路径会直接调用_audio_record_start启动采样硬件。

控制 Audio 设备

应用通过控制命令配置 Audio 设备:

rt_err_t rt_device_control(rt_device_t dev, rt_uint8_t cmd, void* arg);
参数描述
dev设备句柄
cmd控制命令(见下)
arg控制参数(见下)
返回——
RT_EOK操作成功
-RT_ENOSYS操作失败(设备句柄为空)
其他错误操作失败

支持的控制命令:

/* AUDIO commands */ #define _AUDIO_CTL(a) (RT_DEVICE_CTRL_BASE(Sound) + a) #define AUDIO_CTL_GETCAPS _AUDIO_CTL(1) /* 获取设备能力 */ #define AUDIO_CTL_CONFIGURE _AUDIO_CTL(2) /* 配置设备 */

此外 dev_audio.h 还定义了AUDIO_CTL_START(启动音频流)、AUDIO_CTL_STOP(停止音频流)与AUDIO_CTL_GETBUFFERINFO(获取缓冲信息)等命令。

设备能力结构体:

struct rt_audio_caps { int main_type; /* 主命令类型 */ int sub_type; /* 子命令类型 */ union { rt_uint32_t mask; int value; /* 参数值 */ struct rt_audio_configure config; /* 音频配置 */ } udata; };

main_type取值(group_audio_type):AUDIO_TYPE_QUERY(0x00,查询类型)、AUDIO_TYPE_INPUT(0x01,输入/录音)、AUDIO_TYPE_OUTPUT(0x02,输出/播放)、AUDIO_TYPE_MIXER(0x04,混音器/音量控制)。

rt_audio_configure结构体承载实际的音频参数:

struct rt_audio_configure { rt_uint32_t samplerate; /* 采样率 */ rt_uint16_t channels; /* 声道数 */ rt_uint16_t samplebits; /* 采样位数 */ };

设置播放参数

配置播放采样率、声道数与采样位数:

struct rt_audio_caps caps; caps.main_type = AUDIO_TYPE_OUTPUT; /* 播放设备 */ caps.sub_type = AUDIO_DSP_PARAM; /* 设置全部音频参数 */ caps.udata.config.samplerate = 44100; /* 采样率 */ caps.udata.config.channels = 2; /* 声道数 */ caps.udata.config.samplebits = 16; /* 采样位数 */ rt_device_control(device, AUDIO_CTL_CONFIGURE, &caps);

sub_type为 DSP 控制项(group_audio_dsp),除AUDIO_DSP_PARAM(0,一次设置全部参数)外,还可单独设置AUDIO_DSP_SAMPLERATE(采样率)、AUDIO_DSP_CHANNELS(声道)、AUDIO_DSP_SAMPLEBITS(采样位宽)。底层_audio_dev_control收到AUDIO_CTL_CONFIGURE后会回调驱动实现的ops->configure,将配置下发到 Codec。

设置播放音量

调节主播放音量(0-100):

struct rt_audio_caps caps; caps.main_type = AUDIO_TYPE_MIXER; /* 音量控制类型 */ caps.sub_type = AUDIO_MIXER_VOLUME; /* 设置主音量 */ caps.udata.value = volume; /* 取值范围:0-100 */ rt_device_control(snd_dev, AUDIO_CTL_CONFIGURE, &caps);

音量上下限由 dev_audio.h 中AUDIO_VOLUME_MAX (100)/AUDIO_VOLUME_MIN (0)定义。Mixer 控制项(group_audio_mixer)还支持AUDIO_MIXER_MUTE(静音)、AUDIO_MIXER_BASS(低频)、AUDIO_MIXER_MID(中频)、AUDIO_MIXER_TREBLE(高频)、AUDIO_MIXER_EQUALIZER(均衡器)、AUDIO_MIXER_MIC(麦克风)等。

设置录音参数

配置录音采样率、声道数与采样位数:

struct rt_audio_caps caps; caps.main_type = AUDIO_TYPE_INPUT; /* 录音设备 */ caps.sub_type = AUDIO_DSP_PARAM; /* 设置全部音频参数 */ caps.udata.config.samplerate = 44100; /* 采样率 */ caps.udata.config.channels = 2; /* 声道数 */ caps.udata.config.samplebits = 16; /* 采样位数 */ rt_device_control(device, AUDIO_CTL_CONFIGURE, &caps);

设置录音音量

调节麦克风增益(0-100):

struct rt_audio_caps caps; caps.main_type = AUDIO_TYPE_MIXER; /* 音量控制类型 */ caps.sub_type = AUDIO_MIXER_MIC; /* 设置麦克风增益 */ caps.udata.value = volume; /* 取值范围:0-100 */ rt_device_control(player->device, AUDIO_CTL_CONFIGURE, &caps);

写入音频数据

向播放设备写入数据:

rt_size_t rt_device_write(rt_device_t dev, rt_off_t pos, const void* buffer, rt_size_t size);
参数描述
dev设备句柄
pos未使用(保留偏移参数)
buffer待写入的数据缓冲
size待写入的数据大小
返回——
写入字节数实际写入的字节数(同步操作)

该同步函数把数据写入设备内部缓冲,缓冲满时调用会阻塞等待。

从源码看(dev_audio.c 的_audio_dev_write),写入路径实际是把应用数据以RT_AUDIO_REPLAY_MP_BLOCK_SIZE(默认 4096 字节)为块大小切分,先从播放内存池rt_mp_alloc申请块、rt_memcpy填入数据,整块写满后通过rt_data_queue_push压入播放数据队列(队列容量CFG_AUDIO_REPLAY_QUEUE_COUNT,即 4),最后若播放尚未激活则调用_aduio_replay_start启动硬件。整个写操作由互斥锁replay->lock保护。

读取音频数据

从录音设备读取数据:

rt_size_t rt_device_read(rt_device_t dev, rt_off_t pos, void* buffer, rt_size_t size);
参数描述
dev设备句柄
pos未使用(保留偏移参数)
buffer存放读取数据的缓冲
size要读取的数据大小
返回——
读取字节数实际读取的字节数(同步操作)
0读取失败,检查线程 errno 获取错误码

该同步函数从设备内部管道(Pipe)读取数据,数据不足时调用会阻塞等待。

从源码看,_audio_dev_read实际是转发到录音管道设备rt_device_read(RT_DEVICE(&audio->record->pipe), ...)完成的;而录音数据的来源是硬件中断回调rt_audio_rx_done——驱动每收到一帧采样数据就写入录音管道(Pipe,内部为环形缓冲),同时触发rx_indicate回调通知上层有数据可读。底层 Codec 采样率的支持范围见group_audio_samp_rates中的AUDIO_SAMP_RATE_8K ~ AUDIO_SAMP_RATE_192K等宏定义。

关闭 Audio 设备

操作结束后关闭设备:

rt_err_t rt_device_close(rt_device_t dev);
参数描述
dev设备句柄
返回——
RT_EOK设备关闭成功
-RT_ERROR设备已关闭
其他错误关闭失败

底层_audio_dev_close会根据open_flag依次停止播放流(_aduio_replay_stop:冲刷剩余帧、等待播放队列数据传完、再停止硬件)和录音流(_audio_record_stop:停止硬件并关闭录音管道)。

播放 WAV 文件完整示例

Audio 设备用于播放和录音,往往伴随音频文件的编解码。下面给出播放 WAV 文件的完整示例(完整代码亦可参考 RT-Thread 官方的 WAV Player 软件包 wavplayer)。主要步骤如下:

  1. 查找 Audio 设备并获取设备句柄;
  2. 以只写模式打开 Audio 设备;
  3. 设置音频参数(采样率、声道数等);
  4. 解码音频文件数据;
  5. 写入音频文件数据;
  6. 播放完成后关闭设备。
#include <rtthread.h> #include <rtdevice.h> #include <dfs_posix.h> #define BUFSZ 1024 #define SOUND_DEVICE_NAME "sound0" /* Audio 设备名称 */ static rt_device_t snd_dev; /* Audio 设备句柄 */ struct RIFF_HEADER_DEF { char riff_id[4]; // 'R','I','F','F' uint32_t riff_size; char riff_format[4]; // 'W','A','V','E' }; struct WAVE_FORMAT_DEF { uint16_t FormatTag; uint16_t Channels; uint32_t SamplesPerSec; uint32_t AvgBytesPerSec; uint16_t BlockAlign; uint16_t BitsPerSample; }; struct FMT_BLOCK_DEF { char fmt_id[4]; // 'f','m','t',' ' uint32_t fmt_size; struct WAVE_FORMAT_DEF wav_format; }; struct DATA_BLOCK_DEF { char data_id[4]; // 'R','I','F','F' uint32_t data_size; }; struct wav_info { struct RIFF_HEADER_DEF header; struct FMT_BLOCK_DEF fmt_block; struct DATA_BLOCK_DEF data_block; }; int wavplay_sample(int argc, char **argv) { int fd = -1; uint8_t *buffer = NULL; struct wav_info *info = NULL; struct rt_audio_caps caps = {0}; if (argc != 2) { rt_kprintf("Usage:\n"); rt_kprintf("wavplay_sample song.wav\n"); return 0; } fd = open(argv[1], O_RDONLY); if (fd < 0) { rt_kprintf("open file failed!\n"); goto __exit; } buffer = rt_malloc(BUFSZ); if (buffer == RT_NULL) goto __exit; info = (struct wav_info *) rt_malloc(sizeof * info); if (info == RT_NULL) goto __exit; if (read(fd, &(info->header), sizeof(struct RIFF_HEADER_DEF)) <= 0) goto __exit; if (read(fd, &(info->fmt_block), sizeof(struct FMT_BLOCK_DEF)) <= 0) goto __exit; if (read(fd, &(info->data_block), sizeof(struct DATA_BLOCK_DEF)) <= 0) goto __exit; rt_kprintf("wav information:\n"); rt_kprintf("samplerate %d\n", info->fmt_block.wav_format.SamplesPerSec); rt_kprintf("channel %d\n", info->fmt_block.wav_format.Channels); /* 根据名称查找 Audio 设备并获取设备句柄 */ snd_dev = rt_device_find(SOUND_DEVICE_NAME); /* 以只写模式打开 Audio 播放设备 */ rt_device_open(snd_dev, RT_DEVICE_OFLAG_WRONLY); /* 设置采样率、声道数、采样位数等音频参数 */ caps.main_type = AUDIO_TYPE_OUTPUT; /* 输出类型(播放设备) */ caps.sub_type = AUDIO_DSP_PARAM; /* 设置全部音频参数 */ caps.udata.config.samplerate = info->fmt_block.wav_format.SamplesPerSec; /* 采样率 */ caps.udata.config.channels = info->fmt_block.wav_format.Channels; /* 声道数 */ caps.udata.config.samplebits = 16; /* 采样位数 */ rt_device_control(snd_dev, AUDIO_CTL_CONFIGURE, &caps); while (1) { int length; /* 从文件系统读取音频数据 */ length = read(fd, buffer, BUFSZ); if (length <= 0) break; /* 将音频数据写入 Audio 设备 */ rt_device_write(snd_dev, 0, buffer, length); } /* 关闭 Audio 设备 */ rt_device_close(snd_dev); __exit: if (fd >= 0) close(fd); if (buffer) rt_free(buffer); if (info) rt_free(info); return 0; } MSH_CMD_EXPORT(wavplay_sample, play wav file);

将上述代码编译进固件并挂载文件系统后,可在 MSH 控制台执行wavplay_sample song.wav播放指定 WAV 文件。播放参数直接取自 WAV 文件头中的SamplesPerSec与Channels,采样位数固定按 16 位处理。

录音 WAV 文件完整示例

录音示例的主要步骤如下:

  1. 查找 Audio 设备并获取设备句柄;
  2. 以只读模式打开 Audio 设备;
  3. 设置音频参数(采样率、声道数等);
  4. 从 Audio 设备读取数据;
  5. 处理录音数据(写入文件);
  6. 录音完成后关闭设备。
#include <rtthread.h> #include <rtdevice.h> #include <dfs_posix.h> #define RECORD_TIME_MS 5000 #define RECORD_SAMPLERATE 16000 #define RECORD_CHANNEL 2 #define RECORD_CHUNK_SZ ((RECORD_SAMPLERATE * RECORD_CHANNEL * 2) * 20 / 1000) #define SOUND_DEVICE_NAME "mic0" /* Audio 设备名称 */ static rt_device_t mic_dev; /* Audio 设备句柄 */ struct wav_header { char riff_id[4]; /* "RIFF" */ int riff_datasize; /* RIFF chunk 数据大小,不含 riff_id[4] 和 riff_datasize,即 total - 8 */ char riff_type[4]; /* "WAVE" */ char fmt_id[4]; /* "fmt " */ int fmt_datasize; /* fmt chunk 数据大小,PCM 为 16 */ short fmt_compression_code; /* PCM 为 1 */ short fmt_channels; /* 1(单声道)或 2(双声道) */ int fmt_sample_rate; /* 每秒采样数 */ int fmt_avg_bytes_per_sec; /* sample_rate * channels * bit_per_sample / 8 */ short fmt_block_align; /* 每采样点字节数,bit_per_sample * channels / 8 */ short fmt_bit_per_sample; /* 每个采样的位数(8、16、32) */ char data_id[4]; /* "data" */ int data_datasize; /* data chunk 大小,pcm_size - 44 */ }; static void wavheader_init(struct wav_header *header, int sample_rate, int channels, int datasize) { memcpy(header->riff_id, "RIFF", 4); header->riff_datasize = datasize + 44 - 8; memcpy(header->riff_type, "WAVE", 4); memcpy(header->fmt_id, "fmt ", 4); header->fmt_datasize = 16; header->fmt_compression_code = 1; header->fmt_channels = channels; header->fmt_sample_rate = sample_rate; header->fmt_bit_per_sample = 16; header->fmt_avg_bytes_per_sec = header->fmt_sample_rate * header->fmt_channels * header->fmt_bit_per_sample / 8; header->fmt_block_align = header->fmt_bit_per_sample * header->fmt_channels / 8; memcpy(header->data_id, "data", 4); header->data_datasize = datasize; } int wavrecord_sample(int argc, char **argv) { int fd = -1; uint8_t *buffer = NULL; struct wav_header header; struct rt_audio_caps caps = {0}; int length, total_length = 0; if (argc != 2) { rt_kprintf("Usage:\n"); rt_kprintf("wavrecord_sample file.wav\n"); return -1; } fd = open(argv[1], O_WRONLY | O_CREAT); if (fd < 0) { rt_kprintf("open file for recording failed!\n"); return -1; } write(fd, &header, sizeof(struct wav_header)); buffer = rt_malloc(RECORD_CHUNK_SZ); if (buffer == RT_NULL) goto __exit; /* 根据名称查找 Audio 设备并获取设备句柄 */ mic_dev = rt_device_find(SOUND_DEVICE_NAME); if (mic_dev == RT_NULL) goto __exit; /* 以只读模式打开 Audio 录音设备 */ rt_device_open(mic_dev, RT_DEVICE_OFLAG_RDONLY); /* 设置采样率、声道数、采样位数等音频参数 */ caps.main_type = AUDIO_TYPE_INPUT; /* 输入类型(录音设备) */ caps.sub_type = AUDIO_DSP_PARAM; /* 设置全部音频参数 */ caps.udata.config.samplerate = RECORD_SAMPLERATE; /* 采样率 */ caps.udata.config.channels = RECORD_CHANNEL; /* 声道数 */ caps.udata.config.samplebits = 16; /* 采样位数 */ rt_device_control(mic_dev, AUDIO_CTL_CONFIGURE, &caps); while (1) { /* 从 Audio 设备读取 20ms 音频数据 */ length = rt_device_read(mic_dev, 0, buffer, RECORD_CHUNK_SZ); if (length) { /* 将音频数据写入文件系统 */ write(fd, buffer, length); total_length += length; } if ((total_length / RECORD_CHUNK_SZ) > (RECORD_TIME_MS / 20)) break; } /* 重写 WAV 文件头 */ wavheader_init(&header, RECORD_SAMPLERATE, RECORD_CHANNEL, total_length); lseek(fd, 0, SEEK_SET); write(fd, &header, sizeof(struct wav_header)); close(fd); /* 关闭 Audio 设备 */ rt_device_close(mic_dev); __exit: if (fd >= 0) close(fd); if (buffer) rt_free(buffer); return 0; } MSH_CMD_EXPORT(wavrecord_sample, record voice to a wav file);

在 MSH 控制台执行wavrecord_sample file.wav即可录制约 5 秒(RECORD_TIME_MS,可通过宏调整)的双声道 16kHz 16 位 WAV 文件。录音流程的特点:

  • 先写入占位的 WAV 文件头(44 字节),录音结束后用wavheader_init根据实际采集字节数total_length回填fmt_*与data_datasize字段,并lseek到文件头重新写入;
  • 每次读取 20ms 的音频块(RECORD_CHUNK_SZ = samplerate * channels * 2 * 20 / 1000,其中2为 16 位采样字节数),按块写入文件并累加长度,直到达到设定的录制时长。

数据流机制与测试用例佐证

理解 Audio 框架的内部数据流有助于排查播放/录音异常。从 dev_audio.c 可以看出框架采用“应用 ↔ 队列/管道 ↔ 硬件 FIFO”三级缓冲架构:

  • 播放(TX)路径:应用rt_device_write→ 按块拷贝进播放内存池(replay->mp)→ 整块压入数据队列replay->queue→_audio_send_replay_frame从队列取块拷贝到硬件缓冲(buf_info->buffer)并通过ops->transmit送硬件;硬件每传完一帧调用rt_audio_tx_complete触发下一帧发送。队列为空时发送静音零帧,避免硬件欠载;
  • 录音(RX)路径:硬件中断回调rt_audio_rx_done把采样数据写入录音管道record->pipe(环形缓冲),并触发rx_indicate;应用rt_device_read从管道同步读取。

仓库在 components/drivers/audio/utest 下提供了官方测试用例tc_audio_main.c(测试名components.drivers.audio.tc_audio_main),通过rt_device_find查找sound0/mic0,以RT_DEVICE_OFLAG_WRONLY/RT_DEVICE_OFLAG_RDONLY打开设备,写入0xAA、0x55特征字节并回读校验,验证了设备注册、TX/RX 数据流正确性与缓冲边界。该用例还展示了rt_container_of从标准设备句柄回溯struct rt_audio_device、进而访问replay->buf_info等框架内部结构的典型用法。运行该测试需在 menuconfig 中同时开启RT_USING_UTESTCASES、RT_USING_AUDIO与RT_UTEST_USING_AUDIO_DRIVER(见 utest/Kconfig),可在 QEMU 或任何支持 RT-Thread 的硬件平台上执行。

小结

本文围绕 RT-Thread Audio 设备框架,完整覆盖了设备查找、打开、控制(参数配置与音量调节)、读写、关闭的标准 API 流程,并给出了可直接在 MSH 上运行的 WAV 播放与录音示例。实际开发中,建议优先通过AUDIO_CTL_GETCAPS查询硬件能力再配置参数,注意RT_AUDIO_REPLAY_MP_BLOCK_SIZE、RT_AUDIO_RECORD_PIPE_SIZE等配置与内存开销的关系,并在rt_device_write/read返回异常时结合errno与 dev_audio.c 中的数据流实现定位问题。

  • 操作系统
  • 嵌入式
  • 物联网
  • 嵌入式OS
  • RTOS

【免费下载链接】rt-thread

RT-Thread is an open source IoT Real-Time Operating System (RTOS). https://rt-thread.github.io/rt-thread/

项目地址:https://gitcode.com/gh_mirrors/rt/rt-thread
点击查看免费下载

相关推荐

上一篇:5分钟连上BilldDesk Pro:免费开源远程桌面完整指南
下一篇:B站缓存转MP4完整教程:m4s-converter无损合并m4s缓存,11.7GB只要38秒

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询