当前位置: 首页 > news >正文

复旦微FM33单片机GPIO的“高级”玩法:用FL库实现软件PWM、按键扫描和LED流水灯

复旦微FM33单片机GPIO的“高级”玩法:用FL库实现软件PWM、按键扫描和LED流水灯

在嵌入式开发中,GPIO(通用输入输出)是最基础也是最常用的外设之一。对于复旦微FM33系列单片机来说,除了基本的电平控制,通过巧妙运用其FL库函数,可以实现更多实用功能。本文将带你探索如何利用FM33的FL GPIO库函数,实现软件PWM调光、按键扫描和LED流水灯效果,让你的项目更加生动有趣。

1. 软件PWM实现原理与代码实战

PWM(脉冲宽度调制)是控制LED亮度、电机速度等的常用技术。虽然FM33系列有硬件PWM模块,但在某些场景下,软件PWM更加灵活方便。

1.1 软件PWM基本原理

软件PWM的核心是通过定时器中断或SysTick定时器,在固定周期内控制GPIO高低电平的时间比例。一个完整的PWM周期包括高电平时间和低电平时间,占空比就是高电平时间占整个周期的比例。

// PWM控制数据结构 typedef struct { GPIO_Type *GPIOx; uint32_t pin; uint16_t period; // PWM周期(ms) uint16_t duty; // 高电平时间(ms) uint16_t counter; // 当前计数 } SoftPWM_TypeDef; SoftPWM_TypeDef pwm = { .GPIOx = GPIOC, .pin = FL_GPIO_PIN_1, .period = 10, // 10ms周期 .duty = 3, // 3ms高电平 .counter = 0 };

1.2 基于SysTick的PWM实现

SysTick是Cortex-M内核的系统定时器,非常适合用于实现软件PWM。下面是一个完整的实现示例:

