-
If You Can't Beat 'Em, Beat 'Em Into Submission
07/21/2017 at 02:34 • 0 commentsSummary:
A quirk of one of the printers, the APS EPM203-MRS, could not be disabled, but was worked-around.
This project is now functionally complete.
Deets:
Yesterday I had mentioned that one of my printers, the APS EPM203-MRS has a quirk whereby it gratuitously advances the paper before printing bitmaps. Moreover, it only does this sometimes, when there has not been prior output for 'a while'. This made my output look weird on that printer, and I don't like weird things. (OK, that's a general lie, but in this limited case, it's true.)
I scoured the manual looking for some way to turn off the paper advance feature/wart/whatever. Look as I may, and look as I might, there was no such feature found that night. I spoke 'the uncommon words of anguish', and retired for the night.
This morning I awoke to discover that I had turned into a giant cockroach. This situation only lasted a few minutes, but when it had ended I was left with a kooky idea: maybe I can just compensate for the paper advancing by doing a reverse paper feed before emitting the graphic. The sand in that vaseline is that the paper advance only happens /sometimes/, after there has been a period of output quiescence. If the data stream keeps coming to the printer with some reasonable pacing, no gratuitous paper feed is done. (Thank the maker for that little blessing -- really every horizontal line of pixels is it's own distinct graphic. It would be quite a mess if there was a paper feed between each horizontal line!)
So, sometimes, if you are faced with an inability to make things reliably the way you want them to be, you can as an alternative approach make them reliably the way you /don't/ want them to be. If I can make /every/ graphic output reliably have the unwanted paper feed, then I can reliably always do the workaround of the reverse paaper feed beforehand.
The approach I took wound up being a fairly surgical change:
- at then end of transmission of data to the printer, note the time, call it g_nLastTx. Initialize that to 0 at boot, btw. I'm just using the HAL_GetTick() which returns milliseconds since boot.
- before doing ouput, compute 'now' - 'then'. (I.e. HAL_GetTick() - g_nLastTx) 2s complement will make all your rollover worries better (mostly; more later). Call this difference 'duration'.
- if the 'duration' is longer than the safe amount of time to guarantee that the paper advance will happen, then sleep (via osDelay() in the CMSIS API) for the difference.
- now you should be guaranteed that doing a 'reverse paper feed' of a few pixels will counteract the gratuitous paper feed that you know the printer is going to insert.
OK, subtle detail on the timer rollover: we covered the usual case of 'I started just before the counter rolled over, and checked after it did, how do I handle that' with the unsigned 2s complement subtraction, but there is another issue when you rollover twice or more. Then the 2s complement doesn't help you. OK, the timer rollover will happen after 49 days, so you'd have to wait twice that time, so over 3 months of powered on but disuse before you'd be at risk of it. But I'm taking this thing to Jupiter and I can't have Hal telling me 'I'm sorry Dave, I can't open the pod bay doors until I get the output from your thermal printer.' and have to wait for 49/2=25 days while oxygen runs out. (Hal does love its petty torments.)
So I covered that scenario with a little hack. A 'haque', really -- it's not that dirty. The printer rasterizer process blocks waiting for a signal that a birmap is ready to be rasterized. That blocking has a timeout, which I arbitrarily set at 5 seconds. The intention of that is to let the thread awaken to realize that there hasn't been anything to do for a while, but that is nonetheless still alive, and it can just carry on blocking again. This opportunity can be used to reset watchdogs or other idle-time activities. In this case, I made an idle-time activity of forwarding notification of that event into the rasterizer. The rasterizer then simply updates the last transmit time (g_nLastTx) to 'now'. It's not /really/ the last transmit time, but I only care about the time difference being below a certain amount, so just means that g_nLastTx represent 'it happened at least this long ago in the past, but maybe longer'. OK, so now it's safe to go to Jupiter....
The result... worked. It was interesting. It certainly made for quite a jittery printout experience, I could possibly embed a covert Morse code signal in the jumpiness, or maybe the printer can double as a massager. I became practical again and decided to 'window' the jitterbug action into an upper bound (which I had already done: if there hasn't been enough time, sleep and then do the reverse feed), and a lower bound (if new output has come in surely and safely soon enough, then don't even jitterbug at all -- just do like all the sane printers do and send data freely).
This worked best. Here is the new final result, side-by-side with a real HP printer output. I set my contraption near the HP printer, so their bathing in the same sunlight and outputting simultaneously from the same IR signal.
Pretty darn close! I marked the two printouts 'HP' from a real HP82240B printer, and 'APS' from this project using the APS EPM203-MRS driver (the GoojPrt driver is also supported. The Kashino driver is incomplete -- that printer doesn't have flow-control, so I would need to add transmission pacing before I could call that 'supported'.)
This takes it about to the limit of what I intend to do on this project at this time. The physical differences in the printer mechanisms preclude getting the output pixel-perfect, but I think for practical use, this is more than serviceable.
The next thing I want to do is shoot a video of the thing live and in operation, but I need to get a tripod and a camcorder from a friend. In advance of that, I think I'm calling this project is 'complete' for now. I do intend to come back to it for the 'monitor' implementation to support various enhancements, but I have other projects calling for my attention just now, alas.
Next:
In the near term, I'm goign to try to shoot a demo video. Also, I would like to get a better power supply for the GoojPrt so I can turn up the darkness on that unit.
Longer term, I may revisit it for the 'monitor' enhancement.
Longer longer term, I may look at interfacing directly with the printer core, rather than the printer with module. The printer core is closer to the metal of the stepper motors and the thermal head. This part is cheaper than these panel printers. The panel printer I am using here is useful for integrators, because it has a controller board that exposes a high-level print language and controls those lower level electronic elements. However, why do I need that here? This controller is already low-level, so why not interface directly. And SOOOO much more CONTROOOOOL! But that sort of project -- fascinating as it might be -- is really more towards producing an actual product you want to build and sell in scale, rather than just fiddling with stuff one-off. So, that will probably never happen. Alas...
-
Success! I'm the Operator on My Pocket Calculator
07/19/2017 at 18:04 • 0 commentsSummary:
Printing to physical printer is now working with both the GoojPrt and APS EPM203-MRS units.
I am now moving onto optimizations and enhancements.
Deets:
I implemented two physical printer rasterizers for two of the sample printer cores: the GoojPrt unit that I was originally going to use, and the APS EPM203-MRS unit that I got later. They both print correctly (with some caveats):
APS EPM203-MRS output is top two, GoojPrt output is lower two. So, printing is a-happinin'! Yay!
There are two caveats, though:
- as mentioned earlier, the electrical design of the GoojPrt leaves some things to be desired. As you can see in those two printouts, the output is rather faint. This is easily addressed with a command that specifies heating/resting times for the thermal head, but using that so far has only resulted in power supply droops that reset the printer, so I am holding off on addressing that now until I get a better power supply. I find this annoying, but the code must go on!
- also as mentioned earlier, the APS unit's firmware has the annoying tendency of advancing the paper when printing bitmaps (and everything in this printer project is treated as bitmaps so as to realize the 82240 character sets). You can see the effect in the top right printout, where there is noticeable space between the lower printed 'lines'. Those are on separate strips of output bitmap. It doesn't look incredibly horrible here, but that is just good luck. If I were to print, say, a graph, then you would see the intervening paper advances.
That printer happens to have a 'reverse linefeed' command, which in principle I could use to compensate, but the maddening thing is that the gratuitous paper advance only happens if there is a pause in the print data stream greater than some unknown amount (around a second or less). If the pause is below that threshold, there is no advance. You can see this in the top lines of that printout, where the successive rendered bitmaps (three in that case) are output in rapid succession.
This is maddening to me. I am scouring the datasheet to see if this is some feature that can be turned off. I may disassemble the printer's firmware to see if I can find a way to stop it (I disassembled the firmware of the Kashino last week for an unrelated activity).
So, now I am trying to fix the power supply issue for the Gooj, and the gratuitous paper advance issue for the APS. After resolving those, the project will technically be 'done', but is anything really ever 'done'?
As mentioned before, as an enhancement, I want to implement a command-line interface 'monitor' program that runs on the USB side. It would do things like manage settings and whatnot.
As an additional feature, I want to be able to realize some secondary functionality such as:
- send decoded IR to USB port; this would realize an HP82240 to PC adapter for receiving raw data from the calculator for... whatever!
- forward data from USB to printer port; this would realize an PC to thermal receipt printer adapter
- etc.
After that, I should be able to mark the project 'completed'. My favorite task to cross off!
Next:
I want to resolve the annoying paper advance issue in the APS printer, and the annoying power suck issue in the GoojPtr.
I need to capture a video of the printer in operation for the blog.
-
Thoroughly Removing The Uncommon Words of Anguish
07/18/2017 at 14:09 • 0 commentsSummary:
Reading datasheets can be fun!
Deets:
While coding up the rasterizer, I was rummaging through the datasheet for the Kashino printer. I came upon this statement in the "Features" section of the User Manual:
"Printer control panel built-in GB18030 Chinese character, thoroughly remove the uncommon words of anguish."
I can't make this up; q.v.:
(I don't know why the highlight is there -- it's that way in the PDF itself (with other things).)
I do very much appreciate having access to the the document, but I think they might have fallen short of their stated goal because I did utter some words of anguish upon reading this. But maybe my words were too common to have been thoroughly removed.
Next:
Back to work coding printer rasterizers....
-
My Three Sons; Mike, Robbie, and Chip
07/17/2017 at 23:27 • 1 commentSummary:
I was tired of waiting for my printer to come in from China, so I bought a similar one that ships statewide. Then I realized I actually had a printer on-hand all along from a project some 4 years back. No sooner than I dug up the old printer, the other two arrived the same day. Sheesh. So now I have an embarrassment of riches.
Now, I mount the printers and do intial tests with the integrated system -- finally!
Deets:
In impatience to receive my printer module, I went back to eBay and looked to see if a similar module is available from domestic shipping, which should take less time. There was, and not for not much more cost. Further, as best as I can tell, this is the more premium module on which the other module claims compatibility. This is the APS EPM203-MRS. Moreover, I could find a real datasheet on this model. Still, it would take a week or two to get it in.
Well, this morning (Friday; sorry, late post. It takes me more time to compose these posts than it does to do the work on which I'm reporting!) I awoke with the realization that I actually already have a thermal printer from a project I was doing for a client some 4+ years back. That was a 'Kashino CSN-A2-T'. I did dig that up.
Then when the mailman came, a miracle: both my domestically ordered APS module /and/ my Chinese 'GoojPrt' module arrived! Yikes! Too many printers! Here they are:
Mike, Robbie, and Chip -- err... 'Kashino', 'GoojPrt', and 'APS EPM203-MRS'.
They all three have 'TTL' serial, which I measured to be 3.3 V logic (happily; so I don't have to add level shifters). Howver, the Kashino out-of-box only does 'hail, Mary' style serial, and by that I mean there is no flow control. I can open it up and hack it it onthe controller board (it's there), but the Kashino is now less relevant since my other modules came it.
To my dismay, the APS printer module is just the module -- no connecting cables! Yikes! But there was unexpected hope, because the GoojPrt did come with cables. Here is the 'unboxing':
Fortunately the important connectors are the same on the GoojPrn and the APS, so the Gooj wires will work in both instance. And... since I need to solder the other end of the cable to the controller board, and since the wires are pretty long as it is, that means cutting each in two. So, another set of cables! Yay for wasting money on too many printer modules! I cut the cable assemblies and soldered some pin header terminations on one pair, which will be convenient for me to stick it in a breadboard and/or connecting to the FTDI adapter for testing before getting into the real code for the controller board.
Now, time for a sanity check.
I never could find documentation on the GoojPrn, but it looks suspiciously like the APS unit. I found documentation on the APS unit, and have some for the Kashino, too. The Kashino claims that you hold down the Paper Feed button while powering on, and a test print will spew out. The APS claims that you hold down the Online/Offline button while powering on, then click Paper Feed twice, and then releasing the Online/Offline. OK. None of this worked at all on any of the three printers.
The Kashino and the APS printer just could not be made to do anything interesting with those buttons sequences, and the GoojPrt would only print CP437 once and stop. WTF? Incidentally, the GoojPrt and the APS both come with a strip of paper that is the test page printout, so I know what it should look like. If one came with the Kashino, it's been lost to time. On the Kashino, it just did a paperfeed like the paperfeed button should do, and the APS simply gave me a blank stare. Ah, the joys of documentation on these things.
In desperation to see something interesting happen (I mean, surely all three can't be broken?) I went ahead and wired them up to my FTDI. I guessed on COM port speed -- apparently the Kashino is 19,200 and both the GoojPrn and APS are 9600 -- and sent a simple "Hello!" message. Yay! It printed! So wtf could be the problem?
Moving on again, I tried concoting a binary file of an image. I first had to process a monochrome bitmap and then hex-edit it with the printer commands. It should print by dumping the data to the printer.
This totally did not work; all I got was random junk from the GoojPrt. On the other hand, I don't really have a datasheet for the Gooj, only the APS and Kashino, so maybe the language is different. It definitely is different between those two printers for things like graphics. (Text mode is a no-brainer). Since the APS and Gooj have the same connector, and also because the APS has hardware flow control, I decided to hook up the APS, and concoct the image in it's native language for which I have real docs.
The results were not exciting:
Ugh. What NOW! I do get a little printout of the graphics, then it croaks and all Hell breaks loose. I decided to pull back and make a file with just a few graphics lines, and that worked OK, and I could still emit "Hello!" just fine in between and then send more graphics just fine. Hrmrmrmrmm!
I went to Google and searched and searched and at best I could find some reference to folks having somewhat similar troubles and flashing firmware. In desparation I did something a little dumb: I flashed the GoojPrt with firmware intended for the Kashino. I don't really know what I was thinking, but it does work -- sort of. The buttons no longer function, but all the printer is OK. I can't generate a test page from the buttons anymore, but I can do the same by issuing the command over the serial port.
It wasn't entirely stupid for my to flash the Kashino firmware -- I guessed that the units were very similar because I could issue the same commands (that I could make work) on both those units. It was an act of desparation for me to flash in this manner, but it is still working enough. But it did not really solve my problem, so I do regret it. Plus, I can no longer feed paper from the front panel buttons. :(
I fiddled with this for hours, and eventually went to bed, defeated and dejected.
Then I awoke the next day with a kooky idea: let's look at the power bus. I mean, I'm using a regulated 7.2V 1A power supply, SURELY that is enough, right?
Well, I hooked it to the scope and the V+ line did take a dip when printing my message. But still not a shocking amount. So I tried it with the 'Test Page' on the GoojPrt unit, since that's the only one that did anything when I used the magic button sequence.
Well, that caused a much bigger dip. It then occurred to me that maybe what was happening is that the dip was going below the point that the on-board regulator could accept and still maintain it's 3.3V output, and that the processor might be resetting. It was a little difficult for me to notice at first, because the printer has the annoying habit of flashing the online LED when it is online, so the flicker of the LED due to board reset was not really visible versus the flicker of the LED in normal operational mode.
I tried printing out a longer text message: "Hello, I love you. Won't you tell me your name?" and the printer did reset on that message. So, more black dots == more heat == more current == more power demand, and too much for my 7.5V 1A (!) supply to deliver. (Or it has a different taste in musik.) So I pulled out a 5V 3A supply (I didn't have a 7.5V one, but 5V is within operating specs). SURELY that would be enough, right?
That worked... better! I did get most of the test page printout until it got to "FontB" and then it failed. The FontB printout section has a denser print, so probably the power suck again. Coincidentally, I noticed something: Oh, Jesus, the wire carrying power was quite warm! OK, stupid me, I used too low a guage power line (I was using one of those breadboard jumper wires with the Dupont ends), and was measuring on the hot side of it, so even the dips I saw were doubtlessly smaller than on the other side of that wire. Stupid monkey, stupid!
I substituted a bigger piece of wire and then: Bingo! I got the image!
(sorry, blurry; need a tripod) Sweet mystery of print, at last, I've found you!
OK, I feel a little silly for the power thing; but, Sweet Jesus! this thermal does suck the power down!
However, you may notice the space between the two images. I did not put that there. The APS unit put that there. This might be aesthetically pleasing in some practical receipt printing cases, but I do not want that space at all, since I am rasterizing all my output, and need it to stack up without gaps. I did notice an oddity: If I send the images in rapid succession, then they ARE contiguously, but if a pause a bit, the printer adds the gap. Ugh; I don't have time for this.
In desparation, I formulated a Kashino image and connect the GoojPrt, and that did not exhibit the gap. Yay! For that. But, the image was quite faint. Boo! For that. There is a command to increase the heating time, making the print darker, but then that returned me to the rebooting problem, even with the thick wires. I even added a 1000 uF cap in hopes of supplying transient demand, but to no avail. Sigh. I decided to move on and deal with the faint output for now so that I could finish code, and I'll come back to upgrading the power supply later (I'll have to get one; I don't have a 7.5V 3A). Anyway, all this is wired through the breadboard now so that I can swap connections easily, so all the more reason to move on for now.
I suspect that the APS unit has a better electrical design than the GoojPrt, but that I can still use the Gooj if I can get a beefier supply.
Now, enough of hand-crafted graphics commands -- time to code some rasterization!
Next:
Having gotten past printer quirks, it's time to implement the rasterizer fer real. I think this (at least!) should be straight-forward, especially since my previous work involved me manually cooking up printer commands to realize a successful graphics print-out. The biggest additional work is the integration into the rest of the system, and I think I have that implemented and tested enough such that that will not be too onerous.
-
♫ I've Got (too much) Power! (gonna make you sweat) ♫
07/13/2017 at 18:07 • 0 commentsSummary:
The BluePill board is powered directly from the 5V USB line. The printer requires more power than USB can supply, necessitating an external supply (e.g. 5V wall adapter). Wiring the 5V from an external supply then precludes the use of USB, because the circuitry would directly connect that to the computer's power bus.
Some minor surgery was done to avoid damaging the PC when the USB is connected. This was done two ways: good, and better.
Deets:
The printer requires more power that the USB can supply, so an external supply of some sort is needed. It could be batteries, but in my case I am going to use a 5V regulated 'wall wart'. This presents some challenges, though. The design of the BluePill has the +5V USB line connected directly to the input of the 3.3V LDO regulator. Some things that can be done:
- if only the ground of the BluePill is connected to the ground of the printer, this would be OK, except then the BluePill will always have to be connected to some USB source of power. Silly.
- if the power supply for the printer is connected to the BluePill, this would be OK, but then it would be dangerous to connect the BluePill to the USB port of a host, because that would couple the printer power supply into the host's USB. And you know someone will do that because the jack is there, and anyway I do want to do it because I want the USB CDC functionality. Undesireable.
So, much as I am loathe to do it, I need to do some PCB surgery. The first surgery is to disconnect the +5V USB line from the input of the regulator. Here is where it is located on the back of the board:
So, that trace must be cut. Here it is cut:
Now I can power the board from the external 5V supply, along with the printer module. But, it also means that I will always need the external supply to do the USB. I can fix that by adding a Schottky diode where the trace used to be. That way, USB can power the CPU board if there is no external supply, and the external supply will be blocked from powering into the host if it is there.
Coincidentally, I have some MBR0540T1 Schottkys, which are just about the correct size. I scraped off some of the solder mask near the via (you can see the scraped area in the picture, above), and soldered the diode from that spot to the other side of the cut trace (borrowing the pad from C7 for the diode's cathode).
As you know, we typically use Schottkys because they have a low forward voltage drop of about 0.2 V, but really, since the 5V is going into a 3.3 V regulator, you could use a typical silicon diode (0.7 V) just as well.
Now I can use the USB with safety and confidence! Well, almost. If I want it to be really robust, I also need to add another Schottky on the external supply side, so that the USB doesn't try to power the printer if the external supply is not present. This is because the printer takes too much power for the USB to supply, so mI ight as well prevent that accident from happening, too. This won't require board surgery, but rather a through-hold Schottky can be mounted on the 5V pin of the board. (I haven't done this yet, so no picture, but this one is trivial since the 5V pin is clearly marked on the board. Just be sure the diode is pointed the correct way -- with the banded side towards the board, away from the power supply.)
In retrospect, I'm a little surprised that the manufacturer didn't splurge on adding the diodes. They did splurge on adding two crystals, and frankly I'd rather if they simply put the one 32.768 KHz crystal, leaving the 8 MHz unpopulated, and redirected that cost into these protection diodes. The chip has a PLL that can synthesize the high frequency clock and phase lock it onto the low frequency crystal, so you wouldn't lose any capability by dropping the high frequency crystal. But, hey, I didn't design the board, and I'll definitely still take it considering the absurdly low cost.
OK, back to printer interfacing....
Next:
More printer interface code.
-
♫ Flash! (aa-aahh!) Saver of the Usersettings! ♫
07/12/2017 at 19:02 • 0 commentsSummary:
It occurred to me that it would be useful to save some configuration in a non-volatile manner. This board and this chip does not have any conventional non-volatile storage (e.g. EE or external serial EE), but there's plenty of pages of unused program memory, so I use the last one to be used for infrequently changed NVRAM settings.
Also, I validated my HP82240B rendering engine.
Deets:
Over the past couple weeks it occurred to me that this interface board has some utility beyond the primary intent of being an adapter to a receipt printer to emulate a HP82240B -- for instance, it could be also useful as an IR-to-serial adapter, forwarding IR data to the USB port (or UART if you need to, and don't have a printer there). This is easy enough to do and it's easy enough to set the system up in that manner, but this state will not persist boot-to-boot. So, time for some persistent settings.
Most non-volatile memory is known for the exhaustion phenomenon, so you generally try to avoid burning/erasing except when needed. Frankly, for this application: storing user settings, the burning/erasing will naturally happen very infrequently since it is a user-initiated activity that is only occaisionally performed. I'm sure all readers of this log post will have long since passed on to the hackerspace in the sky before they have exhausted the million flash erase-write cycles for settings changes. That being said, like my friend Ming the Merciless says: "I like to play with things awhile, before annihilation." So I'm going to go a little further with this featurette.
The flash is organized in 'pages' which is the smallest unit of erasure. On this part, the page size is 1 KiB. That should be more than enough for my setting storage -- at least until I have a need for large object like strings -- but for now it's just a few integers.
What I'm doing in this implementation is to store a struct of my settings in the last page of flash on the device (leaving all the previous pages for the program). That last page will have zero or more copies of the settings. The last copy of the settings is considered the current values.
So, upon boot up, the code searches for the last copy of settings, and initializes the RAM-based copy with that data. If it doesn't find any settings, then it will intialize the RAM-based copy with the baked-in default values. The rest of the system then proceeds to use whatever is in the RAM-based copy.
I want to have the RAM-based copy because I want to be able to change settings on the fly via the monitor interface (yet-to-be-implemented; it will be on the USB CDC). When you have the device dialed in the way you like, then you can persist the settings.
Persisting works like this:
- find the last valid settings image
- see if there is space after it for one more settings
a) if there is, that is where we will be programming
b) if there is not, erase the page, and let the start be the place we will be programming - program the flash at the place we located
A simple scheme, but I wanted to document it here. This way, the flash exhaustion is reduced by a factor of how ever many settings structures can be fit on a page, which presently for me is 64 (but this will go down as I add more settings, but still).
Other News
I did validate my HP82240 printer rasterizer functionality. It was a bit painful, but I managed to dump the image over the serial port, then meticulously hand-craft a bitmap of the data. For example:
0E 00 00 02 00 00 02 00 04 0E 00 00 00 00 00 00 00 00 00 00 00 C2 07 00 02 00 38 02 00 04 08 00 00 00 00 00 00 00 00 00 00 00 42 B0 3C 47 04 04 87 E3 25 08 00 00 00 00 00 00 00 00 00 00 00 C2 53 45 42 04 38 02 14 14 08 00 00 00 00 00 00 00 00 00 00 00 42 50 45 42 04 40 82 17 0C 08 00 00 00 00 00 00 00 00 00 00 00 42 50 3D 8A 07 44 4A 14 14 08 00 00 00 00 00 00 00 00 00 00 00 CE 17 05 04 04 38 84 E7 25 0E 00 00 00 00 00 00 00 00 00 00 00 00 00 04 80 03 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
renders into:
v v v v v v v v v v v .###.....................#.......................#................#......###.... .#....#####..............#.................###...#................#........#.... .#....#.....##.#..####..###...#...#.......#.....###....###...####.#..#.....#.... .#....####..#.#.#.#...#..#....#...#........###...#........#.#.....#.#......#.... .#....#.....#.#.#.#...#..#....#...#...........#..#.....####.#.....##.......#.... .#....#.....#.#.#.####...#.#...####.......#...#..#.#..#...#.#.....#.#......#.... .###..#####.#...#.#.......#.......#........###....#....####..####.#..#...###.... ..................#............###..............................................
Tada!
That bitmap was captured from within the physical printer rendering task, so that means data is now flowing correctly from sender (HP28C calculator in my testing), to IR detector, to IR decoder, to HP82240 rendering engine, to physical printer rendering engine. So it's getting close to being a working system, once my printer arrives (and I have coded and debugged that module).
Next:
Now I have to transform that bitmap into graphics commands for the physical printer.
And then there will be cake.
-
Fontastic!
07/11/2017 at 17:32 • 0 commentsSummary:
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:
- 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.
- 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.
- 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:
- IR data processor acquires a lock (implemented as a FreeRTOS binary semaphore).
- memcpy() the rendered buffer to the secondary buffer
- release the lock
- task-notifies the Printer data processor that there is a new bitmap to process
- goes back to immediately being ready to process more IR data
and then the Printer data process will awaken... - Printer data process awakens from the task notification
- acquires the lock on the secondary buffer
- parties on the data however it wants
- release the lock on the secondary buffer
- 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:
- work on the monitor task, so I can, say, hex dump the output to study offline
- punt on testing this for now, and work on implementing the Printer data rasterizer
I haven't decided which, they both have their charms...
-
Raster Blaster
07/10/2017 at 15:16 • 0 commentsSummary:
Having gotten the peripheral devices supported and abstracted, I'm on to implementing the higher-level objects. The first one I'm tackling is the interpreter/processor of the HP Printer IR data stream. This will maintain the logical state of the printer (current options like font, wide char, bold/underline, X position).
I am working on implementing the rasterizer of the HP printer data, which will create a bitmap image representing one line of output.
Deets:
Since the printer language the HP printer uses is quite different than that of the receipt printer (also the implied character sets), I've decided to treat everything graphically. This means first rasterizing the HP printer's output to an image, and then outputting that image to the receipt printer in it's native tongue.
The HP 82240B printer has two character sets (the -A had only one, so it's emulation is therefor supported as well), so I'm going to have to go through the tedious process of creating the font for the 256-32=224 X 2 fonts = 448 characters. *sigh* On the positive side, I had completed some character blitting code for a different project a couple months back, so I will reuse that, along with the font format I came up with. It's richer than I need for this, but 'done' is 'done', and there's so much other stuff to do. Also, I'll need to modify it to handle 'wide' mode and 'underline' as well. So there's still some new work to do in that area.
While I was somewhat dreading this activity as tedium -- and it will be -- there are several simplifications that I can avail myself to in this case. In particular, the HP printer is a line-by-line device, so I only need to rasterize one line's image before emitting it to the physical printer. Additionally, since the HP printer is principally a 5x7 text mode printer (i.e. 6x8 with the inter-character -line spacing), and graphics commands simply set a vertical column of pixels, this means that all my clipping logic will be trivial. Yay, it's the small things in life.
So really, I think that generating the font data is going to be the bulk of the work for this step.
I'm reusing a font format I concocted for a previous project. The gist of it is:
- a header with meta info
- an array of character descriptors; so variable character width is supported, along with partial character sets (and UCS-2LE if you really need it)
- an array of glyph bitmaps for those characters
This is certainly overkill for what I need here, but again, I've already written the code for blitting the characters and I'd really rather reuse that than write yet another blit routine.
FWIW, the font header is defined as:
ypedef struct FONT_HDR FONT_HDR; struct FONT_HDR { uint32_t _nSignature; //indicates this is a font object uint32_t _nVersion; //format version uint32_t _nFlags; //various flags uint16_t _arraylen; //how many glyphs in this font uint16_t _height; //common glyph height (pel) };
The signature looks like 'FONT' in a hex editor. The version is 1. The flags indicate the packing of the glyphs (byte, word, dword), whether it is MSB/LSB first, and whether the glyph is mirrored in X and/or Y (not needed here).
The character descriptor is defined as:
typedef struct FONT_CHARENT FONT_CHARENT; struct FONT_CHARENT { uint16_t _charval; //UCS-2LE of this character uint16_t _width; //this glyph width (pel) uint32_t _offset; //relative offset from start of font image };
So, to blit a character, you do a lookup of the character descriptor via binary search, and then you can determine the address of the glyph bitmap, the width, and the height for the blit operation.
For bitmaps, I generally prefer LSB first. The reason is that this gives me the freedom to choose 8-bit, 16-bit, or 32-bit IO operations directly from the buffer without having to bit shuffle. (Well, on a little-endian machine, but come on, little-endian is the one true way.) This will be a minor annoyance later when I transform the HP printer bitmap into physical printer output data, since that data is MSB first. However, doing bit reversal at that one last step will be a much simpler chore than complicating my blit routines (and reducing speed, though that is not paramount here).
As mentioned there are several simplification in this application:
- the Y address for output will always be 0, since it is one line at a time for text, and always one full column of pixels for graphics.
- only full characters will be printed, so clipping can be at the character level, meaning that a character is clipped out if the starting X position is greater than right-width. Simple logic, and no awkward buffer wrap-arounds to the next line.
- the glyph width is constant, and always one byte. This means I can implement support for single and double width simply by promoting a glyph raster line always to a uint16_t, and either copying the data verbatim (single wide), or pixel double (double wide) and the rest of the blit logic will simply work as-is. Yay!
One less obvious simplification is that by re-using my font format, I also potentially can reuse a separate tool I made that will rasterize a True Type Font into my format. Sooo... If I can find a TTF that someone might have produced already for the HP82240, then that will be a huge leg up. Otherwise I will be spending several days working with a magnifying glass to manually create bitmaps from sample printouts.
So, now I need to do a little research to try to find such a TTF out there. Who might have it? Maybe one of the calculator emulator guys might have such a thing, since they usually like stuff to be pretty. Hopefully I will score!
Next:
Aside from the font quest, I will tweak my character blitting code to incorporate the features/simplifications I mentioned, and also implement the state machine for processing the incoming HP printer data.
Ciao for now!
-
USB, what you want to (and all you need is to get your boogie down)
07/09/2017 at 17:37 • 0 commentsSummary:
Despite having peripheral libraries, implementing the USB CDC interface was even more painful than the UART serial. But now the deed is done.
I'm now moving on to processing the incoming printer data stream, and rasterizing output to be printed.
Deets:
The USB CDC implementation is provided by what ST calls 'middleware'. USB is a byzantine protocol, and the on-chip peripheral IP has a myriad of registers to understand if you were attempt to hand-roll it. So, yeah, I had a good reason for taking the easy way out. But, alas, the implementation of this component was worse than the UART from before (well, worse to my tastes, at least). Amongst the problems I had with it were:
- the USBD_CDC_ItfTypeDef is a C style 'interface': a struct of function pointers. These functions are implemented by you (with skeleton generated from ST32CubeMX) and include things like 'initialize', 'deinitialize', 'control packet', and 'received data'. But notably absent is 'transmit complete'. So, you cannot know when a transmission you have started is completed so that you can continue with subsequent transactions. The sample projects use a timer to poll for completion periodically. Polling? Yuck! Fer real?
- there is a TX buffer declared in the generated code, but it's usage seems odd because the generated code seems to not use it at all. It is referenced only once, with a length of 0, and all subsequent transfers bind the caller supplied buffer. Hmm.
- the RX callback function seems odd in that it seems to assume that you can take all the data in the buffer you bind. There is a length param that is a pointer, so presumably you can alter it, but doing so would run counter to the caveat communicated in the @note (which implies you must take it all before returning from the method or there will be lost data). Hmm.
The first item is especially problematic because there is no way to add the TxComplete notification without editing the generated code. This means if I need to re-run STM32CubeMX (and I do all the time), my changes will be overwritten. Yuck.
OK, I simply have to have these changes -- I'm not doing this polling thing. That means I will have to re-apply the changes when I re-run the tool, so obviously I have backed up copies so I can re-apply them, though this is tedious. Additionally, the nature of the changes I have made are such that if they were to be overwritten, that the result will still build successfully, but it will not run correctly (the callbacks will never be called). Since this is a debugging time waster (you will one day forget to manually apply the changes), I have added a do-nothing API in the area with those changes that gets called early in main. It is a waste of code space, but at least it will cause linkage to fail if I do not re-apply the required changes. And obviously I have also left a comment at the call site to the effect of 'if this fails to build, you need to apply the xxx changes at yyy'.
The second item is problematic for several reasons; one, if the buffer is not going to be used, then why does it need to be defined at all (wasted RAM), and two, by binding the caller supplied buffer in the public API CDC_Transmit_FS(), there is now an assumption that the pointer will remain valid long after the call returns, to be used later during subsequent transmission activities (ostensibly at ISR time). This precludes common use cases like this:
void ShowMeTheMoney ( float fSmackers ) { char achBuffer[128]; int nLen; //need linker flag "-u _printf_float" and it will incur ~12k code penalty nLen = sprintf ( achBuffer, "You have $%0.2f smackers\r\n", fSmackers ); CDC_Transmit_FS ( achBuffer, nLen ); }
because the buffer must be of long (and indeterminate!) duration outside this function's scope. So you'll either have to define your own long-lived buffer and copy into it (and arbitrated it as a shared resource, etc.) or some other scheme to work around the nature of the API.
The third item is worrisome; I'll have to study the implementation a bit more to see if there are actual buffer overflow exposures, but in the meantime, I made the internal transfer buffers (which I don't like) 64 bytes, which I believe is the largest size transfer that can occur in one packet in USB full-speed.
Another little thing is that the USBD_CDC_HandleTypeDef in usb_cdc.h has a buffer defined as CDC_DATA_HS_MAX_PACKET_SIZE in size. However, this chip doesn't do high-speed, so we waste 448 bytes. I changes this to CDC_DATA_FS_MAX_PACKET_SIZE to reclaim that memory.
At length, I made the desired changes and provided the higher-level implementation, so now the API for the USB CDC is:
//transmit data via USB CDC implementation; returns bytes enqueued size_t USBCDC_transmit ( const void* pv, size_t nLen ) //read data received from USB CDC implementation; returns bytes dequeued size_t USBCDC_receive ( void* pv, const size_t nLen ) //maximum that can be read from the USB CDC size_t USBCDC_receiveAvailable ( void ) //maximum that can be pushed into the USB CDC size_t USBCDC_transmitFree ( void ) //optional callbacks you can implement to get notified of xmit/recv void USBCDC_DataAvailable ( void ); void USBCDC_TransmitEmpty ( void );
I.e., a form consistent as with UART2. Now I feel clean again...Next:
Now I think I have finally gotten all the low-level components in place, and have bandaged over warts therein. Now I need to do some higher-level stuff, including:
- interpret incoming IR printer data stream, rendering into a bitmap section. This means fonts and blitting and various state machines (e.g. cursor positions, printer modes, etc.)
- transform a completed bitmap section into the ESC/POS graphic printer codes. This means reading up on that language.
my new new new idea is having various operational modes on this interface device. This morning it occurred to me that the interface board could be useful to someone even without a physical printer, e.g. as an HP IR to serial converter, supporting both UART and USB CDC. This mode would be simpler -- essentially 'data pass-through', maybe with some additional options (e.g. translate the special purpose CR (0x04) into a normal CR (0x0d)). More thinking needs to go into this, of course.
-
Home on the Range (of the IR)
07/07/2017 at 17:58 • 0 commentsSummary:
Unscientific tests show that range is good; 5 meters has been actually tested, but it is probably a bit more.
Deets:
It's a bit annoying when testing to point the calculator at the sensor and get then aim right and then press the button (I know -- so lazy). The calculator that I'm using for testing has a book-like folding design, so if I open it about π/3 radians, it will stand up on its end, with the IR emitter pointing upwards towards the ceiling. Situated this way, my detector board is next to it with the detector pointed up as well. So, the signal path is out of the calculator, up to the ceiling, reflecting, and back down to the detector.
I was happy to find that the signal along this path gets reliably decoded. I measured the distance from the desktop to the ceiling at about 2.5 meters, so the total path is about 5 meters -- and who knows how much loss there is from the imperfect reflector of the matte white ceiling.
Nice! I think the IR part can now be considered good enough for production use, though I still may try some of the optimizations I mentioned in earlier posts.
The resulting API is
//valid data received void IR_RedeyeCbk_data ( uint8_t by ); //parity bits included void IR_RedeyeCbk_parityFail ( uint16_t val ); //bad signal was detected void IR_RedeyeCbk_badSignal ( void );
Next:
I'm back on implementing USB CDC device mode.