Quintilien

Quis, quid, ubi, quibus auxiliis, cur, quomodo, quando

Informatique, électronique et train miniature ...

(Dernière mise à jour : 2 octobre 2023)   •   Retour à l'accueil

Sur le même sujet, ne manquez pas la page détection de la position d'un train miniature.

Vous trouverez également d'autres liens en bas de cette page, dans la rubrique pour en savoir plus.

 

Vue d'ensemble

Dans un souci de réalisme, un joystick est utilisé.

Il est censé simuler

  • l'accélération progressive par application d'une certaine puissance
  • le freinage plus ou moins intense suivant les circonstances
  • la sélection de la marche avant ou de la marche arrière

Composants et schéma de raccordement

Réalisation pratique

Quelques précautions :

  • veillez à bien enlever les « jumpers » sur le contrôleur DC (4 + 1 = 5 au total : cf schéma ci-dessus) ;
  • l'afficheur Olimex utilisé doit être alimenté en 3.3 V, de même que le joystick.

Code Micropython (main.py)

# Copyright (C) 2018-2023 PhS & Quintilien
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details.
#
#
# Rev. 2.12 - 23 sept. 2023 
#

from machine import Pin, ADC, Timer, I2C, UART, PWM
import time
import ssd1306


# pwm
pwm_ena = PWM(machine.Pin(18))
pwm_ena.freq(500)
pwm_in1 = machine.Pin(17, Pin.OUT)
pwm_in2 = machine.Pin(16, Pin.OUT)


# joystick (on 3,3 V !)
adc0 = ADC(Pin(26, mode=Pin.IN))              # joystick X
adc1 = ADC(Pin(27, mode=Pin.IN))              # joystick Y
adc2 = Pin(28, Pin.IN, Pin.PULL_UP)           # joystick switch  


# i2C
i2c = I2C(0, sda=machine.Pin(8), scl=machine.Pin(9), freq=400000)
print ('i2C found : ',i2c.scan())
lcd = ssd1306.SSD1306_I2C( 128, 64, i2c )

lcd.text("Ready", 0,0,1)   # column, ligne * 10, 1
lcd.show()  # Display!


