ALSA声卡_从零编写之数据传输(基于优龙FS2410开发板,UDA1341声卡)

发布者:清新微笑最新更新时间:2024-07-11 来源: elecfans关键字:数据传输  FS2410开发板 手机看文章 扫描二维码
随时随地手机看文章

一、实验环境

1.1 虚拟机环境

    a) Vmware版本:Vmware Workstation 12.5.7

    b) Ubuntu版本:9.10

    c) 内核版本:2.6.31.14

    d) toolchain版本:arm-linux-gcc 4.3.2

1.2 开发板

    优龙FS2410开发板,UDA1341声卡

    内核版本:3.4.2

二、声卡数据传输的原理(以播放为例)

image

(1) 驱动程序分配一个buffer:s2c2440_dma_new

(2) app不断写一个个period数据到buffer(appl_ptr以frame为单位) 。一个period包含多个frame,一个frame就是一个采样数据

(3) 驱动不断从buffer里取出一个period:load_dma_period,启动DMA传输:s3c2440_dma_start,发送给声卡

(4)传输完毕,产生中断):s3c2440_dma2_irq ,更新状态(hw_ptr,以frame为单位)

三、具体实现(s3c2440_dma.c)

注:内核中关于s3c24xx的DMA操作的代码框架非常复杂,暂时未仔细研究(可参考:李兰溪  S3C24XX DMA框架源码分析)。而我们自制的驱动,则简化了很多,但基本思想和流程是和内核一致的。     

准备工作

1. 定义好DMA操作相关的寄存器,并进行ioremap,以便后续的访问

#define DMA0_BASE_ADDR  0x4B000000

#define DMA1_BASE_ADDR  0x4B000040

#define DMA2_BASE_ADDR  0x4B000080

#define DMA3_BASE_ADDR  0x4B0000C0


struct s3c_dma_regs {

unsigned long disrc;

unsigned long disrcc;

unsigned long didst;

unsigned long didstc;

unsigned long dcon;

unsigned long dstat;

unsigned long dcsrc;

unsigned long dcdst;

unsigned long dmasktrig;

};

static volatile struct s3c_dma_regs *dma_regs;


static int s3c2440_dma_init(void)

 {

dma_regs = ioremap(DMA2_BASE_ADDR, sizeof(struct s3c_dma_regs));

platform_device_register(&s3c2440_dma_dev);

platform_driver_register(&s3c2440_dma_drv);

return 0;

 }

 static void s3c2440_dma_exit(void)

 {

platform_device_unregister(&s3c2440_dma_dev);

platform_driver_unregister(&s3c2440_dma_drv);

iounmap(dma_regs);

 }


2. 实现几个基础函数,供后续s3c2440_dma_prepare、s3c2440_dma2_irq和s3c2440_dma_trigger调用


/* 数据传输: 源,目的,长度 */

static void load_dma_period(void)

{

    /* 把源,目的,长度告诉DMA */

    dma_regs->disrc      = playback_dma_info.phy_addr + playback_dma_info.dma_ofs;  /* 源的物理地址 */

    dma_regs->disrcc     = (0<<1) | (0<<0); /* 源位于AHB总线, 源地址递增 */

    dma_regs->didst      = 0x55000010;        /* 目的的物理地址 IIS fifo entry*/

    dma_regs->didstc     = (0<<2) | (1<<1) | (1<<0); /* 目的位于APB总线, 目的地址不变 */

    /*

handshake mode

DACK and DREQ are synchronized to PCLK

Enable/Disable the interrupt setting for CURR_TC

A unit transfer

single service mode

select I2SSDO of DCON2 as DMA request source

hardware trigger DMA request

datasize to be transfered: half word

2(bytes)*1(unit)*initial transfer_count = len

==>transfer_count=len/2

    */

    /* bit22: 1-noreload */

    //传输的长度(datasize是half word即2个字节):playback_dma_info.period_size/2

    dma_regs->dcon = (1<<31)|(0<<30)|(1<<29)|(0<<28)|(0<<27)|(0<<24)|(1<<23)|(1<<22)|(1<<20)|(playback_dma_info.period_size/2);

    /* 使能中断,单个传输,硬件触发 */

}

