Embedded

View Original

Karaoke Plays Music

I started with simple state machines last week. This week I want to talk about more complex state machines and interrupts. These are software topics but we’ll get back to the hardware soon.

The karaoke buttons that control music playback are far more complicated than either its disco button or the quadcopter’s power on sequence; if you press play on the karaoke, what happens? What if the door is open? What if there is no CD? What if it is already playing?

What happens when the user presses a button depends very much on what is currently going on, what state the system is in. Let’s start with some user events, these are the buttons we saw in the last chapter; skip -, skip +, stop, play/pause. I’m going to ignore repeat and program as they make everything more complicated.

If the system is already playing a song, pressing any of these buttons makes intuitive sense:

  • skip -: Switch to the beginning of the previous song.

  • skip +: Switch to the beginning of the next song.

  • stop: Stop playing the song.

  • play/pause: Pause playing at this point in the song, ready to resume if this button is pressed again.

Does stop reset the place in the song to go to the beginning of the current song, to the beginning of the next song, or to the beginning of the CD? Does it matter?  While I have used various music playback interfaces like this and expect them to have a similar state machine, minor variations like the stop button are expected. Figure 4-5 shows the karaoke playing a song.

Figure 4-5: Flowchart of the karaoke playing a song

Note that the state transitions are not only caused by user events, they can also be caused by the state finishing its mission and moving along to the next state. I also had to add variables: current track # and track time.

When a state modifies a variable, we say that it has a side effect. Lots of side effects make a state machine more difficult to follow. I tend to put my side effects into one state, then use another state for event handling; you should draw state machines however they make sense to you. You can even use state diagrams, a more formal diagram system that has more rules about how to draw and lay out the system.

My flowchart doesn’t have all the states; there is a point at which the complexity of the chart makes it too hard to follow. So, what happens if you press skip + when it is in the initial waiting state? (The karaoke shows next track number, but reverts if you don’t press play in time). Putting all these events on the chart gets to be bothersome. On the other hand, interesting things happen when designers forget that users can press any button at any time.

Note:  The karaoke buttons are only handled with they are pressed down. While the system could handle also “button released” events, its state machine doesn’t bother.

Let me show you the way I really make/document/decode state machines (Figure 4-6). It is a little strange because it doesn’t involve drawing out the states at all. Down the left side are state names. Across the top are things that happen: side effect that happens on entry to the state, list of events, and what is currently on the display. If you want to know what happens when you push skip + while you are paused, you go to the state Pause row and then over to the Skip+ Button column, it says Pause_Skip+ which is the next state, go there. This state has two side effects: it increments the current track # and sets track time to zero. Then it goes back to the Pause state which waits forever for a button press.

It can be a little intimidating because it is dense: it packs a lot of information into a small space. I like to design with this table based view of state machines because it is easy to see if I’ve forgotten to handle a button in a given state.

Figure 4-6 Table based way of looking at the karaoke state machine

Even this is a simplification, dealing only with button handling. There is another state machine happening on the karaoke involved with reading chunks of data from the CD and playing them on the speakers, and another that interacts with a TV to put the words on the screen. A device can have multiple state machines, sometimes they interact with each other, sometimes not.


Aside: How long do you have to handle a button?

We saw in Buttons and Knobs that some of the karaoke buttons share the inputs in a matrix. They need to cycle through the output lines to figure out exactly which button is pressed.  So how fast do we need to cycle through?

The short answer is that buttons should be handled in less than 250 milliseconds, a quarter of a second. If you’ve ever used a toy that felt sluggish, where you press two or three (or twelve) buttons and then it finally handles the first one, you know how random and bad this feels. But this quarter of a second rule is usually fine for toys like the karaoke. Since it had two buttons on each line (review the matrix buttons if you forgot why), it needs to cycle through at 250ms/2 = 125ms.

With the quadcopter’s joysticks, the same 250 milliseconds would feel slow because there is continuous feedback in how the quadcopter moves. For those, the response time should be under 100ms (and faster is better).


Getting State Change Events

If you open the door to karaoke, the CD stops spinning and displays “OP” on the LED. I noted this error at the beginning of the flowchart in Figure 4-5 but it can happen at any time. It is clearly an interrupt: everything that is going on in the system immediately stops what it was doing to deal with the fact that the user opened the door.

When initially developing code for handling an input like the door open, I write a function that polls the input pin: the code checks to see if the value changes, waits a little then checks again. However, the karaoke can’t just poll, waiting for the door to open; it has too many other things going on. Instead, it uses a hardware interrupt to monitor the input pin, sending the event to the state machine.

When the door opens, the processor stops executing what it was doing and goes to a special location in memory (called the vector table). There it finds what function is needed to handle the particular interrupt and runs that function (called an interrupt service handler or ISR).

The interrupt is usually short, sending a signal to the state machine by setting a global variable the state machine looks at when it runs. ISRs need to be short, because the system may miss other interrupts if this one takes too long to handle.

Some interrupts happen in a system as part of the normal flow. A button being pressed or a timer expiring might cause an interrupt that triggers a transition in the state machine. On the other hand, errors like the door open interrupt essentially reset the system, clearing the state variables to their default states and starting the state machine over.

Interrupts are usually separated into hardware interrupts (button press, door open) or software interrupts (divide by zero errors).  However, both these hardware and software interrupts are handled in software, often in the state machine.


Next week, I’ll start out talking more about BB-8’s state machine and somehow end up looking at processors and a little bit of assembly code.

This is a series. If you’d like to read them in order, check out the Taking Apart Toys index.