alsa 驱动介绍及user层到hw层文件ioctl操作流程分析

程序员人生
 
您当前位置: 首页 > php开源 > 综合技术 > alsa 驱动介绍

alsa 驱动介绍

来源:程序员人生   发布时间:2016-07-02 13:40:22 阅读次数:6838次
 
 
Machine
以装配有CS4270的1款android 智能电视为例
/sound/soc/samsung/exynos.c


Platform
以Samsung cpu exynos4412为例
/sound/soc/samsung/


Codec
以wolfson的Codec芯片cs4270为例
/sound/soc/codecs/cs4270.c


ALSA 框架介绍

Alsa 太多太杂,很难整理的规整,只能看到哪里写到哪里
 

ASoC被分为Machine,Platform和Codec3大部件,Platform驱动的主要作用是完成音频数据的管理,终究通过CPU的数字音频接口(DAI)把音频数据传送

给Codec进行处理,终究由Codec输出驱动耳机或是喇叭的音信信号。在具体实现上,ASoC有把Platform驱动分为两个部份:snd_soc_platform_driver

和snd_soc_dai_driver。其中,platform_driver负责管理音频数据,把音频数据通过dma或其他操作传送至cpu dai中,dai_driver则主要完成cpu 1侧的

dai的参数配置,同时也会通过1定的途径把必要的dma等参数与snd_soc_platform_driver进行交互。

Machine  是指某1款机器,可以是某款装备,某款开发板,又或是某款智能手机,由此可以看出Machine几近是不可重用的,每一个Machine上的硬件实

现可能都不1样,CPU不1样,Codec不1样,音频的输入、输出装备也不1样,Machine为CPU、Codec、输入输出装备提供了1个载体。

Platform  1般是指某1个SoC平台,比如pxaxxx,s3cxxxx,omapxxx等等,与音频相干的通常包括该SoC中的时钟、DMA、I2S、PCM等等,只要指定了SoC,那么我们可以认为它会有1个对应的Platform,它只与SoC相干,与

Machine无关,这样我们就能够把Platform抽象出来,使得同1款SoC不用做任何的改动,就能够用在不同的Machine中。实际上,把Platform认为是某个SoC更好理解。

Codec  字面上的意思就是编解码器,Codec里面包括了I2S接口、D/A、A/D、Mixer、PA(功放),通常包括多种输入(Mic、Line-in、I2S、PCM)和多个

输出(耳机、喇叭、听筒,Line-out),Codec和Platform 1样,是可重用的部件,同1个Codec可以被不同的Machine使用。嵌入式Codec通常通过I2C对
内部的寄存器进行控制。

 

Machine驱动的初始化,codec和dai的注册,都会调用snd_soc_instantiate_cards()进行1次声卡和codec,dai,platform的匹配绑定进程,这里所说的
绑定,正如Machine驱动1文中所描写,就是通过3个全局链表,按名字进行匹配,把匹配的codec,dai和platform实例赋值给声卡每对dai的snd_soc_pcm_runtime变量中。1旦绑定成功,将会使得codec和dai驱动的probe回调被调用。alsa架构的数据交互,是通过对PCM装备的操作来完成的, PCM装备分成playback和capture两个stream, 每一个stream底下有N个substream
alsa驱动最底层需要调试的有3块: DMA部份,IIS驱动部份,codec部份

 

 

 

 

IIS介绍

A)I2S有4根线,
1.串行时钟SCLK,也叫位时钟(BCLK),即对应数字音频的每位数据,SCLK都有1个脉冲。SCLK的频率=2×采样频率×采样位数。
2. 帧时钟LRCK,(也称WS),用于切换左右声道的数据。LRCK为“1”表示正在传输的是右声道的数据,为“0”则表示正在传输的是左声道的数据。LRCK
的频率等于采样频率。
3.串行数据SDATA,就是用2进制补码表示的音频数据。
4.有时为了使系统间能够更好地同步,还需要另外传输1个信号MCLK,称为主时钟,也叫系统时钟(Sys Clock),是采样频率的256倍或384倍。

 

B)声音数据DAT 1般在CLK的上升沿进行采样,有些DAC也是可以调的。每一个声道里面可以容纳的CLK数必须多于数据的位数,多出来的时钟和数据DAC会丢弃不用,比如16bit采样的声音数据当1个声道是32个CLK且left-justify的时候,后面106个时钟的数据会被DAC丢掉,不影响的。



