1. PWM驱动SG90舵机实战
1.1 舵机控制原理与参数计算
SG90舵机作为常见的微型伺服电机,其核心控制原理是通过PWM信号的占空比变化来指定输出轴的角度位置。技术手册明确要求控制信号需满足以下特性:
- 周期:20ms(对应频率50Hz)
- 脉宽范围:0.5ms-2.5ms
- 角度范围:0°-180°线性对应
在STM32的定时器系统中,PWM生成涉及三个关键参数的计算:
- 定时器时钟源:以F407为例,当使用TIM4且APB1预分频器为4时,定时器时钟fCK_PSC=84MHz
- 预分频系数(PSC):用于降低计数频率,计算公式为:
code复制f_cnt = f_CK_PSC / (PSC + 1) - 自动重载值(ARR):决定PWM周期,需满足:
code复制T_pwm = (ARR + 1) / f_cnt
具体到本例的50Hz PWM:
math复制\begin{aligned}
&\text{目标周期} = 20ms = 50Hz \\
&\text{取} PSC = 83 \Rightarrow f_{cnt} = 84MHz / (83+1) = 1MHz \\
&\text{则} ARR = (20ms \times 1MHz) - 1 = 19999 \\
\end{aligned}
1.2 CubeMX配置要点
在STM32CubeMX中进行TIM4配置时需注意:
- 时钟源选择:内部时钟(Internal Clock)
- 通道配置:
- 模式:PWM Generation CH1
- Pulse初始值设为500(对应0°位置)
- 参数设置:
c复制Prescaler (PSC) = 83 Counter Period (ARR) = 19999 Pulse = 500 // 初始0°位置
关键提示:务必检查GPIO复用功能是否自动配置正确,PD12应显示为TIM4_CH1
1.3 硬件连接与代码实现
接线规范:
| 舵机引脚 | STM32连接 | 备注 |
|---|---|---|
| 棕色(GND) | GND | 必须共地 |
| 红色(VCC) | 5V | 需外接电源 |
| 橙色(SIG) | PD12 | 必须使用PWM输出引脚 |
驱动代码:
c复制// 启动PWM输出
HAL_TIM_PWM_Start(&htim4, TIM_CHANNEL_1);
while(1) {
// 0°位置 (0.5ms脉宽)
__HAL_TIM_SET_COMPARE(&htim4, TIM_CHANNEL_1, 500);
HAL_Delay(1000);
// 90°位置 (1.5ms脉宽)
__HAL_TIM_SET_COMPARE(&htim4, TIM_CHANNEL_1, 1500);
HAL_Delay(1000);
// 180°位置 (2.5ms脉宽)
__HAL_TIM_SET_COMPARE(&htim4, TIM_CHANNEL_1, 2500);
HAL_Delay(1000);
}
1.4 常见问题排查
-
舵机无反应:
- 检查5V电源是否足够(建议单独供电)
- 测量PD12信号是否输出(可用示波器观察波形)
- 确认TIM4时钟已使能(__HAL_RCC_TIM4_CLK_ENABLE)
-
角度定位不准:
- 重新校准脉宽范围(有些舵机实际需要0.6ms-2.4ms)
- 检查ARR值计算是否正确(应保证20ms周期)
-
舵机抖动:
- 增加电源滤波电容(推荐100μF电解+0.1μF陶瓷)
- 尝试降低PWM频率(如改为40Hz测试)
2. SPI驱动ST7789液晶屏
2.1 硬件接口设计
ST7789作为240x240分辨率RGB屏,其硬件连接需特别注意:
| 屏幕引脚 | STM32引脚 | 功能说明 |
|---|---|---|
| VCC | 3.3V | 逻辑电平电源 |
| GND | GND | 必须与MCU共地 |
| SCL | PA5 | SPI1_SCK时钟线 |
| SDA | PA7 | SPI1_MOSI数据线 |
| RES | PB0 | 硬件复位(低有效) |
| DC | PB1 | 数据/命令选择 |
| BLK | PB2 | 背光控制(PWM可选) |
致命细节:CS引脚必须接地!这是ST7789的硬件要求,浮空会导致通信失败。
2.2 CubeMX SPI配置
-
SPI1参数设置:
- Mode: Full-Duplex Master
- Prescaler: 8 (10.5MHz @84MHz PCLK)
- Data Size: 8-bit
- CPOL: Low
- CPHA: 1 Edge
-
GPIO配置:
- PB0/PB1/PB2设为GPIO_Output
- SPI1_NSS设为GPIO_Output并强制拉低
-
DMA设置(可选但推荐):
- 添加SPI1_TX的DMA通道(Stream5/Channel3)
- Mode: Normal
- Data Width: Byte
2.3 屏幕初始化序列
ST7789的初始化流程包含关键指令序列:
c复制void ST7789_Init(void) {
// 硬件复位时序
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, 0);
HAL_Delay(100);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, 1);
HAL_Delay(120);
// 软件复位
LCD_WR_REG(0x01);
HAL_Delay(120);
// 退出睡眠模式
LCD_WR_REG(0x11);
HAL_Delay(120);
// 设置像素格式为RGB565
LCD_WR_REG(0x3A);
LCD_WR_DATA8(0x05);
// 设置扫描方向
LCD_WR_REG(0x36);
LCD_WR_DATA8(0x00);
// 开启显示反色(ST7789必需)
LCD_WR_REG(0x21);
// 开启显示
LCD_WR_REG(0x29);
HAL_Delay(20);
// 背光控制
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_2, 1);
}
2.4 核心驱动函数实现
基础通信函数:
c复制// 发送命令字节
void LCD_WR_REG(uint8_t dat) {
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, 0); // DC=0命令模式
HAL_SPI_Transmit(&hspi1, &dat, 1, 100);
}
// 发送数据字节
void LCD_WR_DATA8(uint8_t dat) {
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, 1); // DC=1数据模式
HAL_SPI_Transmit(&hspi1, &dat, 1, 100);
}
显存操作函数:
c复制// 设置显示窗口
void LCD_Address_Set(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2) {
LCD_WR_REG(0x2A); // 列地址设置
LCD_WR_DATA16(x1);
LCD_WR_DATA16(x2);
LCD_WR_REG(0x2B); // 行地址设置
LCD_WR_DATA16(y1);
LCD_WR_DATA16(y2);
LCD_WR_REG(0x2C); // 内存写入
}
// 全屏填充颜色
void LCD_Fill(uint16_t color) {
LCD_Address_Set(0, 0, 239, 239);
uint32_t total = 240 * 240;
while(total--) {
LCD_WR_DATA16(color);
}
}
2.5 性能优化技巧
-
DMA加速:
c复制// 使用DMA传输颜色数据 void LCD_Fill_DMA(uint16_t color) { static uint8_t color_buf[2]; color_buf[0] = color >> 8; color_buf[1] = color & 0xFF; LCD_Address_Set(0, 0, 239, 239); HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, 1); for(int i=0; i<240*240; i++) { HAL_SPI_Transmit_DMA(&hspi1, color_buf, 2); while(HAL_SPI_GetState(&hspi1) != HAL_SPI_STATE_READY); } } -
双缓冲机制:
- 开辟两块显存缓冲区
- 使用DMA在后台传输已完成绘制的缓冲区
- 在前台绘制另一缓冲区
-
局部刷新优化:
- 只更新屏幕变化区域
- 减少传输数据量
3. ADC读取摇杆位置
3.1 硬件电路设计
双轴摇杆模块实质是两个电位器加按键开关:
| 引脚 | 功能 | 连接方式 |
|---|---|---|
| VCC | 5V供电 | 需与MCU共地 |
| GND | 地线 | 必须与MCU共地 |
| VRX | X轴模拟量输出 | 接ADC1通道11(PC1) |
| VRY | Y轴模拟量输出 | 接ADC1通道12(PC2) |
| SW | 按键数字输出 | 接PC3(上拉输入) |
共地警告:若使用独立电源供电,必须将电源地与MCU地连接,否则ADC读数会异常。
3.2 ADC配置要点
CubeMX参数设置:
-
ADC1配置:
- Resolution: 12-bit
- Scan Conversion Mode: Enabled
- Continuous Conversion Mode: Disabled
- DMA Continuous Requests: Disabled
- Sampling Time: 480 Cycles
-
通道分配:
- Rank1: Channel11 (PC1)
- Rank2: Channel12 (PC2)
-
DMA设置:
- Mode: Circular
- Data Width: Half Word
- Memory Data Width: Half Word
关键参数计算:
采样时间计算公式:
code复制T_conv = (SamplingTime + 12.5) / f_ADC
当f_ADC=21MHz时:
- 3 Cycles → 0.74μs(过快会导致系统负载过高)
- 480 Cycles → 23.5μs(推荐值)
3.3 数据采集实现
DMA缓存定义:
c复制__IO uint16_t adc_buffer[2]; // X/Y轴数据
启动ADC采样:
c复制HAL_ADC_Start_DMA(&hadc1, (uint32_t*)adc_buffer, 2);
数据处理技巧:
c复制// 获取校准后的值
uint16_t x_val = adc_buffer[0];
uint16_t y_val = adc_buffer[1];
// 按键状态读取
uint8_t btn_state = HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_3);
// 转换为百分比
int x_percent = (x_val - 2048) * 100 / 2048;
int y_percent = (y_val - 2048) * 100 / 2048;
3.4 常见问题解决
-
DMA数据错位:
现象:Y轴值总为0
原因:DMA宽度配置错误
解决方案:c复制// CubeMX中确保: // DMA Memory Data Width = Half Word // ADC DMA Continuous Requests = Disabled -
采样值跳动:
- 增加硬件滤波(在ADC输入引脚加0.1μF电容)
- 软件采用滑动平均滤波:
c复制#define FILTER_SIZE 8 uint16_t x_history[FILTER_SIZE]; uint16_t filter_x() { static uint8_t idx = 0; x_history[idx++ % FILTER_SIZE] = adc_buffer[0]; uint32_t sum = 0; for(int i=0; i<FILTER_SIZE; i++) sum += x_history[i]; return sum / FILTER_SIZE; }
-
电源干扰:
- 使用LDO稳压器供电
- 在VCC与GND间加10μF+0.1μF电容
- 避免与大功率设备共用电源
4. 系统集成与优化
4.1 多任务协调设计
当同时需要驱动舵机、刷新屏幕和读取ADC时,建议采用以下架构:
-
时间片划分:
- 舵机控制:50Hz PWM(硬件自动维护)
- 屏幕刷新:30-60Hz(根据内容复杂度调整)
- ADC采样:100Hz(通过定时器触发)
-
中断优先级配置:
外设 优先级 说明 TIM4(PWM) 0 最高优先级 SPI1_TX 1 保证显示流畅 ADC1 2 数据采集可适当降低
4.2 低功耗优化
-
动态时钟调整:
c复制// 空闲时降低系统时钟 void Enter_LowPower_Mode() { __HAL_RCC_PLLI2S_DISABLE(); HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_1); } -
外设电源管理:
- 不使用时关闭屏幕背光
- 禁用未使用的ADC通道
- 将空闲GPIO设为模拟输入模式
4.3 调试技巧
-
逻辑分析仪应用:
- 抓取SPI波形验证通信时序
- 检查PWM脉宽精度
- 监测ADC采样间隔
-
STM32CubeMonitor:
- 实时观测变量变化
- 绘制ADC采样曲线
- 监控CPU负载
-
故障注入测试:
- 模拟电源波动测试稳定性
- 故意错配时钟频率观察容错能力
- 长时间运行测试内存泄漏
通过实际项目验证,这套系统在机械臂控制、游戏手柄等应用中表现出色。一个特别实用的技巧是:将摇杆ADC值映射到舵机角度时,建议增加死区处理,避免中间位置的微小抖动。
