-
Google code...
03/18/2015 at 11:33 • 0 commentsHi there,
To anticipate the soon closure of Google code, here is the GitHub repository for the raspberrypi Java code.
https://github.com/pierre-muth/blueprinter-pi
And a big thanks to Adafruit for relaying the project :
-
Java Adventure
01/22/2015 at 16:48 • 0 commentsJava adventure:
I personally know Java better than other language such as python or C/C++. That's why I started to code this project in Java. Since Oracle provided their virtual machine, the Raspberry pi is reasonably capable to run Java. To do so, I installed a Samba server on the Raspberry pi, and use Eclipse IDE on another computer (on windows...). Eclipse take care of the compilation, and we just have to launch the code, remotely with ssh or locally, on the Raspberry pi.
I used also a Google code SVN repository : link
Remember the code I 'produce' is quick and dirty. I wanted to see something out of the prototype...
Control the Thermal printer:
As mentioned on hardware part, the tricky part of interfacing the A2 thermal printer is data flow sent over the serial port. I started first by estimated the black dots on each lines, and wait the corresponding heating time. I used the system nanoTime() to sleep the code more precisely, but I'm still not sure of accuracy…
for (int i = 0; i < length; i++) { if (i%255 == 0) { //we can only send by 255 lines max int remainingLines = length-i; if (remainingLines > 255) remainingLines = 255; printLineCommand = new byte[] {0x12, 0x2A, (byte) remainingLines, 48}; for (int j = 0; j < printLineCommand.length; j++) { start = System.nanoTime(); serial.write(printLineCommand[j]); } } bitONCount = heatingDotsMax -1; heatingStepsCount = 0; for (int j = 0; j < 48; j++) { byte imageByte = imageBytes[(i*48)+j]; serial.write(imageByte); bitONCount += Utils.countBitOn(imageByte); if (bitONCount >= heatingDotsMax) { bitONCount = 0; heatingStepsCount++; } } switch (heatingStepsCount) { case 1: heatingTimeUs = 2000; break; case 2: heatingTimeUs = 4800; break; case 3: heatingTimeUs = 10000; break; case 4: heatingTimeUs = 15000; break; case 5: heatingTimeUs = 20000; break; case 6: heatingTimeUs = 25000; break; default: heatingTimeUs = 0; break; } if (heatingTimeUs > 0) { start = System.nanoTime(); do { end = System.nanoTime(); } while(start + (heatingTimeUs*1000) >= end); } }
It was not very easy to determine correctly these delays, and finally some picture condition still makes the printer not behave correctly.
For the next step, I cabled one of the step motor driving line on an input of the raspberry pi. I had some doubt about the ability of Java to handle interrupt at about 200Hz. But not at all, thanks to the nice Pi4J library, every transition result to an event, makes easy to count the printed number of line.
if (lineSent > 50) { //start to fill the buffer with 50 lines int i=0; while ( (lineSent - linePrinted.get()) > 80 && i < 20) { // if we sent more than 80 lines in advance, wait a millisecond... try { sleep(1); i++; } catch (InterruptedException e) { } } }
With this method I could experiment more easily the printer parameters. I found for the moment these parameter gives reasonably good results:
Setting Control Parameter Command: ( ESC 7 n1 n2 n3 )
heatingMaxDot = 11; ((11+1) *8) = 96 dots, so max 4 heats for a black 384 dots line
heatTime = 70;
heatInterval = 250;Set printing density: (DC2 # n) I didn't see clearly differences when I change these parameter (??!)
Listen for new file in a folder:
printDensity = 0;
printBreakTime = 0;As the Bluetooth transfer is done in the background, we have to observe the destination folder, and process new images. Java implements some strategies for that with WatchService on files. I took the examples on Oracle :
try { watcher = FileSystems.getDefault().newWatchService(); } catch (IOException e1) { e1.printStackTrace(); } Path dir = Paths.get("/home/pi/project/piprint/"); try { key = dir.register(watcher, ENTRY_CREATE, ENTRY_MODIFY); } catch (IOException e) { e.printStackTrace(); } WatchKey akey; for(;;) { try { akey = watcher.take(); for (WatchEvent<?> event: key.pollEvents()) { @SuppressWarnings("unchecked") WatchEvent<Path> ev = (WatchEvent<Path>)event; fileToPrint = ev.context().toString(); screen.display(fileToPrint); } akey.reset(); } catch (InterruptedException x) { return; } }
And here I faced another problem. When a file is transmitted, the Obex system on the background start by create the file, and then, during transfer, re-open the file, save what we already received, and close it. It redo that until the transfer is finished. That means we received event even if the transfer is not finished. That's why I choose to have a button to launch the printing. I have to find a way to detect when the transfer is finished to print the file automatically..
Convert the image to Black OR white:For the moment, image processing is limited to re-size and rotate the image, convert in gray scale, and compute the dithering. The rotation/re-size/gray-scale is implemented with Graphic2D (.rotate(Math.PI/2); and the .createGraphics() of a new BufferedImage).
For dithering, I used the error diffusion Floyd–Steinberg dithering method. It is very efficient, but within Java, image had to be converted to byte arrays :
.
final BufferedImage monoImageRotated = new BufferedImage(resizedImageWidth, resizedImageLength, BufferedImage.TYPE_BYTE_GRAY); g = monoImageRotated.createGraphics(); g.rotate(Math.PI/2); g.drawImage(monoImageresized, 0, -resizedImageWidth, resizedImageLength, resizedImageWidth, null); g.dispose(); pixList = new int[resizedImageLength * resizedImageWidth ]; int[][] pixArray2D = new int[resizedImageWidth][resizedImageLength]; int[][] pixArray2Ddest = new int[resizedImageWidth][resizedImageLength]; monoImageRotated.getData().getPixels(0, 0, resizedImageWidth, resizedImageLength, pixList); // init the arrays for (int i = 0; i < pixList.length; i++) { pixArray2D[i%resizedImageWidth][i/resizedImageWidth] = (int) ((pixList[i])); pixArray2Ddest[i%resizedImageWidth][i/resizedImageWidth] = 0; } int oldpixel, newpixel, error; boolean nbottom, nleft, nright; // dithering for (int y=0; y<resizedImageLength; y++) { nbottom=y<resizedImageLength-1; for (int x=0; x<resizedImageWidth; x++) { nleft = x>0; nright = x<resizedImageWidth-1; oldpixel = pixArray2Ddest[x][y] + pixArray2D[x][y]; if (oldpixel<brightnessThreshold) newpixel = 0; else newpixel = 255; pixArray2Ddest[x][y] = newpixel; error = oldpixel-newpixel; if (nright) pixArray2Ddest[x+1][y] += 7*error/16; if (nleft&&nbottom) pixArray2Ddest[x-1][y+1]+= 3*error/16; if (nbottom) pixArray2Ddest[x][y+1] += 5*error/16; if (nright&&nbottom) pixArray2Ddest[x+1][y+1]+= error/16; } } // Make a 1D array for (int i = 0; i < pixList.length; i++) { pixList[i] = pixArray2Ddest[i%resizedImageWidth][i/resizedImageWidth]; } // Makes 1dot = 1bit of the Byte array result = new byte[(resizedImageWidth/8)*resizedImageLength]; int mask = 0x01; for (int i = 0; i < result.length; i++) { for (int j = 0; j < 8; j++) { mask = 0b10000000 >>> j; if ( (byte)(pixList[(i*8)+j]) == 0 ) { result[i] = (byte) (result[i] | mask); } } } return result;
And the result :
Close-up:
-
Bluetooth Adventure
01/22/2015 at 09:59 • 1 commentBluetooth and RaspberryPi:
Fortunately, a lot of Bluetooth USB keys are supported by the Raspbian linux distribution. Some Goggling and you find how to install it:
It starts with the installs :
sudo apt-get install bluetooth bluez obexpushd
Followed by the config.
sudo hciconfig hci0 name "my_funny_name"
This is to change the name seen by the other devices when scanning.sudo hciconfig hci0 piscan
This is to make it discover-ablesudo bluetooth-agent -c NoInputNoOutput 1234
This is to make it pair-able with the code "1234" and with no confirmation on the raspberry side. (I found it's quite hard to find information on bluetooth-agent command line option...)sudo obexpushd -B -o /home/pi/project/piprint/
And finally start the file server, which auto accept file and save them in the specified folder.To make it each time after boot, I edited the /etc/rc.local file. By adding these lines:
sleep 2
sudo bluetooth-agent -c NoInputNoOutput 1111 &
sleep 2
sudo hciconfig hci0 piscan &
sleep 2
sudo obexpushd -B -o /home/pi/project/piprint/ &
sleep 2
cd /home/pi/project/piprint/bin
sudo java -classpath .:/home/pi/project/piprint/lib/'*' piprint.Launcherexit 0
Last lines are to launch the printer program, that I'll describe a bit later.
-
Thermal Printer adventure
01/21/2015 at 18:41 • 0 commentsThe thermal Printer:
I was first fascinated by the cheap thermal printer made by Cashinotech. Their model CSN-A2 can be found quite easily. By seeing the datasheet link, the printer might be compatible with ESC/POS protocol. It use a serial port at 115200 baud, TTL level.
I start to connect a scope on the serial lines. In my case the pull-up on TX and RX are not 5v but 3.3v. I can then safely connect it to the RaspberryPi serial port.
Then I faced to the behavior of the printer when you want to print a bitmap image. It takes me some time to understand why the printer speed is not constant. It implies also to understand correctly the parameters, which is not very easy from the datasheet.
Many faced to this problem and it's written on the Adafruit arduino code, there is no flow control to avoid overrunning the printer's buffer. If we send data too fast, the printer could either crash, print garbage characters, or just jump into newer data. If we send data too slowly, the printer wait and makes some stops, and the printed image have some white lines.
The printing module inside the printer is a "FTP-628MCL101" link. By combining the datasheets, we can suppose the meaning of the configuration parameters of the printer. The "max heating dots" is apparently the amount of black dot (heated) can be heated at ones. A line is 384 dots, if you put this parameter to 64 as the default ((7+1) *8), it means that for a black line, it takes 6 heating steps. Which a step time is the parameter "heating time" plus "heating interval". We cannot heat all the 384 dots at ones, probably due to the current peak. So here we can try to predict the printing speed to not overrun the data buffer, by counting the black dots sent to the printer, and make some stops. That is for the case when the printer is slower than the data rate. But when there is blank of very light parts on a bitmap, I face the inverse where the printer stops to wait data.
I found someone who also dig quite far on this printer , and he found a software link to change the serial speed from 19.2 Kbaud to 115.2 Kbaud. In addition, it also allow to change the character set from chinese to international.
Even knowing that, it is very hard to find the right delays and I still faced to wrong behaviour. I had a better look to the board of the printer.
The micro used is a NXP LPC1114. The stepper motor driver is an Allegro A3906. The motor makes a step for each printed line. I connected one of the 4 motor driving input lines to a scope. To do so, I used the brutal method. I scratch the copper line to solder directly a wire. (And use an unused pad to secure it)
And yes: each transition correspond to a step. "Bingo" I have a way to know how much line is printed, and have to take care of not sending too much bytes in advance.