发作品签到
专业版

基于天空星的多功能电子琴桌面小摆件

工程标签

274
0
0
4

简介

这个项目是基于立创天空星GD32F407VET6的桌面小摆件,显示时间日期,支持双击、长短按、连击、记录音符长短、暂停播放、加速减速播放、动画和时间显示、蜂鸣器播放音乐、按键扫描消抖互不干扰

简介:这个项目是基于立创天空星GD32F407VET6的桌面小摆件,显示时间日期,支持双击、长短按、连击、记录音符长短、暂停播放、加速减速播放、动画和时间显示、蜂鸣器播放音乐、按键扫描消抖互不干扰
立创·天空星扩展板征集令
复刻成本:100

开源协议

GPL 3.0

创建时间:2024-08-20 18:24:28更新时间:2024-10-09 13:44:47

描述

立创训练营·天空星GD32F407VET6开发板-青春版 的电子琴桌面小摆件

焊接水平一般般哈哈哈~~~

嘉立创的彩色丝印是真的好看,加上面板也是绝了~~~

1. 项目简介

这个项目是基于立创天空星GD32F407VET6的桌面便携式电子小摆件,显示时间日期,也可以是简单的掌上游戏机,无聊的适合玩玩简单解压的小游戏。因为最初设计包含了电子琴功能,加了很多按键,还可以作为电子琴使用。支持充电管理,不用一直插着线供电了,走到哪带到哪。还可以用来学习GD32的开发,这也是我设计这个小玩意的初衷,我参照嘉立创的实战派,给它取个名字,就叫蓝莓派1.0,这也是我第一次做这个,有很多不完善的地方,请大佬帮忙指正,谢谢!!

2. 外设使用

2.1. 1路ADC:采集锂电池电压显示电池电量

GD32F407VET6一共有16个外部ADC通道,我们将光敏电阻模块的AO引脚连接在PC1引脚上,查找数据手册的引脚定义可知,PC1的附加功能有ADC0的通道11,ADC1的通道11,ADC2的通道11。

2.2. 2个串口

2.2.1. 板载串口1调试使用

2.2.2. 扩展板串口2(蓝牙)

2.3. 7路IO输出

2.3.1. 一个无源蜂鸣器

2.3.2. 两个RGB灯6个IO

2.3.3. 1路IO输出开关机

开机POWER_ON,高电平有效

2.4. 1个SPI:W25Q64保存汉字库

2.5. 10个IO输入:

2.5.1. 8个按键输入检测

2.5.2. 锂电池充电CHRG,充电完成的STDBY

2.6. RTC:闹钟日历时间

GD32F4 RTC 闹钟与自动唤醒中断配置深入解析-物联沃-IOTWORD物联网

GD32实战14__RTC_gd32 rtc电路-CSDN博客

32.768KHz

CR2012

2.7. 1个IIC:OLED多级菜单

3. 蜂鸣器播放音乐基本原理

3.1. 音调

频率决定音调,占空比决定音量,这里只用到频率,占空比改变没用,保持50%就行。

PWM的周期为1ms,高电平时间为0.5ms,低电平时间为0.5ms,则频率就为1kHz,占空比就为百分之五十。

pwm的调节作用来源于对“占周期”的宽度控制,“占周期”变宽,输出的能量就会提高,通过阻容变换电路所得到的平均电压值也会上升,“占周期”变窄,输出的电压信号的电压平均值就会降低,通过阻容变换电路所得到的平均电压值也会下降

也就是,在一定的频率下,通过不同的占空比即可得到不同的输出模拟电压,由此,我们可以通过控制PWM输出频率控制蜂鸣器发出不同音调。

Tout为计算所得产生中断周期,其中arr为自动重装载值,为psc预分频,为Tclk对应时钟频率。

若要使音调对应定时器频率,以低音DO为例,将f=262,arr=1000-1, 带入上述公式,即可求出的psc值,修改不同的对应不同的音调。

由定时器周期计算公式我们可知,Tclk为定时器挂靠时钟总线频率,arr为设定定时器自动重装载值,f已知为不同音调频率,图中“dao”频率为262,通过计算得出psc赋给定时器以获得不同音调。

#define  proport          168000	//Tclk/(arr+1)=168000000/(1000)
 
