Toggle navigation
首页
问答
文章
积分商城
专家
专区
更多专区...
文档中心
返回主站
搜索
提问
会员
中心
登录
注册
AB32VG1
RT-Thread
《 玩转中科蓝讯(AB32VG1)开发板》第6章 音乐播放器
发布于 2021-04-24 20:09:45 浏览:1490
订阅该版
[tocm] **开发环境:** RT-Thread版本:4.0.3 操作系统:Windows 10 RT-Thread Studio版本:2.0.1 开发板MCU:AB5301A ## 6.1前言 在前面几章,我们使用AB32VG1做了几个小实验,本章将前面的内容进行组合,做一个音乐播放器,主要功能如下: 1.可存储多首完整音乐; 2.实现歌曲切换; 3.实现音量调节。 当然,以上是最基本的功能,还可以实现歌曲播放模式的选择。本章内容主要实现以上3个基本功能,另外根据音量的大小来改变RGB灯的闪烁频率,非常的炫酷。 好了,接下来就一起来看看如何实现音乐播放器吧。 ## 6.2音乐播放器配置 整个项目配置分三部分:音频部分,存储与文件系统部分,LED、串口等三部分。 关于项目的创建请参看笔者以前的文章: [AB32VG1新建项目](https://blog.bruceou.cn/2021/03/1-design-and-use-of-development-environment/645/) ## 6.2.1音频配置 首先看看音频部分。使能硬件是必不可少的。 ![在这里插入图片描述](https://oss-club.rt-thread.org/uploads/20220714/53a6e949ce0945d83a3535a0dfc6cea700d4bdb9.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTMxNjIwMzU=,size_16,color_FFFFFF,t_70#pic_center) 使能硬件后,我们就可以使用音频设备了,但是音频设备相对其他外设比较复杂,RT-Thread提供了操作音频设备的软件包WavPlay。只需要使能即可。 ![在这里插入图片描述](https://oss-club.rt-thread.org/uploads/20220714/977636eeab8a8b3175ea0e4b311af12a4334de5e.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTMxNjIwMzU=,size_16,color_FFFFFF,t_70#pic_center) 值得注意的是,WavPlay软件包依赖optparse,因此optparse软件包在 wavplayer 勾选后,自动选择。optparse模块主要用来为脚本传递命令参数,采用预先定义好的选项来解析命令行参数。 ![在这里插入图片描述](https://oss-club.rt-thread.org/uploads/20220714/21aae383bb635cd87dfa3c8926db2d086cb21450.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTMxNjIwMzU=,size_16,color_FFFFFF,t_70#pic_center) ### 6.2.2存储文件系统配置 AB32VG1开发板内部存储很小,而音乐文件很大,因此需要使用外部存储设备来存放音乐,这样,这里使用SD卡来存储音乐。首先就需要使能SD卡设备。 ![在这里插入图片描述](https://oss-club.rt-thread.org/uploads/20220714/0dee0b7e83724e94ec58c3c1bfccc46bec0deee5.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTMxNjIwMzU=,size_16,color_FFFFFF,t_70#pic_center) 使能SD卡设备后,文件系统也就默认勾选了。 ![在这里插入图片描述](https://oss-club.rt-thread.org/uploads/20220714/503cd27152aece4882ea653795216311a3372e9c.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTMxNjIwMzU=,size_16,color_FFFFFF,t_70#pic_center) 当然也可适当修改里面的参数。 ### 6.2.3其他配置 前面两部分是这个项目的重要配置,是不可缺少的,接下来就是配置,PWM、UART、KEY设备。 关于PWM和UART的配置参考笔者前面的文章: [PWM LED使用](https://blog.bruceou.cn/2021/03/2-full-color-led-realizes-magic-color/760/) [UART使用](https://blog.bruceou.cn/2021/03/3-use-of-serial-port-equipment/766/) 下面再添加一个按键的功能包即可。 ![在这里插入图片描述](https://oss-club.rt-thread.org/uploads/20220714/0c01760fcc52c7301fd5cd97cbc22c522221e035.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTMxNjIwMzU=,size_16,color_FFFFFF,t_70#pic_center) MultiButton 是一个小巧简单易用的事件驱动型按键驱动模块,可无限量扩展按键,按键事件的回调异步处理方式可以简化你的程序结构,去除冗余的按键处理硬编码,非常好用。 好了,关于音乐播放器的配置就到这里了。 ## 6.3音乐播放器实现 本文将通过按键或者通过串口发送指令来播放音乐、切歌、控制音量等操作,另外根据音量的大小来调节PWM的频率,从而改变RGB灯的闪烁频率。 ### 6.3.1 WavPlay播放音频简析 对于音频设备,其操作流程如下: >1.首先查找 Audio 设备获取设备句柄。 2.以只写方式打开 Audio 设备。 3.设置音频参数信息(采样率、通道等)。 4.解码音频文件的数据。 5.写入音频文件数据。 6.播放完成,关闭设备。 如果自己去实现这些操作还是比较复杂的,wavplayer 软件包将音频设备的操作进行了封装。主需要简单调用几个函数接口播放音乐,主要的接口如下: ```c int wavplayer_play(char *uri);//音乐播放 int wavplayer_stop(void);// 结束播放 int wavplayer_pause(void);//暂停播放 int wavplayer_resume(void):// 继续播放 int wavplayer_volume_set(int volume);//音量设置 ``` 当然啦,这里只讲解应用实现,关于音频驱动请参看官方手册。 [AUDIO 设备](https://www.rt-thread.org/document/site/programming-manual/device/audio/audio/) ### 6.3.2 PWM控制RGB灯 这部分内容在前面的章节已经讲过了,这里就不讲了,代码如下: ```c #include "led_app.h" #define THREAD_PRIORITY 7 #define THREAD_STACK_SIZE 512 #define THREAD_TIMESLICE 3 uint32_t pulse_pulse = 90000; #define PWM_DEV_NAME_R "t5pwm" /* PWM设备名称 */ #define PWM_DEV_CHANNEL_R 1 /* PWM通道 */ #define PWM_DEV_NAME_G "lpwm0" /* PWM设备名称 */ #define PWM_DEV_CHANNEL_G 1 /* PWM通道 */ #define PWM_DEV_NAME_B "lpwm2" /* PWM设备名称 */ #define PWM_DEV_CHANNEL_B 3 /* PWM通道 */ struct rt_device_pwm *pwm_dev_r; /* PWM设备句柄 */ struct rt_device_pwm *pwm_dev_g; /* PWM设备句柄 */ struct rt_device_pwm *pwm_dev_b; /* PWM设备句柄 */ static rt_thread_t pwm_led_tid = RT_NULL; /* 线程 pwm_led_thread_entry 的入口函数 */ /** * @brief pwm_led_thread_entry * @param parameter * @retval None */ static void pwm_led_thread_entry(void *parameter) { rt_uint32_t period, pulse_r,pulse_g,pulse_b, dir_r,dir_g,dir_b; period = 655360; /* 周期为0.5ms,单位为纳秒ns */ dir_r = 1; /* PWM脉冲宽度值的增减方向 */ dir_g = 1; dir_b = 1; pulse_r = 0; /* PWM脉冲宽度值,单位为纳秒ns */ pulse_g = 0; pulse_b = 0; rt_uint16_t r,g,b; /* 查找设备 */ pwm_dev_r = (struct rt_device_pwm *)rt_device_find(PWM_DEV_NAME_R); if (pwm_dev_r == RT_NULL) { rt_kprintf("pwm led r run failed! can't find %s device!\n", PWM_DEV_NAME_G); } pwm_dev_g = (struct rt_device_pwm *)rt_device_find(PWM_DEV_NAME_G); if (pwm_dev_g == RT_NULL) { rt_kprintf("pwm led g run failed! can't find %s device!\n", PWM_DEV_NAME_G); } pwm_dev_b = (struct rt_device_pwm *)rt_device_find(PWM_DEV_NAME_B); if (pwm_dev_b == RT_NULL) { rt_kprintf("pwm led b run failed! can't find %s device!\n", PWM_DEV_NAME_B); } /* 设置PWM周期和脉冲宽度默认值 */ rt_pwm_set(pwm_dev_r, PWM_DEV_CHANNEL_R, period, pulse_r); rt_pwm_set(pwm_dev_g, PWM_DEV_CHANNEL_G, period, pulse_g); rt_pwm_set(pwm_dev_b, PWM_DEV_CHANNEL_B, period, pulse_b); /* 使能设备 */ rt_pwm_enable(pwm_dev_r, PWM_DEV_CHANNEL_R); rt_pwm_enable(pwm_dev_g, PWM_DEV_CHANNEL_G); rt_pwm_enable(pwm_dev_b, PWM_DEV_CHANNEL_B); while (1) { for (r =0 ; r < 8; r++) { if (dir_r) { pulse_r += pulse_pulse; /* 从0值开始每次增加5000ns */ } else { pulse_r -= pulse_pulse; /* 从最大值开始每次减少5000ns */ } if ((pulse_r) >= period) { dir_r = 0; } if (81920 > pulse_r) { dir_r = 1; } /* 设置PWM周期和脉冲宽度 */ rt_pwm_set(pwm_dev_r, PWM_DEV_CHANNEL_R, period, pulse_r); for(g = 0; g < 8; g++) { if (dir_g) { pulse_g += pulse_pulse; /* 从0值开始每次增加5000ns */ } else { pulse_g -= pulse_pulse; /* 从最大值开始每次减少5000ns */ } if ((pulse_g) >= period) { dir_g = 0; } if (81920 > pulse_g) { dir_g = 1; } rt_pwm_set(pwm_dev_g, PWM_DEV_CHANNEL_G, period, pulse_g); for(b = 0; b < 8; b++) { rt_thread_mdelay(10); if (dir_b) { pulse_b += pulse_pulse; /* 从0值开始每次增加5000ns */ } else { pulse_b -= pulse_pulse; /* 从最大值开始每次减少5000ns */ } if ((pulse_b) >= period) { dir_b = 0; } if (81920 > pulse_b) { dir_b = 1; } rt_pwm_set(pwm_dev_b, PWM_DEV_CHANNEL_B, period, pulse_b); } } } } } /* 线程初始化*/ int pwm_led(void) { /* 创建线程,名称是 pwm_led_thread,入口是 pwm_led_thread*/ pwm_led_tid = rt_thread_create("pwm_led_thread", pwm_led_thread_entry, RT_NULL, THREAD_STACK_SIZE, THREAD_PRIORITY, THREAD_TIMESLICE); /* 如果获得线程控制块,启动这个线程 */ if (pwm_led_tid != RT_NULL) rt_thread_startup(pwm_led_tid); return 0; } /* 导出到 msh 命令列表中 */ //MSH_CMD_EXPORT(pwm_led, pwm led); INIT_APP_EXPORT(pwm_led); ``` ### 6.3.3串口控制音频设备 下面先看看UART播放音乐的代码。 ```c #include "uart_app.h" #include "key_app.h" #include "led_app.h" #define SAMPLE_UART_NAME "uart1" uint8_t ch; uint8_t r_index = 0; uint8_t flag = 0; extern uint32_t cnt_music; extern uint32_t cnt_channels; extern uint32_t cnt_volume; extern uint32_t start_flag; extern char *table[NUM_OF_SONGS]; extern uint32_t pulse_pulse; struct serial_configure config = RT_SERIAL_CONFIG_DEFAULT; /* 初始化配置参数 */ /* 用于接收消息的信号量 */ static struct rt_semaphore rx_sem; static rt_device_t serial; void analyticald_data(void) { uint8_t sum; if(ch == 0x01) { wavplayer_play(table[(cnt_music++) % NUM_OF_SONGS]); } else if(ch == 0x02) { if (cnt_volume < 11 ) { if(start_flag) { start_flag = 0; cnt_volume = (int)saia_volume_get()/10; pulse_pulse = 9000; } else { saia_volume_set(cnt_volume * 10); pulse_pulse = cnt_volume*9000; } } else { saia_volume_set(10); cnt_volume = 1; rt_kprintf("The volume has been adjusted to maximum\n"); } cnt_volume ++; rt_kprintf("vol=%d\n", saia_volume_get()); } else if(ch == 0x03) { if (cnt_channels < 3) { saia_channels_set(cnt_channels); } else { saia_channels_set(cnt_channels); cnt_channels = 1; } cnt_channels++; } } /* 接收数据回调函数 */ static rt_err_t uart_rx_ind(rt_device_t dev, rt_size_t size) { /* 串口接收到数据后产生中断,调用此回调函数,然后发送接收信号量 */ if (size > 0) { rt_sem_release(&rx_sem); } return RT_EOK; } static char uart_sample_get_char(void) { uint8_t ch; while (rt_device_read(serial, 0, &ch, 1) == 0) { rt_sem_control(&rx_sem, RT_IPC_CMD_RESET, RT_NULL); rt_sem_take(&rx_sem, RT_WAITING_FOREVER); } return ch; } /* 数据解析线程 */ static void data_parsing(void) { while (1) { ch = uart_sample_get_char(); flag = 1; } } int uart_init(void) { rt_err_t ret = RT_EOK; char uart_name[RT_NAME_MAX]; //char str[] = "hello RT-Thread!\r\n"; rt_strncpy(uart_name, SAMPLE_UART_NAME, RT_NAME_MAX); /* 查找系统中的串口设备 */ serial = rt_device_find(uart_name); if (!serial) { rt_kprintf("find %s failed!\n", uart_name); return RT_ERROR; } /* step2:修改串口配置参数 */ config.baud_rate = BAUD_RATE_9600; //修改波特率为9600 config.data_bits = DATA_BITS_8; //数据位 8 config.stop_bits = STOP_BITS_1; //停止位 1 config.bufsz = 128; //修改缓冲区 buff size 为 128 config.parity = PARITY_NONE; //无奇偶校验位 /* step3:控制串口设备。通过控制接口传入命令控制字,与控制参数 */ rt_device_control(serial, RT_DEVICE_CTRL_CONFIG, &config); /* 初始化信号量 */ rt_sem_init(&rx_sem, "rx_sem", 0, RT_IPC_FLAG_FIFO); /* 以中断接收及轮询发送模式打开串口设备 */ rt_device_open(serial, RT_DEVICE_FLAG_INT_RX); /* 设置接收回调函数 */ rt_device_set_rx_indicate(serial, uart_rx_ind); /* 发送字符串 */ //rt_device_write(serial, 0, str, (sizeof(str) - 1)); /* 创建 serial 线程 */ rt_thread_t thread = rt_thread_create("serial", (void (*)(void *parameter))data_parsing, RT_NULL, 2048, 5, 5); /* 创建成功则启动线程 */ if (thread != RT_NULL) { rt_thread_startup(thread); } else { ret = RT_ERROR; } return ret; } /* 导出到 msh 命令列表中 */ MSH_CMD_EXPORT(uart_init, uart device sample); #define THREAD_PRIORITY 9 #define THREAD_TIMESLICE 5 #define EVENT_FLAG (1 << 3) /* 事件控制块 */ static struct rt_event event; ALIGN(RT_ALIGN_SIZE) /* 线程 1 入口函数 */ static void thread1_recv_event(void *param) { rt_uint32_t e; while(1) { /* 第一次接收事件,事件 3 或事件 5 任意一个可以触发线程 1,接收完后清除事件标志 */ if (rt_event_recv(&event, (EVENT_FLAG ), RT_EVENT_FLAG_OR | RT_EVENT_FLAG_CLEAR, RT_WAITING_FOREVER, &e) == RT_EOK) { rt_kprintf("thread1: recv event 0x%x\n", e); analyticald_data(); rt_kprintf("thread1: delay 1s to prepare the second event\n"); } rt_thread_mdelay(100); } } ALIGN(RT_ALIGN_SIZE) /* 线程 2 入口 */ static void thread2_send_event(void *param) { while(1) { if(flag==1) { flag = 0; rt_kprintf("thread2: send event\n"); rt_event_send(&event, EVENT_FLAG); } rt_thread_mdelay(200); } } int event_wavplayer(void) { rt_err_t result; /* 初始化事件对象 */ result = rt_event_init(&event, "event", RT_IPC_FLAG_FIFO); if (result != RT_EOK) { rt_kprintf("init event failed.\n"); return -1; } rt_thread_t thread1 = rt_thread_create("serial", thread1_recv_event, RT_NULL, 512, 10, 5); rt_thread_startup(thread1); rt_thread_t thread2 = rt_thread_create("serial", thread2_send_event, RT_NULL, 512, 9, 5); rt_thread_startup(thread2); return 0; } /* 导出到 msh 命令列表中 */ //MSH_CMD_EXPORT(event_wavplayer, event sample); INIT_APP_EXPORT(event_wavplayer); ``` 代码还是比较简单的,有两部分内容,一部分是是串口的操作,另一部分是串口数据的解析事件,当串口收到指令后,event_wavplayer解析串口的指令,根据相应的指令来操作音频设备。串口指令如下: |指令| 说明| |--|--| |0x01 |音乐切换| |0x02| 音量调节| ### 6.3.4按键控制音频设备 这里使用MultiButton软件包,代码如下: ```c #include "key_app.h" #include "led_app.h" extern uint32_t pulse_pulse; #define BUTTON_PIN_0 rt_pin_get("PF.0") #define BUTTON_PIN_1 rt_pin_get("PF.1") static struct button btn_0; static struct button btn_1; uint32_t cnt_channels = 1; uint32_t cnt_volume = 1; uint32_t cnt_music = 0; uint32_t start_flag = 1; char *table[NUM_OF_SONGS] = { "/Try.wav", "/Bad.wav", }; static uint8_t button_read_pin_0(void) { return rt_pin_read(BUTTON_PIN_0); } static uint8_t button_read_pin_1(void) { return rt_pin_read(BUTTON_PIN_1); } static void button_0_callback(void *btn) { uint32_t btn_event_val; btn_event_val = get_button_event((struct button *)btn); switch(btn_event_val) { case SINGLE_CLICK: if (cnt_volume < 11 ) { if(start_flag) { start_flag = 0; cnt_volume = (int)saia_volume_get()/10; pulse_pulse = 9000; } else { saia_volume_set(cnt_volume * 10); pulse_pulse = cnt_volume*9000; } } else { saia_volume_set(10); cnt_volume = 1; rt_kprintf("The volume has been adjusted to maximum\n"); } cnt_volume ++; rt_kprintf("vol=%d\n", saia_volume_get()); rt_kprintf("button 0 single click\n"); break; case DOUBLE_CLICK: if (cnt_channels < 3) { saia_channels_set(cnt_channels); } else { saia_channels_set(cnt_channels); cnt_channels = 1; } cnt_channels++; rt_kprintf("button 0 double click\n"); break; case LONG_PRESS_START: rt_kprintf("button 0 long press start\n"); break; case LONG_PRESS_HOLD: rt_kprintf("button 0 long press hold\n"); break; } } static void button_1_callback(void *btn) { uint32_t btn_event_val; btn_event_val = get_button_event((struct button *)btn); switch(btn_event_val) { case SINGLE_CLICK: wavplayer_play(table[(cnt_music++) % NUM_OF_SONGS]); rt_kprintf("button 1 single click\n"); break; case DOUBLE_CLICK: rt_kprintf("button 1 double click\n"); break; case LONG_PRESS_START: rt_kprintf("button 1 long press start\n"); break; case LONG_PRESS_HOLD: rt_kprintf("button 1 long press hold\n"); break; } } static void btn_thread_entry(void* p) { while(1) { /* 5ms */ rt_thread_delay(RT_TICK_PER_SECOND/200); button_ticks(); } } static int multi_button_wavplayer(void) { rt_thread_t thread = RT_NULL; /* Create background ticks thread */ thread = rt_thread_create("btn", btn_thread_entry, RT_NULL, 512, 10, 10); if(thread == RT_NULL) { return RT_ERROR; } rt_thread_startup(thread); /* low level drive */ rt_pin_mode (BUTTON_PIN_0, PIN_MODE_INPUT_PULLUP); button_init (&btn_0, button_read_pin_0, PIN_LOW); button_attach(&btn_0, SINGLE_CLICK, button_0_callback); button_attach(&btn_0, DOUBLE_CLICK, button_0_callback); button_attach(&btn_0, LONG_PRESS_START, button_0_callback); button_attach(&btn_0, LONG_PRESS_HOLD, button_0_callback); button_start (&btn_0); rt_pin_mode (BUTTON_PIN_1, PIN_MODE_INPUT_PULLUP); button_init (&btn_1, button_read_pin_1, PIN_LOW); button_attach(&btn_1, SINGLE_CLICK, button_1_callback); button_attach(&btn_1, DOUBLE_CLICK, button_1_callback); button_attach(&btn_1, LONG_PRESS_START, button_1_callback); button_attach(&btn_1, LONG_PRESS_HOLD, button_1_callback); button_start (&btn_1); return RT_EOK; } MSH_CMD_EXPORT(multi_button_wavplayer, button wavplayer) ``` 按键控制和串口控制差不多,只是按键有限,控制的内容就相对串口少。 SD设备没啥好讲的,值得注意的是,如果没有自动挂载设备,需要在手动挂载SD卡设备,自动挂载的代码如下: ```c #include
#ifdef BSP_USING_SDIO #include
#include
#include
#include "drv_gpio.h" // #define DRV_DEBUG #define DBG_TAG "app.card" #include
void sd_mount(void *parameter) { while (1) { rt_thread_mdelay(500); if(rt_device_find("sd0") != RT_NULL) { if (dfs_mount("sd0", "/", "elm", 0, 0) == RT_EOK) { LOG_I("sd card mount to '/'"); break; } else { LOG_W("sd card mount to '/' failed!"); } } } } int ab32_sdcard_mount(void) { rt_thread_t tid; tid = rt_thread_create("sd_mount", sd_mount, RT_NULL, 1024, RT_THREAD_PRIORITY_MAX - 2, 20); if (tid != RT_NULL) { rt_thread_startup(tid); } else { LOG_E("create sd_mount thread err!"); } return RT_EOK; } INIT_APP_EXPORT(ab32_sdcard_mount); #endif ``` 好了,音乐播放器的实现代码就这些了,完整代码请根据后文提示获取。 ## 6.4功能演示 首先,需要将普通的音乐文件转换成wav格式。推荐的软件是GoldWave。转换完成后,将音乐放入SD卡,将SD卡插入板子,接下来演示音乐播放。 ![在这里插入图片描述](https://oss-club.rt-thread.org/uploads/20220714/1e8f8e12d6a6d490e83247fb89db0c6930549fca.gif#pic_center) ![在这里插入图片描述](https://oss-club.rt-thread.org/uploads/20220714/90a248cdc4dc2198a55bfd70eb39a738a330f506.gif#pic_center) 值得注意的是,需用跳线帽连接 J6、J7、J9、J11,SD卡方可使用。 ![在这里插入图片描述](https://oss-club.rt-thread.org/uploads/20220714/3ff00f6875cf756ff4e2c5193533b079dc187b09.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTMxNjIwMzU=,size_16,color_FFFFFF,t_70#pic_center) [视频地址](https://v.youku.com/v_show/id_XNTE0MzkyMjAyOA==.html) ## 6.5总结 本文的音乐播放器只是抛砖引玉,改进的地方还很多,还需改进的地方如下: >1.播放模式改进,比如顺序播放、随机播放、单曲循环; 2.控制方式改进,可外接无线设备,当然也可使用板载蓝牙来控制音乐的播放; 3.音乐文件获取,如果有网络,还可播放网络上的音乐,这样播放的音乐就能多样多化。 总的来说,音乐播放器的应用还是比较简单的,当时音频驱动、SD卡的驱动以及文件系统的底层逻辑还是比较复杂的,有兴趣的可以深入去研究。 **** **** ### 代码获取方法 1.长按下面二维码,关注公众号[**嵌入式实验楼**] 2.在公众号回复关键词[**AB32VG1**]获取资料 ![在这里插入图片描述](https://oss-club.rt-thread.org/uploads/20220714/fd420eff5a1e144b53d5c2bfca8ec412f963c8db.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTMxNjIwMzU=,size_16,color_FFFFFF,t_70#pic_center)
0
条评论
默认排序
按发布时间排序
登录
注册新账号
关于作者
BruceOu
这家伙很懒,什么也没写!
文章
27
回答
0
被采纳
0
关注TA
发私信
相关文章
1
RT-THREAD在STM32H747平台上移植lwip
2
正点原子miniSTM32开发板读写sdcard
3
反馈rtt串口驱动对低功耗串口lpuart1不兼容的问题
4
Keil MDK 移植 RT-Thread Nano
5
RT1061/1052 带 RTT + LWIP和LPSPI,有什么坑要注意吗?
6
RT thread HID 如何收发数据
7
求一份基于RTT系统封装好的STM32F1系列的FLASH操作程序
8
RT-Thread修改项目名称之后不能下载
9
rt-studio编译c++
10
有木有移植rt-thread(nano)到riscv 32位MCU上
推荐文章
1
RT-Thread应用项目汇总
2
玩转RT-Thread系列教程
3
国产MCU移植系列教程汇总,欢迎查看!
4
机器人操作系统 (ROS2) 和 RT-Thread 通信
5
五分钟玩转RT-Thread新社区
6
【技术三千问】之《玩转ART-Pi》,看这篇就够了!干货汇总
7
关于STM32H7开发板上使用SDIO接口驱动SD卡挂载文件系统的问题总结
8
STM32的“GPU”——DMA2D实例详解
9
RT-Thread隐藏的宝藏之completion
10
【ART-PI】RT-Thread 开启RTC 与 Alarm组件
热门标签
RT-Thread Studio
串口
Env
LWIP
SPI
AT
Bootloader
Hardfault
CAN总线
FinSH
ART-Pi
USB
DMA
文件系统
RT-Thread
SCons
RT-Thread Nano
线程
MQTT
STM32
RTC
FAL
rt-smart
ESP8266
I2C_IIC
WIZnet_W5500
UART
ota在线升级
PWM
cubemx
freemodbus
flash
packages_软件包
BSP
潘多拉开发板_Pandora
定时器
ADC
GD32
flashDB
socket
中断
Debug
编译报错
msh
SFUD
keil_MDK
rt_mq_消息队列_msg_queue
MicroPython
ulog
C++_cpp
本月问答贡献
踩姑娘的小蘑菇
7
个答案
3
次被采纳
a1012112796
16
个答案
2
次被采纳
张世争
9
个答案
2
次被采纳
rv666
5
个答案
2
次被采纳
用户名由3_15位
13
个答案
1
次被采纳
本月文章贡献
程序员阿伟
9
篇文章
2
次点赞
hhart
3
篇文章
4
次点赞
大龄码农
1
篇文章
5
次点赞
RTT_逍遥
1
篇文章
2
次点赞
ThinkCode
1
篇文章
1
次点赞
回到
顶部
发布
问题
投诉
建议
回到
底部