Search This Blog

Thursday 25 March 2021

 Using a Thermal Printer Mechanism

 Using a Thermal Printer Mechanism

The Casio CP-10 is a neat calculator that has a thermal printer attached. The calculator also has a built-in clock and can do useful tasks such as printing this month's calendar. The date range is also useful as it ends in 2099. The printer is a small mechanism that take standard 38mm thermal paper. I found out that the mechanism is the same one that the FP-12 printer uses. This is a small printer that is designed to attach to a variety of calculators and pocket computers.

I bought a CP-10 to see if I could re-use the printer mechanism instead of the receipt printers that I have bought off ebay in the past. They are relatively expensive (over £20) whereas the CP-10 can be had for half the price, if the CP-10 is listed as non-working. Unfortunately the CP-10 has a NiCd battery pack, as the printer mechanism take a fair current when running and the pack is used to supply that. This means that a lot of CP-10s have suffered battery leakage and failed. This is what had happened to my example. After a brief attempt to revive it I cut off the printer mechanism and attached a blue pill and some extra circuitry.


There's seven transistors, one for each thermal element, and one transistor that drives the motor. There's an input for the home position switch as well. The mechanism has a tachometer output that gives a series of pulses as a magnet rotates near a coil. The magnet is attached to the motor, so this arrangement gives a reading of the motor speed. The pulse may also be used for dot timing. Unfortunately my mechanism seems to have a broken tachometer output. The coil reads 25M resistance, which is a bit high for a coil of wire. (I have since bought a second CP-10, this one shows signs of life and its coil reads 82R, so is much healthier). After trying my own tacho circuit, then copying the CP-10 circuit, then building a higher gain circuit with an op-amp I decided that the tacho output wasn't reliable enough to use. This may not be too much of a problem if the motor is run off a stable supply as the speed of the motor should be fixed. I should be able to time the motor moving from home position back to home position and scale the dot timings appropriately if need be.

The output from my working CP-10 is like this:


That's the calendar printout from the CP-10. The FP-12 has a print width of 20 characters, the CP-10 also seems to have a print width of 20 characters.

My first attempt at printing was a little more basic than a calculator. I turned the motor on and fired one element of the print head repeatedly:


This is a start, at least I'm getting dots. The motor drive also performs line feeds and the dots show that. The horizontal dots are the feed across the paper. the sloping dots are where the head is returning and the paper is being fed. After some work with the code, including adding a font table and using the input from the home switch, this is the output:


This is a test printout that is printing the parameters of the code that printed that section. I am also using 20 characters as the width here, simply because the mechanism is set up for it. There's a row at the top of the characters that is faint, I think this is to do with the ground wiring I have used.

There is also an interesting effect which is best shown in the lines of asterisks. They are darker on the right hand side of the line. I'm pretty sure this is down to the heating of the printhead as elements are turned on. Every time a dot is printed, the printhead warms up a bit. This means that to get an equivalent dot, you don't need to turn the element on quite as long the next time. In the printout above, I have a parameter called 'DEC_PERIOD' which controls part of my correction for this thermal heating effect. You can see that the second from bottom section has a DEC_PERIOD of 1 and the asterisks are darker to the right hand side of the line. The section above it has a DEC_PERIOD of 2 and the asterisks are printed more evenly.  The correction code counts the number of elements that have fired, accumulates those counts and uses them to create a 'temperature' for the printhead.  The DEC_PERIOD value controls the cooling of the printhead. This does lead to a working correction as can be seen above.

I think the CP-10 code must also be doing something similar, as the calendar printouts above show an even darkness across all the lines.

The CP-10 must also be correcting for supply voltage variations. It runs off four NiCd cells which will present a range of voltages to the motor and hence different speeds of printing. I have attached my working CP-10 to a power supply and varied the voltage. The time to print a line definitely changes, but the characters printed remain in the correct positions. The printout is fainter when the voltage is lower. The code could be using the tachometer signals from the mechanism. I can't do this but I could measure the time taken to print a blank line and use that to adjust the dot timings. Interestingly it appears that the CP-10 does print one blank line after it is powered on. I wonder if that is a timing line and the CP-10 is doing what I will probably have to do if I want to correct for voltage?

