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

这篇具有很好参考价值的文章主要介绍了【STM32】SPI与PS2手柄解码(CUBEMX+HAL库)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

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

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

目录

 文章来源地址https://www.toymoban.com/news/detail-854771.html

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在时钟脉冲的不同位置采样,四种模式如图所示

stm32 spi hal ps2手柄,STM32,stm32,嵌入式硬件,单片机

 

通信过程简介

主机将片选信号拉低,即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手柄由总共十六个按键和两个摇杆组成

stm32 spi hal ps2手柄,STM32,stm32,嵌入式硬件,单片机

stm32 spi hal ps2手柄,STM32,stm32,嵌入式硬件,单片机

(@图片来自WEEEMAKE WEKI)

不同模式下的ps2按键

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

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

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

ps2接收器

stm32 spi hal ps2手柄,STM32,stm32,嵌入式硬件,单片机

我们不难看到,手柄通过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选择软件方式

stm32 spi hal ps2手柄,STM32,stm32,嵌入式硬件,单片机

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

stm32 spi hal ps2手柄,STM32,stm32,嵌入式硬件,单片机

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

stm32 spi hal ps2手柄,STM32,stm32,嵌入式硬件,单片机

然后点击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文件都在开头处给出的资源里

stm32 spi hal ps2手柄,STM32,stm32,嵌入式硬件,单片机

stm32 spi hal ps2手柄,STM32,stm32,嵌入式硬件,单片机

stm32 spi hal ps2手柄,STM32,stm32,嵌入式硬件,单片机

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

stm32 spi hal ps2手柄,STM32,stm32,嵌入式硬件,单片机

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

stm32 spi hal ps2手柄,STM32,stm32,嵌入式硬件,单片机

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

stm32 spi hal ps2手柄,STM32,stm32,嵌入式硬件,单片机

代码编写

在魔术棒中打开microlib

stm32 spi hal ps2手柄,STM32,stm32,嵌入式硬件,单片机

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

stm32 spi hal ps2手柄,STM32,stm32,嵌入式硬件,单片机

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

stm32 spi hal ps2手柄,STM32,stm32,嵌入式硬件,单片机

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

stm32 spi hal ps2手柄,STM32,stm32,嵌入式硬件,单片机

实物连接

ps2接收器

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

stm32 spi hal ps2手柄,STM32,stm32,嵌入式硬件,单片机

调试结果

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

stm32 spi hal ps2手柄,STM32,stm32,嵌入式硬件,单片机

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

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

 

 

 

到了这里,关于【STM32】SPI与PS2手柄解码(CUBEMX+HAL库)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • STM32CubeMX配置HAL库实现SPI-DMA的递归调用

    核心: STM32F407ZET6   外设ADC: ADS1258   数量:3个   ※ 核心与3个ADC使用SPI总线 “一主多从” 方式连接,PCB布线的方式与下图一致。 ※ 在电路板上STM32与三个ADS1258在同一直线上分布,STM32在一端,三个ADC依次排布。 ※ 离STM32最远ADC的DRDY硬件管脚与STM32的EXTI line4 interrupt连接

    2024年02月15日
    浏览(41)
  • 7针0.96寸OLED的HAL库代码(硬件SPI/全代码/stm32f1/CubeMX配置/包含有正负浮点数/100%一次点亮)

    HC-SR04超声波模块的使用  编码电机以及双电机驱动 4针 0.96\\\'OLED的使用 更多有意思的文章点击“我的主页” --------😐 更多有意思的视频 ----- B站 @想要亿只独角兽 --------😐 之前发布了一篇硬件I2C的0.96\\\' OLED驱动代码,这次就添加一篇硬件SPI的驱动代码。 其实改动的代码不多,

    2024年02月16日
    浏览(36)
  • 【STM32 CubeMX】SPI层次结构SPI协议与SPI控制器结构

    随着嵌入式系统的迅猛发展,STM32系列微控制器在各种应用中得到广泛应用。在嵌入式系统设计中,串行外设接口(SPI)是一种常见且重要的通信协议。为了更便捷地配置和使用SPI,STMicroelectronics提供了一款强大的工具——STM32 CubeMX。本文将着重介绍STM32 CubeMX中SPI的层次结构

    2024年02月20日
    浏览(31)
  • STM32 HAL库 SPI主从双机通信

    最近因为项目需求,需要在一块板子内实现一个主机和五个从机的通信; 主机平台选用的是STM32F407VGT6,从机平台选用的是STM32F103C8T6;通信总线选用的是SPI总线。在构想是觉得采用SPI进行主从通信会很简单,但在实际开发的过程中,各种坑,通信时而正常时而混乱。不过在不

    2024年01月17日
    浏览(33)
  • STM32的HAL库SPI操作(master 模式)-根据时序图配置SPI

    SPI基本概念请自行百度,参考:百度百科SPI简介.我们讲重点和要注意的地方。 接线一一对应 也就是说主控的MISO,MOSI,SCLK,[CSn]分别和设备的MISO,MOSI,SCLK,[CSn]一一对应相连,不交叉,不交叉,不交叉…(重要的事情说三遍)。 这是无线模块CC2500的SPI接口时序,这里可以看到,从

    2024年02月06日
    浏览(35)
  • STM32 通过HAL库实现双机SPI程序烧录之一SPI双机通信

    主要功能:STM32F407VET6作为主机,STM32F103ZET6作为从机实现F4通过SPI接口发送数据给F1进行串口打印功能 主要是使用STM32cubemx进行相关配置(使用这个软件不能只用于生成代码,要学会读懂生成的每一句代码这样才方便后期调试,不然出现问题根本找不到) 选择Serial Waire进行调试

    2024年02月08日
    浏览(31)
  • STM32初学入门笔记(5):使用STM32CubeMX通过SPI,IIC驱动OLED屏幕

    随着时代的进步,OLED显示屏成为了继LCD显示屏之后的新一代显示屏技术,OLED具有可视角高,功耗低,厚度薄,耐冲击、振动能力强,像素响应时间低等优点,在嵌入式开发中,OLED显示器也是一个主要的部分,制作OLED显示模块的驱动也是学习STM32路上的重要一部分,本篇将从

    2024年02月04日
    浏览(35)
  • [HAL]STM32 SPI+DMA驱动WS2812

    该程序是纯手敲,非Cube生成!所有代码均注释。 源码在文章后面获取 Keyword: 单线通讯、归零码、Reset、RGB顺序 RGB一共有24bit位 -相当于驱动一个灯要24bit位 -驱动若干个灯要24* n bit位,通过Reset码决定数据终止(保持) 24bit位应该如何发送? 可见: 表示低电平需要 T0H和T0L的配

    2024年02月09日
    浏览(42)
  • stm32 hal库硬件spi(软件spi)驱动1.8寸tft—lcd屏幕

    屏幕是嵌入式开发中的一个重要的部分,cdsn上有许多解释原理的,还有很多是采用正点原子的屏幕来驱动的,对于刚刚入门不久的我们可能没有资金去购买较为昂贵的屏幕。而对于底层原理我们暂时也不必了解的那么深入,能点亮屏幕就是我们最大的快乐。 除了中景园的资

    2024年02月03日
    浏览(44)
  • Note10:基于STM32H7+HAL+CubeMX+DMA+SPI+串口中断+定时器+RTC的多传感器数据采集系统(2*ADXL355和ADXL375通过Sync时序同步)

    本文的初衷一方面是将我的一些关于STM32开发方面浅显的个人经验分享给初学者、并期望得到大佬的批评指正,另一方面是记录自己的实验过程便于回顾。 我预感应该要写很多,不过鉴于之前的数篇笔迹中,对于SPI/DMA/ADXL3XX系列加表的使用已经详细描述过了,所以这篇博客只

    2024年02月10日
    浏览(44)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包