//根据Tout= ((arr+1)*(psc+1))/Tclk推出psc值就是本句define定义的值,Tout为音调频率131Hz的倒数,Tclk=168MHz                                                
#define  L1       ((proport/262)-1)//低调 do 的频率
#define  L2       ((proport/296)-1)//低调 re 的频率
#define  L3       ((proport/330)-1)//低调 mi 的频率
#define  L4       ((proport/349)-1)//低调 fa 的频率
#define  L5       ((proport/392)-1)//低调 sol 的频率
#define  L6       ((proport/440)-1)//低调 la 的频率
#define  L7       ((proport/494)-1)//低调 si 的频率
                                               
#define  M1       ((proport/523)-1)//中调 do 的频率
#define  M2       ((proport/587)-1)//中调 re 的频率
#define  M3       ((proport/659)-1)//中调 mi 的频率
#define  M4       ((proport/699)-1)//中调 fa 的频率
#define  M5       ((proport/784)-1)//中调 sol的频率
#define  M6       ((proport/880)-1)//中调 la 的频率
#define  M7       ((proport/988)-1)//中调 si 的频率
 
#define  H1       ((proport/1048)-1)//高调 do 的频率
#define  H2       ((proport/1176)-1)//高调 re 的频率
#define  H3       ((proport/1320)-1)//高调 mi 的频率
#define  H4       ((proport/1480)-1)//高调 fa 的频率
#define  H5       ((proport/1640)-1)//高调 sol的频率
#define  H6       ((proport/1760)-1)//高调 la 的频率
#define  H7       ((proport/1976)-1)//高调 si 的频率
 
#define  Z0       0//

3.2. 音阶

点在上面为高音,没有点为中音,点在下面为低音

3.3. 音长

简谱对应音阶下无横线为一拍,有单横线为半拍,双横线为1/4拍。音阶数字后有点加半拍音长,有横线加一拍。

const uint16_t music4[]=
{
    //我的心总是不安。
    L6,25,L6,25,M3,25,M4,25,M3,25,M4,25,M3,50,L6,50,
    //哦,我现在已病人膏肓。
    Z0,50,L6,50,L5,25,L6,25,L6,25,L5,25,L6,50,L6,50,M1,100,
    //哎哟!难道,真的因你而疯狂吗?
    M4,100,M3,100,L6,50,L6,50,Z0,50,L6,50,L6,100,M3,25,M4,25,M3,25,M4,25,M3,50,L6,50,
    //嗯,我本来不是这种人,因你变成奇怪的人。
    Z0,50,L6,50,L5,25,L6,25,L6,25,L5,25,L6,50,L6,50,L6,100,Z0,50,L6,50,L5,25,L6,25,L6,25,L5,25,L6,50,L6,50,M1,100,
    //第一次呀变成这样的我
    H1,100,M7,50,M5,50,M5,50,M3,50,M3,50,M3,50,H1,100,M7,50,M5,50,M5,50,M3,50,M3,50,M5,150,
    //不管我怎么去否认
    Z0,50,M3,50,M3,50,M3,50,M5,50,M5,50,M5,50,M6,50,M7,50,
    //只因你太美,baby
    L6,25,L6,25,M3,50,M3,50,L6,100,Z0,100,Z0,50,L6,25,L6,25,L6,50,Z0,50,Z0,150,
    //只因你太美,baby
    Z0,50,L6,25,L6,25,M3,50,M3,50,L6,100,Z0,100,Z0,50,L6,25,L6,25,
    //只因你是在是太美,baby,
    L6,50,Z0,50,Z0,50,L6,25,L6,25,L6,50,L6,25,L6,25,M3,50,M3,50,L6,100,Z0,100,Z0,50,L6,25,L6,25,L6,50,Z0,50,
};

