【STM32】SPI与PS2手柄解码(CUBEMX+HAL库)

本文工程文件以及ps2数据手册在这个链接,我设置成免费了

【免费】STM32PS2解码工程以及代码(CUBEMX+HAL库)资源-CSDN文库

目录

 

SPI简介

SPI引脚说明

一些参数的含义

通信的四种模式

通信过程简介

关于SPI的常用HAL库函数

PS2简介

ps2手柄

ps2接收器

PS2解码

CUBEMX工程配置

PS2解码

必要的前期工作

原始数据获取

原始数据解码

按键状态获取

摇杆模拟量获取

PS2状态获取

PS2数据清除函数

PS2更新函数

PS2解码实验

工程创建

代码编写

实物连接

调试结果


 

SPI简介

关于SPI在这里就不过多阐述,具体的通信原理可以参考其他博主的博文,这里只提及几个要使用的地方

SPI是串行外设接口(Serial Peripheral Interface)的缩写,是一种高速、全双工、同步通信总线,SPI没有定义速度限制,通常能达到甚至超过10M/bps。

SPI有主、从两种模式,通常由一个主模块和一个或多个从模块组成(SPI不支持多主机),主模块选择一个从模块进行同步通信,从而完成数据的交换。提供时钟的为主设备(Master),接收时钟的设备为从设备(Slave),SPI接口的读写操作,都是由主设备发起,当存在多个从设备时,通过各自的片选信号进行管理。

SPI引脚说明

  • SCLK(Serial Clock):用于提供串行时钟脉冲信号,也通常被写作SCK

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

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

  • SS(Slave Select):片选,也写成NSS;通常多个有几个从机就有几个NSS,NSS1,NSS2……

一些参数的含义

  • LSB/MSB        LSB: 在通信时先发送最低有效位 MSB: 在通信时先发送最高有效位

  • CKP/CPOL(Clock Polarity)        时钟极性,CKP=0时,时钟空闲时为低电平;CKP=1时,时钟空闲时为高电平

  • CKE/CPHA(Clock Phase(Edge))        时钟相位,CKE=0时,在时钟信号第一个跳变沿采样;CKE=1时,在时钟信号第二个跳变沿采样

通信的四种模式

根据设置的时钟极性和时钟相位,我们可以控制SPI在时钟脉冲的不同位置采样,四种模式如图所示

dfb89a9c3ce845f580800675afeaf5a3.png

 

通信过程简介

主机将片选信号拉低,即NSS拉低,通信开始 CKP和CKE配置为00或者11时,主机和从机在时钟高电平采集信号;配置为01或者10时,在时钟低电平采集信号。 在互补时钟信号跳变沿发送信号 主机将片选信号拉高,即NSS拉高,一次通信结束

值得注意的是:SPI没有固定的从机地址,需要哪个从机时就将对应从机的NSS拉低开始通信,未拉低的从机选择性失聪

关于SPI的常用HAL库函数

  • 轮询模式

    • 发送函数

      • HAL_SPI_Transmit(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size, uint32_t Timeout);

    • 接收函数

      • HAL_SPI_Receive(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size, uint32_t Timeout);

    • 发送接收函数

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

  • 中断模式

    • 中断发送函数

      • HAL_SPI_Transmit_IT(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size, uint32_t Timeout);

    • 中断接收函数

      • HAL_SPI_Receive_IT(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size, uint32_t Timeout);

    • 中断发送接收函数

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

  • DMA模式

    • DMA发送函数

      • HAL_SPI_Transmit_DMA(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size, uint32_t Timeout);

    • DMA接收函数

      • HAL_SPI_Receive_DMA(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size, uint32_t Timeout);

    • DMA发送接收函数

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

  • 中断回调函数

    • 发送完成回调函数

      • HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi);

    • 接收完成回调函数

      • HAL_SPI_RxCpltCallback(SPI_HandleTypeDef *hspi);

    • 发送接收完成回调函数

      • HAL_SPI_TxRxCpltCallback(SPI_HandleTypeDef *hspi);

    • 发送过半回调函数

      • HAL_SPI_TxHalfCpltCallback(SPI_HandleTypeDef *hspi);

    • 接收过半回调函数

      • HAL_SPI_RxHalfCpltCallback(SPI_HandleTypeDef *hspi);

    • 发送接收过半回调函数

      • HAL_SPI_TxRxHalfCpltCallback(SPI_HandleTypeDef *hspi);

待会会使用其中一些函数构建PS2驱动文件

PS2简介

ps2手柄

PS2手柄由总共十六个按键和两个摇杆组成

17a6cd8dd15c400ea05705ed070fef15.png

e6194efbaa9e42afabfc547151653da3.png

