Feedback!

Thermal Printer

Views: 7774 Difficulty: 4 Status: Deprecated
Thermal

Print bitmaps, cellular automata, drawings and text..

Thermal printers can do so much more than print receipts in cabs. In this tutorial we will show how to print drawings, bitmaps, text and cellular automata! You can pick up the type of thermal printer we will use from adafruit or sparkfun. Just a warning this printer can be pretty finicky and you may have to tweak settings based on the voltage you are supplying and the particulars of your setup. The bitmap results are less than perfect and vary between runs more than we would like. An astute observer will notice that we are using the same circuit board here as we do in the HSB Joy Drawer. Well, the joy PCB is versatile and has the necessary pin pulled out into headers which can connect to the thermal printer.

Drawings

Screen_shot_2013-03-01_at_7.32.49_pm
Print out little joystick drawings!

Printing Video

Check out the drawings and pictures being printed in fast forward! The printer is pretty slow after all, little arduino has a lot of work to do: Read data from an SD Card, Control the LCD Screen, control the printer, the tactile inputs, etc. etc.. Notice the cute USBTiny programmer from adafruit chilling out in the bottom left corner.

Print Drawing Code

This code uses a bunch of libraries: the SPI library to talk to the TFT LCD Screen, The ST7735.h library TFT LCD Screen from adafruit, and the SoftwareSerial library to talk to the printer. We also use the thermal printer library available for download below. There are a few thermal printer libs floating around. This one is based on bildr's, but we added some functions so if you want to use this code make sure to download our Thermal Printer library. The drawing code works by following the joy stick around the screen and waiting for a button press. This is the code that handles button presses:
if ( digitalRead(btnPin) == HIGH ){
    tft.fillCircle(cur_x, cur_y, 2,WHITE);
    int x_index = cur_x / 8;
    int y_index = constrain(cur_y,0, pic_height-8);
    for (int j = 0; j < 8; j++){
      int i =  x_index + ((y_index+j)*pic_width);
      bitmap[i] = 0xFF ;
    }
On a button press we draw a WHITE circle, and then translate between the 2D point on the screen to the 1D point in the bitmap array. The 1D bitmap array is a flattened version of of 2D pixel array. We divide by 8 to translate from bytes to bits. Now, how do we make it print?
  if ( digitalRead(btnPin) == HIGH &&  cur_x < 20 && cur_y < 20){
   printer.printBitmap(pic_width*8,pic_height,bitmap);
   delay(100); 
   tft.fillScreen(BLACK);
   delay(100);
   bitmap_white();
  }
If the button is pressed when the cursor is in the top left corner (i.e. cur_x < 20 && cur_y < 20) then we call printer.printBitmap(pic_width*8,pic_height,bitmap); And out comes our drawing!
/* LucidTronix joystick drawings 
 * printed on a thermal printer 
 * For instructions, details and schematic, See:
 * http://www.lucidtronix.com/tutorials/28
 * Printer needs strong power source > 1.5A,
 * 5V-9V
 */

#include <SoftwareSerial.h>
#include <Thermal.h>
#include <ST7735.h>
#include <SPI.h>

int printer_RX_Pin = 6; // This is the green wire
int printer_TX_Pin = 7; // This is the yellow wire

Thermal printer(printer_RX_Pin, printer_TX_Pin);

#define cs 10   // for MEGAs you probably want this to be pin 53
#define dc 9
#define rst 8  // you can also connect this to the Arduino reset

// Color definitions
#define	BLACK           0x0000
#define	BLUE            0x001F
#define	RED             0xF800
#define	GREEN           0x07E0
#define CYAN            0x07FF
#define MAGENTA         0xF81F
#define YELLOW          0xFFE0  
#define WHITE           0xFFFF

ST7735 tft = ST7735(cs, dc, rst);

const int bitmap_size = 1600;
const int pic_width = 16;
const int pic_height = 100;
uint8_t bitmap[bitmap_size];
int btnPin = 1;

void setup(){
  tft.initR();               // initialize a ST7735R chip
  tft.writecommand(ST7735_DISPON); 
  tft.fillScreen(BLACK);
  delay(500);
  int bx = 0;
  int by = 0;
  for(int i = 0 ; i < bitmap_size; i++){
    
    if( (bx > 4 && bx < 8 && by > 30 && by < 60)
      ||(bx > 10 && bx < 15 && by > 70 && by < 90) )  bitmap[i] = 0xFF ;
    else bitmap[i] = 0x00 ;
    bx++;
    if ( bx == pic_width){
     bx = 0;
     by++; 
    }
  }
  printer.justify('C'); //sets text justification (right, left, center) accepts 'L', 'C', 'R'
  bitmap_white();
  pinMode(btnPin, OUTPUT);
}

int cur_x  = 64;
int cur_y = 80;
void loop(){
  int y_in = (1024-analogRead(1));
  int x_in = (analogRead(0));
  if ( x_in > 700 && cur_x < 125 ) cur_x++;
  else if ( x_in < 130 &&  cur_x > 5 ) cur_x--;
  if ( y_in > 700 && cur_y < 157 ) cur_y++;
  else if ( y_in < 300 && cur_y > 5 ) cur_y--;
  
  int cur_r = 10;
  int cur_g = 20;
  int cur_b = 10;
  int color = cur_r  | (cur_g << 5) | (cur_b << 11); 
  
  
  if ( digitalRead(btnPin) == HIGH ){
    tft.fillCircle(cur_x, cur_y, 2,WHITE);
    int x_index = cur_x / 8;
    int y_index = constrain(cur_y,0, pic_height-8);
    for (int j = 0; j < 8; j++){
      int i =  x_index + ((y_index+j)*pic_width);
      bitmap[i] = 0xFF ;
    }
    
   
    
  } else tft.drawCircle(cur_x, cur_y, 2,color);
  delay(10);
  if ( digitalRead(btnPin) == HIGH &&  cur_x < 20 && cur_y < 20){
   printer.printBitmap(pic_width*8,pic_height,bitmap);
   delay(100); 
   tft.fillScreen(BLACK);
   delay(100);
   bitmap_white();
  }
  if ( digitalRead(btnPin) == HIGH &&  cur_x < 20 && cur_y > 130){
   delay(100); 
   tft.fillScreen(BLACK);
   delay(100);
   bitmap_white();
  }
  testdrawtext(0,0,"Print", WHITE);
  testdrawtext(0,150,"Clear", WHITE);
}
void bitmap_white(){
  for(int i = 0 ; i < bitmap_size; i++){
      bitmap[i] = 0x00 ;
    }
}
void testdrawtext(int ax, int ay, char *text, uint16_t color) {
  tft.drawString(ax, ay, text, color);
}

Bitmap and Automata Code

Ditto to the previous example about the included libraries. This code reads a bitmap image off an SD card and displays it on an LCD Screen while simultaneously printing it on the thermal printer. It involves a primitive thresholding shown in code here:
      boolean a_bit = 1;
      if (bb+gg+pp > 384) a_bit = 0;
      set_bit_from_index(j, a_bit);
bb, gg, and pp are the red green and blue values from the bitmap each stored in a byte. We sum them together and check if the total is less than 384 which is the expected value of a uniformly distributed random variable on the interval of 256 + 256 + 256 = 768. We fill the scanline with a 1 or 0 depending on the brightness (or average of the color channels). We the print the scanline and start over. Each scanline is a row pixels. Similar idea for the cellular automata except now the values are derived by applying the cellular automata rule not from reading an SD card. The relevant code is shown below:
for (int i = 1; i < (scanline_size*8)-1; i++) {
      int left = get_bit_from_index(i-1);   // Left neighbor state
      int me = get_bit_from_index(i);       // Current state
      int right = get_bit_from_index(i+1);  // Right neighbor state
      boolean abit = get_bit_from_rules(left,me,right); // Compute next generation state based on ruleset
      set_bit_from_index(i, abit);
      if ( i < 160) {
        if (abit )  tft.drawPixel(cur_y, i, CYAN);
        else tft.drawPixel(cur_y, i, MAGENTA);
      }       
  }
  cur_y++;
  if (cur_y > 128) cur_y = 0;
  printer.printScanline( scanline_size*8, nextline);
  for (int i = 0; i < scanline_size; i++) {
    scanline[i] = nextline[i];
  } 
Post a comment if you have a question!
/* LucidTronix bitmap and cellular automata
 * printed on a thermal printer 
 * For instructions, details and schematic, See:
 * http://www.lucidtronix.com/tutorials/28
 * Printer needs strong power source > 1.5A,
 * 5V-9V
 */
#include <SoftwareSerial.h>
#include <Thermal.h>
#include <ST7735.h>
#include <SPI.h>
#include <SD.h>

int printer_RX_Pin = 6;
int printer_TX_Pin = 7;

Thermal printer(printer_RX_Pin, printer_TX_Pin);

#define cs 10   // for MEGAs you probably want this to be pin 53
#define dc 9
#define rst 8  // you can also connect this to the Arduino reset
#define sclk 13
#define mosi 11

// Color definitions
#define	BLACK           0x0000 
#define WHITE           0xFFFF

#define SD_CS 4    // Set the chip select line to whatever you use (4 doesnt conflict with the library)

// the file itself
File bmpFile1;
// information we extract about the bitmap file
int bmpWidth, bmpHeight;
uint8_t bmpDepth, bmpImageoffset;
ST7735 tft = ST7735(cs, dc, rst);

const int scanline_size = 30;
uint8_t scanline[scanline_size];
uint8_t nextline[scanline_size];



boolean rules[] = { 0,1,0,1,1,0,1,0}; 
int btnPin = 1;
int cur_y = 0;
void setup(){
  pinMode(cs, OUTPUT);
  digitalWrite(cs, HIGH);
  tft.initR();               // initialize a ST7735R chip
  tft.writecommand(ST7735_DISPON); 
  tft.fillScreen(BLACK);
  tft.drawString(12,22, "Try SD" ,BLUE);
  if (!SD.begin(SD_CS)) {
    tft.drawString(12,42, "SD Failed" ,RED);
    return;
  }
  tft.drawString(12,42, "SD GOOD" ,BLUE);
  
  delay(1000);
  tft.fillScreen(BLACK);
  int bx = 0;
  int by = 0;
  //printer.testScanline();
  delay(2000);
  pinMode(btnPin, OUTPUT);
  for (int i = 0; i < scanline_size; i++) {
    scanline[i] = 0;
    if ( i == scanline_size/2) scanline[i] = 0x08;
  }
  bmp_load_and_draw_image("face66.bmp");
  printer.println(" ");  
}

void loop(){
  for (int i = 1; i < (scanline_size*8)-1; i++) {
      int left = get_bit_from_index(i-1);   // Left neighbor state
      int me = get_bit_from_index(i);       // Current state
      int right = get_bit_from_index(i+1);  // Right neighbor state
      boolean abit = get_bit_from_rules(left,me,right); // Compute next generation state based on ruleset
      set_bit_from_index(i, abit);
      if ( i < 160) {
        if (abit )  tft.drawPixel(cur_y, i, CYAN);
        else tft.drawPixel(cur_y, i, MAGENTA);
      }       
  }
  cur_y++;
  if (cur_y > 128) cur_y = 0;
  printer.printScanline( scanline_size*8, nextline);
  for (int i = 0; i < scanline_size; i++) {
    scanline[i] = nextline[i];
  } 
}
void testdrawtext(int ax, int ay, char *text, uint16_t color) {
  tft.drawString(ax, ay, text, color);
}
boolean get_bit_from_index(int index){
  int byte_index = index / 8;
  int bit_index = 7 - (index % 8);
  byte cur_b = scanline[byte_index];
  if ( (cur_b & (1 << bit_index)) != 0x00 ) return 1;
  else return 0;
}
void set_bit_from_index(int index, boolean abit){
  int byte_index = index / 8;
  int bit_index = 7-(index % 8);
  byte cur_b = nextline[byte_index];
  if ( abit)  cur_b |= (1 << bit_index);
  else cur_b &= ~(1 << bit_index) ;
  nextline[byte_index] = cur_b;
}

int get_bit_from_rules(int a, int b, int c) {
    if (a == 1 && b == 1 && c == 1) return rules[0];
    if (a == 1 && b == 1 && c == 0) return rules[1];
    if (a == 1 && b == 0 && c == 1) return rules[2];
    if (a == 1 && b == 0 && c == 0) return rules[3];
    if (a == 0 && b == 1 && c == 1) return rules[4];
    if (a == 0 && b == 1 && c == 0) return rules[5];
    if (a == 0 && b == 0 && c == 1) return rules[6];
    if (a == 0 && b == 0 && c == 0) return rules[7];
    return 0;
  }
  
  void bmp_load_and_draw_image(char* filename) {
  bmpFile1 = SD.open(filename);

  if (! bmpFile1) {
    tft.drawString(12,62, "Coodn't find image" ,RED);
    while (1);
  }
  
  if (! bmpReadHeader(bmpFile1,0)) { 
     tft.drawString(12,82, "BAD image" ,RED);
     return;
  }
  
  tft.drawString(12,102, "TRY 2 draw image" ,GREEN);
  delay(1500);
  bmpdraw(bmpFile1, 0, 0);
}

/*********************************************/
// This procedure reads a bitmap and draws it to the screen
// its sped up by reading many pixels worth of data at a time
// instead of just one pixel at a time. increading the buffer takes
// more RAM but makes the drawing a little faster. 20 pixels' worth
// is probably a good place

#define BUFFPIXEL 20

void bmpdraw(File f, int x, int y) {
  bmpFile1.seek(bmpImageoffset);
  
  uint32_t time = millis();
  uint16_t p; 
  uint8_t g, b;
  int i, j;
  
  uint8_t sdbuffer[3 * BUFFPIXEL];  // 3 * pixels to buffer
  uint8_t buffidx = 3*BUFFPIXEL;
  
  //Serial.print("rotation = "); Serial.println(tft.getRotation(), DEC);
  
  //set up the 'display window'
  tft.setAddrWindow(x, y, x+bmpWidth-1, y+bmpHeight-1);
  
  uint8_t rotback = tft.getRotation();
  //tft.setRotation();
  
  for (i=0; i< bmpHeight; i++) {
    // bitmaps are stored with the BOTTOM line first so we have to move 'up'
  
    for (j=0; j<bmpWidth; j++) {
      // read more pixels
      if (buffidx >= 3*BUFFPIXEL) {
        bmpFile1.read(sdbuffer, 3*BUFFPIXEL);
        buffidx = 0;
      }
      
     
      b = sdbuffer[buffidx++];     // blue
      g = sdbuffer[buffidx++];     // green
      p = sdbuffer[buffidx++];     // red
      
      boolean a_bit = 1;
      int bb = b;
      int gg = g;
      int pp = p;
      if (bb+gg+pp > 384) a_bit = 0;
      set_bit_from_index(j, a_bit);
      
      // convert pixel from 888 to 565
      p >>= 3;
      p <<= 6;
      
      g >>= 2;
      p |= g;
      p <<= 5;
      
      b >>= 3;
      p |= b;
     //Serial.print(p, HEX);
      // write out the 16 bits of color
      tft.drawPixel(j, i, p);
      //tft.pushColor(p);
    }
    printer.printScanline( scanline_size*8, nextline);


  }
}
boolean bmpReadHeader(File f, int mode) {
   // read header
  uint32_t tmp;
  
  if (read16(f) != 0x4D42) {
    // magic bytes missing
    return false;
  }
 
  // read file size
  tmp = read32(f);  
  //Serial.print("size 0x"); Serial.println(tmp, HEX);
  
  // read and ignore creator bytes
  read32(f);
  
  bmpImageoffset = read32(f); 
  //Serial.print("offset "); Serial.println(bmpImageoffset, DEC);
  
  // read DIB header
  tmp = read32(f);
  //Serial.print("header size "); Serial.println(tmp, DEC);
 
  bmpWidth = read32(f);
  bmpHeight = read32(f); 
  if (read16(f) != 1)
    return false;
    
  bmpDepth = read16(f);
  if (read32(f) != 0) {
    // compression not supported!
    return false;
  }

  return true;
}

/*********************************************/

// These read data from the SD card file and convert them to big endian 
// (the data is stored in little endian format!)

// LITTLE ENDIAN!
uint16_t read16(File f) {
  uint16_t d;
  uint8_t b;
  b = f.read();
  d = f.read();
  d <<= 8;
  d |= b;
  return d;
}


// LITTLE ENDIAN!
uint32_t read32(File f) {
  uint32_t d;
  uint16_t b;
 
  b = read16(f);
  d = read16(f);
  d <<= 16;
  d |= b;
  return d;
}

Download Arduino Code

Download the arduino libraries for the screen and the printer as well as example code showing how to display drawings, pictures, and automata. Remember you will also need the SD and SPI libraries.
Click Here to Download: Download Arduino Code

Parts

Title Description # Cost Link Picture
Thermal Printer Thermal printer can print text, barcodes, bitmaps (so so) 1 $49.95 Link Thermal_supply
HSB Joy PCB PCB controls a screen, thermal printer, joystick and SD card 1 $11.95 Link Screen_shot_2013-03-01_at_7.31.39_pm
TFT LCD Screen 1.8 inch 160 x 128 pixels LCD screen. ST7735R driver. JDT-1800. 1 $9.95 Link Id618_lrg
Joystick Thumb joystick with push button. 1 $3.95 Link 09032-03-l
microSD Card Connector Secure Digital - microSD™ Push In, Push Out Value: microSD 1 $3.44 Link 0473340001
ATMEGA328P-AU Integrated Circuits (ICs) MCU AVR 32K FLASH 32TQFP Value: 1.8 V ~ 5.5 V 20MHz 1 $3.05 Link Screen_shot_2012-12-28_at_7.31.33_pm
Potentiometer POT ROTARY, Linear 10K OHM 9MM SNAPIN Value: 10k 3 $0.76 Link Screen_shot_2012-12-28_at_7.41.04_pm
USB connector USB Mini type B Value: female 1 $0.68 Link Usb-m26ftr
Crystal CRYSTAL 16.00000 MHZ 20PF SMD Value: 16MHz 1 $0.53 Link Ecs-270-20-3x-en-tr
Voltage Regulator 3v3 SMD IC REG LDO 3.3V .15A SOT23 Up to 6V input output 3.3V 150mA (Max) Value: 3.3V 1 $0.46 Link Sot-23-3_pkg
CD4050 IC BUFF/CONVERTER HEX 16SOICN 8mA, 48mA Value: 3 V ~ 18 V 1 $0.45 Link 16-soic_(7.5mmwidth)
Capacitor Ceramic Capacitors CAP CER 0.1UF 50V 20% RADIAL Value: 0.1µF 4 $0.24 Link Screen_shot_2012-12-28_at_7.39.17_pm
Button Tactile switch SMD SPST 0.05A 12V Value: SPST 3 $0.2 Link Fsm4jsma
Resistor RES 10K OHM 1/4W 5% CF MINI Value: 10k 6 $0.08 Link Screen_shot_2012-12-28_at_7.28.15_pm
Permalink: http://lucidtronix.com/tutorials/28
3d view of a face using an SD card, accelerometer and a TFT LCD Screen...
Control the hue, value and saturation on this pocket-sized joystick drawing machine....
This PCB controls a camera and an SD card so you can make automatic time lapse videos. ...
Arduino-powered spherical camera with 2 Servo Motors and an SD Card....
Play pong on a little LCD screen with two big joysticks....
Servo motor, JPEG camera, and Arduino Leonardo combine to make a panoramic camera....
From simple rules emerge mysterious and intricate patterns....
A wearable real-time clock with vibrant color display....