Thermal printer project

Hello all. I am quite new to Spark, Arduino etc. and need some help.

I have a mini thermal printer (the same as the adafruit thermal printer) connected to a Sparkcore. I have got the printer working following this thread - https://community.spark.io/t/help-thermal-printer-library-for-spark/5237/4 using this code - https://github.com/pkourany/Adafruit_Thermal_Printer

My end goal is to create a printer (packed up in a nice box) that i can send messages, images etc. to print. It’s going to be a gift for my son - we live apart and thought it would be a fun way to send him messages.

I wanted to follow this - https://github.com/exciting-io/printer/wiki/Making-your-own-printer - but this is based on using an Arduino with ethernet shield and SD card.

Ok. I have an SD card module connected to the spark. I presume i need that as the sparkcore does not have enough memory to handle large images (i tried and it prints a few lines of an image then craps out printing garbage.)

Can anyone help me get the ‘exciting-io’ code working on the sparkCore, I can return the favour and offer my design skills (I am primarily a designer).

I can replace the SD.h with the sparkler SD library but not sure where to start on the rest. Any help would be greatly appreciated even if its just a nudge in the right direction.

(PS. in this photo the SD card is connected but currently not doing anything, although i did get it read/write with the web IDE SD card library example. )

Thanks
Stuart

3 Likes

@Stu, it is good news that you have the SD and printer working with the Core so far. To get the Arduino code going, you will need to replace the EthernetClient code with the Core’s TCPClient(). Perhaps @bko can help with that part.

All the libraries included at the top are not necessary, except for the printer and SD .h files and Bounce2 library which I have to look at. Take a first crack at the code and see how far you get. :smile:

2 Likes

Additionally, I’m be suprised if you can’t just store incoming images into flash. That’s how I’m handling a similar(ish) project for storing images before displaying them on a e-ink display. The resolution on the thermal printer can’t be that big!

Speaking of, I have one of these (printers) and now really want to make a “XKCD-a-day” printer. Adding it to my backlog

3 Likes

Great, thanks for the pointers. I have started to go through the code bit by bit to see what I can do myself. Will let you know how i get on.

@harrisonhjones Yes, I was hoping it could just handle it! Maybe its just a limitation on the printer rather than just memory. I have done a few test and very small images are printing ok, but large once just produce garbage prints.

But according to this http://exciting.io/2012/04/12/hello-printer/

" The specific printer would only print up to 255 rows of dots, and so I needed to figure out how to split larger graphics up into chunks which could be sent to the printer sequentially. Eventually, I was able to print graphics that were longer than 255 rows: "

and

" However, the Arduino only has a limited amount of flash memory (normally around 32k), which meant that the largest full-width image that could be compiled into the program was around 450 rows high. "

How are you getting on with your e-project, sounds interesting.

I’m afraid my knowledge of programming Sparkcore / Arduio is pretty poor but I am willing to plug away! and probably embarrass myself in the process.

@peekay123 Hi. I have made some good progress!

I have all the code working (sort of), it flashes to the Sparkcore ok but I am still having a few issues.

// This #include statement was automatically added by the Spark IDE.
#include "Adafruit_Thermal/Adafruit_Thermal.h"
// This #include statement was automatically added by the Spark IDE.
#include "sd-card-library/sd-card-library.h"




// -- Settings for YOU to change if you want

byte mac[] = { 0x08, 0x00, 0x28, 0x56, 0xF5, 0x4D }; // physical mac address

// The printerType controls the format of the data sent from the server
// If you're using a completely different kind of printer, change this
// to correspond to your printer's PrintProcessor implementation in the
// server.
//
// If you want to control the darkness of your printouts, append a dot and
// a number, e.g. A2-raw.240 (up to a maximum of 255).
//
// If you want to flip the vertical orientation of your printouts, append
// a number and then .flipped, e.g. A2-raw.240.flipped
const char printerType[] = "A2-raw";

const char host[] = "printer.exciting.io"; // the host of the backend server
const unsigned int port = 80;

const unsigned long pollingDelay = 10000; // delay between polling requests (milliseconds)

const int printer_TX_Pin = 0; // this is the yellow wire
const int printer_RX_Pin = 0; // this is the green wire
const int errorLED = 7;       // the red LED
const int downloadLED = 6;    // the amber LED
const int readyLED = 5;       // the green LED
const int buttonPin = 3;      // the print button
// int SD_Pin = 4;         // the SD Card SPI pin

// SD Card
const uint8_t chipSelect = A2;
const uint8_t mosiPin = A5;
const uint8_t misoPin = A4;
const uint8_t clockPin = A3;

#define DEBUG // When debug is enabled, log a bunch of stuff to the hardware Serial

// -- Everything below here can be left alone

const char sketchVersion[] = "1.0.6";

// -- Debugging

