1 前言

本文将基于STM32F4 Discovery板,从零开始设计并实现一个USB Audio的例子。

2 设计构思

所谓的USB AUDIO就是制作一个盒子,这个盒子可以通过USB连接到PC,PC端将其识别为Audio设备,然后在PC端播放音乐的时候,声音可以通过盒子播放出来。

2.1 从原理框图开始

图1

如上图所示,我们大概构思一下,为了实现USB AUDIO功能,我们使用一个MCU的USB外设连接PC端,整个流程是这样: PC端播放音乐时,代表音乐的数据流从PC端通过USB传输到MCU端,MCU端然后将其转发给一个外部Codec,最后通过Codec上连接的扬声器或耳机播放音乐。

2.2 硬件支撑

这里选择ST官方的STM32F4-DISCOVERY板来实现,之所以选择这块板子,就是因为其上有USB接口和Codec,正好符合我们设计的要求。

2.2.1 USB接口

如下图为USB接口部分的电路:

图2

这个一个将USB作为OTG的电路设计,在本设计中,我们只是将USB作为device来使用,因此,上图我们关注下面部分就可以了。在本设计中,我们使用到全速USB,从上图可以看出D+与D-引脚分别为PA12,PA11。

2.2.2 Codec部分

如下图所示:

图3

如上图所示,这里的Codec为具体型号为CS43L22,MCU通过I2C接口(PB9,PB6)连接Codec,作为其控制接口,使用I2S(PC7,PC10,PC12,PA4)作为数据通道,此外,MCU使用PD4这个IO管脚控制Codec的reset。CS43L22的14,15脚连接到外面的耳机插孔,也就是说,我们可以通过插入耳机线的方式来收听PC端播放的声音。

2.3 软件设计

为了简化开发流程,这里使用CubeMx自动生成代码工具来生成初始化代码,首先基于Cube库架构以及USB协议栈的特点,我们得先设计一个合理的软件框架。

图4

如上图,蓝色表示的模块为标准模块,不需要我们去修改它,将由CubeMx自动生成,而绿色部分则可能涉及到需要修改,其中BSP部分是需要自己添加的代码,其他的都是由CubeMx生成。

各个模块的工作流程如下设计:

  • 初始化流程: 由main开始,它首先对将使用到的外设I2C,I2S初始化,这最终将调到HAL MSP底层部分实现对具体IO管脚和外设的初始化。同时main使用usb description的数据通过调用USB栈初始化接口来完成对USB接口的初始化,这一步还涉及到USB的枚举过程。
  • USB数据传输过程:PC端软件在播放音乐后,通过USB通道向MCU传输音频数据,音频数据到达MCU时,首先触发USB中断,然后进入到HAL driver层,在回调到 usb conf模块,接着进入到usb core,usb core再转给usb audio class,最后音频数据到达usb audio interface模块,到达这里,就只剩下对音频数据进行处理了。Usb audio interface模块是一个数据接收到数据处理的一个中间对接模块。
  • USB音频数据处理过程: usb audio interface 模块将从USB stack底层传上来的音频数据转发给Codec组件,最终通过Codec组件连接的耳机播放出来。

从以上的音频数据流程来看,最主要的就是usbaudio interface模块,它实现了从USB audio stack到codec驱动的对接。

接下来,我们来看看软件层面上的实现。

3 软件实现

还是老办法,采用CubeMx这个工具来生成初始化代码,这样可以节省我们花费在基本外设上的调试初始参数时间。

3.1 创建CubeMx工程

由于我们使用到的硬件平台是STM32F4Discovery板,上面搭载的MCU型号是STM32F407VGT6,我们就以此型号创建一个名为Audio_Test的工程。

pinout:

外设有用到USB_OTG_FS(PA11,PA12device模式),I2C1(PB6,PB9),I2S3(PC12,PA4,PC10,PC7,半双工主模式),此外Codec的reset使用PD4管脚控制,使用外部8M HSE。其pinout如下图所示:

图5

Clock configuration:

图6

时钟树如上设置,主频使用168M,I2S时钟输出初始化为96M。

