ESP32与Xbox手柄的UART通信测试,基于Arduino框架和pyserial+pygame
ESP32与Xbox手柄的UART通信测试
- 1. 说明
- 2. 环境
- 3. 手柄与PC之间的通信测试
- 4. python与ESP32的通信测试
- 5. 手柄与ESP32的通信测试
1. 说明
这个项目的目标是实现使用手柄来控制ESP32。最近正在进行无人机项目,但是由于没有适合的遥控器来控制四轴,画板子也有些占用时间,所以比较有效的方法就是基于手头有的Xbox手柄来进行一个DIY,在手柄与ESP32之间建立串口通信。此处使用PC作为中继,可能速度有些慢,但是基于目前需求,速度已经足够了。下图说明了无人机项目的通信方式,红框部分为本次涉及部分。
2. 环境
这里我使用主要Ubuntu 18作为开发环境,Win10下也能正常运行。python版本为3.9,所需库为pygame
与pyserial
。
3. 手柄与PC之间的通信测试
手柄与PC之间通过Pygame建立通信,以下提供了两个测试程序,第一个测试程序是一个简单的终端输出,如果手柄工作正常,就会看到六轴的输出。
import pygame
import timepygame.init()
pygame.joystick.init()
done=Falsewhile (done != True):for event in pygame.event.get(): # User did somethingif event.type == pygame.QUIT: # If user clicked closedone = True # Flag that we are done so we exit this loopjoystick_count = pygame.joystick.get_count()for i in range(joystick_count):joystick = pygame.joystick.Joystick(i)joystick.init()axes = joystick.get_numaxes()print('================')time.sleep(0.1)for i in range(axes):axis = joystick.get_axis(i)print(axis)
以下测试程序提供了一个简单的GUI来对每个按键进行测试
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# vi:ts=4 sw=4 et
#
# This tool runs fine on both Python 2 and Python 3.
#
# https://github.com/denilsonsa/pygame-joystick-testfrom __future__ import division
from __future__ import print_functionimport sys
import pygame
from pygame.locals import *class joystick_handler(object):def __init__(self, id):self.id = idself.joy = pygame.joystick.Joystick(id)self.name = self.joy.get_name()self.joy.init()self.numaxes = self.joy.get_numaxes()self.numballs = self.joy.get_numballs()self.numbuttons = self.joy.get_numbuttons()self.numhats = self.joy.get_numhats()self.axis = []for i in range(self.numaxes):self.axis.append(self.joy.get_axis(i))self.ball = []for i in range(self.numballs):self.ball.append(self.joy.get_ball(i))self.button = []for i in range(self.numbuttons):self.button.append(self.joy.get_button(i))self.hat = []for i in range(self.numhats):self.hat.append(self.joy.get_hat(i))class input_test(object):class program:"Program metadata"name = "Pygame Joystick Test"version = "0.2"author = "Denilson Figueiredo de Sá Maia"nameversion = name + " " + versionclass default:"Program constants"fontnames = [# Bold, Italic, Font name(0, 0, "Bitstream Vera Sans Mono"),(0, 0, "DejaVu Sans Mono"),(0, 0, "Inconsolata"),(0, 0, "LucidaTypewriter"),(0, 0, "Lucida Typewriter"),(0, 0, "Terminus"),(0, 0, "Luxi Mono"),(1, 0, "Monospace"),(1, 0, "Courier New"),(1, 0, "Courier"),]# TODO: Add a command-line parameter to change the size.# TODO: Maybe make this program flexible, let the window height define# the actual font/circle size.fontsize = 20circleheight = 10resolution = (640, 480)def load_the_fucking_font(self):# The only reason for this function is that pygame can find a font# but gets an IOError when trying to load it... So I must manually# workaround that.# self.font = pygame.font.SysFont(self.default.fontnames, self.default.fontsize)for bold, italic, f in self.default.fontnames:try:filename = pygame.font.match_font(f, bold, italic)if filename:self.font = pygame.font.Font(filename, self.default.fontsize)# print("Successfully loaded font: %s (%s)" % (f, filename))breakexcept IOError as e:# print("Could not load font: %s (%s)" % (f, filename))passelse:self.font = pygame.font.Font(None, self.default.fontsize)# print("Loaded the default fallback font: %s" % pygame.font.get_default_font())def pre_render_circle_image(self):size = self.default.circleheightself.circle = pygame.surface.Surface((size,size))self.circle.fill(Color("magenta"))basecolor = ( 63, 63, 63, 255) # RGBAlightcolor = (255, 255, 255, 255)for i in range(size // 2, -1, -1):color = (lightcolor[0] + i * (basecolor[0] - lightcolor[0]) // (size // 2),lightcolor[1] + i * (basecolor[1] - lightcolor[1]) // (size // 2),lightcolor[2] + i * (basecolor[2] - lightcolor[2]) // (size // 2),255)pygame.draw.circle(self.circle,color,(int(size // 4 + i // 2) + 1, int(size // 4 + i // 2) + 1),i,0)self.circle.set_colorkey(Color("magenta"), RLEACCEL)def init(self):pygame.init()pygame.event.set_blocked((MOUSEMOTION, MOUSEBUTTONUP, MOUSEBUTTONDOWN))# I'm assuming Font module has been loaded correctlyself.load_the_fucking_font()# self.fontheight = self.font.get_height()self.fontheight = self.font.get_linesize()self.background = Color("black")self.statictext = Color("#FFFFA0")self.dynamictext = Color("white")self.antialias = 1self.pre_render_circle_image()# self.clock = pygame.time.Clock()self.joycount = pygame.joystick.get_count()if self.joycount == 0:print("This program only works with at least one joystick plugged in. No joysticks were detected.")self.quit(1)self.joy = []for i in range(self.joycount):self.joy.append(joystick_handler(i))# Find out the best window sizerec_height = max(5 + joy.numaxes + joy.numballs + joy.numhats + (joy.numbuttons + 9) // 10for joy in self.joy) * self.fontheightrec_width = max([self.font.size("W" * 13)[0]] +[self.font.size(joy.name)[0] for joy in self.joy]) * self.joycountself.resolution = (rec_width, rec_height)def run(self):self.screen = pygame.display.set_mode(self.resolution, RESIZABLE)pygame.display.set_caption(self.program.nameversion)self.circle.convert()while True:for i in range(self.joycount):self.draw_joy(i)pygame.display.flip()# self.clock.tick(30)for event in [pygame.event.wait(), ] + pygame.event.get():# QUIT none# ACTIVEEVENT gain, state# KEYDOWN unicode, key, mod# KEYUP key, mod# MOUSEMOTION pos, rel, buttons# MOUSEBUTTONUP pos, button# MOUSEBUTTONDOWN pos, button# JOYAXISMOTION joy, axis, value# JOYBALLMOTION joy, ball, rel# JOYHATMOTION joy, hat, value# JOYBUTTONUP joy, button# JOYBUTTONDOWN joy, button# VIDEORESIZE size, w, h# VIDEOEXPOSE none# USEREVENT codeif event.type == QUIT:self.quit()elif event.type == KEYDOWN and event.key in [K_ESCAPE, K_q]:self.quit()elif event.type == VIDEORESIZE:self.screen = pygame.display.set_mode(event.size, RESIZABLE)elif event.type == JOYAXISMOTION:self.joy[event.joy].axis[event.axis] = event.valueelif event.type == JOYBALLMOTION:self.joy[event.joy].ball[event.ball] = event.relelif event.type == JOYHATMOTION:self.joy[event.joy].hat[event.hat] = event.valueelif event.type == JOYBUTTONUP:self.joy[event.joy].button[event.button] = 0elif event.type == JOYBUTTONDOWN:self.joy[event.joy].button[event.button] = 1def rendertextline(self, text, pos, color, linenumber=0):self.screen.blit(self.font.render(text, self.antialias, color, self.background),(pos[0], pos[1] + linenumber * self.fontheight)# I can access top-left coordinates of a Rect by indexes 0 and 1)def draw_slider(self, value, pos):width = pos[2]height = self.default.circleheightleft = pos[0]top = pos[1] + (pos[3] - height) // 2self.screen.fill((127, 127, 127, 255),(left + height // 2, top + height // 2 - 2, width - height, 2))self.screen.fill((191, 191, 191, 255),(left + height // 2, top + height // 2, width - height, 2))self.screen.fill((127, 127, 127, 255),(left + height // 2, top + height // 2 - 2, 1, 2))self.screen.fill((191, 191, 191, 255),(left + height // 2 + width - height - 1, top + height // 2 - 2, 1, 2))self.screen.blit(self.circle,(left + (width - height) * (value + 1) // 2, top))def draw_hat(self, value, pos):xvalue = value[0] + 1yvalue = -value[1] + 1width = min(pos[2], pos[3])height = min(pos[2], pos[3])left = pos[0] + (pos[2] - width ) // 2top = pos[1] + (pos[3] - height) // 2self.screen.fill((127, 127, 127, 255), (left, top , width, 1))self.screen.fill((127, 127, 127, 255), (left, top + height // 2, width, 1))self.screen.fill((127, 127, 127, 255), (left, top + height - 1, width, 1))self.screen.fill((127, 127, 127, 255), (left , top, 1, height))self.screen.fill((127, 127, 127, 255), (left + width // 2, top, 1, height))self.screen.fill((127, 127, 127, 255), (left + width - 1, top, 1, height))offx = xvalue * (width - self.circle.get_width() ) // 2offy = yvalue * (height - self.circle.get_height()) // 2# self.screen.fill((255,255,255,255),(left + offx, top + offy) + self.circle.get_size())self.screen.blit(self.circle, (left + offx, top + offy))def draw_joy(self, joyid):joy = self.joy[joyid]width = self.screen.get_width() // self.joycountheight = self.screen.get_height()pos = Rect(width * joyid, 0, width, height)self.screen.fill(self.background, pos)# This is the number of lines required for printing info about this joystick.# self.numlines = 5 + joy.numaxes + joy.numballs + joy.numhats + (joy.numbuttons+9)//10# Joy name# 0 Axes:# -0.123456789# 0 Trackballs:# -0.123,-0.123# 0 Hats:# -1,-1# 00 Buttons:# 0123456789# Note: the first character is the color of the text.text_colors = {"D": self.dynamictext,"S": self.statictext,}output_strings = ["S%s" % joy.name,"S%d axes:" % joy.numaxes]+[ "D %d=% .3f" % (i, v) for i, v in enumerate(joy.axis) ]+["S%d trackballs:" % joy.numballs]+[ "D%d=% .2f,% .2f" % (i, v[0], v[1]) for i, v in enumerate(joy.ball) ]+["S%d hats:" % joy.numhats]+[ "D %d=% d,% d" % (i, v[0], v[1]) for i, v in enumerate(joy.hat ) ]+["S%d buttons:" % joy.numbuttons]for l in range(joy.numbuttons // 10 + 1):s = []for i in range(l * 10, min((l + 1) * 10, joy.numbuttons)):if joy.button[i]:s.append("%d" % (i % 10))else:s.append(" ")output_strings.append("D" + "".join(s))for i, line in enumerate(output_strings):color = text_colors[line[0]]self.rendertextline(line[1:], pos, color, linenumber=i)tmpwidth = self.font.size(" ")[0]for i, v in enumerate(joy.axis):self.draw_slider(v,(pos[0],pos[1] + (2 + i) * self.fontheight,tmpwidth,self.fontheight))tmpwidth = self.font.size(" ")[0]for i, v in enumerate(joy.hat):self.draw_hat(v,(pos[0],pos[1] + (4 + joy.numaxes + joy.numballs + i) * self.fontheight,tmpwidth,self.fontheight))# self.draw_hat((int(joy.axis[3]),int(joy.axis[4])), (pos[0], pos[1] + (4+joy.numaxes+joy.numballs+0)*self.fontheight, tmpwidth, self.fontheight))def quit(self, status=0):pygame.quit()sys.exit(status)if __name__ == "__main__":program = input_test()program.init()program.run() # This function should never return
如果环境没有问题,就会看到如下的GUI界面。这时候按动手柄上的相关按键,就能看到数值的实时更新。
4. python与ESP32的通信测试
接下来测试python与Esp32之间的通信。 这里在PC上直接使用pyserial
库,
import serial
import timeser = serial.Serial(port='/dev/ttyUSB0',baudrate=9600,parity=serial.PARITY_ODD,stopbits=serial.STOPBITS_TWO,bytesize=serial.SEVENBITS
)ser.isOpen()# read a string
while 1:trans_data = "Hello World"ser.write(trans_data.encode('utf-8')) # write a stringreceived_data = ser.readline().decode() # read a byteprint(received_data)
ESP32使用Arduino框架,,只需要使用串口就可以了。程序的逻辑也非常简单, 就是读取上位机发送的信息,并返回信息。
#include <Arduino.h>String received_data;void ReadData(void)
{if ( Serial.available() ) {received_data = Serial.readString();}
}
void setup() {Serial.begin(9600);
}void loop() {ReadData();Serial.println(received_data);delay(200);
}
如果一切正常,那么输出如下:
Hello World
Hello World
Hello World
Hello World
Hello World
...
5. 手柄与ESP32的通信测试
如果以上两个的测试正常通过,我们接下来就可以测试手柄和ESP之间的通信了。这里我们的目标是使用UART发送给ESP32相应的手柄的值,并返回一个解码的值。
首先我们需要对原始数据进行一个处理,因为原始的手柄数据都是浮点值,为了方便esp的处理,我们在PC端就需要对原始数据进行一个处理,将其全部转换为整型。并将所有轴上的数据拼合成一个字符串来方便发送。
首先我们需要对上位机程序进行一个整合,上位机程序需要做的任务是读取手柄的值,然后转码并发送到ESP32,然后读取串口上的值。
程序如下,
import pygame
import time
import serialclass Joystick:def __init__(self):# initialization for joystickpygame.init()pygame.joystick.init()self.joystick_count = pygame.joystick.get_count()self.joystick = pygame.joystick.Joystick(0)self.joystick.init()self.axes = self.joystick.get_numaxes()self.joy_val = ""# initialization for serialself.ser = serial.Serial(port='/dev/ttyUSB0',baudrate=9600,timeout=1)self.ser.isOpen()self.done=Falsedef JoystickRead(self):for event in pygame.event.get():if event.type == pygame.QUIT:self.done = Trueself.joy_val = ""for i in range(self.axes):axis = int(round(self.joystick.get_axis(i), 2)*100) + 100axis_str = str(axis).zfill(3)self.joy_val = self.joy_val + axis_str#print("joy_val: ", self.joy_val)def SerialWriteAndRead(self):self.ser.write(self.joy_val.encode())# print(axis_str)received_data = self.ser.readline().decode()print(received_data, '\n')if __name__ == '__main__':joystick = Joystick()while (joystick.done != True):joystick.JoystickRead()joystick.SerialWriteAndRead()time.sleep(0.05)
接下来对esp32的程序进行一个更改。在这里,esp32任务是读取上位机发来的手柄的值,然后解码。之后将解码后的值发送给上位机来验证手柄信息是否正常发送。相关代码如下,
#include <Arduino.h>using namespace std;String received_data;
string received_data_string;
string output_data;
char *p_data;String default_data = "100100000100100000";String joy_left_x;
String joy_left_y;
String joy_right_x;
String joy_right_y;
const int ktest= 100;void ReadData(void)
{if ( Serial.available() ) {received_data = Serial.readString();}else {received_data = default_data;}delay(20);
}void ProcessData(void)
{if (received_data.length() > 17){joy_left_x = received_data.substring(0, 3);int left_x = joy_left_x.toInt();joy_left_y = received_data.substring(3, 6);int left_y = joy_left_y.toInt();joy_right_x = received_data.substring(6, 9);int right_x = joy_right_x.toInt();joy_right_y = received_data.substring(9, 12);int right_y = joy_right_y.toInt();delay(20);}
}void ShowData(void)
{// Serial.print("Data received: \n");// Serial.println(received_data);// Serial.print("Joy Left X: \n");Serial.println(joy_left_x);Serial.println(joy_left_y);// Serial.println(joy_right_x);// Serial.println(joy_right_y);
}void setup() {Serial.begin(9600);
}void loop() {ReadData();ProcessData();ShowData();delay(20);
}
首先下载ESP32的程序,再运行上位机程序。如果正常输出,则会看到,
100100100...
这是因为默认状态下,左摇杆的 x x x轴位置为0。为了方便进行串口通信,我们取到小数点后两位小数,并将其放大100倍。最后我们将其映射到0~200这个范围,这也是为什么输出为100的原因。
在调试过程中,需要注意的是UART的配置问题,如果配置有误,则会造成上位机输出信息错误。此外UART时序也是一个需要注意的问题。如果在调试过程中,发现结果不对,但是程序逻辑是正常的, 那么需要检查一下两侧的UART配置。
ESP32与Xbox手柄的UART通信测试,基于Arduino框架和pyserial+pygame相关推荐
- ESP32C3基于Arduino框架下的 ESP32 RainMaker开发示例教程
ESP32C3基于Arduino框架下的 ESP32 RainMaker开发示例教程 ESP RainMaker ESP RainMaker 是乐鑫推出的一个端到端平台.基于该平台,用户无需管理基础设 ...
- 【Arduino】TFT LCD显示屏显示gif小电视太空人动图(基于Arduino框架ESP8266/ESP32、TFT_eSPI库、使用python脚本GIF转十六进制文件)
前言 之前使用ESP32 来控制TFT屏幕显示动图时,找到现有的工具,需要先将动图gif格式一帧帧转为jpg格式,再将一帧帧的jgp转为hex十六进制格式,整个过程好麻烦.现用python写了脚本 ...
- ESP32基于Arduino框架下U8g2驱动I2C OLED 时间显示
ESP32基于Arduino框架下U8g2驱动I2C OLED时间显示
- ESP32基于Arduino框架,SD卡+MAX98357模块+MP3播放器
ESP32基于Arduino框架,SD卡+MAX98357模块+MP3播放器
- STM32F103C8T6基于Arduino框架下利用定时器跑RBG灯闪烁
STM32F103C8T6基于Arduino框架下利用定时器跑RGB灯闪烁
- CH55X 基于Arduino框架开发程序上传相关注意事项
CH55X 基于Arduino框架开发程序上传相关注意事项
- STM32G070RBT6基于Arduino框架下点灯程序
STM32G070RBT6基于Arduino框架下点灯程序 ✨说明:Arduino STM32系列开发环境搭建不在本示例范围内. 相关篇<[硬件开源电路]STM32G070RBT6开发板> ...
- STM32F401RCT6基于Arduino框架点灯程序
STM32F401RCT6基于Arduino框架点灯程序
- STM32G070RBT6基于Arduino框架GPIO输入输出模式
STM32G070RBT6基于Arduino框架GPIO输入输出模式
最新文章
- 度量.net framework 迁移到.net core的工作量
- MVC Filter
- Struts2漏洞分析之Ognl表达式特性引发的新思路
- 数学学习--最小二乘法案例剖析
- 由于更换了java版本,Eclipse启动时报错:JRE or JDK must be available in order to run Eclipse
- python安装函数库pip网址_批量安装python库函数---pip
- jni问题总结:jni error (app bug): accessed stale local reference
- windows ssh命令_如何启用和使用Windows 10的新内置SSH命令
- ios设置导航条背景图片
- 华为云服务查找手机_华为云服务登录入口
- js 调用 百度/腾讯/高德地图app 导航 初始位置为我的位置
- windows 打印机管理机制(任务后台等待机制)
- 【转】正则表达式 匹配中文,英文字母和数字及_的写法!同时控制长度
- WIN10 USB 代码19 无法识别USB
- 有哪些道理是你毕业多年后才明白的?
- Python+Vue计算机毕业设计停车场管理系统8f46a(源码+程序+LW+部署)
- 计算机毕业设计Java印染公司信息管理系统(系统+程序+mysql数据库+Lw文档)
- DownloadManager下载管理
- [转帖]流言终结者 —— “SQL Server 是Sybase的产品而不是微软的”
- 【技术篇】文件的md5值