C)I2S数据的格式分I2S, Left-justify, Right-justify。3种格式的区分在于声音数据与WS的对应关系:
1 .  I2S模式DAT的MSB在WS变化后的第2个上升沿开始传输
2.  Left-justify模式DAT的MSB在WS变化后的第1个上升沿开始传输
3.   Right-justify模式DAT的LSB在WS行将变换到下1声道前的最后1个时钟传输


I2S部份触及的几个频率:
  * 输出采样频率 fs = 44.1KHz.  (也有其它fs的音源, 但加了resampler后, 都变成44.1KHz输出了). 这是个关键频率.
  * LRCLK, 就等于fs. (L/R声道信号)
  * BCLK = 32倍fs = 1411.2KHz = 1.4112MHz. (bit clock). 2声道16bit, 故32倍fs. 若2声道24bit, 则48倍fs.
  * MCLK是全部audio模块的工作频率, 通常选fs的256, 384, 512倍. 比如: 256倍fs = 11289.6KHz = 11.2896MHz.

从频率设置来讲, MCLK是个主要频率, 它是全部audio模块的工作频率.

那末, 从软件来讲要设置两个方面的寄存器: 1是该PLL从晶振频率如何得到PLLout频率(比如P/M/S/k). 2是PLLout如何分频得到audio部份的MCLK.


IIS驱动部份最重要的就是注册以下钩子函数,挂到了alsa驱动上
static const struct snd_soc_dai_ops samsung_i2s_dai_ops = {
.trigger = i2s_trigger,
.hw_params = i2s_hw_params,
.set_fmt = i2s_set_fmt,
.set_clkdiv = i2s_set_clkdiv,
.set_sysclk = i2s_set_sysclk,
.startup = i2s_startup,
.shutdown = i2s_shutdown,
.delay = i2s_delay,
};




codec芯片介绍


cs4270的驱动要设置的参数有:
静音,传输模式,比特位长度,时钟主从模式,音量大小
cs4270驱动里面定义了snd_soc_dai_driver结构成员,里面定义了playback和capture两个substream,同时也挂了1个snd_soc_dai_ops结构体,里面全是操作函数指针。
alsa上面1层层的终究会调用到这些指针

static const struct snd_soc_dai_ops cs4270_dai_ops = {
.hw_params = cs4270_hw_params,
.set_sysclk = cs4270_set_dai_sysclk,
.set_fmt = cs4270_set_dai_fmt,
.digital_mute = cs4270_dai_mute,
};
static struct snd_soc_dai_driver cs4270_dai = {
.name = "cs4270-hifi",
.playback = {
.stream_name = "Playback",
.channels_min = 1,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_CONTINUOUS,
.rate_min = 4000,
.rate_max = 216000,
.formats = CS4270_FORMATS,
},
.capture = {
.stream_name = "Capture",
.channels_min = 1,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_CONTINUOUS,
.rate_min = 4000,
.rate_max = 216000, .
formats = CS4270_FORMATS,
},
.ops = &cs4270_dai_ops,
};




DMA介绍

IIS总线是慢速总线,相对CPU来讲,太慢。所以采取DMA的方式最能节省CPU性能。
PCM playback的时候,DMA目的地址是IIS FIFO寄存器。源地址是寄存PCM数据的内存。
DMA的驱动采取了linux pl330的驱动架构,采取中断的方式来触发后续DMA。
IIS中通过DMA的方式写入FIFO寄存器,在DMA的驱动中挂接了1个回调函数audio_buffdone。DMA完成后,回函数调用,刷新alsa的环,便于下1次DMA
DMA的目的地址,就是IIS发送寄存器的地址。源地址,就是申请的DMA buffer,只不过DMAbuffer被映照成了1个环
 
