目录
(2)读取手柄数据函数 PS2_ReadData(void)
一、前言
PS2 手柄凭借高性价比、丰富按键及可扩展性强等优势,成为嵌入式开发中常用的遥控设备。本文将详细介绍如何利用 STM32C8T6 开发板与 STM32CubeIDE 集成开发环境,实现 PS2 手柄的解码与控制,涵盖硬件连接、软件配置及功能测试等关键环节。
这里提供PS2手柄控制舵机并打印串口信息的例程。
二、硬件准备
1. 核心组件
- STM32C8T6 开发板:作为主控芯片,具备丰富的 GPIO 资源与稳定的性能。
- PS2 手柄及接收器:需注意接收器引脚定义,关键引脚包括:
- DI/DAT:手柄到主机的 8 位串行数据信号,同步于时钟下降沿传输。
- DO/CMD:主机到手柄的 8 位串行数据信号,与 DI 方向相反。
- CS/SEL:触发信号,通讯期间保持低电平。
- CLK:主机发出的时钟信号,用于数据同步。
- VDD/GND:电源引脚(3-5V),需注意防反接保护。
2. 硬件连接
参考手册中接收器与 STM32 的连接方式,建议采用以下引脚映射(可根据实际需求调整):
接收器引脚 | STM32C8T6 引脚(GPIO) | 功能说明 |
---|---|---|
DI/DAT | PB12 | 数据输入(浮空输入) |
DO/CMD | PB13 | 数据输出(推挽输出) |
CS/SEL | PB14 | 片选信号(推挽输出) |
CLK | PB15 | 时钟信号(推挽输出) |
VDD | 3.3V 或 5V 电源 | 接收器电源 |
GND | 开发板 GND | 共地 |
三、STM32CubeIDE 配置流程
1. 新建工程
- 打开 STM32CubeIDE,创建新项目,选择芯片型号STM32F103C8T6。
- 配置系统时钟:通过 RCC 设置外部时钟(如 8MHz 晶振),配置系统时钟为 72MHz(参考手册中
Stm32_Clock_Init(9)
的配置逻辑)。 - 创建的时候需要勾选生成.c和.h文件。
2. GPIO 初始化
根据硬件连接,在 CubeMX 图形界面中配置引脚:
- PB12(DI):设置为浮空输入(模拟 PS2 通讯中的输入模式)。
- PB13(DO)、PB14(CS)、PB15(CLK):设置为推挽输出,默认电平为高电平(需与手册中
PS2_Init
函数的初始化逻辑一致)。
记住一定要设置RCC和SYS
然后修改外部晶振为72MHz
SYS设置Serial Wire,不然忘记后芯片会锁住
usart1和usart2设置为异步,并给usart1添加串口助手
这里设置PWM为50Hz
3. 编写 PS2 通讯驱动
参考手册中的pstwo.c
文件逻辑,在 CubeIDE 中实现以下核心函数:
(1)发送命令函数 PS2_Cmd(u8 CMD)
c
void PS2_Cmd(uint8_t CMD) {
uint16_t ref;
for (ref = 0x01; ref < 0x0100; ref <<= 1) {
if (CMD & ref) HAL_GPIO_WritePin(GPIOB, GPIO_PIN_13, GPIO_PIN_SET); // DO_H
else HAL_GPIO_WritePin(GPIOB, GPIO_PIN_13, GPIO_PIN_RESET); // DO_L
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_15, GPIO_PIN_SET); // CLK_H
HAL_Delay_us(10); // 延时10μs
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_15, GPIO_PIN_RESET); // CLK_L
HAL_Delay_us(10);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_15, GPIO_PIN_SET); // 再次拉高时钟以读取DI
if (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_12)) Data[1] |= ref; // 读取DI数据(仅在特定命令中使用)
}
HAL_Delay_us(16); // 命令间隔延时
}
(2)读取手柄数据函数 PS2_ReadData(void)
c
void PS2_ReadData(void) {
uint8_t byte, ref;
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_14, GPIO_PIN_RESET); // CS_L
PS2_Cmd(0x01); // 开始命令
PS2_Cmd(0x42); // 请求数据
for (byte = 2; byte < 9; byte++) { // 读取8字节数据(索引2-8)
for (ref = 0x01; ref < 0x100; ref <<= 1) {
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_15, GPIO_PIN_SET);
HAL_Delay_us(10);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_15, GPIO_PIN_RESET);
HAL_Delay_us(10);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_15, GPIO_PIN_SET);
if (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_12)) Data[byte] |= ref;
}
HAL_Delay_us(16);
}
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_14, GPIO_PIN_SET); // CS_H
}
(3)初始化与模式配置
- 端口初始化:在
main()
中调用MX_GPIO_Init()
完成 GPIO 初始化。 - 手柄配置初始化:参考手册中的
PS2_SetInit()
逻辑,实现短轮询、进入配置模式、设置震动及保存配置等步骤。
四、功能实现与测试
1. 按键检测
- 原理:手柄按键状态存储于
Data[3]
(低 8 位)和Data[4]
(高 8 位),按下为0
,未按下为1
。通过组合这两个字节得到 16 位按键值(Handkey = (Data[4] << 8) | Data[3]
)。 - 代码示例:
c
uint8_t PS2_DataKey(void) {
uint8_t index;
PS2_ClearData();
PS2_ReadData();
uint16_t Handkey = (Data[4] << 8) | Data[3];
for (index = 0; index < 16; index++) {
if ((Handkey & (1 << (MASK[index] - 1))) == 0) return index + 1; // 返回按键编号(1-16)
}
return 0; // 无按键按下
}
2. 摇杆模拟值读取(红灯模式)
- 条件:仅在红灯模式下有效(通过
PS2_RedLight()
判断),摇杆模拟值存储于Data[5]~Data[8]
(分别对应右摇杆 X/Y、左摇杆 X/Y)。 - 代码示例:
c
uint8_t PS2_AnologData(uint8_t button) {
return Data[button]; // button取值:5=PSS_RX, 6=PSS_RY, 7=PSS_LX, 8=PSS_LY
}
3. 震动控制
- 初始化:在
PS2_SetInit()
中调用PS2_VibrationMode()
开启震动模式。 - 控制函数:
c
void PS2_Vibration(uint8_t motor1, uint8_t motor2) {
// motor1:右侧小电机(0x00关,其他值开)
// motor2:左侧大电机(0x40-0xFF开,值越大震动越强)
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_14, GPIO_PIN_RESET);
PS2_Cmd(0x01);
PS2_Cmd(0x42);
PS2_Cmd(0x00);
PS2_Cmd(motor1);
PS2_Cmd(motor2);
// 后续命令可参考手册填充
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_14, GPIO_PIN_SET);
}
五、测试与调试
1. 串口监控
通过 STM32CubeIDE 配置 USART1,在main()
中使用printf()
输出按键值与摇杆模拟值:
c
int main(void) {
// 初始化代码...
while (1) {
uint8_t key = PS2_DataKey();
if (key != 0) printf("Key %d pressed\r\n", key);
// 输出摇杆模拟值(红灯模式有效)
printf("LX: %d, LY: %d, RX: %d, RY: %d\r\n",
PS2_AnologData(PSS_LX), PS2_AnologData(PSS_LY),
PS2_AnologData(PSS_RX), PS2_AnologData(PSS_RY));
HAL_Delay(100);
}
}
2. 模式切换测试
- 红灯模式:摇杆输出模拟值(0-255),L3/R3 按键有效。
- 绿灯模式:摇杆推至极限时输出方向键值(如 UP/DOWN 等),L3/R3 无效,可通过手柄
MODE
键或软件锁存配置切换。
六、注意事项
- 电源保护:接收器与开发板共电源(3-5V),需确保极性正确,避免过压烧毁器件。
- 配对问题:若手柄与接收器无法配对,可尝试仅连接电源(不接数据线),配对成功后再检查时序与程序逻辑。
- 时序精度:PS2 通讯时钟频率需严格遵循手册(如时钟周期约 20μs),避免因延时误差导致通讯失败。
- 模式锁存:通过
PS2_TurnOnAnalogMode()
的0xEE
(不锁存)或0x03
(锁存)参数,可控制是否允许手柄按键切换模式。
七、总结
通过 STM32C8T6 与 STM32CubeIDE 开发 PS2 手柄,核心在于精准实现手册中的通讯时序与命令协议。本文基于手册提供了从硬件连接到软件编程的完整流程,开发者可在此基础上扩展应用场景,如机器人控制、工业设备遥控等。如需进一步优化,可尝试调整震动参数、添加无线传输模块或集成更多传感器功能。
八、完整代码
用cubeide配置完成后,按照ide把线连好后将PS2.c,PS2.h,Key.c,Key.h,main.c粘贴就可以使用了
当然这里我会把完整工程上传。
Key.h
/*
* Key.h
*
* Created on: Apr 11, 2025
* Author: 29245
*/
#ifndef INC_KEY_H_
#define INC_KEY_H_
#include "PS2.h"
#include <stdint.h>
uint8_t Key_GetNum(void); // 获取按键值(带去抖)
#endif /* INC_KEY_H_ */
Key.c
/*
* Key.c
*
* Created on: Apr 11, 2025
* Author: 29245
*/
#include "Key.h"
#include "PS2.h"
#include <stdint.h>
uint8_t PS2_DataKey(void);
// 定义消抖延时次数
#define DEBOUNCE_DELAY 5
// 获取按键值(带去抖)
uint8_t Key_GetNum(void) {
static uint8_t key_state = 0;
static uint8_t debounce_count = 0;
uint8_t key_val = PS2_DataKey();
if (key_val) {
if (!key_state) {
// 检测到按键按下,开始消抖计数
debounce_count++;
if (debounce_count >= DEBOUNCE_DELAY) {
// 消抖延时结束,确认按键按下
key_state = 1;
debounce_count = 0;
return key_val;
}
}
} else {
// 按键释放,重置状态和计数器
key_state = 0;
debounce_count = 0;
}
return 0;
}
PS2.h
/*
* PS2.h
*
* Created on: Apr 11, 2025
* Author: 29245
*/
#ifndef INC_PS2_H_
#define INC_PS2_H_
#include "main.h"
#include "gpio.h"
#include <stdint.h>
// 硬件引脚定义(手册版本:PB12-PB15)
#define DAT_GPIO_Port GPIOB
#define DAT_Pin GPIO_PIN_12
#define CMD_GPIO_Port GPIOB
#define CMD_Pin GPIO_PIN_13
#define CS_GPIO_Port GPIOB
#define CS_Pin GPIO_PIN_14
#define CLK_GPIO_Port GPIOB
#define CLK_Pin GPIO_PIN_15
#define DI HAL_GPIO_ReadPin(DAT_GPIO_Port, DAT_Pin)
#define DO_H HAL_GPIO_WritePin(CMD_GPIO_Port, CMD_Pin, GPIO_PIN_SET)
#define DO_L HAL_GPIO_WritePin(CMD_GPIO_Port, CMD_Pin, GPIO_PIN_RESET)
#define CS_H HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_SET)
#define CS_L HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_RESET)
#define CLK_H HAL_GPIO_WritePin(CLK_GPIO_Port, CLK_Pin, GPIO_PIN_SET)
#define CLK_L HAL_GPIO_WritePin(CLK_GPIO_Port, CLK_Pin, GPIO_PIN_RESET)
// 按键定义(与手册一致)
#define PSB_SELECT 1
#define PSB_L3 2
#define PSB_R3 3
#define PSB_START 4
#define PSB_PAD_UP 5
#define PSB_PAD_RIGHT 6
#define PSB_PAD_DOWN 7
#define PSB_PAD_LEFT 8
#define PSB_L2 9
#define PSB_R2 10
#define PSB_L1 11
#define PSB_R1 12
#define PSB_Y 13
#define PSB_X 14
#define PSB_A 15
#define PSB_B 16
// 摇杆定义(与手册一致)
#define PSS_RX 5 // 右摇杆X轴
#define PSS_RY 6 // 右摇杆Y轴
#define PSS_LX 7 // 左摇杆X轴
#define PSS_LY 8 // 左摇杆Y轴
extern uint8_t Data[9];
extern uint16_t MASK[16];
extern uint16_t Handkey;
// 核心函数声明(补充完整)
void PS2_SetInit(void);
uint8_t PS2_DataKey(void);
void PS2_ReadData(void);
void PS2_ClearData(void);
uint8_t PS2_AnologData(uint8_t button);
// 配置相关函数声明(与PS2.c实现一致)
void PS2_ShortPoll(void);// 短轮询(建立连接)
void PS2_EnterConfing(void);// 进入配置模式
void PS2_TurnOnAnalogMode(void);// 开启模拟模式(红灯模式)
void PS2_VibrationMode(void);// 开启震动模式
void PS2_ExitConfing(void);// 退出配置模式并保存
// 震动控制声明
void PS2_Vibration(uint8_t motor1, uint8_t motor2);
#endif /* INC_PS2_H_ */
PS2.c
/*
* PS2.c
*
* Created on: Apr 11, 2025
* Author: 29245
*/
#include "PS2.h"
#include "usart.h"
#include "gpio.h"
#include <stdint.h>
uint16_t Handkey;
uint8_t Comd[2] = {0x01, 0x42}; // 开始命令,请求数据
uint8_t Data[9] = {0}; // 数据存储数组
void delay_us(uint32_t us) {
uint32_t delay = (HAL_RCC_GetHCLKFreq() / 4000000 * us);
while (delay--);
}
uint16_t MASK[16] = {
PSB_SELECT, PSB_L3, PSB_R3, PSB_START, // 1-4
PSB_PAD_UP, PSB_PAD_RIGHT, PSB_PAD_DOWN, PSB_PAD_LEFT, // 5-8
PSB_L2, PSB_R2, PSB_L1, PSB_R1, // 9-12
PSB_Y, PSB_X, PSB_A, PSB_B // 13-16(修改后)
};
// 向手柄发送命令
void PS2_Cmd(uint8_t CMD) {
uint16_t ref;
Data[1] = 0;
for (ref = 0x01; ref < 0x100; ref <<= 1) {
CMD & ref ? DO_H : DO_L;
CLK_H; delay_us(5);
CLK_L; delay_us(5);
CLK_H;
if (DI) Data[1] |= ref;
}
delay_us(16);
}
// 判断红灯模式
uint8_t PS2_RedLight(void) {
CS_L;
PS2_Cmd(Comd[0]);
PS2_Cmd(Comd[1]);
CS_H;
return (Data[1] == 0x73) ? 0 : 1;
}
// 读取手柄数据
void PS2_ReadData(void) {
uint8_t byte;
uint16_t ref;
CS_L;
PS2_Cmd(Comd[0]);
PS2_Cmd(Comd[1]);
for (byte = 2; byte < 9; byte++) {
for (ref = 0x01; ref < 0x100; ref <<= 1) {
CLK_H; delay_us(5);
CLK_L; delay_us(5);
CLK_H;
if (DI) Data[byte] |= ref;
}
delay_us(16);
}
CS_H;
}
// 按键处理
uint8_t PS2_DataKey(void) {
uint8_t index;
PS2_ClearData();
PS2_ReadData();
Handkey = (Data[4] << 8) | Data[3];
for (index = 0; index < 16; index++) {
if (!(Handkey & (1 << (MASK[index] - 1)))) {
return index + 1;
}
}
return 0;
}
// 清除数据
void PS2_ClearData(void) {
for (uint8_t a = 0; a < 9; a++) {
Data[a] = 0;
}
}
// 震动控制
void PS2_Vibration(uint8_t motor1, uint8_t motor2) {
CS_L; delay_us(16);
PS2_Cmd(0x01); PS2_Cmd(0x42);
PS2_Cmd(0x00); PS2_Cmd(motor1);
PS2_Cmd(motor2); PS2_Cmd(0x00);
PS2_Cmd(0x00); PS2_Cmd(0x00);
PS2_Cmd(0x00); CS_H; delay_us(16);
}
// 初始化配置
void PS2_SetInit(void) {
PS2_ShortPoll(); PS2_ShortPoll(); PS2_ShortPoll();
PS2_EnterConfing();
PS2_TurnOnAnalogMode();
PS2_VibrationMode();
PS2_ExitConfing();
}
// 短轮询(建立连接)
void PS2_ShortPoll(void) {
CS_L;
delay_us(16);
PS2_Cmd(0x01);
PS2_Cmd(0x42);
PS2_Cmd(0x00);
PS2_Cmd(0x00);
PS2_Cmd(0x00);
CS_H;
delay_us(16);
}
// 进入配置模式
void PS2_EnterConfing(void) {
CS_L;
delay_us(16);
PS2_Cmd(0x01);
PS2_Cmd(0x43);
PS2_Cmd(0x00);
PS2_Cmd(0x01);
PS2_Cmd(0x00);
PS2_Cmd(0x00);
PS2_Cmd(0x00);
PS2_Cmd(0x00);
PS2_Cmd(0x00);
CS_H;
delay_us(16);
}
// 开启模拟模式(红灯模式)
void PS2_TurnOnAnalogMode(void) {
CS_L;
PS2_Cmd(0x01);
PS2_Cmd(0x44);
PS2_Cmd(0x00);
PS2_Cmd(0x01); // 模拟模式
PS2_Cmd(0xEE); // 允许手柄MODE键切换(0x03为锁存)
PS2_Cmd(0x00);
PS2_Cmd(0x00);
PS2_Cmd(0x00);
PS2_Cmd(0x00);
CS_H;
delay_us(16);
}
// 开启震动模式
void PS2_VibrationMode(void) {
CS_L;
delay_us(16);
PS2_Cmd(0x01);
PS2_Cmd(0x4D);
PS2_Cmd(0x00);
PS2_Cmd(0x00);
PS2_Cmd(0x01);
CS_H;
delay_us(16);
}
// 退出配置模式并保存
void PS2_ExitConfing(void) {
CS_L;
delay_us(16);
PS2_Cmd(0x01);
PS2_Cmd(0x43);
PS2_Cmd(0x00);
PS2_Cmd(0x00);
PS2_Cmd(0x5A); // 连续发送0x5A以保存配置
PS2_Cmd(0x5A);
PS2_Cmd(0x5A);
PS2_Cmd(0x5A);
PS2_Cmd(0x5A);
CS_H;
delay_us(16);
}
// 获取摇杆模拟量(关键函数,之前未正确实现)
uint8_t PS2_AnologData(uint8_t button) {
if (button >= PSS_RX && button <= PSS_LY) { // 5-8对应摇杆数据
return Data[button];
}
return 0; // 无效按钮返回0
}
Servo.h
/*
* Servo.h
*
* Created on: Apr 11, 2025
* Author: 29245
*/
#ifndef INC_SERVO_H_
#define INC_SERVO_H_
#include <stdint.h>
// 舵机初始化
void Servo_Init(void);
// 设置舵机角度
void Servo_SetAngle(uint8_t angle);
#endif /* INC_SERVO_H_ */
Servo.c
/*
* Servo.c
*
* Created on: Apr 11, 2025
* Author: 29245
*/
#include "Servo.h"
#include "tim.h"
// 舵机角度转换为 PWM 占空比
uint16_t angle_to_pwm(uint8_t angle) {
// 0.5ms - 2.5ms 对应 0 - 180 度
// 定时器 72Mhz 720 分频,自动重装载值 2000,周期 20ms
// 0.5ms 对应 50,2.5ms 对应 250
return (angle * (250 - 50) / 180) + 50;
}
// 舵机初始化
void Servo_Init(void) {
HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_1);
}
// 设置舵机角度
void Servo_SetAngle(uint8_t angle) {
if (angle > 180) {
angle = 180;
}
__HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, angle_to_pwm(angle));
}
main.c
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file : main.c
* @brief : Main program body
******************************************************************************
* @attention
*
* Copyright (c) 2025 STMicroelectronics.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "dma.h"
#include "tim.h"
#include "usart.h"
#include "gpio.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
/* USER CODE END PTD */
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
/* USER CODE END PM */
/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN PV */
uint8_t uart_tx_buffer[64]; // 足够存储按键信息字符串
volatile uint8_t dma_transmit_complete = 1; // DMA传输完成标志(原子操作)
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */
/* USER CODE END PFP */
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
/* USER CODE END 0 */
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_DMA_Init();
MX_TIM3_Init();
MX_USART1_UART_Init();
MX_USART2_UART_Init();
/* USER CODE BEGIN 2 */
PS2_SetInit(); // 初始化PS2手柄
HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_1); // 启动舵机PWM输出
PS2_TurnOnAnalogMode(); // 开启模拟模式
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
// //串口调试
// char test[] = "UART Test OK\r\n";
// HAL_UART_Transmit_DMA(&huart1, (uint8_t*)test, sizeof(test));
//
// // 等待 DMA 传输完成(调试时可阻塞,正式项目用回调)
// while (HAL_UART_GetState(&huart1) != HAL_UART_STATE_READY); // 临时阻塞等待
//
// HAL_Delay(1000); // 发送间隔 1 秒
// 串口调试按键
uint8_t key = Key_GetNum();
if (key && dma_transmit_complete) { // 仅在传输完成时处理新按键
dma_transmit_complete = 0; // 标记传输中
char buffer[50];
snprintf(buffer, sizeof(buffer), "Key %d pressed\r\n", key);
HAL_UART_Transmit_DMA(&huart1, (uint8_t*)buffer, strlen(buffer));
}
HAL_Delay(10); // 降低CPU占用,配合按键去抖
// }
// //遥感器控制舵机
// PS2_ReadData();
// uint8_t lx, ly, rx, ry; // PS2摇杆X/Y轴坐标(左/右摇杆)
// int angle;// 获取PS2数据
// lx = PS2_AnologData(PSS_LX);
// ly = PS2_AnologData(PSS_LY);
// rx = PS2_AnologData(PSS_RX);
// ry = PS2_AnologData(PSS_RY);
//
// // 舵机角度计算(映射0-255到0-180,含边界保护)
// angle = (int)lx * 180 / 255; // 类型转换避免溢出
// angle = (angle < 0) ? 0 : angle; // 下限保护(若舵机支持0°)
// angle = (angle > 180) ? 180 : angle; // 上限保护
//
// Servo_SetAngle(angle); // 控制舵机
//
// // 格式化输出(使用局部变量避免重复函数调用)
// char buffer[100];
// snprintf(buffer, sizeof(buffer), "LX:%3d LY:%3d RX:%3d RY:%3d Angle:%3d\r\n",
// lx, ly, rx, ry, angle);
// HAL_UART_Transmit_DMA(&huart1, (uint8_t*)buffer, strlen(buffer));
//
// PS2_ClearData(); // 清除PS2数据缓冲区
// HAL_Delay(10); // 控制循环频率
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
/**
* @brief System Clock Configuration
* @retval None
*/
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
/** Initializes the RCC Oscillators according to the specified parameters
* in the RCC_OscInitTypeDef structure.
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
/** Initializes the CPU, AHB and APB buses clocks
*/
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
{
Error_Handler();
}
}
/* USER CODE BEGIN 4 */
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart) {
if (huart == &huart1) {
dma_transmit_complete = 1; // 传输完成后重置标志
}
}
/* USER CODE END 4 */
/**
* @brief This function is executed in case of error occurrence.
* @retval None
*/
void Error_Handler(void)
{
/* USER CODE BEGIN Error_Handler_Debug */
/* User can add his own implementation to report the HAL error return state */
__disable_irq();
while (1)
{
}
/* USER CODE END Error_Handler_Debug */
}
#ifdef USE_FULL_ASSERT
/**
* @brief Reports the name of the source file and the source line number
* where the assert_param error has occurred.
* @param file: pointer to the source file name
* @param line: assert_param error line source number
* @retval None
*/
void assert_failed(uint8_t *file, uint32_t line)
{
/* USER CODE BEGIN 6 */
/* User can add his own implementation to report the file name and line number,
ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
/* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */