查看: 354|回复: 0

用 FRDM-MCXN236 的 FlexIO 模拟 I2S 实现音频输出

[复制链接]

该用户从未签到

745

主题

6406

帖子

0

超级版主

Rank: 8Rank: 8

积分
27244
最后登录
2026-3-31
发表于 2025-11-21 11:05:04 | 显示全部楼层 |阅读模式
本帖最后由 小恩GG 于 2025-11-21 11:04 编辑

FRDM-MCXN236 FlexIO 模拟 I2S 实现音频输出

概述
在嵌入式音频应用中,I2S 接口是连接音频 Codec 与微控制器的标准方案。NXP MCXN 系列芯片虽搭载 SAI 模块,但实际项目中可能面临 I2S 通道数量不足的问题,此时借助灵活可配置的 FlexIO 外设模拟 I2S 接口,是高效的功能扩展方案。
NXP 官网已提供 MCXA156 芯片通过 FlexIO 模拟 I2S 的应用笔记 AN14527 及配套源码 AN14527SW,本文在此基础上,补充 FRDM-MCXN236 开发板的完整实现案例。该开发板具备 3.5mm 耳机接口,且支持焊接 DA7212 音频 Codec(默认 DNP 状态,需用户自行焊接),硬件条件适配音频输出验证需求。
下文将详细拆解基于 MCXN236 芯片的 FlexIO 模拟 I2S 实现流程,为开发者提供可直接复用的技术参考。

核心概念:什么是 FlexIO

FlexIO 是一款高度灵活的可编程外设,核心由移位寄存器(Shifter)和定时器(Timer)构成。通过软件编程对这些基础资源进行组合配置,可灵活模拟 UART、SPI、I2C、I2S 等多种串 / 并行通信协议,无需依赖专用硬件模块。这一特性极大提升了微控制器外设资源的复用率,降低了对专用接口的依赖,为嵌入式项目开发提供了更高的灵活性。关于 FlexIO 的更多底层细节,可参考 AN14527 应用笔记的 3.1 节内容以及芯片的参考手册。

硬件准备
完成本实验需准备以下硬件设备及配件:
·   FRDM-MCXN236 开发板(Rev B)
·   Type-C USB 电缆
·   个人电脑
·   符合OMTP 标准的3.5mm耳机(需配合自行焊接的板载 3.5mm 接口使用)
·   DA7212 AudioCodec(需用户自行焊接至开发板)

Picture1.png

Picture2.png

引脚连接
FRDM-MCXN236 FlexIO 引脚需与板载音频相关引脚精准映射,以实现I2S 信号的正常传输,具体连接关系如下:

FlexIO 引脚
板上位置
I2S 信号
板上位置
FLEXI0_D2(P0_18)
J4 pin 5
TX_DATA(P2_8)
J1 pin 5
FLEXI0_D3(P0_19)
J9 pin 32
RX_DATA(P2_9)
J1 pin 15
FLEXI0_D4(P0_20)
J8 pin 8
FS (P3_17)
J1 pin 11
FLEXI0_D5(P0_21)
J8 pin 6
BCLK (P3_16)
J1 pin 1
FLEXI0_D6(P0_22)
J1 pin 16
MCLK (P3_6)
J1 pin 7

Picture3.jpg

