DotMatrixDrucker mit dem Micro:bit

Stellt hier eure Projekte vor.
Internetseiten, Skripte, und alles andere bzgl. Python.
Antworten
Benutzeravatar
kaytec
User
Beiträge: 608
Registriert: Dienstag 13. Februar 2007, 21:57

Hallo,

bin am basteln eines „Dotmatrixdruckers“.
https://en.wikipedia.org/wiki/Dot_matrix

Die ganze Bastelei basiert auf einem Micro:bit, Fischertechnik und dem Motordriverboar
https://www.waveshare.com/wiki/Motor_Dr ... _micro:bit

Ich verwende die normalen Motoren und an diese habe ich „Pappencoderscheiben“ gesteckt und geklebt. Die Position und Laufrichtung lese ich mit Lichtschranken aus und bringe die Werte mit einem CD4093 auf 0 oder 1.
https://strippenstrolch.de/1-2-10-der-s ... -4093.html

So richtig Ahnung habe ich davon nicht, doch habe versucht mich einzulesen, da die Beschaltung der Ein/Ausgänge des Micro:bit/Motorboards nur in bestimmten Wertebereichen möglich ist. Läuft bis jetzt stabil und noch keinen Wohnungsbrand verursacht – solche Basteleien nur unter Beobachtung laufen lassen !

Falls Interesse besteht, mache ich auch ein Video.

Das Modul für das Board habe ich umgeschrieben:

Code: Alles auswählen

import microbit as mb
import utime

class MotorDriverBoard(object):
    MOTOR_A_PWM = mb.pin8
    MOTOR_A_IN_1 = mb.pin13
    MOTOR_A_IN_2 = mb.pin12
    MOTOR_B_PWM = mb.pin16
    MOTOR_B_IN_1 = mb.pin14
    MOTOR_B_IN_2 = mb.pin15
    #SERVO_0 = mb.pin0
    #SERVO_1 = mb.pin1
    SERVO_2 = mb.pin2
    SERVO_PERIOD = 20

    def __init__(self):
        self.motors = {"MOTOR_A": {"PWM": self.MOTOR_A_PWM,
                                   "SPEED": 0,
                                   "DIRECTION": "",
                                   "POSITION": 0,
                                   "PINS": (self.MOTOR_A_IN_1,
                                            self.MOTOR_A_IN_2)},
                       "MOTOR_B": {"PWM": self.MOTOR_B_PWM,
                                   "SPEED": 0,
                                   "DIRECTION": "",
                                   "POSITION": 0,
                                   "PINS": (self.MOTOR_B_IN_1,
                                            self.MOTOR_B_IN_2)}}
        self.servos = {#"SERVO_0": self.SERVO_0}
                       #"SERVO_1": self.SERVO_1,
                       "SERVO_2": self.SERVO_2}
        for servo in self.servos.keys():
            self.servos[servo].set_analog_period(self.SERVO_PERIOD)

    def drive_position(self, motor, new_pos, pin, speed):
        self.set_motor_speed(motor, speed)
        self.set_motor_direction(motor, "forward") \
            if self.motors[motor]["POSITION"] < new_pos \
            else self.set_motor_direction(motor, "backward")
        while new_pos != self.motors[motor]["POSITION"]:
            last_state = pin.read_digital()
            new_state = pin.read_digital()
            while new_state == last_state:
                new_state = pin.read_digital()
            if self.motors[motor]["DIRECTION"] == "forward":
                self.motors[motor]["POSITION"] += 1
            else:
                self.motors[motor]["POSITION"] -= 1
        self.set_motor_speed(motor, 0)

    def set_motor_speed(self, motor, speed):
        if speed >= 1 and speed <= 16:
            self.motors[motor]["PWM"].write_analog(speed * 64 - 1)
            self.motors[motor]["SPEED"] = speed
        elif speed == 0:
            self.motors[motor]["PWM"].write_analog(speed)
            self.motors[motor]["SPEED"] = speed
        else:
            raise RuntimeError("Error: {0} -> wrong speed -> {1}".format(
                               motor, speed))

    def start_soft_motor(self, motor, end_speed, time_step=100):
        for speed in range(0, end_speed):
            self.set_motor_speed(motor, speed)
            utime.sleep_ms(time_step)

    def stop_soft_motor(self, motor, time_step=100):
        for speed in range(self.motors[motor]["SPEED"], 0, -1):
            self.set_motor_speed(motor, speed)
            utime.sleep_ms(time_step)
        self.motors[motor]["PWM"].write_analog(0)
        self.motors[motor]["SPEED"] = 0

    def set_motor_direction(self, motor, direction):
        directions = {"forward": (0, 1),
                      "backward": (1, 0)}
        if direction in list(key for key in directions):
            for pin, state in zip(self.motors[motor]["PINS"],
                                  directions[direction]):
                pin.write_digital(state)
            self.motors[motor]["DIRECTION"] = direction

    def change_motor_direction(self, motor):
        self.set_motor_direction(motor, "backward") \
            if self.motors[motor]["DIRECTION"] == "forward" \
            else self.set_motor_direction(motor, "forward")

    def turn_servo(self, servo, angle):
        if angle >= 0 and angle <= 180:
            self.servos[servo].write_analog(angle / 2 + 25)
        else:
            raise RuntimeError("Error: {0} -> wrong angle -> {1}".format(
                               servo, angle))

    def turn_servo_off(self, servo):
        self.servos[servo].write_analog(0)
