STM32掌机教程6,电子琴

发布者:真诚相伴最新更新时间:2019-05-31 来源: eefocus关键字:STM32  掌机教程  电子琴 手机看文章 扫描二维码
随时随地手机看文章

  本节原来是想讲一讲无源蜂鸣器发声的原理,用于添加BGM功能。为了讲原理,就写了一些通俗的代码,没想到越写越多,后来,干脆就形成了一个小小的项目吧——基于STM32与无源蜂鸣器的电子琴。


灯光效果

  首先想到的是做一个灯光的效果,按下哪个按键,哪个按键的灯要亮;松手后,灯灭掉。顺带,检测一下带松手检测的按键功能好不好用。后续还可以做成通过亮灯提示需要按下那个按键,类似于节奏大师的功能——哪里要响点哪里。




  我去掉了无关的代码,主函数里通过死循环,来确保按键按下的时候,灯是亮起来的 :


//main.c 

while(1)

{

AllLED_OFF();

while(!SKEY1)

{

SLED1 = LED_ON;

}

while(!SKEY2)

{

SLED2 = LED_ON;

}

while(!SKEY3)

{

SLED3 = LED_ON;

}

while(!SKEY4)

{

SLED4 = LED_ON;

}

while(!SKEY5)

{

SLED5 = LED_ON;

}

while(!SKEY6)

{

SLED6 = LED_ON;

}

while(!SKEY7)

{

SLED7 = LED_ON;

}

while(!SKEY8)

{

SLED8 = LED_ON;

}

if(PAUSE_PRES == KEY_Scan(0))

{

LED1 = !LED1;

}

}



  下载程序并看看现象。


无源蜂鸣器的音调控制

  音调和频率是息息相关的,可以在网上查找到频率和音调对应的表格。本文的代码参考了这篇文章,表示感谢

在这里插入图片描述

  根据图片,可以做宏定义,中音的C调:


#define CM1 523

#define CM2 587


  为了讲清楚原理,这里蜂鸣器先当做LED用。引脚给高电平,蜂鸣器就能响(只有一瞬间有声音)。然而,只给高电平,无源蜂鸣器不能自己持续发出声音;需要马上给低电平,然后再给一个高电平。即在一个很短的周期内,无源蜂鸣器在高电平持续器件工作,在低电平持续器件休息。周期的倒数就是频率。

  蜂鸣器的引脚是PB1,初始化跟LED一样,我直接写在了LED的初始化函数里。

  接下来先写两个按键的功能,用按键1和2来演奏C调的哆和唻。我定义了一个变量,是us为单位的时间,这是蜂鸣器的一个周期。它的值就是1000000us(1百万us就是1s)除以频率。频率是查表得到的。在周期内,高电平持续的时间和低电平持续的时间各占一半。


//main.c

u32 F_us;   //特定频率对应的周期时间,单位us

while(!SKEY1)

{

    SLED1 = LED_ON;

    F_us = 1000000/CM1;

    BEEP = 1;

    delay_us(F_us/2);

    BEEP = 0;

    delay_us(F_us/2);

}

while(!SKEY2)

{

    SLED2 = LED_ON;

    F_us = 1000000/CM2;

    BEEP = 1;

    delay_us(F_us/2);

    BEEP = 0;

    delay_us(F_us/2);

}


  下载程序,按下按键1或2,就可以听到不同的音调。原理就是这么简单。


音量控制

  无源蜂鸣器可以用高电平持续的时间调整音量,在一个周期中,高电平持续的时间越长,蜂鸣器声音越大;高电平持续的时间越短,蜂鸣器的声音越小。这句话还有一个时髦的描述方法——脉宽调制

在这里插入图片描述

  原理很简单,实现起来也不复杂。在上一个案例的基础上,我把高电平持续的时间由50%改成了通过变量volum来计算。如果volum=1,那么高电平持续的时间就是周期的一半(右移一位等于除以2);如果volum=5,那么高电平持续的时间就是周期的64分之1,(右移n位等于除以2的n次方)。为了方便比较,我先让按键1和2的音调一样,音量不一样。