#ifdef DEBUG
void debugTimeAndSeparator() {
  Serial.print(millis()); Serial.print(": ");
}
void debug(const char *a) {
  debugTimeAndSeparator(); Serial.println(a);
}
#define debug2(a, b) debugTimeAndSeparator(); Serial.print(a); Serial.println(b);
#else
#define debug(a)
#define debug2(a, b)
#endif


// -- Initialize the printer ID

const byte idAddress = 0;
char printerId[17]; // the unique ID for this printer.

inline void initPrinterID() {
  if ((EEPROM.read(idAddress) == 255) || (EEPROM.read(idAddress+1) == 255)) {
    debug("Generating new ID");
    randomSeed(analogRead(0) * analogRead(5));
    for(int i = 0; i < 16; i += 2) {
      printerId[i] = random(48, 57); // 0-9
      printerId[i+1] = random(97, 122); // a-z
      EEPROM.write(idAddress + i, printerId[i]);
      EEPROM.write(idAddress + i+1, printerId[i+1]);
    }
  } else {
    for(int i = 0; i < 16; i++) {
      printerId[i] = (char)EEPROM.read(idAddress + i);
    }
  }
  printerId[16] = '\0';
  debug2("ID: ", printerId);
}


// -- Initialize the LEDs

void initDiagnosticLEDs() {
  debug("Initializing LEDs");
  pinMode(errorLED, OUTPUT);
  pinMode(downloadLED, OUTPUT);
  pinMode(readyLED, OUTPUT);
  digitalWrite(errorLED, HIGH);
  digitalWrite(downloadLED, HIGH);
  digitalWrite(readyLED, HIGH);
  delay(1000);
  digitalWrite(errorLED, LOW);
  digitalWrite(downloadLED, LOW);
  digitalWrite(readyLED, LOW);
  delay(500);
}

// -- Initialize the printer connection

// SoftwareSerial *printer;
Adafruit_Thermal printer;
// #define PRINTER_WRITE(b) printer->write(b)

void initPrinter() {
//   printer = new SoftwareSerial(printer_RX_Pin, printer_TX_Pin);
//   printer->begin(19200);
  debug("Initializing printer");
  Serial.begin(9600);
  Serial1.begin(19200);
  printer.begin(&Serial1);
}


// -- Initialize the SD card

inline void initSD() {
//   pinMode(SD_Pin, OUTPUT);
//   if (!SD.begin(SD_Pin)) {
//     // SD Card failure.
//     terminalError(2);
//   }
  Serial.begin(115200);
  while (!Serial.available());

  debug("Initializing SD card");
   
  // Initialize HARDWARE SPI with user defined chipSelect
  if (!SD.begin(chipSelect)) {
    debug("initialization failed!");
    return;
  }
}


// -- Initialize the Ethernet connection & DHCP

// EthernetClient client;
TCPClient client;
inline void initNetwork() {
//   // start the Ethernet connection:
//   if (Ethernet.begin(mac) == 0) {
//     // DHCP Failure
//     terminalError(3);
//   }
//   delay(1000);
//   // print your local IP address:
//   debug2("IP: ", Ethernet.localIP());
  debug("initialization network...");
  IPAddress myIP = WiFi.localIP();
  Serial.println(myIP);    // prints the core's IP address
}


// // -- Initialize debouncing of buttons

// Bounce bouncer = Bounce();

// void initBouncer() {
//   bouncer.attach(buttonPin);
//   bouncer.interval(5);
// }

// // -- Setup; runs once on boot.

void setup(){
  #ifdef DEBUG
  Serial.begin(9600);
#endif
  initDiagnosticLEDs();
  initPrinterID();
  initSD();
  initPrinter();
  initNetwork();
//   initBouncer();
}

// // -- Check for new data and download if found

boolean downloadWaiting = false;
char cacheFilename[] = "TMP";
unsigned long content_length = 0;
boolean statusOk = false;

void checkForDownload() {
  unsigned long length = 0;
  content_length = 0;
  statusOk = false;

#ifdef DEBUG
  unsigned long start = millis();
#endif

  if (SD.exists(cacheFilename)) {
    if (!SD.remove(cacheFilename)) {
      // Failed to clear cache.
      digitalWrite(errorLED, HIGH);
      terminalError(4);
    }
  }
  File cache = SD.open(cacheFilename, FILE_WRITE);

  debug2("Attempting to connect to ", host);
  if (client.connect(host, port)) {
    debug2("Connected to ", host);
    digitalWrite(downloadLED, HIGH);
    client.print("GET "); client.print("/printer/"); client.print(printerId); client.println(" HTTP/1.0");
    client.print("Host: "); client.print(host); client.print(":"); client.println(port);
    client.flush();
    client.print("Accept: application/vnd.exciting.printer."); client.println(printerType);
    client.print("X-Printer-Version: "); client.println(sketchVersion);
    client.println();
    boolean parsingHeader = true;

    while(client.connected()) {
      while(client.available()) {
        if (parsingHeader) {
          client.find((char*)"HTTP/1.1 ");
          char statusCode[] = "xxx";
          client.readBytes(statusCode, 3);
          statusOk = (strcmp(statusCode, "200") == 0);
          client.find((char*)"Content-Length: ");
          char c;
          while (isdigit(c = client.read())) {
            content_length = content_length*10 + (c - '0');
          }
          debug2("Content length: ", content_length);
          client.find((char*)"\n\r\n"); // the first \r may already have been read above
          parsingHeader = false;
        } else {
          cache.write(client.read());
          length++;
        }
      }
      debug("Waiting for data");
    }

    debug("Server disconnected");
    digitalWrite(downloadLED, LOW);
    // Close the connection, and flush any unwritten bytes to the cache.
    client.stop();
    cache.seek(0);

    if (statusOk) {
      if ((content_length == length) && (content_length == cache.size())) {
        if (content_length > 0) {
          downloadWaiting = true;
          digitalWrite(readyLED, HIGH);
        }
      }
#ifdef DEBUG
      else {
        debug2("Failure, content length: ", content_length);
        if (content_length != length) debug2("length: ", length);
        if (content_length != cache.size()) debug2("cache: ", cache.size());
        digitalWrite(errorLED, HIGH);
      }
#endif
    } else {
      debug("Response code != 200");
      recoverableError();
    }
  } else {
    debug("Couldn't connect");
    recoverableError();
  }

  cache.close();

#ifdef DEBUG
  unsigned long duration = millis() - start;
  debug2("Bytes: ", length);
  debug2("Duration: ", duration);
#endif
}

void flashErrorLEDs(unsigned int times, unsigned int pause) {
  while (times--) {
    digitalWrite(errorLED, HIGH); delay(pause);
    digitalWrite(errorLED, LOW); delay(pause);
  }
}

inline void recoverableError() {
  flashErrorLEDs(5, 100);
}

inline void terminalError(unsigned int times) {
  flashErrorLEDs(times, 500);
  digitalWrite(errorLED, HIGH);
  // no point in carrying on, so do nothing forevermore:
  while(true);
}

// // -- Print send any data from the cache to the printer

inline void printFromDownload() {
  File cache = SD.open(cacheFilename);
  byte b;
  while (content_length--) {
    b = (byte)cache.read();
    //PRINTER_WRITE(b);
    printer.println(b);
  }
  cache.close();
  downloadWaiting = false;
  digitalWrite(readyLED, LOW);
}


// // -- Check for new data, print if the button is pressed.

void loop() {
  if (downloadWaiting) {
    // bouncer.update();
    // if (buttonPin == HIGH) {
    //   if (digitalRead(buttonPin) == HIGH) { //if push-button pressed
        // delay(250); //primitive button debounce
        printFromDownload();
    // }
    // }
  } else {
    checkForDownload();
    if (!downloadWaiting) {
      delay(pollingDelay);
    }
  }
}

Not quite sure how to share code in here, sorry if this does not display very well. I have commented out lots from the original code.

My printer actually shows up with this url - http://printer.exciting.io/my-printer which suggest the majority of the code is working at least. I have temporarily disabled the button so it should just print when it receives something. The button was not working for some reason.

It is currently just printing all the bytes on a new line but I know why. I am not sure what to do about these lines.

// SoftwareSerial *printer;
Adafruit_Thermal printer;
// #define PRINTER_WRITE(b) printer->write(b)

void initPrinter() {
//   printer = new SoftwareSerial(printer_RX_Pin, printer_TX_Pin);
//   printer->begin(19200);
  debug("Initializing printer");
  Serial.begin(9600);
  Serial1.begin(19200);
  printer.begin(&Serial1);
}

I commented out SoftwareSerial *printer as I presume its now using Serial1 instead. I have tried with #define PRINTER_WRITE uncommented but this next bit is not working.

inline void printFromDownload() {
  File cache = SD.open(cacheFilename);
  byte b;
  while (content_length--) {
    b = (byte)cache.read();
    //PRINTER_WRITE(b);
    printer.println(b);
  }
  cache.close();
  downloadWaiting = false;
  digitalWrite(readyLED, LOW);
}

I get errors if i try and use PRINTER_WRITE(b)

I think the data it receives is bitmap, how can I get it to print that. I don’t know what the height of it will be.

Currently when it prints using printer.println(b); it does not seem to reach the end, close the cache and turn off the readyLED.

Feel like I am pretty close to getting this working, somewhat surprised myself!

@peekay123 I should also add that after attempting a print I can’t flash to the SparkCore (pulsing light but the Web IDE says it cant connect).

I have to either hard reset it all or I have found by restarting it without the SD card in creates an error and then i can flash to the device. If you see what i mean!

Current wiring setup

@Stu, great start! I edited your post to make the code appear correctly using the </> selection in the toolbar. It sounds like your code is hanging thus preventing the background firmware from staying connected to the cloud. This would prevent OTA firmware updates from happening.

I have to look at the printer library code to see if the code is calling it correctly so stay tuned. :smile:

1 Like

@Stu, you need to look at the Adafruit_Thermal_Printer library code. First, the library assumes Serial1 is being used. Second, the printer.begin() call takes a “heatTime” argument, so your code passes &Serial1 as that argument! If you look at the examples in the library, it is best to call printer.begin() with no argument and let it use default values. I believe the PRINTER_WRITE(b) macro should be enabled and defined as “printer.write(b)”. There is also a way to print bitmaps from a “stream” of data but I will have to look at that later. :smile:

2 Likes

@peekay123 I have tried this without any printer.begin() without parameters but it always returns and error saying it was expecting 2 parameters. I have also tried: printer.begin(&Serial1,80);

I don’t get any more errors now that the macro looks like this: #define PRINTER_WRITE(b) printer.write(b)

The printer does actually attempt to do something … it sounds like it is printing a few dots with the paper feeding a tiny bit each time but then just stops / dies after a seconds.

@peekay123 Actually, in the ADAFRUIT_THERMAL library in the Web IDE the example has this:

Serial.begin(9600);
Serial1.begin(19200);
printer.begin(&Serial1);

@Stu, I just realized that the web IDE library is not the same as the one in my github repo! So the .begin() call IS correct. You say you tested your printer with my repo code to begin with?

So just a few things. First, make sure your LED pins use Spark denominations like D7, D6, etc. Second, I suggest you add debug Serial.print() statements in checkForDownload() to view the raw downloaded data on your terminal. Put Serial.print(client.peek(), HEX) right before the if (parsingHeader) line. This will print of bytes in HEX format as they are received.

Then send data to the Core so data is built in the SD file. Then take the SD out and open the file left on there with an editor like Notepad++ to see if the data matches the screen output and is as expected. :smile:

@peekay123 You are right, I did original use your code but then I saw that there was a Thermal lib in the Web IDE. Is there much difference, which one would you recommend i use? I had a some issues with Spark Dev app so I jumped back onto the web IDE at that point.

I will follow your suggestions, see if the data matches up. I feel this is ridiculously close to working :smile:

Thanks for all your help, very much apreciated.

@peekay123 Ok, that was interesting! The HEX output in the terminal was kind of what I expected:

1B401B377F0321223FF122ABF300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000

etc…

But the TMP file saved is like this:

e@e7a2#ˇ*ø0

@Stu, for printing, you may want to go back to my repo, only because it is a known good library. Second, the SD output needs to be in HEX. I believe there is a HEX editor plug-in for Notepad++ you could use. However, it does look odd. Can you tell me what did you expect exactly?

@peekay123 I think i may have found the issue. The lib used by the exciting.printer.io developer has been modified to print from an SD file. https://github.com/lazyatom/Thermal-Printer-Library/commit/82dacbdd6c3ea6a32fe123cb1ff17b6810a4c330 The width and height of the image is included in the first four bytes of the stream. So it kind of makes sense now why its not working!

But I am currently working in the Web IDE so can’t make these modifications. I have tried to use the Spaek Dev app local but I am not sure how to add all the SD-card lib files I would need. It seems like libs don’t work the same in the Web IDE and Spark Dev. I think I am reaching the limit of what I can do now :blush:

I don’t suppose the Spark Dev app can pull in libs from the web IDE?

@peekay123 If I can add the sd-card-lib to the spark dev and build locally then I can use your code and just modify this few lines.

I was not entirely sure what i was expecting in the SD card TMP file. But rereading the printer.io blog i think it is a bmp image but with the width and height added to the the start of the stream. This level of coding is starting to go over my head.

I will have another bash at trying to add the sd card library locally, would I need to include the .cpp files?

@peekay123 I managed to open the TMP file in a HEX Viewer on my Mac. In HEX format it does match the output in the terminal.

@Stu, all you need to compile your code is to put ALL files (SD, printer, etc) in a single directory and compile the directory. You can copy the SD files from the source github repo. Each library has a link to its originating repo so you can download the files “as a zip” and go from there.

Can you tell me what the printer is supposed to be trying to print? Perhaps you could enable the debug2 messages to get some details?

@peekay123 This is what is sent to the printer, basically just BMP’s
http://printer.exciting.io/archive/5b1q3x8q4e6v6n3n

I have it building from Spark Dev. I am not sure how to merge his and your code together though. I think its just the print bitmap functions that need adjusting, not sure.

Have to head out today, will take another look later. Thanks.