Das Script für den Drucker:

Code: Alles auswählen

import microbit as mb
import utime
from MotorDriverBoard import MotorDriverBoard

class DotMatrixPrinter(object):
    ENCODER_PIN_X = mb.pin0
    SPEED_X = 10
    STEP_X = 7
    ENCODER_PIN_Y = mb.pin1
    SPEED_Y = 10
    def __init__(self):
        self.motor_board = MotorDriverBoard()
        
    def print_image(self):
        with open("print_job.txt") as print_job:
            dot = True
            while dot:
                dot = print_job.readline()
                x, y = dot.rstrip().split("|")
                self.motor_board.drive_position("MOTOR_A", int(x) * self.STEP_X,
                                                self.ENCODER_PIN_X,
                                                self.SPEED_X)
                self.motor_board.drive_position("MOTOR_B", int(y),
                                                self.ENCODER_PIN_Y,
                                                self.SPEED_Y)
                self.motor_board.set_motor_speed("MOTOR_B", 0)
                self.motor_board.turn_servo("SERVO_2", 30)
                utime.sleep_ms(200)
                self.motor_board.turn_servo("SERVO_2", 0)
                utime.sleep_ms(200)
        self.motor_board.turn_servo_off("SERVO_2")
        
printer = DotMatrixPrinter()
printer.print_image()
Die Positionsangaben für den Druckkopf kommen aus einer Textdatei, die ich mittels dieses Scriptes erzeuge:

Code: Alles auswählen

#! /usr/bin/env python
# -*- coding: utf-8
from PIL import Image, ImageDraw
import cv2
import numpy as np
img_name = "audrey_2.png"
image = Image.open(img_name)
resolution = 170
print_width = 85
show = True
resize_factor_show_img = 5
dot_width = 4
y = 0
x = 0
new_img_width = int(image.size[0] / (image.size[1 if image.size[1] 
                    > image.size[0] else 0] / print_width))
new_img_height = int(image.size[1] / (image.size[1 if image.size[1] 
                    > image.size[0] else 0] / print_width))

with open("print_job.txt","w") as print_job:
    for i, pix in enumerate(Image.frombytes('L',
                           (
                            image.width,
                            image.height
                            ),
                            cv2.blur(
                            cv2.adaptiveThreshold(
                            cv2.cvtColor(
                            np.asarray(image), cv2.COLOR_BGR2GRAY
                            ),
                            255,
                            cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
                            cv2.THRESH_BINARY,
                            63,
                            5,
                            ),
                            (5, 
                            5
                            )),
                            'raw',
                            ).resize(
                            (new_img_width, 
                            new_img_height 
                            )).getdata()):
        if pix < resolution:
            print_job.write("{0}|{1}\n".format(y, x))
        if i % new_img_width == new_img_width - 1:
            x = 0
            y += 1
        else:
            x += 1
            
print_job_img = Image.new("P", (new_img_width * resize_factor_show_img, 
    new_img_height * resize_factor_show_img), color="white")
