HP 82240B IR Receiver and Printer Interface

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

Similar projects worth following
The HP 82240B infrared printer was developed by HP in the 1980's, and is expensive. Some folks asked about the feasibility of crufting one together from cheap receipt printer cores, and I thought that might be a fun thing to do, so here we go a-hacking...

Many folks that use HP calculators of yore like to be able to use the infrared printer. These printers are really expensive given what they are, and some folks in a separate forum wondered if a workalike could be crafted from cheap printer cores from China. I thought they could be created from a POS printer core and using the famous 'Blue Pill' development board as the interface. Since I had coincidentally just implemented the generation of the related IR protocol, I was freshly familiar with how it works, so on a lark I am now attempting this interface.

It's all in the name of fun, really. I'm expecting that the total cost of this printer knockoff will be about usd$30 as described here (e.g. there is no case to speak of, just rubber band the board to the printer!) The printer new (NOS) costs a ruinous $200 or so, but you can typically find them on eBay used for about $50, so I don't know that this will be particularly cost effective relative to a polished commercial production, but who knows, it might be of interest to someone. And the infrared-to-serial/usb part could be interesting in some as-yet unimagined way.

  • 1 × GOOJPRT QR203 58mm Micro Receipt Thermal Printer RS232/TTL Compatible +EML203 printer core; serial 'TTL' interface. I got mine from eBay from China for USD$ 18, free shipping. The source also supports the 'Kashino' (e.g. the kind from SparkFun or AdaFruit), and an APS model. The printer support is modular, so you can add a new one relatively easily.
  • 1 × STM32F103C8T6 ARM STM32 Minimum System Development Board Module so called 'Blue Pill' development board; I got mine from eBay from China for around USD$ 2.25, free shipping
  • 1 × Vishay TSOP4133 IR receiver; I got mine from Mouser for about USD$ 1.30 It must be exactly this model number, because of AGC characteristics.
  • 1 × FT232RL 3.3V 5.5V FTDI USB to TTL Serial Adapter Module this is just needed once for the bootloader to upload pre-built firmware; these are about USD$ 2.50 from China. I keep a handful of these around.
  • 1 × Power Supply; regulated, 7V, 3A Yes, you need a hefty power supply for the thermal printer. It's not about voltage, it's about power when those heaters fire. Weaker power supplies will sag under the impulse load, and you'll reset your controller board. The degree to which this is true varies by printer model, but I recommend not going over 7v to avoid stressing out the linear regulators on the boards.

View all 6 components

  • If You Can't Beat 'Em, Beat 'Em Into Submission

    ziggurat2907/21/2017 at 02:34 0 comments


    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.


    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...

    Read more »

  • Success! I'm the Operator on My Pocket Calculator

    ziggurat2907/19/2017 at 18:04 0 comments


    Printing to physical printer is now working with both the GoojPrt and APS EPM203-MRS units.

    I am now moving onto optimizations and enhancements.


    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:

    1. 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!
    2. 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!


    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

    ziggurat2907/18/2017 at 14:09 0 comments


    Reading datasheets can be fun!


    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.


    Back to work coding printer rasterizers....

  • My Three Sons; Mike, Robbie, and Chip

    ziggurat2907/17/2017 at 23:27 1 comment


    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!


    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...

    Read more »

  • ♫ I've Got (too much) Power! (gonna make you sweat) ♫

    ziggurat2907/13/2017 at 18:07 0 comments


    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.


    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:

    1. 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.
    2. 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....


    More printer interface code.

  • ♫ Flash! (aa-aahh!) Saver of the Usersettings! ♫

    ziggurat2907/12/2017 at 19:02 0 comments


    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.


    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:

    1. find the last valid settings image
    2. 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
    3. 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
    Read more »

  • Fontastic!

    ziggurat2907/11/2017 at 17:32 0 comments


    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.


    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

    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
    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
    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...

    Read more »

  • Raster Blaster

    ziggurat2907/10/2017 at 15:16 0 comments


    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.


    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...

    Read more »

  • USB, what you want to (and all you need is to get your boogie down)

    ziggurat2907/09/2017 at 17:37 0 comments


    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.


    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:

    1. 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?
    2. 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.
    3. 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...

    Read more »

  • ​Home on the Range (of the IR)

    ziggurat2907/07/2017 at 17:58 0 comments


    Unscientific tests show that range is good; 5 meters has been actually tested, but it is probably a bit more.


    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 );


    I'm back on implementing USB CDC device mode.

