【STM32F4系列】【HAL库】【自制库】ps2手柄模块驱动

news2025/1/8 5:52:36

外观和电气连接

外观

手柄外观如下

在这里插入图片描述

在这里插入图片描述

接收器外观

这是接收器和底座

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4iYzkU5O-1672150811066)(图片/3.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LmF2PFAo-1672150811067)(图片/4.png)]

电气连接

需要4根连接线

单片机输出是CLK DO CS

单片机输入是DI

电源电压是3.3-5v

注意模块和单片机共地

模块不支持高速,最大时钟周期约为4us左右

因此使用软件模拟时序的方式来与模块通信

只需要将模块的4根线与单片机软件中对应的GPIO连接起来就行了

#define PS2_CS_GPIOx GPIOA
#define PS2_CS_Pin GPIO_PIN_4

#define PS2_CLK_GPIOx GPIOA
#define PS2_CLK_Pin GPIO_PIN_5

#define PS2_DO_GPIOx GPIOA
#define PS2_DO_Pin GPIO_PIN_7

#define PS2_DI_GPIOx GPIOA
#define PS2_DI_Pin GPIO_PIN_6

软件设计

HAL初始化

需要 3个推挽输出模式的GPIO 分别是 CLK DO CS

1个上拉输入模式 DI

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mnFxvz6d-1672150811067)(图片/5.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ORM1j06t-1672150811068)(图片/6.png)]

协议分析

cs拉低开始单片机与模块的通信,拉高则通信结束

DO,DI在在CLK时钟的下降沿完成数据的发送和读取

本协议是类似SPI的协议

通信一共9个字节

这里的DODI指的是单片机的GPIO名称

Byte功能
0DO->0x01 DI <-(无意义)
1DO->0x42 DI<-(红灯0x73 无灯0X41)
2DO->0xFF DI<-0X5A
3-8DO->0xFF DI<-(按键数据)

按键数据如下

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IpAyuWLb-1672150811068)(图片/7.png)]

按键分析

手柄发送来的有两种模式的数据

无灯模式(MOD LED不亮)发送的摇杆是数字量,摇杆左右两侧的四向按键功能和摇杆重合

即如下图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZTfNvHpV-1672150811069)(图片/8.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wJ8AWDsg-1672150811069)(图片/9.png)]

红灯模式(MODE LED亮红灯)

模块传输的是摇杆的模拟量

外侧大按键独立

代码

为了方便建立了一个结构体来存储按键的数据,代码有注释,请根据设备自行测试是哪个按键即可

typedef struct
{
    uint8_t A_D;                                       //模拟(红灯)为1 数字(无灯)为0
    int8_t Rocker_RX, Rocker_RY, Rocker_LX, Rocker_LY; //摇杆值(模拟状态为实际值0-0xFF)(数字态为等效的值0,0x80,0xFF)
    //按键值0为未触发,1为触发态
    uint8_t Key_L1, Key_L2, Key_R1, Key_R2;                //后侧大按键
    uint8_t Key_L_Right, Key_L_Left, Key_L_Up, Key_L_Down; //左侧按键
    uint8_t Key_R_Right, Key_R_Left, Key_R_Up, Key_R_Down; //右侧按键
    uint8_t Key_Select;                                    //选择键
    uint8_t Key_Start;                                     //开始键
    uint8_t Key_Rocker_Left, Key_Rocker_Right;             //摇杆按键

} PS2_TypeDef;
extern PS2_TypeDef PS2_Data;
#include "PS2.h"
uint8_t PS2_RawData[9] = {0};
PS2_TypeDef PS2_Data = {0};
void PS2_CS(uint8_t Val)
{
    if (Val)
        HAL_GPIO_WritePin(PS2_CS_GPIOx, PS2_CS_Pin, GPIO_PIN_SET);
    else
        HAL_GPIO_WritePin(PS2_CS_GPIOx, PS2_CS_Pin, GPIO_PIN_RESET);
}
void PS2_CLK(uint8_t Val)
{
    if (Val)
        HAL_GPIO_WritePin(PS2_CLK_GPIOx, PS2_CLK_Pin, GPIO_PIN_SET);
    else
        HAL_GPIO_WritePin(PS2_CLK_GPIOx, PS2_CLK_Pin, GPIO_PIN_RESET);
}
void PS2_DO(uint8_t Val)
{
    if (Val)
        HAL_GPIO_WritePin(PS2_DO_GPIOx, PS2_DO_Pin, GPIO_PIN_SET);
    else
        HAL_GPIO_WritePin(PS2_DO_GPIOx, PS2_DO_Pin, GPIO_PIN_RESET);
}
uint8_t PS2_Read_DI()
{
    return HAL_GPIO_ReadPin(PS2_DI_GPIOx, PS2_DI_Pin);
}
void PS2_Delay()
{
    for (int i = 0; i < 0xBf; i++)
        __nop();
}
uint8_t PS2_ReadWrite_Byte(uint8_t TxData)
{
    uint8_t TX = TxData;
    uint8_t RX = 0;
    for (int i = 0; i < 8; i++)
    {
        if (TX & 0x01)
            PS2_DO(1);
        else
            PS2_DO(0);
        TX >>= 1;
        PS2_CLK(1);
        PS2_Delay();
        PS2_CLK(0);
        RX >>= 1;
        RX |= (PS2_Read_DI() << 7);
        PS2_Delay();
        PS2_CLK(1);
        PS2_Delay();
    }
    return RX;
}