软件实现(基于NXP MCUXpresso IDE
软件实现核心围绕 FlexIO 初始化、I2S 协议配置、MCLK 生成及中断传输展开,以下是完整代码解析。
1. 基础配置:宏定义与全局变量
该部分明确硬件引脚、时钟参数、音频格式及Codec 配置,为后续初始化提供基础参数,确保各模块参数一致。
  1. #include "pin_mux.h"
  2. #include "clock_config.h"
  3. #include "board.h"
  4. #include "fsl_flexio_i2s.h"
  5. #include "fsl_debug_console.h"
  6. #include "fsl_codec_common.h"
  7. #include "fsl_codec_adapter.h"
  8. #include "fsl_dialog7212.h"  
  9. #include "music.h"           // 包含 215Hz 正弦波音频数据

  10. /* 硬件实例定义 */
  11. #define DEMO_I2C         LPI2C2                  // Codec 控制 I2C 实例
  12. #define DEMO_FLEXIO_BASE FLEXIO0                 // 模拟 I2S 的 FlexIO 实例

  13. /* 时钟与引脚配置 */
  14. #define DEMO_FLEXIO_CLK_FREQ CLOCK_GetFlexioClkFreq() // 获取 FlexIO 时钟频率
  15. #define BCLK_PIN       (5U)    // FlexIO_D5 (P0_21) - I2S 位时钟
  16. #define FRAME_SYNC_PIN (4U)    // FlexIO_D4 (P0_20) - I2S 帧同步
  17. #define TX_DATA_PIN    (2U)    // FlexIO_D2 (P0_18) - I2S 发送数据
  18. #define RX_DATA_PIN    (3U)    // FlexIO_D3 (P0_19) - I2S 接收数据(预留)
  19. #define MCLK_PIN           (6U)    // FlexIO_D6 (P0_22) - I2S 主时钟

  20. /* 音频与缓冲配置 */
  21. #define OVER_SAMPLE_RATE (384) // MCLK 倍频系数(MCLK = 采样率 × 倍频系数)
  22. #define BUFFER_SIZE      (256) // 单块音频缓冲大小(字节)
  23. #define BUFFER_NUM       (4)   // 乒乓缓冲块数量
  24. #define DEMO_AUDIO_SAMPLE_RATE (kFLEXIO_I2S_SampleRate16KHz) // 采样率:16kHz
  25. #define DEMO_AUDIO_DATA_CHANNEL (2U) // 通道数:立体声
  26. #define DEMO_AUDIO_BIT_WIDTH (kFLEXIO_I2S_WordWidth16bits) // 位宽:16位
  27. #define DEMO_CODEC_VOLUME 60U  // 音量(0-100,避免过载失真)

  28. /* MCLK 频率计算(适配不同硬件场景) */
  29. #if (defined FSL_FEATURE_SAI_HAS_MCLKDIV_REGISTER && FSL_FEATURE_SAI_HAS_MCLKDIV_REGISTER) || \
  30.     (defined FSL_FEATURE_PCC_HAS_SAI_DIVIDER && FSL_FEATURE_PCC_HAS_SAI_DIVIDER)
  31. #define DEMO_AUDIO_MASTER_CLOCK OVER_SAMPLE_RATE *DEMO_AUDIO_SAMPLE_RATE // 16kHz×384=6.144MHz
  32. #else
  33. #define DEMO_AUDIO_MASTER_CLOCK DEMO_SAI_CLK_FREQ
  34. #endif

  35. /* DA7212 Codec 配置(与硬件匹配) */
  36. da7212_config_t da7212Config = {
  37.     .i2cConfig    = {.codecI2CInstance = BOARD_CODEC_I2C_INSTANCE, .codecI2CSourceClock = 12000000},
  38.     .dacSource    = kDA7212_DACSourceInputStream,
  39.     .slaveAddress = DA7212_ADDRESS,
  40.     .protocol     = kDA7212_BusI2S,
  41.     .format       = {.mclk_HZ = 12288000, .sampleRate = DEMO_AUDIO_SAMPLE_RATE, .bitWidth = DEMO_AUDIO_BIT_WIDTH},
  42.     .sysClkSource = kDA7212_SysClkSourceMCLK,
  43.     .isMaster     = false, // Codec 为从机,由 FlexIO 提供时钟
  44. };

  45. /* 通用 Codec 配置(适配驱动框架) */
  46. codec_config_t boardCodecConfig = {.codecDevType = kCODEC_DA7212, .codecDevConfig = &da7212Config};

  47. /* 传输相关全局变量 */
  48. flexio_i2s_handle_t txHandle = {0}; // 发送句柄(音频输出)
  49. flexio_i2s_handle_t rxHandle = {0}; // 接收句柄(预留)
  50. static volatile bool isTxFinished = false; // 发送完成标志
  51. static volatile uint32_t beginCount = 0, sendCount = 0; // 发送计数
  52. static volatile uint8_t emptyBlock = 0; // 空缓冲块数
  53. static volatile bool isZeroBuffer = true; // 零数据缓冲标志(静音切换)
  54. FLEXIO_I2S_Type s_base; // FlexIO I2S 核心结构体
  55. codec_handle_t codecHandle; // Codec 操作句柄
复制代码

2. 中断回调函数:乒乓缓冲管理
采用中断驱动的非阻塞机制,避免占用 CPU 资源,同时通过乒乓缓冲管理,确保音频输出连续性,解决数据生成与传输的时序冲突。

  1. /**
  2. * @brief 发送中断回调函数(音频数据发送完成后触发)
  3. * 管理缓冲状态,更新发送计数,标记播放完成
  4. */
  5. static void txCallback(FLEXIO_I2S_Type *i2sBase, flexio_i2s_handle_t *handle, status_t status, void *userData)
  6. {
  7.     // 若存在空缓冲且当前不是零数据缓冲,更新空缓冲计数与发送计数
  8.     if ((emptyBlock < BUFFER_NUM) && (!isZeroBuffer))
  9.     {
  10.         emptyBlock++;  // 空缓冲块+1(表示可向该缓冲填充新音频数据)
  11.         sendCount++;   // 已发送块数+1(记录播放进度)
  12.     }

  13.     // 若当前使用零数据缓冲(静音),切换为非零缓冲(准备播放音频)
  14.     if (isZeroBuffer)
  15.     {
  16.         isZeroBuffer = false;
  17.     }

  18.     // 若已发送块数达到总发送块数,标记发送完成(播放结束)
  19.     if (sendCount == beginCount)
  20.     {
  21.         isTxFinished = true;
  22. }
  23. }

  24. /**
  25. * @brief 接收中断回调函数(预留,本实验未使用 RX 通道)
  26. * @note  若后续需要音频输入(如麦克风),可基于此函数扩展
  27. */
  28. static void rxCallback(FLEXIO_I2S_Type *i2sBase, flexio_i2s_handle_t *handle, status_t status, void *userData)
  29. {
  30.     // 若存在空缓冲,更新缓冲计数与接收计数(预留逻辑)
  31.     if (emptyBlock > 0)
  32.     {
  33.         emptyBlock--;    // 空缓冲块-1(表示缓冲已被接收数据填充)
  34.         receiveCount++;  // 已接收块数+1(记录接收进度)
  35.     }

  36.     // 若已接收块数达到总发送块数,标记接收完成(预留逻辑)
  37.     if (receiveCount == beginCount)
  38.     {
  39.         isRxFinished = true;
  40. }
  41. }
复制代码

3. 核心初始化:FlexIO I2S 配置
该部分是核心逻辑,分为 FlexIO 核心初始化” MCLK 初始化”,分别配置 I2S 协议时序(移位寄存器 + 定时器)和 Codec 工作时钟,确保符合 I2S 协议要求。
3.1 FlexIO I2S 核心初始化(driver fsl_flexio_i2s 中)
配置移位寄存器(数据收发)和定时器(BCLK/FS时钟),完全遵循 I2S 协议时序,支持主 / 从模式切换。
  1. void FLEXIO_I2S_Init(FLEXIO_I2S_Type *base, const flexio_i2s_config_t *config)
  2. {
  3.     assert((base != NULL) && (config != NULL));

  4.     flexio_shifter_config_t shifterConfig = {0};
  5.     flexio_timer_config_t timerConfig     = {0};

  6. #if !(defined(FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL) && FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL)
  7.     /* Ungate flexio clock. */
  8.     CLOCK_EnableClock(s_flexioClocks[FLEXIO_I2S_GetInstance(base)]);
  9. #endif /* FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL */

  10.     /* reset Flexio */
  11.     FLEXIO_Reset(base->flexioBase);

  12.     /* Set shifter for I2S Tx data */
  13.     shifterConfig.timerSelect   = base->bclkTimerIndex;
  14.     shifterConfig.pinSelect     = base->txPinIndex;
  15.     shifterConfig.timerPolarity = config->txTimerPolarity;
  16.     shifterConfig.pinConfig     = kFLEXIO_PinConfigOutput;
  17.     shifterConfig.pinPolarity   = config->txPinPolarity;
  18.     shifterConfig.shifterMode   = kFLEXIO_ShifterModeTransmit;
  19.     shifterConfig.inputSource   = kFLEXIO_ShifterInputFromPin;
  20.     shifterConfig.shifterStop   = kFLEXIO_ShifterStopBitDisable;
  21.     if (config->masterSlave == kFLEXIO_I2S_Master)
  22.     {
  23.         shifterConfig.shifterStart = kFLEXIO_ShifterStartBitDisabledLoadDataOnShift;
  24.     }
  25.     else
  26.     {
  27.         shifterConfig.shifterStart = kFLEXIO_ShifterStartBitDisabledLoadDataOnEnable;
  28.     }

  29.     FLEXIO_SetShifterConfig(base->flexioBase, base->txShifterIndex, &shifterConfig);

  30.     /* Set shifter for I2S Rx Data */
  31.     shifterConfig.timerSelect   = base->bclkTimerIndex;
  32.     shifterConfig.pinSelect     = base->rxPinIndex;
  33.     shifterConfig.timerPolarity = config->rxTimerPolarity;
  34.     shifterConfig.pinConfig     = kFLEXIO_PinConfigOutputDisabled;
  35.     shifterConfig.pinPolarity   = config->rxPinPolarity;
  36.     shifterConfig.shifterMode   = kFLEXIO_ShifterModeReceive;
  37.     shifterConfig.inputSource   = kFLEXIO_ShifterInputFromPin;
  38.     shifterConfig.shifterStop   = kFLEXIO_ShifterStopBitDisable;
  39.     shifterConfig.shifterStart  = kFLEXIO_ShifterStartBitDisabledLoadDataOnEnable;

  40.     FLEXIO_SetShifterConfig(base->flexioBase, base->rxShifterIndex, &shifterConfig);

  41.     /* Set Timer to I2S frame sync */
  42.     if (config->masterSlave == kFLEXIO_I2S_Master)
  43.     {
  44.         timerConfig.triggerSelect   = FLEXIO_TIMER_TRIGGER_SEL_PININPUT(base->txPinIndex);
  45.         timerConfig.triggerPolarity = kFLEXIO_TimerTriggerPolarityActiveHigh;
  46.         timerConfig.triggerSource   = kFLEXIO_TimerTriggerSourceExternal;
  47.         timerConfig.pinConfig       = kFLEXIO_PinConfigOutput;
  48.         timerConfig.pinSelect       = base->fsPinIndex;
  49.         timerConfig.pinPolarity     = config->fsPinPolarity;
  50.         timerConfig.timerMode       = kFLEXIO_TimerModeSingle16Bit;
  51.         timerConfig.timerOutput     = kFLEXIO_TimerOutputOneNotAffectedByReset;
  52.         timerConfig.timerDecrement  = kFLEXIO_TimerDecSrcOnFlexIOClockShiftTimerOutput;
  53.         timerConfig.timerReset      = kFLEXIO_TimerResetNever;
  54.         timerConfig.timerDisable    = kFLEXIO_TimerDisableNever;
  55.         timerConfig.timerEnable     = kFLEXIO_TimerEnableOnPrevTimerEnable;
  56.         timerConfig.timerStart      = kFLEXIO_TimerStartBitDisabled;
  57.         timerConfig.timerStop       = kFLEXIO_TimerStopBitDisabled;
  58.     }
  59.     else
  60.     {
  61.         timerConfig.triggerSelect   = FLEXIO_TIMER_TRIGGER_SEL_PININPUT(base->bclkPinIndex);
  62.         timerConfig.triggerPolarity = kFLEXIO_TimerTriggerPolarityActiveHigh;
  63.         timerConfig.triggerSource   = kFLEXIO_TimerTriggerSourceInternal;
  64.         timerConfig.pinConfig       = kFLEXIO_PinConfigOutputDisabled;
  65.         timerConfig.pinSelect       = base->fsPinIndex;
  66.         timerConfig.pinPolarity     = config->fsPinPolarity;
  67.         timerConfig.timerMode       = kFLEXIO_TimerModeSingle16Bit;
  68.         timerConfig.timerOutput     = kFLEXIO_TimerOutputOneNotAffectedByReset;
  69.         timerConfig.timerDecrement  = kFLEXIO_TimerDecSrcOnTriggerInputShiftTriggerInput;
  70.         timerConfig.timerReset      = kFLEXIO_TimerResetNever;
  71.         timerConfig.timerDisable    = kFLEXIO_TimerDisableOnTimerCompare;
  72.         timerConfig.timerEnable     = kFLEXIO_TimerEnableOnPinRisingEdge;
  73.         timerConfig.timerStart      = kFLEXIO_TimerStartBitDisabled;
  74.         timerConfig.timerStop       = kFLEXIO_TimerStopBitDisabled;
  75.     }
  76.     FLEXIO_SetTimerConfig(base->flexioBase, base->fsTimerIndex, &timerConfig);

  77.     /* Set Timer to I2S bit clock */
  78.     if (config->masterSlave == kFLEXIO_I2S_Master)
  79.     {
  80.         timerConfig.triggerSelect   = FLEXIO_TIMER_TRIGGER_SEL_SHIFTnSTAT(base->txShifterIndex);
  81.         timerConfig.triggerPolarity = kFLEXIO_TimerTriggerPolarityActiveLow;
  82.         timerConfig.triggerSource   = kFLEXIO_TimerTriggerSourceInternal;
  83.         timerConfig.pinSelect       = base->bclkPinIndex;
  84.         timerConfig.pinConfig       = kFLEXIO_PinConfigOutput;
  85.         timerConfig.pinPolarity     = config->bclkPinPolarity;
  86.         timerConfig.timerMode       = kFLEXIO_TimerModeDual8BitBaudBit;
  87.         timerConfig.timerOutput     = kFLEXIO_TimerOutputOneNotAffectedByReset;
  88.         timerConfig.timerDecrement  = kFLEXIO_TimerDecSrcOnFlexIOClockShiftTimerOutput;
  89.         timerConfig.timerReset      = kFLEXIO_TimerResetNever;
  90.         timerConfig.timerDisable    = kFLEXIO_TimerDisableNever;
  91.         timerConfig.timerEnable     = kFLEXIO_TimerEnableOnTriggerHigh;
  92.         timerConfig.timerStart      = kFLEXIO_TimerStartBitEnabled;
  93.         timerConfig.timerStop       = kFLEXIO_TimerStopBitDisabled;
  94.     }
  95.     else
  96.     {
  97.         timerConfig.triggerSelect   = FLEXIO_TIMER_TRIGGER_SEL_TIMn(base->fsTimerIndex);
  98.         timerConfig.triggerPolarity = kFLEXIO_TimerTriggerPolarityActiveHigh;
  99.         timerConfig.triggerSource   = kFLEXIO_TimerTriggerSourceInternal;
  100.         timerConfig.pinSelect       = base->bclkPinIndex;
  101.         timerConfig.pinConfig       = kFLEXIO_PinConfigOutputDisabled;
  102.         timerConfig.pinPolarity     = config->bclkPinPolarity;
  103.         timerConfig.timerMode       = kFLEXIO_TimerModeSingle16Bit;
  104.         timerConfig.timerOutput     = kFLEXIO_TimerOutputOneNotAffectedByReset;
  105.         timerConfig.timerDecrement  = kFLEXIO_TimerDecSrcOnPinInputShiftPinInput;
  106.         timerConfig.timerReset      = kFLEXIO_TimerResetNever;
  107.         timerConfig.timerDisable    = kFLEXIO_TimerDisableOnTimerCompareTriggerLow;
  108.         timerConfig.timerEnable     = kFLEXIO_TimerEnableOnPinRisingEdgeTriggerHigh;
  109.         timerConfig.timerStart      = kFLEXIO_TimerStartBitDisabled;
  110.         timerConfig.timerStop       = kFLEXIO_TimerStopBitDisabled;
  111.     }
  112.     FLEXIO_SetTimerConfig(base->flexioBase, base->bclkTimerIndex, &timerConfig);

  113.     /* If enable flexio I2S */
  114.     if (config->enableI2S)
  115.     {
  116.         base->flexioBase->CTRL |= FLEXIO_CTRL_FLEXEN_MASK;
  117.     }
  118.     else
  119.     {
  120.         base->flexioBase->CTRL &= ~FLEXIO_CTRL_FLEXEN_MASK;
  121.     }
  122. }
复制代码

3.2 MCLK 初始化(Codec 工作时钟)
I2S 协议中 MCLK Codec 提供稳定工作时钟,通过 FlexIO 定时器生成,确保与音频采样时序严格同步。

  1. /**
  2. * @brief FlexIO I2S MCLK 初始化函数(生成 Codec 所需的主时钟)
  3. * @note  仅在 FlexIO 作为 I2S 主机时生效,通过定时器生成稳定 MCLK
  4. */
  5. void FLEXIO_I2S_Init_FOR_MCLK(FLEXIO_I2S_Type *base, const flexio_i2s_config_t *config)
  6. {
  7.     flexio_timer_config_t timerConfig     = {0}; // MCLK 定时器配置结构体

  8.     /* 仅主机模式需要生成 MCLK(从机由外部提供 MCLK) */
  9.     if (config->masterSlave == kFLEXIO_I2S_Master)
  10.     {
  11.         // 触发源:发送移位寄存器状态(确保 MCLK 与音频发送时序同步)
  12.         timerConfig.triggerSelect   = FLEXIO_TIMER_TRIGGER_SEL_SHIFTnSTAT(base->txShifterIndex);
  13.         timerConfig.triggerPolarity = kFLEXIO_TimerTriggerPolarityActiveLow; // 触发极性:低电平有效
  14.         timerConfig.triggerSource   = kFLEXIO_TimerTriggerSourceInternal;    // 触发源:内部(FlexIO 资源)
  15.         timerConfig.pinSelect       = MCLK_PIN;                              // 引脚:MCLK 引脚
  16.         timerConfig.pinConfig       = kFLEXIO_PinConfigOutput;               // 引脚模式:输出
  17.         timerConfig.pinPolarity     = kFLEXIO_PinActiveHigh;                 // 引脚极性:高电平有效
  18.         timerConfig.timerMode       = kFLEXIO_TimerModeDual8BitBaudBit;      // 定时器模式:双8位波特率(生成时钟)
  19.         timerConfig.timerOutput     = kFLEXIO_TimerOutputOneNotAffectedByReset; // 复位不影响输出(时钟连续)
  20.         timerConfig.timerDecrement  = kFLEXIO_TimerDecSrcOnFlexIOClockShiftTimerOutput; // 递减源:FlexIO 时钟
  21.         timerConfig.timerReset      = kFLEXIO_TimerResetNever;               // 复位:从不复位(持续生成时钟)
  22.         timerConfig.timerDisable    = kFLEXIO_TimerDisableNever;             // 禁用:从不禁用(持续生成时钟)
  23.         timerConfig.timerEnable     = kFLEXIO_TimerEnableOnTriggerHigh;      // 使能:触发信号高电平时启用
  24.         timerConfig.timerStart      = kFLEXIO_TimerStartBitEnabled;          // 启动:允许启动(使能后立即生成)
  25.         timerConfig.timerStop       = kFLEXIO_TimerStopBitDisabled;          // 停止:不允许停止(持续生成)
  26.     }

  27.     // 应用 MCLK 定时器配置(绑定到 FlexIO 定时器)
  28.     FLEXIO_SetTimerConfig(base->flexioBase, MCLK_PIN, &timerConfig);

  29.     // 设置 MCLK 频率(通过定时器比较值调整,此处配置为 ~12MHz,匹配 da7212Config 中的 mclk_HZ)
  30.     base->flexioBase->TIMCMP[MCLK_PIN] = FLEXIO_TIMCMP_CMP(2 - 1U);/*--10-12M...*/
  31. }



  32.     FLEXIO_SetTimerConfig(base->flexioBase, MCLK_PIN, &timerConfig);
  33.     base->flexioBase->TIMCMP[MCLK_PIN] = FLEXIO_TIMCMP_CMP(2 - 1U); // 配置 MCLK 频率(~12MHz)
  34. }