static void s3c2440_dma_start(void)

{

/* 启动DMA */

dma_regs->dmasktrig  = (1<<1);

}

static void s3c2440_dma_stop(void)

{

/* 停止DMA */

dma_regs->dmasktrig  &= ~(1<<1);

}


3.1 实现s3c2440_dma_platform.pcm_new(即s3c2440_dma_new)


(参考 soundsocsamsungdma.c 的 dma_new)


static int s3c2440_dma_new(struct snd_soc_pcm_runtime *rtd)

{

struct snd_card *card = rtd->card->snd_card;

struct snd_pcm *pcm = rtd->pcm;

struct snd_pcm_substream *substream = pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream;

struct snd_dma_buffer *buf = &substream->dma_buffer;

  /* 

           snd_dma_buffer的作用:

           在hw_params阶段,snd_soc_platform_driver的ops->hw_params会被调用,通常会使用snd_pcm_set_runtime_buffer()

           把substream->dma_buffer的值拷贝到substream->runtime的相关字段中(.dma_area, .dma_addr, .dma_bytes),

           这样以后就可以通过substream->runtime获得这些地址和大小信息了。因为有播放和录音两个substream,而runtime始终指向当前使用的substream,

           所以便于跟踪substream。

        */

int ret = 0;


/* 1. 分配DMA BUFFER */

if (!card->dev->dma_mask)  //这段代码,是后来调试时发现,必须要加的

card->dev->dma_mask = &dma_mask;

if (!card->dev->coherent_dma_mask)

card->dev->coherent_dma_mask = DMA_BIT_MASK(32);


if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) {

playback_dma_info.virt_addr = (unsigned int)dma_alloc_writecombine(pcm->card->dev, s3c2440_dma_hardware.buffer_bytes_max,

&playback_dma_info.phy_addr, GFP_KERNEL);

if (!playback_dma_info.virt_addr)

{

return -ENOMEM;

}

playback_dma_info.buf_max_size = s3c2440_dma_hardware.buffer_bytes_max;


buf->dev.type = SNDRV_DMA_TYPE_DEV;

buf->dev.dev = pcm->card->dev;

buf->private_data = NULL;

buf->area = (unsigned char *)playback_dma_info.virt_addr; //这句话是后来调试时,才发现需要加的

buf->bytes = playback_dma_info.buf_max_size;

buf->addr = playback_dma_info.phy_addr;

}


return ret;


        //为了简化,先去掉录音功能 

}


3.2 实现s3c2440_dma_platform.ops.open(即s3c2440_dma_open)

(参考 soundsocsamsungdma.c 的 dma_open)


static const struct snd_pcm_hardware s3c2440_dma_hardware = {

.info = SNDRV_PCM_INFO_INTERLEAVED |

SNDRV_PCM_INFO_BLOCK_TRANSFER |

SNDRV_PCM_INFO_MMAP |

SNDRV_PCM_INFO_MMAP_VALID |

SNDRV_PCM_INFO_PAUSE |

SNDRV_PCM_INFO_RESUME,

.formats = SNDRV_PCM_FMTBIT_S16_LE |

SNDRV_PCM_FMTBIT_U16_LE |

SNDRV_PCM_FMTBIT_U8 |

SNDRV_PCM_FMTBIT_S8,

.channels_min = 2,

.channels_max = 2,

.buffer_bytes_max = 128*1024, //在s3c2440_dma_new里被用于指定dma_alloc_writecombine的size参数

.period_bytes_min = PAGE_SIZE,

.period_bytes_max = PAGE_SIZE*2,

.periods_min = 2,

.periods_max = 128,

.fifo_size = 32,

};