(@图片来自WEEEMAKE WEKI)

不同模式下的ps2按键

当模式指示灯熄灭时,摇杆不返回模拟值,而是触发对应侧的按键,如左摇杆上下左右依次触发十字按键的上下左右,右摇杆的上下左右分别触发YAXB

当模式指示灯亮起时,摇杆返回模拟值

两种模式下其它按键的值都能被正常读取,且按mode可以切换按键

ps2接收器

3b3a4ef498244ffe80102d27bacf355c.jpeg

我们不难看到,手柄通过SPI协议向单片机传输接收到的按键数据

注意,同时打开手柄和给接收器上电,他们会自动配对,且当二者指示灯均不闪烁,为常亮时,表示配对成功

PS2解码

CUBEMX工程配置

关于一些操作前的基础配置我们在这篇博客中有提到,这里就不再叙述了

【STM32】CUBEMX之串口:串口三种模式(轮询模式、中断模式、DMA模式)的配置与使用示例 + 串口重定向 + 使用HAL扩展函数实现不定长数据接收-CSDN博客

先按照这个博客配置好一个串口,不需要打开中断和DMA

配置完成后在connectivity下找到SPI1,这里我们选择Full-Duplex Master,First Bit 选择 LSB,预分频Prescaler选择256,CPOL选择High,CPHA选择1 Edge,片选信号NSS选择软件方式

d85a8cd766fc4338a102e5110b5aff58.png

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

d592227822ae497b8ef498adf4c3e3b3.png

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

6d51b8fa92ff49088d4bd4b793c13c4e.png

然后点击Generate Code生成代码,到此CubeMX的配置就结束了

PS2解码

必要的前期工作

根据数据手册,我们首先需要定义一系列按键,将其与返回的数据进行关联

#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          3
#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

查阅手册得知,我们发送的命令共有3条,一次通信总共返回的原始数据共9字节,两个摇杆共四个方向,所以创建一个大小为4的数组用于储存模拟值,按钮共16个,所以再创建一个大小为16的数组储存每个按键的状态,此外,还需创建变量i用于循环语句,mode用在全局表示PS2的实时状态,这段代码如下

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;

由于通信速度十分快,所以需要使用HAL库定义一个微秒级的延时,用于通信过程中的必要的等待

延时函数如下

void Delay_us(uint32_t udelay)    //定义hal库us级延迟
{
	uint32_t startval, tickn, delays, wait;

	startval = SysTick->VAL;
	tickn = HAL_GetTick();
	delays = udelay * 72;
	if (delays > startval)
	{
		while (HAL_GetTick() == tickn)
		{

		}
		wait = 72000 + startval - delays;
		while (wait < SysTick->VAL)
		{

		}
	}
	else
	{
		wait = startval - delays;
		while (wait < SysTick->VAL && HAL_GetTick() == tickn)
		{

		}
	}
}

原始数据获取

接下来就是启动SPI通信从接受其中获得9字节的原始数据

具体流程如下:

  • 拉低片选CSS,代表选中设备可以开始通信
  • 根据数据手册,首先需要向接收器发送0x01请求接收数据
  • 接下来需要向接收器发送0x42,请求开始通信,接收到0x01表示通信正式开始
  • 然后发送0x00,可以接收到PS2ID,此ID可以代表PS2手柄目前的模式,如果为0x73,则为红灯模式,如果为0x41,则为绿灯模式,此状态下摇杆返回模拟值
  • 接下来持续发送0x00,可以接收到按键信息以及摇杆信息
  • 完成后将片选拉高

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

原始数据接收函数如下

void PS2OriginalValueGet(void)
{
	short i = 0;

	HAL_GPIO_WritePin(CSS_GPIO_Port, CSS_Pin, GPIO_PIN_RESET);

	HAL_SPI_TransmitReceive(&hspi1, &CMD[0], &PS2OriginalValue[0], 1, HAL_MAX_DELAY);
	Delay_us(10);
	HAL_SPI_TransmitReceive(&hspi1, &CMD[1], &PS2OriginalValue[1], 1, HAL_MAX_DELAY);
	Delay_us(10);
	HAL_SPI_TransmitReceive(&hspi1, &CMD[2], &PS2OriginalValue[2], 1, HAL_MAX_DELAY);
	Delay_us(10);
	for (i = 3; i < 9; i++)
	{
		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);

}

原始数据解码

按键状态获取

根据数据手册,最后在while中接收到的前两字节共16位数据对应16按键的状态,后四字节数据分别是四个摇杆模拟值的八位模拟量

所以我们需要对PS2OriginalValue[3]与PS2OriginalValue[4]进行逐位判断并存储进对应的ButtonValue中

注意:由于对应的按键按下时,该位为0,为方便判断,需要将所有按键值取反