static void dma_enqueue(struct snd_pcm_substream *substream)
{
      struct runtime_data *prtd = substream->runtime->private_data;
     dma_addr_t pos = prtd->dma_pos;
     unsigned int limit;
     struct samsung_dma_prep dma_info;
     pr_debug("Entered %s\n", __func__);
     limit = (prtd->dma_end - prtd->dma_start) / prtd->dma_period;
     pr_debug("%s: loaded %d, limit %d\n", __func__, prtd->dma_loaded, limit);
     dma_info.cap = (samsung_dma_has_circular() ? DMA_CYCLIC : DMA_SLAVE);
     dma_info.direction = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? DMA_MEM_TO_DEV : DMA_DEV_TO_MEM);
    dma_info.fp = audio_buffdone; //回调函数
    dma_info.fp_param = substream;
    dma_info.period = prtd->dma_period;
    dma_info.len = prtd->dma_period*limit;
     while (prtd->dma_loaded < limit) {
                 pr_debug("dma_loaded: %d\n", prtd->dma_loaded);
                 if ((pos + dma_info.period) > prtd->dma_end) {
                                             dma_info.period = prtd->dma_end - pos;
                                             pr_debug("%s: corrected dma len %ld\n", __func__, dma_info.period);
                   }
                                                           dma_info.buf = pos;
                                                           prtd->params->ops->prepare(prtd->params->ch, &dma_info); //DMA注册
                                                          prtd->dma_loaded++;
                                                          pos += prtd->dma_period;
                                                          if (pos >= prtd->dma_end) pos = prtd->dma_start;
      }
                                           prtd->dma_pos = pos;
}
 
static void audio_buffdone(void *data)
{
      struct snd_pcm_substream *substream = data;
      struct runtime_data *prtd = substream->runtime->private_data;
      pr_debug("Entered %s\n", __func__);
      if (prtd->state & ST_RUNNING) {
           prtd->dma_pos += prtd->dma_period;
           if (prtd->dma_pos >= prtd->dma_end)
                    prtd->dma_pos = prtd->dma_start;
           if (substream)
                  snd_pcm_period_elapsed(substream);
          spin_lock(&prtd->lock);
          if (!samsung_dma_has_circular()) {
                  prtd->dma_loaded--;
                  dma_enqueue(substream);
            } spin_unlock(&prtd->lock);
      }
}

DMA部份主要通过注册以下钩子函数来挂到alsa驱动里面
 
static struct snd_pcm_ops dma_ops = {
 .open = dma_open,
 .close = dma_close,
 .ioctl = snd_pcm_lib_ioctl,
 .hw_params = dma_hw_params,
 .hw_free = dma_hw_free,
 .prepare = dma_prepare,
 .trigger = dma_trigger,
 .pointer = dma_pointer,
 .mmap = dma_mmap,
};

alsa数据读写简介

播放时,应用程序把音频数据源源不断地写入dma buffer中,然后相应platform的dma操作则不停地从该buffer中取出数据,经dai送往codec中。录音时
则正好相反,codec源源不断地把A/D转换好的音频数据经过dai送入dma buffer中,而应用程序则不断地从该buffer中读走音频数据

以播放(playback)为例,我现在知道最少有3个途径可以完成对dma buffer的写入:
应用程序调用alsa-lib的snd_pcm_writei、snd_pcm_writen函数;
应用程序使用ioctl:SNDRV_PCM_IOCTL_WRITEI_FRAMES或SNDRV_PCM_IOCTL_WRITEN_FRAMES;
应用程序使用alsa-lib的snd_pcm_mmap_begin/snd_pcm_mmap_commit;

以上几种方式终究把数据写入dma buffer中,然后修改runtime->control->appl_ptr的值。
播放进程中,通常会配置成每个period size生成1个dma中断,中断处理函数最重要的任务就是:
更新dma的硬件当前位置,该数值通常保存在runtime->private_data中;
调用snd_pcm_period_elapsed函数,该函数会进1步调用snd_pcm_update_hw_ptr0函数更新上述所说的4个缓冲区管理字段,然后唤醒相应的等待进程;
这个中断实际上在DMA驱动内部,给DMA驱动1个回调函数就能够了。就是我们前面说的audio_buffdone

Playback时数据流向

/sound/soc/samsung/里面,写入到DMA源buffer时,
用的是mmap的写入方式,不是采取的snd_pcm_hw_writei

几个关键点:
1,Pos计算方式
dma_pointer里面, res = prtd->dma_pos - prtd->dma_start;
此pos就是在DMA 源buffer中的位置
2,dma的初始化
dma_enqueue里面,把DMA源buffer切成period_size大小,挂到DMA队列里,每次period_size传输完了,就会调用audio_buffdone终端处理函数,更新dma_pos
同时audio_buffdone也会调用snd_pcm_update_hw_ptr0重新计算hw_ptr,从而计算是否是有足够的可用空间,来唤醒等待的poll

