Eine Kamera mit einem eher unterdimensionierten Mikrocontroller

Disclaimer: Dieses Projekt entstand hauptsächlich an einem Wochenende. Sehr viel Dokumentation ist außer dem Quelltext daher nicht vorhanden. Wer diese Kamera nachbauen möchte (und an einen der verwendeten Thermodruckköpfe gelang ist), muss sich mit der Information aus pinning.h begnügen und sich selbst um Pegelwandler und Schrittmotortreiber kümmern.

Ich hatte vier dieser Thermodruckermodule, die ich vor Jahren gekauft hatte, weil sie billig und interessant waren. Jahrelang lagen sie also in einer Schublade und verspotteten mich. Etwas musste getan werden!

Die beschriebenen Module sind LTP1245s von SII. Volle Dokumentation ist damit verfügbar (sonst hätte ich sie auch nicht gekauft). Elektrisch gesehen brauchen sie einen Treiber für den Schrittmotor, einen ADC für den Heißleiter zur Temperaturmessung und einige 5 V-Signale zur Steuerung. Der Betriebsbereich ist flexibel genug für einen Betrieb an einem 2s-LiIon-Akkupack. Alles in allem nicht zu viel Aufwand für die Ansteuerung.

Nun aber, was macht man mit einem Thermodrucker? Ich erinnerte mich dunkeln an ein Video von mikeselectricstuff über günstige CMOS-Kameramodule an Mikrocontrollern. Direkt entstand der Plan, ein LTP1245-Thermodruckermodul über einen der beliebten STM32F103C8T6 mit einem OV7670-Kameramodul zu bedienen.

Die Wahl fiel auf diesen Mikrocontroller, da ich keine eigene Platine erstellen und ätzen wollte, um die Zeit mit der Software verbringen zu können. Alle Development-/Evaluationboards mit leistungsstärkeren Controllern, die ich hatte, waren deutlich größer als die klassische Blue Pill und damit zu groß für meine Idee der Thermodrucker-Selfiekamera. Also fing ich mit diesem Controller an, auch um zu sehen, wie weit ich damit kommen würde. Es stellte sich dann tatsächlich als komplizierter als gedacht heraus, aus den folgenden Gründen:

Ausgabe

Der erste Schritt war der Druckerteil. Obwohl ein LTP1245 nicht sehr viel externe Beschaltung benötigt, ist er intern auch nicht besonders komplex. Außer dem schrittmotorgetriebenen Papiereinzug enthält er:

Um eine Zeile zu drucken, werden die Daten ins Schieberegister getaktet und die Heizelemente über die Latches für eine bestimmte Zeit aktiviert. Anschließend wird das Papier mittels Schrittmotor um einen Schritt weiterbewegt und der Prozess wiederholt sich. Die Aktivierungszeit kann anhand von der Versorgungsspannung, der Druckkopftemperatur und weniger Konstanten für das verwendete Papier berechnet werden. Dadurch wird eine definierte Energie in die Beschichtung des Papiers übertragen und wiederholbare Druckqualität erreicht.

Die Druckgeschwindigkeit ist hauptsächlich durch den Schrittmotor begrenzt (und steigt daher auch mit höherer Versorgungsspannung). Als Treiber kommen zwei Emitterfolger-Paare pro Phase zum Einsatz. Nachdem der Motor keine hohe Leistung erfordert und nicht einmal Microstepping nötig ist, besteht kein Bedarf für ein eigenes Treiber- oder Steuerungs-IC und diese Lösung ist mehr als ausreichend.

Softwareseitig landen Temperaturmessung, Papiereinzug und zeilenweises Drucken in einer Zustandsmaschine. Neben dem Bilddrucken implementierte ich auch eine einfache Textausgabe, die zwar am Ende nicht gebraucht wurde, aber beim Debuggen hilfreich war. Zudem kann der Controller noch ein PWM-Signal erzeugen um ein Servo für einen Papierschneider anzusteuern (was letztendlich auch nicht gebraucht wurde).

