【EEPW电子鼓DIY活动】成果展示——深受小宝宝喜爱
前期准备
在开始电子鼓DIY活动前,想到要给我家这位15个月的小家伙制作第一个玩具,确实有点兴奋。把DIY的焊接工具全部找了出来,又测试了一番,静静地等待审核与快递。当然,等待的时候还是需要学习基础知识的,比如WS2812灯的驱动,DAC外设驱动等。
很快,快递就到了。拿着手中异常整齐的物料,心情甭提多高兴了——反正,我没有这个实力整理得这么好,但我确实希望别人能整理好后给我。嘿嘿!
想看物料整理得有多好!详见《电子鼓DIY开箱帖——收到最用心的材料包》 http://forum.eepw.com.cn/thread/375578/1
焊接
整齐的物料绝对加速了我的焊接。在焊接前阅读了原理图,0805大电容全部都是104封装。LED灯附近的是1K欧,其余0603封装电阻阻值在PCB丝印上均有标注了。我又全部观看了鹏老师上传到EEPW的教学视频,提前了解到焊接的注意点,易错点,比如WS2812灯珠的对齐方向等。
核对原理图后,我省略了RESET信号的复位按键,如需复位,通过电池的开关完成即可。我还省略了3.5mm音频座,考虑到也不会连接到音箱,或传入音源,所以音频座的意义也就不大了。我还省略了2x5插针座——没有啥可扩展的。
这里有一个小Tips,我先把触摸焊盘用透明胶条覆盖了,免得不小心沾上焊锡无法清理。下面秀一下焊接成果图:
系统框图
根据功能实现,下面是我本次电子鼓DIY的系统框图。
软件架构
Blinky闪灯
焊接完成之后,自然是软件的编写。前期准备阶段已经完成系统时钟,LED引脚初始化的代码编写。连接好jlink,直接flash,LED灯闪烁轻松实现。
int main(void) { bsp_init(); SysTick_Config(72000); NVIC_EnableIRQ(SysTick_IRQn); while (1) { if (time_line_ms > 500) { time_line_ms = 0; bsp_led_toggle(); } } }
电子鼓
程序按照模块化方式编写,软件框图上节已经提供,这里不再重复。直接上代码展示了:
int main(void) { bsp_init(); SysTick_Config(72000); NVIC_EnableIRQ(SysTick_IRQn); for (int i = 0; i < 12; i++) { WS2812_Buff[i][0] = rand(); WS2812_Buff[i][1] = rand(); WS2812_Buff[i][2] = rand(); WS2812_Refresh(); time_line_ms = 100; while (time_line_ms > 0) { ; } } time_line_ms = 1000; while (time_line_ms > 0) { ; } for (int i = 0; i < 12; i++) { WS2812_Buff[i][0] = 0; WS2812_Buff[i][1] = 0; WS2812_Buff[i][2] = 0; } WS2812_Refresh(); while (1) { if (key_code < 6) { for (int i = 0; i < 12; i++) { WS2812_Buff[i][0] = 0; WS2812_Buff[i][1] = 0; WS2812_Buff[i][2] = 0; } // WS2812_Refresh(); WS2812_Buff[ws2812_led_couple[key_code][0]][0] = rand() & 0x0F; WS2812_Buff[ws2812_led_couple[key_code][0]][1] = rand() & 0x0F; WS2812_Buff[ws2812_led_couple[key_code][0]][2] = rand() & 0x0F; WS2812_Buff[ws2812_led_couple[key_code][1]][0] = rand() & 0x0F; WS2812_Buff[ws2812_led_couple[key_code][1]][1] = rand() & 0x0F; WS2812_Buff[ws2812_led_couple[key_code][1]][2] = rand() & 0x0F; WS2812_Refresh(); time_line_ms = 800; key_code = 6; } else if ((time_line_ms == 0) && (key_code == 6)) { for (int i = 0; i < 12; i++) { WS2812_Buff[i][0] = 0; WS2812_Buff[i][1] = 0; WS2812_Buff[i][2] = 0; } WS2812_Refresh(); key_code = 7; } } }
DIY软件
完全重复鹏老师的作品,那算不上DIY,由于有了以下几个DIY的成果:
1. 击鼓声的声源波形数组。
* 原参考程序的波形数组使用了int16_t格式,在应用程序传入DAC数据寄存器前,又做了线性计算。
reg = wave[i] / 16 + 2048;
其实,浪费了MCU的时间,我们在DIY前将其数值计算出来并更新,再存入wave[]数组,减少MCU的计算时间,提升MCU的效率。
* 波形数组使用了十进制数字书写。在格式上面混乱不堪,本次更新为uint16_t,使用16进制数字,在形式上更整齐,阅读代码时更舒心。
DAC外设初始化
static void dac_init(void) { LL_DAC_InitTypeDef DAC_InitStruct = {0}; LL_TIM_InitTypeDef TIM_InitStruct = {0}; /* Peripheral clock enable */ LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_DAC1); LL_AHB1_GRP1_EnableClock(LL_AHB1_GRP1_PERIPH_DMA2); NVIC_SetPriority(DMA2_Channel3_IRQn, NVIC_EncodePriority(NVIC_GetPriorityGrouping(), 0, 0)); NVIC_EnableIRQ(DMA2_Channel3_IRQn); /* DAC_CH1 Init */ LL_DMA_SetDataTransferDirection(DMA2, LL_DMA_CHANNEL_3, LL_DMA_DIRECTION_MEMORY_TO_PERIPH); LL_DMA_SetChannelPriorityLevel(DMA2, LL_DMA_CHANNEL_3, LL_DMA_PRIORITY_LOW); LL_DMA_SetMode(DMA2, LL_DMA_CHANNEL_3, LL_DMA_MODE_NORMAL); LL_DMA_SetPeriphIncMode(DMA2, LL_DMA_CHANNEL_3, LL_DMA_PERIPH_NOINCREMENT); LL_DMA_SetMemoryIncMode(DMA2, LL_DMA_CHANNEL_3, LL_DMA_MEMORY_INCREMENT); LL_DMA_SetPeriphSize(DMA2, LL_DMA_CHANNEL_3, LL_DMA_PDATAALIGN_HALFWORD); LL_DMA_SetMemorySize(DMA2, LL_DMA_CHANNEL_3, LL_DMA_MDATAALIGN_HALFWORD); LL_DMA_SetPeriphAddress(DMA2, LL_DMA_CHANNEL_3, (uint32_t)&DAC->DHR12R1); /** DAC channel OUT1 config */ DAC_InitStruct.TriggerSource = LL_DAC_TRIG_EXT_TIM6_TRGO; DAC_InitStruct.WaveAutoGeneration = LL_DAC_WAVE_AUTO_GENERATION_NONE; DAC_InitStruct.OutputBuffer = LL_DAC_OUTPUT_BUFFER_ENABLE; LL_DAC_Init(DAC, LL_DAC_CHANNEL_1, &DAC_InitStruct); LL_DAC_EnableDMAReq(DAC, LL_DAC_CHANNEL_1); LL_DAC_EnableTrigger(DAC, LL_DAC_CHANNEL_1); LL_DAC_Enable(DAC, LL_DAC_CHANNEL_1); LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_TIM6); TIM_InitStruct.Prescaler = 7; TIM_InitStruct.CounterMode = LL_TIM_COUNTERMODE_UP; TIM_InitStruct.Autoreload = 815; LL_TIM_Init(TIM6, &TIM_InitStruct); LL_TIM_EnableARRPreload(TIM6); LL_TIM_SetTriggerOutput(TIM6, LL_TIM_TRGO_UPDATE); LL_TIM_DisableMasterSlaveMode(TIM6); LL_TIM_EnableCounter(TIM6); LL_TIM_GenerateEvent_UPDATE(TIM6); }
波形文件规整
const uint16_t wave_0[] = { 0x0800, 0x0800, 0x0800, 0x0800, 0x0800, 0x0800, 0x0800, 0x0800, 0x0800, 0x0800, 0x0800, 0x0800, 0x0800, 0x0800, 0x0800, 0x0800, 0x0800, 0x0800, 0x0800, 0x0800, 0x0800, 0x0800, 0x0800, 0x0800, 0x0800, 0x0800, 0x0800, 0x0800, 0x0800, 0x0800, 0x0800, 0x0800, 0x0800, 0x0800, 0x0800, 0x0800, 0x0800, 0x0800, 0x0800, 0x0800, 0x0800, 0x0800, 0x0800, 0x0800, 0x0800, 0x0800, 0x0800, 0x0800, 0x0800, 0x0800, 0x0800, 0x0800, 0x0800, 0x0800, 0x0800, 0x0800, 0x0800, 0x0800, 0x0800, 0x0800, 0x0800, 0x0800, 0x0800, 0x0800, };
2. DAC驱动
* 使用Timer6进行采样率的匹配设定,使用prescale=7, period=203;计算可得采样率为44.117KHz,但本次限于Flash的空间大小,将采样率缩小为11.02KHz,因此,period=815。
采用DMA + Timer6 + DAC的组合方式实现击鼓声音。每次有按键按下时,配置DMA,启动DMA,启动Timer6。完成声音波形文件的输出。
* 使用更加轻便的LL驱动库.示例程序是HAL库的输出,多少有些冗余。所以在本次DIY时,我使用了LL库,精简了代码,也让程序运行更加流畅。
详细源代码请详见文末工程版全部源代码
DIY硬件
本来计划DIY一个ADC外设,用于采样锂电池的电压,估算其SOC,并使用语音提示需要充电。但无奈硬件设计未能引出引脚。尝试直接在芯片引脚上加焊飞线,未果,遂放弃。
总结
本次利用DIY电子鼓的活动,不仅增强我的动手能力,而且学习到很多知识。电子鼓主要是利用数字转模拟电路的原理实现声音的播放。我在DIY的活动期间也重点学习了DAC的知识,包括STM32F103的DAC外设,DAC采样原理,Timer6的Event触发方式,DMA功能的原理与实现,还有声音波形文件WAV文件格式等等。
本次DIY活动也是我第一次接触WS2812这个简单又实用的彩色RGB灯带。学习了单总线通讯协议的原理,也动手实现了。看着炫丽的灯带成功点亮成就感拉得满满的。
这次DIY活动也有少量遗憾。由于平时带娃占用的精力较大,投入到DIY活动的时间也就非常有限,又赶上小家伙生病2周,我自己又发烧,唉!最后SPI Flash芯片的读写功能未完成在活动结束前搞定。待我完善,再回帖补充上吧!
DIY活动成果
* 《【了解一下】R2R电阻网络DAC》 http://forum.eepw.com.cn/thread/376070/1
* 《还有谁不了解三角波?赶紧点开看看!》http://forum.eepw.com.cn/thread/376608/1
* 《WAV文件格式并不难?让我带大家一步一步分析》 http://forum.eepw.com.cn/thread/376748/1
致谢
感谢EEPW论坛组织本次活动。
感谢鹏老师提供技术帮助。
感谢各位网友们的帮助与关注!
谢谢各位了。
成果展示
1. 电子鼓源代码分享
* EEPW电子鼓DIY工程全部源代码 http://share.eepw.com.cn/share/download/id/391073
* EEPW电子鼓成品hex文件 http://share.eepw.com.cn/share/download/id/391074
2. 视频链接
* 成果视频:http://v.eepw.com.cn/video/play/id/15801