【RT-Thread】nxp rt10xx 设备驱动框架之--Audio搭建和使用

13 篇文章 2 订阅
10 篇文章 3 订阅

开发前准备

  • 硬件平台:nxp rt10xx单片机
  • IDE: Keil

1.Kconfig 修改和menuconfig配置

在menuconfig中的Hardware Drivers Config中不一定有SAI的选项,需要用户修改Kconfig完善

    menuconfig BSP_USING_AUDIO
        bool "Enable AUDIO"
        select RT_USING_AUDIO
        default n
        if BSP_USING_AUDIO
            config BSP_USING_AUDIO_PLAY
                bool "Enable Audio Play"
                default y
            config BSP_USING_AUDIO_RECORD
                bool "Enable AUDIO RECORD"
                default n
            config BSP_AUDIO_USING_DMA
                bool "Enable AUDIO DMA"
                default n   		
        endif    

请添加图片描述

RT-Thread Components组件中选择Using Audio device drivers,这样才能使用audio框架

请添加图片描述

2.添加audio驱动框架和BSP驱动接口

驱动框架:audio.c audio_pipe.c BSP接口:drv_sai.c fsl_edma.c fsl_sai.c fsl_sai_edma.c

请添加图片描述

3.添加或修改drv_sai.c

官方bsp不一定完全支持,我们需要修改bsp,实现ops相关函数

  • 定义rt_audio_ops结构
audio.h头文件中修改

struct rt_audio_ops
{
    rt_err_t (*getcaps)(struct rt_audio_device *audio, struct rt_audio_caps *caps);
    rt_err_t (*configure)(struct rt_audio_device *audio, struct rt_audio_caps *caps);

    rt_err_t (*init)(struct rt_audio_device *audio);
    rt_err_t (*start)(struct rt_audio_device *audio, int stream);
    rt_err_t (*stop)(struct rt_audio_device *audio, int stream);

    rt_err_t  (*control)(struct rt_audio_device *audio, int cmd, void *arg);
    rt_size_t (*transmit)(struct rt_audio_device *audio, const void *writeBuf, void *readBuf, rt_size_t size);
    /* get page size of codec or private buffer's info */
    void (*buffer_info)(struct rt_audio_device *audio, struct rt_audio_buf_info *info);
};
  • 实现ops

从git下载下来的rt系列bsp支持包有很多问题,有些英文命名有很多错误地方笔者顺便也修改了

static struct rt_audio_ops imxrt_payer_ops =
{
    .getcaps     = imxrt_audio_getcaps,
    .configure   = imxrt_audio_configure,
    .init        = imxrt_audio_init,
    .start       = imxrt_audio_start,
    .stop        = imxrt_audio_stop,
    .transmit    = imxrt_audio_transmit,
    .buffer_info = imxrt_audio_buffer_info,

    .control    = imxrt_audio_control,    
};
  • 设备创建注册
int rt_hw_sound_init(void)
{
    rt_uint8_t* tx_fifo = RT_NULL;
    rt_uint8_t* rx_fifo = RT_NULL;

    sai_tx.base = SAI1;
    sai_rx.base = SAI1;
    sai_tx.irqn = SAI1_IRQn;

#ifdef BSP_AUDIO_USING_DMA
    static struct saidma_tx_config sai_txdma = { .channel = C052_TX_CHANNEL, .request = kDmaRequestMuxSai1Tx };
    sai_tx.dma_tx = &sai_txdma;
    sai_tx.dma_flag |= RT_DEVICE_FLAG_DMA_TX;
#if defined (BSP_USING_AUDIO_RECORD)
    static struct saidma_rx_config sai_rxdma = { .channel = C052_RX_CHANNEL, .request = kDmaRequestMuxSai1Rx };
    sai_rx.dma_rx = &sai_rxdma;
#endif
#endif

    /* 分配 DMA 搬运 buffer */
    tx_fifo = rt_calloc(1, AUD_FIFO_SIZE);
    rx_fifo = rt_calloc(1, AUD_FIFO_SIZE);
    if(tx_fifo == RT_NULL || rx_fifo == RT_NULL)
    {
        return -RT_ENOMEM;
    }

    //缓存初始化
    rt_memset(tx_fifo, 0, AUD_FIFO_SIZE);
    imxrt_payer_dev.tx_fifo = tx_fifo;
    rt_memset(rx_fifo, 0, AUD_FIFO_SIZE);
    imxrt_payer_dev.rx_fifo = rx_fifo;

    imxrt_payer_dev.audio.ops = &imxrt_payer_ops;

    //audio 注册会调用 init 所以前面不用init
    rt_audio_register(&imxrt_payer_dev.audio, "sound0", RT_DEVICE_FLAG_RDWR, &imxrt_payer_dev);

    return RT_EOK;
}
INIT_DEVICE_EXPORT(rt_hw_sound_init);