uint16_t length4 = sizeof(music4)/sizeof(music4[0]);
void music_play(const uint16_t* music_buf,uint16_t length)
{
    clear_buzzer_buf();
    OLED_DrawRectangle(122,8,3,56,2);//清除进度条
    buzzer_record_mode =1;//音符记录模式
    /*
      如果这里不把音乐的音符数据music_buf通过buzzer_beep_tone,
      存到待播放buzzer_beep_data[]里面去就会,
      就需要用while等一个音符播放完,才能播放下一个音符,这样程序暂时会卡死在这个while里面,
      无法相应其他的操作,比如刷新OLED的显示时间,但是如果用for把要播放的音符一次性都转移到待播放数组里面,
      然后再放在定时器中断里面一直扫描就行,牺牲空间换取了时间,这里也体现了任务调度的重要性,任务一多还是要用RTOS会更好一点
    */
    for(int i=0;(i<(length/2));i++)
    {
        buzzer_beep_data_write_pos++;//记录模式,记录每个按下的音符
        if (buzzer_beep_data_write_pos >= MAX_BUZZER_DATA_SIZE)
                buzzer_beep_data_write_pos = 0;
        buzzer_beep_tone(music_buf[i*2],100,(music_buf[i*2+1]),0);//赋值
    }
    buzzer_record_play = 1;//记录完就播放
    buzzer_record_mode = 2;//记录播放进入播放音乐模式
}

4. 按键扫描消抖实现长短按,双击,连击

4.1. 头文件宏定义

#define CONFIRM_TIME 10//按键消抖时间窗10ms
#define LONGPRESS_TIME 1000//长按时间窗1000ms
#define FAST_PRESS_TIME 10//长按触发执行10ms
#define DOUBLE_PRESS_TIME 130//连击间隔130ms

#define KEY1_SHORT_PRESS  0X01
#define KEY1_DOUBLE_PRESS 0x11
#define KEY1_FAST_PRESS   0X71
#define KEY1_LONG_PRESS   0X81

#define KEY2_SHORT_PRESS  0X02
#define KEY2_DOUBLE_PRESS 0x12
#define KEY2_FAST_PRESS   0X72
#define KEY2_LONG_PRESS   0X82

#define KEY3_SHORT_PRESS  0X03
#define KEY3_DOUBLE_PRESS 0x13
#define KEY3_FAST_PRESS   0X73
#define KEY3_LONG_PRESS   0X83

#define KEY4_SHORT_PRESS  0X04
#define KEY4_FAST_PRESS   0X74
#define KEY4_LONG_PRESS   0X84

#define KEY5_SHORT_PRESS  0X05
#define KEY5_FAST_PRESS   0X75
#define KEY5_LONG_PRESS   0X85

#define KEY_NUM_MAX  (sizeof(g_gpioList) / sizeof(g_gpioList[0]))

4.2. 初始化

typedef struct
{
    rcu_periph_enum rcu;
    uint32_t port;
    uint32_t pin;
} Key_GPIO_t;

/*按键初始化和扫描用到的结构体数组*/
static Key_GPIO_t g_gpioList[]=
{
    {RCU_GPIOC,GPIOC,GPIO_PIN_12},//keyl
    {RCU_GPIOC,GPIOC,GPIO_PIN_11},//key2
    {RCU_GPIOC,GPIOC,GPIO_PIN_13},//key3
    {RCU_GPIOE,GPIOE,GPIO_PIN_5},//key4
    {RCU_GPIOC,GPIOC,GPIO_PIN_2}//key5
};

/**
 * @function  KeyDrvInit
 * @brief 按键初始化
 * @param none
 * @return none
 */
