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
- Microcontrôleur Raspberry Pico RP2040
- Régulateur de tension 12V - 5V
- Contrôleur DC L298H
- Joystick
- Afficheur OLED
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
- Détection de la position d'un train miniature
- Comment contrôler la vitesse d'un train miniature : un peu de théorie
- Les cantons
- Quelques vidéos
- Le coin du modéliste