Close

​Serial Killer; The Sorrow of Sad Sorry Serial Stuff

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

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

ziggurat29ziggurat29 07/07/2017 at 15:460 Comments

Summary:

I implemented UART functionality. The STM HAL libraries an 'middleware' left a lot to be desired. But the cracks therein have been spackled-over, so things look sane from the outside.

Next is to implement USB CDC.

Deets:

Implemented circular buffer. For this first implementation I chose to make this implementation a macro that expands to a full definition of the data and API, with name, data type, and size as parameters. This gives me an STL-esque capability that I can use in C.

The first implementation simply declared the data at file scope, but I noticed that the linker strewed the 'members' about, which was not to my debugging taste, so I made a trivially different second version that kept the members in a struct. To my surprise, this struct version saved several bytes in code space. Hmm! Maybe because the compiler could use a single register to point to the struct and encode the small offsets in instructions, rather than doing loads of the full addresses of the strewn-about members. I didn't look at the generated assembly for this one, so I am just guessing.

I may do some other implementations later, anyway. One idea being to be a little more OO in the implementation so that I won't get the redundant code instantiation since most of the implementation of the methods are identical, only differing in the size of the stored type, and the size of the buffer itself. But that's optimizations for later. Now I need to use this for my serial port code -- both conventional UART (for the printer) and for the USB CDC (for whatever, certainly a logging console initially; I'm a little beyond my one debug LED).

My intention is to use the ST-provided peripheral libraries -- why do I want to hand write all that register-level code? Thats when things got... less than nice.

UART serial implementation in STM32CubeMX HAL

The API look OK; transmit and receive, a spin-wait version (no), and interrupt-based version (OK), and a DMA-based version(OK), and callbacks for TX empty and RX done and error. All this is just OK, but there are some aspects that are not-so-great.

One is that the buffers provided are long-lived linearly addressable buffers. This is not the end of the world, but it requires a little care with my circular buffers in case a run for transmit or receive wraps around. That's my burden to code. The other problem, though, is that when I am notified that either a TX or RX is complete, I don't know what was the originally requested transfer size, so I cannot know how far to advance the dequeue pointer. So as it is now, I do one-byte only transfers so I can know fer shur how far to advance. For RX I needed to do this anyway because I want to set a Data Available (DAV) event for a consumer process to pick it up the moment data does come in, but it is unfortunate for TX that I have to do it byte-by-byte. But whatever.

The second wart in the implementation is the notion of 'state' in the peripherals' Device Control Block (they call it a 'handle', but it is really a big struct you pass around by pointer. The pointer should be called the 'handle'). The state has various enumerated values and it looks like it was conceptually meant to be a bitfield, but the author couldn't quite figure it out. So you need to understand that carefully if you want to be able to full duplex and event driven. Side note, they did alter that implementation to split it into two state variables in later versions of the library, but not the one that is current for this chip. That implementation has it's own quirks, though. While on that side note, here is a caveat: ST does change their libraries' APIs in a non-backwards compatible way from time to time without warning. So, caveat upgrader!

Anyway, I worked around these warts and made an API that looks a bit like a non-blocking stream that you can push data into and pull data from at will whenever, and also has a DAV event that you can have a consumer thread block on until stuff comes in to process. I initially implemented that with a binary semaphore, but then I learned about 'task notifications', which are much more efficient in this case, so I migrated to that. (Oh, did I mention I'm using FreeRTOS for this? I am.)

My initial test was to squirt out a fixed text string to the serial port which I then attached to the ever-popular and thoroughly-indispensable FTDI adapter and then to my computer, to view with PuTTY. (Tip: I try to always have about 5 or so FTDI boards on-hand at any time. They're just too handy. Tip on Tip: ALWAYS use FTDI and NEVER use Prolific. Just sayin'.) I had a bug initially by not locking around the circular buffer operations. I knew better than this and already had comments in where I wanted the locking calls to go, but I thought I might get away with it. Wrong! Most of the time it worked fine, but once in a blue moon a character was dropped, and then subsequently a stray character was in the stream. Well, that's what I get! I put in locks around the enqueue and dequeue buffer operations and now the serial code is seems quite solid.

So, all that worked out, and I tested by forwarding the decoded IR data to the serial port, and it looked as expected. No more manually decoding on the scope trace!

I also hooked the callbacks that are invoked on IR parity error, and lost/bad signal, and use them to light the one LED on the board. A separate task turns the LED back off after a few seconds. This will allow me to do some simple range tests with ease.

The resulting API is:

//transmit data via UART2; returns bytes enqueued
size_t UART2_transmit ( const void* pv, size_t nLen );

//read data received from UART; returns bytes dequeued
size_t UART2_receive ( void* pv, const size_t nLen );

//maximum that can be read from the UART2
size_t UART2_receiveAvailable ( void );

//maximum that can be pushed into the UART2
size_t UART2_transmitFree ( void );

//optional callbacks you can implement to get notified of xmit/recv
void UART2_DataAvailable ( void );
void UART2_TransmitEmpty ( void );

Next:

Next, I want to get the virtual serial port over USB working. I want this at a minimum for debugging logging output, but eventually I will put more fancy functionality in there. It has occurred to me that this receiving interface might be useful to some folks in it's own right, even without the printer attached.

Discussions