开发环境:
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、串口等三部分。
关于项目的创建请参看笔者以前的文章:
6.2.1音频配置
首先看看音频部分。使能硬件是必不可少的。
使能硬件后,我们就可以使用音频设备了,但是音频设备相对其他外设比较复杂,RT-Thread提供了操作音频设备的软件包WavPlay。只需要使能即可。
值得注意的是,WavPlay软件包依赖optparse,因此optparse软件包在 wavplayer 勾选后,自动选择。optparse模块主要用来为脚本传递命令参数,采用预先定义好的选项来解析命令行参数。
6.2.2存储文件系统配置
AB32VG1开发板内部存储很小,而音乐文件很大,因此需要使用外部存储设备来存放音乐,这样,这里使用SD卡来存储音乐。首先就需要使能SD卡设备。
使能SD卡设备后,文件系统也就默认勾选了。
当然也可适当修改里面的参数。
6.2.3其他配置
前面两部分是这个项目的重要配置,是不可缺少的,接下来就是配置,PWM、UART、KEY设备。
关于PWM和UART的配置参考笔者前面的文章:
下面再添加一个按键的功能包即可。
MultiButton 是一个小巧简单易用的事件驱动型按键驱动模块,可无限量扩展按键,按键事件的回调异步处理方式可以简化你的程序结构,去除冗余的按键处理硬编码,非常好用。
好了,关于音乐播放器的配置就到这里了。
6.3音乐播放器实现
本文将通过按键或者通过串口发送指令来播放音乐、切歌、控制音量等操作,另外根据音量的大小来调节PWM的频率,从而改变RGB灯的闪烁频率。
6.3.1 WavPlay播放音频简析
对于音频设备,其操作流程如下:
1.首先查找 Audio 设备获取设备句柄。
2.以只写方式打开 Audio 设备。
3.设置音频参数信息(采样率、通道等)。
4.解码音频文件的数据。
5.写入音频文件数据。
6.播放完成,关闭设备。
如果自己去实现这些操作还是比较复杂的,wavplayer 软件包将音频设备的操作进行了封装。主需要简单调用几个函数接口播放音乐,主要的接口如下:
int wavplayer_play(char *uri);//音乐播放
int wavplayer_stop(void);// 结束播放
int wavplayer_pause(void);//暂停播放
int wavplayer_resume(void):// 继续播放
int wavplayer_volume_set(int volume);//音量设置
当然啦,这里只讲解应用实现,关于音频驱动请参看官方手册。
6.3.2 PWM控制RGB灯
这部分内容在前面的章节已经讲过了,这里就不讲了,代码如下:
#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播放音乐的代码。
#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软件包,代码如下:
#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卡设备,自动挂载的代码如下:
#include <rtthread.h>
#ifdef BSP_USING_SDIO
#include <dfs_elm.h>
#include <dfs_fs.h>
#include <dfs_posix.h>
#include "drv_gpio.h"
// #define DRV_DEBUG
#define DBG_TAG "app.card"
#include <rtdbg.h>
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卡插入板子,接下来演示音乐播放。
值得注意的是,需用跳线帽连接 J6、J7、J9、J11,SD卡方可使用。
6.5总结
本文的音乐播放器只是抛砖引玉,改进的地方还很多,还需改进的地方如下:
1.播放模式改进,比如顺序播放、随机播放、单曲循环;
2.控制方式改进,可外接无线设备,当然也可使用板载蓝牙来控制音乐的播放;
3.音乐文件获取,如果有网络,还可播放网络上的音乐,这样播放的音乐就能多样多化。
总的来说,音乐播放器的应用还是比较简单的,当时音频驱动、SD卡的驱动以及文件系统的底层逻辑还是比较复杂的,有兴趣的可以深入去研究。
资源获取方法
1.长按下面二维码,关注公众号[嵌入式实验楼]
2.在公众号回复关键词[AB32VG1]获取资料提取码