audio.c 框架中,注册实现如下内容

rt_err_t rt_audio_register(struct rt_audio_device *audio, const char *name, rt_uint32_t flag, void *data)
{
    rt_err_t result = RT_EOK;
    struct rt_device *device;

    RT_ASSERT(audio != RT_NULL);
    device = &(audio->parent);

    device->type = RT_Device_Class_Sound;
    device->rx_indicate = RT_NULL;
    device->tx_complete = RT_NULL;

#ifdef RT_USING_DEVICE_OPS
    device->ops  = &audio_ops;
#else
    device->init    = _audio_dev_init;
    device->open    = _audio_dev_open;
    device->close   = _audio_dev_close;
    device->read    = _audio_dev_read;
    device->write   = _audio_dev_write;
    device->control = _audio_dev_control;
#endif
    device->user_data = data;

    /* register a character device */
    result = rt_device_register(device, name, flag | RT_DEVICE_FLAG_REMOVABLE);

    /* initialize audio device */
    if (result == RT_EOK)
        result = rt_device_init(device);

    return result;
}

注册流程,主要实现设备链表注册和audio外设初始化

1.int rt_hw_sound_init(void)
2.rt_err_t rt_audio_register(struct rt_audio_device *audio, const char *name, rt_uint32_t flag, void *data)
函数内实现 rt_device_register 然后执行 rt_device_init即:ops结构体中注册的init
3.rt_err_t rt_device_register(rt_device_t dev,const char *name,rt_uint16_t flags)
4.void rt_object_init(struct rt_object *object,enum rt_object_class_type type,const char *name)   
将设备对象信息写入链表  

补充一点,笔者发现官方audio框架很多地方英文名称有误,比如:将audio写成aduio,希望官方组件注意完善

请添加图片描述

  • 初始化接口函数配置

初始化函数,就按照裸机思维方式写就行

