《嵌入式系统 – 玩转中科蓝讯(AB32VG1)开发板(基于RT-Thread系统)》第6章 音乐播放器

开发环境:
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新建项目

6.2.1音频配置

首先看看音频部分。使能硬件是必不可少的。

cvZ2JU.png

使能硬件后,我们就可以使用音频设备了,但是音频设备相对其他外设比较复杂,RT-Thread提供了操作音频设备的软件包WavPlay。只需要使能即可。

cve9TP.png

值得注意的是,WavPlay软件包依赖optparse,因此optparse软件包在 wavplayer 勾选后,自动选择。optparse模块主要用来为脚本传递命令参数,采用预先定义好的选项来解析命令行参数。

cveElQ.png

6.2.2存储文件系统配置

AB32VG1开发板内部存储很小,而音乐文件很大,因此需要使用外部存储设备来存放音乐,这样,这里使用SD卡来存储音乐。首先就需要使能SD卡设备。

cveVyj.png

使能SD卡设备后,文件系统也就默认勾选了。

cveZOs.png

当然也可适当修改里面的参数。

6.2.3其他配置

前面两部分是这个项目的重要配置,是不可缺少的,接下来就是配置,PWM、UART、KEY设备。
关于PWM和UART的配置参考笔者前面的文章:

PWM LED使用
UART使用

下面再添加一个按键的功能包即可。

cvewtK.png

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);//音量设置

当然啦,这里只讲解应用实现,关于音频驱动请参看官方手册。

AUDIO 设备

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卡方可使用。

cve59S.png

6.5总结

本文的音乐播放器只是抛砖引玉,改进的地方还很多,还需改进的地方如下:

1.播放模式改进,比如顺序播放、随机播放、单曲循环;
2.控制方式改进,可外接无线设备,当然也可使用板载蓝牙来控制音乐的播放;
3.音乐文件获取,如果有网络,还可播放网络上的音乐,这样播放的音乐就能多样多化。

总的来说,音乐播放器的应用还是比较简单的,当时音频驱动、SD卡的驱动以及文件系统的底层逻辑还是比较复杂的,有兴趣的可以深入去研究。


资源获取方法

1.长按下面二维码,关注公众号[嵌入式实验楼]
2.在公众号回复关键词[AB32VG1]获取资料提取码

嵌入式实验楼

Related posts

Leave a Comment