7 minute(s) de lecture

PybStickRP2040 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

PybStickRP2040 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).

PybStickRP2040

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

PybStickRP2040 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.