STM32
直播中

王秀兰

7年用户 1364经验值
私信 关注
[问答]

如何去实现一种基于STM32的电子琴设计呢

如何利用基于STM32乐音频率去完整音乐的合成和播放功能呢?

如何去实现一种基于STM32的电子琴设计呢?

回帖(1)

杨敏

2021-11-18 14:51:03
  1.前言
  本文为本学期一门课程的一次课程设计大作业,题目是《音乐合成器设计》,想着作业做也做了,不如写下这篇博客巩固一下学习所得,同时也给有需要的人分享经验。所做工作主要有一下几个:1.基于乐曲简谱和“十二平均律”,使用MATLAB分析不同单音节的频谱;2.基于STM32利用乐音频率实现完整音乐的合成和播放功能。
  2.前置知识
  十二平均律
  十二平均律,亦称“十二等程律”,世界上通用的一组音(八度)分成十二个半音音程的律制,各相邻两律之间的振动数之比完全相等。十二平均律在交响乐队和键盘乐器中得到广泛使用,钢琴即是根据十二平均律来定音的。-——来源百度百科
  
  划重点,十二平均律是组成一首音乐的基础,各相邻两律之间的振动数之比完全相等。换言之就是常见的Do Re Mi Fa So La Si Do和其他四个半音。
  3.单音频谱分析
  篇幅有限,笔者只录下7个常见单音。话不多说,立马翻出半途而废,尘封多年的吉他,用手机把7个单音逐个录了下来。
  Do 2弦第1品、 Re 2弦第3品、Mi 1弦空弦、Fa 1弦第1品、So 1弦第3品、La 1弦第5品、Si 1弦第7品。——来自百度,笔者吉他水平实在太差
  使用MATLAB对单音进行傅里叶变换(FFT),得到该音节的频谱图
  MATLAB代码:
  close all;
  clear all;
  clc;
  figure;
  [filename,filepath]=uigetfile(‘.wav’,‘Open wav file’);
  [w,fs,bits]=wavread([filepath,filename]); %从电脑文件夹选择wav音频文件
  sound(w,fs,bits);
  y=w(:,1);
  display(‘声音文件的大小为:’);size(w)
  subplot(211);plot(y);title(‘时域波形’);
  N=pow2(nextpow2(length(y)));
  Y=fft(y,N);
  subplot(212);plot(fs*[1:N]/N,abs(Y));title(‘幅度值’);grid;
  注意有些手机录音机输出的是.m4a的MPEG-4音频文件,要使用转码软件转成.wav格式。
  频谱如下:
  1.Do:频率大约在380Hz
  
  
  2.Re:436Hz
  
  
  3.Mi:492Hz
  
  4.Fa:522Hz
  
  5.So:584Hz
  
  6.La:654Hz
  
  7.Si:734Hz
  
  综上,Do:380Hz Re:436Hz Mi:492Hz Fa:522Hz So:584Hz La:654Hz Si:734Hz。
  他们之间的频率差56Hz 56Hz 30Hz 62Hz 70Hz 80Hz。
  除了Re 和Mi之间没有半度音,其他都有半度音,考虑到手机录音存在偏差,基本各音节的频率差为30,基本满足了“各相邻两律之间的振动数之比完全相等”。
  十二平均律的频谱分析到此为止。
  4.基于STM32电子琴设计
  本实验使用的是STM32F103和一个无源蜂鸣器。由于无源蜂鸣器的工作频率是在2KHz~5KHz,而上面吉他所测得的频率都在1KHz以下,显然不能满足无源蜂鸣器的工作频率。于是笔者查到下表:
  
  小字四组则是2KHz以上的十二平均律频率。
  4.1 利用STM32内部定时器产生PWM输出信号
  PWM就是脉冲宽度调制,由高电平和低电平来回跳变组成一组信号来驱动元件,其中有两个关键指标:频率和占空比。
  1秒内,0.5秒开,0.5秒灭,占空比是50%对吧?那么,1毫秒内,0.5毫秒开,0.5毫秒灭,占空比也是50%,对吧?如果是1秒呢,频率就是1HZ,如果是1毫秒,频率就是1KHZ,显然,同样是50%占空比,如果频率是1HZ,那电机肯定是跳着走的,灯光肯定闪得可以跳舞,不具有调速和调光的意义。——@一个在奋斗路上前行的小菜鸟
  笔者使用Tim3通道2输出一路PWM信号,定时器的参数由结构体TimeBaselnitTypeDef定义,主要包括预分频系数、时钟分割、计数器模式、计数溢出大小等。例如,要由TIM3(定时器3)产生一个时长为1 s的定时,首先,应进行系统时钟的设置,得到TIM3CLK=72MHz,然后进行定时器设置。其中,预分频系数为35 999,此时,TIM3时钟为72 MHz/36 000=2 kHz,无时钟分割。设置计数溢出大小为1 999,即每计2 000个数就产生一个更新事件,输出频率为2 kHz/2 000=1 Hz。设置如下:
  TIM_TimeBaseStructure.TIM_Period=2000-1; //自动装载周期值
  TIM_TimeBaseStructure.TIM_Prescaler=36000-1; //预分频值
  TIM_TimeBaseStructure.TIM_ClockDivision=0; //时钟分割
  TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up;//向上计数模式
  4.2占空比
  有如下含义:在一串理想的脉冲周期序列(如方波)中,正脉冲的持续时间与脉冲总周期的比值。
  
  当TIM_Period为1 999时,若想得到占空比50%,则TIM_Pulse应设置为(1999+1)/2=1000。具体设置如下:
  TIM_OCInitStructure.TIM_OCMode=TIM_OCMode_PWM2; //PWM模式2
  TIM_OCInitStructure.TIM_OutputState=TIM_OutputState_Enable;//比较输出使能
  TIM_OCInitStructure.TIM_OCPolarity=TIM_OCPolarity_High;//输出极性高
  4.3频率
  音阶的产生依赖于PWM输出信号的频率。为了简化设计,我们令定时器的TIM_Period为1 999,且占空比始终为50%,根据式(1)则TIM_ Pulse为1000。此时,PWM输出信号频率仅与定时器预分频系数TIM_Prescaler有关,只需要调整该系数,即可得到所需信号频率。
  TIM_Prescaler由下式得到:
  
  fsound为音阶对应的频率,如Do频率为2093 Hz。要产生该音频,TIM_Prescaler应为17。
  4.4威廉希尔官方网站 设计
  
  4.5主要实现代码
  beer.h
  #ifndef _beer_H
  #define _beer_H
  #include “stm32f10x.h”
  #include “delay.h”
  void TIM3_PWM_Init(u16 arr,u16 psc);//arr
  void music_note(u16 psc,u16 ms);
  #endif
  1
  2
  3
  4
  5
  6
  7
  8
  9
  10
  beer.c
  #include “beer.h”
  void TIM3_PWM_Init(u16 arr,u16 psc)
  {
  GPIO_InitTypeDef GPIO_InitStructure;
  TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
  TIM_OCInitTypeDef TIM_OCInitStructure;
  RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB|RCC_APB2Periph_AFIO, ENABLE); //使能 PB,AFIO 端口时钟
  GPIO_PinRemapConfig(GPIO_PartialRemap_TIM3, ENABLE); //部分重映射,到PB5
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; //LED0--》PB.5 端口配置
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //推挽输出
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //IO 口速度为 50MHz
  GPIO_Init(GPIOB, &GPIO_InitStructure); //初始化 GPIOB.5
  TIM_TimeBaseStructure.TIM_Period=arr; //自动装载周期值
  TIM_TimeBaseStructure.TIM_Prescaler=psc; //预分频值
  TIM_TimeBaseStructure.TIM_ClockDivision=0; //时钟分割
  TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up;//向上计数模式
  TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
  TIM_OCInitStructure.TIM_OCMode=TIM_OCMode_PWM2; //PWM模式2
  TIM_OCInitStructure.TIM_OutputState=TIM_OutputState_Enable;//比较输出使能
  TIM_OCInitStructure.TIM_OCPolarity=TIM_OCPolarity_High;//输出极性高
  TIM_OC2Init(TIM3, &TIM_OCInitStructure);
  TIM_OC2PreloadConfig(TIM3, TIM_OCPreload_Enable);//使能预装载寄存器
  TIM_Cmd(TIM3, ENABLE);//使能TIM3
  }
  void music_note(u16 psc,u16 ms) //单音节输出,包含预分频值和延迟时间
  {
  TIM3_PWM_Init(2000-1,psc);
  TIM_SetCompare2(TIM3,1000);
  delay_ms(ms);
  }
  1
  2
  3
  4
  5
  6
  7
  8
  9
  10
  11
  12
  13
  14
  15
  16
  17
  18
  19
  20
  21
  22
  23
  24
  25
  26
  27
  28
  29
  30
  31
  32
  33
  34
  35
  36
  37
  38
  39
  40
  41
  main.c
  #include “stm32f10x.h”
  #include “delay.h”
  #include “beer.h”
  int main()
  {
  delay_init();
  NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
  while(1)
  {
  music_note(17,500);
  music_note(15,500);
  music_note(14,500);
  music_note(13,500);
  music_note(11,500);
  music_note(10,500);
  music_note(9,500);
  }
  }
  实现效果:Do Re Mi Fa So La Si循环播放。
举报

更多回帖

发帖
×
20
完善资料,
赚取积分