# matrix for big display
big_num = [
    [
        [ 0,1,1,1,0 ] , 
        [ 1,0,0,0,1 ] , 
        [ 1,0,0,0,1 ] , 
        [ 1,0,0,0,1 ] , 
        [ 1,0,0,0,1 ] , 
        [ 1,0,0,0,1 ] , 
        [ 1,0,0,0,1 ] , 
        [ 0,1,1,1,0 ]
    ] ,
    [
        [ 0,0,1,0,0 ] , 
        [ 0,1,1,0,0 ] , 
        [ 1,0,1,0,0 ] , 
        [ 0,0,1,0,0 ] , 
        [ 0,0,1,0,0 ] , 
        [ 0,0,1,0,0 ] , 
        [ 0,0,1,0,0 ] , 
        [ 1,1,1,1,1 ]
    ] ,
    [
        [ 0,1,1,1,0 ] , 
        [ 1,0,0,0,1 ] , 
        [ 1,0,0,0,1 ] , 
        [ 0,0,0,1,0 ] , 
        [ 0,0,1,0,0 ] , 
        [ 0,1,0,0,0 ] , 
        [ 1,0,0,0,0 ] , 
        [ 1,1,1,1,1 ]
    ] ,
    [
        [ 0,1,1,1,0 ] , 
        [ 1,0,0,0,1 ] , 
        [ 0,0,0,0,1 ] , 
        [ 0,0,1,1,0 ] , 
        [ 0,0,0,0,1 ] , 
        [ 0,0,0,0,1 ] , 
        [ 1,0,0,0,1 ] , 
        [ 0,1,1,1,0 ]  
    ] ,
    [
        [ 1,0,0,0,1 ] , 
        [ 1,0,0,0,1 ] , 
        [ 1,0,0,0,1 ] , 
        [ 1,0,0,0,1 ] , 
        [ 0,1,1,1,1 ] , 
        [ 0,0,0,0,1 ] , 
        [ 0,0,0,0,1 ] , 
        [ 0,0,0,0,1 ]  
    ] ,
    [
        [ 1,1,1,1,1 ] , 
        [ 1,0,0,0,0 ] , 
        [ 1,0,0,0,0 ] , 
        [ 1,1,1,1,0 ] , 
        [ 0,0,0,0,1 ] , 
        [ 0,0,0,0,1 ] , 
        [ 1,0,0,0,1 ] , 
        [ 0,1,1,1,0 ]  
    ] ,
    [
        [ 0,1,1,1,0 ] , 
        [ 1,0,0,0,1 ] , 
        [ 1,0,0,0,0 ] , 
        [ 1,1,1,1,0 ] , 
        [ 1,0,0,0,1 ] , 
        [ 1,0,0,0,1 ] , 
        [ 1,0,0,0,1 ] , 
        [ 0,1,1,1,0 ]  
    ] ,
    [
        [ 1,1,1,1,1 ] , 
        [ 1,0,0,0,1 ] , 
        [ 0,0,0,0,1 ] , 
        [ 0,0,0,1,0 ] , 
        [ 0,0,1,0,0 ] , 
        [ 0,1,0,0,0 ] , 
        [ 0,1,0,0,0 ] , 
        [ 0,1,0,0,0 ]  
    ] ,
    [
        [ 0,1,1,1,0 ] , 
        [ 1,0,0,0,1 ] , 
        [ 1,0,0,0,1 ] , 
        [ 0,1,1,1,0 ] , 
        [ 1,0,0,0,1 ] , 
        [ 1,0,0,0,1 ] , 
        [ 1,0,0,0,1 ] , 
        [ 0,1,1,1,0 ]  
    ] ,
    [
        [ 0,1,1,1,0 ] , 
        [ 1,0,0,0,1 ] , 
        [ 1,0,0,0,1 ] , 
        [ 1,0,0,0,1 ] , 
        [ 0,1,1,1,1 ] , 
        [ 0,0,0,0,1 ] , 
        [ 1,0,0,0,1 ] , 
        [ 0,1,1,1,0 ]  
    ]
    ]

def display_big_numbers(v):
    v2 = list()
    for tmp in (str(v)):
        v2.append(int(tmp))
    v2.reverse()    
    # fill_rect( x, y, w, h, c ) 
    lcd.fill_rect( 10,20,118,40, 0 )

    for p, n in enumerate ( v2 ): # p = position, n = digit to display
        for l in range (8):
            for c in range(5):
                if big_num[n][l][c] == 1:
                    lcd.fill_rect( c*5+(4-p)*30-20,l*5+20, 5, 5, 1 )
    lcd.show()  


tictac = 1

def running(x):
    global tictac
    if tictac == 1:      
        lcd.fill_rect( 0,59,4,4,1 ) # fill_rect( x, y, w, h, c )
    else:    
        lcd.fill_rect( 0,59,4,4,0 ) # fill_rect( x, y, w, h, c )
    tictac = tictac * (-1)
    lcd.show()


def set_speed(number=0, speed=0, acceleration=10, deceleration=12, direction=0):
    global vc, di, vt, ac, dc, dt
    vt[number] = speed
    if direction != 0:
        dt[number] = direction
    if vc[number] < speed:
        ac[number] = acceleration
        dc[number] = 0
    if vc[number] > speed:    
        ac[number] = 0
        dc[number] = deceleration