Configuration:

  • HAL层:

Usb_FS:使用默认参数。

I2C:100K速率,7位地址宽度,使用默认参数。

I2S:主发模式,标准16位宽,默认音频为48K,如下图:

图7

并为I2S发送添加DMA,半字位宽:

图8

  • MiddleWares:

USB选择Audiodevice class,其配置参数如下:

图9

这里都是默认参数。

图10

在描述符参数内得为usb audio class修改两个参数:

  • PID得修改为0x5730(否则windows驱动会加载出错)
  • 序列号:序列号字符串内不能包含字母,只能是数据(否则windowsaudio驱动在枚举后也不会将音频数据传输下来)。

最后修改工程设置,将堆大小设为4K,栈大小设为1K,如下图:

图11

如此就可以生成工程了,我们生成IAR工程。

3.2 生成的IAR工程介绍

图12

如上图所示,生成的IAR工程,主要有User,Drivers,Middleware3个目录。

  • User目录下为用户源码文件,用户的主要修改也将集中在此目录下,在这里,我们的主要工作是集中在usbd_audio_if.c文件,它对应着之前软件框图中的usbaudio interface模块,主要是实现USB audio协议栈与Codec的对接。其他源文件都保持不变就可以了。
  • Middlewares目录对应着usb audio stack模块,它由CubeMx自动生成,保持原样就可以,不需要任何修改。
  • Drivers目录对应着HAL层,它包含CMSIS,HAL驱动。

3.3 开发

3.3.1 初次编译测试

首先我们不做任何修改,先编译一下工程,发现能顺利编译通过,并烧录进STM32F4DISCOVERY板,运行后通过USB连接上电脑,发现在设备管理器中能正常识别到这个USB AUDIO设备,如下图所示:

图13

这说明,USB与PC端的连接是OK的,但不知道具体有没有数据。我们使用USB分析仪TOTAL PHASE USB480这个设备对USB总线进行数据监控,能够正常采集USB枚举过程和播放音乐的通信数据,如下图所示:

图14

这表明,到目前为止,从PC端到USB端都是能正常工作的,从PC端发送过来的音频数据已经到达usb audio interface模块,目前只不过还没有对这些数据进行处理,显然,接下来的工作,我们就需要将这些音频数据通过codec驱动发送出去,最终到达外部组件CS32L22.

3.3.2 添加codec驱动和audio bsp模块

我们已经知道,我们需要为audio添加BSP模块,在这里,我们将BSP归属于drivers类,因此,在drivers目录下添加BSP目录,通过之前的软件架构图我们可以知道,BSP包含Codec驱动(CS43L22)和Audio bsp模块,因此,我们在BSP目录下有添加了Codec的驱动源码cs43l22.c与bsp_audio.c,如下图所示:

图15

其中cs43l22.c为codec cs32l22的驱动,我们可以从ST的组件驱动中找到它,并copy过来直接使用,不需要修改任何代码。而bsp_audio.c是我们自己写的,它的任务是为usbd_audio_if.c与cs43l22.c提供服务,让这两个模块胜利对接。

3.3.2.1 Codec与HAL的对接

首先我们来看Codec驱动文件cs43l22.c源文件,这个文件需要使用这个外部需要提供的接口:

  1. AUDIO_IO_Init()
  2. AUDIO_IO_DeInit()
  3. AUDIO_IO_Write()
  4. AUDIO_IO_Read()

