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,所需库为pygamepyserial

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相关推荐

  1. ESP32C3基于Arduino框架下的 ESP32 RainMaker开发示例教程

    ESP32C3基于Arduino框架下的 ESP32 RainMaker开发示例教程 ESP RainMaker ESP RainMaker 是乐鑫推出的一个端到端平台.基于该平台,用户无需管理基础设 ...

  2. 【Arduino】TFT LCD显示屏显示gif小电视太空人动图(基于Arduino框架ESP8266/ESP32、TFT_eSPI库、使用python脚本GIF转十六进制文件)

    前言 ​ 之前使用ESP32 来控制TFT屏幕显示动图时,找到现有的工具,需要先将动图gif格式一帧帧转为jpg格式,再将一帧帧的jgp转为hex十六进制格式,整个过程好麻烦.现用python写了脚本 ...

  3. ESP32基于Arduino框架下U8g2驱动I2C OLED 时间显示

    ESP32基于Arduino框架下U8g2驱动I2C OLED时间显示

  4. ESP32基于Arduino框架,SD卡+MAX98357模块+MP3播放器

    ESP32基于Arduino框架,SD卡+MAX98357模块+MP3播放器

  5. STM32F103C8T6基于Arduino框架下利用定时器跑RBG灯闪烁

    STM32F103C8T6基于Arduino框架下利用定时器跑RGB灯闪烁

  6. CH55X 基于Arduino框架开发程序上传相关注意事项

    CH55X 基于Arduino框架开发程序上传相关注意事项

  7. STM32G070RBT6基于Arduino框架下点灯程序

    STM32G070RBT6基于Arduino框架下点灯程序 ✨说明:Arduino STM32系列开发环境搭建不在本示例范围内. 相关篇<[硬件开源电路]STM32G070RBT6开发板> ...

  8. STM32F401RCT6基于Arduino框架点灯程序

    STM32F401RCT6基于Arduino框架点灯程序

  9. STM32G070RBT6基于Arduino框架GPIO输入输出模式

    STM32G070RBT6基于Arduino框架GPIO输入输出模式

最新文章

  1. 度量.net framework 迁移到.net core的工作量
  2. MVC Filter
  3. Struts2漏洞分析之Ognl表达式特性引发的新思路
  4. 数学学习--最小二乘法案例剖析
  5. 由于更换了java版本,Eclipse启动时报错:JRE or JDK must be available in order to run Eclipse
  6. python安装函数库pip网址_批量安装python库函数---pip
  7. jni问题总结:jni error (app bug): accessed stale local reference
  8. windows ssh命令_如何启用和使用Windows 10的新内置SSH命令
  9. ios设置导航条背景图片
  10. 华为云服务查找手机_华为云服务登录入口
  11. js 调用 百度/腾讯/高德地图app 导航 初始位置为我的位置
  12. windows 打印机管理机制(任务后台等待机制)
  13. 【转】正则表达式 匹配中文,英文字母和数字及_的写法!同时控制长度
  14. WIN10 USB 代码19 无法识别USB
  15. 有哪些道理是你毕业多年后才明白的?
  16. Python+Vue计算机毕业设计停车场管理系统8f46a(源码+程序+LW+部署)
  17. 计算机毕业设计Java印染公司信息管理系统(系统+程序+mysql数据库+Lw文档)
  18. DownloadManager下载管理
  19. [转帖]流言终结者 —— “SQL Server 是Sybase的产品而不是微软的”
  20. 【技术篇】文件的md5值

热门文章

  1. 三分钟实现快速查单,物流信息批量查询高效完成
  2. Java学习教程!java培训北京多少钱
  3. 娜迦:大数据时代的来临 监管体系的创新
  4. 学习软件测试线上和线下哪个好,线上VS线下陪玩APP独家测评|好用到哭!
  5. 关于匿名内部类的一些理解
  6. 面谈Python基础
  7. 【makefile笔记】patsubst和wildcard函数使用小结
  8. 计算机一级基础知识点
  9. 3.31期货黄金月线收官价格走势@黄金全面策略分析@黄金解套在线
  10. 固态硬盘SSD学习笔记:闪存