def set_pwm():
    global vc, dac, ddc, vt, ac, dc, di, dt, m1, m2, vp, cja, cjd
    global ch 
    global pwm_ena, pwm_in1, pwm_in2

    if di[ch] == 0:
        pwm_in1.value(0)
        pwm_in2.value(0)
    if di[ch] == 1:
        pwm_in1.value(1)
        pwm_in2.value(0)          
    if di[ch] == -1:
        pwm_in1.value(0)
        pwm_in2.value(1)

    if vc[ch] == 0:
        pwm_ena.duty_u16( 0 )
    else:         
        pwm_ena.duty_u16( ( vc[ch] + 1 ) * 64 - 1 )


def display_ch():
    global di, ch, lcd
    lcd.fill_rect( 110,0,20,10,0 ) # fill_rect( x, y, w, h, c )
    if di[ch] == 0: 
        lcd.text("--", 110,1,1)   # column, line * 10, 1
    if di[ch] == 1: 
        lcd.text("->", 110,1,1)   # column, line * 10, 1
    if di[ch] == -1: 
        lcd.text("<-", 110,1,1)   # column, line * 10, 1


# speed control : settings for freq(PWM) = 500 Hz 
# Code designed to manage 3 locomotives 

ch = 0   # Loco 0 selected

vp      = [0, 0, 0]              # previous speed
vt      = [0, 0, 0]              # target speed
ac      = [0, 0, 0]              # acceleration
dc      = [0, 0, 0]              # deceleration
vc      = [0, 0, 0]              # current speed
di      = [1, 1, 1]              # current direction
dt      = [1, 1, 1]              # target direction

m1      = [250, 250, 250]        # minimum pulse threshold when accelerate
m2      = [250, 250, 250]        # minimum pulse threshold when decelerate
dac     = [10, 10, 10]           # default acceleration
ddc     = [18, 18, 18]           # default deceleration
cja     = [128, 256, 256]        # coeff acceleration joystick 
cjd     = [64, 64, 64]           # coeff deceleration joystick


def speed_control(x):
    global lcd
    global vc, dac, ddc, vt, ac, dc, di, dt, m1, m2, vp, cja, cjd

    jy = int ( ( 32767 - adc1.read_u16() ) / 32 )   # y -1023 -> 0 -> 1023
    if jy > 1000:
        jy = 1023
    if jy <= -1000:            
        jy = -1023
    if jy > -100 and jy < 100:
        jy = 0
    # 
    jx = int ( ( 32767 - adc0.read_u16() ) / -32 )    # x -1023 -> 0 -> 1023   
    if jx > 1000:
        jx = 1023
    if jx <= -1000:            
        jx = -1023
    if jx > -300 and jx < 300:
        jx = 0
    # 
    jsw = adc2.value()
    #
    if jx > 750:
        dt[ch] = 1                            # target direction       
    if jx < -750:
        dt[ch] = -1                           # target direction

    jt = 0                                    # joystick target speed
    ja = 0                                    # joystick acceleration
    jd = 0                                    # joystick deceleration

    if jy > 0:
        jt = jy                               # target speed
        ja = int ( jy / cja[ch] )             # acceleration   ( TO BE "FINE TUNED" )
    if jy < 0:
        jd = int ( jy / cjd[ch] * -1 )        # deceleration


    # designed to manage more than one locomotive simultaneously !
    for i, v in enumerate (vc):        
        if di[i] != dt[i]:                    # changing direction -> stop before !
            vc[i] = 0
            vt[i] = 0
            ac[i] = 0
            dc[i] = 0
            di[i] = dt[i]
            set_pwm()
        if di[i] != 0:
            if i == ch:                       # joystick activated
                if v < jt and ja !=0 :        # accelerate with joystick
                    if v + ja > jt:
                        vc[i] = jt
                    else:
                        if vc[i] + ja < m1[i]:
                            vc[i] = m1[i]
                        else:
                            vc[i] = v + ja

                if v > vt[i] and jd !=0 :     # decelerate with joystick
                    if v - jd < jt:
                        vc[i] = jt
                    else:
                        vc[i] = vc[i] - jd
                    if vc[i] < m2[i]:         # if target < minimum pulse  => target = min
                        vc[i] = 0


            if v < vt[i] and ac[i] !=0 :      # accelerate with external instruction
                if v + ac[i] >= vt[i]:
                    vc[i] = vt[i]
                    vt[i] = 0
                    ac[i] = 0
                else:
                    if vc[i] + ac[i] < m1[i]:
                        vc[i] = m1[i]
                    else:
                        vc[i] = vc[i] + ac[i]

            if v > vt[i] and dc[i] !=0 :      # decelerate 
                if v - dc[i] <= vt[i]:
                    vc[i] = vt[i]
                    vt[i] = 0
                    dc[i] = 0
                else:
                    vc[i] = vc[i] - dc[i]
                if vc[i] < m2[i]:             # if target < minimum pulse  => target = min
                    vc[i] = 0
                    vt[i] = 0
                    dc[i] = 0

            if vc[i] != vp[i]:
                vp[i] = vc[i]
                set_pwm()                       


def display_control(x):
    global lcd, ch, sig
    global vc, dac, ddc, vt, ac, dc, di, dt, m1, m2, vp, cja, cjd

    display_ch()

    display_big_numbers(vc[ch])



timer0=Timer()
timer2=Timer()
timer4=Timer()

timer0.init(freq=2,  callback=running)
timer2.init(freq=10, callback=speed_control)   
timer4.init(freq=2,  callback=display_control)


print ('*************************************')
print ('**                                 **') 
print ('**  Test de commande externe       **')
print ('**  (dans ce cas, via le clavier)  **')
print ('**                                 **') 
print ('*************************************')
di[0]  = 1
dt[0]  = 1
# set_speed(number=0, speed=0, acceleration=10, deceleration=12, direction=0)
while 1:
    if dt[0] == 1:
        print ('*** Marche avant ***')
    else:    
        print ('*** Marche arrière ***')
    print ('Taper <enter> pour démarrer et accélérer')
    input()
    set_speed(speed=400, acceleration=10)
    print ('Taper <enter> pour freiner et arrêter')
    input()
    set_speed(speed=0, deceleration=10) 
    print ("Taper <enter> pour recommencer dans l'autre sens")
    input()
    dt[0]  = dt[0] * -1 # target direction
    set_speed(direction=dt[0])

Bibliothèque à importer : ssd1306.py


# MicroPython SSD1306 OLED driver, I2C and SPI interfaces

from micropython import const
import framebuf


# register definitions
SET_CONTRAST = const(0x81)
SET_ENTIRE_ON = const(0xA4)
SET_NORM_INV = const(0xA6)
SET_DISP = const(0xAE)
SET_MEM_ADDR = const(0x20)
SET_COL_ADDR = const(0x21)
SET_PAGE_ADDR = const(0x22)
SET_DISP_START_LINE = const(0x40)
SET_SEG_REMAP = const(0xA0)
SET_MUX_RATIO = const(0xA8)
SET_COM_OUT_DIR = const(0xC0)
SET_DISP_OFFSET = const(0xD3)
SET_COM_PIN_CFG = const(0xDA)
SET_DISP_CLK_DIV = const(0xD5)
SET_PRECHARGE = const(0xD9)
SET_VCOM_DESEL = const(0xDB)
SET_CHARGE_PUMP = const(0x8D)