这个都是Codec的基本控制接口,是通过I2C来控制的。都是需要用户在驱动外部来提供这些接口给到驱动,于是,我们在bsp_audio.c文件中来提供这个接口的实现:

  1. //---------------------for c43l22 port--------------------------//
  2. static void I2Cx_Error(uint8_t Addr)
  3. {
  4. /* De-initialize the IOE comunication BUS */
  5. HAL_I2C_DeInit(&hi2c1);
  6. /* Re-Initiaize the IOE comunication BUS */
  7. //I2Cx_Init();
  8. //MX_I2C1_Init();
  9. }
  10. static void CODEC_Reset(void)
  11. {
  12. HAL_GPIO_WritePin(AUDIO_RESET_GPIO_Port, AUDIO_RESET_Pin, GPIO_PIN_RESET);
  13. HAL_Delay(5);
  14. HAL_GPIO_WritePin(AUDIO_RESET_GPIO_Port, AUDIO_RESET_Pin, GPIO_PIN_SET);
  15. HAL_Delay(5);
  16. }
  17. void AUDIO_IO_Init(void)
  18. {
  19. //I2Cx_Init();
  20. }
  21. void AUDIO_IO_DeInit(void)
  22. {
  23. }
  24. /**
  25. * @brief  Writes a single data.
  26. * @param  Addr: I2C address
  27. * @param  Reg: Reg address
  28. * @param  Value: Data to be written
  29. */
  30. static void I2Cx_Write(uint8_t Addr, uint8_t Reg, uint8_t Value)
  31. {
  32. HAL_StatusTypeDef status = HAL_OK;
  33. status = HAL_I2C_Mem_Write(&hi2c1, Addr, (uint16_t)Reg, I2C_MEMADD_SIZE_8BIT, &Value, 1, I2C_TIMEOUT);
  34. /* Check the communication status */
  35. if(status != HAL_OK)
  36. {
  37. /* I2C error occured */
  38. I2Cx_Error(Addr);
  39. }
  40. }
  41. void AUDIO_IO_Write(uint8_t Addr, uint8_t Reg, uint8_t Value)
  42. {
  43. I2Cx_Write(Addr, Reg, Value);
  44. }
  45. /**
  46. * @brief  Reads a single data.
  47. * @param  Addr: I2C address
  48. * @param  Reg: Reg address
  49. * @retval Data to be read
  50. */
  51. static uint8_t I2Cx_Read(uint8_t Addr, uint8_t Reg)
  52. {
  53. HAL_StatusTypeDef status = HAL_OK;
  54. uint8_t Value = 0;
  55. status = HAL_I2C_Mem_Read(&hi2c1, Addr, Reg, I2C_MEMADD_SIZE_8BIT, &Value, 1, I2C_TIMEOUT);
  56. /* Check the communication status */
  57. if(status != HAL_OK)
  58. {
  59. /* Execute user timeout callback */
  60. I2Cx_Error(Addr);
  61. }
  62. return Value;
  63. }
  64. uint8_t AUDIO_IO_Read(uint8_t Addr, uint8_t Reg)
  65. {
  66. return I2Cx_Read(Addr, Reg);
  67. }

由于在main函数中已经对I2C初始化过了,因此,在AUDIO_IO_Init函数中不需要再次初始化。

就这样,就完成了Codec驱动与与HAL的对接。

3.3.2.2 usb audiointerface与codec的对接

我们打开usb audio interface源码文件usbd_audio.if.c文件,此文件由CubeMx自动生成,已经自动给出了一些关于usb audio class的函数,且这些函数体内容都是空白的,毫无疑问,接下来的工作,我们就是要完成这个空白的内容,就好比做填空题一样,当然,在做这些”填空题”的过程中,我们将使用到Codec驱动提供的接口,这一过程,就是usb audiointerface与codec的对接过程。

按照这一清晰思路,我们首先找到usbd_audio_if.c的一个接口:

  1. static int8_t AUDIO_Init_FS(uint32_t  AudioFreq, uint32_t Volume, uint32_t options)
  2. {
  3. /* USER CODE BEGIN 0 */
  4. return (USBD_OK);
  5. /* USER CODE END 0 */
  6. }