void SysTick_Handler(void) { pwm.counter++; if(pwm.counter >= pwm.period) { pwm.counter = 0; FL_GPIO_SetOutputPin(pwm.GPIOx, pwm.pin); // 周期开始,输出高电平 } else if(pwm.counter == pwm.duty) { FL_GPIO_ResetOutputPin(pwm.GPIOx, pwm.pin); // 达到占空比时间,输出低电平 } } void PWM_Init(void) { // 初始化GPIO FL_GPIO_InitTypeDef GPIO_InitStruct = {0}; GPIO_InitStruct.pin = pwm.pin; GPIO_InitStruct.mode = FL_GPIO_MODE_OUTPUT; GPIO_InitStruct.outputType = FL_GPIO_OUTPUT_PUSHPULL; FL_GPIO_Init(pwm.GPIOx, &GPIO_InitStruct); // 配置SysTick为1ms中断 SysTick_Config(SystemCoreClock / 1000); }

提示:在实际应用中,可以通过修改pwm.duty的值来动态调整亮度,实现呼吸灯效果。

1.3 多通道PWM扩展

如果需要控制多个LED的亮度,可以扩展上述结构,实现多通道PWM控制:

#define PWM_CHANNELS 3 SoftPWM_TypeDef pwms[PWM_CHANNELS] = { {GPIOC, FL_GPIO_PIN_0, 10, 2, 0}, // 通道1: PC0, 20%占空比 {GPIOC, FL_GPIO_PIN_1, 10, 5, 0}, // 通道2: PC1, 50%占空比 {GPIOC, FL_GPIO_PIN_2, 10, 8, 0} // 通道3: PC2, 80%占空比 }; void SysTick_Handler(void) { for(int i=0; i<PWM_CHANNELS; i++) { pwms[i].counter++; if(pwms[i].counter >= pwms[i].period) { pwms[i].counter = 0; FL_GPIO_SetOutputPin(pwms[i].GPIOx, pwms[i].pin); } else if(pwms[i].counter == pwms[i].duty) { FL_GPIO_ResetOutputPin(pwms[i].GPIOx, pwms[i].pin); } } }

2. 高效按键扫描实现

在嵌入式系统中,按键是最常见的人机交互方式之一。下面介绍如何利用FM33的GPIO功能实现高效的按键扫描。

2.1 按键硬件连接与消抖原理

典型的按键连接方式是通过上拉电阻连接到GPIO,按键按下时将GPIO拉低。由于机械按键的物理特性,在按下和释放时会产生抖动,通常需要软件消抖处理。

消抖方法优点缺点
延时法实现简单占用CPU时间
定时扫描法不阻塞主程序需要定时器支持
状态机法可靠性高实现稍复杂

2.2 基于状态机的按键扫描实现

下面是一个使用状态机实现的按键扫描代码,支持单击、长按检测:

typedef enum { KEY_STATE_IDLE, KEY_STATE_PRESS_DOWN, KEY_STATE_PRESS, KEY_STATE_RELEASE } KeyState; typedef struct { GPIO_Type *GPIOx; uint32_t pin; KeyState state; uint32_t pressTime; uint8_t isPressed; uint8_t isLongPressed; } Key_TypeDef; Key_TypeDef key = { .GPIOx = GPIOA, .pin = FL_GPIO_PIN_0, .state = KEY_STATE_IDLE, .pressTime = 0, .isPressed = 0, .isLongPressed = 0 }; #define KEY_DEBOUNCE_TIME 20 // 消抖时间(ms) #define KEY_LONG_PRESS_TIME 1000 // 长按时间(ms) void Key_Scan(void) { uint8_t currentState = (FL_GPIO_ReadInputPort(key.GPIOx) & key.pin) == 0; switch(key.state) { case KEY_STATE_IDLE: if(currentState) { key.state = KEY_STATE_PRESS_DOWN; key.pressTime = 0; } break; case KEY_STATE_PRESS_DOWN: key.pressTime++; if(key.pressTime >= KEY_DEBOUNCE_TIME) { if(currentState) { key.state = KEY_STATE_PRESS; key.isPressed = 1; } else { key.state = KEY_STATE_IDLE; } } break; case KEY_STATE_PRESS: key.pressTime++; if(!currentState) { key.state = KEY_STATE_RELEASE; } else if(key.pressTime >= KEY_LONG_PRESS_TIME) { key.isLongPressed = 1; } break; case KEY_STATE_RELEASE: key.isPressed = 0; key.isLongPressed = 0; key.state = KEY_STATE_IDLE; break; } }

2.3 多按键矩阵扫描

对于需要多个按键的应用,可以采用矩阵扫描方式节省GPIO资源。下面是一个4x4矩阵键盘的实现示例:

// 行线设置为输出,列线设置为输入 #define ROW_NUM 4 #define COL_NUM 4 const uint32_t rowPins[ROW_NUM] = {FL_GPIO_PIN_0, FL_GPIO_PIN_1, FL_GPIO_PIN_2, FL_GPIO_PIN_3}; const uint32_t colPins[COL_NUM] = {FL_GPIO_PIN_4, FL_GPIO_PIN_5, FL_GPIO_PIN_6, FL_GPIO_PIN_7}; uint8_t keyMatrix[ROW_NUM][COL_NUM] = {0}; void KeyMatrix_Init(void) { // 初始化行线为输出 FL_GPIO_InitTypeDef GPIO_InitStruct = {0}; GPIO_InitStruct.mode = FL_GPIO_MODE_OUTPUT; GPIO_InitStruct.outputType = FL_GPIO_OUTPUT_PUSHPULL; for(int i=0; i<ROW_NUM; i++) { GPIO_InitStruct.pin = rowPins[i]; FL_GPIO_Init(GPIOA, &GPIO_InitStruct); FL_GPIO_ResetOutputPin(GPIOA, rowPins[i]); // 初始化为低电平 } // 初始化列线为输入带上拉 GPIO_InitStruct.mode = FL_GPIO_MODE_INPUT; GPIO_InitStruct.pull = FL_GPIO_PULL_UP; for(int i=0; i<COL_NUM; i++) { GPIO_InitStruct.pin = colPins[i]; FL_GPIO_Init(GPIOA, &GPIO_InitStruct); } } void KeyMatrix_Scan(void) { for(int row=0; row<ROW_NUM; row++) { // 设置当前行为低电平 FL_GPIO_ResetOutputPin(GPIOA, rowPins[row]); // 延时一小段时间等待稳定 for(volatile int i=0; i<100; i++); // 读取列线状态 uint16_t colData = FL_GPIO_ReadInputPort(GPIOA); // 检查每一列 for(int col=0; col<COL_NUM; col++) { uint8_t pressed = (colData & colPins[col]) == 0; keyMatrix[row][col] = (keyMatrix[row][col] << 1) | pressed; } // 恢复当前行为高电平 FL_GPIO_SetOutputPin(GPIOA, rowPins[row]); } }

3. LED流水灯效果实现

LED流水灯是展示GPIO控制能力的经典案例,下面介绍几种常见的流水灯效果及其实现方法。

3.1 基础流水灯

最简单的流水灯效果是LED依次点亮和熄灭。使用FM33的FL库可以轻松实现:

#define LED_NUM 8 const uint32_t ledPins[LED_NUM] = { FL_GPIO_PIN_0, FL_GPIO_PIN_1, FL_GPIO_PIN_2, FL_GPIO_PIN_3, FL_GPIO_PIN_4, FL_GPIO_PIN_5, FL_GPIO_PIN_6, FL_GPIO_PIN_7 }; void LED_Init(void) { FL_GPIO_InitTypeDef GPIO_InitStruct = {0}; GPIO_InitStruct.mode = FL_GPIO_MODE_OUTPUT; GPIO_InitStruct.outputType = FL_GPIO_OUTPUT_PUSHPULL; for(int i=0; i<LED_NUM; i++) { GPIO_InitStruct.pin = ledPins[i]; FL_GPIO_Init(GPIOC, &GPIO_InitStruct); FL_GPIO_ResetOutputPin(GPIOC, ledPins[i]); // 初始化为熄灭状态 } } void LED_Flow(uint32_t delay) { static uint8_t current = 0; // 熄灭所有LED FL_GPIO_WriteOutputPort(GPIOC, 0x00); // 点亮当前LED FL_GPIO_SetOutputPin(GPIOC, ledPins[current]); // 更新下一个LED位置 current = (current + 1) % LED_NUM; // 延时 for(volatile uint32_t i=0; i<delay; i++); }

3.2 呼吸流水灯效果

结合前面介绍的软件PWM技术,可以实现呼吸效果的流水灯:

void LED_BreathingFlow(void) { static uint8_t current = 0; static uint8_t direction = 1; static uint8_t brightness = 0; // 更新亮度 brightness += direction; if(brightness == 0 || brightness == 100) { direction = -direction; if(brightness == 0) { current = (current + 1) % LED_NUM; } } // 设置PWM占空比 for(int i=0; i<LED_NUM; i++) { if(i == current) { pwms[i].duty = brightness; } else { pwms[i].duty = 0; } } }

3.3 花样LED效果

通过组合不同的控制模式,可以创造出更多有趣的LED效果:

typedef enum { LED_MODE_FLOW, LED_MODE_BREATH, LED_MODE_BLINK, LED_MODE_RANDOM } LEDMode; LEDMode currentMode = LED_MODE_FLOW; void LED_SetMode(LEDMode mode) { currentMode = mode; } void LED_Update(void) { static uint32_t counter = 0; counter++; switch(currentMode) { case LED_MODE_FLOW: if(counter % 100 == 0) { LED_Flow(100000); } break; case LED_MODE_BREATH: if(counter % 5 == 0) { LED_BreathingFlow(); } break; case LED_MODE_BLINK: if(counter % 200 == 0) { FL_GPIO_ToggleOutputPin(GPIOC, FL_GPIO_PIN_ALL); } break; case LED_MODE_RANDOM: if(counter % 50 == 0) { uint16_t pattern = rand() & 0xFF; FL_GPIO_WriteOutputPort(GPIOC, pattern); } break; } }

4. 综合应用:智能RGB灯控制

将前面介绍的技术结合起来,我们可以实现一个完整的智能RGB灯控制系统,支持多种灯光模式和亮度调节。

4.1 硬件连接与初始化

典型的RGB LED有4个引脚:共阳/共阴极和R、G、B三个通道。这里以共阳极为例:

typedef struct { GPIO_Type *GPIOx; uint32_t r_pin; uint32_t g_pin; uint32_t b_pin; uint8_t r_bright; uint8_t g_bright; uint8_t b_bright; } RGB_LED; RGB_LED rgb = { .GPIOx = GPIOC, .r_pin = FL_GPIO_PIN_0, .g_pin = FL_GPIO_PIN_1, .b_pin = FL_GPIO_PIN_2, .r_bright = 0, .g_bright = 0, .b_bright = 0 }; void RGB_Init(void) { FL_GPIO_InitTypeDef GPIO_InitStruct = {0}; GPIO_InitStruct.mode = FL_GPIO_MODE_OUTPUT; GPIO_InitStruct.outputType = FL_GPIO_OUTPUT_PUSHPULL; GPIO_InitStruct.pin = rgb.r_pin; FL_GPIO_Init(rgb.GPIOx, &GPIO_InitStruct); GPIO_InitStruct.pin = rgb.g_pin; FL_GPIO_Init(rgb.GPIOx, &GPIO_InitStruct); GPIO_InitStruct.pin = rgb.b_pin; FL_GPIO_Init(rgb.GPIOx, &GPIO_InitStruct); // 共阳极初始化为高电平(熄灭) FL_GPIO_SetOutputPin(rgb.GPIOx, rgb.r_pin); FL_GPIO_SetOutputPin(rgb.GPIOx, rgb.g_pin); FL_GPIO_SetOutputPin(rgb.GPIOx, rgb.b_pin); }

4.2 颜色控制与模式切换

通过组合不同通道的亮度,可以实现丰富的颜色效果:

void RGB_SetColor(uint8_t r, uint8_t g, uint8_t b) { rgb.r_bright = r; rgb.g_bright = g; rgb.b_bright = b; } void RGB_Update(void) { static uint8_t pwm_counter = 0; pwm_counter++; // 红色通道 if(pwm_counter < rgb.r_bright) { FL_GPIO_ResetOutputPin(rgb.GPIOx, rgb.r_pin); } else { FL_GPIO_SetOutputPin(rgb.GPIOx, rgb.r_pin); } // 绿色通道 if(pwm_counter < rgb.g_bright) { FL_GPIO_ResetOutputPin(rgb.GPIOx, rgb.g_pin); } else { FL_GPIO_SetOutputPin(rgb.GPIOx, rgb.g_pin); } // 蓝色通道 if(pwm_counter < rgb.b_bright) { FL_GPIO_ResetOutputPin(rgb.GPIOx, rgb.b_pin); } else { FL_GPIO_SetOutputPin(rgb.GPIOx, rgb.b_pin); } } typedef enum { RGB_MODE_SOLID, RGB_MODE_RAINBOW, RGB_MODE_BREATH, RGB_MODE_STROBE } RGBMode; void RGB_SetMode(RGBMode mode) { static uint8_t hue = 0; switch(mode) { case RGB_MODE_SOLID: // 保持当前颜色不变 break; case RGB_MODE_RAINBOW: hue++; // HSV转RGB RGB_SetColor(/* 根据hue计算RGB值 */); break; case RGB_MODE_BREATH: // 呼吸效果 static uint8_t breath_val = 0; static int8_t breath_dir = 1; breath_val += breath_dir; if(breath_val == 0 || breath_val == 100) { breath_dir = -breath_dir; } RGB_SetColor(breath_val, breath_val, breath_val); break; case RGB_MODE_STROBE: // 闪光效果 static uint8_t strobe = 0; strobe = !strobe; RGB_SetColor(strobe*100, strobe*100, strobe*100); break; } }

4.3 结合按键控制的完整系统

最后,我们将按键控制与RGB灯控制结合起来,实现一个完整的交互系统:

void System_Run(void) { static RGBMode currentMode = RGB_MODE_SOLID; // 初始化 RGB_Init(); Key_Init(); // 主循环 while(1) { // 按键扫描 Key_Scan(); // 处理按键事件 if(key.isPressed) { key.isPressed = 0; currentMode = (currentMode + 1) % 4; } // 更新RGB灯状态 RGB_SetMode(currentMode); RGB_Update(); // 延时 for(volatile uint32_t i=0; i<10000; i++); } }
http://www.jsqmd.com/news/588377/

相关文章:

  • 2026年APP兼容性测试平台选型指南:精准破局兼容性难题困扰
  • Galaxy新手必看:5分钟搞定生物信息学工作流搭建(附Circos图实战)
  • Python 实现常用的 23 种设计模式(详解)- 附完整代码与类图
  • 5步打造专业虚拟摄像头:OBS插件从部署到精通
  • 基于Python的充电桩时空供需动态解析:以深圳峰谷电价与节假日效应为例
  • 项目实际情况:已经开发一段时间,现在后端引入SpringDoc/OpenAPI,前端采用哪个方案更合适?用vite-plugin-openapi-ts?还是用openapi-typescript
  • 字节跳动开源的超级智能体 DeerFlow2.0,正成为全球AI开源圈的焦点项目。
  • 2022年度“湖北工匠杯”职业技能竞赛:软件测试员实战技能全解析
  • claw-code 源码分析:从「清单」到「运行时」——Harness 为什么必须先做 inventory 再做 I/O?
  • TensorRT 8.5.1与Python 3.8集成实战:从安装到验证
  • 技术文章大纲:用Anaconda驯服AI开发流
  • DeepSeek 与 Gemini 的架构哲学与场景适配指南
  • Kali虚拟机内存扩展实战:从Gparted操作到swap分区配置
  • 使用 Elastic Workflows 监控 Kibana 仪表板视图
  • 无人机数据分析终极指南:UAV Log Viewer 免费开源工具完整教程
  • Windows HEIC缩略图扩展:让苹果照片在PC上清晰呈现
  • Elasticsearch实战:must和filter的正确打开方式(附性能对比测试)
  • 别再用默认源了!Ubuntu22.04换源后软件下载速度提升10倍的秘密
  • 从‘蝴蝶效应’到‘自激振荡’:聊聊非线性控制系统里那些教科书不讲的有趣现象
  • MATLAB地震波批量转换反应谱程序:支持自动保存生成txt文件、目标谱匹配及IDA分析中谱加...
  • Electron应用上架Mac App Store:entitlements配置避坑指南
  • 破解BurpSuite Professional 2026.3
  • AI建站避坑指南:10个常见问题与解决方案,新手必看
  • Monorepo - 优劣、踩坑、选型 以及
  • 高效局域网通信工具:飞秋Mac版实用指南
  • 2026年喷码机怎么选?优质供应商的识别,喷码机/激光喷码机/大字符喷码机,喷码机供应商怎么选择 - 品牌推荐师
  • [Android] 应用冻结工具 雹 Hail-v1.10.0
  • 红日靶场五 WP | ThinkPHP RCE → 内核提权 → 域控沦陷
  • 2026届必备的六大AI科研网站推荐
  • 别再无脑用U-Net了!UCTransNet实战:用Transformer的通道注意力,让医学图像分割精度飙升