本来想自己写库的,但是既然逐飞开源了,那就用逐飞库吧!不思进取,只等开源(
关于配置教程可以看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);
编译,烧录,成功!