查看: 1211|回复: 3

[分享] 基于LPC54114的声音氛围灯

[复制链接]
  • TA的每日心情
    开心
    2024-3-26 15:16
  • 签到天数: 266 天

    [LV.8]以坛为家I

    3298

    主题

    6545

    帖子

    0

    管理员

    Rank: 9Rank: 9Rank: 9

    积分
    32004
    最后登录
    2024-4-9
    发表于 2022-8-16 21:36:09 | 显示全部楼层 |阅读模式
    基于LPC54114的声音氛围灯

    作者:不是茄子
    项目简介:
    本项目基于万利LPC54110开发板,通过DMIC子系统实现音频信号16ksps的拾取,并利用该信号进行FFT变换,将8kHz内频谱信息显示在8×8的WS2812面板上,通过SPI接口完成对WS2812数据通信,实现声音氛围灯。
    直接看视频介绍:

    万利LPC54110开发板板载LPC54114高性能双核MCU,板载数字麦克风芯片,TF卡以及丰富的外设接口。LPC54114内部带有数字麦克风子系统DMIC,能够直接实现PDM、I2S等数字音频方面的应用。本项目基于万利LPC54110开发板,通过DMIC子系统实现音频信号16ksps的拾取,并利用该信号进行FFT变换,将8kHz内频谱信息显示在8×8的WS2812面板上,实现声音氛围灯。限于时间和作者水平,该项目仅仅呈现出了该芯片的一小部分功能。

    系统原理:
    代码介绍: WS2812驱动

    WS2812不同于其它显示驱动芯片,其仅采用一个管脚,通过高低电平持续时间长短来区分一个code(0或1)。Code0时,高电平≈400ns,低电平≈800ns;Code1时,高电平≈800ns,低电平≈400ns;RESET时,低电平大于50μs。

    结合其数据特征,可以直接使用SPI中MOSI管脚,无数据时,MOSI一直输出低电平,等效于WS2812的RESET信号,需要指出的是,该RESET信号并不会清除显示,只是让各个WS2812重新进入数据采集状态。而在发送数据时,则一次性发送全部显示数据。由于SPI在时钟输出前MOSI会提前两个时钟周期上线,因此,SPI发送数据的第一个字节为0x00,显示数据从第二个字节开始填充。

    考虑使用3个bit数据来表示一个code,WS2812为GRB24bit颜色模式,因此,一个WS2812的颜色需要24codes = 72bits=9bytes的数据。利用共用体数据结构,按照大端在前规则,设置如下数据结构。
    1. union WS2812_pixelData //Stand for one pixel data for WS2812

    2. {

    3. unsigned int box; //4 byte

    4. struct

    5. {

    6. unsigned int pixelBit0:3;

    7. unsigned int pixelBit1:3;

    8. unsigned int pixelBit2:3;

    9. unsigned int pixelBit3:3;

    10. unsigned int pixelBit4:3;

    11. unsigned int pixelBit5:3;

    12. unsigned int pixelBit6:3;

    13. unsigned int pixelBit7:3;

    14. }pixelByteX; //Share space of box, 4 byte, but 24bit used

    15. unsigned char byteX[3]; //used for GRB

    16. }WSpData[3];
    复制代码
    根据数据结构开始进行底层显存masterTxData填充,发送的第一个字节为全0数据,后续字节按照应用层显存WS_masterTxDataX结合位段操作填充WS2812_CODE1(0x06,0b‘110)或者WS2812_CODE0(0x04,0b‘100)。
    1. masterTxData[0] = 0x00;

    2. for(i = 0;i < (TRANSFER_SIZE / 9);i++)

    3. {

    4. //Green pixel byte

    5. G_pixel = ((*(WS_masterTxDataX + i * 3 + 2)) > WS_brightness) ? (WS_brightness) : (*(WS_masterTxDataX + i * 3 + 2));

    6. WSpData[2].pixelByteX.pixelBit7 = ((0x01 << 7) & (G_pixel)) ? (WS2812_CODE1) : (WS2812_CODE0);

    7. WSpData[2].pixelByteX.pixelBit6 = ((0x01 << 6) & (G_pixel)) ? (WS2812_CODE1) : (WS2812_CODE0);

    8. WSpData[2].pixelByteX.pixelBit5 = ((0x01 << 5) & (G_pixel)) ? (WS2812_CODE1) : (WS2812_CODE0);

    9. WSpData[2].pixelByteX.pixelBit4 = ((0x01 << 4) & (G_pixel)) ? (WS2812_CODE1) : (WS2812_CODE0);

    10. WSpData[2].pixelByteX.pixelBit3 = ((0x01 << 3) & (G_pixel)) ? (WS2812_CODE1) : (WS2812_CODE0);

    11. WSpData[2].pixelByteX.pixelBit2 = ((0x01 << 2) & (G_pixel)) ? (WS2812_CODE1) : (WS2812_CODE0);

    12. WSpData[2].pixelByteX.pixelBit1 = ((0x01 << 1) & (G_pixel)) ? (WS2812_CODE1) : (WS2812_CODE0);

    13. WSpData[2].pixelByteX.pixelBit0 = ((0x01 << 0) & (G_pixel)) ? (WS2812_CODE1) : (WS2812_CODE0);

    14. //Red pixel byte

    15. R_pixel = ((*(WS_masterTxDataX + i * 3 + 1)) > WS_brightness) ? (WS_brightness) : (*(WS_masterTxDataX + i * 3 + 1));

    16. WSpData[1].pixelByteX.pixelBit7 = ((0x01 << 7) & (R_pixel)) ? (WS2812_CODE1) : (WS2812_CODE0);

    17. WSpData[1].pixelByteX.pixelBit6 = ((0x01 << 6) & (R_pixel)) ? (WS2812_CODE1) : (WS2812_CODE0);

    18. WSpData[1].pixelByteX.pixelBit5 = ((0x01 << 5) & (R_pixel)) ? (WS2812_CODE1) : (WS2812_CODE0);

    19. WSpData[1].pixelByteX.pixelBit4 = ((0x01 << 4) & (R_pixel)) ? (WS2812_CODE1) : (WS2812_CODE0);

    20. WSpData[1].pixelByteX.pixelBit3 = ((0x01 << 3) & (R_pixel)) ? (WS2812_CODE1) : (WS2812_CODE0);

    21. WSpData[1].pixelByteX.pixelBit2 = ((0x01 << 2) & (R_pixel)) ? (WS2812_CODE1) : (WS2812_CODE0);

    22. WSpData[1].pixelByteX.pixelBit1 = ((0x01 << 1) & (R_pixel)) ? (WS2812_CODE1) : (WS2812_CODE0);

    23. WSpData[1].pixelByteX.pixelBit0 = ((0x01 << 0) & (R_pixel)) ? (WS2812_CODE1) : (WS2812_CODE0);

    24. //Blue pixel byte

    25. B_pixel = ((*(WS_masterTxDataX + i * 3 + 0)) > WS_brightness) ? (WS_brightness) : (*(WS_masterTxDataX + i * 3 + 0));

    26. WSpData[0].pixelByteX.pixelBit7 = ((0x01 << 7) & (B_pixel)) ? (WS2812_CODE1) : (WS2812_CODE0);

    27. WSpData[0].pixelByteX.pixelBit6 = ((0x01 << 6) & (B_pixel)) ? (WS2812_CODE1) : (WS2812_CODE0);

    28. WSpData[0].pixelByteX.pixelBit5 = ((0x01 << 5) & (B_pixel)) ? (WS2812_CODE1) : (WS2812_CODE0);

    29. WSpData[0].pixelByteX.pixelBit4 = ((0x01 << 4) & (B_pixel)) ? (WS2812_CODE1) : (WS2812_CODE0);

    30. WSpData[0].pixelByteX.pixelBit3 = ((0x01 << 3) & (B_pixel)) ? (WS2812_CODE1) : (WS2812_CODE0);

    31. WSpData[0].pixelByteX.pixelBit2 = ((0x01 << 2) & (B_pixel)) ? (WS2812_CODE1) : (WS2812_CODE0);

    32. WSpData[0].pixelByteX.pixelBit1 = ((0x01 << 1) & (B_pixel)) ? (WS2812_CODE1) : (WS2812_CODE0);

    33. WSpData[0].pixelByteX.pixelBit0 = ((0x01 << 0) & (B_pixel)) ? (WS2812_CODE1) : (WS2812_CODE0);

    34. masterTxData[9 * i + 1] = WSpData[2].byteX[2];

    35. masterTxData[9 * i + 2] = WSpData[2].byteX[1];

    36. masterTxData[9 * i + 3] = WSpData[2].byteX[0];

    37. masterTxData[9 * i + 4] = WSpData[1].byteX[2];

    38. masterTxData[9 * i + 5] = WSpData[1].byteX[1];

    39. masterTxData[9 * i + 6] = WSpData[1].byteX[0];

    40. masterTxData[9 * i + 7] = WSpData[0].byteX[2];

    41. masterTxData[9 * i + 8] = WSpData[0].byteX[1];

    42. masterTxData[9 * i + 9] = WSpData[0].byteX[0];

    43. }

    44. /* Set up handle for spi master */

    45. SPI_MasterTransferCreateHandleDMA(WS2812_SPI_MASTER, &masterHandle, SPI_MasterUserCallback, NULL, &masterTxHandle,

    46. &masterRxHandle);

    47. /* Start master transfer */

    48. masterXfer.txData = (uint8_t *)&masterTxData;

    49. masterXfer.rxData = (uint8_t *)&masterRxData;

    50. masterXfer.dataSize = TRANSFER_SIZE * sizeof(masterTxData[0]);

    51. masterXfer.configFlags = kSPI_FrameAssert;

    52. if (kStatus_Success != SPI_MasterTransferDMA(WS2812_SPI_MASTER, &masterHandle, &masterXfer))

    53. {

    54. PRINTF("There is an error when start SPI_MasterTransferDMA \r\n ");

    55. }

    56. /* Wait until transfer completed */

    57. while (!isTransferCompleted)

    58. {

    59. }

    60. SysTick_DelayTicks(4); //ms
    复制代码
    DMIC子系统


    DMIC子系统时LPC54114的一个特色功能,能够实现PDM、PCM、I2S数据接口之间的数据直通,大幅降低了数据驱动开发的工作量。其初始化代码以及传输启动代码如下所示。
    1. void DMICx_Config(void)

    2. {

    3. dmic_channel_config_t dmic_channel_cfg;

    4. //////////DMIC Init//////////

    5. dmic_channel_cfg.divhfclk = kDMIC_PdmDiv1;

    6. dmic_channel_cfg.osr = 25U;

    7. dmic_channel_cfg.gainshft = 3U;

    8. dmic_channel_cfg.preac2coef = kDMIC_CompValueZero;

    9. dmic_channel_cfg.preac4coef = kDMIC_CompValueZero;

    10. dmic_channel_cfg.dc_cut_level = kDMIC_DcCut155;

    11. dmic_channel_cfg.post_dc_gain_reduce = 1;

    12. dmic_channel_cfg.saturate16bit = 1U;

    13. dmic_channel_cfg.sample_rate = kDMIC_PhyFullSpeed;

    14. #if defined(FSL_FEATURE_DMIC_CHANNEL_HAS_SIGNEXTEND) && (FSL_FEATURE_DMIC_CHANNEL_HAS_SIGNEXTEND)

    15. dmic_channel_cfg.enableSignExtend = true;

    16. #endif

    17. DMIC_Init(DMIC0);

    18. #if !(defined(FSL_FEATURE_DMIC_HAS_NO_IOCFG) && FSL_FEATURE_DMIC_HAS_NO_IOCFG)

    19. DMIC_SetIOCFG(DMIC0, kDMIC_PdmDual);

    20. #endif

    21. DMIC_Use2fs(DMIC0, true);

    22. DMIC_SetOperationMode(DMIC0, kDMIC_OperationModeDma);

    23. DMIC_ConfigChannel(DMIC0, kDMIC_Channel0, kDMIC_Left, &dmic_channel_cfg);

    24. DMIC_FifoChannel(DMIC0, kDMIC_Channel0, FIFO_DEPTH, true, true);

    25. DMIC_EnableChannelDma(DMIC0, APP_DMIC_CHANNEL, true);

    26. DMIC_ConfigChannel(DMIC0, APP_DMIC_CHANNEL, kDMIC_Left, &dmic_channel_cfg);

    27. DMIC_FifoChannel(DMIC0, APP_DMIC_CHANNEL, FIFO_DEPTH, true, true);

    28. DMIC_EnableChannnel(DMIC0, APP_DMIC_CHANNEL_ENABLE);

    29. PRINTF("Configure DMA\r\n");

    30. // DMA_Init(DMA0);

    31. DMA_EnableChannel(DMA0, APP_DMAREQ_CHANNEL);

    32. DMA_SetChannelPriority(DMA0, DMAREQ_DMIC0, kDMA_ChannelPriority1);

    33. /* Request dma channels from DMA manager. */

    34. DMA_CreateHandle(&g_dmicRxDmaHandle, DMA0, APP_DMAREQ_CHANNEL);

    35. /* Create DMIC DMA handle. */

    36. DMIC_TransferCreateHandleDMA(DMIC0, &g_dmicDmaHandle, DMICx_UserCallback, NULL, &g_dmicRxDmaHandle);

    37. }

    38. void DMICx_start(void)

    39. {

    40. receiveXfer.dataSize = 2 * BUFFER_LENGTH;

    41. receiveXfer.data = (uint16_t *)g_rxBuffer;//DmicState.Buffer;//(uint16_t *)testbuffer;//

    42. DMIC_TransferReceiveDMA(DMIC0, &g_dmicDmaHandle, &receiveXfer, kDMIC_Channel0);

    43. DMIC_EnableChannnel(DMIC0, DMIC_CHANEN_EN_CH0(1));

    44. }
    复制代码
    需要注意的是,由于DMIC和SPI均使用了DMA数据传输功能,因此,在初始化和操作时需要注意不要对DMA0重复初始化,另外,SPI传输完成后不能调用DMA_Deinit(WS2812_DMA),这样会关闭所有的DMA传输导致程序卡死。

    管脚配置与时钟配置

    该项目利用MCUXpresso IDE进行开发,该工具类似与STMCubeIDE,能够进行以GUI形式配置GPIO、时钟、外设等。配置时钟如下图:新建BOARD_BootClockPLL150M功能组,并配置相应的时钟。

    快速傅里叶变换(FFT)

    该部分代码直接搬运Arduino中的FFT例程,为了实现比较短的采样间隔,使用800kHz时钟频率,并降低采样点数为128点,虽然,频率精度变低,但是一个完整显示和处理周期时间相比于256点由45ms降低至26ms,能够较快响应外部声音变化。

    在FFT部分,对计算得到的频域功率谱进行分段求和、归一,得到WS2812显示面板每一列像素数量,之后在主程序中调用WS2812_ShowLineDMA(Red,Blue4);函数进行显示。

    小结:

    氛围灯一直时我想做的项目之一,然而,因为各种各样的借口一拖再拖。这次,实现了声音氛围灯的雏形,为后续继续开展相关创作奠定基础,树立信心。本次项目的感受:

    1、MCU领域日新月异,有各种各样强大功能的MCU陆续问世。身处此洪流之中,虽有官方例程加持,但核心还是ARM、RISC-V等内核原理和典型外设(DMA等)的工作原理、调试方法。
    2、学会如何使用别人的轮子。别人的“轮子”,特别是开源平台上的,代码规范,水平高,学习他们的代码要强于自己一点一点写,做出来的功能也要更丰富。
    3、为了扩展产品的应用,各个厂商制作了丰富的教程、例程、IDE等,加快了产品投放市场的速度。那么,嵌入式MCU的共性技术时什么呢?

    签到签到
    回复

    使用道具 举报

  • TA的每日心情
    开心
    2024-4-10 22:38
  • 签到天数: 1335 天

    [LV.10]以坛为家III

    88

    主题

    4292

    帖子

    12

    版主

    Rank: 7Rank: 7Rank: 7

    积分
    9049
    最后登录
    2024-4-13
    发表于 2022-8-19 14:19:12 | 显示全部楼层
    是不是可以开个帖子分析一下这个实现的硬件架构、软件架构。
    有感兴趣的吗?
    该会员没有填写今日想说内容.
    回复 支持 反对

    使用道具 举报

  • TA的每日心情
    开心
    2024-3-26 15:16
  • 签到天数: 266 天

    [LV.8]以坛为家I

    3298

    主题

    6545

    帖子

    0

    管理员

    Rank: 9Rank: 9Rank: 9

    积分
    32004
    最后登录
    2024-4-9
     楼主| 发表于 2022-8-19 14:47:00 | 显示全部楼层
    jobszheng5 发表于 2022-8-19 14:19
    是不是可以开个帖子分析一下这个实现的硬件架构、软件架构。
    有感兴趣的吗? ...

    已增加
    签到签到
    回复 支持 反对

    使用道具 举报

  • TA的每日心情
    擦汗
    2016-12-2 08:40
  • 签到天数: 3 天

    [LV.2]偶尔看看I

    97

    主题

    836

    帖子

    7

    版主

    Rank: 7Rank: 7Rank: 7

    积分
    3967
    最后登录
    2024-4-16
    发表于 2022-8-23 10:23:10 | 显示全部楼层
    我们内部有个小项目在run,类似的效果
    回复 支持 反对

    使用道具 举报

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

    本版积分规则

    关闭

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

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

    GMT+8, 2024-4-20 19:51 , Processed in 0.121041 second(s), 23 queries , MemCache On.

    Powered by Discuz! X3.4

    Copyright © 2001-2021, Tencent Cloud.

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