Damit ist die Ausgabe auf Papier abgedeckt. Mit einem funktionierenden Drucker konnte ich mich dem Kamera-Teil widmen.

Eingabe

Der OV7670 hat einen langsamen I²C-Bus für die Konfiguration und einen schnelleren Parallelbus für die Bilddaten. Der Takt für letzteren wird vom Kameramodul auf Basis eines Eingangstakts erzeugt. Nachdem es keinen Puffer gibt und er direkt vom Frame-Timing abhängt, ist er relativ frei konfigurierbar.

Nachdem der Controller kein Kamerainterface hat und nur wenig RAM verfügbar ist, musste ich Bildauflösung und Pixeltakt so weit wie möglich herunterstellen. Nachdem der Thermodrucker nur 384 Pixel pro Zeile hat, hilft eine geringere Bildauflösung auch eher als dass sie stört. Die Dokumentation des OV7670 sagt nicht ganz eindeutig, wie die verschiedenen Einstellungen sich gegenseitig beeinflussen, daher entstand die Konfiguration hauptsächlich durch Herumprobieren. Ich landete letztendlich bei diesen Einstellungen:

// Disable timing resets
WriteRegister(REG_COM6, 0x00);

// Set clock prescaler to 2
WriteRegister(REG_CLKRC, 0x4 | 1);

// Enable scaling
WriteRegister(REG_COM3, 0x08);

// Use QCIF output format
WriteRegister(REG_COM7, 0x08);

// Blank pixel clock during sync pulses
WriteRegister(REG_COM10, 0x20);

// Enable pixel clock scaling
WriteRegister(REG_COM14, 0x18 | 1);
WriteRegister(REG_SCALING_PCLK_DIV, 1);

Das Kameramodul spuckt nun Daten aus, der Mikrocontroller muss sie aber noch aufnehmen. Das geschieht wie folgt:

Der Pixeltakt kann so konfiguriert werden, dass er während der Synchronisationspulse aussetzt. Dadurch gibt es nur Taktflanken für echte Pixel und keine überflüssigen Daten werden aufgezeichnet.

Bildverarbeitung

Es bleiben somit 160 auf 144 Pixel Bilddaten. Wer mitrechnet, wird bemerken, dass ein komplettes Frame 22.5 KiB hat und damit alleine schon größer als das verfügbare RAM wäre. Damit wird auch verständlich, weshalb das HSYNC-Interrupt nicht übersprungen und ein ganzes Frame auf einmal eingelesen werden kann.

Glücklicherweise ist die HSYNC-Pause lang genug für grundlegende Bildverarbeitung. Jede Zeile wird daher mittels Dithering zu schwarz-weiß konvertiert, was den Speicherbedarf auf ein Achtel reduziert. Zu Beginn verwendete ich ein einfaches 1D-Dithering, später den Floyd-Steinberg-Algorithmus. Nachdem mir das 1D-Dithering ästhetisch gut gefiel, ließ ich es als Option im Code.

Sobald ein komplettes Frame eingelesen und in ein Schwarzweißbild konvertiert wurde, kann die Aufnahme gestoppt und das Bild gedruckt werden. Nach Abschluss des Drucks schaltet die Kamera ihre eigene Stromzufuhr mittels eines p-Kanal-MOSFETs ab und wartet auf die nächste Aktivierung.

Auch wenn es etwas ramponiert aussieht, zeigt sich ein einfaches Gehäuse aus Platinenmaterial als robust. Der Auslöseknopf verbirgt sich an der Rückseite.

Luft nach oben

An diesem Punkt war mir die Kamera gut genug, und sie hat sich auch als unterhaltsamer Party-Gimmick bewährt. Einige Möglichkeiten zur Verbesserung:

Der Sourcecode findet sich auf GitHub.