描述
<p style="line-height:1.8;"> </p>
<h3 style="line-height:1.8;">* 1、项目功能介绍</h3>
<hr>
<p>这次的项目是一个环境氛围灯,能够实现根据环境声音变化的灯效。</p>
<p style="line-height:1.8;"> </p>
<h3 style="line-height:1.8;">*2、项目属性</h3>
<hr>
<p>原创,首次公开项目</p>
<p style="line-height:1.8;"> </p>
<h3 style="line-height:1.8;">* 3、开源协议</h3>
<hr>
<p>GPL 3.0</p>
<p style="line-height:1.8;"> </p>
<p style="line-height:1.8;"><strong><span style="color:#0093e6;">请在竞赛阶段填写 ↓</span></strong></p>
<p style="line-height:1.8;"> </p>
<h3 style="line-height:1.8;">*4、硬件部分</h3>
<hr>
<p>硬件上由2部分组成:灯板和底座部分。灯板内置了20个WS2812 LED,用于发光显示;底座则是Ch571主控和声音传感器。</p>
<p style="line-height:1.8;">灯板两端预留了接口,如果有需要可以将多个灯板串联在一起。</p>
<p style="line-height:1.8;"><img src="//image.lceda.cn/pullimage/82iC0VaxmzBgSCB8S5RssfDexMif6v72W2jn4gCn.png" alt="82iC0VaxmzBgSCB8S5RssfDexMif6v72W2jn4gCn.png"></p>
<p>PCB 比较长,无法使用免费打样:</p>
<p style="line-height:1.8;"><img src="//image.lceda.cn/pullimage/LvzBYcppeXNdbOfJ74QKSGFGXgjMYbkUei07lTn6.png" alt="LvzBYcppeXNdbOfJ74QKSGFGXgjMYbkUei07lTn6.png"></p>
<p style="line-height:1.8;"> </p>
<p>底板部分为了简化设计直接选择淘宝购买 Ch571F 开发板作为主控,价格便宜便于使用。</p>
<p style="line-height:1.8;"> </p>
<p style="line-height:1.8;"><img src="//image.lceda.cn/pullimage/ZKpvvoJ9W8x3uqu143SCY8rocu5XEh2RUEr5f1yC.png" alt="ZKpvvoJ9W8x3uqu143SCY8rocu5XEh2RUEr5f1yC.png"></p>
<p>底板主要部分有一个 USB TypeB 的接口用于供电(实际上 Ch571F 上带有的TypeC也能够提供供电,测试下来发现WS2812 LED 功耗并没有想象中那么高,500ma 即可满足条件);此外,还有一个电流消耗电路,通过GPIO控制SS8050三极管拉一个较大的电流出来,这样在使用充电宝供电时,可以维持输出不会因为电流较小自动断电;麦克风使用的是来自DFRobot的 Fermion全向MEMS麦克风模块,它是一个模拟输出的数字麦克风,非常灵敏。</p>
<p><img src="//image.lceda.cn/pullimage/WcPgUNyNGScdfJWxKmTIm85c8adfF5cuiB1CUzEa.png" alt="WcPgUNyNGScdfJWxKmTIm85c8adfF5cuiB1CUzEa.png"></p>
<p> PCB是六边形造型独特,中间的洞可以穿过40mm 的塑料柱子,实现灯光柔化。边长48mm的六边形应该是能够满足PCB免费打样条件下的最大边长,有兴趣的朋友可以尝试证明一下。</p>
<p><img src="//image.lceda.cn/pullimage/T4uqBiBBtZAMD7jeStfvmV6RFMJPfWnNZYp1Z36F.png" alt="T4uqBiBBtZAMD7jeStfvmV6RFMJPfWnNZYp1Z36F.png"></p>
<p> </p>
<p> </p>
<h3 style="line-height:1.8;">*5、软件部分</h3>
<hr>
<p>软件使用 MSR2 开发,这个IDE 启动速度快,使用简单手上迅速,也是Ch57X 官方提供的开发环境,使用起来也无需担心收费版权的问题。</p>
<p>Ch571支持 USB 烧写,按住板子上的Boot按钮插入USB 即可进入下载模式,使用软件即可下载。</p>
<p> </p>
<p><code>#include "CH57x_common.h"</code><br><code>#include "ws2812_spi.h"</code></p>
<p><code>/* ADC采样参数 */</code><br><code>#define MIC_ADC_CHANNEL 0 // PA4 -> ADC通道0</code><br><code>#define AUDIO_SAMPLE_RATE 8000 // 8kHz</code><br><code>#define AUDIO_FRAME_SAMPLES 64 // 每64个采样更新一次显示</code></p>
<p><code>/* 音量算法参数 */</code><br><code>#define MID_FILTER_SHIFT 6 // 中值跟踪速度</code><br><code>#define VOL_FILTER_SHIFT 3 // 音量平滑:1/8</code></p>
<p><code>/* 这些值需要你实测后微调 */</code><br><code>#define NOISE_FLOOR 60 // 静音门限</code><br><code>#define VOLUME_MAX 420 // 满格音量映射阈值</code></p>
<p><code>/* 显示动态控制 */</code><br><code>#define LEVEL_RISE_STEP 2 // 每次最多上升2格</code><br><code>#define LEVEL_FALL_STEP 1 // 每次最多下降1格</code><br><code>#define MIN_ACTIVE_LEVEL 1 // 有效声音时至少亮1格,避免忽明忽灭</code></p>
<p><code>/* LED亮度控制,避免太亮 */</code><br><code>#define LED_BRIGHT_GREEN 128</code><br><code>#define LED_BRIGHT_YELLOW_R 128</code><br><code>#define LED_BRIGHT_YELLOW_G 126</code><br><code>#define LED_BRIGHT_RED 128</code></p>
<p><code>#define PCTRL GPIO_Pin_15</code><br><code>#define POWERONTIME 20 </code><br><code>volatile UINT32 Counter=0;</code></p>
<p><br><code>/* -------------------- 全局变量 -------------------- */</code><br><code>volatile uint8_t g_sample_flag = 0; // 定时器采样标志</code><br><code>volatile uint32_t g_sample_tick = 0; // 可选调试用</code></p>
<p><code>int16_t RoughCalib_Value = 0; // ADC粗校准值</code></p>
<p><code>/* 音频处理变量 */</code><br><code>static int32_t g_adc_mid = 2048; // ADC中值跟踪,初值先设中点</code><br><code>static uint16_t g_smooth_volume = 0; // 平滑后的音量</code></p>
<p><code>static uint32_t g_frame_amp_acc = 0; // 一帧累计音量</code><br><code>static uint16_t g_frame_sample_cnt = 0; // 一帧采样计数</code></p>
<p><code>static uint8_t g_led_level = 0; // 当前灯条级别 0~20</code></p>
<p><code>void TMR0_TimerInit (uint32_t t) {</code><br><code> R32_TMR0_CNT_END = t;</code><br><code> R8_TMR0_CTRL_MOD = RB_TMR_ALL_CLEAR;</code><br><code> R8_TMR0_CTRL_MOD = RB_TMR_COUNT_EN;</code><br><code>}</code></p>
<p><code>/* -------------------- 工具函数 -------------------- */</code><br><code>static uint16_t my_abs_i32 (int32_t x) {</code><br><code> return (x >= 0) ? (uint16_t)x : (uint16_t)(-x);</code><br><code>}</code></p>
<p><code>static uint8_t clamp_u8 (int32_t x, uint8_t min, uint8_t max) {</code><br><code> if (x < min)</code><br><code> return min;</code><br><code> if (x > max)</code><br><code> return max;</code><br><code> return (uint8_t)x;</code><br><code>}</code></p>
<p><code>/* 将音量映射到 0~NUM_LEDS */</code><br><code>static uint8_t map_volume_to_level(uint16_t vol)</code><br><code>{</code><br><code> uint32_t x;</code><br><code> uint32_t level;</code></p>
<p><code> if(vol <= NOISE_FLOOR)</code><br><code> {</code><br><code> return 0;</code><br><code> }</code></p>
<p><code> if(vol >= VOLUME_MAX)</code><br><code> {</code><br><code> return WS2812_LED_COUNT;</code><br><code> }</code></p>
<p><code> /* 先归一化到 0 ~ 1000 */</code><br><code> x = (uint32_t)(vol - NOISE_FLOOR) * 1000 / (VOLUME_MAX - NOISE_FLOOR);</code></p>
<p><code> /* 分段压缩映射:</code><br><code> - 前段细腻,防止一点点声音就亮很多</code><br><code> - 中段正常增长</code><br><code> - 后段压缩,防止一下满格 */</code><br><code> if(x < 200)</code><br><code> {</code><br><code> level = x * 4 / 200; // 0~4</code><br><code> }</code><br><code> else if(x < 700)</code><br><code> {</code><br><code> level = 4 + (x - 200) * 10 / 500; // 4~14</code><br><code> }</code><br><code> else</code><br><code> {</code><br><code> level = 14 + (x - 700) * 6 / 300; // 14~20</code><br><code> }</code></p>
<p><code> if(level > WS2812_LED_COUNT)</code><br><code> {</code><br><code> level = WS2812_LED_COUNT;</code><br><code> }</code></p>
<p><code> return (uint8_t)level;</code><br><code>}</code></p>
<p><code>static uint8_t smooth_level_transition(uint8_t current, uint8_t target)</code><br><code>{</code><br><code> if(target > current)</code><br><code> {</code><br><code> uint8_t diff = target - current;</code><br><code> if(diff > LEVEL_RISE_STEP)</code><br><code> {</code><br><code> return current + LEVEL_RISE_STEP;</code><br><code> }</code><br><code> else</code><br><code> {</code><br><code> return target;</code><br><code> }</code><br><code> }</code><br><code> else if(target < current)</code><br><code> {</code><br><code> uint8_t diff = current - target;</code><br><code> if(diff > LEVEL_FALL_STEP)</code><br><code> {</code><br><code> return current - LEVEL_FALL_STEP;</code><br><code> }</code><br><code> else</code><br><code> {</code><br><code> return target;</code><br><code> }</code><br><code> }</code></p>
<p><code> return current;</code><br><code>}</code></p>
<p><code>/*</code><br><code>static void get_level_color (uint8_t index, uint8_t *r, uint8_t *g, uint8_t *b) {</code><br><code> if(index < 5)</code><br><code> {</code><br><code> // 青绿色</code><br><code> *r = 0;</code><br><code> *g = 90;</code><br><code> *b = 40;</code><br><code> }</code><br><code> else if(index < 10)</code><br><code> {</code><br><code> // 天蓝色 </code><br><code> *r = 0;</code><br><code> *g = 70;</code><br><code> *b = 120;</code><br><code> }</code><br><code> else if(index < 15)</code><br><code> {</code><br><code> // 紫色 </code><br><code> *r = 70;</code><br><code> *g = 20;</code><br><code> *b = 110;</code><br><code> }</code><br><code> else</code><br><code> {</code><br><code> // 粉紫高亮</code><br><code> *r = 120;</code><br><code> *g = 30;</code><br><code> *b = 90;</code><br><code> }</code><br><code>}</code><br><code>*/</code></p>
<p><code>static uint8_t lerp_u8(uint8_t a, uint8_t b, uint16_t t, uint16_t t_max)</code><br><code>{</code><br><code> return (uint8_t)(a + ((int16_t)(b - a) * t) / t_max);</code><br><code>}</code></p>
<p><code>/* 平滑渐变现代配色:</code><br><code> 青绿 -> 蓝 -> 紫 -> 粉 */</code><br><code>static void get_level_color(uint8_t index, uint8_t *r, uint8_t *g, uint8_t *b)</code><br><code>{</code><br><code> uint16_t pos;</code></p>
<p><code> if(WS2812_LED_COUNT <= 1)</code><br><code> {</code><br><code> *r = 0;</code><br><code> *g = 90;</code><br><code> *b = 60;</code><br><code> return;</code><br><code> }</code></p>
<p><code> /* 归一化到 0 ~ 255,避免硬分段 */</code><br><code> pos = (uint16_t)index * 255 / (WS2812_LED_COUNT - 1);</code></p>
<p><code> if(pos < 85)</code><br><code> {</code><br><code> /* 青绿 -> 蓝 */</code><br><code> *r = lerp_u8(0, 0, pos, 85);</code><br><code> *g = lerp_u8(90, 70, pos, 85);</code><br><code> *b = lerp_u8(60, 140, pos, 85);</code><br><code> }</code><br><code> else if(pos < 170)</code><br><code> {</code><br><code> /* 蓝 -> 紫 */</code><br><code> pos -= 85;</code><br><code> *r = lerp_u8(0, 90, pos, 85);</code><br><code> *g = lerp_u8(70, 30, pos, 85);</code><br><code> *b = lerp_u8(140, 150, pos, 85);</code><br><code> }</code><br><code> else</code><br><code> {</code><br><code> /* 紫 -> 粉 */</code><br><code> pos -= 170;</code><br><code> *r = lerp_u8(90, 140, pos, 85);</code><br><code> *g = lerp_u8(30, 40, pos, 85);</code><br><code> *b = lerp_u8(150, 110, pos, 85);</code><br><code> }</code><br><code>}</code></p>
<p><code>/* 刷新音量条 */</code><br><code>static void led_show_volume (uint8_t level) {</code><br><code> uint8_t i;</code><br><code> uint8_t r, g, b;</code></p>
<p><code> for (i = 0; i < WS2812_LED_COUNT; i++) {</code><br><code> if (i < level) {</code><br><code> get_level_color (i, &r, &g, &b);</code><br><code> set_led_color (i, r, g, b);</code><br><code> } else {</code><br><code> set_led_color (i, 0, 0, 0);</code><br><code> }</code><br><code> }</code></p>
<p><code> WS2812_Show();</code><br><code>}</code></p>
<p><code>/* -------------------- ADC初始化 -------------------- */</code><br><code>void MIC_ADC_Init (void) {</code><br><code> /* PA4 = ADC通道0,浮空输入 */</code><br><code> GPIOA_ModeCfg (GPIO_Pin_4, GPIO_ModeIN_Floating);</code></p>
<p><code> /* 单通道采样初始化</code><br><code> SampleFreq_3_2 / ADC_PGA_0 直接沿用你的示例 */</code><br><code> ADC_ExtSingleChSampInit (SampleFreq_3_2, ADC_PGA_0);</code></p>
<p><code> /* ADC粗校准 */</code><br><code> RoughCalib_Value = ADC_DataCalib_Rough();</code><br><code> PRINT ("RoughCalib_Value = %d\r\n", RoughCalib_Value);</code></p>
<p><code> /* 选择通道0 */</code><br><code> ADC_ChannelCfg (MIC_ADC_CHANNEL);</code><br><code>}</code></p>
<p><code>/* 单次读取麦克风ADC */</code><br><code>uint16_t MIC_ADC_GetSample (void) {</code><br><code> int32_t adc_val;</code></p>
<p><code> adc_val = ADC_ExcutSingleConver();</code><br><code> adc_val += RoughCalib_Value;</code></p>
<p><code> /* 防止校准后越界 */</code><br><code> if (adc_val < 0) {</code><br><code> adc_val = 0;</code><br><code> }</code></p>
<p><code> /* 这里按12bit做上限保护,如实际位宽不同可调整 */</code><br><code> if (adc_val > 4095) {</code><br><code> adc_val = 4095;</code><br><code> }</code></p>
<p><code> return (uint16_t)adc_val;</code><br><code>}</code></p>
<p><code>/* -------------------- Timer0初始化 -------------------- */</code><br><code>void TMR0_Init_For_AudioSample (void) {</code></p>
<p><code> /* 设置TMR0中断周期 = 1 / AUDIO_SAMPLE_RATE */</code><br><code> TMR0_TimerInit (FREQ_SYS / AUDIO_SAMPLE_RATE);</code><br><code> TMR0_ITCfg (ENABLE, TMR0_3_IT_CYC_END);</code><br><code> PFIC_EnableIRQ (TMR0_IRQn);</code></p>
<p><code> //PRINT ("TMR0 sample rate = %d Hz\r\n", AUDIO_SAMPLE_RATE);</code><br><code>}</code></p>
<p><code>void Audio_ProcessSample(uint16_t sample)</code><br><code>{</code><br><code> uint16_t amp;</code><br><code> uint16_t frame_avg;</code><br><code> uint8_t target_level;</code></p>
<p><code> /* 1. 更快的中值跟踪 */</code><br><code> g_adc_mid += (((int32_t)sample - g_adc_mid) >> MID_FILTER_SHIFT);</code></p>
<p><code> /* 2. 交流幅度 */</code><br><code> amp = my_abs_i32((int32_t)sample - g_adc_mid);</code></p>
<p><code> /* 3. 非对称包络:上升快,下降慢 */</code><br><code> if(amp > g_smooth_volume)</code><br><code> {</code><br><code> g_smooth_volume += (amp - g_smooth_volume) >> 1; // 快速上升</code><br><code> }</code><br><code> else</code><br><code> {</code><br><code> g_smooth_volume -= (g_smooth_volume - amp) >> 3; // 缓慢下降</code><br><code> }</code></p>
<p><code> /* 4. 一帧累计 */</code><br><code> g_frame_amp_acc += g_smooth_volume;</code><br><code> g_frame_sample_cnt++;</code></p>
<p><code> /* 5. 到一帧后更新显示 */</code><br><code> if(g_frame_sample_cnt >= AUDIO_FRAME_SAMPLES)</code><br><code> {</code><br><code> frame_avg = (uint16_t)(g_frame_amp_acc / g_frame_sample_cnt);</code></p>
<p><code> /* 低于门限时不立即剧烈清零,而是交给缓动处理 */</code><br><code> if(frame_avg <= NOISE_FLOOR)</code><br><code> {</code><br><code> target_level = 0;</code><br><code> }</code><br><code> else</code><br><code> {</code><br><code> target_level = map_volume_to_level(frame_avg);</code></p>
<p><code> /* 只要检测到有效声音,至少亮1格,减少忽明忽灭 */</code><br><code> if(target_level < MIN_ACTIVE_LEVEL)</code><br><code> {</code><br><code> target_level = MIN_ACTIVE_LEVEL;</code><br><code> }</code><br><code> }</code></p>
<p><code> /* 加入显示缓动,避免整体亮/整体灭 */</code><br><code> g_led_level = smooth_level_transition(g_led_level, target_level);</code></p>
<p><code> led_show_volume(g_led_level);</code></p>
<p><code> g_frame_amp_acc = 0;</code><br><code> g_frame_sample_cnt = 0;</code><br><code> }</code><br><code>}</code></p>
<p><code>/* -------------------- 中断服务函数 -------------------- */</code><br><code>__attribute__ ((interrupt ("WCH-Interrupt-fast")))</code><br><code>__attribute__ ((section (".highcode"))) void</code><br><code>TMR0_IRQHandler (void) {</code><br><code> if (TMR0_GetITFlag (TMR0_3_IT_CYC_END)) {</code><br><code> TMR0_ClearITFlag (TMR0_3_IT_CYC_END); // 清中断标志</code></p>
<p><code> g_sample_flag = 1; // 只置位标志</code><br><code> g_sample_tick++;</code><br><code> }</code></p>
<p><code> Counter++;</code><br><code>}</code></p>
<p><code>// 可选:上电测试灯条</code><br><code>static void led_test_power_on (void) {</code><br><code> uint8_t i;</code></p>
<p><code> for (i = 0; i < WS2812_LED_COUNT; i++) {</code><br><code> set_led_color (i, 0, 0, 0);</code><br><code> }</code><br><code> WS2812_Show();</code><br><code> mDelaymS (100);</code></p>
<p><code> for (i = 0; i < WS2812_LED_COUNT; i++) {</code><br><code> set_led_color (i, 20, 0, 0);</code><br><code> }</code><br><code> WS2812_Show();</code><br><code> mDelaymS (150);</code></p>
<p><code> for (i = 0; i < WS2812_LED_COUNT; i++) {</code><br><code> set_led_color (i, 0, 20, 0);</code><br><code> }</code><br><code> WS2812_Show();</code><br><code> mDelaymS (150);</code></p>
<p><code> for (i = 0; i < WS2812_LED_COUNT; i++) {</code><br><code> set_led_color (i, 0, 0, 20);</code><br><code> }</code><br><code> WS2812_Show();</code><br><code> mDelaymS (150);</code></p>
<p><code> for (i = 0; i < WS2812_LED_COUNT; i++) {</code><br><code> set_led_color (i, 0, 0, 0);</code><br><code> }</code><br><code> WS2812_Show();</code><br><code>}</code></p>
<p><code>/**</code><br><code> * @brief 主函数</code><br><code> */</code><br><code>int main() {</code><br><code> // 1. 系统初始化</code><br><code> SetSysClock (CLK_SOURCE_PLL_60MHz); // 设置系统时钟为 60MHz (或 48MHz,需同步修改 SPI 分频)</code></p>
<p><code> // 2. 调试串口初始化</code><br><code> /* 配置串口1:先配置IO口模式,再配置串口 */</code><br><code> GPIOA_SetBits (GPIO_Pin_9);</code><br><code> GPIOA_ModeCfg (GPIO_Pin_8, GPIO_ModeIN_PU); // RXD-配置上拉输入</code><br><code> GPIOA_ModeCfg (GPIO_Pin_9, GPIO_ModeOut_PP_5mA); // TXD-配置推挽输出,注意先让IO口输出高电平</code><br><code> UART1_DefInit();</code><br><code> PRINT ("WS2812 SPI Driver Start\r\n");</code></p>
<p><code> // 3. WS2812 初始化</code><br><code> WS2812_Init();</code></p>
<p><code> // ADC初始化</code><br><code> MIC_ADC_Init();</code></p>
<p><code> /* 启动定时采样 */</code><br><code> TMR0_Init_For_AudioSample();</code></p>
<p><code> GPIOA_ModeCfg(PCTRL, GPIO_ModeOut_PP_5mA);</code></p>
<p><code> GPIOA_ResetBits(PCTRL);</code></p>
<p><br><code> while (1) {</code><br><code> if (g_sample_flag) {</code><br><code> g_sample_flag = 0;</code></p>
<p><code> uint16_t sample = MIC_ADC_GetSample();</code><br><code> Audio_ProcessSample (sample);</code><br><code> }</code></p>
<p><code> // 如果达到了设定的时间</code><br><code> if (Counter>AUDIO_SAMPLE_RATE*POWERONTIME) {</code><br><code> GPIOA_SetBits(PCTRL);</code><br><code> if (Counter>AUDIO_SAMPLE_RATE*POWERONTIME+AUDIO_SAMPLE_RATE/2) {</code><br><code> GPIOA_ResetBits(PCTRL);</code><br><code> Counter=0;</code><br><code> }</code><br><code> }</code><br><code> /* 可选调试输出:不要打印太频繁,否则影响实时性 */</code><br><code>#if 0</code><br><code> static uint32_t dbg_cnt = 0;</code><br><code> dbg_cnt++;</code><br><code> if(dbg_cnt >= 4000)</code><br><code> {</code><br><code> dbg_cnt = 0;</code><br><code> PRINT("mid=%ld, vol=%u, level=%u\r\n", g_adc_mid, g_smooth_volume, g_led_level);</code><br><code> }</code><br><code>#endif</code><br><code> }</code><br><code>}</code></p>
<p style="line-height:1.8;"> </p>
<h3 style="line-height:1.8;">*6、BOM清单</h3>
<hr>
<p style="line-height:1.8;"><span style="color:#95a5a6;font-size:14px;">请输入内容…</span></p>
<p style="line-height:1.8;"> </p>
<p style="line-height:1.8;"><span style="color:#95a5a6;font-size:14px;">注:项目涉及的BOM清单。在<span style="text-decoration:underline;"><a href="https://lceda.cn/editor" target="_blank">嘉立创EDA</a> </span>生成/上传设计文件后,BOM将自动生成至项目详情;建议包括型号、品牌、名称、封装、采购渠道、用途等内容。具体内容和形式应以表达清楚项目构成为准。 </span></p>
<p style="line-height:1.8;"> </p>
<h3 style="line-height:1.8;">*7、大赛LOGO验证</h3>
<hr>
<p style="line-height:1.8;"><img src="//image.lceda.cn/pullimage/dBHPbGD1sGTUxRXoDQ9hdUTHV52YVGdCoopbYjdh.jpeg" alt="" width="1200" height="1200"></p>
<p style="line-height:1.8;"> </p>
<p style="line-height:1.8;"> </p>
<h3 style="line-height:1.8;">* 8、演示您的项目并录制成视频上传</h3>
<hr>
<p style="line-height:1.8;"> </p>
<p style="line-height:1.8;"> </p>
<p style="line-height:1.8;"> </p>
<p style="line-height:1.8;"><span style="font-size:14px;"><a style="text-decoration:underline;" href="/posts/b2b65ad17ac34080a318a5ab47a632c2" target="_blank">前往查看更多详情 ></a></span></p>
<p style="line-height:1.8;"> </p>
评论(0)