//main.c

u32 F_us;   //特定频率对应的周期时间,单位us

u32 time_ON;   //蜂鸣器响的时间

u32 time_OFF;   //蜂鸣器不响的时间

u8 volum;    //音量

while(!SKEY1)

{

    SLED1 = LED_ON;

    F_us = 1000000/CM1;

    volum = 1;

    time_ON = F_us>>volum;

    time_OFF = F_us - time_ON;

    BEEP = 1;

    delay_us(time_ON);

    BEEP = 0;

    delay_us(time_OFF);

}

while(!SKEY2)

{

    SLED2 = LED_ON;

    F_us = 1000000/CM1;

    volum = 6;

    time_ON = F_us>>volum;

    time_OFF = F_us - time_ON;

    BEEP = 1;

    delay_us(time_ON);

    BEEP = 0;

    delay_us(time_OFF);

}


  按下两个按键,可以听出响度是不一样。事实上我调整过比例,感觉,50%的占空比可能是最大的声音了,,volum < 4之前都听不大出来。


提取函数

  既然按键1和按键2都既能控制音调,用能控制音量了,别的按键把代码复制粘贴就能实现功能了。只不过,复制粘贴是代码不好的表现。


  所以,再次提取出一个函数,传入音调和音量,就能发出声音。


void play(u32 tone,u8 tvolum)

{

u32 F_us;   //特定频率对应的周期时间,单位us

u32 time_ON;   //蜂鸣器响的时间

u32 time_OFF;   //蜂鸣器不响的时间

F_us = 1000000/tone;

time_ON = F_us>>tvolum;

time_OFF = F_us - time_ON;

BEEP = 1;

delay_us(time_ON);

BEEP = 0;

delay_us(time_OFF);

}


  然后修改死循环。我有8个带灯按键,但是音调只有7个,所以预留8和pause用于升降调,这两个按键无需松手检测。其它按键按下时,调用play函数。


while(1)

{

key = KEY_Scan(0);

if(KEY8_PRES == key)

{

LED2 = !LED2;

}

else if(PAUSE_PRES == key)

{

LED1 = !LED1;

}

else

{

AllLED_OFF();

}

while(!SKEY1)

{

SLED1 = LED_ON;

play(CM1,volum);

}

while(!SKEY2)

{

SLED2 = LED_ON;

play(CM2,volum);

}

while(!SKEY3)

{

SLED3 = LED_ON;

play(CM3,volum);

}

while(!SKEY4)

{

SLED4 = LED_ON;

play(CM4,volum);

}

while(!SKEY5)

{

SLED5 = LED_ON;

play(CM5,volum);

}

while(!SKEY6)

{

SLED6 = LED_ON;

play(CM6,volum);

}

while(!SKEY7)

{

SLED7 = LED_ON;

play(CM7,volum);

}

}


  至此,就已经实现了最简单的电子琴的功能。


升调和降调功能

  默认情况下,我们演奏的都是C调中间那个音阶。我定义按键8升调,按键PAUSE为降调(其实调整的不是音调而是音阶)。然后定义个变量用于储存当前是C调还是F调,也就是音阶?


while(1)

{

key = KEY_Scan(0);

if(KEY8_PRES == key)

{

LED2 = !LED2;

tone_level++;

}

else if(PAUSE_PRES == key)

{

LED1 = !LED1;

tone_level--;

}

else

{

AllLED_OFF();

}

        。。。

     }


  修改play函数,根据音阶与音调来计算周期。


void play(u32 tone,u8 tvolum)