这是个空白函数,它是在USB枚举结束并收到set_configuration消息时会被调用到,我们利用他来实现对codec的初始化。在bsp_audio.c文件中,我们添加一个函数,如下:

  1. static void I2Sx_Init(uint32_t AudioFreq)
  2. {
  3. /* Initialize the haudio_i2s Instance parameter */
  4. hi2s3.Instance = SPI3;
  5. /* Disable I2S block */
  6. __HAL_I2S_DISABLE(&hi2s3);
  7. hi2s3.Init.Mode = I2S_MODE_MASTER_TX;
  8. hi2s3.Init.Standard = I2S_STANDARD;
  9. hi2s3.Init.DataFormat = I2S_DATAFORMAT_16B;
  10. hi2s3.Init.AudioFreq = AudioFreq;
  11. hi2s3.Init.CPOL = I2S_CPOL_LOW;
  12. hi2s3.Init.MCLKOutput = I2S_MCLKOUTPUT_ENABLE;
  13. if(HAL_I2S_GetState(&hi2s3) == HAL_I2S_STATE_RESET)
  14. {
  15. HAL_I2S_MspInit(&hi2s3);
  16. }
  17. /* Init the I2S */
  18. HAL_I2S_Init(&hi2s3);
  19. }
  20. const uint32_t I2SFreq[8] = {8000, 11025, 16000, 22050, 32000, 44100, 48000, 96000};
  21. const uint32_t I2SPLLN[8] = {256, 429, 213, 429, 426, 271, 258, 344};
  22. const uint32_t I2SPLLR[8] = {5, 4, 4, 4, 4, 6, 3, 1};
  23. uint8_t BSP_AUDIO_OUT_Init(uint16_t OutputDevice, uint8_t Volume, uint32_t AudioFreq)
  24. {
  25. uint32_t deviceid = 0x00;
  26. uint8_t ret = AUDIO_ERROR;
  27. uint8_t index = 0, freqindex = 0xFF;
  28. RCC_PeriphCLKInitTypeDef RCC_ExCLKInitStruct;
  29. //get the according P,N value and set into config,this is for audio clock provide
  30. for(index = 0; index < 8; index++)
  31. {
  32. if(I2SFreq[index] == AudioFreq)
  33. {
  34. freqindex = index;
  35. }
  36. }
  37. HAL_RCCEx_GetPeriphCLKConfig(&RCC_ExCLKInitStruct);
  38. if(freqindex != 0xFF)
  39. {
  40. RCC_ExCLKInitStruct.PeriphClockSelection = RCC_PERIPHCLK_I2S;
  41. RCC_ExCLKInitStruct.PLLI2S.PLLI2SN = I2SPLLN[freqindex];
  42. RCC_ExCLKInitStruct.PLLI2S.PLLI2SR = I2SPLLR[freqindex];
  43. HAL_RCCEx_PeriphCLKConfig(&RCC_ExCLKInitStruct);
  44. }
  45. else
  46. {
  47. RCC_ExCLKInitStruct.PeriphClockSelection = RCC_PERIPHCLK_I2S;
  48. RCC_ExCLKInitStruct.PLLI2S.PLLI2SN = 258;
  49. RCC_ExCLKInitStruct.PLLI2S.PLLI2SR = 3;
  50. HAL_RCCEx_PeriphCLKConfig(&RCC_ExCLKInitStruct);
  51. }
  52. //reset the Codec register
  53. CODEC_Reset();
  54. deviceid = cs43l22_drv.ReadID(AUDIO_I2C_ADDRESS);
  55. if((deviceid & CS43L22_ID_MASK) == CS43L22_ID)
  56. {
  57. /* Initialize the audio driver structure */
  58. audio_drv = &cs43l22_drv;
  59. ret = AUDIO_OK;
  60. }
  61. else
  62. {
  63. ret = AUDIO_ERROR;
  64. }
  65. if(ret == AUDIO_OK)
  66. {
  67. audio_drv->Init(AUDIO_I2C_ADDRESS, OutputDevice, Volume, AudioFreq);
  68. /* I2S data transfer preparation:
  69. Prepare the Media to be used for the audio transfer from memory to I2S peripheral */
  70. /* Configure the I2S peripheral */
  71. I2Sx_Init(AudioFreq);
  72. }
  73. return AUDIO_OK;
  74. }

在BSP_AUDIO_OUT_Init()这个函数内,根据所传入的采样率,程序在预定义的数组内选择出MCU内部时钟树对I2S时钟的一个合理的分频值,并设置进时钟树配置内,然后再对codec进行初始化,最后对I2S外设初始化。