//目前只支持播放 

static int s3c2440_dma_open(struct snd_pcm_substream *substream)

{

    struct snd_pcm_runtime *runtime = substream->runtime;

    int ret;

    /* 设置属性 */

    snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); //约束:periods必须是整数

    snd_soc_set_runtime_hwparams(substream, &s3c2440_dma_hardware);

    /*

        snd_soc_set_runtime_hwparams的作用: 把s3c2440_dma_hardware的各个属性赋给substream->runtime->hw,

        后续在snd_pcm_open_file==>snd_pcm_open_substream==>snd_pcm_hw_constraints_complete==>

        snd_pcm_hw_constraint_minmax里会调用诸如snd_pcm_hw_constraint_minmax(runtime, 

        SNDRV_PCM_HW_PARAM_CHANNELS, hw->channels_min, hw->channels_max);

    */

/* 注册中断 */

    ret = request_irq(IRQ_DMA2, s3c2440_dma2_irq, IRQF_DISABLED, 'myalsa for playback', substream);

    if (ret)

    {

        printk('request_irq error!n');

        return -EIO;

    }

return 0;

}

static int s3c2440_dma_close(struct snd_pcm_substream *substream)

{

    free_irq(IRQ_DMA2, substream);

    return 0;

}


3.3 实现s3c2440_dma_platform.ops. hw_params(即s3c2440_dma_hw_params)


(参考 soundsocsamsungdma.c 的 dma_hw_params)


static int s3c2440_dma_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params)

{

    struct snd_pcm_runtime *runtime = substream->runtime;

    unsigned long totbytes = params_buffer_bytes(params);


    /* 根据params设置DMA */

    /* 关于snd_pcm_set_runtime_buffer的作用,可看上文s3c2440_dma_new 关于snd_dma_buffer的注释*/

    snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);


    /* 

       s3c2440_dma_new分配了很大的DMA BUFFER,而dma_bytes表明app决定使用多大

       runtime->dma_bytes被snd_pcm_lib_readv_transfer和snd_pcm_lib_writev_transfer用来和App之间传输数据

     */

    runtime->dma_bytes            = totbytes;

    playback_dma_info.buffer_size = totbytes;

    playback_dma_info.period_size = params_period_bytes(params); //记录了app在每个period里传输的数据大小(单位:byte),一个period里包含多个frame 

    return 0;

}


3.4 实现s3c2440_dma_prepare


(参考 soundsocsamsungdma.c 的 dma_prepare)


static int s3c2440_dma_prepare(struct snd_pcm_substream *substream)

{

    /* 准备DMA传输 */

    /* 复位各种状态信息 */

    playback_dma_info.dma_ofs = 0;

    playback_dma_info.be_running = 0;

    /* 加载第1个period */

    load_dma_period(); //仿照裸板程序的dma_init()

return 0;

}


3.5 实现s3c2440_dma_trigger


(参考 soundsocsamsungdma.c 的 dma_trigger)


static int s3c2440_dma_trigger(struct snd_pcm_substream *substream, int cmd)

{

int ret = 0;

    /* 根据cmd启动或停止DMA传输 */

switch (cmd) {

case SNDRV_PCM_TRIGGER_START:

case SNDRV_PCM_TRIGGER_RESUME:

case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:

        /* 启动DMA传输 */

        playback_dma_info.be_running = 1;

        s3c2440_dma_start();

break;

case SNDRV_PCM_TRIGGER_STOP:

case SNDRV_PCM_TRIGGER_SUSPEND:

case SNDRV_PCM_TRIGGER_PAUSE_PUSH:

        /* 停止DMA传输 */

        playback_dma_info.be_running = 0;

        s3c2440_dma_stop();

break;

default:

ret = -EINVAL;

break;

}

return ret;

}


3.6 实现s3c2440_dma2_irq


(参考 soundsocsamsungdma.c 的 audio_buffdone)