{

u32 F_us;   //特定频率对应的周期时间,单位us

u32 time_ON;   //蜂鸣器响的时间

u32 time_OFF;   //蜂鸣器不响的时间

if(tone_level<1)

tone_level = 1;

else if(tone_level>12)

tone_level = 12;

if(1 == tone_level)

{

switch(tone)

{

case 1:F_us = 1000000/CL1;

case 2:F_us = 1000000/CL2;

case 3:F_us = 1000000/CL3;

case 4:F_us = 1000000/CL4;

case 5:F_us = 1000000/CL5;

case 6:F_us = 1000000/CL6;

case 7:F_us = 1000000/CL7;

}

}

else if(2 == tone_level)

{

switch(tone)

{

case 1:F_us = 1000000/CM1;

case 2:F_us = 1000000/CM2;

case 3:F_us = 1000000/CM3;

case 4:F_us = 1000000/CM4;

case 5:F_us = 1000000/CM5;

case 6:F_us = 1000000/CM6;

case 7:F_us = 1000000/CM7;

}

}

//F_us = 1000000/tone;

time_ON = F_us>>tvolum;

time_OFF = F_us - time_ON;

BEEP = 1;

delay_us(time_ON);

BEEP = 0;

delay_us(time_OFF);

}


  这段代码太糟糕了,才写了两种音阶我就受不了了。之前音调的信息都是宏定义,为了方便调用,我改成数组。


//beep.c

u16 CL[7]={262,294,330,349,392,440,494};

u16 CM[7]={523,587,659,698,784,880,988};

u16 CH[7]={1047,1175,1319,1397,1568,1760,1976};

u16 DL[7]={294,330,370,392,440,494,554};

u16 DM[7]={587,659,740,784,880,988,1109};

u16 DH[7]={1175,1319,1480,1568,1760,1976,2217};

u16 EL[7]={330,370,415,440,494,554,622};

u16 EM[7]={659,740,831,880,988,1109,1245};

u16 EH[7]={1319,1480,1661,1760,1976,0,0};

u16 FL[7]={349,392,440,466,523,587,659};

u16 FM[7]={698,784,880,932,1047,1175,1319};

u16 FH[7]={1397,1568,1760,1865,0,0,0};


  然后修改演奏函数。


void play(u32 tone,u8 tvolum)

{

u32 F_us;   //特定频率对应的周期时间,单位us

u32 time_ON;   //蜂鸣器响的时间

u32 time_OFF;   //蜂鸣器不响的时间

if(tone_level<1)

tone_level = 1;

else if(tone_level>12)

tone_level = 12;

switch(tone_level)

{

case 1: F_us = 1000000/CL[tone];break;

case 2: F_us = 1000000/CM[tone];break;

case 3: F_us = 1000000/CH[tone];break;

case 4: F_us = 1000000/DL[tone];break;

case 5: F_us = 1000000/DM[tone];break;

case 6: F_us = 1000000/DH[tone];break;

case 7: F_us = 1000000/EL[tone];break;

case 8: F_us = 1000000/EM[tone];break;

case 9: F_us = 1000000/EH[tone];break;

case 10:F_us = 1000000/FL[tone];break;

case 11:F_us = 1000000/FM[tone];break;

case 12:F_us = 1000000/FH[tone];break;

}

//F_us = 1000000/tone;

time_ON = F_us>>tvolum;

time_OFF = F_us - time_ON;

BEEP = 1;

delay_us(time_ON);

BEEP = 0;

delay_us(time_OFF);

}


  主函数调用的部分也修改了。注意,数组的索引是从零开始的。


while(1)