Next step? probably a PCB. But do I use a blue pill or STM32 chip? Given the size of the mechanism, a blue pill is a bit on the big side, so I think a chip would be better. The only problem is that there is quite a shortage of STM32s at the moment. I've got a few so that is probably OK.

There's a video here of the prototype:

 


 



Saturday 6 March 2021

Finite State Machines and the Software Hammer

Finite State Machines and the Software Hammer

The firmware that runs the Casio FX502P gadget that I recently created has a structure and uses a technique that I though was worth documenting. The gadget uses the Arduino framework and is described in the following videos and blog entry.




The prototype:

This device has some tight timing constraints while performing interactions with a real-time interface. It cannot waste time deciding what to do next as it has a bidirectional synchronous bit stream to attend to at about 200kHz. The processor itself runs at tens of MHz so there's time for several instructions to run between clock edges, but not enough time that the code can perform time consuming operations.

As the protocol isn't officially documented I'd like to be able to change the interface behaviour to add new commands, remove commands and maybe alter some of the existing commands. The code needs to be easy to understand and change.

So, we have two requirements:

A. Code must run fast

B. Code must be easy to understand and not break if altered  

These requirements, even if they don't officially declare war on each other, fight against one another. Code that is easy to understand and change isn't usually fast, and code that is fast isn't usually easy to understand or change.

Fast Code

One fast way to respond to inputs is to use interrupts. Something happens and a fragment of code runs. That's the basis of interrupts. If you have something simple then it's relatively easy to implement. A push-button, for instance, is a single input and an interrupt service routine (ISR) that runs when the input is either changed or at a certain logic level. (An interrupt that runs code when an input changes is edge-triggered, one that fires at a certain level is, well, level-triggered).

In the push-button example it's all straightforward. The push-button is pressed, the input changes level and the code runs. Whatever is needed to be done is done and the ISR exits. There are details that have to be attended to, such as using the volatile keyword (in C), we are more interested in the overall structure here than details.

For the FX502P gadget the interface has:

A clock line that runs at approximately 200kHz and clocks data on both edges

A data line, which is bidirectional

Two control lines, active high

A serial data format that uses packets of different lengths, including a longer packet that has a payload with start bit, stop bits,data and parity, which also gets clocked out on a different edge to the data clocked in.

We therefore have edge triggered signals, level triggered signals and have to collect and build packets of different lengths and formats. Suddenly it's all a lot more complicated than a push-button.

At the time of writing (and as this code is easy to change, it changes now and again), there are two interrupts, one driven from the clock and one from a chip enable (CE) control line. The complexity has just moved up a notch. There's now two interrupts that have to be handled. Fortunately in this case they cannot execute simultaneously, although the method I have used doesn't have problems if they do execute simultaneously (on different cores, for example).

Orchestration

So how can you create something that can react to interrupts and perform actions based on those interrupts? Not only react, but react in a way that has memory of what has happened in the past. For instance, if the clock changes state do we clock data in or out? We need to know if we are in the middle of receiving a packet or sending one. 

In the gadget this is done with a Finite State Machine (FSM). It's a thing (Machine) that can be in one of a Finite number of States at any time. FSMs move between states when they receive an input (stimulus). (An FSM is always in a state, and changing from one state to another logically takes no time). There are many ways an FSM can drive outputs, in the one used here, a function is called when the FSMmoves to a new state. An action is taken on entry to a state.

When an ISR runs, it sends a stimulus to the FSM and that may causes a state change. Code is run when entering a new state, that code can do anything required at that time.

Implementation

There are several ways to implement an FSM. As the gadget doesn't have time to spare it uses nested switch statements. You can use a table driven approach but searching the table uses processor cycles and hence more time than a switch statement approach. Using a table, though, allows you to use a higher level of abstraction when defining the FSM. With the gadget I wanted a similar abstraction, but to achieve this I used what I call a 'software hammer'. The idea is to have an abstract description of what you want to achieve, in a form that is easy to understand and easy to alter, and support code that hides the details. This support code is 'hammered' into shape to support the abstract code and handle all the details.

