This Episode is also a PDF document available.
After building the mechanics of the Joyball-Wizzard in Episode 1 and the programming of the game in Episode 2, today we will integrate a feature of the ESP8266 that has so far had no technical importance for the game. But the approach that I will represent not only has playful benefits but can also be used for many further projects when it comes to replacing data with radio. We are not talking about an RS232 connection, although the whole thing is similar. In short, we replace the cable of the RS232 connection with the radio connection via WLAN and let the protocol speak UDP. So welcome to another episode from the series
MicroPython on the ESP32 and ESP8266
Today part 3
Joy Ball Wizzard goes WiFi
In fact, the game would have to be renamed, because I sent the joystick on vacation and hired an MPU6050. The MPU6050 is a chip that can measure accelerations, i.e. an accelerometer (from English accelerates = accelerate). The GY-521 module contains such a chip that can be addressed via the I2C bus.
The GY-521 accelerometer as a motion sensor
Acceleration values indicate how much the speed of a body in meters per second increases during a second. The unit is (1m/s)/s therefore 1m/s². In the heavy field of the earth, a body in free fall is accelerated with g = 9.81m/s². If I let a stone fall from a high tower, please note the subjunctive, then, of course, the stone, not the tower, has a speed of 9.81m/s after a further second, 19.62m/s, and so forth. This statement is of course only correct if the stone in Fall Tower in Bremen falls in a vacuum because otherwise, the air resistance slows it down the faster it goes. Please also note that the falling stone could result in criminal investigations, especially if the stone was accidentally braked by the head of a person below the tower.
What does all this have to do with the game? Well, since Newton we have known that strength is the same as acceleration, as a formula: F = m • a. That means that an acceleration A to a force F on the mass m leads. This is exactly the measuring principle in the MPU6050. A mass m, which is attached at the end of a "leaf spring", is the change of movement, which is caused by the acceleration. The inertia of the mass leads to a deforming force on the spring. But not only a change of movement can cause the spring to deform.
Illustration 1: Accelerometer with piezoelement
Due to the weight Fg, which results from the acceleration of the earth g = 9.81m/s², the mass becomes m, which is attached to springs of piezoelement, deflected from its rest position. The bending of the piezoelement creates a voltage on its surface that is reinforced, digitized and made available via the I2C interface. Of these units, the MPU contains three pieces that are aligned according to the spatial directions. We use the X direction for side inclination (roll = roll) and the Y direction for front and back (nod = pitch). Due to the inclination from the vertical level, the number of force changes, which acts perpendicular to the arm of the piezoelement and bends it, in one or in the other direction. The measured values fluctuate between approx. +/- 16000. How these values can be used to control our game, we will look at this immediately after the hardware is discussed. The mechanics and hardware, as well as the basic software description for the game itself, are in the previous consequences (1 and 2) comprehensively described.
Hardware
1 |
|
1 |
|
1 |
|
2 |
|
1 |
|
1 |
|
2 |
Resistance 2.2kΩ |
1 |
Resistance 560Ω |
1 |
Handy battery, LI 3.7V 600mAh |
1 |
TP4056 MICRO USB 5V 1A Daditor Lithium Li-Ion Battery Charger Module |
various |
|
1 |
Breadboard kit - 3 x 65stk. Jumper Wire Kabel M2M and 3 x mini Breadboard 400 pins |
All parts fit on a mini breadboard. As an OLED display, this can be used from episode 2 because the display functions are sent to all remote control via a UDP connection. Otherwise, the parts listed here only refer to the remote control.
Illustration 2: Remote control
Illustration 3: Remote control - circuit
Illustration 3 shows the basic circuit structure. For energy supply, I intended to use the lithium battery from an old cell phone. The originally planned upcycling in connection with a battery loading module was developed in the wrong way.
Some loading parts even have an integrated step-up converter that raises the 3.7V of the battery to a constant 5V. There is only one problem. After about 30 seconds, things switch off the 5V output for unexpected reasons. An attempted workaround with a Hallo-Wach pulse from the ESP8266 ultimately did not bring a solution. Therefore, in my approach, in addition to the simpler charge controller with the TP4056, I used a separate boost converter that is connected directly to the battery and ensures the 5V supply of the ESP8266. A USB cable with a micro-B connector that is connected to a 5V cell phone charging part can serve as a charging cable. As a result, the battery is brought to a voltage of approx. 4.2V. The loading part then switches off automatically. A small switch in the 5V line between the battery and remote control is also useful, as shown in Illustration 4.
Illustration 4: Remote control circuit with battery loader and a step-up converter
To supplement this, here is the stripped-down circuit for the game
Illustration 5: Circuit for remote access
The software
For flashing and the programming of the ESP32:
Thonny or
Firmware used for the ESP8266/ESP32:
Please choose a stable version (ESP8266 with 1MB Version 1.18 Status: 05.03.2022)
The MicroPython programs for the project:
pca9685.py: Module with the Servo class, PCA9685 driver
SSD1306.PY: Oled driver
oled.py: OLED class
gy521_level.py: Driver for the GY-521 accelrometer module
joyball_funk.py: Software for the game unit
joyball_ensor.py: Software for remote control
Other software:
Packet transmitter Download page
Packet transmitter Windows Install version
Packet transmitter Windows Portable
Packet transmitter Linux
MicroPython - Language - Modules and Programs
To install Thonny you will find one here with detailed instructions (English version). There is also a description of how the Micropython firmware (As of 05.02.2022) on the ESP chip burned becomes.
MicroPython is an interpreter language. The main difference to the Arduino IDE, where you always flash entire programs, is that you only have to flash the MicroPython Firmware once on the ESP32 so that the controller understands MicroPython instructions. You can use Thonny, µpycraft, or ESPTOOL.PY. I have the process for Thonny here described.
As soon as the Firmware has flashed, you can easily talk to your controller in a dialogue, test individual commands and see the answer immediately without having to compile and transfer an entire program beforehand. That is exactly what bothers me about the Arduino IDE. You simply save an enormous time if you can check simple tests of the syntax and hardware up to trying out and refining functions and entire program parts via the command line before knitting a program from it. For this purpose, I always like to create small test programs. As a kind of macro, they summarize recurring commands. Whole applications then develop from such program fragments.
Autostart
If the program is to start autonomously by switching on the controller, copy the program text into a newly created blank file. Save this file under boot.py in Workspace and upload it to the ESP chip. The program starts automatically the next time the reset or switching is on.
Test programs
Programs from the current editor window in the Thonny-IDE are started manually via the F5 button. This can be done faster than the mouse click on the start button, or via the menu run. Only the modules used in the program must be in the flash of the ESP32.
In between Arduino IDE again?
Should you later use the controller together with the Arduino IDE, just flash the program in the usual way. However, the ESP32/ESP8266 then forgot that it has ever spoken MicroPython. Conversely, any Espressif chip that contains a compiled program from the Arduino IDE or AT-Firmware or LUA or ... can be easily provided with the MicroPython Firmware. The process is always like here described.
The program for the game
That Joyball.py for the game from episode 2 receives a few adjustments and a larger addition for the WLAN connection and the UDP unit. I deliberately do not say UDP server here, because the boundaries between the server and the client are washed out at UDP. Any server can also play client and vice versa. But we'll come to that later. Let's go through the program first and take a closer look at the striking areas.
import OS,sys
from machine import Pin code, reset, I2C, ADC
from time import sleep, Ticks_ms
from PCA9685 import Servo
# from ads1115 import ads1115
# From Oled Import OLED
import ESP
ESP.Osdebug(None)
import GC
GC.collect()
import network
import socket
import struct
In the case of imports, the associated lines are eliminated, but three new ones are added network for the WLAN connection or the ESP8266-own access point, socket for the UDP protocol, and struct for the encoding and decoding of numerical values. The latter saves the transmission time during the transfer because instead of the up to 5 ASCII characters of the numbers, only the high-byte and low-byte of the value have to be sent.
The instance of the I2C object does not experience any change, not even in the subsequent section with the declaration of the GPIO pins.
The instances can also be D for the OLED display and joy for the ADC. The OLED display is used in the remote control. We use the ESP8266 internal ADC for the operating voltage measurement. The A0 input tolerates tensions up to 3.3V. The voltage divider from the two 10kΩ resistors can therefore be retained to reduce the 6V supply voltage to 3V.
servo=Servo(I2C)
# D = OLED (I2C, 128.32)
# joy = ads1115 (i2c, 0x48)
ADC=ADC(0)
ADC.read()
The methods bump() and flashing() remain unchanged while goToOrigin() in the places, a change is made where expenses to the OLED display should take place. Instead of sending the data directly to the display, this goes to the remote control via the network connection, where they are displayed. In order to save transmission time here too, and on a large scale, only an ASCII sign as a connoisseur follows from one ":" and possibly a numerical value over the ether. In preparation, all data is coded as Bytefolge. The outputs that were previously at these places have been transplanted into the operating software of the remote control.
def goToOrigin():
global trigger, gameOver,start
oePin.value(0)
gate.irq(handler=None)
border.irq(handler=None)
# Wait, ball goes back to the start
text="R: 0".encode()
S.broadcast(text,address)
getdata()
servo.Write PulseTimeslice(7,0,1800)
sleep(1.2)
servo.Write PulseTimeslice(4,0,350)
sleep(1.2)
servo.Write PulseTimeslice(7,0,850)
sleep(1.2)
servo.Write PulseTimeslice(4,0,1500)
sleep(1.2)
trigger=True
gameover=False
# Waiting for a push of a button to start the game
text="G: 0".encode()
S.broadcast(text,address)
Waitforbuttonis0()
point=specification
# Message of the start of the game
text="S:".encode()
text=text+struct.pack("H",point)
S.broadcast(text,address)
gate.IRQ(handler=bump,trigger=Pin code.Irq_falling)
border.IRQ(handler=bump,trigger=Pin code.Irq_falling)
begin=Ticks_ms()
score=0
The use of the method is a remarkable pack() from the module struct. The "H" parameter is a kind of format instruction. It says that the integer value point is a result of two bytes. The following lines in the Terminal of Thonny demonstrate this. The byte order is by default Little Endian, so the LSB comes first.
>>> import struct
>>> struct.pack("H",1035)
B '\ X0B \ X04'
The method gains some clarity due to the emigrated text editions. At the same time, however, the side effect of the texts is lost as an explanation of the program. Therefore, it can make sense to insert a few comments.
Preparatory for the handling of the data transfer, three new functions were created.
def getdata():
try:
Rec,ADR=S.recvfoma(6)
IF Rec is need None:
respect=struct.unpack("BHH",Rec)
Rec=""
return respect
except:
#print (".", End = "")
return None
getData() gets up to 6 bytes from the reception buffer of the UDP socket S. Its declaration takes place below. This reading is connected to a timeout. If there is no sign in the buffer within 100ms, a OSError-Exception throwed up with try have to intercept to prevent a program. If there is a message from the remote control, it ends up in the variable rec as a bytes object, while ADR takes the sender's socket address as a tuple as usual.
For safety we make sure that rec does not contain a reference to the big nothing and then unpack what the remote control sent us - a byte for the key code and twice two bytes for the X and Y value. The function returns the reference to the tuple of the unpacked three numerical values. In the except branch could also simply say pass. But to recognize what is actually happening, we spend a point. That end = "" Suppresses the line feed. The return value in this case is None, nothing.
def button():
request=getdata()
IF request is need None:
button,*_=request
return button
Else:
return None
getButton() grabs a data record, isolates the key information by unpacking the tuple, the getData() returns. The spelling is interesting button, *_ = request. Here it will Tuble-wing applied. The button information moves to button And the X and Y values end up in the temporary variables _. The "*" ensures that everything that follows the first tuple element ends up in _. _ can be understood as an insignificant, temporary variable. If there is a key information, this is returned, otherwise None.
>>> T=(1,234,-345)
>>> button, *_ = T
>>> _
[234, -345]
>>> button
1
When entering direct, _ has an additional meaning. In _ the last issue is saved in the terminal. Therefore _ must be queried in the example before button.
def Waitforbuttonis0():
SW=button()
while SW is need 0:
SW=button()
SW=button()
while SW is 0:
SW=button()
In various places we have to wait for the play board to press a button. The long radio time finger to the game board consists of a push of a button on the remote control and the transfer of the corresponding code, 0 or 1. The ESP8266 must first wait for the arrival of a 0 and can only continue if he then received the first 1.
The subsequent section
# *************** Connect to WLAN ********************
is sufficiently commented on the program, which is why I don't go into it in more detail here. The only important thing is that you specify your Credentials for mySSID and myPass.
myssid = 'Here_goes_your_ssid'
mypass = 'Here_goes_your_password'
The second critical point is the information of the network data, IP, network mask, gateway and DNS server, which you have to adapt to your network.
# Structure of the connection to the WLAN Accesspoint
# We set a static IP address
nic.Ifconfig(("10.2.1.95","255.255.255.0","10.2.1.20","10.2.1.100"))
This information should be given by hand because, as with remote control, network participants are concerned, who always have to come together again without any problems. Dynamic addresses from the DHCP server of the WLAN access point are very counterproductive.
Of course, you also have to do one thing in connection with the Accesspoint, enter the Mac addresses of game and remote control on the router as permitted. Please consult your router manual.
The network connection provides the radio connection what you do when setting up a serial connection via USB to the ESP8266 by inserting the cable.
As with serial transmission, we now have to open an interface. With the RS232 we would have a UART object (Universal Asynchronous Receiver / Transmitter). At radio level we create a base object S.
S = socket.socket(socket.Af_inet, socket.Sock_dgram)
S.setsockopt(socket.Sol_socket,socket.So_reuseaddr,1)
S.binding(('', 9009))
print("Waiting on Port 9009 ...")
S.set(0.1)
With socket.SOCK_DGRAM we agree on UDP as a protocol that transports packages (datagrams), in contrast to TCP, which relies on streaming (socket.SOCK_STREAM). In the event of a restart, the next line allows us that we can reuse the same base address. In the third line we bind the interface to the IP address specified in the WLAN part and port 9009. By setting a timeout of 0.1 seconds, we make it that the main loop and the other places in the program where it (mainly) about receiving messages, do not block the program flow. This is the case wherever where getData() direct or implicit (for example waitForButtonIs0()).
After all the preparations in your own stable, the program will take up work with the other section in the next section.
Uservo=ADC.read()/158.569
text="W:".encode()
text=text+struct.pack("F",Uservo)
S.broadcast(text,address)
sleep(5)
Gotoorigin()
We measure the operating voltage on the 6V line and send the result to the remote control. Also the reports that goToOrigin() commissioned are sent. In between, the ball is locked back to the start and the global variables get their starting values.
The main loop takes care of three jobs. First there is the picking up of inclination data. If there was an entrance, then that tuple returned by getData() is parsed for X and Y value and the values continue to the PCA9685. In this case, the key information in _ disposed of.
while 1:
response=getdata()
IF response is need None:
_,X,y=response
servo.Write PulseTimeslice(4,0,X)
servo.Write PulseTimeslice(7,0,y)
# D.Writeat ("score: {}". Format (points), 0.2)
IF target.value()==0:
gameover=True
length of time=intimately((Ticks_ms() -begin)/1000)
timepoints=(15-length of time)*time factor
score=point+timepoints
point=Max(0,point)
print(point, timepoints, score)
text="O:".encode()
text=text+struct.pack("H",score)
S.broadcast(text,address)
Waitforbuttonis0()
Gotoorigin()
IF button()==0:
text="C:".encode()
text=text+struct.pack("H",point)
S.broadcast(text,address)
sys.exit()
#Reset ()
If the entrance target went to 0, the ball is at the finish. The playing time is calculated and thus also the points from the season. We put this together for the score and send the encoded byte episode to the remote control. Then we are waiting for the button actuation on the remote control and reset the game for the next round.
If the button on the remote control is pressed during a game round, this is considered a demolition signal and the program is left onto the console. This option is actually only important during the development phase and can be removed if everything works. Alternatively, you can command reset() degenerate and sys.exit() hide in a comment. Then the ESP8266 makes a complete cold start.
The entire program joyball_funk.py is available for download.
The remote control
Except for the power supply, the parts for the remote control have space on a mini Breadboard (Illustration 2).
The program joyball_ensor.py is structured in a similar way in terms of network use as joyball_funk.py. So the modules are in imports network, socket and struct again with the game. The class is for the query of the MPU6050 GY521 in the module gy521_level.py responsible. We create an I2C instance, the accelerator object AC and an OLED object D.
# joyball_ensor.py
# Accelerator unit for the joyball wizzard
#
# Import webrepl_setup
#> D FUR DISABLE
# Then RST; Restart!
import sys,OS
from machine import Pin code,reset,I2C
from gy521_level import Gy521
from OLED import OLED
import struct
import ESP
ESP.Osdebug(None)
import GC
GC.collect()
from time import sleep, Ticks_ms
import network
import socket
#
Scl=Pin code(5)
Sda=Pin code(4)
I2C=I2C(-1,Scl,Sda,freq=400000)
AC=Gy521(I2C)
D=OLED(I2C,128,32)
taste and button are the two key objects for the emergency brake and the game button. The various LED objects serve to represent the connection states for the WLAN router, blue for the radio connection, green for the Heartbeat, this is function of the main loop and red for error conditions.
button=Pin code(0,Pin code.IN,Pin code.Pull_up) # D3
button=Pin code(13,Pin code.IN,Pin code.Pull_up) # D7
bluepin,greenpin,Redpin=16,14,12
onair=Pin code(bluepin,Pin code.OUT,value=0) #D16
Heartbeat=Pin code(greenpin,Pin code.OUT,value=0) #14
error=Pin code(Redpin,Pin code.OUT,value=0) # 12
blue,green,red=0,1,2
LED=[onair,Heartbeat,error]
target=("10.0.1.95",9009) # Socket of the game unit
myaddr=("10.0.1.94",9009) # Socket of the remote control
The MPU6050 should be calibrated before the first start of the game so that the ESP8266 knows which values can occur and where the mean is. If the calibration was carried out, the method writes calibrate() the data in the file accel.ini in the root files system of the ESP8266. If this file exists, the reading attempt is successful. Otherwise the calibration is called.
This requires five steps. The terminal edition of Thonny says what to do. The X and Y directional arrows can be found on the GY-521 platinum.
try:
AC.reabation()
except:
AC.calibrate(5)
The function parse() takes care of the deciphering of the messages from the game program. The bytes object is searched for a ":" searched that separates the command part from the data. Everything to the spot POS (exclusively) comes after CMD, all afterwards vala. In the subsequent IF-ELIF structures, the expenses on the OLED display pass. The following listing shows the beginning of the function as a section.
def parse(code):
POS=code.find(B ':')
CMD=code[:POS].decode()
vala=code[POS+1:]
print("CMD, Val",CMD,vala)
IF CMD == "R":
D.clearall()
D.writer("Going Back",2,0,False)
D.writer("To Origin",3,1,False)
D.writer("Please wait!",1,2)
elif CMD == "G":
D.clearall()
D.writer("Click Button",1,0,False)
D.writer("To start game",1,1)
elif CMD == "S":
point=struct.unpack("H",vala)
D.clearall()
D.writer("Game",3,0,False)
D.writer("Started",4,1,False)
D.writer("Score: {}".format(point[0]),0,2)
The subsequent section for connecting with the waln-accesspoint is except for the line
nic.Ifconfig(("10.2.1.94","255.255.255.0","10.2.1.20","10.2.1.100"))
Identical with program part in joyball_funk.py. The structure of the socket object also runs analogously. In doing so, we use the function misused parse() to issue a start report. Two seconds to read and then go to the job loop.
# Server loop
while 1:
switch=button.value()
try: # Send command
X,y=AC.getxy()
#print (x, y)
X=Max(min(X,AC.Maxx),AC.minx)
y=Max(min(y,AC.Maxy),AC.miny)
X=AC.transform(-X,-AC.Maxx,-AC.minx,350,2300)
y=AC.transform(y,AC.miny,AC.Maxy,350,2300)
command=struct.pack("BHH",switch,X,y)
S.broadcast(command,target)
except:
passport
try:
# Receive Response
Rec,ADR=S.recvfoma(10)
print("raw:",Rec)
# parse it
parse(Rec)
Rec=""
except:
passport # overflow
IF button.value()==0:
sys.exit()
flashing(0.005,0.03,Col,CNT=1)
Four things have to be done: query of the game button, obtaining the inclination data from the GY-521, check whether the ESP8266 sent us a message and finally check the demolition button. Finally, a short flashing for the Heartbeat, green for normal function, red in a communication error.
After reading the inclination values, you will first be fitted into the window, which is specified by the calibration-cover values. For rolling (side inclination) we have to reflect the values so that the game board follows the movements of the remote control sensor correctly. Then we convert the numbers into a bytes object and send it to the game. If a mistake occurs with all of this, it doesn't take care of it, pass, another package will follow shortly.
The reception of messages initially runs analogously to the program joyball_funk.py. Since the breakdown is quite extensive, this task of the function is parse() handed over, which we pass on the received bytes object. If no message has arrived, the timeout will be try intercepted. except has nothing to do, so pass.
An extremely short flashing closes the loop jobs.
So we arrived at the end of the joyball sequence. The Program for remote control is of course also available for download. I presented another blog series, in which I use a large display of 8x8 LED matrix fields, which can be controlled via radio or I2C or RS232. This means that not only play results in large format, but also measured values of sensors, etc.
Until then.