Close

Fontastic!

A project log for HP 82240B IR Receiver and Printer Interface

This is an IR receiver and interface for adapting a commodity receipt printer to be 82240B-compatible.

ziggurat29ziggurat29 07/11/2017 at 17:320 Comments

Summary:

My hunting for an existing HP82240B font paid off, and a couple days drugery was avoided. I have converted the found glyph images into my personal format and have implemented the rendering engine for the HP82240B data stream.

Now I am off to work on the physical printer driver. Or not.

Deets:

As mentioned in a previous post, I wanted to look for some existing TTF fonts that might already exist for the HP82240B printer. Sometimes folks create these for simulators to give an authentic look to the output. Having such would save me many hours of manually digitizing the 448 code points (well, OK, about 1/3 less than that of /distinct/ code points) from printouts since I already have a tool for rasterizing TTF to my personal font format.

At first I found 'HP Printer' somewhere, but it was not for this printer, so no good. In the end, I never found a TTF font, but I did stumble across a printer simulator produced by Christoph Gießelink

http://hp.giesselink.com/hp82240b.htm

and somewhere in my travels (I can't recall where, now, alas; so much searching) I found source code bearing this imprint:

/*
These are the HP82240B font tables for the Roman8 and ECMA94 character set.

The code is part of my HP82240B Printer Simulator available at
http://hp.giesselink.com/hp82240b.htm.

This code is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE.

There's no licence behind, so use it in any way you like it.

May 2012, Christoph Giesselink
*/

which I interpret as being functionally public domain.

The data was not useable as-is because it is in column-major form, and I need row-major. So, 'big whoop', I wrote a little one-off program (significantly derived from my existing TTF rasterizer -- it wasn't that much new work) to do the relevant rotations and emit font data.

I have no way of verifying it at this time, but the rasterizer program emits comments showing the image form of the bit pattern, so it seems to be good. Here's a couple examples:

a couple from the ASCII section

	//glyph for 0x37 (7), 8x6, at offset 184
	0x00, 	//........
	0x1f, 	//#####...
	0x10, 	//....#...
	0x08, 	//...#....
	0x04, 	//..#.....
	0x02, 	//.#......
	0x02, 	//.#......
	0x00, 	//........

	//glyph for 0x33 (3), 8x6, at offset 152
	0x00, 	//........
	0x0e, 	//.###....
	0x11, 	//#...#...
	0x0c, 	//..##....
	0x10, 	//....#...
	0x11, 	//#...#...
	0x0e, 	//.###....
	0x00, 	//........

and one from the high set:

	//glyph for 0x9f, 8x6, at offset 1016
	0x00, 	//........
	0x0e, 	//.###....
	0x12, 	//.#..#...
	0x12, 	//.#..#...
	0x12, 	//.#..#...
	0x00, 	//........
	0x00, 	//........
	0x00, 	//........

and so on, for the 'Roman 8' and 'ECMA94' sets. Yay! Time saved. (I did send a 'thank you' note to Christoph Gießelink for his work.) With that leg-up, I proceeded to implement the printer state machine, and rasterizer.

As mentioned, for the rasterizer I am reusing some character blitting code I had already produced for another project, so that part was pretty easy. I did hack it to support wide characters and underline mode, and exploit some peculiarities of this printer.

The state machine wound up being pretty easy, too. There's just a few states for TEXT, GRAPHICS, and ESCAPE states. The TEXT state checks for a couple special characters, and otherwise does a character blit of the incoming character. The GRAPHICS state instead sends the data to a 'bar blt' routine, which sets the pixels in a vertical bar according to the bit pattern of the received data. The ESCAPE state checks for the few known escapes and otherwise interprets the data as the 'length' parameter before transitioning to the GRAPHICS state.

The machine invokes some callbacks that the user is expected to implement. Notably, the 'image complete' callback is invoked when a carriage return is encountered. The expected implementation would be to do something useful with the completed bitmap before returning (whereupon it will be cleared and ready to be re-rendered).

It might be handy to have a block diagram of the system at this point:

(sorry, I don't do Visio; takes too long) Short story:

  1. main() cranks up the hardware and subsystems. There is a tool-generated 'default' task that I don't really need, but I choose not to fight the tool, so I keep it around to do debug things, like monitor the heap and turn off leds I use in debugging.
  2. the physical hardware devices are directly managed by ISR code, which indirectly interacts with the rest of the system through queues and event notifications. This makes the hardware devices principally look like byte streams.
  3. the bulk of the system produces/consumes events and data to/from those streams, and is (currently) realized with three 'tasks' (threads/processes):
    a) IR data processor
    consumes incoming IR data and runs the HP printer rasterizer machine. Since that machine invokes it's event callbacks, those callbacks are therefor running in the context of this task.
    b) Printer rasterizer
    is notified of completed bitmaps from the IR data processor, and transforms those into native printer commands for the physical printer.
    c) Monitor
    I haven't done any work here, but this task is responsible for running a command interpreter attached to the USB CDC device I recently completed. It is intended to take configuration commands and provide diagnostic debug output, and also to potentially serve as a 'tee' of the incoming IR data, so that the interface board also can serve as an 'HP IR Redeye to Serial' converter.
    d) default
    As mentioned, the STM32CubeMX tool generates a 'default' task, so I am just leaving it in-place for now, rather than fighting with the tool when I regenerate code. All it's doing now is sleeping, and periodically waking to sample heap usage and turn off any LEDs that may have otherwise been turned on when errors (like parity errors on IR, etc) occur. I might do something more creative with it later, or maybe I'll merge the Monitor task into this one. But right now they are separate.

The only other thing of note I think is that I'm using FreeRTOS 'task notifications' where possible, and I'm using a binary semaphore between the 'IR data processor' and the 'Printer rasterizer' because I think I must. Their interaction is pretty simply, but here it is:

'IR data processor' renders a bitmap via the rasterizing engine. That engine makes a callback that the bitmap is complete. The callback must do it's business before returning, because upon return the bitmap will be cleared and reused for further incoming data. I could do all the subsequent printer data rendering right now, but I chose instead to do that in a separate concurrent task.

The original printer was designed as a one-way communications system, and in that design the sender must be conscientious about pacing it's output such that the physical printer has adequate time to do things like move it's print head, etc, because there's no communications channel back to signal that those things are completed. So it's a 'Hail Mary' system. As such, I have plenty of time between lines being sent to re-rasterize and send to the new physical printer (plus, the new printer is much faster and quieter).

That being said, I prefer to be more robust, and I've got the flash and ram to do better, so I'm instead implemented a double-buffer scheme where the Printer rasterizer is in a separate task, and has it's own copy of the source image. Now, when the IR data processor/rasterizer calls back to indicate that a completed bitmap has been rendered, the following happens:

  1. IR data processor acquires a lock (implemented as a FreeRTOS binary semaphore).
  2. memcpy() the rendered buffer to the secondary buffer
  3. release the lock
  4. task-notifies the Printer data processor that there is a new bitmap to process
  5. goes back to immediately being ready to process more IR data

    and then the Printer data process will awaken...
  6. Printer data process awakens from the task notification
  7. acquires the lock on the secondary buffer
  8. parties on the data however it wants
  9. release the lock on the secondary buffer
  10. goes back to sleep waiting on a new task notification

I think this decouples the two subsystems (except of course for the semaphore and task notification) and allows them to process at their leisure. Stalls will eventually be propagated back through the various locks, with critical failure occurring when the IR data queue overflows and otherwise naturally draining back out whenever the stall condition is removed.

This arrangement turned out to be handy sooner-than-later when I implemented the 'Reset' command in the HP data rasterizer. That command is implemented as sending 12-lines of simulated printer data to be rasterized. I didn't need to put in any artificial delays -- I could send it full throttle. I'm looking forward to having the physical printer to see how that command executes....

Next:

I haven't really been able to test all of this to my satisfaction because I am only rendering to in-memory images, and passing them off to a second task to be processed. Now I can either:

  1. work on the monitor task, so I can, say, hex dump the output to study offline
  2. punt on testing this for now, and work on implementing the Printer data rasterizer

I haven't decided which, they both have their charms...

Discussions