void KeyDrvInit(void)
{
    for(uint8_t i=0; i

4.3. 调用

/**
 * @function  GetKeyVal
 * @brief 获取按键状态值
 * @param none
 * @return 按下返回按键值,没按下返回0
 */
uint8_t GetKeyVal(void)
{
    uint8_t res=0;
    for(uint8_t i=0; i=255)g_key_cout=0;
        break;
    case KEY4_FAST_PRESS:
        if(++g_key_cout>=255)g_key_cout=0;
        break;
    case KEY5_SHORT_PRESS:
        if(--g_key_cout<=0)g_key_cout=255;
        break;
    case KEY5_FAST_PRESS:
        if(--g_key_cout<=0)g_key_cout=255;
        break;

    case KEY1_DOUBLE_PRESS:
        bsp_led_off(LED2_R);
        break;
    case KEY2_DOUBLE_PRESS:
        bsp_led_off(LED2_B);
        break;
    case KEY3_DOUBLE_PRESS:
        bsp_led_off(LED2_G);
        break;
    default:
        break;
    }
}

4.4. 短按(非阻塞)

typedef struct
{
    uint16_t key_count1;
    uint8_t key_short_flag;
} Key_Info_t;

static Key_Info_t g_keyInfo[KEY_NUM_MAX];

/**
 * @function  KeyScan
 * @brief 按键动态扫描
 * @param keyIndex 按键索引值
 * @return 返回按键键值
 */
static uint8_t KeyScan(uint8_t keyIndex)
{
    uint8_t keyPress;
    keyPress = gpio_input_bit_get(g_gpioList[keyIndex].port,g_gpioList[keyIndex].pin);
    if (keyPress)
    {
        g_keyInfo[keyIndex].key_count1=0;
        g_keyInfo[keyIndex].key_short_flag=0;
    }
    else if(!g_keyInfo[keyIndex].key_short_flag)
    {
        g_keyInfo[keyIndex].key_count1++;
        if(g_keyInfo[keyIndex].key_count1 > CONFIRM_TIME)
        {
            g_keyInfo[keyIndex].key_short_flag=1;
            g_keyInfo[keyIndex].key_count1=0;
            return (keyIndex+1);
        }
    }
    return 0;
}

4.5. 长短按

typef struct
{
    uint32_t key_count;
    uint8_t key_lock_flag;
    uint8_t key_short_flag;
    uint8_t key_long_flag;
} Key_Info_t;

static Key_Info_t g_keyInfo[KEY_NUM_MAX];
/**
 * @function  KeyScan
 * @brief 按键动态扫描
 * @param keyIndex 按键索引值
 * @return 返回按键键值
 */
static uint8_t KeyScan(uint8_t keyIndex)
{
    uint8_t keyPress;
    keyPress = gpio_input_bit_get(g_gpioList[keyIndex].port,g_gpioList[keyIndex].pin);
    if (keyPress)
    {
        g_keyInfo[keyIndex].key_count=0;
        g_keyInfo[keyIndex].key_lock_flag=0;
        g_keyInfo[keyIndex].key_long_flag=0;
        if(g_keyInfo[keyIndex].key_short_flag)
        {
            g_keyInfo[keyIndex].key_short_flag=0;
            return (keyIndex+1);
        }
    }
    else if((!g_keyInfo[keyIndex].key_lock_flag)&&(g_keyInfo[keyIndex].key_long_flag==0))
    {
        g_keyInfo[keyIndex].key_count++;
        if(g_keyInfo[keyIndex].key_count > CONFIRM_TIME)
        {
            g_keyInfo[keyIndex].key_short_flag=1;
        }
        if(g_keyInfo[keyIndex].key_count > LONGPRESS_TIME)
        {
            g_keyInfo[keyIndex].key_count=0;
            g_keyInfo[keyIndex].key_short_flag=0;
            g_keyInfo[keyIndex].key_long_flag=1;
            return (keyIndex+0x81);
        }
    }
    return 0;
}

4.6. 连发

typedef struct
{
    uint16_t key_count1;
    uint16_t key_count2;
    uint8_t key_short_flag;
    uint8_t key_long_flag;
} Key_Info_t;

static Key_Info_t g_keyInfo[KEY_NUM_MAX];
/**
 * @function  KeyScan
 * @brief 按键动态扫描
 * @param keyIndex 按键索引值
 * @return 返回按键键值
 */
static uint8_t KeyScan(uint8_t keyIndex)
{
    uint8_t keyPress;
    keyPress = gpio_input_bit_get(g_gpioList[keyIndex].port,g_gpioList[keyIndex].pin);
    if (keyPress)
    {
        g_keyInfo[keyIndex].key_count1=0;
        g_keyInfo[keyIndex].key_short_flag=0;
        g_keyInfo[keyIndex].key_long_flag=0;
    }
    else if(!g_keyInfo[keyIndex].key_short_flag)
    {
        g_keyInfo[keyIndex].key_count1++;
        if(g_keyInfo[keyIndex].key_count1 > CONFIRM_TIME)
        {
            g_keyInfo[keyIndex].key_short_flag=1;
            g_keyInfo[keyIndex].key_count1=0;
            return (keyIndex+1);
        }
    }
    else if(g_keyInfo[keyIndex].key_count1 < LONGPRESS_TIME)
    {
        g_keyInfo[keyIndex].key_count1++;
    }
    else if(!g_keyInfo[keyIndex].key_long_flag)
    {
        g_keyInfo[keyIndex].key_long_flag=1;
        return (keyIndex+0x81);
    }
    else
    {
        g_keyInfo[keyIndex].key_count2++;
        if(g_keyInfo[keyIndex].key_count2 > FAST_PRESS_TIME)
        {
            g_keyInfo[keyIndex].key_count2 =0;
            return (keyIndex+0x71);
        }
    }
    return 0;
}

4.7. 长短按+多击+连发

/**
 * @function  KeyScan
 * @brief 按键动态扫描
 * @param keyIndex 按键索引值
 * @return 返回按键键值
 */
static uint8_t KeyScan(uint8_t keyIndex)
{
    uint8_t keyPress;
    keyPress = gpio_input_bit_get(g_gpioList[keyIndex].port,g_gpioList[keyIndex].pin);
    if (keyPress)
    {
        g_keyInfo[keyIndex].key_count1=0;
        g_keyInfo[keyIndex].key_short_flag=0;
        g_keyInfo[keyIndex].key_long_flag=0;
        if(g_keyInfo[keyIndex].key_times > 0)
        {
            g_keyInfo[keyIndex].key_count3++;
            if(g_keyInfo[keyIndex].key_count3 > DOUBLE_PRESS_TIME)
            {
                if(g_keyInfo[keyIndex].key_times == 1)
                {
                    g_keyInfo[keyIndex].key_times = 0;
                    return (keyIndex+1);
                }
                else if(g_keyInfo[keyIndex].key_times == 2)
                {
                    g_keyInfo[keyIndex].key_times = 0;
                    return (keyIndex+0x11);
                }
                g_keyInfo[keyIndex].key_times = 0;
            }
        }
    }
    else if(!g_keyInfo[keyIndex].key_short_flag)
    {
        g_keyInfo[keyIndex].key_count1++;
        if(g_keyInfo[keyIndex].key_count1 > CONFIRM_TIME)
        {
            g_keyInfo[keyIndex].key_short_flag=1;
            g_keyInfo[keyIndex].key_count1=0;
            g_keyInfo[keyIndex].key_times++;
            g_keyInfo[keyIndex].key_count3 = 0;
        }
    }
    else if(g_keyInfo[keyIndex].key_count1 < LONGPRESS_TIME)
    {
        g_keyInfo[keyIndex].key_count1++;
    }
    else if(!g_keyInfo[keyIndex].key_long_flag)
    {
        g_keyInfo[keyIndex].key_long_flag=1;
        g_keyInfo[keyIndex].key_times = 0;
        return (keyIndex+0x81);
    }
    else
    {
        g_keyInfo[keyIndex].key_count2++;
        if(g_keyInfo[keyIndex].key_count2 > FAST_PRESS_TIME)
        {
            g_keyInfo[keyIndex].key_count2 =0;
            return (keyIndex+0x71);
        }
    }
    return 0;
}

5. 项目总结

这个项目大概花了两个月的时间,因为在边实习,边搞,所以都是挤时间做,硬件也遇到一些问题,主要是软件代码方面花的时间最多,这个项目,看起来简单,其实一点也不容易,就是因为容易,所以我才精益求精,支持双击、长短按、连击、记录音符长短、暂停播放、加速减速播放、动画和时间显示、蜂鸣器播放音乐、按键扫描消抖互不干扰,几乎没用到delay,全是动态扫描,没有移植操作系统,后续升级可能会移植,上操作系统还是要简单些,OLED用的硬件iic,还在很多地方用到了c++面向对象的编程思维,这是我第一次用这种编程思维,用了都说好。

这个项目真的是我认真做了很久的项目,一有空就埋在电脑前搞,也有过崩溃的时候,一个bug能困惑我好几天,吃饭睡觉都在想,因为没用操作系统,东西一多,逻辑就有点乱,bug层出不穷,但是解决之后发现,不过如此,哈哈哈。

那个写乐谱让蜂鸣器唱歌,和OLED显示动画给我搞得好累的,一个kunkun跳舞119帧,取模的时候快崩溃了哈哈哈,还好最后也像模像样的播放了。

 

6. 功能演示

具体功能演示见B站视频:

接着奏乐,接着舞♫₍•◡•₎♫_哔哩哔哩_bilibili

设计图

未生成预览图,请在编辑器重新保存一次

BOM

暂无BOM

附件

序号文件名称下载次数
暂无数据
克隆工程
添加到专辑
0
0
分享
侵权投诉

工程成员

评论

全部评论(1
按时间排序|按热度排序
粉丝0|获赞0
相关工程
暂无相关工程

底部导航