复制代码

4. 主函数:流程整合与启动
主函数按 “时钟初始化→板级初始化→FlexIO/I2S 配置→Codec 初始化→中断传输启动” 的顺序整合所有模块,确保硬件就绪后启动音频输出。
  1. int main(void)
  2. {
  3.     /* 局部变量定义 */
  4.     flexio_i2s_config_t config;
  5.     flexio_i2s_format_t format;
  6.     flexio_i2s_transfer_t txXfer, rxXfer;

  7.     /*****************************************************************************
  8.      * 1. 时钟初始化(确保串口、I2C、FlexIO 时钟稳定)
  9.      ****************************************************************************/
  10.     // 调试串口时钟配置
  11.     CLOCK_SetClkDiv(kCLOCK_DivFlexcom4Clk, 1u);
  12.     CLOCK_AttachClk(BOARD_DEBUG_UART_CLK_ATTACH);

  13.     // I2C 时钟配置(Codec 通信)
  14.     CLOCK_SetClkDiv(kCLOCK_DivFlexcom2Clk, 1u);
  15.     CLOCK_AttachClk(kFRO12M_to_FLEXCOMM2);

  16.     // 系统时钟与 PLL1 配置(生成 FlexIO 所需 49.152MHz 时钟)
  17.     CLOCK_EnableClock(kCLOCK_Scg);
  18.     CLOCK_SetupFROHFClocking(48000000U);
  19.     const pll_setup_t pll1Setup = {
  20.         .pllctrl = SCG_SPLLCTRL_SOURCE(1U) | SCG_SPLLCTRL_SELI(3U) | SCG_SPLLCTRL_SELP(1U),
  21.         .pllndiv = SCG_SPLLNDIV_NDIV(25U),
  22.         .pllpdiv = SCG_SPLLPDIV_PDIV(5U),
  23.         .pllmdiv = SCG_SPLLMDIV_MDIV(256U),
  24.         .pllRate = 49152000U
  25.     };
  26.     CLOCK_SetPLL1Freq(&pll1Setup);
  27.     CLOCK_SetClkDiv(kCLOCK_DivPLL1Clk0, 1U);

  28.     // FlexIO 时钟配置(绑定 PLL1 时钟)
  29.     CLOCK_SetClkDiv(kCLOCK_DivFlexioClk, 1u);
  30.     CLOCK_AttachClk(kPLL1_CLK0_to_FLEXIO);

  31.     /*****************************************************************************
  32.      * 2. 板级初始化(引脚、时钟、串口就绪)
  33.      ****************************************************************************/
  34.     BOARD_InitPins();
  35.     BOARD_InitBootClocks();
  36.     BOARD_InitDebugConsole();
  37.     PRINTF("FLEXIO_I2S interrupt example started!\n\r");

  38.     /*****************************************************************************
  39.      * 3. FlexIO I2S 硬件资源映射(引脚、移位寄存器、定时器绑定)
  40.      ****************************************************************************/
  41.     s_base.bclkPinIndex   = BCLK_PIN;
  42.     s_base.fsPinIndex     = FRAME_SYNC_PIN;
  43.     s_base.txPinIndex     = TX_DATA_PIN;
  44.     s_base.rxPinIndex     = RX_DATA_PIN;
  45.     s_base.txShifterIndex = 0;
  46.     s_base.rxShifterIndex = 2;
  47.     s_base.bclkTimerIndex = 0;
  48.     s_base.fsTimerIndex   = 1;
  49.     s_base.flexioBase     = DEMO_FLEXIO_BASE;

  50.     /*****************************************************************************
  51.      * 4. FlexIO I2S 协议配置(主从模式、功能启用)
  52.      ****************************************************************************/
  53.     FLEXIO_I2S_GetDefaultConfig(&config);
  54.     config.masterSlave = kFLEXIO_I2S_Master; // FlexIO 作为 I2S 主机
  55.     config.enableI2S = true; // 启用 I2S 功能
  56.     FLEXIO_I2S_Init(&s_base, &config); // 初始化 I2S 核心
  57.     FLEXIO_I2S_Init_FOR_MCLK(&s_base, &config); // 初始化 MCLK

  58.     /*****************************************************************************
  59.      * 5. 音频格式配置(与 Codec 保持一致)
  60.      ****************************************************************************/
  61.     format.bitWidth      = DEMO_AUDIO_BIT_WIDTH;
  62.     format.sampleRate_Hz = DEMO_AUDIO_SAMPLE_RATE;

  63.     /*****************************************************************************
  64.      * 6. Codec 初始化(配置 DA7212,设置音量)
  65.      ****************************************************************************/
  66.     if (CODEC_Init(&codecHandle, &boardCodecConfig) != kStatus_Success)
  67.     {
  68.         assert(false); // 初始化失败,检查 I2C 或 Codec 焊接
  69.     }
  70.     if (CODEC_SetVolume(&codecHandle, kCODEC_PlayChannelHeadphoneLeft | kCODEC_PlayChannelHeadphoneRight,
  71.                         DEMO_CODEC_VOLUME) != kStatus_Success)
  72.     {
  73.         assert(false); // 音量设置失败
  74.     }

  75.     /*****************************************************************************
  76.      * 7. 中断传输初始化(创建句柄,绑定回调)
  77.      ****************************************************************************/
  78.     FLEXIO_I2S_TransferTxCreateHandle(&s_base, &txHandle, txCallback, NULL);
  79.     FLEXIO_I2S_TransferRxCreateHandle(&s_base, &rxHandle, rxCallback, NULL);
  80.     FLEXIO_I2S_TransferSetFormat(&s_base, &txHandle, &format, DEMO_FLEXIO_CLK_FREQ);
  81.     FLEXIO_I2S_TransferSetFormat(&s_base, &rxHandle, &format, DEMO_FLEXIO_CLK_FREQ);

  82.     /*****************************************************************************
  83.      * 8. 启动音频非阻塞发送(中断驱动)
  84.      ****************************************************************************/
  85.     txXfer.dataSize = sizeof(music);
  86.     txXfer.data     = music;
  87.     FLEXIO_I2S_TransferSendNonBlocking(&s_base, &txHandle, &txXfer);

  88.     PRINTF("\n\r FLEXIO_I2S interrupt example finished!\n\r ");

  89.     /*****************************************************************************
  90.      * 9. 主循环(音频传输由中断驱动,可扩展循环播放逻辑)
  91.      ****************************************************************************/
  92.     while(1)
  93.     {
  94.         // 可选:循环播放
  95.         // if (isTxFinished)
  96.         // {
  97.         //     isTxFinished = false;
  98.         //     sendCount = 0;
  99.         //     FLEXIO_I2S_TransferSendNonBlocking(&s_base, &txHandle, &txXfer);
  100.         // }
  101.     }
  102. }
