電賽備賽中,打算系統過一遍MSPM0G3507的各個部分,同時把過程記錄下來。本系列以代碼全部能用+復用性、可移植性高為目的。本系列所有的代碼會開源至github,如果覺得有用,請點個贊/給我的github倉庫點一顆star吧。
github地址:https://github.com/whyovo/MSPM0G3507Learning-Notes
(一)基礎配置:https://blog.csdn.net/qq_23220445/article/details/148502065?spm=1001.2014.3001.5501
1.工程介紹
所有代碼都在code中,device是設備相關代碼,hardware是底層相關代碼,ti是ti的庫函數。入口是main.c,general.h包含所有需要的頭文件。
2.led與按鍵使用方法
在led.c里面添加led,key.c里面添加按鍵即可,像這樣就添加了pb2與pb3這兩個led,pa18這一個按鍵,按鍵需要指明按下時是高電平還是低電平(即active_level)然后在main.c里面初始化即可直接使用
key_init();
led_init();
led支持開關、閃爍、呼吸燈與流水燈,分別調用以下函數即可:
void led_on(LED_Config *led);
void led_off(LED_Config *led);
void led_toggle(LED_Config *led);
void led_blink(LED_Config *led, uint32_t times, uint32_t delay_ms);
void led_breathing(LED_Config *led, uint32_t cycles, uint32_t duration_ms);
void led_water_flow(LED_Config *leds, uint8_t count, uint32_t delay_ms);
按鍵支持短按、長按與雙擊。
下面是一個綜合案例,根據不同的按鍵操作觸發不同的LED效果——短按讓LED1閃爍3次,長按讓LED1產生呼吸燈效果,雙擊觸發兩個LED的流水燈效果。
main.c:
#include "general.h"
#include <stdio.h>int main(void)
{SYSCFG_DL_init();key_init(); // 啟用按鍵初始化led_init(); // 啟用LED初始化while (1) {// 按鍵掃描并觸發對應LED效果KEY_State key_state = key_scan(&key_configs[0]);switch (key_state) {case KEY_SHORT_PRESS:// 短按:LED1閃爍3次,每次閃爍間隔200msled_blink(&led_configs[0], 3, 200);break;case KEY_LONG_PRESS:// 長按:LED1呼吸燈效果,2個周期,持續1000msled_breathing(&led_configs[0], 2, 1000);break;case KEY_DOUBLE_CLICK:// 雙擊:兩個LED流水燈效果led_water_flow(led_configs, 2, 300);break;default:// 無按鍵操作,不執行特殊效果break;}delay_ms(10);}
}
效果:
雙擊:流水燈效果
短按:LED1閃爍3次
長按:LED1呼吸燈2個周期,每個周期1秒
3.gpio使用方法
需要用gpio但不是led或者按鍵,用以下方法:先在gpio.c里面定義引腳,說明是輸出還是輸入,上拉還是下拉,以及初始電平
然后直接在主函數調用gpio_init(),即可一鍵配置,然后直接調用函數即可:
// 函數聲明
bool gpio_init_one(const GPIO_Config *config);
bool gpio_init_multiple(const GPIO_Config *configs, uint8_t count);
void gpio_set_pin(GPIO_Regs *port, uint32_t pin);
void gpio_clear_pin(GPIO_Regs *port, uint32_t pin);
void gpio_toggle_pin(GPIO_Regs *port, uint32_t pin);
void gpio_write_pin(GPIO_Regs *port, uint32_t pin, GPIO_Level level);
GPIO_Level gpio_read_pin(GPIO_Regs *port, uint32_t pin);
bool gpio_is_pin_set(GPIO_Regs *port, uint32_t pin);
void gpio_init(void);
4.部分代碼參考
這里放led與key的代碼,詳細見github倉庫里面的tjuwyh這個文件夾。led.c
#include "led.h"
LED_Config led_configs[] = {{.port = GPIOB,.pin = DL_GPIO_PIN_2,.initialized = false}, // LED1{.port = GPIOB,.pin = DL_GPIO_PIN_3,.initialized = false}, // LED2
};void led_init(void)
{for (int i = 0; i < sizeof(led_configs) / sizeof(led_configs[0]); i++) {// 創建臨時GPIO配置結構體GPIO_Config gpio_config = {.port = led_configs[i].port,.pin = led_configs[i].pin,.dir = GPIO_DIR_OUTPUT,.pull = GPIO_PULL_NONE,.init_level = GPIO_HIGH // 默認高電平};// 直接調用led_init_one初始化LED和GPIOif (!led_init_one(&led_configs[i], &gpio_config)) {// 初始化失敗,可以添加錯誤處理}}
}// LED初始化
bool led_init_one(LED_Config *led, const GPIO_Config *gpio_config)
{if (!led || !gpio_config) return false;// 檢查GPIO配置是否為輸出模式if (gpio_config->dir != GPIO_DIR_OUTPUT) {return false;}// 初始化GPIOif (!gpio_init_one(gpio_config)) {return false;}// 配置LED結構體led->port = gpio_config->port;led->pin = gpio_config->pin;led->initialized = true;return true;
}// 檢查LED是否已初始化
static bool led_is_initialized(LED_Config *led)
{return led && led->initialized;
}void led_on(LED_Config *led)
{if (!led_is_initialized(led)) return;gpio_clear_pin(led->port, led->pin); // 共陰極LED,低電平點亮
}void led_off(LED_Config *led)
{if (!led_is_initialized(led)) return;gpio_set_pin(led->port, led->pin); // 共陰極LED,高電平熄滅
}void led_toggle(LED_Config *led)
{if (!led_is_initialized(led)) return;gpio_toggle_pin(led->port, led->pin);
}void led_blink(LED_Config *led, uint32_t times, uint32_t delay_ms_val)
{if (!led_is_initialized(led)) return;for (uint32_t i = 0; i < times; i++) {led_on(led);delay_ms(delay_ms_val);led_off(led);delay_ms(delay_ms_val);}
}void led_breathing(LED_Config *led, uint32_t cycles, uint32_t duration_ms)
{if (!led_is_initialized(led)) return;// 呼吸燈參數配置const uint32_t steps = 100; // 每個方向的步數const uint32_t pwm_period_us = 1000; // PWM周期1msconst uint32_t step_delay_ms = duration_ms / (steps * 2); // 每步延時for (uint32_t cycle = 0; cycle < cycles; cycle++) {// 漸亮過程for (uint32_t i = 1; i <= steps; i++) {// 計算占空比 (1-100%)uint32_t duty_percent = (i * 100) / steps;uint32_t on_time_us = (pwm_period_us * duty_percent) / 100;uint32_t off_time_us = pwm_period_us - on_time_us;// PWM周期數,確保有足夠的時間看到效果uint32_t pwm_cycles = (step_delay_ms * 1000) / pwm_period_us;if (pwm_cycles < 1) pwm_cycles = 1;// 執行PWM - 使用GPIO函數for (uint32_t j = 0; j < pwm_cycles; j++) {if (on_time_us > 0) {gpio_clear_pin(led->port, led->pin); // LED點亮delay_us(on_time_us);}if (off_time_us > 0) {gpio_set_pin(led->port, led->pin); // LED熄滅delay_us(off_time_us);}}}// 漸暗過程for (uint32_t i = steps; i >= 1; i--) {// 計算占空比 (100%-1%)uint32_t duty_percent = (i * 100) / steps;uint32_t on_time_us = (pwm_period_us * duty_percent) / 100;uint32_t off_time_us = pwm_period_us - on_time_us;// PWM周期數uint32_t pwm_cycles = (step_delay_ms * 1000) / pwm_period_us;if (pwm_cycles < 1) pwm_cycles = 1;// 執行PWM - 使用GPIO函數for (uint32_t j = 0; j < pwm_cycles; j++) {if (on_time_us > 0) {gpio_clear_pin(led->port, led->pin); // LED點亮delay_us(on_time_us);}if (off_time_us > 0) {gpio_set_pin(led->port, led->pin); // LED熄滅delay_us(off_time_us);}}}}// 確保最后LED是關閉狀態led_off(led);
}void led_water_flow(LED_Config *leds, uint8_t count, uint32_t delay_ms_val)
{if (!leds) return;for (uint8_t i = 0; i < count; i++) {if (led_is_initialized(&leds[i])) {led_on(&leds[i]);delay_ms(delay_ms_val);led_off(&leds[i]);}}
}
led.h
#ifndef __LED_H
#define __LED_H#if __has_include("gpio.h")
#include "gpio.h"#else
// 如果沒有定義GPIO模塊,使用其他庫,以HAL庫為例
#include "stm32f1xx_hal.h" // 根據實際MCU型號調整// 定義GPIO類型別名
typedef GPIO_TypeDef GPIO_Regs;// 將自定義GPIO函數映射到HAL庫函數
#define gpio_set_pin(port, pin) HAL_GPIO_WritePin(port, pin, GPIO_PIN_SET)
#define gpio_clear_pin(port, pin) HAL_GPIO_WritePin(port, pin, GPIO_PIN_RESET)
#define gpio_toggle_pin(port, pin) HAL_GPIO_TogglePin(port, pin)
#define gpio_write_pin(port, pin, level) HAL_GPIO_WritePin(port, pin, (level) ? GPIO_PIN_SET : GPIO_PIN_RESET)
#define gpio_read_pin(port, pin) (HAL_GPIO_ReadPin(port, pin) == GPIO_PIN_SET ? 1 : 0)
#define gpio_is_pin_set(port, pin) (HAL_GPIO_ReadPin(port, pin) == GPIO_PIN_SET)// 定義GPIO配置結構體(簡化版)
typedef struct {GPIO_Regs *port;uint32_t pin;uint8_t dir; // 方向uint8_t init_level; // 初始電平uint8_t pull; // 上拉下拉
} GPIO_Config;// 定義枚舉值
#define GPIO_DIR_INPUT 0
#define GPIO_DIR_OUTPUT 1
#define GPIO_LOW 0
#define GPIO_HIGH 1// 提供兼容函數聲明
static inline bool gpio_init_one(const GPIO_Config *config)
{GPIO_InitTypeDef GPIO_InitStruct = {0};GPIO_InitStruct.Pin = config->pin;GPIO_InitStruct.Mode = (config->dir == GPIO_DIR_OUTPUT) ? GPIO_MODE_OUTPUT_PP : GPIO_MODE_INPUT;GPIO_InitStruct.Pull = GPIO_NOPULL; // 簡化處理GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;HAL_GPIO_Init(config->port, &GPIO_InitStruct);if (config->dir == GPIO_DIR_OUTPUT && config->init_level == GPIO_HIGH) {HAL_GPIO_WritePin(config->port, config->pin, GPIO_PIN_SET);}return true;
}
#endif#include "gpio.h"
#include "delay.h"// LED配置結構體
typedef struct {GPIO_Regs *port;uint32_t pin;bool initialized; // 標記是否已初始化
} LED_Config;// LED初始化和控制函數
bool led_init_one(LED_Config *led, const GPIO_Config *gpio_config);
void led_on(LED_Config *led);
void led_off(LED_Config *led);
void led_toggle(LED_Config *led);
void led_blink(LED_Config *led, uint32_t times, uint32_t delay_ms);
void led_breathing(LED_Config *led, uint32_t cycles, uint32_t duration_ms);
void led_water_flow(LED_Config *leds, uint8_t count, uint32_t delay_ms);
void led_init(void);// 便捷宏定義
#define LED_ON(led) led_on(led)
#define LED_OFF(led) led_off(led)
#define LED_TOGGLE(led) led_toggle(led)extern LED_Config led_configs[];#endif
key.c
#include "key.h"KEY_Config key_configs[] = {{.port = GPIOA,.pin = DL_GPIO_PIN_18,.active_level = KEY_ACTIVE_HIGH,.initialized = false}, // KEY1
};#define KEY_DEBOUNCE_TIME 20 // 消抖時間 20ms
#define KEY_LONG_PRESS_TIME 500 // 長按時間 500ms
#define KEY_DOUBLE_CLICK_TIME 300 // 雙擊間隔時間 300ms// 按鍵狀態定義
typedef enum {KEY_STATE_IDLE = 0, // 空閑狀態KEY_STATE_PRESSED, // 按下狀態KEY_STATE_RELEASED, // 釋放狀態KEY_STATE_DOUBLE_WAIT // 雙擊等待狀態
} KEY_StateMachine;void key_init(void)
{for (int i = 0; i < sizeof(key_configs) / sizeof(key_configs[0]); i++) {// 根據按鍵有效電平選擇合適的上拉/下拉配置GPIO_Pull pull_config;if (key_configs[i].active_level == KEY_ACTIVE_HIGH) {pull_config = GPIO_PULL_DOWN; // 高電平有效時使用下拉電阻} else {pull_config = GPIO_PULL_UP; // 低電平有效時使用上拉電阻}// 創建臨時GPIO配置結構體GPIO_Config gpio_config = {.port = key_configs[i].port,.pin = key_configs[i].pin,.dir = GPIO_DIR_INPUT,.pull = pull_config, // 根據按鍵有效電平配置上拉/下拉.init_level = GPIO_HIGH // 對輸入引腳無實際影響};// 直接調用key_init_one初始化按鍵和GPIOif (!key_init_one(&key_configs[i], &gpio_config, key_configs[i].active_level)) {// 初始化失敗,可以添加錯誤處理}}
} // KEY初始化
bool key_init_one(KEY_Config *key, const GPIO_Config *gpio_config, KEY_ActiveLevel active_level)
{if (!key || !gpio_config) return false;// 檢查GPIO配置是否為輸入模式if (gpio_config->dir != GPIO_DIR_INPUT) {return false;}// 初始化GPIOif (!gpio_init_one(gpio_config)) {return false;}// 配置KEY結構體key->port = gpio_config->port;key->pin = gpio_config->pin;key->active_level = active_level;key->initialized = true;// 初始化內部狀態key->last_time = 0;key->press_count = 0;key->long_press_flag = 0;return true;
}// 檢查KEY是否已初始化
static bool key_is_initialized(KEY_Config *key)
{return key && key->initialized;
}uint8_t key_read(KEY_Config *key)
{if (!key_is_initialized(key)) return 0;uint8_t pin_state = DL_GPIO_readPins(key->port, key->pin) ? 1 : 0;// 根據配置的有效電平返回按鍵狀態if (key->active_level == KEY_ACTIVE_HIGH) {return pin_state; // 高電平有效} else {return !pin_state; // 低電平有效}
}KEY_State key_scan(KEY_Config *key)
{if (!key_is_initialized(key)) return KEY_NONE;static KEY_StateMachine key_state = KEY_STATE_IDLE;static uint32_t press_start_time = 0;static uint32_t release_start_time = 0;static uint32_t last_scan_time = 0;uint32_t current_time = get_tick();// 每1ms掃描一次if (current_time - last_scan_time < 1) {return KEY_NONE;}last_scan_time = current_time;uint8_t key_pressed = key_read(key);static uint8_t last_key_state = 0;// 消抖處理if (key_pressed != last_key_state) {delay_ms(KEY_DEBOUNCE_TIME);if (key_read(key) != key_pressed) {return KEY_NONE; // 抖動,忽略}last_key_state = key_pressed;}switch (key_state) {case KEY_STATE_IDLE:if (key_pressed) {key_state = KEY_STATE_PRESSED;press_start_time = current_time;}break;case KEY_STATE_PRESSED:if (!key_pressed) {// 按鍵釋放,判斷按下時間uint32_t press_duration = current_time - press_start_time;if (press_duration >= KEY_LONG_PRESS_TIME) {// 長按事件,直接回到空閑狀態key_state = KEY_STATE_IDLE;return KEY_LONG_PRESS;} else {// 進入釋放狀態,等待可能的雙擊key_state = KEY_STATE_RELEASED;release_start_time = current_time;}}break;case KEY_STATE_RELEASED:if (key_pressed) {// 在釋放狀態內再次按下,進入雙擊等待狀態key_state = KEY_STATE_DOUBLE_WAIT;press_start_time = current_time;} else if (current_time - release_start_time >= KEY_DOUBLE_CLICK_TIME) {// 超時未再次按下,判定為單擊key_state = KEY_STATE_IDLE;return KEY_SHORT_PRESS;}break;case KEY_STATE_DOUBLE_WAIT:if (!key_pressed) {// 雙擊的第二次按下釋放key_state = KEY_STATE_IDLE;return KEY_DOUBLE_CLICK;} else if (current_time - press_start_time >= KEY_LONG_PRESS_TIME) {// 如果第二次按下時間過長,按長按處理key_state = KEY_STATE_IDLE;return KEY_LONG_PRESS;}break;}return KEY_NONE;
}
key.h
#ifndef __KEY_H
#define __KEY_H#include "sys_init.h"
#include "gpio.h"
#include "delay.h"typedef enum {KEY_NONE = 0,KEY_SHORT_PRESS,KEY_LONG_PRESS,KEY_DOUBLE_CLICK
} KEY_State;typedef enum {KEY_ACTIVE_LOW = 0, // 按鍵按下為低電平KEY_ACTIVE_HIGH = 1 // 按鍵按下為高電平
} KEY_ActiveLevel;// KEY配置結構體
typedef struct {GPIO_Regs *port;uint32_t pin;KEY_ActiveLevel active_level; // 按鍵有效電平bool initialized; // 標記是否已初始化// 內部狀態變量uint32_t last_time; // 上次掃描時間uint8_t press_count; // 按鍵計數uint8_t long_press_flag; // 長按標志
} KEY_Config;// 函數聲明
bool key_init_one(KEY_Config *key, const GPIO_Config *gpio_config, KEY_ActiveLevel active_level);
KEY_State key_scan(KEY_Config *key);
uint8_t key_read(KEY_Config *key);// 便捷宏定義
#define KEY_IS_PRESSED(key) (key_read(key) == 1)
#define KEY_IS_RELEASED(key) (key_read(key) == 0)
void key_init(void);extern KEY_Config key_configs[];#endif