-
1Resize the Image
The QR204 thermal printer has a maximum printable width of 384 pixels. Any image wider than this will either be cropped or distorted.
Recommended image size:
Width : 384 pixels
Height : Any value (280 pixels used in this project)Width : 384 pixelsHeight : Any value (280 pixels used in this project)
If your image is larger, resize it before importing it into Image2CPP.
-
2Open Image2CPP
Open Image2CPP and load your image.
Configure the settings exactly as shown below.
Image Settings
Background
White
Color Mode
Monochrome
Brightness
128 (default works well)
Output Setting
Select
Horizontal - 1 bit per pixel
Disable
Compression
Output Format
Arduino Code
Do not enable any byte swapping or special encoding options.
Finally, click Generate Code.
Image2CPP will produce something similar t
const unsigned char epd_bitmap[] = {0xFF,0xFF,0xFF,...};Copy the complete array.
Arduino Boilerplate
The following sketch acts as a reusable template.
The only thing you'll need to replace in future projects is the Image2CPP array.
Simply paste the generated bitmap inside:
const uint8_t epd_bitmap[] = { // Paste Image2CPP output here};Then update
#define IMAGE_WIDTH#define IMAGE_HEIGHT
Everything else remains unchanged.
Understanding the Printer Commands
The QR204 printer communicates using ESC/POS commands.
The first command initializes the printer.
PRINTER.write(0x1B);PRINTER.write('@');This clears the printer's internal state before sending new data.
Next comes the raster bitmap command.
PRINTER.write(0x1D);PRINTER.write('v');PRINTER.write('0');PRINTER.write(0);This tells the printer that the following bytes represent a bitmap image.
The printer then needs to know the dimensions of the image.
First, the width is transmitted in bytes, not pixels.
uint16_t bytesPerLine = width / 8;
Since every byte represents eight pixels, a 384-pixel image becomes
384 / 8 = 48 bytes
Those bytes are then transmitted as
PRINTER.write(bytesPerLine & 0xFF);PRINTER.write(bytesPerLine >> 8);
The image height is transmitted in a similar manner.
PRINTER.write(height & 0xFF);PRINTER.write(height >> 8);
Once the printer knows the dimensions, it simply waits for
Width × Height
worth of bitmap data.
The Interesting Discovery
Initially, everything appeared correct.
The ESC/POS command was valid.
The printer received the correct image dimensions.
The bitmap array generated by Image2CPP was also correct.
Yet the printer produced:
- Scrambled graphics
- Thick black bars
- Distorted logos
- Random symbols
At first, it looked like a communication problem.
However, after testing different baud rates, power supplies, image sizes, and ESC/POS commands, the real issue turned out to be much simpler.
The QR204 printer expects every bitmap byte to be transmitted with its bit order reversed.
For example,
Normal
10010000
must become
00001001
before sending it to the printer.
This tiny detail completely changes the output quality.
Reversing the Bit Order
The following function reverses all eight bits inside a byte
uint8_t reverseBits(uint8_t b){ b = (b & 0xF0) >> 4 | (b & 0x0F) << 4; b = (b & 0xCC) >> 2 | (b & 0x33) << 2; b = (b & 0xAA) >> 1 | (b & 0x55) << 1; return b;}Before transmitting every byte, simply execute
uint8_t b = reverseBits(bmp[i]);PRINTER.write(b);
This single line solved the bitmap rendering problem entirely.
Printing the Bitmap
The complete bitmap printing routine calculates the image size, initializes the printer, sends the raster command, transmits the dimensions, and finally streams the bitmap data byte by byte.
Since Image2CPP already generates a perfectly formatted bitmap array, the Arduino only needs to iterate through every byte and send it to the printer after reversing its bit order.
The result is a sharp, correctly aligned bitmap printed directly onto the thermal paper.
First Successful Print
After implementing the bit-reversal function, the QR204 immediately produced a clean bitmap with correct proportions and alignment.
This approach works reliably for
- Company logos
- School logos
- QR codes
- Icons
- Small graphics
- Certificates
- Attendance slips
- Custom receipt
Because the code uses a reusable boilerplate, printing a different image is as simple as replacing the Image2CPP array with a new one.
Troubleshooting Guide
One of the most interesting aspects of this project was the amount of debugging required before achieving a successful print. Although printing plain text worked immediately, printing bitmap graphics involved several challenges. This section summarizes the most common issues encountered during development and explains how each one was resolved.
If your printer doesn't produce the expected output, the following troubleshooting steps will help you identify the problem quickly.
Problem 1 – Random Characters Instead of an Image
Typical output looked like:
<B B<X(Hello
Cause
The printer was interpreting the bitmap bytes as regular text instead of raster graphics.
Possible reasons include:
- Incorrect ESC/POS command sequence
- Wrong baud rate
- Corrupted serial communication
Solution
Verify that the printer is configured for 9600 baud and ensure the bitmap is transmitted using the GS v 0 raster image command.
Problem 2 – Only Text Prints
Sometimes the printer would print:
END
without printing the image.
Cause
The raster command was being accepted, but no valid bitmap data followed.
Solution
Check:
- Image width
- Image height
- Total number of transmitted bytes
The total bitmap size should always be:
(width / 8) × height
For a 384 × 280 image:
48 × 280 = 13,440 bytes
If fewer bytes are transmitted, the printer simply skips the image.
Problem 3 – A Single Black Dot
One of the earliest tests printed only a single black dot followed by normal text.
Cause
The raster command itself was correct.
However, only one byte of image data was reaching the printer.
Solution
Increase the bitmap width and verify that the complete image buffer is transmitted.
This test confirmed that the printer supported raster graphic
Problem 4 – Thick Black Horizontal Line
Increasing the transmitted data produced a thick black horizontal line.
Although this wasn't the desired image, it actually confirmed something very important.
It proved that:
- Communication was working correctly.
- ESC/POS raster commands were supported.
- The printer was interpreting bitmap data successfully.
The only remaining issue was the bitmap layout.
Problem 5 – Completely Distorted Image
After transmitting the full bitmap, the printer produced a heavily distorted image.
At this stage it appeared as though the bitmap itself was incorrect.
Several possibilities were investigated:
- Wrong raster command
- Incorrect width calculation
- Wrong image orientation
- Data alignment issues
- ESC * graphics mode
- GS v 0 raster mode
- Image2CPP settings
- Printer firmware differences
None of these solved the issue.
The Actual Solution
After carefully analyzing the output, it became clear that the printer expected every byte with its bit order reversed.
Instead of transmitting
10010000
the printer expected
00001001
The following function solved the problem completely.
uint8_t reverseBits(uint8_t b){ b = (b & 0xF0) >> 4 | (b & 0x0F) << 4; b = (b & 0xCC) >> 2 | (b & 0x33) << 2; b = (b & 0xAA) >> 1 | (b & 0x55) << 1; return b;}Every transmitted byte is processed using:
PRINTER.write(reverseBits(bmp[i]));
After implementing this single change, the bitmap printed perfectly.
Problem 6 – Black and White Colors Swapped
Sometimes the printed image appeared as a photographic negative.
White became black and black became white.
Cause
The bitmap bytes were inverted before transmission.
~b
Solution
Do not invert the data.
Simply transmit
reverseBits(b);
This preserves the correct pixel polarity.
Problem 7 – Printer Resets During Image Printing
Printing text worked correctly.
However, larger images caused incomplete prints or printer resets.
Cause
The printer was initially powered directly from the Arduino's 5V pin.
While text printing consumes relatively little current, printing a bitmap activates hundreds of heating elements simultaneously, causing the current demand to increase significantly.
Solution
Use a dedicated:
- 5V
- 2A (or higher)
regulated power supply.
Also ensure that the Arduino ground and printer ground are connected together.
After switching to an external supply, printing became stable and reliable.
Testing Strategy
Rather than immediately sending a large bitmap, smaller tests were used to verify each stage of the communication.
The debugging sequence looked like this:
- Print plain text.
- Initialize the printer.
- Print a single black pixel.
- Print an 8 × 8 square.
- Print a horizontal black strip.
- Print a small bitmap.
- Print the complete 384 × 280 image.
This systematic approach made it much easier to identify exactly where the problem occurred.
Performance Results
After implementing the final solution, the printer successfully produced high-quality monochrome graphics.
Test Platform
Component
Specification
Microcontroller
Arduino UNO R4 WiFi
Printer
QR204 58 mm TTL Thermal Printer
Baud Rate
9600
Image Width
384 pixels
Image Height
280 pixels
Successful Tests
Successful Tests
- Company logos
- School logos
- QR Codes
- Icons
- Small graphics
- Certificates
- Attendance slips
- Custom receipts
Because the code uses a reusable boilerplate, printing a different image is as simple as replacing the Image2CPP array with a new one.
- School logos
- Custom graphics
- Icons
- QR codes
- Receipt headers
- Attendance system branding
Each image printed cleanly with correct proportions and sharp edges.
Real-World Applications
With bitmap printing working correctly, the same boilerplate can now be integrated into numerous embedded systems.
Some examples include:
- Smart School Attendance System
- RFID Access Control
- Library Management Systems
- Hospital Registration Kiosks
- Restaurant Billing Systems
- POS Machines
- Smart Inventory Management
- Visitor Management Systems
- IoT Receipt Printing
- Event Ticketing
- Portable Label Printers
- Industrial Data Logging
Instead of printing plain text, these applications can now include company logos, QR codes, signatures, custom icons, and branded graphics, giving the final product a much more professional appearance.
Rohan Barnwal
Discussions
Become a Hackaday.io Member
Create an account to leave a comment. Already have an account? Log In.