这个选择I2S时钟合理分频值的过程是根据STM32F407的参考手册中的建议来做的,如下参考手册中的28.4.4中表126:

图16

在48K采样率下,假设时钟树下的PLLM VCO=1MHz情况下,且MCK使能,为了尽可能输出靠近期望的时钟,此时应该将时钟树内的PLL2SN设为258,且PLL2SR设为3,I2S内部的预分频因子I2SDIV设为3,以及零散因子I2SODD设为1。这个计算公式为:

图17

也就是(1M*258/3)/[(16*2)*((2*3)+1)]=47991.07142857143,差不多48K。

PLL2SN,与PLL2SR的设置在上述代码中都有所体现,但是,预分频因子I2SDIV和零散因子I2SODD又是在哪里设置的呢?答案是在代码调用HAL_I2S_Init()时,在这个HAL接口内部会根据I2S的Audio Frequency(CubeMx中的I2S的Configuration中配置的参数),以及I2S的输入时钟频率和MCK是否使能这些前提条件来自动计算出预分频因子I2SDIV和零散因子I2SODD的值,以此来尽可能匹配输出想要的位时钟,也对应着采样率48K。

搞懂了这些之后,我们马上将其代码进行对接:

  1. static int8_t AUDIO_Init_FS(uint32_t  AudioFreq, uint32_t Volume, uint32_t options)
  2. {
  3. /* USER CODE BEGIN 0 */
  4. BSP_AUDIO_OUT_Init(OUTPUT_DEVICE_AUTO, Volume, AudioFreq);
  5. return (USBD_OK);
  6. /* USER CODE END 0 */
  7. }

接下来下一个需要对接的接口:

  1. static int8_t AUDIO_DeInit_FS(uint32_t options)
  2. {
  3. /* USER CODE BEGIN 1 */
  4. BSP_AUDIO_OUT_Stop(CODEC_PDWN_SW);
  5. return (USBD_OK);
  6. /* USER CODE END 1 */
  7. }

很明显,这个个反初始化的接口,它的具体实现如下:

  1. uint8_t BSP_AUDIO_OUT_Stop(uint32_t Option)
  2. {
  3. /* Call the Media layer stop function */
  4. HAL_I2S_DMAStop(&hi2s3);
  5. /* Call Audio Codec Stop function */
  6. if(audio_drv->Stop(AUDIO_I2C_ADDRESS, Option) != 0)
  7. {
  8. return AUDIO_ERROR;
  9. }
  10. else
  11. {
  12. if(Option == CODEC_PDWN_HW)
  13. {
  14. /* Wait at least 1ms */
  15. HAL_Delay(1);
  16. /* Reset the pin */
  17. //BSP_IO_WritePin(AUDIO_RESET_PIN, RESET);
  18. HAL_GPIO_WritePin(AUDIO_RESET_GPIO_Port, AUDIO_RESET_Pin, GPIO_PIN_RESET);
  19. }
  20. /* Return AUDIO_OK when all operations are correctly done */
  21. return AUDIO_OK;
  22. }
  23. }

先关闭I2S的DMA,在调用Codec的停止接口。

OK,下一个:

  1. static int8_t AUDIO_AudioCmd_FS (uint8_t* pbuf, uint32_t size, uint8_t cmd)
  2. {
  3. /* USER CODE BEGIN 2 */
  4. switch(cmd)
  5. {
  6. case AUDIO_CMD_START:
  7. BSP_AUDIO_OUT_Play((uint16_t *)pbuf, size);
  8. break;
  9. case AUDIO_CMD_PLAY:
  10. BSP_AUDIO_OUT_ChangeBuffer((uint16_t *)pbuf, size);
  11. break;
  12. }
  13. return (USBD_OK);
  14. /* USER CODE END 2 */
  15. }