void PS2_Decode()
{
    if (PS2_RawData[2] == 0x5A)
    {
        PS2_Data.Key_Select = (~PS2_RawData[3] >> 0) & 0x01; //选择键
        PS2_Data.Key_Start = (~PS2_RawData[3] >> 3) & 0x01;  //开始键

        //左侧按键
        PS2_Data.Key_L_Up = (~PS2_RawData[3] >> 4) & 0x01;
        PS2_Data.Key_L_Right = (~PS2_RawData[3] >> 5) & 0x01;
        PS2_Data.Key_L_Down = (~PS2_RawData[3] >> 6) & 0x01;
        PS2_Data.Key_L_Left = (~PS2_RawData[3] >> 7) & 0x01;

        //后侧按键
        PS2_Data.Key_L2 = (~PS2_RawData[4] >> 0) & 0x01;
        PS2_Data.Key_R2 = (~PS2_RawData[4] >> 1) & 0x01;
        PS2_Data.Key_L1 = (~PS2_RawData[4] >> 2) & 0x01;
        PS2_Data.Key_R1 = (~PS2_RawData[4] >> 3) & 0x01;

        //右侧按键
        PS2_Data.Key_R_Up = (~PS2_RawData[4] >> 4) & 0x01;
        PS2_Data.Key_R_Right = (~PS2_RawData[4] >> 5) & 0x01;
        PS2_Data.Key_R_Down = (~PS2_RawData[4] >> 6) & 0x01;
        PS2_Data.Key_R_Left = (~PS2_RawData[4] >> 7) & 0x01;

        if (PS2_RawData[1] == 0x41)
        { //无灯模式(摇杆值八向)
            PS2_Data.Rocker_LX = 127 * (PS2_Data.Key_L_Right - PS2_Data.Key_L_Left);
            PS2_Data.Rocker_LY = 127 * (PS2_Data.Key_L_Up - PS2_Data.Key_L_Down);

            PS2_Data.Rocker_RX = 127 * (PS2_Data.Key_R_Right - PS2_Data.Key_R_Left);
            PS2_Data.Rocker_RY = 127 * (PS2_Data.Key_R_Up - PS2_Data.Key_R_Down);
        }
        else if (PS2_RawData[1] == 0x73)
        { //红灯模式(摇杆值模拟)

            //摇杆按键
            PS2_Data.Key_Rocker_Left = (~PS2_RawData[3] >> 1) & 0x01;
            PS2_Data.Key_Rocker_Right = (~PS2_RawData[3] >> 2) & 0x01;

            //摇杆值
            PS2_Data.Rocker_LX = PS2_RawData[7] - 0x80;
            PS2_Data.Rocker_LY = -1 - (PS2_RawData[8] - 0x80);
            PS2_Data.Rocker_RX = PS2_RawData[5] - 0x80;
            PS2_Data.Rocker_RY = -1 - (PS2_RawData[6] - 0x80);
        }
    }
}
void PS2_Read_Data(void)
{
    PS2_CS(0);
    PS2_RawData[0] = PS2_ReadWrite_Byte(0x01); // 0
    PS2_RawData[1] = PS2_ReadWrite_Byte(0x42); // 1
    for (int i = 2; i < 9; i++)
        PS2_RawData[i] = PS2_ReadWrite_Byte(0xff);
    PS2_CS(1);
    PS2_Decode();
}