static rt_err_t imxrt_audio_init(struct rt_audio_device* audio)
{
    RT_ASSERT(audio != RT_NULL);

    CLOCK_EnableClock(kCLOCK_Iomuxc);
	CLOCK_EnableClock(kCLOCK_Sai1);

	IOMUXC_SetPinMux(IOMUXC_GPIO_AD_B1_09_SAI1_MCLK, 1U); 
	IOMUXC_SetPinMux(IOMUXC_GPIO_AD_B1_12_SAI1_RX_DATA00, 1U); 
	IOMUXC_SetPinMux(IOMUXC_GPIO_AD_B1_13_SAI1_TX_DATA00, 1U); 
	IOMUXC_SetPinMux(IOMUXC_GPIO_AD_B1_14_SAI1_TX_BCLK, 1U); 
	IOMUXC_SetPinMux(IOMUXC_GPIO_AD_B1_15_SAI1_TX_SYNC, 1U); 

	IOMUXC_SetPinConfig(IOMUXC_GPIO_AD_B1_09_SAI1_MCLK,0x10B0u);                              
	IOMUXC_SetPinConfig(IOMUXC_GPIO_AD_B1_12_SAI1_RX_DATA00,0x10B0u);                               
	IOMUXC_SetPinConfig(IOMUXC_GPIO_AD_B1_13_SAI1_TX_DATA00,0x10B0u);                              
	IOMUXC_SetPinConfig(IOMUXC_GPIO_AD_B1_14_SAI1_TX_BCLK,0x10B0u);                              
	IOMUXC_SetPinConfig(IOMUXC_GPIO_AD_B1_15_SAI1_TX_SYNC,0x10B0u);  

    CLOCK_InitAudioPll(&audioPllConfig);
    CLOCK_SetMux(kCLOCK_Sai1Mux, DEMO_SAI1_CLOCK_SOURCE_SELECT);
    CLOCK_SetDiv(kCLOCK_Sai1PreDiv, DEMO_SAI1_CLOCK_SOURCE_PRE_DIVIDER);
    CLOCK_SetDiv(kCLOCK_Sai1Div, DEMO_SAI1_CLOCK_SOURCE_DIVIDER);
    BOARD_EnableSaiMclkOutput(RT_TRUE);

    EDMA_CreateHandle(&sai_tx.dma_tx->edma, DMA0, sai_tx.dma_tx->channel);
    DMAMUX_SetSource(DMAMUX, sai_tx.dma_tx->channel, (rt_uint8_t)sai_tx.dma_tx->request);
    DMAMUX_EnableChannel(DMAMUX, sai_tx.dma_tx->channel);
    SAI_TxGetDefaultConfig(&config);
	config.masterSlave = kSAI_Master;
	config.protocol = kSAI_BusI2S;     
    SAI_TxInit(sai_tx.base, &config);

#if defined (BSP_USING_AUDIO_RECORD)
    EDMA_CreateHandle(&sai_rx.dma_rx->edma, DMA0, sai_rx.dma_rx->channel);
    DMAMUX_SetSource(DMAMUX, sai_rx.dma_rx->channel, (rt_uint8_t)sai_rx.dma_rx->request);
    DMAMUX_EnableChannel(DMAMUX, sai_rx.dma_rx->channel);
    SAI_RxGetDefaultConfig(&config);
	config.masterSlave = kSAI_Slave;
	config.protocol = kSAI_BusI2S;	   
    SAI_RxInit(sai_rx.base, &config);
#endif

    //dma 中断优先级设置
    NVIC_SetPriority(C052_SAIDMA_IRQ,SAI_ISR_PRE);

    format.bitWidth = kSAI_WordWidth32bits;
    format.channel = 0U;
    format.sampleRate_Hz = kSAI_SampleRate44100Hz;
    format.protocol = config.protocol;
    format.stereo = kSAI_Stereo;
    format.isFrameSyncCompact = false;
    format.watermark = FSL_FEATURE_SAI_FIFO_COUNT / 2U;

    SAI_TransferTxCreateHandleEDMA(sai_tx.base, &sai_tx.dma_tx->txHandle, sai_TxDmaCallback, NULL, &sai_tx.dma_tx->edma);
    SAI_TransferTxSetFormatEDMA(sai_tx.base, &sai_tx.dma_tx->txHandle, &format, DEMO_SAI_CLK_FREQ, DEMO_SAI_CLK_FREQ);
#if defined (BSP_USING_AUDIO_RECORD)
    SAI_TransferRxCreateHandleEDMA(sai_rx.base, &sai_rx.dma_rx->rxHandle, sai_RxDmaCallback, NULL, &sai_rx.dma_rx->edma);
    SAI_TransferRxSetFormatEDMA(sai_rx.base, &sai_rx.dma_rx->rxHandle, &format, DEMO_SAI_CLK_FREQ, DEMO_SAI_CLK_FREQ);
#endif

    return RT_EOK;
}
  • dma中断回调处理
#define AUD_BLOCK_CNT   2
#define AUD_BLOCK_SIZE  512 
#define AUD_FIFO_SIZE   (AUD_BLOCK_SIZE * AUD_BLOCK_CNT)

static void sai_TxDmaCallback(I2S_Type* base, sai_edma_handle_t* handle, rt_int32_t status, void* userData)
{
    rt_audio_tx_complete(&imxrt_payer_dev.audio);
}

#if defined (BSP_USING_AUDIO_RECORD)
static void sai_RxDmaCallback(I2S_Type* base, sai_edma_handle_t* handle, rt_int32_t status, void* userData)
{
    rt_audio_rx_done(&imxrt_payer_dev.audio, &imxrt_payer_dev.rx_fifo[0], AUD_BLOCK_SIZE);
}
#endif

//开始dma传输
static rt_err_t imxrt_audio_start(struct rt_audio_device* audio, int stream)
{
    RT_ASSERT(audio != RT_NULL);

    xfer.data = imxrt_payer_dev.rx_fifo;
    xfer.dataSize = AUD_BLOCK_SIZE;
#if defined (BSP_USING_AUDIO_RECORD)
    SAI_TransferReceiveEDMA(sai_rx.base, &sai_rx.dma_rx->rxHandle, &xfer);
#endif
	xfer.data = imxrt_payer_dev.tx_fifo;
    xfer.dataSize = AUD_BLOCK_SIZE;
    SAI_TransferSendEDMA(sai_tx.base, &sai_tx.dma_tx->txHandle, &xfer);
    return RT_EOK;
}
  • 控制函数配置
static rt_err_t imxrt_audio_control (struct rt_audio_device *audio, int cmd, void *args)
{
    rt_err_t result = RT_EOK;

    switch (cmd)
    {
    case AUDIO_CTL_START: 
        imxrt_audio_start(audio,AUDIO_STREAM_REPLAY);
        break;

    case AUDIO_CTL_STOP:
        imxrt_audio_stop(audio,AUDIO_STREAM_REPLAY);
        break;    

    default:
        result = -RT_ERROR;
        break;
    }

    return result;
}

4.搭建应用层demo

应用层使用可参考官方链接,功能不正常就需要看bsp层是不是没搭建好:AUDIO设备 (rt-thread.org)

/**************************************************START OF FILE*****************************************************/






/*------------------------------------------------------------------------------------------------------------------
Includes
*/
#include <rtthread.h>
#include <rtdevice.h>
#include <string.h>


/*------------------------------------------------------------------------------------------------------------------
Macros
*/
#define SOUND_DEVICE_NAME    "sound0"   
#define MIC_DEVICE_NAME      "record"   

#define THREAD_PRIORITY         12
#define THREAD_STACK_SIZE       512
#define THREAD_TIMESLICE        2

#define FIFO_SIZE   512

/*------------------------------------------------------------------------------------------------------------------
Variables
*/
static rt_device_t h_dev_speaker;       
static rt_device_t h_dev_mic;            
static rt_thread_t h_thread_audio = RT_NULL;
int32_t micBuf[FIFO_SIZE/4],speBuf[FIFO_SIZE/4];

/*------------------------------------------------------------------------------------------------------------------
Functions
*/
static void thread_audio(void *parameter)
{
    int i;
    int length;

    rt_kprintf("thread audio start\n");

#if 0  //bypass
    while(1)
    {
        length = rt_device_read(h_dev_mic, 0, micBuf, FIFO_SIZE);
        if(length)
        {
            memcpy(speBuf,micBuf,length);
            rt_device_write(h_dev_speaker, 0, speBuf, length);
        }
    }
#else  //短接输出到输入

	for(i=0;i<FIFO_SIZE/4;i++)
	{
		speBuf[i] = i*0x100;
	}

	rt_device_write(h_dev_speaker, 0, speBuf, FIFO_SIZE);

	while(1)
	{
		length = rt_device_read(h_dev_mic, 0, micBuf, FIFO_SIZE);
        if(length)
        {
            rt_device_write(h_dev_speaker, 0, speBuf, length);
        }
	}

#endif
}

int xAPP_AudioInit(void)
{
	rt_err_t ret = RT_EOK;

    /* 根据设备名称查找 Audio 设备,获取设备句柄 */
    h_dev_speaker = rt_device_find(SOUND_DEVICE_NAME);
	if (!h_dev_speaker)
	{
		rt_kprintf("find %s failed!\n", SOUND_DEVICE_NAME);
		return RT_ERROR;
	}    

    ret = rt_device_open(h_dev_speaker, RT_DEVICE_OFLAG_WRONLY);
	if (ret != RT_EOK)
	{
		rt_kprintf("open %s failed!\n", SOUND_DEVICE_NAME);
		return RT_ERROR;
	}

    h_dev_mic = rt_device_find(MIC_DEVICE_NAME);
	if (!h_dev_mic)
	{
		rt_kprintf("find %s failed!\n", MIC_DEVICE_NAME);
		return RT_ERROR;
	}    

    ret = rt_device_open(h_dev_mic, RT_DEVICE_OFLAG_WRONLY);
	if (ret != RT_EOK)
	{
		rt_kprintf("open %s failed!\n", MIC_DEVICE_NAME);
		return RT_ERROR;
	}

	ret = rt_device_control(h_dev_speaker, AUDIO_CTL_START, RT_NULL);
	if (ret != RT_EOK)
	{
		rt_kprintf("start %s failed!\n", SOUND_DEVICE_NAME);
		return RT_ERROR;
	}	
	
    //创建Audio线程
    h_thread_audio = rt_thread_create("t_audio",thread_audio,RT_NULL,THREAD_STACK_SIZE,THREAD_PRIORITY,THREAD_TIMESLICE);
    if (h_thread_audio != RT_NULL)
	{	
		rt_thread_startup(h_thread_audio);
	}

    return 0;
}

/* 导出到 msh 命令列表中 */ 
//MSH_CMD_EXPORT(audioInit, audio init);

/****************************************************END OF FILE*****************************************************/


测试输入输出,SAI RT和TX短接

请添加图片描述

请添加图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值