They are small, very bright and available in different arrangements. In addition, several of them are very easily controlled By just one line, including the illumination and this in 16.7 million colors. We are talking about neopixel LEDs. I have used neopixel rings several times in interesting circuits, for example At the Advent wreath calendar, at the Bicycle brake light, at the compass, the Table clock with touch operation And with the games bandit and Master ring. I had the 16x16 neopixel array for a Christmas special Used and finally one with the neopixel strips Linear watch with abacus coding built. As you can see, the parts can be used in a variety of ways.
Now I have set up a game again with an LED strip and programmed it in Micropython. It is called roulned and is reproduced to a roulette kettle. You can find out in the series which thoughts played an important role in development, which controllers were tested and what the play goods look like
Micropython on the ESP32 and Raspberry Pi Picotoday
Rouled - on the trail of happinessWe use the following parts.
We will build the final program so that it will run for all three controller types without a change. But the D1 Mini Nodemcu and the Raspberry Pi Pico has a significant difference, both have no flash key. But I like to use this in the development to achieve a precisely controlled program. If you want to use this feature, you need an optional button if you want to use one of the two development boards. The ESP32 board offers the flash key on GPIO0 for this purpose.
From the 1M LED strip with 30 LEDs per meter, we need 25 pieces for the roulette circle and one for the status display. We also solve the stubborn supply lines (red, green, white) on the strip input and replace the cables with flexible, thinner.
illustration 1: Connection of the strip
The strip section with the 25 LED segments has a length of 100 cm / 30 x 25 = 83.3 cm. As the strip of the strip, I chose a square chipboard of 9 mm thickness and 30cm edge length. From this, a circle of 83.3 cm / (2 x 3.1415) = 26.5 cm diameter is cut out so that the strip fits precisely. The end of the strip is isolated with adhesive film. A three -pin pin strip is soldered to the end of the supply cable.
illustration 2: Frame dimensions 300mm x 300mm
To cover the frame, I printed out a rosette with the numbers from 0 to 24 on two DIN A 4 leaves, which are glued together and attached to the frame (Part 1,Part 2). Incidentally, I decided to reduce the original figures from 0 to 36, as can be found in the original roulette, to 0 to 24, because I get By with a single LED strip of 1m length and the whole thing does not become so large (26.5 cm instead of just under 40 cm diameter!)
I also soldered a three -pole pen strip on the strip segment with the individual LED. To see in Figure 3 left under the ESP32. On the collected Breadboards, all three controllers with which I tested the circuit are peacefully united. The service is the ESP8266-Amica bottom right.
illustration 3: Test setup with the three controllers
Of course, a gaming table also includes roulette. Here is a PDF template, Bidddeschööön (Part 1,Part 2).
illustration 4: Play table
The circuit is not very demanding and for the ESP32 ESP8266 and Raspberry Pi Pico- Families running. If the structure is to run without a PC connection, we naturally still need a voltage source of 5V for operation. This can be a plug-in power supply or, for example, a battery owner with lithium-battery of the 18650 type.
The softwareFor flashing and the programming of the ESP32:
Thonny or
To represent bus signalsSaleae – Logic analyzer software (64 bit) For Windows 8, 10, 11
Used firmware for the ESP32:Used firmware for the Raspberry Pi Pico (W):The micropython programs for the project:rouled.py: A first approach to the project
rouled2.py: The program for the ESP32
rouled3.py: The universal solution
timeout.py: Software-timer module
Micropython - Language - Modules and ProgramsTo install Thonny you will find one here Detailed instructions (English version). There is also a description of how that Micropython firmware (As of 01/25/2024) on the ESP chip burned becomes. How to get the Raspberry Pi Pico ready for use here.
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. For Thonny I have the process 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 transmit an entire program beforehand. That is exactly what bothers me on the Arduino IDE. You simply save an enormous time if you can check simple tests of the syntax and hardware 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.
AutostartIf the program is to start autonomously By switching on the controller, copy the program text into a newly created blank tile. Save this file under main.py in the workspace and upload them to the ESP chip. The program starts automatically the next time the reset or switching on.
Test programsPrograms 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 id 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 neopixel stripType WS2812 neopixel LEDs contain three individual pixels that release red, green or blue light. They are addressed By a controller who receives its instructions via a kind of bus system that is clocked with 800kHz.
At the I2C bus or the SPI bus, the signals from the controller, for example an ESP32, reach all slaves on the bus in the same way, everyone sees everything. It is different with the WS2812 modules. Each component has a data input and data output. Several building blocks can be cascaded By connecting At the I2C bus or the SPI bus, the signals from the controller, for example an ESP32, reach all slaves on the bus in the same way, everyone sees everything. It is different with the WS2812 modules. Each component has a data input and data output. Several building blocks can be cascaded By for At the I2C bus or the SPI bus, the signals from the controller, for example an ESP32, reach all slaves on the bus in the same way, everyone sees everything. It is different with the WS2812 modules. Each component has a data input and data output. Several building blocks can be cascaded By for the individual colors. We give the color code in the form of one Tuels for each WS2812 as an element of one list to. First the Class Pin and Neopixel imported. I create a PIN object as an outcome and instance a neopixel object with 25 building blocks. The neopixel instance neo Contains a Bytearar buf And the method write(), with which the content of the Bytearar is transferred to the neopixel ring.
>>> From Machine Import Pin
>>> From Neopixel Import Neopixel
>>> NP = PIN (14, pin.out) # D5
>>> Neo = Neopixel (NP, 25)
>>> Neo [0] = (0xe0.0x07.0x3c)
>>> Neo [1] = (0xf0.0xf0.0xf0)
>>> Neo.Write ()
>>> Neo.buf
ByTearay (B '\ X07 \ XE0 <\ xf0 \ xf0 \ \ x00 \ x00 \ x00 \ x00 \ x00 \ x00 \ x00 \ x00 \ x00 \ x00 \ x00 \ x00')
With neoI address the first three elements of the array and hand over the values for red, green and blue, 0xe0, 0x07 and 0x3c. Internally, this makes the private function __Sitem__(). The values are entered in the buffer in a changed order. As we will see at the curve, which I recorded with the help of the Logic Analyzer. The values are sent as they are in the buffer. One 0 corresponds to a narrow impulse of approx. 250ns width followed By a ). The values are entered in the buffer in a changed order. As we will see at the curve, which I recorded with the help of the Logic Analyzer. The values are sent as they are in the buffer. One 0 corresponds to a narrow impulse of approx. 250ns width followed By a pulse of 750ns and a break of 500ns.
illustration 12: Pulse sequence for RGB = 0xe0, 0x07, 0x3c
At the output of the first WS2812, the three Bytes 0xe0, 0x07 and 0x3c are missing that has eaten this building block. Instead, the code 0xff, 0xff, 0xff, the code for the second component. The input for channel 1 of the Logic Analyzer was connected to this measurement at the entrance of the first LED, channel 2 at the output of it.
Now we know how the kangaroo is running at the WS2812 and can turn to the program for the roulette project.
The rouled projectSome questions employed me to the rouled project before the start of program development. With what frequency is it possible to control a certain LED in the strip of 25 neopixel LEDs? And how can you understand the run-out behavior of the ball in a real roulette pool through a program? Are there differences in the behavior of the micropython dialects in the targeted controllers ESP32, ESP8266 and Raspberry Pi Pico? In addition to the hardware problems already treated, I actually found differences in the Micropython firmware. In addition we come later, we take care of a functioning program for the ESP32, which brings the best conditions for this.
The LED sequel frequency and the first programHow long does the Pulse Train (AKA impulse sequence) take for the 25 LEDs of the strip? Well, the frequency on the Neopixel bus, as already said, is f = 800kHz. Die Übertragung eines Bits passiert also in t = 1 / f = 1 / 800000kHz = 1, 25µs. We have to transmit 3 • 8 bits = 24 bits per pixel. And for 25 pixels we need t = 1.25 µs • 24 • 25 = 750µs, at least, apart from system -related eventual cases. The transmission of conditions for the 25 neopixel LEDs cannot therefore take place faster than with about 1ms. This is the first limit.
Then we know that a ball that is thrown in in a round bowl of tangential relatively high speed moves over the inner surface of the bowl, then at some point slower to approach the lower area. In the case of roulette, the ball then pushes to obstacles that further disturb the run of the ball. It begins to jump and finally gets tangled in one of the numbered fields where they finally to lie. This behavior must now be reproduced.
Energy discation (loss of kinetic energy) through friction should be exponentially in our modeling. This means nothing other than the time that the ball needs to reach the next number segment in the rotary shell, the longer the "ball" runs. The time difference for reaching the next angle position therefore follows an exponential function. We will try to make the run of the ball as realistic as possible from the start to the snapping on the target position. The time duration that the ball is in a angle segment is low at the beginning (high speed) and grows slowly. It is only against the end that the length of the sphere per segment quickly greater. It comes to rest when a given limit is exceeded. The target position must of course be determined in any form of "chance".
The first program rouled.pyarose according to these requirements. Let's take a look at it. It runs on the ESP32.
From machine import Pin
From neopixel import Neopixel
From time import Sleep_ms
From sys import exit
import random
Pin and Neopixel We need for LED strips and demolition button. With Sleep_ms Let's determine the length of stay. The function exit() we need for the targeted program and the module random the method delivers edge() to produce "random numbers" in given areas.
At GPIO14 we connect the control line of the LED strip with the 25 neopixel LEDs. The constructor creates the desired neopixel object n.
np=Pin(14, Pin.OUT)
nbrOfLeds=25
n=NeoPixel(np, nbrOfLeds)
As a demolition button, we take the flash button of the board on GPIO0.
taste=Pin(0,Pin.IN,Pin.PULL_UP)
The function LEDsoff() switches NBROFLEDS LEDs dark By describing their buffer with the RGB tupel (0.0.0) and then the buffer content with write() send it to the strip.
def ledsOff():
for i in range(nbrOfLeds):
n[i]=(0,0,0)
n.write()
We define some variables that we will find in the main loop. old and new are the indices in the array of the color values of the neopixel LEDs. With delay Let us state the delay time for the change of segment. offset and limit are occupied By random generator with total numbers from the areas 4 to 9 or 100 to 4999. With even and odd Let us define the color tupes for straight and odd indices in the list of LEDs. factor is the value that defines the dynamics of increasing the delay time. We get an increase in small levels near 1, while the curve from 1.2 shows a very steep climb towards the end. According to the resulting top values, we must choose the upper value of the area for the limit. For evaluation purposes we let ourselves limit and offset in Replica spend. We delete all LEDs.
old=0
new=0
delay=3
offset=random.randint(4,10)
limit=random.randint(100,5000)
odd=(0x40,0,0)
even=(0,0,0x40)
faktor=1.2
print(limit, offset)
ledsOff()
illustration 5: factor = 1.03
illustration 6: factor = 1.2
Then it goes to the Main Loop, the main loop. First we do the LED with the number old off and the LED new Either in red when the LED index is odd, or in blue with a clear index. So red and blue alternate. The LED has a special status with number 0, where the LED lights up in white - but only when the write() Command is sent.
while 1:
n[old]=(0,0,0)
n[new]=odd if new % 2 else even
if new == 0:
n[new]=(0x30,0x30,0x30)
n.write()
Now we remember the new position in old and increase the position in new. The Modulo 25 division remnants ensure that the count remains from 0 to 25 within the ring. A delay around delay Milliseconds follow.
old=new
new +=1
new %= 25
Sleep_ms(delay)
We calculate the new offset, increase the delay time and add the value to the previous status. The method Sleep_ms() only takes full number, so we cut with us intimately() the proportion of breakage.
offset=offset*factor
delay = int(delay+offset)
As soon as delay We have reached or crossed the limit, we break off the loop. This also happens when the flash button is pressed.
if delay >= limit:
break
if taste() == 0:
ledsOff()
exit()
How it works betterThat was a beginning, but not much more either. Depending on the set values for offset and limit If the ball runs too short or too long and then the moment of standstill is difficult to assess. So we work a little on the performance and the architecture of the program. This becomes more complex and therefore it makes sense to outsource the individual parts in functions, which then only have to be called up in the main loop (modularization). It follows rouled2.py, but that is still bound to the ESP32. There are two more methods for the imports, pow() and Timeoutms(). With pow() we calculate potency values, and Timeoutms() creates a non-blocking softwear timer, while the process can also happen.
from machine import Pin
from neopixel import NeoPixel
from time import sleep_us,sleep
from sys import exit
import random
from math import pow
from timeout import TimeOutMs
A status LED will tell us the condition of the system in the future. A single neopixel LED of the strip symbolizes the starting will in green, in blue, that the start button is pressed and in red, that the game runs and nothing can be set. Another green shows the end of the game and the new willingness to take place. Despite the three colors, we only need a control line for the second neopixel object on GPIO12. We also define the color tupes for blue and off and move odd and even here.
np=Pin(14, Pin.OUT)
nbrOfLeds=25
n=NeoPixel(np, nbrOfLeds)
num=1
statePin=Pin(12, Pin.OUT)
state=NeoPixel(statePin,num)
red=(0x20,0,0)
green=(0,0x20,0)
blue=(0,0,0x20)
off=(0,0,0)
odd=(0x80,0,0)
even=(0,0,0x80)
At GPIO13 we connect the button to start a new run, because it is bad if you have to restart the program for every game round. It wouldn't work without a PC anyway.
taste=Pin(0,Pin.IN,Pin.PULL_UP)
start=Pin(13,Pin.IN,Pin.PULL_UP)
The function set() schickt das übergebene Farb-Tupel an die Status-LED.
def setState(col):
state[0]=col
state.write()
def ledsOff():
for i in range(nbr):
n[i]=(0,0,0)
n.write()
I have improved the strategy for a more fluid expiry of the rounds for the ball. The croupier (game master) should be given the opportunity to start the ball with more or less strength. He does this By pressing the start button for more or less long. The philosophy behind the new approach also aims at a more fluid gameplay. The function trout() Now only determines the number of entire rounds and has nothing to do with the random selection of the target position. It is only controlled in the very last round.
8 full rounds are presented By default if no other value is handed over when the function is started. As a minimum we put in Val the value 2. Then we are waiting for the start of the start button, the condition of which is queried every 0.2 second. In this loop we also take the demolition button into account. If it is pressed, the LEDs run out, the status LED also before the program ends.
Without detraction, we switch off the status LED on blue and the LEDs in the strip. After 0.2 seconds we start the non -blocking software timer. The expiry time results from the red -reduced round maximum multiplied By half the clock frequency of the timer. The timer itself is one Closure. You can find more information in the linked article. The essentials are that Timeoutms() the reference to a function compare() returns that is declared to your functional body. We remember this reference under the identifier done. done() gives False back as long as they Timeoutms() handed over time has not yet expired in milliseconds. Then the return value is true.
def getRounds(rounds=8):
val=2
while start() != 0:
sleep(0.2)
if taste() == 0:
ledsOff()
setState(off)
exit()
setState(blue)
ledsOff()
sleep(0.2) # Starttaste entprellen
done=TimeOutMs((rounds-2)*500)
while not done():
Val += 1
sleep(0.5)
IF start():
set(off)
return Val
set(off)
return random.edge(3,8)
At the beginning of the While loop, we ask the return value. Val Now we increase at a distance of half a second until the start button is released. Then we switch off the status LED and give the round value in Val back.
If the timer expired before the start button is released, the status LED is also switched off and a random value of 3 to 7 is returned for the number of rounds.
I also took the modeling of the spherical movement out of the main loop and to the function go() packaged. Der Segmentzähler wird auf 0 gesetzt und der erste Verzögerungswert in Millisekunden berechnet. Because something always has 1 as a result, I would have delay can also put on 1 right away. Almost nothing has been changed on the next lines, only the zero is now instead of white, green light and the 25 was through the constant NBROFLEDS replaced. This is clearer for program maintenance, because the value only has to be maintained in the program at a single position and thus avoided redundancies.
def go():
segment=0
delay=int(pow(basis,segment))
print(rounds, layers, basis, delay)
old=0
new=0
while 1:
n[old]=(0,0,0)
n[new]=odd IF new % 2 Else even
IF new == 0:
n[new]=(0,0xc0,0)
n.write()
old=new
new +=1
new %= nbrOfLeds
sleep_us(delay)
segment+=1
delay=startDelay+int(pow(basis,segment))
IF segment >= layers:
break
if taste() == 0:
ledsOff()
break
The calculation of the delay time was revised. We are now hanging directly from segment to segment and calculating with the segment number segment As an exponent to the base base directly the delay time delay in microse customers taking into account the minimal delay of starting delay MS. Wir erreichen damit eine bessere Auflösung und einen realistischeren Kugellauf, weil wir schon vor Ablauf der Spielzeit die Anzahl der Rundendurchläufe kennen. In the global variables layers If the maximum number of segments to be continued. Once the value has been reached, the While loop and thus the function are left. This is also possible at any time using the demolition button.
We rely on the maximum delay time at 800, 000 µs = 800ms and the minimum delay on 20ms. With these values we can model the ball run. Hart signal on green, we can get started.
load=800000 # US
starting delay=20000
set(green)
The Main Loop now contains only the essential areas, summarized in functional calls.
round is composed of all the rounds that we have with trout() Determine plus a break in a break, which we By calling up random.random() determine as a random part. We receive the total number of segments to go through layers Based on the product value round And the number of LEDs in the strip. Out of round = 4, 21358, for example layers = 105.
The exponential function with which we calculate the delay has the following form. starting delay is specified in the program and segment is the running variable. So there is still no value for base.
illustration 7: So we calculate the delay
We get this through the maximum delay time load and dissolve the functional equation after base.
illustration 8: The value for the basis varies from case to case
The baseValue we have to recalculate with every game run. Then the status LED goes to red and the run of the ball can start, go()!
while 1:
round= trout() + random.random()
layers=int(rounds*nbrOfLeds)
basis=pow(lastDelay-startDelay,1/layers)
setState(red)
go()
setState(green)
if taste() == 0:
ledsOff()
setState(off)
exit()
After returning, we give the green light for a new game round if we don't stop the program.
For the ESP32 we could now live well with the result.
Question to Radio Eriwan:
Can you the program rouled2.py also for the ESP8266 and the Raspberry Pi Pico Get to run?
Answer:
In principle yes, you just have to have the differences in the Micropython dialects of ESP32, ESP8266 and Raspberry Pi Pico eliminate.
And what do they look like? The answer is the Object Inspector from Thonny. The module random Provides different methods for the three controller families.
ESP32:
illustration 9: Methods of the chocolate module at the ESP32
illustration 10: Methods of the module Random at the ESP8266
illustration 11: Methods of the module Random at the RPP
So the problem arises that the micropython dialects for the three families deviate from each other, especially when creating pseudo-infusions. We get this problem under control if we have already carried out an automatic detection of the controller used. This succeeds with the attribute platform From the module sys. While ESP32 and Raspberry Pi Pico We have to largely offer the same methods, we have to help with the ESP8266. For the ESP8266, we simply define the two missing methods as functions. random(), as with ESP32 and at the ESP32 Raspberry Pi Pico, deliver a flow of flow from 0 to 0.9999... edge() an integer in a given area. beditory(n) delivers an integer whose value is required up to n bits in binary representation.
From machine import Pin
From neopixel import Neopixel
From time import Sleep_us,sleep
From sys import exit, platform
import random
From math import pow
From time-out import Timeoutms
IF platform == "ESP32":
From random import random, edge
elif platform == "ESP8266":
From random import beditory
def random():
return getrandbits(24)/(2**24)
def randint(von,bis):
return von+int(getrandbits(24)*(bis-von)/(2**24))
elif platform == "rp2":
from random import random, randint
else:
print("Not supported controller")
exit()
Two positions must now be adjusted in the program.
def getRounds(rounds=8):
val=2
while start() != 0:
sleep(0.2)
if taste() == 0:
ledsOff()
setState(off)
exit()
setState(blue)
ledsOff()
sleep(0.2) # Starttaste entprellen
done=TimeOutMs((rounds-2)*500)
while not done():
val += 1
sleep(0.5)
if start():
setState(off)
return val
setState(off)
return randint(3,8) #
<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
while 1:
rounds= getRounds() + random() #
<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
layers=int(rounds*nbrOfLeds)
basis=pow(lastDelay-startDelay,1/layers)
setState(red)
go()
setState(green)
IF button() == 0:
LEDsoff()
setState(off)
exit()
The extended program is now under rouled3.py Saved and runs on all three controller families without further adjustments.
So that this works without a USB connection to the PC, we have to use the program under the name main.py Upload it into the flash of the controller. The controller starts autonomously at the next restart or reset.
Comments