第一次USB audio stack接收到USB OUT数据时会回调这个接口并传入AUDIO_CMD_START参数,这里的处理代码是:

  1. uint8_t BSP_AUDIO_OUT_Play(uint16_t* pBuffer, uint32_t Size)
  2. {
  3. /* Call the audio Codec Play function */
  4. if(audio_drv->Play(AUDIO_I2C_ADDRESS, pBuffer, Size) != 0)
  5. {
  6. return AUDIO_ERROR;
  7. }
  8. else
  9. {
  10. /* Update the Media layer and enable it for play */
  11. HAL_I2S_Transmit_DMA(&hi2s3, pBuffer, DMA_MAX(Size/AUDIODATA_SIZE));
  12. return AUDIO_OK;
  13. }
  14. }

很明显,它是调用Codec驱动处理数据,也就是通过I2S的DMA方式发送给Codec。

然后I2S的DMA会产生传输完成中断和半传输完成中断,在这两个中断处理上,会回调到AUDIO_AudioCmd_FS()接口,并且此时传入的参数变为AUDIO_CMD_PLAY,此时,音频数据的处理函数为:

  1. void BSP_AUDIO_OUT_ChangeBuffer(uint16_t *pData, uint16_t Size)
  2. {
  3. HAL_I2S_Transmit_DMA(&hi2s3, pData, Size);
  4. }

也是通过I2S的DMA将数据传输给外部Codec。

上述过程涉及到另外两个usbd_audio_if接口函数,即I2S的DMA半传输完成和传输完成中断回调,如下所示:

  1. void TransferComplete_CallBack_FS(void)
  2. {
  3. /* USER CODE BEGIN 7 */
  4. USBD_AUDIO_Sync(&hUsbDeviceFS, AUDIO_OFFSET_FULL);
  5. /* USER CODE END 7 */
  6. }
  7. void HalfTransfer_CallBack_FS(void)
  8. {
  9. /* USER CODE BEGIN 8 */
  10. USBD_AUDIO_Sync(&hUsbDeviceFS, AUDIO_OFFSET_HALF);
  11. /* USER CODE END 8 */
  12. }

此代码为CubeMx自动生成,且在自动生成的代码中就已经调用了usb audio class函数USBD_AUDIO_Sync(),在这里,对于这个,我们是不需要添加任何额外代码的。之前我们说过,在USBD_AUDIO_Sync()函数内部,会实现对AUDIO_AudioCmd_FS()的回调,目的是,需要及时将数据缓冲中另一半准备好的数据也通过I2S的DMA传输给外部Codec,这个不间断的传输,才能实现音频播放的连贯性。

此外,在USB设备端,后续接收到的音频数据会紧接着之前的数据进行存放,这里实现了一个数据环形缓冲区,来实现了USB接收端与I2S输出端数据有效的缓存。

接下来看下一个usbd_audio_if接口函数对接:

  1. static int8_t AUDIO_VolumeCtl_FS (uint8_t vol)
  2. {
  3. /* USER CODE BEGIN 3 */
  4. BSP_AUDIO_OUT_SetVolume(vol);
  5. return (USBD_OK);
  6. /* USER CODE END 3 */
  7. }

很明显,这个是音量控制接口,也对接下:

  1. uint8_t BSP_AUDIO_OUT_SetVolume(uint8_t Volume)
  2. {
  3. /* Call the codec volume control function with converted volume value */
  4. if(audio_drv->SetVolume(AUDIO_I2C_ADDRESS, Volume) != 0)
  5. {
  6. return AUDIO_ERROR;
  7. }
  8. else
  9. {
  10. /* Return AUDIO_OK when all operations are correctly done */
  11. return AUDIO_OK;
  12. }
  13. }

直接调用Codec启动的相应接口。需要注意地是,实际上,在PC端进行音量的调节,并不会向USB端发送相应的音量调节指令,这里只是象征性的对接下,实际上在USB AUDIO中代码并不会允许到这里,音量的放大和变小直接体现在音频数据本身内。

下一个:

  1. static int8_t AUDIO_MuteCtl_FS (uint8_t cmd)
  2. {
  3. /* USER CODE BEGIN 4 */
  4. BSP_AUDIO_OUT_SetMute(cmd);
  5. return (USBD_OK);
  6. /* USER CODE END 4 */
  7. }

