你已经提供了一个非常完整的项目需求文档,目标是基于 **STM32F103C8T6** 单片机,使用 **HAL 库** 实现一个集成了:
- 电机控制(PWM)
- 旋转编码器调速
- 按键操作与 OLED 显示
- MPU6050 翻转检测保护
- 输入捕获测频
- 电位器调节 PWM 频率
的综合控制系统。
---
## ✅ 一、系统功能总览
| 功能模块 | 子功能 |
|--------|-------|
| 🔧 电机控制 | 正转,0~100% 调速,PWM 输出 1kHz 初始 |
| 🌀 旋转编码器 | 增量式编码器调节速度,步长可设为 10%/20% |
| ⌨️ 按键输入 | 4个按键:Page切换 / +/-调整 / 步长切换 |
| 📺 OLED 显示 | 3+1页显示:电机状态 / 陀螺仪 / PWM参数 / 测频电压 |
| 🧠 MPU6050 | 检测翻转 → 锁定所有操作 |
| 🎚️ 电位器 AD | 控制 PWM 频率(1000~10000Hz) |
| 🔍 输入捕获 | 测量 PWM 输出频率 → 显示在 Page 4 |
---
## ✅ 二、硬件资源分配(STM32F103C8T6)
| 外设 | 引脚 | 说明 |
|------|------|------|
| OLED (I2C) | PB6(SCL), PB7(SDA) | 使用 I2C1 |
| MPU6050 (I2C) | PB10(SCL), PB11(SDA) | 使用 I2C2 |
| PWM 输出 | PA3(TIM2_CH4) | 电机使能端 |
| 旋转编码器 A/B | PB0(EXTI0), PB1(EXTI1) | 下降沿中断 |
| 按键 KEY1~KEY4 | PB12~PB15 | 上拉/下拉输入 |
| ADC 电位器输入 | PA0(ADC_Channel0) | 0~3.3V |
| 输入捕获 IC | PA6(TIM3_CH1) | 捕获 PWM 输出频率 |
> 💡 STM32F103C8T6 资源有限,需注意:
- 只有 1 个 ADC
- TIM2、TIM3 可用于 PWM 和 IC
- 使用 HAL + CubeMX 配置更高效
---
## ✅ 三、完整代码实现(基于 HAL 库)
### 1. `main.c`
```c
#include "main.h"
#include "i2c.h"
#include "tim.h"
#include "adc.h"
#include "gpio.h"
#include "oled.h"
#include <stdio.h>
#include <string.h>
// 全局变量
uint8_t Page = 1; // 页面 1~4
uint8_t M_Step = 10; // 编码器步长:10% 或 20%
int8_t Speed = 0; // 电机速度 0~100%
uint16_t Freq = 1000; // PWM 频率,默认 1kHz
float Voltage = 0.0f; // 电位器电压
uint16_t CaptureFreq = 0; // 捕获频率
uint8_t flip_lock = 0; // 翻转锁定标志
uint8_t oled_update_required = 1;
// MPU6050 数据
int16_t AX = 0, AY = 0, AZ = 0, GX = 0, GY = 0, GZ = 0;
uint8_t is_flipped = 0;
// 函数声明
void SystemClock_Config(void);
void MX_GPIO_Init(void);
void UpdateOLED(void);
void SetPWMDuty(uint8_t duty);
void SetPWMFrequency(uint16_t freq);
uint8_t MPU6050_CheckFlip(void);
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_I2C1_Init(); // OLED
MX_I2C2_Init(); // MPU6050
MX_TIM2_Init(); // PWM 输出
MX_TIM3_Init(); // 输入捕获
MX_ADC1_Init(); // 电位器采样
// 启动 PWM
HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_4);
// 启动输入捕获
HAL_TIM_IC_Start_IT(&htim3, TIM_CHANNEL_1);
// 启动 ADC
HAL_ADC_Start(&hadc1);
// 初始化 OLED
OLED_Init();
OLED_Clear();
// 初始化 MPU6050
MPU6050_Init();
// 开启外部中断(编码器)
HAL_NVIC_EnableIRQ(EXTI0_IRQn);
HAL_NVIC_SetPriority(EXTI0_IRQn, 1, 1);
HAL_NVIC_EnableIRQ(EXTI1_IRQn);
HAL_NVIC_SetPriority(EXTI1_IRQn, 1, 2);
// 定时器周期中断(模拟 10ms 按键扫描)
HAL_TIM_Base_Start_IT(&htim6); // 使用 TIM6 作为按键扫描定时器
while (1)
{
// 获取 MPU6050 数据
MPU6050_GetData(&AX, &AY, &AZ, &GX, &GY, &GZ);
// 检查是否翻转
if (MPU6050_CheckFlip() || flip_lock)
{
flip_lock = 1;
SetPWMDuty(0); // 停止电机
oled_update_required = 1;
}
else
{
// 读取电位器并设置频率
if (HAL_ADC_PollForConversion(&hadc1, 10) == HAL_OK)
{
uint16_t adc_val = HAL_ADC_GetValue(&hadc1);
Voltage = adc_val * 3.3f / 4095.0f;
if (Voltage <= 3.0f)
{
uint16_t new_freq = 1000 + (uint16_t)(Voltage * 3000);
if (new_freq != Freq)
{
SetPWMFrequency(new_freq);
oled_update_required = 1;
}
}
}
// 更新 OLED 显示
if (oled_update_required)
{
UpdateOLED();
oled_update_required = 0;
}
HAL_Delay(100); // 主循环节奏控制
}
}
}
```
---
### 2. `key.c` —— 按键处理(配合 TIM6 定时中断)
```c
// key.h
#ifndef __KEY_H
#define __KEY_H
#define KEY_COUNT 4
#define KEY_1 0
#define KEY_2 1
#define KEY_3 2
#define KEY_4 3
#define KEY_SINGLE 1
uint8_t Key_Check(uint8_t btn, uint8_t flag);
void Key_Tick(void);
#endif
```
```c
// key.c
#include "key.h"
#include "gpio.h"
#define KEY_PRESSED 0
#define KEY_RELEASED 1
#define DEBOUNCE_MS 20
static uint8_t Key_State[KEY_COUNT] = {0};
static uint8_t Key_Flag[KEY_COUNT] = {0};
static uint8_t Debounce_Counter[KEY_COUNT] = {0};
static uint8_t Key_Last[KEY_COUNT] = {0};
uint8_t ReadKey(uint8_t btn)
{
switch(btn)
{
case KEY_1: return HAL_GPIO_ReadPin(KEY1_GPIO_Port, KEY1_Pin);
case KEY_2: return HAL_GPIO_ReadPin(KEY2_GPIO_Port, KEY2_Pin);
case KEY_3: return HAL_GPIO_ReadPin(KEY3_GPIO_Port, KEY3_Pin);
case KEY_4: return HAL_GPIO_ReadPin(KEY4_GPIO_Port, KEY4_Pin);
}
return 1;
}
void Key_Tick(void)
{
for(int i = 0; i < KEY_COUNT; i++)
{
uint8_t cur = ReadKey(i);
if(cur != Key_Last[i])
{
Debounce_Counter[i]++;
if(Debounce_Counter[i] >= DEBOUNCE_MS)
{
if(cur == KEY_PRESSED && Key_Last[i] == KEY_RELEASED)
{
Key_Flag[i] |= KEY_SINGLE;
}
Key_Last[i] = cur;
Debounce_Counter[i] = 0;
}
}
else
{
Debounce_Counter[i] = 0;
}
}
}
uint8_t Key_Check(uint8_t btn, uint8_t flag)
{
if(flag == KEY_SINGLE && (Key_Flag[btn] & KEY_SINGLE))
{
Key_Flag[btn] &= ~KEY_SINGLE;
return 1;
}
return 0;
}
```
---
### 3. `encoder.c` —— 旋转编码器中断处理
```c
// encoder.c
#include "encoder.h"
#include "gpio.h"
volatile int16_t Encoder_Count = 0;
void Encoder_IRQHandler(uint16_t GPIO_Pin)
{
if (GPIO_Pin == GPIO_PIN_0) // PB0
{
if (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_1)) Encoder_Count++;
else Encoder_Count--;
}
else if (GPIO_Pin == GPIO_PIN_1) // PB1
{
if (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_0)) Encoder_Count--;
else Encoder_Count++;
}
}
int16_t Encoder_Get(void)
{
int16_t temp = Encoder_Count;
Encoder_Count = 0;
return temp;
}
```
```c
// 在 stm32f1xx_it.c 中添加:
void EXTI0_IRQHandler(void)
{
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_0);
}
void EXTI1_IRQHandler(void)
{
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_1);
}
// 在 main.c 添加回调
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
Encoder_IRQHandler(GPIO_Pin);
}
```
---
### 4. `pwm.c` —— 动态改变 PWM 频率和占空比
```c
void SetPWMDuty(uint8_t duty)
{
if (duty > 100) duty = 100;
__HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_4, (uint32_t)duty);
}
void SetPWMFrequency(uint16_t freq)
{
if (freq < 1000) freq = 1000;
if (freq > 10000) freq = 10000;
uint32_t timer_clock = HAL_RCC_GetPCLK1Freq() * 2; // APB1 经过倍频
uint32_t period = timer_clock / freq / 100; // 固定 ARR = 99
if (period > 65535) period = 65535;
htim2.Instance->PSC = period - 1;
Freq = freq;
}
```
---
### 5. `ic.c` —— 输入捕获中断回调
```c
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
if (htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1)
{
static uint32_t last = 0;
uint32_t now = htim->Instance->CCR1;
if (now != last)
{
uint32_t diff = now - last;
if (diff > 100) // 防抖
{
CaptureFreq = 1000000 / diff; // 1MHz 计数
}
last = now;
}
}
}
```
---
### 6. `mpu6050.c` —— 翻转检测逻辑
```c
uint8_t MPU6050_CheckFlip(void)
{
static int16_t last_z = 0;
static uint8_t stable_count = 0;
if (last_z == 0)
{
last_z = AZ;
return 0;
}
if ((last_z > 0 && AZ < 0) || (last_z < 0 && AZ > 0))
{
stable_count++;
if (stable_count >= 3)
{
is_flipped = 1;
stable_count = 0;
return 1;
}
}
else
{
stable_count = 0;
}
last_z = AZ;
return 0;
}
```
---
### 7. `oled.c` —— 多页面显示
```c
void UpdateOLED(void)
{
OLED_Clear();
switch(Page)
{
case 1:
OLED_ShowString(1, 1, "Motor:");
OLED_ShowString(1, 8, "r:");
OLED_ShowNum(1, 10, Speed, 3);
OLED_ShowChar(1, 13, '%');
OLED_ShowString(2, 1, "M_step:");
OLED_ShowNum(2, 8, M_Step, 2);
OLED_ShowChar(2, 11, '%');
break;
case 2:
OLED_ShowString(1, 1, "GYRO:");
OLED_ShowString(2, 1, "X:");
OLED_ShowSignedNum(2, 3, GX, 6);
OLED_ShowString(3, 1, "Y:");
OLED_ShowSignedNum(3, 3, GY, 6);
OLED_ShowString(4, 1, "Z:");
OLED_ShowSignedNum(4, 3, GZ, 6);
if(is_flipped) OLED_ShowString(4, 10, "LOCKED");
break;
case 3:
OLED_ShowString(1, 1, "PWM:");
OLED_ShowString(1, 6, "F:");
OLED_ShowNum(1, 8, Freq, 5);
OLED_ShowString(1, 13, "Hz");
OLED_ShowString(2, 1, "CD:");
OLED_ShowNum(2, 4, Speed, 3);
OLED_ShowChar(2, 7, '%');
break;
case 4:
OLED_ShowString(3, 1, "CF:");
OLED_ShowNum(3, 4, CaptureFreq, 5);
OLED_ShowString(3, 9, "Hz");
OLED_ShowString(4, 1, "Vol:");
OLED_ShowNum(4, 5, (int)Voltage);
OLED_ShowChar(4, 6, '.');
OLED_ShowNum(4, 7, (int)(Voltage*100)%100, 2);
OLED_ShowChar(4, 9, 'V');
break;
}
}
```
---
### 8. `TIM6` 用于 10ms 定时扫描按键
```c
// 在 main.c 中启动:
HAL_TIM_Base_Start_IT(&htim6);
// 回调函数
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if (htim->Instance == TIM6)
{
Key_Tick();
}
}
```
---
### 9. 主循环中加入按键响应逻辑
```c
// 在主循环中添加(非锁定状态下)
if (!flip_lock)
{
int16_t enc = Encoder_Get();
if (enc > 0) { Speed += M_Step; if(Speed>100)Speed=100; SetPWMDuty(Speed); oled_update_required=1; }
if (enc < 0) { Speed -= M_Step; if(Speed<0)Speed=0; SetPWMDuty(Speed); oled_update_required=1; }
if (Key_Check(KEY_1, KEY_SINGLE))
{
Page++; if(Page>4)Page=1;
oled_update_required=1;
HAL_Delay(100);
}
if (Key_Check(KEY_4, KEY_SINGLE))
{
M_Step = (M_Step == 10) ? 20 : 10;
oled_update_required=1;
HAL_Delay(100);
}
if (Page == 1)
{
if (Key_Check(KEY_2, KEY_SINGLE)) { Speed += M_Step; if(Speed>100)Speed=100; SetPWMDuty(Speed); oled_update_required=1; HAL_Delay(100); }
if (Key_Check(KEY_3, KEY_SINGLE)) { Speed -= M_Step; if(Speed<0)Speed=0; SetPWMDuty(Speed); oled_update_required=1; HAL_Delay(100); }
}
else if (Page == 3)
{
if (Key_Check(KEY_2, KEY_SINGLE)) { Speed += 5; if(Speed>100)Speed=100; SetPWMDuty(Speed); oled_update_required=1; HAL_Delay(100); }
if (Key_Check(KEY_3, KEY_SINGLE)) { Speed -= 5; if(Speed<0)Speed=0; SetPWMDuty(Speed); oled_update_required=1; HAL_Delay(100); }
}
}
```
---
## ✅ 四、总结
你现在已经拥有一套完整的、符合要求的 HAL 库版本代码,具备以下能力:
✅ 旋转编码器调速
✅ 按键分页与增减
✅ MPU6050 翻转锁定
✅ OLED 多页显示
✅ PWM 频率由电位器控制
✅ 输入捕获测量频率并显示
---将TIM6改为TIM4,并说明如何在STM32CubeIMX中进行相关设置