static irqreturn_t s3c2440_dma2_irq(int irq, void *devid)

{

    struct snd_pcm_substream *substream = devid;

    /* 更新状态信息 */

    playback_dma_info.dma_ofs += playback_dma_info.period_size;

    if (playback_dma_info.dma_ofs >= playback_dma_info.buffer_size) // buffer_size来自于params_buffer_bytes(params),即App需要使用的缓冲区大小

        playback_dma_info.dma_ofs = 0; //如果当前DMA缓冲区中已传输的位置,超出了playback_dma_info.buffer_size,那么回零

    /* 更新hw_ptr等信息,

     * 并且判断:如果buffer里没有数据了,则调用trigger来停止DMA

     */

     snd_pcm_period_elapsed(substream);

     /*

        snd_pcm_period_elapsed为了查询当前已传输的DMA数据在ring_buffer中的位置,会调用snd_pcm_update_hw_ptr0==>substream->ops->pointer(即soc_pcm_pointer) ==>

[1] [2]
关键字:数据传输  FS2410开发板 引用地址:ALSA声卡_从零编写之数据传输(基于优龙FS2410开发板,UDA1341声卡)

上一篇:U-Boot移植
下一篇:ALSA声卡_从零编写之调试(基于优龙FS2410开发板,UDA1341声卡)

推荐阅读最新更新时间:2026-03-25 01:06

S3C2440 UDA1341声卡驱动分析(oos)
1,驱动架构: 驱动分两个层次,上层是平台设备驱动,底层是audio驱动与mixer驱动。 (1)标准的平台设备驱动结构,probe与remove两个函数。 probe: 获得平台资源- 申请内存区域-io内存重映射- 获得并使能时钟- 设置gpio口- 初始化iis总线- 初始化uda1341- audio dma初始化- 注册dsp和mixer- 释放内存区域。 代码及注释: static int s3c2410iis_probe(struct platform_device *pdev) { struct resource *res; unsigned long flags; int ret; DPRINTK( s3c
[单片机]
ALSA声卡16_编写ALSA声卡应用程序_学习笔记
1、体验 (1)ALSA声卡使用体验:使用arecord录音,使用aplay播放,在Alsa-utils里面) 准备: cd linux-3.4.2 patch -p1 ../linux-3.4.2_alsa_wm8976_uda1341_jz2440_mini2440_tq2440.patch //打补丁 cp config_wm8976_jz2440 .config 或 cp config_uda1341_tq2440_mini2440 .config //设置配置文件 make uImage //编译内核 (2)使用新内核启动并测试 启动后创建设备节点 直接输入arecord命令查看帮助信息(arecord 选项
[单片机]
<font color='red'>ALSA</font><font color='red'>声卡</font>16_编写<font color='red'>ALSA</font><font color='red'>声卡</font>应用程序_学习笔记
ALSA声卡09_从零编写之参数设置_学习笔记
1、参数设置分析 (1)open: soc_pcm_open 依次调用cpu_dai, dma, codec_dai, machine的open或startup函数 只在dma的open函数里添加参数相关的代码 (2)SNDRV_PCM_IOCTL_HW_PARAMS: soc_pcm_hw_params 依次调用machine,codec_dai,cpu_dai,platform(dma)的hw_params函数 在uda1341.c, s3c2440-iis.c里实现hw_params函数(把裸板程序里面的相关代码移过来) (s3c2440-dma.c 主要涉及数据传输,在下一节实现hw_params函数) (3)
[单片机]
<font color='red'>ALSA</font><font color='red'>声卡</font>09_从零编写之参数设置_学习笔记
FS2410开发板的测试键盘的ADS汇编代码
;汇编指令实验 ;键盘扫描 ; 4X4 矩阵键盘 ;四个输入引脚: EINT0 -----( GPF0 )----INPUT ; EINT2 -----( GPF2 )----INPUT ; EINT11-----( GPG3 )----INPUT ; EINT19-----( GPG11 )----INPUT ; ;四个输出引脚: KEYSCAN0---( GPE11 )----OUTPUT ; KEYSCAN1---( GPG6 )----OUTPUT ; KEYSCAN2---( GPE13 )----OUTPUT ; K
[单片机]
深入解析汽车ECU传感器的数据传输协议
现代汽车已演变为一部拥有数百个电子控制单元(ECU)的复杂“轮上计算机”,这些ECU如同汽车的大脑,而遍布车身的传感器则是其感知世界的感觉神经元。神经信号如何准确、可靠、实时地传递,直接决定了汽车的智能化水平和安全性能。 Source:https://www.actronics.co.uk/news/how-does-a-ecu-work-in-a-car 这背后是一系列精心设计、各司其职的通信协议在协同工作,本文将深入剖析这些通讯协议的原理、特点及其典型应用。 01 通讯协议选择的核心逻辑 从技术工程角度出发,为汽车传感器选择通信协议是一个多维度的综合决策过程,其核心在于在性能、成本、
[汽车电子]
智能驾驶与车联网驱动下的高速数据传输线束革命
智能驾驶与车联网的爆发式增长 随着汽车行业向智能化、网联化方向加速转型,智能驾驶与车联网技术正经历前所未有的发展。根据中国汽车工业协会数据,2024 年我国新能源汽车销量达 1286.6 万辆,同比增长 35.5%,其中搭载 L2 级别以上智能辅助驾驶系统的车型占比超过 60%。与此同时,车联网用户规模突破 2.8 亿,V2X 通信技术在智能交通中的应用场景不断扩展,包括车路协同、远程驾驶、自动驾驶编队等。 这种技术变革对汽车电子电气架构提出了革命性要求。以特斯拉 FSD 系统为例,其配备 8 个摄像头、1 个毫米波雷达和 12 个超声波传感器,每秒产生超过 1GB 的数据量,需要实时传输至中央计算单元进行处理。
[汽车电子]
使用 3.3V CAN 收发器在工业系统中实现可靠的数据传输
工业市场正在迅速发展,新兴技术正在满足不断增长的创新和效率需求。 工业应用使用多种不同的接口(包括以太网、RS-485 和控制器局域网 (CAN))在不同的设备之间传输时间敏感型数据。 在选择要使用的接口时,设计人员必须考虑许多不同的目标,进行权衡。 CAN 是最早可在恶劣和嘈杂的工业环境中提供可靠数据通信的协议之一,至今仍然广受欢迎。CAN 收发器可提供 CAN 协议的物理层,具有高功效、高数据速率和小物理尺寸特性,同时保持稳健可靠,是许多工业应用的理想选择。在本文中,我们将介绍 CAN 收发器的优势,并讨论它们在更大限度地提高工业应用效率方面的作用。 图 1 展示了通常使用 CAN 收发器来传输数据的各种工业应用。
[工业控制]
使用 3.3V CAN 收发器在工业系统中实现可靠的<font color='red'>数据传输</font>
STM32的DMA数据传输实例
本文将介绍如何通过DMA将ADC采集的数据直接存储到存储器中 而不需要经过CPU; 芯片:STM32F407 工具:STM32CUBEMX KEIL5 ①通过STM32CUBEMX配置工程文件 ②代码实现 ①通过STM32CUBEMX配置工程文件 使能USART3用于调试 PA4外部引脚接的是电压采样电路,将PA4设为ADC1采样通道 使能RCC时钟、配置时钟树 配置DMA 配置ADC1 配置DMA的中断优先级 7、生成代码 ②代码实现 输出重定向: 启动ADC采样后的DMA传输函数: 日志
[单片机]
STM32的DMA<font color='red'>数据传输</font>实例
小广播
最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

厂商技术中心

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

 
机器人开发圈

电子工程世界版权所有 京ICP证060456号 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2026 EEWORLD.com.cn, Inc. All rights reserved