手柄的效果还可以,对得起25元的价格

可以二次开发做成遥控车遥控器或者电脑手柄

成品

Github

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/118617.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Golang Context 的几种应用场景

Golang context主要用于定义超时取消&#xff0c;取消后续操作&#xff0c;在不同操作中传递值。本文通过简单易懂的示例进行说明。 超时取消 假设我们希望HTTP请求在给定时间内完成&#xff0c;超时自动取消。 首先定义超时上下文&#xff0c;设定时间返回取消函数&#xff…

Apache POI操作百万数据excel实战方案及JDK性能监控工具Jvisualvm实战

百万数据报表概述 文章目录**百万数据报表概述****1、** **概述****2、 JDK性能监控工具介绍****2.1、 Jvisualvm概述****2.2、 Jvisualvm的位置****2.3、 Jvisualvm的使用****3、** **解决方案分析****4**、**百万数据报表导出****4.1** **需求分析****4.2** **解决方案****4.…

玩转门店管理新方法,促进营收利润加倍

门店管理的好坏是门店是否可以运营下去的重要因素&#xff0c;决定了门店的存亡与兴衰。以往很多门店管理者为了更简单方便&#xff0c;采用的是传统方式进行管理。即运用手工的方式记录和计算门店的各种信息。但是随着门店规模的扩大、商品种类的丰富、客户需求的增加以及员工…

普惠微光汇聚暖阳,招联携手奋斗者筑梦前行

撰稿 | 多客 来源 | 贝多财经 近年来&#xff0c;受疫情反复带来的经济下行压力&#xff0c;收入减少生活难以保障成了社会一大难题。值此艰难时刻&#xff0c;一大批爱心企业出资出力&#xff0c;纷纷用实际行动诠释企业的使命和担当。口罩、防护服、矿泉水、食品、药物、免费…

vue配置webpack生产环境.env.production、测试环境.env.development(配置不同环境的打包访问地址)

vue-cli区分办法 vue配置生产环境.env.production、测试环境.env.development vue配置webpack生产环境、测试环境 在使用webpack创建完vue2项目的时候&#xff0c;为了解决生产打包、测试打包对应的全局变量不一致的问题。 首先看一下package.json的改动&#xff1a; "…

MARL算法系列(1):IQL【原理+代码实现】

原文题目&#xff1a;Multiagent cooperation and competition with deep reinforcement learning 作者&#xff1a;Tampuu, Ardi and Matiisen, Tambet and Kodelja, Dorian等 发表时间&#xff1a;2017年 主要内容&#xff1a;相互独立的两个DQN智能体&#xff0c;竞争任务下…

2022年威胁隐私和安全的数个“罪魁祸首”

随着互联网技术的不断发展&#xff0c;我们对网络的信任也在不断增加&#xff0c;甚至将自己的私人数据委托给各种在线平台&#xff0c;如个人数字身份信息、银行账户、各种机密信息。网络一方面的确带来变革型的进步&#xff0c;但另一方面&#xff0c;频频曝光的数据泄露事件…

VueJs中setup的使用(下)

前言在Vue当中,父组件想要向子组件传值,是通过在父组件标签上通过自定义属性实现的,而在子组件中是通过props进行接收在Vue2.0里面,在子组件中的选项式API配置项选项中props进行接收就可以了的,在子组件中的模板中可以直接使用但在Vue3里面与Vue2.0存在一些差异,这个主要是针对…

excel文件管理:如何进行密码保护和破解? 下篇

在上篇文章中&#xff0c;我们提到了设置工作簿的打开权限密码、修改权限密码、保护工作簿的密码、允许编辑区域的密码&#xff0c;并且讲到了两种破解excel密码的方式。今天&#xff0c;我们书接上回&#xff0c;继续讲解excel中常见的密码保护和破解方式&#xff0c;一起来看…

浅谈屏幕适配

文章目录1. 概述2. 屏幕尺寸3. 屏幕分辨率4. 屏幕像素密度5. dp、sp、px6. mdpi、hdpi、xdpi..7. 屏幕分辨率限定符8. 最小宽度限定符8.1 获取设计图最小宽度(dp)8.2 生成对应的dimens.xml文件8.3 尺寸限定符8.4 其它9. 今日头条相关9.1 系统状态栏获取不对问题9.2 autosize1. …

Elasticsearch8.X入门实战(二)Elasticsearch集群架构

Elasticsearch集群由一个或多个节点(服务器)组成,这些节点一起保存Elasticsearch的所有数据,并提供跨所有节点的联合索引和搜索功能。集群由一个唯一的名称来标识,该名称默认为“elasticsearch”(可以在配置文件中修改)。当某个节点被设置为相同的集群名称时,该节点才能…

Docker容器的简单介绍与使用

前言&#xff1a;大家好&#xff0c;我是小威&#xff0c;24届毕业生&#xff0c;曾经在某央企公司实习&#xff0c;目前入职某税务公司。本篇文章将记录和分享docker容器相关的知识点。 本篇文章记录的基础知识&#xff0c;适合在学Java的小白&#xff0c;也适合复习中&#x…

如何更好地进行 Android 组件化开发——路由原理篇

前言 组件化开发的会实现代码隔离&#xff0c;在开发时访问不到模块的代码&#xff0c;降低代码耦合度。那么如何跳转组件的页面、如何进行组件间的通信是个问题。这通常会使用到 ARouter、TheRouter、WMRouter 等路由框架。可能有不少人只知道怎么去调用&#xff0c;并不知道…

Pod内容详情梳理

本篇是笔者的一篇读书笔记&#xff0c;用于梳理pod的详情&#xff0c;方便理解和学习&#xff0c;也方便后续自己查询。一、Pod的概述Pod是k8s里面典型的CR&#xff0c;从它的元数据来看&#xff0c;具有所有CR的基本数据构成&#xff0c;分别是 version、kind&#xff0c;以及…

迅为RK3568开发板支持多屏同显/异显动态方案

iTOP-RK3568开发板采用四核Cortex-A55处理器&#xff0c;芯片内置VOP控制器&#xff0c;支持HDMI、LVDS、MIPI、EDP四种显示接口的多屏同显、异显和异触&#xff0c;可有效提高行业定制的拓展性。 三屏同显&#xff1a; 三屏异显&#xff1a; 双屏同显&#xff1a; 双屏异显&am…

Docker容器里进程的 pid 是如何申请出来的?

大家好&#xff0c;我是飞哥&#xff01;如果大家有过在容器中执行 ps 命令的经验&#xff0c;都会知道在容器中的进程的 pid 一般是比较小的。例如下面我的这个例子。# ps -ef PID USER TIME COMMAND1 root 0:00 ./demo-ie13 root 0:00 /bin/bash21 root …

编程小技巧9-如何生成没有水印的代码图片(IDEA carbon-now-sh插件使用教程)

陈老老老板&#x1f9b8;&#x1f468;‍&#x1f4bb;本文专栏&#xff1a;快速变成小技巧&#xff08;主要讲一些平时常用的、有助于提高开发素的内容&#xff09;&#x1f468;‍&#x1f4bb;本文简述&#xff1a;本文讲一下使用carbon-now-sh插件生成图片超详细教程。&…

STM32

一&#xff1a;生成独立的他.h和.c文件 勾选后&#xff0c;生成单独的.h和.c文件。不勾选的话都在main里面。 二&#xff1a;常用。 1&#xff1a;电平输出。 HAL_GPIO_WritePin(PIN_LED_1_GPIO_Port, PIN_LED_1_Pin, GPIO_PIN_SET); HAL_GPIO_WritePin(PIN_LED_1_GPIO_Port, …

django 登录流程实现

一、简介&#xff1a; 1、用户输入正确的用户名、密码、验证码点击登录即可跳转到管理员页面。 2、用户输入错误的用户名或者密码或者验证码需要错误信息提示&#xff08;数据校验&#xff09; 二、实现步骤 1、新建一个项目&#xff08;创建项目过程和数据库略&#xff0c;…

签完三方后无法去实习,有什么可以弥补的吗?

作者&#xff1a;阿秀校招八股文学习网站&#xff1a;https://interviewguide.cn这是阿秀的第「228」篇原创你好&#xff0c;我是阿秀。2023届秋招已经步入尾声&#xff0c;很多小伙伴都已经找到工作&签约三方&#xff0c;慢慢结束了自己的秋招之旅&#xff0c;不过也有一些…