该函数具体编写步骤可简化如下

  • 使用循环逐字节读取PS2OriginalValue[3]中的状态
  • 使用循环逐字节读取PS2OriginalValue[4]中的状态
  • 对所有按键状态取反

该函数具体代码编写可以是如下

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;
	}
}

摇杆模拟量获取

摇杆模拟量直接将PS2OriginalValue后四位注意对应着写入存储数组即可

该函数代码编写如下

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

PS2状态获取

该函数也在只需要对之前定义的mode进行判断即可

该函数代码编写如下,这里采取判断是否为红灯模式,若是红灯,返回1,不是则返回0

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

PS2数据清除函数

在完成解码后,需要将所有的原始数据进行清零,保证下一次获取原始数据的准确性

直接对PS2OriginalValue清零即可,参考代码如下

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

PS2更新函数

最后为完成解码,需要对以上函数进行合理调用,并且该函数使得PS2数据更新的调用简洁

逻辑可以为

  • 获取原始数据
  • 更新摇杆模拟量
  • 更新按键状态
  • 更新模式
  • 清除原始数据

代码如下

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

至此,就完成了PS2的通信以及解码过程

PS2解码实验

工程创建

接在cubemx中generate code之后,我们打开创建的工程,将ps2.c和ps2.h分别粘贴在工程文件中Core-Src和Core-Inc文件夹下,.c和.h文件都在开头处给出的资源里

a2913d7b2a084d138dce73500bd11f0d.png

e97ce3c87d8a4c92ba3bdfbaeb77e65c.png

19b92fa7e4ee438a975e0b045b64e2cd.png

然后打开keil,点击品字图形的图标,找到后选定Core文件夹,在右下方点击Add Files添加文件

6b5c4742ae0a4319afaea7dadfbd3054.png

找到刚刚存放ps2.c的文件夹,选中ps2.c,将其添加进工程中

56db8e8a25614666906f007e45448c2f.png

完成后点击ok保存,这时我们可以看到在旁边的工程文件夹中看到了ps2.c文件,就说明添加完成了

2b2d025e7d8b496aac0c7f18f6f2465f.png

代码编写

在魔术棒中打开microlib

40b2d1e8940e42f89bfd601930ea646b.png

引用相关的头文件以及ps2.c中定义的数组

674f51b4c99e4477a151228b604d65cf.png

编写串口重定向函数,对串口重定向不清楚的可以看看上面的博客链接

e162e808ab1b4610aea4f5034ccc1ca7.png

编写主函数,由于函数比较简单,且下载文件中也有,这里就不单独写出来了

44adcb1a7c4a421cbe455cbc04235c70.png

实物连接

ps2接收器

  • PA4->CS
  • PA5->CLK
  • PA6->DAT
  • PA7->CMD

509571f8cdb34363ad5245c792babce1.jpeg

调试结果

编译下载并打开串口调试助手,按动手柄上的按键,摇动摇杆,出现如下结果说明解码成功

3fec1ced27134f87b375eda0f22f77c6.png

其中1表示被按下的按键,后面四个则是摇杆模拟值

ps2手柄解码到此就结束了,欢迎大家讨论

 

 

 

  • 36
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论
嵌入式系统设计是指将计算机系统集成到其他设备或系统中,以实现特定功能。而基于STM32CubeMXHAL库的嵌入式系统设计是指利用ST公司的STM32系列微控制器,结合STM32CubeMX工具和HAL库(Hardware Abstraction Layer),进行软硬件设计和开发的过程。 STM32CubeMX是ST公司推出的一款嵌入式系统设计工具,它提供了图形化的界面,可以帮助开发者快速配置STM32微控制器的引脚、时钟、外设等参数,生成初始化代码和项目文件,使整个开发过程更加简化和快捷。 HAL库STM32系列微控制器的硬件抽象层库,提供了一组封装了底层硬件操作的API接口,使开发者能够更加方便地进行外设控制和数据处理等操作。开发者可以根据具体的需求,选择需要的API接口,编写相应的代码,完成对硬件的控制和操作。 在使用STM32CubeMXHAL库进行嵌入式系统设计时,可以通过STM32CubeMX工具进行硬件的初始化和配置,生成相应的代码和项目文件。然后,在IDE(集成开发环境)中使用HAL库提供的API接口,编写代码进行软件的开发和应用程序的编写。通过这种方式,开发者可以在短时间内快速完成嵌入式系统设计,提高开发效率和产品质量。 总而言之,基于STM32CubeMXHAL库的嵌入式系统设计提供了一种快速、简化的嵌入式软硬件开发方法,使开发者能够更加高效地进行嵌入式应用的设计与开发。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

翎智骁

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值