从alsalib来看,要先调用snd_pcm_start,触发DMA操作开始
snd_pcm_start---SNDRV_PCM_IOCTL_START---snd_pcm_action_lock_irq--snd_pcm_do_start----dma_trigger
每次写入mmap buffer之前,要先snd_pcm_wait,等待有足够可用的空间.
snd_pcm_wait----snd_pcm_wait_nocheck----poll-----snd_pcm_playback_poll---poll_wait
然后调用snd_pcm_mmap_begin获得mmap 内存
然后写入,然后调用snd_pcm_mmap_commit做1下alsalib和驱动里面的环同步

DMA实际上是在不停的DMA的。有空闲的了,上层就不用wait了,就会写入了

几个典型调用流程

设置hw_param参数时,调用流程
 
snd_pcm_hw_params
_snd_pcm_hw_params
pcm->ops->hw_params
snd_pcm_hw_hw_params
SNDRV_PCM_IOCTL_HW_PARAMS
//snd_pcm_common_ioctl1
//snd_pcm_hw_params_user
 
//snd_pcm_hw_params
//substream->ops->hw_params
soc_pcm_hw_params
codec_dai->driver->ops->hw_params
cpu_dai->driver->ops->hw_params
cs4270_hw_params
 
 

注册ioctl

vendor/qcom/opensource/audio-kernel/asoc/msm8952.c

static int msm8952_asoc_machine_probe(struct platform_device *pdev)

                                                 |

     /kernel/msm-4.9/sound/soc/soc-devres.c

  int devm_snd_soc_register_card(struct device *dev, struct snd_soc_card *card)

kernel/msm-4.9/sound/soc/soc-core.c

 /* probes a new socdev */
 static int soc_probe(struct platform_device *pdev)

                                              |

 /**
  * snd_soc_register_card - Register a card with the ASoC core   *
  * @card: Card to register
  *
  */
 int snd_soc_register_card(struct snd_soc_card *card)

                                             |

static int snd_soc_instantiate_card(struct snd_soc_card *card)

                                               |

 static int soc_probe_link_dais(struct snd_soc_card *card,
   struct snd_soc_pcm_runtime *rtd, int order)

                                            |

/kernel/msm-4.9/sound/soc/soc-pcm.c

 /* create a new pcm */
 int (struct snd_soc_pcm_runtime *rtd, int num)

                                        |

/kernel/msm-4.9/sound/core/pcm.c  

 

 int snd_pcm_new(struct snd_card *card, const char *id, int device,
822    int playback_count, int capture_count, struct snd_pcm **rpcm)
823  {
824   return _snd_pcm_new(card, id, device, playback_count, capture_count,
825     false, rpcm);
826  }
827  EXPORT_SYMBOL(snd_pcm_new);
828  

