bitbank2 / Thermal_Printer

Arduino library to draw text and graphics on BLE thermal printers
Apache License 2.0
367 stars 54 forks source link

PeriPage printer not feeding paper while printing JPG from ov2640 camera #38

Closed nicholas-gh closed 2 years ago

nicholas-gh commented 2 years ago

I'm hoping that users of this library might have practical experience which can tell me if I'm doing something wrong in code, or if my printer is faulty. I don't find similar issues on the web, so also hoping a discussion here might help future people with the same problem. Many thanks for any thoughts on what might be wrong!

It fed paper fine until I started printing images captured using esp32-camera; >90% of the time images from esp32-camera do not print/feed the paper correctly. If I go back afterwards and use the PeriPage app on a phone, or use the example jpg printing from the Thermal_Printer library, it sometimes feeds and sometimes does not. Using 'banner' feature in the PeriPage app does seem to feed OK.

It feels like maybe my esp32-camera images are overheating something which causes the paper feed to slide instead of actually feeding the paper. The images look OK if I gently pull the paper while it's printing. I can make a video of this if helpful.

I've experimented with a few different jpeg_quality, and also different image sizes from the camera, and different scaling (half, quarter, none).

My code (not really mine so much, I simply combined together a few examples):

#include "esp_camera.h"
#include "Arduino.h"
#include <Thermal_Printer.h>
#include <JPEGDEC.h>

// https://docs.m5stack.com/en/unit/m5camera
#define PWDN_GPIO_NUM     -1
#define RESET_GPIO_NUM    15
#define XCLK_GPIO_NUM     27
#define SIOD_GPIO_NUM     22
#define SIOC_GPIO_NUM     23

#define Y9_GPIO_NUM       19
#define Y8_GPIO_NUM       36
#define Y7_GPIO_NUM       18
#define Y6_GPIO_NUM       39
#define Y5_GPIO_NUM        5
#define Y4_GPIO_NUM       34
#define Y3_GPIO_NUM       35
#define Y2_GPIO_NUM       32
#define VSYNC_GPIO_NUM    25
#define HREF_GPIO_NUM     26
#define PCLK_GPIO_NUM     21

// https://github.com/bitbank2/JPEGDEC/wiki
uint8_t ucDither[(640/2) * 16]; // Image width (after scaling), x 16
JPEGDEC jpg;
static int iWidth;

int JPEGDraw(JPEGDRAW *pDraw)
{
  int i, iCount;
  uint8_t *s = (uint8_t *)pDraw->pPixels;

  tpSetBackBuffer((uint8_t *)pDraw->pPixels, pDraw->iWidth, pDraw->iHeight);
  // The output is inverted, so flip the bits
  iCount = (pDraw->iWidth * pDraw->iHeight) / 8;
  for (i = 0; i < iCount; i++)
    s[i] = ~s[i];

  tpPrintBuffer(); // Print this block of pixels
  return 1; // Continue decode
}

void setup() {
  Serial.begin(115200);

  Serial.println((char *)"Scanning for BLE printer");
  if (tpScan()) // Scan for any supported printer name
  {
    Serial.println((char *)"Found a printer!, connecting...");
    if (tpConnect())
    {
      Serial.println("Connected!");
      tpSetWriteMode(MODE_WITHOUT_RESPONSE);
    }
    else
    {
      Serial.println("Failed to connected :(");
      while (1) {};
    }
  } // if scan
  else
  {
    Serial.println((char *)"Didn't find a printer :( ");
    while (1) {};
  }

  camera_config_t config;
  config.ledc_channel = LEDC_CHANNEL_0;
  config.ledc_timer = LEDC_TIMER_0;
  config.pin_d0 = Y2_GPIO_NUM;
  config.pin_d1 = Y3_GPIO_NUM;
  config.pin_d2 = Y4_GPIO_NUM;
  config.pin_d3 = Y5_GPIO_NUM;
  config.pin_d4 = Y6_GPIO_NUM;
  config.pin_d5 = Y7_GPIO_NUM;
  config.pin_d6 = Y8_GPIO_NUM;
  config.pin_d7 = Y9_GPIO_NUM;
  config.pin_xclk = XCLK_GPIO_NUM;
  config.pin_pclk = PCLK_GPIO_NUM;
  config.pin_vsync = VSYNC_GPIO_NUM;
  config.pin_href = HREF_GPIO_NUM;
  config.pin_sscb_sda = SIOD_GPIO_NUM;
  config.pin_sscb_scl = SIOC_GPIO_NUM;
  config.pin_pwdn = PWDN_GPIO_NUM;
  config.pin_reset = RESET_GPIO_NUM;
  config.xclk_freq_hz = 20000000;
  config.pixel_format = PIXFORMAT_JPEG;

  // https://github.com/espressif/esp32-camera/blob/master/driver/include/esp_camera.h
  // https://github.com/espressif/esp32-camera/blob/master/driver/include/sensor.h
  config.frame_size = FRAMESIZE_VGA; // // 640x480
  config.jpeg_quality = 30;
  config.fb_count = 1;
  //config.fb_location = CAMERA_FB_IN_DRAM;

  // Init Camera
  esp_err_t err = esp_camera_init(&config);
  if (err != ESP_OK) {
    Serial.printf("Camera init failed with error 0x%x", err);
    Serial.println("");
    return;
  } else {
    Serial.println("Camera init");
  }

  pinMode(4, INPUT);
}