draw_img = ImageDraw.Draw(print_job_img)

if show:
    with open("print_job.txt") as print_job:
        for dot in print_job.readlines():
            y, x = dot.split("|")
            draw_img.ellipse([int(x) * resize_factor_show_img,
                          int(y) * resize_factor_show_img, 
                          int(x) * resize_factor_show_img + dot_width, 
                          int(y) * resize_factor_show_img + dot_width], 
                          fill="red")
    print_job_img.show()
    print_job_img.save("{0}_{1}.{2}".format(img_name.split(".")[0], "print_job", "png"))
print_width ist die Laufweite des Druckers und fast quadratisch - so auch nur ein Wertbereich. Ich nehme normale Bilder (Porträt) und den Hintergrund färbe ich weiß ein (langt Konturen mit dem Stift umfahren). Mit show=True kann das Ergebnis angeschaut werden und der Druck sieht genauso aus.

Gruß Frank
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

In `MotorDriverBoard.__init__`, wenn Du die Werte eines Wörterbuchs brauchst dann iteriere doch nicht über die Keys.
In `drive_position` sind if-else-Ausdrücke nicht als Ersatz für richtiges if gedacht, hier steht sowieso zu viel kopierter Code.
In `start_soft_motor` wird der end_speed gar nicht erreicht.
In `set_motor_direction` steht ein besonders kreativer Weg, um zu testen, ob ein Schlüssel in einem Wörterbuch ist.

Code: Alles auswählen

import microbit as mb
import utime

class MotorDriverBoard(object):
    MOTOR_A_PWM = mb.pin8
    MOTOR_A_IN_1 = mb.pin13
    MOTOR_A_IN_2 = mb.pin12
    MOTOR_B_PWM = mb.pin16
    MOTOR_B_IN_1 = mb.pin14
    MOTOR_B_IN_2 = mb.pin15
    #SERVO_0 = mb.pin0
    #SERVO_1 = mb.pin1
    SERVO_2 = mb.pin2
    SERVO_PERIOD = 20
    FORWARD = "forward"
    BACKWARD = "backward"

    def __init__(self):
        self.motors = {"MOTOR_A": {"PWM": self.MOTOR_A_PWM,
                                   "SPEED": 0,
                                   "DIRECTION": "",
                                   "POSITION": 0,
                                   "PINS": (self.MOTOR_A_IN_1,
                                            self.MOTOR_A_IN_2)},
                       "MOTOR_B": {"PWM": self.MOTOR_B_PWM,
                                   "SPEED": 0,
                                   "DIRECTION": "",
                                   "POSITION": 0,
                                   "PINS": (self.MOTOR_B_IN_1,
                                            self.MOTOR_B_IN_2)}}
        self.servos = {#"SERVO_0": self.SERVO_0}
                       #"SERVO_1": self.SERVO_1,
                       "SERVO_2": self.SERVO_2}
        for servo in self.servos.values():
            servo.set_analog_period(self.SERVO_PERIOD)

    def drive_position(self, motor, new_pos, pin, speed):
        self.set_motor_direction(motor, FORWARD
            if self.motors[motor]["POSITION"] < new_pos else BACKWARD)
        self.set_motor_speed(motor, speed)
        while new_pos != self.motors[motor]["POSITION"]:
            new_state = last_state = pin.read_digital()
            while new_state == last_state:
                new_state = pin.read_digital()
            self.motors[motor]["POSITION"] += (1
                if self.motors[motor]["DIRECTION"] == FORWARD else -1)
        self.set_motor_speed(motor, 0)

    def set_motor_speed(self, motor, speed):
        if speed >= 1 and speed <= 16:
            pwm = speed * 64 - 1
        elif speed == 0:
            pwm = 0
        else:
            raise RuntimeError("Error: {0} -> wrong speed -> {1}".format(
                               motor, speed))
        self.motors[motor]["PWM"].write_analog(pwm)
        self.motors[motor]["SPEED"] = speed

    def start_soft_motor(self, motor, end_speed, time_step=100):
        for speed in range(0, end_speed):
            self.set_motor_speed(motor, speed)
            utime.sleep_ms(time_step)
        self.set_motor_speed(motor, end_speed)

    def stop_soft_motor(self, motor, time_step=100):
        for speed in range(self.motors[motor]["SPEED"], 0, -1):
            self.set_motor_speed(motor, speed)
            utime.sleep_ms(time_step)
        self.set_motor_speed(motor, 0)

    def set_motor_direction(self, motor, direction):
        directions = {FORWARD: (0, 1),
                      BACKWARD: (1, 0)}
        if direction in directions:
            for pin, state in zip(self.motors[motor]["PINS"],
                                  directions[direction]):
                pin.write_digital(state)
            self.motors[motor]["DIRECTION"] = direction

    def change_motor_direction(self, motor):
        self.set_motor_direction(motor, BACKWARD
            if self.motors[motor]["DIRECTION"] == FORWARD
            else FORWARD)

    def turn_servo(self, servo, angle):
        if angle >= 0 and angle <= 180:
            self.servos[servo].write_analog(angle / 2 + 25)
        else:
            raise RuntimeError("Error: {0} -> wrong angle -> {1}".format(
                               servo, angle))

    def turn_servo_off(self, servo):
        self.servos[servo].write_analog(0)