静音控制,其实现为:

  1. uint8_t BSP_AUDIO_OUT_SetMute(uint32_t Cmd)
  2. {
  3. /* Call the Codec Mute function */
  4. if(audio_drv->SetMute(AUDIO_I2C_ADDRESS, Cmd) != 0)
  5. {
  6. return AUDIO_ERROR;
  7. }
  8. else
  9. {
  10. /* Return AUDIO_OK when all operations are correctly done */
  11. return AUDIO_OK;
  12. }
  13. }

很简单,直接调用codec驱动的静音接口。静音接口与音量控制不同,在PC端进行静音操作会发送相应的mute指令,进而运行到这里。

OK,就这样,usbd_audio_if模块的接口基本上对接到这样就可以了。

4    测试验证

将代码编译后烧录进STM32F4DISCVOERY板进行验证。

图18

最终验证是OK的,可以从耳机上听到PC端播放的音乐。

嵌入式学习交流群:561213221

5    结束语

在CubeMx上对中间件USB配置时,将USB audio class的音频采样率设置为48K,那个这个参数会再USB枚举期间会传递给windows的audio驱动,在枚举通过后,后续通过USB传输的音频数据都将是固定以48K采样率来的,也就是192bytes/ms,也就是说,不管PC端播放什么音乐,windows的audio驱动都会固定以48K采样率向USB端口进行传输。这种特性是由windows的audio驱动决定的。

I2S外设向codec传输的时钟是可以改变的,在本应用中是用不着改变,这个是因为USB端固定以48K采样率接收数据,那么I2S也可以固定以48K采样率所对应的速度向Codec传输速度,这个特点,正式因为USB audio的固定传输特性所决定的。若换成播放本地U盘音频文件或连接iPhone并播放iPhone的音乐时,则I2S外设的时钟是根据每次播放的具体音乐所对应的采样率来配置I2S的时钟的,这种机制稍微有所不同,这里只需注意下,理解了就可以了。

在本例中,从I2S传输数据的速率是48K的采样率,但实际精度却是47991.07142857143。这个与标准的48K还是有所偏差的,实际上,无论USB端和I2S端的传输速度在理论上有多匹配,在实际上,多少都会存在些偏差,这也就意味着,在USB与I2S这两个”入口”与”出口”之间的缓存,在随着时间流逝,如不进行任何处理,这个缓存理论上一定会爆掉或掏空。那么这里就需要针对这个缓存这种现象的一种处理,或者叫做算法,算法的好坏在一定程度上决定了音质的好坏。而本例中,我们使用的是CubeMx生成的默认的最简单的算法,我们不做深入讨论,只是让大家有这么一个概念即可。

USB Audio设计与实现的更多相关文章

  1. USB AUDIO Device CLASS Requests

    写在前面 本文翻译自 USB Device Class Definition for Audio Devices 1998年版.主要是鄙人个人使用,所以只挑对我有用的翻译.有些我认为不是很重要的可能就 ...

  2. 一个最简的 USB Audio 示例

    经过了两三个月的痛苦,USB 协议栈的 Audio Device Class 框架已具雏形了,用了两三天时间,使用这个框架实战了一个基于新唐 M0 的最简单的 USB Audio 程序,可以作为 US ...

  3. 如何在Android平台上使用USB Audio设备

    http://blog.csdn.net/kevinx_xu/article/details/12951131 需求:USB Headset插上去后,声音要从本地CODEC切换到USB Headset ...

  4. 基于PCM2912a的USB声卡设计

    将近一年时间没有做过硬件了,感觉都不会用Altium Designer软件了.这次做这个USB 声卡有两个目的,其一是复习Altium Designer软件:其二是在业余时间找个事做做,打发一下自己的 ...

  5. 第六章Audio设备

    6.1 Audio设备介绍 USB协议制定时,为了方便不同设备的开发商基于USB进行设计,定义了不同的设备类来支持不同类型的设备.虽然在USB标准中定义了USB_DEVICE_CLASS_AUDIO- ...

  6. Linux usb子系统(一):子系统架构

    一.USB协议基础知识   前序:USB概念概述 USB1.0版本速度1.5Mbps(低速USB) USB1.1版本速度12Mbps(全速USB)  USB2.0版本速度480Mbps(高速USB). ...

  7. USB Type-C 接口有什么优点?

    USB Type-C 接口有什么优点? 提到USB Type-C接口(以下简称为USB-C),大家第一个能想到的是USB-C接口能正反插,用起来很舒服.了解更多的可能还支持USB-C接口速度更快, 达 ...

  8. win7中USB音箱没有声音解决的方法

    Win7装好后,原来在XP中工作正常的USB小音箱却不工作了,重装了声卡驱动还是一样,后来通过下面尝试最终好用了. 1.右键右下角喇叭button. 2.点击"播放设备". 3.设 ...

  9. USB (Universal Serial Bus)

    USB歷史簡介 USB規格演變 標準 USB 2.0 介面 實體層 訊號傳輸 傳輸速率 網路層 USB 通訊模型 Endpoints 傳輸型態 USB 資料連結 Transaction Frame P ...

