MSPM0G3507学习笔记(四)逐飞库的下载与配置,led,按键(短按长按与双击)

发布于:2025-07-05 ⋅ 阅读:(17) ⋅ 点赞:(0)

本来想自己写库的,但是既然逐飞开源了,那就用逐飞库吧!不思进取,只等开源(

关于配置教程可以看https://blog.csdn.net/qq_23220445/article/details/148502065?spm=1001.2014.3001.5501
重置版适配逐飞库,见https://blog.csdn.net/qq_23220445/article/details/149125397?spm=1001.2014.3001.5501

一、逐飞库下载与配置

前往https://gitee.com/seekfree/MSPM0G3507_Library,点击右侧的克隆/下载,下载到本地。

最简单的方法是在想要保存的目录下,右键,选择在终端中打开,然后输入git clone https://gitee.com/seekfree/MSPM0G3507_Library.git,回车后即可克隆代码。

前往MSPM0G3507_Library\SeekFree_MSPM0G3507_Opensource_Library\project\keil目录,打开SeekFree_MSPM0G3507_Device_Library.uvprojx

打开魔术棒,着重检查以下几点:

1.逐飞库不用sysconfig,确保user这里不要运行syscfg的脚本

2.烧录器选对,我的是daplink

点击setting,这边选择any

3.如图找到启动汇编文件,把堆栈大小改大一些,现在是1kb,我习惯改为0x00001000,省的因为爆堆栈进硬件错误的回调函数。

4.output中勾选产生hex文件,可以方便串口uniflash烧录以及pyocd烧录。

5.确保晶振频率填写正确,请查看原理图。我买的最小核心板是48Mhz的,因此这里填写48。填写不对,你的程序将卡死在clock_init(SYSTEM_CLOCK_80M);这句话里面,别问我怎么知道的(

此时应该可以直接编译烧录成功。

导入vscode的eide,同样能正常编译、烧录,至此环境配置结束。

二、gpio的使用——led

逐飞的gpio函数如下,还是很好懂怎么使用的。

void        gpio_set_level              (gpio_pin_enum pin, const uint8 dat);
uint8       gpio_get_level              (gpio_pin_enum pin);
void        gpio_toggle_level           (gpio_pin_enum pin);
void        gpio_set_dir                (gpio_pin_enum pin, gpio_dir_enum dir, gpio_mode_enum mode);
void        gpio_init                   (gpio_pin_enum pin, gpio_dir_enum dir, const uint8 dat, gpio_mode_enum mode);

先是经典的点灯。主函数修改为:

#include "zf_common_headfile.h"

// **************************** 代码区域 ****************************

int main (void)
{
    clock_init(SYSTEM_CLOCK_80M);   // 时钟配置及系统初始化<务必保留>
    debug_init();                   // 调试串口信息初始化
    // 此处编写用户代码 例如外设初始化代码等

    // 初始化PB2和PB3为推挽输出,初始为低电平
    gpio_init(B2, GPO, GPIO_LOW, GPO_PUSH_PULL);
    gpio_init(B3, GPO, GPIO_LOW, GPO_PUSH_PULL);

    // 此处编写用户代码 例如外设初始化代码等
    while(true)
    {
        gpio_toggle_level(B2);
        gpio_toggle_level(B3);// 切换PB2和PB3的电平状态
        system_delay_ms(500); // 延时
    }
}

编译,烧录,看到核心板灯闪烁,频率正确。

三、定时器与外部中断的使用——按键的短按、长按与双击

然后是按键的短按,长按与双击的判定,使用外部中断。

逐飞的按键感觉写的通用性没有那么强,于是我打算重写一个,在zf_device里面新建key.c .h,并将其添加到工程中,并将key.h和iar.h添加到zf_common_headfile.h中

为了获取时间戳,修改逐飞的zf_driver_delay.c如下:

#include "zf_driver_delay.h"

//-------------------------------------------------------------------------------------------------------------------
// 函数简介     system 延时函数 ms 级别
// 参数说明     time        需要延时的时间 ms 级别
// 返回参数     void
// 使用示例     system_delay_ms(100);
// 备注信息     
//-------------------------------------------------------------------------------------------------------------------
void system_delay_ms (uint32 ms)
{
    if (ms == 0)
        return;

    uint32_t start_tick = get_tick();
    while ((get_tick() - start_tick) < ms)
    {
        // 等待毫秒级延时
        __WFE(); // 等待事件,降低功耗
    }
}

//-------------------------------------------------------------------------------------------------------------------
// 函数简介     system 延时函数 us 级别
// 参数说明     time        需要延时的时间 us 级别
// 返回参数     void
// 使用示例     system_delay_us(100);
// 备注信息     受限于程序运行跳转 此延时会比输入值高出一些
//-------------------------------------------------------------------------------------------------------------------
void system_delay_us (uint32 us)
{
    if (us == 0)
        return;

    // 对于微秒延时,使用循环计数方式
    // 80MHz时钟,每个周期12.5ns,需要80个周期约等于1us
    uint32_t cycles = (CPUCLK_FREQ / 1000000) * us;

    // 考虑函数调用开销,进行补偿
    if (cycles > 20)
    {
        cycles -= 20; // 减去函数调用开销
    }

    delay_cycles(cycles);
}

//-------------------------------------------------------------------------------------------------------------------
// 函数简介     system delay 初始化
// 参数说明     void
// 返回参数     void
// 使用示例     system_delay_init();
// 备注信息     本函数由 clock_init 内部调用
//-------------------------------------------------------------------------------------------------------------------
void system_delay_init (void)
{
    DL_SYSTICK_config(CPUCLK_FREQ / 1000);
}


static volatile uint32_t tick_count = 0;

void SysTick_Handler(void)
{
    tick_count++;
}

uint32_t get_tick(void)
{
    return tick_count;
}



zf_driver_delay后面添加定义uint32_t get_tick(void);

移除逐飞外部中断中的gpio初始化硬编码,因为和我的按键配置不一样。

编写key.c:

#include "key.h"
#include "stdlib.h"
// 按键控制数组
static key_control_t key_list[MAX_KEY_COUNT];
static uint8 key_count = 0;

// 查找按键索引
static uint8 find_key_index(gpio_pin_enum pin)
{
    for (uint8 i = 0; i < key_count; i++)
    {
        if (key_list[i].pin == pin && key_list[i].is_active)
        {
            return i;
        }
    }
    return 0xFF; // 未找到
}

// 按键初始化函数
uint8 key_init(gpio_pin_enum pin, key_trigger_enum trigger,
               key_callback_t short_press_cb,
               key_callback_t long_press_cb,
               key_callback_t double_click_cb)
{
    if (key_count >= MAX_KEY_COUNT)
    {
        return 0xFF; // 超出最大按键数量
    }

    // 初始化GPIO
    gpio_init(pin, GPI, GPIO_LOW, (trigger == KEY_TRIGGER_HIGH) ? GPI_PULL_DOWN : GPI_PULL_UP);

    // 配置按键结构体
    key_list[key_count].pin = pin;
    key_list[key_count].trigger = trigger;
    key_list[key_count].state = KEY_IDLE;
    key_list[key_count].press_time = 0;
    key_list[key_count].release_time = 0;
    key_list[key_count].click_count = 0;
    key_list[key_count].process_flag = 0;
    key_list[key_count].is_active = 1;

    // 保存回调函数
    key_list[key_count].short_press_callback = short_press_cb;
    key_list[key_count].long_press_callback = long_press_cb;
    key_list[key_count].double_click_callback = double_click_cb;

    // 初始化外部中断,传递按键索引
    uint8 *key_index = (uint8 *)malloc(sizeof(uint8));
    *key_index = key_count;
    exti_init(pin, EXTI_TRIGGER_BOTH, key_exti_handler, key_index);

    return key_count++;
}

// 按键外部中断回调函数
void key_exti_handler(uint32 event, void *ptr)
{
    uint8 key_index = *(uint8 *)ptr;

    if (key_index >= key_count || !key_list[key_index].is_active)
    {
        return;
    }

    key_control_t *key = &key_list[key_index];

    // 读取GPIO实际电平状态
    uint8 current_level = gpio_get_level(key->pin);
    uint8 is_pressed = 0;

    // 根据触发类型判断按键是否按下
    if (key->trigger == KEY_TRIGGER_HIGH)
    {
        is_pressed = current_level; // 高电平触发:高电平=按下,低电平=释放
    }
    else
    {
        is_pressed = !current_level; // 低电平触发:低电平=按下,高电平=释放
    }

    if (is_pressed)
    {
        // 按键按下
        key->press_time = get_tick();
        key->state = KEY_PRESS;
    }
    else
    {
        // 按键释放
        if (key->state == KEY_PRESS) // 只有在按下状态时才处理释放
        {
            key->release_time = get_tick();
            uint32 press_duration = key->release_time - key->press_time;

            if (press_duration >= KEY_LONG_PRESS_TIME)
            {
                // 长按
                key->state = KEY_LONG_PRESS;
                key->process_flag = 1;
                key->click_count = 0;
            }
            else if (press_duration >= KEY_DEBOUNCE_TIME) // 添加最小按键时间,防止抖动
            {
                // 短按
                key->state = KEY_RELEASE;
                key->click_count++;

                if (key->click_count >= 2)
                {
                    // 双击
                    key->state = KEY_DOUBLE_CLICK;
                    key->process_flag = 1;
                    key->click_count = 0;
                }
            }
            else
            {
                // 按键时间太短,可能是抖动,忽略
                key->state = KEY_IDLE;
            }
        }
    }
}

// 扫描单个按键
void key_scan_single(uint8 key_index)
{
    if (key_index >= key_count || !key_list[key_index].is_active)
    {
        return;
    }

    key_control_t *key = &key_list[key_index];

    if (key->state == KEY_DOUBLE_CLICK && key->process_flag)
    {
        if (key->double_click_callback != NULL)
        {
            key->double_click_callback();
        }
        key->process_flag = 0;
        key->state = KEY_IDLE;
    }
    else if (key->state == KEY_LONG_PRESS && key->process_flag)
    {
        if (key->long_press_callback != NULL)
        {
            key->long_press_callback();
        }
        key->process_flag = 0;
        key->state = KEY_IDLE;
    }
    else if (key->state == KEY_RELEASE)
    {
        uint32 current_time = get_tick();
        if (current_time - key->release_time > KEY_DOUBLE_CLICK_TIME && key->click_count == 1)
        {
            key->state = KEY_SHORT_PRESS;

            if (key->short_press_callback != NULL)
            {
                key->short_press_callback();
            }

            key->click_count = 0;
            key->state = KEY_IDLE;
        }
    }
}

// 扫描所有按键
void key_scan_all(void)
{
    for (uint8 i = 0; i < key_count; i++)
    {
        key_scan_single(i);
    }
}

key.h:

#ifndef _key_h_
#define _key_h_

#include "zf_common_typedef.h"
#include "zf_driver_gpio.h"
#include "zf_driver_exti.h"
#include "zf_driver_delay.h"

#define MAX_KEY_COUNT           8       // 最大支持按键数量
#define KEY_LONG_PRESS_TIME     500     // 长按时间阈值(ms)
#define KEY_DOUBLE_CLICK_TIME   200     // 双击间隔时间阈值(ms)
#define KEY_DEBOUNCE_TIME       20      // 防抖时间(ms)

// 按键状态定义
typedef enum {
    KEY_IDLE = 0,
    KEY_PRESS,
    KEY_RELEASE,
    KEY_SHORT_PRESS,
    KEY_LONG_PRESS,
    KEY_DOUBLE_CLICK
} key_state_enum;

// 按键触发类型
typedef enum {
    KEY_TRIGGER_LOW = 0,
    KEY_TRIGGER_HIGH
} key_trigger_enum;

// 按键回调函数类型定义
typedef void (*key_callback_t)(void);

// 按键控制结构体
typedef struct {
    gpio_pin_enum pin;                  // 按键引脚
    key_trigger_enum trigger;           // 触发类型
    key_state_enum state;               // 按键状态
    uint32 press_time;                  // 按下时间戳
    uint32 release_time;                // 释放时间戳
    uint8 click_count;                  // 点击次数
    uint8 process_flag;                 // 处理标志
    uint8 is_active;                    // 是否激活
    
    // 回调函数
    key_callback_t short_press_callback;
    key_callback_t long_press_callback;
    key_callback_t double_click_callback;
} key_control_t;

// 函数声明
uint8 key_init(gpio_pin_enum pin, key_trigger_enum trigger, 
               key_callback_t short_press_cb, 
               key_callback_t long_press_cb, 
               key_callback_t double_click_cb);
void key_exti_handler(uint32 event, void *ptr);
void key_scan_all(void);
void key_scan_single(uint8 key_index);

#endif

想修改按键参数,在key.h里面修改即可:

然后在主函数写调用,key_init需要传入配置的按键引脚,按下是高电平还是低电平(我的按键按下是高电平,用KEY_TRIGGER_HIGH,会自动根据这个来配置正确的gpio),以及短按、长按、双击的回调函数。

本代码支持多个按键,确保写正确的回调函数即可。

#include "zf_common_headfile.h"

// **************************** 代码区域 ****************************

int main (void)
{
    clock_init(SYSTEM_CLOCK_80M);   // 时钟配置及系统初始化<务必保留>
    debug_init();                   // 调试串口信息初始化
                                    // 配置按键 ,下拉输入,我的按键按下是高电平
    // 配置按键,A18引脚,按下为高电平,三个回调函数
    key_init(A18, KEY_TRIGGER_HIGH, short_press_handler_1, long_press_handler_1, double_click_handler_1);
    // 初始化LED引脚
    gpio_init(B2, GPO, GPIO_LOW, GPO_PUSH_PULL);
    gpio_init(B3, GPO, GPIO_LOW, GPO_PUSH_PULL);
    while(true)
    {
        key_scan_all();          // 按键扫描
        system_delay_ms(10); // 延时10ms
    }
}

之后我们在isr.c的最后,加入这三个回调函数,编写你自己的逻辑。并在isr.h里面申明。我的逻辑是:短按翻转电平,长按满闪2次,长按快闪3次。

void short_press_handler_1(void)
{
    gpio_toggle_level(B2);
    gpio_toggle_level(B3);
}

// 长按回调函数 - 慢速闪烁2下
void long_press_handler_1(void)
{
    for (uint8 i = 0; i < 4; i++)
    {
        gpio_toggle_level(B2);
        gpio_toggle_level(B3);
        system_delay_ms(500);
    }
}

// 双击回调函数 - 快速闪烁3下
void double_click_handler_1(void)
{
    for (uint8 i = 0; i < 6; i++)
    {
        gpio_toggle_level(B2);
        gpio_toggle_level(B3);
        system_delay_ms(100);
    }
}
void short_press_handler_1(void);
void long_press_handler_1(void);
void double_click_handler_1(void);

编译,烧录,成功!