{

key = KEY_Scan(0);

if(KEY8_PRES == key)

{

LED2 = !LED2;

tone_level++;

}

else if(PAUSE_PRES == key)

{

LED1 = !LED1;

tone_level--;

}

else

{

AllLED_OFF();

}

while(!SKEY1)

{

SLED1 = LED_ON;

play(0,volum);//数组的第一个元素是0

}

while(!SKEY2)

{

SLED2 = LED_ON;

play(1,volum);

}

while(!SKEY3)

{

SLED3 = LED_ON;

play(2,volum);

}

while(!SKEY4)

{

SLED4 = LED_ON;

play(3,volum);

}

while(!SKEY5)

{

SLED5 = LED_ON;

play(4,volum);

}

while(!SKEY6)

{

SLED6 = LED_ON;

play(5,volum);

}

while(!SKEY7)

{

SLED7 = LED_ON;

play(6,volum);

}

}


  也可以把音阶的信息作为一个变量传入参数,避免使用全局的变量。

  实际演奏时,还发现了小小的BUG,E和F的高音,数组不够7个,如果传入的参数是0,那么F_us的时候分母是0,程序可能卡死,所以把0音调改成1了。当然也可以用判断语句来避免这种情况。

  我还设想了很多功能,比如屏幕显示个乐谱,屏幕显示音调;按键亮起作为提示,然后按下对应的按键,发出声音。想法越来越多,我只好赶紧收手了,毕竟,,,我原来的计划是打地鼠掌机啊!电子琴只是为了讲蜂鸣器的原理啊!

  放上两只老虎的简谱,来弹奏一曲吧。

关键字:STM32  掌机教程  电子琴 引用地址:STM32掌机教程6,电子琴

上一篇:STM32掌机教程7,演奏音乐
下一篇:STM32掌机教程5,程序框架,随机,加命与升级

推荐阅读最新更新时间:2024-11-09 11:22