随机推荐

  1. eclipse报错

    1.eclipse报错具体如下 Error occurred during the build. Errors running builder 'JavaScript Validator' on pr ...

  2. org.hibernate.MappingException:Unknown entity:java.util.ArrayList

    1.错误描述 [CQ] ERROR [http-apr-8888-exec-3] com.opensymphony.xwork2.util.logging.commons.CommonsLogger. ...

  3. 芝麻HTTP:Gerapy的安装

    Gerapy是一个Scrapy分布式管理模块,本节就来介绍一下它的安装方式. 1. 相关链接 GitHub:https://github.com/Gerapy 2. pip安装 这里推荐使用pip安装 ...

  4. 芝麻HTTP:在无GUI的CentOS上使用Selenium+Chrome

    各位小伙伴儿的采集日常是不是被JavaScript的各种点击事件折腾的欲仙欲死啊?好不容易找到个Selenium+Chrome可以解决问题! 但是另一个▄█▀█●的事实摆在面前,服务器都特么没有GUI ...

  5. angular路由操作

    在单页面应用程序中比如angular应用,我们需要根据url的变化(即:不同的请求),来分配不同的资源.根据请求的URL来决定执行哪个模块,这个过程叫路由,同时,我们需要设计路由规则. 下面给出一个简 ...

  6. CF384 div2 E. Vladik and cards

    题意 给你一个的排列,求一个满足条件的最长子序列 每种数字的差小于等于,并且每种数字之内是连续的 解法 首先单纯认为用肯定不行的 所以应该考虑二分答案(所求长度具有二分性) 再用dp判断是否可行,这个 ...

  7. box-sizing -- 盒模型

    项目开发中,在浏览同事的代码,发现他经常用一个属性--box-sizing,很好奇是什么,于是乎,上网查阅资料学了起来. 首先我们先复习一下盒模型的组成:一个div通常由 content(内容)+ma ...

  8. java实现组合问题

    刚才刚更新了排列问题,顺带把组合问题也发表一下 1.问题描述: 已知有m个球,从m个球中取n个球有多少种不同的取法. 2.输入示例: 请输入总球的个数和要取的球的个数 6 5 3.输出示例: 共有6种 ...

  9. [BZOJ1051] [HAOI2006] 受欢迎的牛 (强联通分量)

    Description 每一头牛的愿望就是变成一头最受欢迎的牛.现在有N头牛,给你M对整数(A,B),表示牛A认为牛B受欢迎. 这 种关系是具有传递性的,如果A认为B受欢迎,B认为C受欢迎,那么牛A也 ...

  10. 对java的Thread的理解

    最好不适用线程的子线程,直接调用线程,然后用rannable接口 然后如果要公用一个参数,就是公用资源的时候,一定要在run方法的前面加上synchronized 例子 猫和狗喝同一杯水(共用资源的问 ...