# Subclassing FrameBuffer provides support for graphics primitives
# http://docs.micropython.org/en/latest/pyboard/library/framebuf.html
class SSD1306(framebuf.FrameBuffer):
    def __init__(self, width, height, external_vcc):
        self.width = width
        self.height = height
        self.external_vcc = external_vcc
        self.pages = self.height // 8
        self.buffer = bytearray(self.pages * self.width)
        super().__init__(self.buffer, self.width, self.height, framebuf.MONO_VLSB)
        self.init_display()

    def init_display(self):
        for cmd in (
            SET_DISP | 0x00,  # off
            # address setting
            SET_MEM_ADDR,
            0x00,  # horizontal
            # resolution and layout
            SET_DISP_START_LINE | 0x00,
            SET_SEG_REMAP | 0x01,  # column addr 127 mapped to SEG0
            SET_MUX_RATIO,
            self.height - 1,
            SET_COM_OUT_DIR | 0x08,  # scan from COM[N] to COM0
            SET_DISP_OFFSET,
            0x00,
            SET_COM_PIN_CFG,
            0x02 if self.width > 2 * self.height else 0x12,
            # timing and driving scheme
            SET_DISP_CLK_DIV,
            0x80,
            SET_PRECHARGE,
            0x22 if self.external_vcc else 0xF1,
            SET_VCOM_DESEL,
            0x30,  # 0.83*Vcc
            # display
            SET_CONTRAST,
            0xFF,  # maximum
            SET_ENTIRE_ON,  # output follows RAM contents
            SET_NORM_INV,  # not inverted
            # charge pump
            SET_CHARGE_PUMP,
            0x10 if self.external_vcc else 0x14,
            SET_DISP | 0x01,
        ):  # on
            self.write_cmd(cmd)
        self.fill(0)
        self.show()

    def poweroff(self):
        self.write_cmd(SET_DISP | 0x00)

    def poweron(self):
        self.write_cmd(SET_DISP | 0x01)

    def contrast(self, contrast):
        self.write_cmd(SET_CONTRAST)
        self.write_cmd(contrast)

    def invert(self, invert):
        self.write_cmd(SET_NORM_INV | (invert & 1))

    def show(self):
        x0 = 0
        x1 = self.width - 1
        if self.width == 64:
            # displays with width of 64 pixels are shifted by 32
            x0 += 32
            x1 += 32
        self.write_cmd(SET_COL_ADDR)
        self.write_cmd(x0)
        self.write_cmd(x1)
        self.write_cmd(SET_PAGE_ADDR)
        self.write_cmd(0)
        self.write_cmd(self.pages - 1)
        self.write_data(self.buffer)


class SSD1306_I2C(SSD1306):
    def __init__(self, width, height, i2c, addr=0x3C, external_vcc=False):
        self.i2c = i2c
        self.addr = addr
        self.temp = bytearray(2)
        self.write_list = [b"\x40", None]  # Co=0, D/C#=1
        super().__init__(width, height, external_vcc)

    def write_cmd(self, cmd):
        self.temp[0] = 0x80  # Co=1, D/C#=0
        self.temp[1] = cmd
        self.i2c.writeto(self.addr, self.temp)

    def write_data(self, buf):
        self.write_list[1] = buf
        self.i2c.writevto(self.addr, self.write_list)


class SSD1306_SPI(SSD1306):
    def __init__(self, width, height, spi, dc, res, cs, external_vcc=False):
        self.rate = 10 * 1024 * 1024
        dc.init(dc.OUT, value=0)
        res.init(res.OUT, value=0)
        cs.init(cs.OUT, value=1)
        self.spi = spi
        self.dc = dc
        self.res = res
        self.cs = cs
        import time

        self.res(1)
        time.sleep_ms(1)
        self.res(0)
        time.sleep_ms(10)
        self.res(1)
        super().__init__(width, height, external_vcc)

    def write_cmd(self, cmd):
        self.spi.init(baudrate=self.rate, polarity=0, phase=0)
        self.cs(1)
        self.dc(0)
        self.cs(0)
        self.spi.write(bytearray([cmd]))
        self.cs(1)

    def write_data(self, buf):
        self.spi.init(baudrate=self.rate, polarity=0, phase=0)
        self.cs(1)
        self.dc(1)
        self.cs(0)
        self.spi.write(buf)
        self.cs(1)

 

Pour en savoir plus


Retourner en début de page