STM32库函数SystemInit()详解
STM32单片机应用非常广泛,官方提供了标准的接口库,用户可以不用直接操作寄存器,只需要调用接口函数就可以了。在官方库中有一个非常重要的函数void SystemInit (void), 该函数用户可能不会直接调用,而在启动文件中一定会调用。函数原型如下: 函数原型 void SystemInit (void) { /* Reset the RCC clock configuration to the default reset state(for debug purpose) */ /* Set HSION bit */ RCC- CR |= (uint32_t)0x00000001; /* Reset
[单片机]
基于STM32无人超市消费系统设计
一、前言 针对传统超市购物车结账排队时间长、付款效率低的问题,提出了一种更符合现代社会人们购物方式-基于RFID的自助收银系统。习惯了快节奏生活的人们都会选择自助收银机结账,理由显而易见:自助收银机结账很方便,几乎不用排队,也不用近距离和收银员接触,在防疫时期特别感觉安心。而且自助结账对每件物品的售价更是一次清晰地核对,最终需支付合计购物支出自己也更加清晰明了;这两年来,越来越多的智能设备应用在我们的生活领域里,为我们的生活提供了很多智能和便利。自助收银机从几年前就陆续涌入到各地商场、超市、便利店,自去年疫情发生后自助收银的需求比例更是呈直线上升趋势。自助收银机的启用,不仅节约了超市的人力开支成本,也从根本上提升了超市的购物支付效
[单片机]
基于<font color='red'>STM32</font>无人超市消费系统设计
STM32学习笔记—通信容易出错的情况
I²C:全称为Inter-Integrated Circuit(内部集成电路),是一种串行通讯总线,常用于嵌入式电子产品中。 I²C是飞利浦公司在1980年为了让各种低速设备(飞利浦芯片)连接起来而研发的一种通信总线。目前,I²C依然是最常见的通信总线之一,现在绝大部分MCU都内部集成了I²C控制器,STM32也不例外,至少有一个I²C控制器,有的型号甚至多达6个。 STM32 I2C基础内容 I²C总线协议有多个版本,有的STM32遵循的是第2版本,有的是第3版本。所以,不同型号的 STM32 中I²C 可能存在一些差异,但基本功能相似。 1. 主从模式特性 主模式特性: 时钟生成 起始位和停止位生成 从模式特
[单片机]
<font color='red'>STM32</font>学习笔记—通信容易出错的情况
探究STM32、FreeRTOS低功耗设计思路和原理
如今电池供电的产品很多,电池供电通常设计到一个问题,那就是低功耗。 本文为大家讲讲基于STM32、FreeRTOS实现低功耗思想和原理。 一 低功耗设计常规思路应用中使用的 RTOS 一般采用基于时间片轮转的抢占式任务调度机制,一般的低功耗设计思路如下:1. 当 Idle 任务运行时,进入低功耗模式;2. 在适当的条件下,通过中断或者外部事件唤醒 MCU。 但是, 从第二点可以看出,每次当 OS 系统定时器产生中断时,也会将 MCU 从低功耗模式中唤醒,而频繁的进入低功耗模式/从低功耗模式中唤醒会使得 MCU 无法进入深度睡眠,对低功耗设计而言也是不合理的。 在 FreeRTOS 中给出了一种低功耗设计模式 ——Tickl
[单片机]
探究<font color='red'>STM32</font>、FreeRTOS低功耗设计思路和原理
用Keil编译STM32工程出现下面错误
Keil提示:*.axf: Error: L6967E: Entry point (0x08000000) points to a Thumb instruction but is not a valid Thumb code pointer. 解决办法: 1、菜单 options for target- linker- misc controls加入 --entry Reset_Handler --first __Vectors 2、options for target- asm- Include Paths 然后倒入startup_stm32f10x_hd.s或者startup_stm32f10x_md.s(说明:不同的
[单片机]
Robomaster-stm32-PWM学习笔记(stm32控制pwm输出)
学习笔记: 脉冲宽度调制(PWM),是英文“Pulse Width Modulation”的缩写,简称脉宽调制,是利用微处理器的数字输出来对模拟电路进行控制的一种非常有效的技术,广泛应用在从测量、通信到功率控制与变换的许多领域中。 将通用 定时器 分为四个部分: 1-选择 时钟 2-时基电路 3-输入捕获 4-输出比较 实践1-pwm- led 闪烁 要求产生周期为200ms,占空比为50%的PWM 信号 来控制led灯。 1.cubemax配置 由原理图可知,led引脚复用为 ti m5 周期为200ms,占空比为50%,Tim5挂在APB1总线上,CLK =
[单片机]
Robomaster-stm32-PWM学习笔记(<font color='red'>stm32</font>控制pwm输出)
STM32开发环境(3)----下载调试
下载调试 STM32 程序下载主要有两种方法:串口和JLINK。串口下载也可以扩展为USB,JLINK支持JTAG、 SWD。 STM32 的串口下载一般是通过串口 1 下载的,使用USB下载其实也是应用这种方式。只是要在PC端下载USB转串口的驱动,在硬件端加一片USB转串口的片子。USB转串口的片子常用的是CH340,在PC端下端其驱动程序就可。另外还需使用STM32串口下载软件,受正点原子影响我使用FLYMCU,这个软件好用、易用,打开就能知道怎么用。该软件可以在 www.mcuisp.com 免费下载。 特别提醒:不要选择使用 RamIsp,否则,可能没法正常下载。 需要在线调试时,JTAG
[单片机]
<font color='red'>STM32</font>开发环境(3)----下载调试
实战经验 | 如何在用户应用中开启 LoRa CAD
01 LoRa CAD 应用场景举例 在 STM32WL LoRa 某些应用中,尤其是电池供电的设备上,需要按需发送数据,如下发指令,让 STM32WL LoRa 发送数据,或做相应的操作。为了降低功耗,STM32WL不能一直处于接收状态,这样功耗会很高。我们可以开启 LoRa CAD(信道活动检测)功能,STM32WL 通过开启 CAD 功能,检测前导码的前 1/2/4/8/16 个码元,当检测到 LoRa(唤醒)信号后再开启接收,否则系统进入低功耗,这样会极大的降低系统的整体功耗。 02 STM32WL LoRa CAD 原理和驱动 LoRa 信号可以在低于噪声强度的情况下被正确解调。所以,仅
[单片机]
实战经验 | 如何在用户应用中开启 LoRa CAD
小广播
设计资源 培训 开发板 精华推荐

最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

换一换 更多 相关热搜器件

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved