基于STM32F103RCT6的PS2手柄控制舵机转向小车

一、PS2

        (1)当接收器上的绿灯常亮时,证明手柄和接收器配对成功,可以正常进行数据通讯。如果手柄和接收器断开了,按手柄上的START键即可恢复配对;

        (2)当手柄上的MODE指示灯没有点亮的时候,游戏摇杆四个方向输出按键键码值;当点击MODE按键后,手柄上的MODE指示灯变亮(红色),此时游戏摇杆四个方向输出AD值。

        (3)PS2 由手柄与接收器两部分组成,手柄主要负责发送按键信息。接通电源并打开手柄开关时,手柄与接收器自动配对连接,在未配对成功的状态下,接收器绿灯闪烁,手柄上的灯也会闪烁,配对成功后,接收器上绿灯常亮,手柄上灯也常亮,这时可以按“MODE”键,选择手柄发送模式

        (4)红灯模式:遥杆输出模拟值; 绿灯模式:遥杆对应上面四个按键,只有四个极限方向对应。

1.亮灯模式

        即手柄MODE指示灯亮时,手柄ID输出为0x73(网上所谓的红灯模式),左右摇杆才会输出模拟量。L3、R3按键有效

2.无灯模式

        即MODE指示灯不亮灯时,手柄输出为0x41(网上所谓的绿灯模式),左右摇杆不会输出模拟量,其极限值“化身”为左右两边四个方向的按键。L3、R3按键无效

【注意】

        不论在亮灯模式还是无灯模式里,除了L3、R3的其他所有按键均有效

        (在本次项目里我还没有使用过L3、R3,所以不知其工作模式)

3.硬件连接

(1)PS2接收器
(2)PS2接收器转接板

        麻烦一定要用转接板,别想着直接用杜邦线把接收器与单片机连在一起!!!(本人之前一天PS2与单片机没有通讯成功,就是因为没有接转接板)(这个我没有试过,后续可以试一下看看不用转接板是不是真的不行)

注意:

 (1) 使用的时候只需要把接收器插在转接板上面,然后使用杜邦线或者商家给的线连在单片机上即可

(2)注意信号线一定不要接反或者接错。

        用好的代码测新接的硬件就可以知道新接的硬件有没有问题,所以每次要有一个外设测试的基础代码(舵机、电机、PS2、超声波、九轴等这些外设的基础代码,然后CubeMx里面配置都是配置一样的输出口,不然还要一直换引脚)

        这个可以通过自己写好的底层示例代码(就是一个工程只创建单片机与PS2通信的测试代码,通过串口来显示出PS2的数据是否传输到了单片机上)如果原来的代码是正确的,那么就直接压缩文件,再测试硬件是不是有问题的时候,就直接把测试代码烧入新的硬件,如果有反应说明新的硬件连接正确,如果没有成功,说明硬件有问题

        硬件可能出现的问题:

        (1)PS2的信号线和VCC、GND接在单片机上可能接错了,这个是最常见的问题

        (2)单片机的板子有问题:STM32F103RCT6可能出现了问题,换一块板子试一下

        (3)有可能ST-Link接到单片机的杜邦线有问题:可以换新的杜邦线试一下,因为有些老的杜邦线可能接触不良之类出现问题了。(我之前就是换了好多块RCT6的板子接电之后那个LED灯时亮时不亮的,我一直以为是板子的问题,没想到是杜邦线坏了。虽然板子也容易坏,但是一下坏很多块也是没太可能的,就看看是不是ST-Link或者杜邦线有问题,就是一点点排查)

        (4)还有要特别注意的是,接收器的VCC和GND的给电情况(这个可以后续再好好学学PS2的硬件补充一下相关知识)

        如果电压给得很大的话,有可能会出现程序运行到一半就重启的情况(当时省赛的时候就是硬件软件都没问题,但就是走到一快结束的时候突然整个车都重启了,就是因为当时电压给的是11伏,太大了,所以相当于复位了(之前CBT6复位有个小方法就是VCC和GND短接一下,就能很起到复位的效果))

         所以当时我的阿克曼小车一按MODE键过一会儿就重启有可能是我稳压上面是12伏,电压太大直接复位了,后面把电压调小了,还是这种问题就是我的PS2接的是5伏的VCC,我给他给到3.3的VCC就没有出现这种情况了,所以有可能是电压的问题。(后续学一下TB6612给单片机供电,单片机给外设供电的知识,这块儿每次都是靠蒙)

        (5)还有一种可能性很低的情况出现:ST-Link和PS2的硬件坏了,但是这种一般不会出现,一般都是板子有问题(因为很多板子都用了很久了,所以也不知道之前有没有出现什么毛病)

        (6)有一种可能性很高的情况:就是软件输入进去,硬件还没有反应过来,过一会儿就好了        

        (之前省赛的时候,在软件里面加一个延时的Delay函数,硬件的OLED就可以显示C8T6和RCT6的超声波的数据了,而且C8T6也不需要复位了,这种情况就是软件准备好了,但是硬件还没有准备好,所以加一个Delay函数,等待硬件准备好)

        (我记得我的阿克曼小车当时调试的时候就是可以后退但不能前进,不过前进有扭矩,就说明信号是接收到的,但是把硬件放一会儿了就好了,所以有时候可能是代码烧入太多,然后硬件卡住了,所以等一会儿,以后可以多留心一下这种情况,看看到底是哪里出现了问题)

(3)PS2接收器转接板与单片机的引脚连接

使用PA7-->CMD\DO将数据从单片机发送给PS2手柄(单片机发、PS2接)

                 MOSI(Master Output Slave Input):主机输出,从机输入

使用PA6-->DAT\DI将数据从PS2手柄接收到单片机(单片机接、PS2发)

                 MISO(Master Input Slave Output):主机输入,从机输出      

使用PA4-->CS作为片选信号

使用PA5-->CLK作为时钟信号

使用GND与3V3给PS2接收器供电
 

二、CubeMx的配置

(1)配置好一个串口,不需要打开中断和DMA

首先配置CubeMx里面的串口 

 然后配置Keil5里面的串口
(1)放在int 外面

头文件里面要包含这两个,方便打印(C语言里面的知识) 

(可以试试没有这个会发生什么,补充一下C语言的理论知识,忘了)

#include "stdio.h"
#include "string.h"

 这个是串口的重定向

(可以试试没有这个会发生什么,补充一下串口的理论知识,忘了)

int fputc(int ch,FILE *f)
{
	HAL_UART_Transmit(&huart1,(uint8_t *)&ch,1,HAL_MAX_DELAY);
	return ch;
}

int fgetc(FILE *f)
{
	uint8_t ch;
	HAL_UART_Receive( &huart1,(uint8_t*)&ch,1, HAL_MAX_DELAY );
	return ch;
}
(2) 放在while里面

        (int函数里面while函数外面没有什么需要配置的,因为没有开串口中断,如果开了中断的话需要写一下)

printf ("%d",ButtonValue[i]);//printf("Hello world");
//这个是打印的东西
HAL_Delay (10);
//这个是延时,就不会打印的那么快    
printf ("\r\n");
//这个是换行符,跟C语言有点区别
(3)最重要的 

        如果串口的keil5的代码写的没问题,串口助手的波特率没有问题,端口没有选错的情况下,还是没有办法显示串口打印的信息,就看看配置里面串口的这个开没开,同时可以检测串口上除了红色的灯亮了,看看紫色的灯亮没亮闪没闪,一般紫色的灯在闪的话(这个我也记不清了,记得是在闪)就说明串口在工作,代码没问题,硬件没问题,CubeMx和串口助手的配置都没有问题的情况下还没有反应,那就只能是串口助手有问题,换一个串口助手

        (这种都是上电之后都会亮的灯,如果没亮要么就是VCC、GND接反了,要么就是硬件坏了,要么就是杜邦线接触不良。像RCT6、C8T6、ESP这种开发板,上电之后指示灯一闪一闪的就说明没有代码烧入进去还是新的板子,如果同时这些开发板还接了其它外设比如PS2、TB6612可以看看这些外设的指示灯亮没亮,没亮就有问题,要检查一下) 

 

(2)配置PS2

        配置完成后在connectivity下找到SPI1        

        这里我们选择Full-Duplex Master

        First Bit 选择 LSB,预分频Prescaler选择256,CPOL选择High,CPHA选择1 Edge,片选信号NSS选择软件方式(我也不知道这个为什么这么选,后续学学)

        CubeMx配置好了之后Keil里面不需要再配置什么了,只需要把PS2的库移植过来,就可以直接使用PS2了,上面配置串口是为了检查PS2的程序是不是正常执行

        打开任意SPI即可,配置严格遵循上图,可能你会发现你的Baud Rate不是250KBit/s,这就需要你调整你的时钟频率,试凑法给调到250KBit/s!这很重要!!!!

        你需要一个输出引脚来连接接收器的CS,因为在配置中使用了标签,所以在写代码的时候就要用标签的代码(具体知识点我也忘了,可以后面补补)

HAL_GPIO_WritePin(CSS_GPIO_Port, CSS_Pin, GPIO_PIN_RESET);//拉低片选CSS,代表选中设备可以开始通信

(3)时钟树配置

三、ps2库.c和.h以及delay(微秒级).c和.h和main.c的代码

       PS2 手柄由手柄与接收器两部分组成,手柄主要负责发送按键信息;接收器与单片机(也可叫作主机)相连,接收器用于接收手柄发来的信息,并传递给单片机,单片机也可通过接收器,向手柄发送命令,配置手柄的发送模式。

(1)DAT\DI代表单片机接收的数据,将数据从PS2手柄接收到单片机

(2)CMD\DO代表单片机发送的数据,将数据从单片机发送给PS2手柄

(3)数据在时钟CLK下降沿的时候才可以正常传输。

(4)数据的接收需要在CS端口为低电平时传输,且在通信时,只有一串数据传输完,CS才由低拉到高,而不是传输完一个字节后就由低拉到高

        当主机想读手柄数据时,将会拉低 CS 线电平,并发出一个命令“0x01”;手柄会回复它的 ID“0x41=模拟绿灯,0x73=模拟红灯”;在手柄发送 ID 的同时,主机将传送 0x42,请求数据;随后手柄发送出 0x5A,告诉主机“数据来了” 

       

        单片机通过PC6和PC7模拟上面那个时序,向PS2发送0x01,再发送一个0x42,PS2会传输回单片机一个ID(0x73代表亮灯模式,0x41代表无灯模式),接着你再随便发一个数,PS2会回一个0x5A,说我要开始传输你需要的按键信息了

HAL_GPIO_WritePin(CSS_GPIO_Port, CSS_Pin, GPIO_PIN_RESET);

                        //拉低片选CSS,代表选中设备可以开始通信

HAL_SPI_TransmitReceive(&hspi1, &CMD[0], &PS2OriginalValue[0], 1, HAL_MAX_DELAY);

                                                           0x01         0x00
    Delay_us(10);
    HAL_SPI_TransmitReceive(&hspi1, &CMD[1], &PS2OriginalValue[1], 1, HAL_MAX_DELAY);

                                                            0x42            0x41/0x73
    Delay_us(10);
    HAL_SPI_TransmitReceive(&hspi1, &CMD[2], &PS2OriginalValue[2], 1, HAL_MAX_DELAY);

                                                                0x01          0x5A
    Delay_us(10);

        接着你按照之前的方式发6个数,同时也会接着收到PS2发回来的6个数,这六个数就是你需要的按键信息,下面这个表讲的就是这个事:

for (i = 3; i < 9; i++)//接下来持续发送0x00,可以接收到按键信息以及摇杆信息
{
      HAL_SPI_TransmitReceive(&hspi1, &CMD[2], &PS2OriginalValue[i], 1, HAL_MAX_DELAY);

                                                                   0x00        

PS2OriginalValue[3]第一组按键:选择键、开始键、左侧按键

PS2OriginalValue[4]第二组按键:前面按键、右侧按键

PS2OriginalValue[5]右摇杆左右

PS2OriginalValue[6]右摇杆上下

PS2OriginalValue[7]左摇杆左右

PS2OriginalValue[8]左摇杆上下
        Delay_us(10);


}

(1)没有用过但是想要尝试版

https://blog.csdn.net/m0_62524451/article/details/136681842?fromshare=blogdetail&sharetype=blogdetail&sharerId=136681842&sharerefer=PC&sharesource=2301_81764359&sharefrom=from_link

 工程链接可以参考这篇博文

#ifndef PS2_PS2_H_
#define PS2_PS2_H_
 
#include "main.h"
#include "delay.h"
/*
硬件spi
*/
 
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;
 
void PS2_read_data(void);
 
#endif

         这里的CS使用的是PA4而且在CubeMx里面配置的是Output模式,所以这里写的就是HAL_GPIO_WritePin(GPIOA,GPIO_PIN_4,GPIO_PIN_RESET);

  • 发送接收函数

    • HAL_SPI_TransmitReceive(SPI_HandleTypeDef *hspi, uint8_t *pTxData, uint8_t *pRxData, uint16_t Size, uint32_t Timeout);

        采用HAL_SPI_TransmitReceive(&hspi1, &CMD[1], &PS2OriginalValue[1], 1, HAL_MAX_DELAY);函数,可以同时进行收发,其中第一个参数是spi句柄,第二个参数是要发送信息的地址,第三个参数是接收到的信息存储的地址,第四个参数是发送和接收的数据大小(注意:只有一个数据大小的参数说明在调用该函数时只能同时接受和发送相同大小的数据),最后一个参数时超时时长,这里设置为无限等待即可

       PS2_TypeDef PS2_Data = {0};  //存储手柄返回数据//PS2_TypeDef这个是使用的结构体

#include "ps2.h"
#include "spi.h"
 
PS2_TypeDef PS2_Data = {0};
uint8_t cmd[3] = {0x01,0x42,0x00};                                     // 请求接受数据
uint8_t PS2data[9] = {0};   //存储手柄返回数据
 
void PS2_Get(void)//硬件SPI通信函数,这里要求通讯速率必须在250KHZ,函数中的延时可微调不可去
{
	uint8_t i = 0;
	
	HAL_GPIO_WritePin(GPIOA,GPIO_PIN_4,GPIO_PIN_RESET);                 //拉低,开始通讯
		
	HAL_SPI_TransmitReceive(&hspi1,&cmd[0],&PS2data[0],1,100);          // 发送0x01,请求接受数据
	delay_us(5);
	HAL_SPI_TransmitReceive(&hspi1,&cmd[1],&PS2data[1],1,100);          // 发送0x42,接受0x01(PS2表示开始通信)
	delay_us(5);
	HAL_SPI_TransmitReceive(&hspi1,&cmd[2],&PS2data[2],1,100);          // 发送0x00,接受ID(红绿灯模式)
	delay_us(5);
	for(i = 3;i <9;i++)
	{
		HAL_SPI_TransmitReceive(&hspi1,&cmd[2],&PS2data[i],1,0xffff);     // 接受数据
		delay_us(1);
		
	}
	HAL_GPIO_WritePin(GPIOA,GPIO_PIN_4,GPIO_PIN_SET);                    //拉低,准备下次通讯
}
 
 
void PS2_Decode11(void)//接收数据处理函数
{
    if (PS2data[2] == 0x5A)
    {
        PS2_Data.Key_Select = (~PS2data[3] >> 0) & 0x01;                //选择键
        PS2_Data.Key_Start = (~PS2data[3] >> 3) & 0x01;                 //开始键
        //左侧按键
        PS2_Data.Key_L_Up = (~PS2data[3] >> 4) & 0x01;
        PS2_Data.Key_L_Right = (~PS2data[3] >> 5) & 0x01;
        PS2_Data.Key_L_Down = (~PS2data[3] >> 6) & 0x01;
        PS2_Data.Key_L_Left = (~PS2data[3] >> 7) & 0x01;
        //后侧按键
        PS2_Data.Key_L2 = (~PS2data[4] >> 0) & 0x01;
        PS2_Data.Key_R2 = (~PS2data[4] >> 1) & 0x01;
        PS2_Data.Key_L1 = (~PS2data[4] >> 2) & 0x01;
        PS2_Data.Key_R1 = (~PS2data[4] >> 3) & 0x01;
        //右侧按键
        PS2_Data.Key_R_Up = (~PS2data[4] >> 4) & 0x01;
        PS2_Data.Key_R_Right = (~PS2data[4] >> 5) & 0x01;
        PS2_Data.Key_R_Down = (~PS2data[4] >> 6) & 0x01;
        PS2_Data.Key_R_Left = (~PS2data[4] >> 7) & 0x01;
        if (PS2data[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 (PS2data[1] == 0x73)//红灯模式(摇杆值模拟)
        { 
				//摇杆按键
				PS2_Data.Key_Rocker_Left = (~PS2data[3] >> 1) & 0x01;
				PS2_Data.Key_Rocker_Right = (~PS2data[3] >> 2) & 0x01;
				//摇杆值
				PS2_Data.Rocker_LX = PS2data[7] - 0x80;
				PS2_Data.Rocker_LY = -1 - (PS2data[8] - 0x80);
				PS2_Data.Rocker_RX = PS2data[5] - 0x80;
				PS2_Data.Rocker_RY = -1 - (PS2data[6] - 0x80);
        }
    }
}
 
void PS2_read_data(void)//在需要的地方调用该函数,不可调用速率太快,up这里在主循环中调用给予10ms延时效果较好
{
	PS2_Get();//这个是PS2通信的底层
	PS2_Decode11();//这个是PS2传输过来的数据
}
	
#ifndef DELAY_DELAY_H_
#define DELAY_DELAY_H_
 
#include "stm32f1xx_hal.h" //HAL库文件声明
void delay_us(uint32_t us); //C文件中的函数声明
 
#endif /* DELAY_DELAY_H_ */
 
#include "delay.h"
 
void delay_us(uint32_t us) //利用CPU循环实现的非精准应用的微秒延时函数
{
    uint32_t delay = (HAL_RCC_GetHCLKFreq() / 8000000 * us); //使用HAL_RCC_GetHCLKFreq()函数获取主频值,经算法得到1微秒的循环次数
    while (delay--); //循环delay次,达到1微秒延时
}
 

         这个main.c文件里面就是包含了一下ps2.h和delay.h的头文件,然后在int外面写了一下PS2_read_data();这个函数里面需要的两个函数void PS2_Get(void);void PS2_Decode11(void);,最后在主函数里面调用了PS2_read_data();这个函数,并且延时了1ms(我写的主函数里面也有一个延时函数,因为手柄上电之后会发送一串乱码,好像是上电就会发送,然后才是正常的数据,然后再配对,再切换模式,这个可以后续再开个串口看一下传输过来的数据哪些时候是乱码,哪些时候是配对的数据,哪些时候是切换模式的数据)

        注意!!!!!!!!!!!!!!
读取函数不要太快运行,在我这里我给了1ms的延时,太快了会导致读取数据乱码(你没按按键,但是在程序里你会发现你按下了按键),还有就是按键状态都在结构体PS2_Data中,直接引用此结构体你所需的变量即可。

/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : main.c
  * @brief          : Main program body
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2023 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 "spi.h"
#include "gpio.h"
 
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "delay.h"
#include "ps2.h"
/* USER CODE END Includes */
 
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
void PS2_Get(void);
void PS2_Decode11(void);
/* 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 */
 
/* 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 */
  uint8_t ps = 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_SPI1_Init();
  /* USER CODE BEGIN 2 */
 
  /* USER CODE END 2 */
 
  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
	 PS2_read_data();
	 HAL_Delay(1);
    /* 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_MUL8;
  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 */
 
/* 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 */

         参考一下https://blog.csdn.net/weixin_43002939/article/details/130319973?fromshare=blogdetail&sharetype=blogdetail&sharerId=130319973&sharerefer=PC&sharesource=2301_81764359&sharefrom=from_link

这篇文章,跟上面的PS2.c和,h的底层很像,看看在主函数里面这样调用结构体可不可以(应该是可以的)

/* USER CODE BEGIN Header */
/**
 ******************************************************************************
 * @file           : main.c
 * @brief          : Main program body
 ******************************************************************************
 * @attention
 *
 * Copyright (c) 2023 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 "i2c.h"
#include "rtc.h"
#include "tim.h"
#include "usart.h"
#include "gpio.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "../../icode/motor/motor.h"
#include "../inc/retarget.h"//用于printf函数的串口重映射
#include "../../icode/oled/oled.h"
#include "../../icode/ps2/ps2.h"
/* 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 */

/* 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_RTC_Init();
  MX_TIM1_Init();
  MX_USART1_UART_Init();
  MX_I2C1_Init();
  /* USER CODE BEGIN 2 */
	HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_4);
	HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1);
	RetargetInit(&huart1);//将printf函数映射到uart1串口上
	OLED_Init();                           //OLED初始
	OLED_Clear();                         //清屏

  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
	while (1) {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */

		PS2_Read_Data();

		if(PS2_Data.Key_L_Up){
			MotorControl(0,600,600);
		}
		else if(PS2_Data.Key_L_Down){
			MotorControl(1,600,600);
		}

		else if(PS2_Data.Key_L_Left){
			MotorControl(0,300,0);
		}
		else if(PS2_Data.Key_L_Right){
			MotorControl(0,0,300);
		}
		else {
			MotorControl(2, 0, 0);
		}


		HAL_Delay(2);





		printf("LX:%d LY:%d RX:%d RY:%d\r\n", PS2_Data.Rocker_LX, PS2_Data.Rocker_LY, PS2_Data.Rocker_RX, PS2_Data.Rocker_RY);
		HAL_Delay(10);

//		printf("hellow 510\r");
//		OLED_ShowNum(0, 1, 3, 1, 16, 1);
//		OLED_ShowString(0, 5, "hellow world", 16, 1);



	}
  /* USER CODE END 3 */
}

/**
  * @brief System Clock Configuration
  * @retval None
  */
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
  RCC_PeriphCLKInitTypeDef PeriphClkInit = {0};

  /** Initializes the RCC Oscillators according to the specified parameters
  * in the RCC_OscInitTypeDef structure.
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_LSI|RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.LSIState = RCC_LSI_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();
  }
  PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_RTC;
  PeriphClkInit.RTCClockSelection = RCC_RTCCLKSOURCE_LSI;
  if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK)
  {
    Error_Handler();
  }
}

/* USER CODE BEGIN 4 */

/* 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 */

(2)成功过的PS2底层移植代码

        参考文章https://blog.csdn.net/Rick0725/article/details/136604217?fromshare=blogdetail&sharetype=blogdetail&sharerId=136604217&sharerefer=PC&sharesource=2301_81764359&sharefrom=from_link

        但是这个文章的底层我是真的看不懂,也看不懂PS2收发数据底层的那个顺序 

(1)CubeMx的配置

                 由于选择的是软件片选,所以我们需要配置一个GPIO用作片选信号,我们直接点击PA4引脚,将其设置为GPIO_Output

         接着在system core中点击GPIO,为其设置CSS的用户标签

(2)keil的代码

PS2.c

#include "PS2.h"
#include "main.h"

uint8_t CMD[3] = { 0x01,0x42,0x00 };  // 请求接受数据
uint8_t PS2OriginalValue[9] = { 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 };   //存储手柄返回数据
uint8_t RockerValue[4] = { 0x00,0x00,0x00,0x00 };  //摇杆模拟值
uint8_t ButtonValue[16] = { 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 };  //所有按键状态值
uint8_t i,mode;

void PS2OriginalValueGet(void)
{
	short i = 0;
 
	HAL_GPIO_WritePin(CSS_GPIO_Port, CSS_Pin, GPIO_PIN_RESET);//拉低片选CSS,代表选中设备可以开始通信
	
 //HAL_SPI_TransmitReceive(SPI_HandleTypeDef *hspi, uint8_t *pTxData, uint8_t *pRxData, uint16_t Size, uint32_t Timeout);
	
	HAL_SPI_TransmitReceive(&hspi1, &CMD[0], &PS2OriginalValue[0], 1, HAL_MAX_DELAY);//根据数据手册,首先需要向接收器发送0x01请求接收数据
	Delay_us(10);
	HAL_SPI_TransmitReceive(&hspi1, &CMD[1], &PS2OriginalValue[1], 1, HAL_MAX_DELAY);//接下来需要向接收器发送0x42,请求开始通信,接收到0x01表示通信正式开始
	Delay_us(10);
	HAL_SPI_TransmitReceive(&hspi1, &CMD[2], &PS2OriginalValue[2], 1, HAL_MAX_DELAY);//然后发送0x00,可以接收到PS2ID,此ID可以代表PS2手柄目前的模式,如果为0x73,则为红灯模式,如果为0x41,则为绿灯模式,此状态下摇杆返回模拟值
	Delay_us(10);
	for (i = 3; i < 9; i++)//接下来持续发送0x00,可以接收到按键信息以及摇杆信息
	{
		HAL_SPI_TransmitReceive(&hspi1, &CMD[2], &PS2OriginalValue[i], 1, HAL_MAX_DELAY);
		Delay_us(10);
	}
 
	HAL_GPIO_WritePin(CSS_GPIO_Port, CSS_Pin, GPIO_PIN_SET);//完成后将片选拉高
 
}

void ButtonValueGet(void)
{
	uint8_t bit = 1;
	uint8_t button = 0;
	for (bit = 8; bit > 0; bit--)
	{
		bit -= 1;
		ButtonValue[button] = (PS2OriginalValue[3] & (1 << bit)) >> bit;
		bit += 1;
		button++;
	}
	for (bit = 8; bit > 0; bit--)
	{
		bit -= 1;
		ButtonValue[button] = (PS2OriginalValue[4] & (1 << bit)) >> bit;
		bit += 1;
		button++;
	}
	for (button = 0; button < 16; button++)
	{
		if (ButtonValue[button] == 1)  ButtonValue[button] = 0;
		else  ButtonValue[button] = 1;
	}
}

void RockerValueGet(void)
{
	int i;
	 
	for (i = 5; i < 9; i++)
	{
		PS2OriginalValue[i] = (int)PS2OriginalValue[i];
		RockerValue[i - 5] = PS2OriginalValue[i];
    
	}
}

int PS2RedLight(void)
{
	if (mode == 0X73)
		return 1;
	else
		return 0;
}

void PS2OriginalValueClear(void)
{
	for (i = 0; i < 9; i++)
	{
		 PS2OriginalValue[i] = 0x00; 
	}
}

void PS2AllValueUpdate(void)
{
	PS2OriginalValueGet(); 
	RockerValueGet();
	ButtonValueGet();
	mode = PS2OriginalValue[1];
	PS2OriginalValueClear();
}

 PS2.h的代码

#ifndef __PS2_H
#define __PS2_H

#include "delay.h"
#include "spi.h"

#define PSS_Rx 0 
#define PSS_Ry 1
#define PSS_Lx 2
#define PSS_Ly 3
 
#define PSB_Left        0
#define PSB_Down        1
#define PSB_Right       2
#define PSB_Up          Left_Front
#define PSB_Start       4
#define PSB_RightRocker	5
#define PSB_LeftRocker  6
#define PSB_Select      7
#define PSB_Square      8
#define PSB_Cross       9
#define PSB_Circle      10
#define PSB_Triangle    11
#define PSB_X           8
#define PSB_A			9
#define PSB_B			10
#define PSB_Y			11
#define PSB_R1          12
#define PSB_L1          13
#define PSB_R2          14
#define PSB_L2          15

void PS2OriginalValueGet(void);
void ButtonValueGet(void);
void RockerValueGet(void);
int PS2RedLight(void);
void PS2OriginalValueClear(void);
void PS2AllValueUpdate(void);

#endif

main.c函数里的代码

 

 (3)调试结果

        编译下载并打开串口调试助手,按动手柄上的按键,摇动摇杆,出现如下结果说明解码成功,其中1表示被按下的按键,后面四个则是摇杆模拟值 

(3)大概总结

        以上两个代码的PS2初始化是一样的,只是在读取按键的值和摇杆的值的代码书写会不一样一些,虽然我还是看不懂到底是怎么来计算的传输的代码,但是不管是ButtonValue[button]还是 PS2_Data.Key_Select这种,都相当于是名字(PS2按键摇杆传过来的数据取的一个名字)

        现在的PS2的读取都是在主函数里面,我不知道开中断能不能读取,然后就是ButtonValue[0]像这种要在main函数上面声明一下这个变量。

        这个函数的逻辑就是,我先在主函数里面调用一下读取按键的函数,然后按键传过来的数据就放在ButtonValue[button]这个里面的,所以我可以使用这个名字去做一些相应的功能。所以我猜测,我在主函数里面使用PS2_Data.Key_Select这种结构体也能实现相应的功能,可以尝试一下

四、工程代码

五、参考文章

https://blog.csdn.net/Rick0725/article/details/136604217?fromshare=blogdetail&sharetype=blogdetail&sharerId=136604217&sharerefer=PC&sharesource=hxnwhsh&sharefrom=from_link

https://blog.csdn.net/qq_61568285/article/details/134831115?fromshare=blogdetail&sharetype=blogdetail&sharerId=134831115&sharerefer=PC&sharesource=2301_81764359&sharefrom=from_link

http://【STM32 CUBEMX HAL PS2数据读取并解析 硬件SPI - CSDN App】https://blog.csdn.net/m0_62524451/article/details/136681842?sharetype=blogdetail&shareId=136681842&sharerefer=APP&sharesource=2401_88973651&sharefrom=link

http://【STM32 HAL库 PS2手柄控制电机转动 - CSDN App】https://blog.csdn.net/weixin_43002939/article/details/130319973?sharetype=blogdetail&shareId=130319973&sharerefer=APP&sharesource=2401_88973651&sharefrom=link

http://【基于STM32HAL库的PS2遥控手柄 - CSDN App】https://blog.csdn.net/2201_75475731/article/details/132760068?sharetype=blogdetail&shareId=132760068&sharerefer=APP&sharesource=2401_88973651&sharefrom=link

下面这一篇文章一点儿也看不懂

https://blog.csdn.net/yuaner_cxy/article/details/96650089?fromshare=blogdetail&sharetype=blogdetail&sharerId=96650089&sharerefer=PC&sharesource=2301_81764359&sharefrom=from_link

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值