void loop() {
  Serial.println("Waiting for red button press!");
  while (digitalRead(4)) {};

  camera_fb_t * fb = NULL;
  // Take Picture with Camera
  fb = esp_camera_fb_get();
  if (!fb) {
    Serial.println("Camera capture failed");
    return;
  } else {
    Serial.println("Camera captured");
  }

  uint8_t *pImage;
  int iImageSize;

  iWidth = tpGetWidth(); // get the width of the printer in pixels
  Serial.printf("Printer width is %d\n", iWidth);

  Serial.printf("Jpg is %d x %d\n", fb->width, fb->height);

  pImage = (uint8_t *)fb->buf;
  iImageSize = (int)fb->len;

  if (jpg.openRAM(pImage, iImageSize, JPEGDraw)) {
    jpg.setPixelType(ONE_BIT_DITHERED);
    jpg.decodeDither(ucDither, JPEG_SCALE_HALF);
    Serial.printf("Decoded %d x %d\n", jpg.getWidth(), jpg.getHeight());
    Serial.printf("Last jpg decoder error %d\n", jpg.getLastError());
  }
  esp_camera_fb_return(fb);

  tpFeed(32); // advance the paper 32 scan lines
  Serial.println("Finished printing!");
} 
bitbank2 commented 2 years ago

I'm wondering why it works at all :) The JPEGDRAW callback can send you any "shape" of rectangle because it's decoding MCUs which can be 8x8, 16x8, 8x16 or 16x16. It fits as many as it can in the 4k internal buffer for each call. This means that you can get a callback for the left half of a 16-pixel row followed by a call for the right half. This won't print properly in this case.

I think it would be wise to allocate a buffer big enough for the whole decoded image, decode into that buffer and then print it in one shot.

nicholas-gh commented 2 years ago

Yes, awesome - that was it. JPEGDraw callback now copies in to a large buffer, and when it's all decoded, then I sent it to the printer.

It's very hard coded now, since I didn't make it cope will variable answers for how much it needs to pad each row (since the camera image isn't the ideal size for the printer rows). I don't really want to show exactly how lazy I did it, but I will since maybe it helps the next person...

uint8_t fullBuffer[(PRINTER_WIDTH * (VGA_HEIGHT / 2)) / 8] = {1};

int JPEGDraw(JPEGDRAW *pDraw)
{
  int i, iCount;
  uint8_t *s = (uint8_t *)pDraw->pPixels;

  Serial.printf("We got a pixel block of %d x %d\n", pDraw->iWidth, pDraw->iHeight);

  // The output is inverted, so flip the bits as we copy to fullBuffer
  // Hard coded for what we get from the camera jpg decoding (4 full rows)
  // TODO cope if we got something else
  for (i = 0; i < 40; i++)
    fullBuffer[fullBuffer_i++] = ~s[i];
  fullBuffer_i += 8; // pad row
  for (i = 40; i < 80; i++)
    fullBuffer[fullBuffer_i++] = ~s[i];
  fullBuffer_i += 8; // pad row
  for (i = 80; i < 120; i++)
    fullBuffer[fullBuffer_i++] = ~s[i];
  fullBuffer_i += 8; // pad row
  for (i = 120; i < 160; i++)
    fullBuffer[fullBuffer_i++] = ~s[i];
  fullBuffer_i += 8; // pad row

  Serial.printf("Current fullBuffer_i is %d of %d\n", fullBuffer_i, sizeof(fullBuffer));
  return 1; // Continue decode
}

But it works well. I'll do some more work and maybe have it print sideways to get more useful pixels out.

Many thanks!

nicholas-gh commented 2 years ago

PS: well, it kind of works; the image printed seems to be a previous captured image. I assume I'm missing something on the esp-camera end there! (Edit: https://www.esp32.com/viewtopic.php?t=24566 but no answers so far)