View all 21 project logs

  • 1
    Throw it all in a shoebox and shake vigorously until it comes together

    (OK, I'll put something serious in here when I get done with it.)

View all instructions

Enjoy this project?



Rob Jordan wrote 12/14/2022 at 15:27 point

Excellent work! The description of how you overcame the faulty demodulated output of the IR receiver was stupendous. Especially since I'd just been reading on HP forums that the TSOP4133 "won't work" due a lengthy minimum post-burst period. Your writing is fun too!

  Are you sure? yes | no

ziggurat29 wrote 12/15/2022 at 14:16 point

Thanks!  It is on the very edge of the spec.  It works reliably with my calculator, but I can see that perhaps a different calculator's signal might be beyond the edge of detection.
If I were to do it over, I'd probably use a detector designed for IrDA.  In that case, the signal would be the actual modulation, rather than the envelope.  This would mean I need to modify the code for the decoder, but I'd happily do that so that I can keep the integrated electronics goodies of the AGC and filtering. This should be very reliable across calculator variations.
I started working on that some time back, but I destroyed my module somehow, so ¯\_(ツ)_/¯.

  Are you sure? yes | no

Rob Jordan wrote 12/15/2022 at 15:06 point

I'm inspired to try both approaches, so yesterday's ebay purchases included an HP C4103A, which I think is intended for IRDA; Martin Hepperle has written about  how to adapt it for HP calculators. And I also bought some Telefunken TFMS1330, which I think are the predecessors to the Vishay part, and possibly - not sure - have a shorter gap requirement. Thanks.

  Are you sure? yes | no

ziggurat29 wrote 12/15/2022 at 16:19 point

I think the one I was trying was the TDFU4100.  The (modulated) signal looked great on the scope, so I just needed to tweak the code to work with modulated signal rather than envelope demodulated.  As mentioned I destroyed my module somehow.  I'm not sure that part is still in production, but there should be modern equivalents (and that one being SMD was a hassle, anyway).
So I think the IrDA receiver approach is viable and will lead to a more reliable solution than the 4133.

  Are you sure? yes | no

jp cuzzourt wrote 10/19/2019 at 00:16 point

I am just about to embark on a very similar project, and I ran across your write up. Excellent work. I'm starting with a Goojprt QR204 printer similar to one of yours. It is still on the slow boat from China. I also have a working HP 82240B, and several IR capable calculators. I'm planning my first attempt with TSOP38533, and I'm aware of the timing issues. I had read elsewhere that it was insurmountable, but I believed I could correctly decode the IR anyway, and I wanted to play with it. While I was ordering from Mouser anyway, I grabbed a Vishay TFBS4711-TR1 which is an SIR transceiver. I thought if the ir remote demodulator didn't work out, I'd try the SIR hoop-de-doo. 
I'm probably going to start with an arduino, but eventually I think I want to move it to an RPi 0W. I'd love to have some help along the way if I get stuck :-)

Thanks so much for sharing your work on this!

  Are you sure? yes | no

ziggurat29 wrote 12/29/2021 at 14:26 point

The IR part was not at all insurmountable for me, but maybe I got a lucky batch of receivers.  As the years went on, I now believe that a module intended for IRDA will work more predictably, though the decode algorithm will need to be changed significantly because the burst filter in the IR remote modules is effectively not there (the part that makes the output a nice square envelope around the modulated IR burst).  You'll instead get whacked for every pulse within the burst.  But this is straightforward to filter in code; just different.

  Are you sure? yes | no

merrittgene wrote 02/28/2019 at 13:50 point

What about building an interface so my HP-28S could print to any USB printer or infrared transfer to a text file?  Maybe a Raspberry Pi with an IR sensor?

  Are you sure? yes | no

ziggurat29 wrote 12/29/2021 at 14:27 point

that's conceivable.  Also, this project can be run stand-alone (without an attached printer) to serve as a HPIR-to-USB-CDC adapter, allowing your PC to capture the print stream over a serial port.

  Are you sure? yes | no

ddavidmelo wrote 07/18/2017 at 11:20 point

I saw your project, very interesting. I have a similar printer (not equal, but works with HP calculator) and I would like to do something like this with my arduino, is possible and how to do? 

  Are you sure? yes | no

ziggurat29 wrote 07/18/2017 at 11:46 point

Sure, but help me get a clearer picture.
'similar printer... works with an HP calculator'.  Meaning you already have an IR printer? And you want to print to it from an Arduino?  Can you give me a model number I can google, or a link?

  Are you sure? yes | no

ddavidmelo wrote 07/18/2017 at 13:21 point

Hello again, I have this printer [hp printer 82240b]. I would like print few words, by send them from Serial Monitor of Arduino IDE. Something like this:
[String of words] » [Serial Monitor of Arduino IDE] » [Arduino UNO] » [IR led transmitter] » [HP printer]

  Are you sure? yes | no

ziggurat29 wrote 07/18/2017 at 13:53 point

(this goofy web site, I have to reply to your previous post since I can't reply to your lastest post -- apparently too deep nesting for the platform to handle?).

OK, now I get it.  You already have an HP82240B, and you want to print to it.  OK, couple things:

1)  this project is about the reverse, making a function equivalent to the HP82240B, receiving IR, rather than sending -- not sure if you were clear on that, but....

2)   I have already done the sending part successfully in a separate project (not on this forum; it was trivial), so I definitely can help you there, too.

Good news, it's super easy.

Hardware:  You will need an IR LED and a resistor.  Strictly you will need to look at the specs of your LED to really compute the optimal value of the resistor, but you will probably do just fine to a first approximation with something like 220 ohms.

Software:  I'm unfamiliar with the Aurduino platform, so bear with me.  The gist of what you do is send a timed sequence of LED blinks.  In my case, I used two timer resources.  The first one was configured as a PWM generator, the output of which drives the LED.  The second one was configured to generated interrupts at a regular rate, which drives the encoder state machine (which turns on and off the PWM output).

If you like, I will send you my code and a brief walkthrough of what's going on.  I'm quite sure you'll need to modify it to reflect the Arduino API and also the clock speed of the board.  If you have a scope, that will help a lot in debugging, since the printer will just ignore what you send if it's not encoded correctly, and it will otherwise be hard to see if you have the timing correct, or incorrect and in what way.

  Are you sure? yes | no

ziggurat29 wrote 06/25/2017 at 16:20 point

Thanks; Mouser said they ship tomorrow, so sometime this week, hopefullly.  I'll test them the second I get them, and possibly post a screenshot of the scope traces (if I can figure out how to post images -- surely I can do that).

  Are you sure? yes | no

Arya wrote 06/25/2017 at 01:28 point

Great project, I'm very interested in the IR part =) Good luck with it!

  Are you sure? yes | no

Similar Projects

Does this project spark your interest?

Become a member to follow this project and never miss any updates