In the gadget code, the nested switch FSM is the abstract part, the rest of the code is hammered to support that abstraction.


Here's a fragment of the FSM code which shows the two nested switch statements:

switch(ce_isr_state)
    {
    case CIS_IDLE:
      
      switch(captured_word)
{
  // Close followed by reset is a '.' key
case IP_CLOSE:
  ce_isr_state = CIS_POSSIBLE_DOT;
  break;
case IP_RESET:
  ce_isr_state = CIS_POSSIBLE_AC;
  break;
case IP_UNKA:
  ce_isr_state = CIS_RX_UNKNOWN_A;
  break;

The first switch decides which state the FSM is in, the second is one which looks at the word that has been captured by the support code in the ISRs. Depending on the value of the packet, the FSM is moved to a new state.

For example, if we are in the IDLE state and we receive a CLOSE packet, then we move to the POSSIBLE_DOT state. It's pretty easy to see how the states follow the packets received from this fragfment, and altering this code is not tricky at all.

The first fragment covered receiving packets and deciding what to do, based on the contents. What about sending data? here's a fragment from a state that needs to send data:

case IP_WAIT:
  
  ce_isr_state = CIS_RX_WAIT;

  num_data_words = 0;
  
  // We want to send a 0 bit on the next clock cycle,
  // set it up
  isr_send_data = 0;
  isr_send_data_save = isr_send_data;
  isr_send_bits = 1;
  isr_send_bits_save = 1;
  isr_send_flag = true;
  SET_DATA_BIT0;
  break;

This is run when the WAIT packet has been received. The required action is to send a packet back of length 1 with the value 0. It's pretty easy to read:

 isr_send_data = 0;      

sets up the data to send while:

isr_send_bits = 1;

sets up the length of the packet.

Then:

isr_send_flag = true;

indicates to the support code that a transmission is needed.

There's some other housekeeping stuff, because the universe requires it, such as the SET_DATA_BIT0 which is exposed here due to the timing of the interface. It can't be done later.

What is important is that the fact that the received data packets are clocked in on one edge of the clock while the transmitted packets are clocked out on the other edge is completely hidden at this layer of abstraction. That detail has been hammered into the support code.

Even though the details of the support code are probably horrible with lots of special case required by the interface, that doesn't matter once it works, as from then on it should only be necessary to change the abstract code when new packets need to be decoded or transmitted. The support code handles the interface, and that isn't going to change.

The whole of the FSM looks pretty much like these two pieces of code, although there are other things that need to be done, like this:

case CIS_WAIT_DATA:
      if(word_bits == 16)
{
  // 16 bits of data
  
  // If data is all 1s then it is header data
  
  if( (captured_word & 0xFFF0) == 0xFFF0 )
    {
      // Header word, ignore it
      // Back for more data
      num_header_words++;
      // We know the length of the next packet
      isr_hint_length = 6;
      ce_isr_state = CIS_WAIT_2;
    }
  else
    {
      // Store this data
      data_words[num_data_words++] = captured_word;

Notes

FSM State

The state an FSM is in is determined by it's state variable. Whatever number is in that variable is the state it is in. It can't be in two states at once and it take no time (logically) to update a variable's value.

DFSM

These FSMs are actually DFSMs (Deterministic Finite State Machines), which means that the state transitions are deterministic. Non deterministic state machines can also be useful, their state transitions can be random in some way. They are not that common, though.

Race Conditions

FSMs can be resilient against race conditions. If stimulii are queued and then processed then you can build your FSM to handle stimulii in any order and still get the correct behaviour. Every state should be examined to see what action it should take for every possible stimulus. It doesn't matter when or in what order the stimulii arrive, the FSM states will always take the appropriate action, and it won't miss stimulii as long as the queuing code is correct.