`DotMatrixPrinter` ist nicht wirklich eine Klasse. `dot` mit einem Dummywert zu belegen, nur um eine while-Schleife zu starten, macht man nicht. Ist eh falsch, da auch das split bei einer leeren Zeile nicht mehr funktioniert. Hier wäre ein for-Schleife eh klarer.

Code: Alles auswählen

ENCODER_PIN_X = mb.pin0
SPEED_X = 10
STEP_X = 7
ENCODER_PIN_Y = mb.pin1
SPEED_Y = 10
        
def print_image(filename):
    motor_board = MotorDriverBoard()
    with open(filename) as print_job:
        for line in print_job:
            x, y = map(int, line.split("|"))
            motor_board.drive_position("MOTOR_A", x * STEP_X,
                                        ENCODER_PIN_X,
                                        SPEED_X)
            motor_board.drive_position("MOTOR_B", y,
                                        ENCODER_PIN_Y,
                                        SPEED_Y)
            motor_board.set_motor_speed("MOTOR_B", 0)
            motor_board.turn_servo("SERVO_2", 30)
            utime.sleep_ms(200)
            motor_board.turn_servo("SERVO_2", 0)
            utime.sleep_ms(200)
    motor_board.turn_servo_off("SERVO_2")
        
print_image("print_job.txt")
Eine Datei pro Klasse macht man in Python auch nicht, das ist Java. Module schreibt man immer komplett klein.
Benutzeravatar
kaytec
User
Beiträge: 608
Registriert: Dienstag 13. Februar 2007, 21:57

Hallo Sirius,

danke für deine Kritik und die Klasse "DotMatrixPrinter" war mal größer, da ich das Asuslesen der Encoder drin hatte und dies eigentlich keine Funktion des Motorboards ist - nehme die pins der restlichen Servos. Das mit der Schleife hatte ich auch mal versucht und es wirft diese Fehlermeldung: "TypeError: 'TextIO' object is not iterable"

Werde deine Verbesserungen mal durchprobieren und melde mich.

Gruß
Benutzeravatar
kaytec
User
Beiträge: 608
Registriert: Dienstag 13. Februar 2007, 21:57

Hallo Sirius3,

was meinst du mit reinkopierten Code ?

Gruß Frank
Benutzeravatar
kaytec
User
Beiträge: 608
Registriert: Dienstag 13. Februar 2007, 21:57

Hallo Sirius3,

- Das mit den keys und values stimmt natürlich - bin halt "Hilfsprorammierer" und vergesse auch viel.

- Der Code in "drive_position" habe ich mal so auf Verdacht geschrieben und bei dem Dotmatrixdrucker funktioniert es ja so . Nächste Versionen werden
Fehler schon aufzeigen und ich bastel es in weiteren Versionen hin - man kann es halt nur im Betrieb testen und wundert sich dann über komische
Verhaltensweisen.

- Der Fehler in "start_soft_motor" wird im Betrieb nicht sichtbar - du hast es auch so bemerkt - danke !

- Mit "in" bei "Dict" prüfen war mir nicht bekannt und so habe ich es über die Liste gemacht.

Gruß und Dank Frank
Antworten