PybStick RP2040
J’ai reçu une nouvelle version de la PYBSTICK de Garatronic. Cette fois-ci elle est équipée du même microcontrôleur qui équipe les Raspberry PICO: un RP2040. Il s’agit d’une version made in France commercialisée via le shop MCHOBBY. J’avais eu l’occasion de réaliser quelques projets intéressants avec la PYBSTICK équipée d’un SMT32. Puisqu’en ce moment je travaille sur des contrôles de moteurs pas à pas et des animations de bande de leds, voici quelques adaptations et idées qui trouveront leur place dans de nombreux projets.
Hello World
Tout d’abord la configuration se passe exactement comme avec un Raspberry PICO. Je réalise tous mes projets en micropython sur ce type de microcontrôleur: il faut y déposer la dernière version du fichier UF2 fourni par Raspberry et installer Thonny sur son ordinateur pour y préparer et tester les scripts micropython.
On peut commencer par faire clignoter les 4 petites leds (bleue, orange, verte, rouge) intégrées à la carte avec ce script, elles s’arrêteront lors d’un appui sur le bouton USR:
from machine import Pin
import utime
print('Hello PYBSTICK RP2040 World')
leds = [ Pin(15, Pin.OUT), #BLUE led
Pin(14, Pin.OUT), #ORANGE led
Pin(25, Pin.OUT), #GREEN led
Pin(23, Pin.OUT)] #RED led
sw = Pin( 4, Pin.IN ) # USR button
blink = True
while blink:
for led in leds: # parcours des leds
led.value(1) # allume la led
utime.sleep(0.1) # attente 0.1 seconde
led.value(0) # éteint la led
if sw.value(): # bouton user pressé
blink = False
# éteint toutes les leds
for led in leds:
led.value(0)
print('bye!')
Commande d’un moteur pas à pas
Pour commander un moteur pas à pas, la technique est exactement la même qu’avec un PICO. J’ai à peine adapté ma bibliothèque puisqu’il faut tenir compte des 4 PIN utilisées (PIN0, 1, 2 et 3) qui commandent les entrées IN1 à IN4 du driver LN289N. J’en ai profité pour faire clignoter les 4 petites leds selon la séquences des 4 entrées IN1 à IN4.
Le câblage à faire est le suivant (attention la moindre erreur et le moteur ne va rien faire d’autre que vibrer au lieu de tourner correctement).
Créez le script suivant, nommez-le bipolarStepper.py
from machine import Pin
import utime
class BipolarStepper():
# constructor
def __init__(self, speed='high', direction='forward', steps360=200):
''' constructor
speed = 'high' (default), 'medium', 'low', 'test'
direction = 'forward' (default), 'backward'
steps360 (200 default): nb of steps to rotate 360°
'''
#speed motor: delays between 2 steps
self.speed = {'high': 0.005, # 5ms delay between 2 steps
'medium': 0.008, # 8ms delay between 2 steps
'low': 0.016, # 20ms delay between 2 steps
'test':0.5, # 0.5s delay between 2 steps for testing activity
}
self.set_speed(speed)
#direction forward or backward
self.dic_direction = { 'forward':1, 'backward':-1 }
self.set_direction(direction)
self.steps360 = steps360
#Raspberry PICO pins output
self.pins = [
Pin(0, Pin.OUT), #IN1 - A - BLACK
Pin(1, Pin.OUT), #IN2 - A\ - GREEN
Pin(3, Pin.OUT), #IN3 - B - RED
Pin(2, Pin.OUT)] #IN4 - B\ - BLUE
self.pinLeds = [
Pin(15, Pin.OUT), #blue
Pin(14, Pin.OUT), #orange
Pin(25, Pin.OUT), #green
Pin(23, Pin.OUT)] #red
# sequences to run in circle
self.full_step_seq = [
[1,0,1,0], # AB
[0,1,1,0], # A/B
[0,1,0,1], # A/B/
[1,0,0,1]] # AB/
self.seq = 0 # current sequence: 0,1,2,3
#init motor with first position seq 0
print('init motor')
self.move_motor()
utime.sleep(0.5)
# Private methods
# -------------------------------------------------
def move_motor(self):
''' move motor to curent step and wait
'''
#run current sequence
for i in range(4):
self.pins[i].value(self.full_step_seq[self.seq][i])
self.pinLeds[i].value(self.full_step_seq[self.seq][i])
utime.sleep(self.delay)
# Public methods
# ---------------------------------------------------------------
def set_speed(self, speed):
''' motor speed: 'high' (default), 'medium', 'low' or 'test'
'''
try:
self.delay = self.speed[speed]
except:
self.delay = self.speed['high']
def set_direction(self, direction):
''' set direction either forward (default) or backward
'''
try:
self.direction = self.dic_direction[direction]
except:
self.direction = self.dic_direction['forward']
def next_steps(self, nsteps=1):
''' move motor to the next sequence * nsteps
'''
for i in range(nsteps):
self.seq = (self.seq + self.direction)%4
self.move_motor()
def next_angle(self, angle=90):
''' move motor nsteps = steps360*angle//360 '''
self.next_steps(self.steps360*angle//360)
def split_steps(self, nsplits=1):
''' split steps360 into a list of nsplits equivalent steps
'''
l = nsplits*[self.steps360//nsplits] # list of nsplits equal steps
r = self.steps360%nsplits # add this value, so that sum of steps = steps360
for i in range(r):
l[-1-i]+= 1 #r value added from the end of the list
return l
Créez ce second script qui utilise la bibliothèque bipolarStepper.py et nommez le testStepper.py:
from machine import Pin
import utime
from bipolarStepper import BipolarStepper
bp = BipolarStepper(speed='test')
bp.next_steps(15)
bp = BipolarStepper(speed='medium')
bp.next_steps(100)
utime.sleep(0.5)
bp.set_speed('high')
bp.set_direction('backward')
bp.next_steps(50)
Lors de l’exécution, le moteur va d’abord s’initialiser (il peut bouger d’un cran à ce moment puisqu’il va se caler sur une position), ensuite il va touner 15 fois à très faible allure de test: on peut voir les leds s’allumer en fonction des séquences envoyées dans IN1 à IN4. Ensuite il tourne de 100 pas à allure moyenne, puis de 50 pas en sens inverse à vive allure.
Rubans de leds RGB
Toujours très sympa ces rubans de leds RGB2812 et d’une grande facilité à utiliser avec la bibliothèque adéquate: j’ai réutilisé la même que j’utilise avec un PICO et ça fonctionne très bien. Le câblage est très simple. le PIN 0 va commander le ruban, ne pas oublier de relier les masses entre elles et d’utiliser une source externe 5V pour alimenter le RUBAN.
Bibliothèque à utiliser: nommez la neopixel.py
import array, time, rp2
from machine import Pin
@rp2.asm_pio(sideset_init=rp2.PIO.OUT_LOW, out_shiftdir=rp2.PIO.SHIFT_LEFT, autopull=True, pull_thresh=24)
def ws2812():
T1 = 2
T2 = 5
T3 = 3
wrap_target()
label("bitloop")
out(x, 1) .side(0) [T3 - 1]
jmp(not_x, "do_zero") .side(1) [T1 - 1]
jmp("bitloop") .side(1) [T2 - 1]
label("do_zero")
nop() .side(0) [T2 - 1]
wrap()
class LedsRGBws2812:
''' Neopixel RGB WS2812 Class
instance: leds = LedsRGBws2812() or LedsRGBws2812(n_leds, pin_num, brightness)
'''
def __init__(self, n_leds=16, pin_num=0, brightness=0.2):
''' constructor: Configure the WS2812 LEDs. '''
print('init leds RGB')
self.NUM_LEDS = n_leds
self.PIN_NUM = pin_num
self.brightness = brightness
self.BLACK = (0, 0, 0)
self.RED = (255, 0, 0)
self.YELLOW = (255, 150, 0)
self.GREEN = (0, 255, 0)
self.CYAN = (0, 255, 255)
self.BLUE = (0, 0, 255)
self.PURPLE = (180, 0, 255)
self.WHITE = (255, 255, 255)
self.COLORS = (self.BLACK, self.RED, self.YELLOW, self.GREEN, self.CYAN, self.BLUE, self.PURPLE, self.WHITE)
# Create the StateMachine with the ws2812 program, outputting on pin
self.sm = rp2.StateMachine(0, ws2812, freq=8_000_000, sideset_base=Pin(self.PIN_NUM))
# Start the StateMachine, it will wait for data on its FIFO.
self.sm.active(1)
# Display a pattern on the LEDs via an array of LED RGB values.
self.ar = array.array("I", [0 for _ in range(self.NUM_LEDS)])
def pixels_show(self):
''' add data into sm FIFO: show de pixels '''
dimmer_ar = array.array("I", [0 for _ in range(self.NUM_LEDS)])
for i,c in enumerate(self.ar):
r = int(((c >> 8) & 0xFF) * self.brightness)
g = int(((c >> 16) & 0xFF) * self.brightness)
b = int((c & 0xFF) * self.brightness)
dimmer_ar[i] = (g<<16) + (r<<8) + b
self.sm.put(dimmer_ar, 8)
time.sleep_ms(10)
def pixels_set(self, i, color):
''' set pixel number i with color=(r,g,b) '''
self.ar[i] = (color[1]<<16) + (color[0]<<8) + color[2]
def pixels_fill(self, color):
''' set all pixels with same color (r,g,b) '''
for i in range(len(self.ar)):
self.pixels_set(i, color)
def pixels_off(self):
self.pixels_fill(self.BLACK)
self.pixels_show()
def color_chase(self, color, wait):
''' color(r,g,b) grow from pixel 0 to the end.'''
for i in range(self.NUM_LEDS):
self.pixels_set(i, color)
time.sleep(wait)
self.pixels_show()
time.sleep(0.2)
def wheel(self, pos):
''' Input a value 0 to 255 to get a color value (r,g,b).
The colours are a transition r - g - b - back to r.
'''
if pos < 0 or pos > 255:
return (0, 0, 0)
if pos < 85:
return (255 - pos * 3, pos * 3, 0)
if pos < 170:
pos -= 85
return (0, 255 - pos * 3, pos * 3)
pos -= 170
return (pos * 3, 0, 255 - pos * 3)
def rainbow_cycle(self, wait=0):
''' move colors in a rainbow cycle '''
for j in range(255):
for i in range(self.NUM_LEDS):
rc_index = (i * 256 // self.NUM_LEDS) + j
self.pixels_set(i, self.wheel(rc_index & 255))
self.pixels_show()
time.sleep(wait)
def fade_out(self, n_steps, color, wait):
''' fade out color(r,g,b) to BLACK in n_steps '''
r_delta, g_delta, b_delta = color[0]//n_steps, color[1]//n_steps, color[2]//n_steps
for j in range(n_steps):
self.pixels_fill( (color[0]-j*r_delta , color[1]-j*g_delta , color[2]-j*b_delta) )
self.pixels_show()
time.sleep(wait)
self.pixels_off()
time.sleep(wait)
def fade_in(self, n_steps, color, wait):
''' fade in from BLACK to color(r,g,b) in n_steps '''
r_delta, g_delta, b_delta = color[0]//n_steps, color[1]//n_steps, color[2]//n_steps
for j in range(n_steps, 0, -1 ):
self.pixels_fill( (color[0]-j*r_delta , color[1]-j*g_delta , color[2]-j*b_delta) )
self.pixels_show()
time.sleep(wait)
self.pixels_off()
time.sleep(wait)
Le script ci-dessous va alors animer un effet arc-en-ciel du plus bel effet:
import time
from neopixel import LedsRGBws2812
strip = LedsRGBws2812(n_leds=32)
while(True):
strip.rainbow_cycle()
Le tarif a certes du mal à concurrencer celui d’un Raspberry PICO mais il faut comparer ce qui est comparable: on a ici une carte made in France à un prix vraiment canon pour du projet maker, elle a un encombrement plus réduit (on peut la commander sans l’USB soudé), elle dispose de 4 leds et 2 boutons poussoirs programmables, les entrées sont toutes déjà équipées de pull-up (pas besoin de rajouter des résistances, elles y sont déjà !), et l’alimentation tolère 18v en entrée. J’en suis très ravi elle va m’accompagner dans de nombreux projets.