复制代码

测试步骤
1. 将USB 电缆连接到 PC和开发板的MCU-Link USB 端口
2.  打开串口终端,配置参数:波特率 115200、数据位 8、校验位无、停止位 1
3.  将程序下载到开发板
4.  将耳机插入板上的耳机插孔(J14)
5.  按下复位按钮或启动调试器开始运行程序

实验结果
当程序正常运行时,耳机将输出清晰的 215Hz 正弦波声音,表明 FlexIO 模拟的 I2S 接口工作稳定,数据传输时序准确,音频 Codec 与微控制器通信正常。

总结
本文详细展示了基于FRDM-MCXN236 开发板的FlexIO 模拟 I2S 接口实现方案,通过中断驱动的乒乓缓冲机制,实现了音频数据传输。该方案充分利用FlexIO 外设的灵活性,为I2S 通道不足或无专用I2S 硬件的 MCU 提供了可靠的音频解决方案。
此外,FlexIO的配置思路可复用至其他通信协议模拟(如SPII2C)。实际应用中,开发者可根据需求调整音频采样率、位宽、缓冲区大小及 MCLK 倍频系数等参数,适配不同规格的音频 Codec 与应用场景。使用 FlexIO 模块模拟 I2S 接口时,需要注意两个注意事项:
1. 由于 FlexIO 存在同步延迟,在其模拟 I2S 主设备时,I2S BCLK 最大时钟频率为 FlexIO 时钟频率的四分之一;而当模拟 I2S 从设备时,I2S BCLK 最大时钟频率则为 FlexIO 时钟频率的六分之一。
2. FlexIO 是一个功能强大,非常灵活的模块,除了本文给出的 Timer SHIFTER 的配置外,读者也可以利用其它配置模拟出I2S 接口。

参考资料
• MCX N23x Reference Manual (Rev. 4, 03/2025) (document MCXN23XRM)
• Emulating I2S Bus with the FlexIO on MCXA156 (document AN14527)
• DA7212 Data Sheet
• FRDM-MCXN236 Board User Manual (document UM12041)





回复

使用道具 举报

您需要登录后才可以回帖 注册/登录

本版积分规则

关闭

站长推荐上一条 /3 下一条

Archiver|手机版|小黑屋|恩智浦技术社区

GMT+8, 2026-4-2 03:17 , Processed in 0.083837 second(s), 22 queries , Redis On.

Powered by Discuz! X3.4

Copyright © 2001-2024, Tencent Cloud.

快速回复 返回顶部 返回列表