851   struct snd_pcm **rpcm)
852  {
853   return _snd_pcm_new(card, id, device, playback_count, capture_count,
854     true, rpcm);
855  }
856  EXPORT_SYMBOL(snd_pcm_new_internal);

                            |

 static int _snd_pcm_new(struct snd_card *card, const char *id, int device,
   int playback_count, int capture_count, bool internal,
   struct snd_pcm **rpcm)

                               |

  static int snd_pcm_dev_register(struct snd_device *device)
  {
   int cidx, err;
   struct snd_pcm_substream *substream;
   struct snd_pcm_notify *notify;
   struct snd_pcm *pcm;
 
   if (snd_BUG_ON(!device || !device->device_data))
    return -ENXIO;
   pcm = device->device_data;
   if (pcm->internal)
    return 0;
 
   mutex_lock(&register_mutex);
   err = snd_pcm_add(pcm);
   if (err)
    goto unlock;
   for (cidx = 0; cidx < 2; cidx++) {
    int devtype = -1;
    if (pcm->streams[cidx].substream == NULL)
     continue;
    switch (cidx) {
    case SNDRV_PCM_STREAM_PLAYBACK:
     devtype = SNDRV_DEVICE_TYPE_PCM_PLAYBACK;
     break;
    case SNDRV_PCM_STREAM_CAPTURE:
     devtype = SNDRV_DEVICE_TYPE_PCM_CAPTURE;
     break;
    }

   /* register pcm */
   err = snd_register_device(devtype, pcm->card, pcm->device,
        &snd_pcm_f_ops[cidx], pcm,
        &pcm->streams[cidx].dev);

                                  |

/kernel/msm-4.9/sound/core/pcm_native.c

  

  const struct file_operations snd_pcm_f_ops[2] = {
   {
    .ownerTHIS_MODULE,
    .writesnd_pcm_write,
    .write_itersnd_pcm_writev,
    .open =   snd_pcm_playback_open,
    .releasesnd_pcm_release,
    .llseekno_llseek,
    .poll =   snd_pcm_playback_poll,
    .unlocked_ioctl = snd_pcm_playback_ioctl,
    .compat_ioctlsnd_pcm_ioctl_compat,
    .mmap =   snd_pcm_mmap,
    .fasyncsnd_pcm_fasync,
    .get_unmapped_area = snd_pcm_get_unmapped_area,
   },
   {
    .ownerTHIS_MODULE,
    .read =   snd_pcm_read,
    .read_itersnd_pcm_readv,
    .open =   snd_pcm_capture_open,
    .releasesnd_pcm_release,
    .llseekno_llseek,
    .poll =   snd_pcm_capture_poll,
    .unlocked_ioctl = snd_pcm_capture_ioctl,
    .compat_ioctlsnd_pcm_ioctl_compat,
    .mmap =   snd_pcm_mmap,
    .fasyncsnd_pcm_fasync,
    .get_unmapped_area = snd_pcm_get_unmapped_area,
   }
  };
 

///

音频播放流程调用:
 
 
 

 static int voip_start_call(struct audio_device *adev,
                            struct pcm_config *voip_config)
 {

 

void play_sample(FILE *file, unsigned int card, unsigned int device, unsigned int channels,
                   unsigned int rate, unsigned int bits, unsigned int period_size,
                   unsigned int period_count)

                                           |

 

struct pcm *pcm_open(unsigned int card, unsigned int device, unsigned int flags, struct pcm_config *config)

               if (ioctl(pcm->fd, , &params)) {

     if (ioctl(pcm->fd, , &params)) {
         oops(pcm, errno, "cannot set hw params");
         goto fail_close;
     }

                                                |

/kernel/msm-4.9/sound/core/pcm_native.c

 static long snd_pcm_playback_ioctl1(struct file *file, unsigned int cmd,
        unsigned long arg)

       

return snd_pcm_common_ioctl1 (file, substream, cmd, arg);

 

                                |

 static int snd_pcm_common_ioctl1 (struct file *file,

      struct snd_pcm_substream *substream,
      unsigned int cmd, void __user *arg)

2808  {
2809   switch (cmd) {
2810   case SNDRV_PCM_IOCTL_PVERSION:
2811    return put_user(SNDRV_PCM_VERSION, (int __user *)arg) ? -EFAULT : 0;
2812   case SNDRV_PCM_IOCTL_INFO:
2813    return snd_pcm_info_user(substream, arg);
2814   case SNDRV_PCM_IOCTL_TSTAMP: /* just for compatibility */
2815    return 0;
2816   case SNDRV_PCM_IOCTL_TTSTAMP:
2817    return snd_pcm_tstamp(substream, arg);
2818   case SNDRV_PCM_IOCTL_HW_REFINE:
2819    return snd_pcm_hw_refine_user(substream, arg);
2820   case SNDRV_PCM_IOCTL_HW_PARAMS:
2821    return snd_pcm_hw_params_user(substream, arg);
2822   case SNDRV_PCM_IOCTL_HW_FREE:
2823    return snd_pcm_hw_free(substream);
2824   case SNDRV_PCM_IOCTL_SW_PARAMS:
2825    return snd_pcm_sw_params_user(substream, arg);
2826   case SNDRV_PCM_IOCTL_STATUS:
2827    return snd_pcm_status_user(substream, arg, false);
2828   case SNDRV_PCM_IOCTL_STATUS_EXT:
2829    return snd_pcm_status_user(substream, arg, true);
2830   case SNDRV_PCM_IOCTL_CHANNEL_INFO:
2831    return snd_pcm_channel_info_user(substream, arg);
2832   case SNDRV_PCM_IOCTL_PREPARE:
2833    return snd_pcm_prepare(substream, file);
2834   case SNDRV_PCM_IOCTL_RESET:
2835    return snd_pcm_reset(substream);
2836   case SNDRV_PCM_IOCTL_START:
2837    return snd_pcm_action_lock_irq(&snd_pcm_action_start, substream, SNDRV_PCM_STATE_RUNNING);
2838   case SNDRV_PCM_IOCTL_LINK:
2839    return snd_pcm_link(substream, (int)(unsigned long) arg);
2840   case SNDRV_PCM_IOCTL_UNLINK:
2841    return snd_pcm_unlink(substream);
2842   case SNDRV_PCM_IOCTL_RESUME:
2843    return snd_pcm_resume(substream);
2844   case SNDRV_PCM_IOCTL_XRUN:
2845    return snd_pcm_xrun(substream);
2846   case SNDRV_PCM_IOCTL_HWSYNC:
2847    return snd_pcm_hwsync(substream);
2848   case SNDRV_PCM_IOCTL_DELAY:
2849    return snd_pcm_delay(substream, arg);
2850   case SNDRV_PCM_IOCTL_SYNC_PTR:
2851    return snd_pcm_sync_ptr(substream, arg);
2852  #ifdef CONFIG_SND_SUPPORT_OLD_API
2853   case SNDRV_PCM_IOCTL_HW_REFINE_OLD:
2854    return snd_pcm_hw_refine_old_user(substream, arg);
2855   case SNDRV_PCM_IOCTL_HW_PARAMS_OLD:
2856    return snd_pcm_hw_params_old_user(substream, arg);
2857  #endif
2858   case SNDRV_PCM_IOCTL_DRAIN:
2859    return snd_pcm_drain(substream, file);
2860   case SNDRV_PCM_IOCTL_DROP:
2861    return snd_pcm_drop(substream);
2862   case SNDRV_PCM_IOCTL_PAUSE:
2863   {
2864    int res;
2865    snd_pcm_stream_lock_irq(substream);
2866    res = snd_pcm_pause(substream, (int)(unsigned long)arg);
2867    snd_pcm_stream_unlock_irq(substream);
2868    return res;
2869   }
2870   case SNDRV_COMPRESS_GET_CAPS:
2871   case SNDRV_COMPRESS_GET_CODEC_CAPS:
2872   case SNDRV_COMPRESS_SET_PARAMS:
2873   case SNDRV_COMPRESS_GET_PARAMS:
2874   case SNDRV_COMPRESS_TSTAMP:
2875   case SNDRV_COMPRESS_DRAIN:
2876    return snd_compressed_ioctl(substream, cmd, arg);
2877   default:
2878    if (((cmd >> 8) & 0xff) == 'U')
2879     return snd_user_ioctl(substream, cmd, arg);
2880   }
2881   pcm_dbg(substream->pcm, "unknown ioctl = 0x%x\n", cmd);
2882   return -ENOTTY;
2883  }

                               |

err = (substream, params);
   if (copy_to_user(_params, params, sizeof(*params))) {

                               |

if (substream->ops->hw_params != NULL) {
   err = substream->ops->hw_params(substream, params);
   if (err < 0)
    goto _error;
  }

 
设置mixer 参数时,volume为例,调用流程
 
snd_mixer_selem_set_playback_volume_all
snd_mixer_selem_set_playback_volume
set_volume_ops _snd_mixer_selem_set_volume
selem_write
selem_write_main
elem_write_volume
snd_hctl_elem_write
snd_ctl_elem_write
snd_ctl_hw_elem_write
snd_ctl_elem_write_user
snd_ctl_elem_write
snd_soc_put_volsw
snd_soc_update_bits_locked
regmap_update_bits_check
 
IIS clk 设置流程
snd_pcm_common_ioctl1
snd_pcm_hw_params_user
snd_pcm_hw_params
substream->ops->hw_params
soc_pcm_hw_params
rtd->dai_link->ops->hw_params
smdk_wm8994_pcm_hw_params
snd_soc_dai_set_sysclk
dai->driver->ops->set_sysclk
i2s_set_sysclk

 

转自:https://mp.csdn.net/editor/html/111975376

生活不易,码农辛苦
如果您觉得本网站对您的学习有所帮助,可以手机扫描二维码进行捐赠
 
------分隔线----------------------------
分享到:
 
------分隔线----------------------------
  • 1
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值