Tuesday, September 20, 2011
My Front Panel boards arrived!
My Front Panel boards arrived! Thanks Laen! I placed the order on August 28th, and received the boards yesterday (September 19th). They look black (this is what happens when you use a camera phone with bad lighting), but they're really purple. Oh, so purple.
Monday, September 5, 2011
Front Panel Software
[This is a continuation of the Front Panel Hardware post; you should read it first]
Embedded Software
The software which runs on the front panel's microcontroller is about as simple as I could make it. It uses the ATMega168's built-in UART to communicate with the host, with RTS and CTS controlled manually by the embedded software.
To keep things simple, I made everything query-response. This way there's no need for timers or anything like that on the microcontroller -- the embedded software does nothing without first being asked. There are four essential commands:
Embedded Software
The software which runs on the front panel's microcontroller is about as simple as I could make it. It uses the ATMega168's built-in UART to communicate with the host, with RTS and CTS controlled manually by the embedded software.
To keep things simple, I made everything query-response. This way there's no need for timers or anything like that on the microcontroller -- the embedded software does nothing without first being asked. There are four essential commands:
- Reset all output pins to 0.
- Set an output pin to a given state (1 or 0).
- Specify the pins which should be monitored for input.
- Return the value of all input pins.
Pins are identified using a three-digit number decimal number. The first digit is 0 for output pins, and 1 for input pins. The second and third digit are the zero-padded pin numbers as silk-screened on the PCB. By separating the input and output pin spaces like this, we simplify the host program, and minimize the chances of error. Here's a diagram of the pin numbers:
Host Software
The program running on the host is responsible for displaying the user interface, for passing output state changes to the front panel, and for keeping the indicators up to date. I've implemented it in Python, using WxWidgets as the UI toolkit.
One of the primary requirements is that the user interface be customizable. This is necessary because we'll be using the front panel with a number of different circuits, each of which will have different input and output configurations. Accordingly, the configuration isn't hard-coded in the front panel program, but is instead driven by an external config file. Here's an example:
The program running on the host is responsible for displaying the user interface, for passing output state changes to the front panel, and for keeping the indicators up to date. I've implemented it in Python, using WxWidgets as the UI toolkit.
One of the primary requirements is that the user interface be customizable. This is necessary because we'll be using the front panel with a number of different circuits, each of which will have different input and output configurations. Accordingly, the configuration isn't hard-coded in the front panel program, but is instead driven by an external config file. Here's an example:
toggle('Bit': 0='0', 1='1',)
momentary('Actions': 2='A', 3='B',)
indicator('Result': 100='1', 101='2', 102='3', 103='4',)
(Yes, it's true. I was too lazy to tune the regexps to allow for the omission of the trailing commas)
We're using output pins 0-3, and input pins 0-3 to display a user interface with three different types of UI element. Here's what it looks like when running:
We're using output pins 0-3, and input pins 0-3 to display a user interface with three different types of UI element. Here's what it looks like when running:
The bit 0 and 1 buttons are toggle buttons -- one click is required to turn on the associated pin, and a second click is required to turn it off. Action buttons A and B are momentary -- their associated pins turn on when the button is pressed, and turn off when it is released. Indicators Result 1-4 change their background color when their input pins go high, and appear as shown when said pins go low.
Not a terribly complicated program, but I did have an interesting battle, and (re?)learned something new along the way: UI toolkits and threads are like oil and water. Tkinter (the default toolkit for Python) is like oil on fire.
In general, you confine all interactions with the UI to a single thread -- the UI thread. Anyone else who needs to change the UI posts something to the UI thread, and lets the UI thread take care of it. If you don't do this -- if you muck with the UI from a thread other than the UI thread -- the world ends in fire. If you're lucky, you get a stack trace. If not, just fire.
Tkinter exposes a monolithic event loop -- you call Tk.mainloop(), and it runs until it's time for your program to exit. There doesn't appear to be a way to break the loop up into its component parts so you could, say, wait for UI events and events from your non-UI threads using an interface like poll(2). Instead, everything is supposed to be done through the Tkinter event system. External things wanting attention post events -- events which are passed to callbacks which have registered using bind.
This would be fine, except for the fact that the event-poster isn't thread safe either. That is, you can't post an event from the non-UI thread. In fact, the only way for a non-UI thread to get the attention of the UI thread appears to be through polling. The UI thread asks to be woken up every n milliseconds, during which time it checks a queue of things that need attention. I don't like this approach at all.
After much gnashing of teeth (I still can't believe Tkinter doesn't have a thread-safe event post) and failed attempts to get around this problem without using polling, I found wxWidgets (specifically wxPython). WxWidgets has the same threading restrictions as Tkinter and other UI toolkits, in that it requires all UI interactions to take place on the UI thread. The crucial difference, however, is that wxWidgets specifically allows events to be posted from non-UI threads. Once you have that, everything is easy.
Aside: Why do I use threads? For the most part I don't, except for the serial port handler. Serial port reads and writes are handled by independent threads. This allows me to have non-blocking writes, which in turn allows me to do writes from the UI thread, since I don't have to worry about blocking it. On the read side, it's much simpler to have a separate read thread than to mix reading and writing. The only downside of this approach is that some reads (indicator status updates) need to directly affect the UI, and thus I need a toolkit which allows me to talk to the UI thread from a non-UI thread. WxWidgets allows that, and so everything comes together nicely.
Not a terribly complicated program, but I did have an interesting battle, and (re?)learned something new along the way: UI toolkits and threads are like oil and water. Tkinter (the default toolkit for Python) is like oil on fire.
In general, you confine all interactions with the UI to a single thread -- the UI thread. Anyone else who needs to change the UI posts something to the UI thread, and lets the UI thread take care of it. If you don't do this -- if you muck with the UI from a thread other than the UI thread -- the world ends in fire. If you're lucky, you get a stack trace. If not, just fire.
Tkinter exposes a monolithic event loop -- you call Tk.mainloop(), and it runs until it's time for your program to exit. There doesn't appear to be a way to break the loop up into its component parts so you could, say, wait for UI events and events from your non-UI threads using an interface like poll(2). Instead, everything is supposed to be done through the Tkinter event system. External things wanting attention post events -- events which are passed to callbacks which have registered using bind.
This would be fine, except for the fact that the event-poster isn't thread safe either. That is, you can't post an event from the non-UI thread. In fact, the only way for a non-UI thread to get the attention of the UI thread appears to be through polling. The UI thread asks to be woken up every n milliseconds, during which time it checks a queue of things that need attention. I don't like this approach at all.
After much gnashing of teeth (I still can't believe Tkinter doesn't have a thread-safe event post) and failed attempts to get around this problem without using polling, I found wxWidgets (specifically wxPython). WxWidgets has the same threading restrictions as Tkinter and other UI toolkits, in that it requires all UI interactions to take place on the UI thread. The crucial difference, however, is that wxWidgets specifically allows events to be posted from non-UI threads. Once you have that, everything is easy.
Aside: Why do I use threads? For the most part I don't, except for the serial port handler. Serial port reads and writes are handled by independent threads. This allows me to have non-blocking writes, which in turn allows me to do writes from the UI thread, since I don't have to worry about blocking it. On the read side, it's much simpler to have a separate read thread than to mix reading and writing. The only downside of this approach is that some reads (indicator status updates) need to directly affect the UI, and thus I need a toolkit which allows me to talk to the UI thread from a non-UI thread. WxWidgets allows that, and so everything comes together nicely.
Sunday, September 4, 2011
Front Panel Hardware
As described in an earlier post, I'm designing a front panel for the circuits in CODE. After much delay (this is what happens when you have too many hobbies), I've got the first version ready. This post will describe the hardware. A subsequent post will describe the software -- both that which runs on my Mac and that which runs on the front panel's microcontroller.
At its simplest, the purpose of the front panel is to control outputs and monitor inputs. The PC sends commands to the front panel via RS-232, and receives responses the same way. So at the very least, we'll need a microcontroller and an RS-232 transceiver (a future version might switch to USB, but not this one). Everything else depends on the number of input and output lines.
The most complicated front panel in CODE calls for 27 output lines and 8 inputs. Even an ATMega644 can't handle that many (40 pins minus 7 for non-general-purpose pins, 4 for the ISP, and 4 for RS-232 leaves 25), so one way or the other we're using a latch for some of the inputs or outputs. As it happens, I fried my ATMega644, and ended up prototyping this circuit with an ATMega168. A '644 would've been a few dollars cheaper once latch and PCB size are taken into account, but it is what it is. Perhaps a subsequent version will use the '644 (or whatever the USB-enabled equivalent may be).
The ATMega168 has 28 pins. Subtract out the preassigned pins, those used for the ISP, the clock, and those used for RS-232 (I'm using RX/TX and RTS/CTS, though that too could be changed in a future version), and we have 13 pins. 13 pins is just enough to get us 27+8, assuming we use three SIPO latches and 3 direct lines for the output, and one PISO latch for the input. So that's what I've done.
Here's the schematic:
This was my first schematic drawn using Kicad, and I'm very happy with the way it turned out. I had to draw a couple of the symbols myself (the switch, to my surprise, and the barrel connector), but other than that it was pretty painless.
Having drawn the schematic, having gone through the Teho labs tutorial, and knowing that I'd be using the front panel again and again without wanting to monopolize the breadboard, I decided to go all the way to having a PCB fabricated.
The first step on that road, with Kicad, is footprint assignment. Eagle differs in this respect, as you pick your footprints when you lay out the schematic. I have to say I really prefer the Kicad approach here. When I'm putting together a schematic (especially for something I have no intention of turning into a PCB), I don't want to have to care about the footprint. Not at all. So anyway, picking footprints. Kicad was missing a few that I wanted, so I ended up making my own. That was much less painful than I had expected.
After footprint assigment, board layout. Here's where I discovered that I'm nowhere near as intelligent as the auto-router. Sigh. All that education -- for nothing. When I first laid out the board, I figured no way the auto-router could route this -- it's too complicated (I laugh at that now). So I routed it myself. A couple of hours later, I had it routed, but the last couple of traces had 3-4 vias each as they wended their way about the board. Oh all right, I'll try the auto-router. A few minutes later, it has the entire board routed, with no vias.
Here's the routed board, in all its glory:
In real life, though, the board won't be green -- it'll be purple. I decided to use Dorkbot PDX to make the PCB. They have reasonable prices and a reasonably short turnaround time. Shorter than the places which make the boards overseas, but more expensive. The price difference was low enough that I was happy to pay it to have the board made here. I can't wait for it to arrive.
[This post is continued in Front Panel Software]
At its simplest, the purpose of the front panel is to control outputs and monitor inputs. The PC sends commands to the front panel via RS-232, and receives responses the same way. So at the very least, we'll need a microcontroller and an RS-232 transceiver (a future version might switch to USB, but not this one). Everything else depends on the number of input and output lines.
The most complicated front panel in CODE calls for 27 output lines and 8 inputs. Even an ATMega644 can't handle that many (40 pins minus 7 for non-general-purpose pins, 4 for the ISP, and 4 for RS-232 leaves 25), so one way or the other we're using a latch for some of the inputs or outputs. As it happens, I fried my ATMega644, and ended up prototyping this circuit with an ATMega168. A '644 would've been a few dollars cheaper once latch and PCB size are taken into account, but it is what it is. Perhaps a subsequent version will use the '644 (or whatever the USB-enabled equivalent may be).
The ATMega168 has 28 pins. Subtract out the preassigned pins, those used for the ISP, the clock, and those used for RS-232 (I'm using RX/TX and RTS/CTS, though that too could be changed in a future version), and we have 13 pins. 13 pins is just enough to get us 27+8, assuming we use three SIPO latches and 3 direct lines for the output, and one PISO latch for the input. So that's what I've done.
Here's the schematic:
This was my first schematic drawn using Kicad, and I'm very happy with the way it turned out. I had to draw a couple of the symbols myself (the switch, to my surprise, and the barrel connector), but other than that it was pretty painless.
Having drawn the schematic, having gone through the Teho labs tutorial, and knowing that I'd be using the front panel again and again without wanting to monopolize the breadboard, I decided to go all the way to having a PCB fabricated.
The first step on that road, with Kicad, is footprint assignment. Eagle differs in this respect, as you pick your footprints when you lay out the schematic. I have to say I really prefer the Kicad approach here. When I'm putting together a schematic (especially for something I have no intention of turning into a PCB), I don't want to have to care about the footprint. Not at all. So anyway, picking footprints. Kicad was missing a few that I wanted, so I ended up making my own. That was much less painful than I had expected.
After footprint assigment, board layout. Here's where I discovered that I'm nowhere near as intelligent as the auto-router. Sigh. All that education -- for nothing. When I first laid out the board, I figured no way the auto-router could route this -- it's too complicated (I laugh at that now). So I routed it myself. A couple of hours later, I had it routed, but the last couple of traces had 3-4 vias each as they wended their way about the board. Oh all right, I'll try the auto-router. A few minutes later, it has the entire board routed, with no vias.
Here's the routed board, in all its glory:
In real life, though, the board won't be green -- it'll be purple. I decided to use Dorkbot PDX to make the PCB. They have reasonable prices and a reasonably short turnaround time. Shorter than the places which make the boards overseas, but more expensive. The price difference was low enough that I was happy to pay it to have the board made here. I can't wait for it to arrive.
[This post is continued in Front Panel Software]
Friday, September 2, 2011
Switching to Kicad
I've been using Eagle for a number of circuits, but recently switched to Kicad. I was never too fond of Eagle's free-until-your-board-gets-too-complicated thing, since I knew that was likely to compel me to shell out a decent chunk of change to get the real thing when my boards finally got big enough (and I had enough invested in them). I also didn't like the way Eagle made me choose footprints during schematic capture, though that's really a nit.
It was this tutorial that finally got me to switch. Highly recommended. It walks you through the creation of a small power supply board, from schematic capture all the way through layout and routing. I liked the way everything fit together, and so I decided to switch.
One wrinkle. Kicad is unusable on the Mac as of "kicad_osx_v3056_DEV". There's some display problem which causes part moves leave trails, requiring you to hit refresh all the time. There was another problem which made parts disappear until you hit refresh. In summary, not usable.
As it happens, Kicad works pretty well on Linux, and VMWare Fusion makes Linux (Ubuntu) work pretty well on my Mac, so now I've got a VM running Ubuntu running Kicad. I don't even have to use GNOME -- I just ssh into the VM and remote-display Kicad back to the Mac's X server. Coordinating files between the two machines is a bit of a pain, but I've started checking everything into Mercurial, so I use a master repository to coordinate between the two. I'll certainly be happy if/when Kicad starts working properly on the Mac, but until then I've got a perfectly workable solution.
A couple of downsides, now that I've switched, and have spent some time working with Kicad:
It was this tutorial that finally got me to switch. Highly recommended. It walks you through the creation of a small power supply board, from schematic capture all the way through layout and routing. I liked the way everything fit together, and so I decided to switch.
One wrinkle. Kicad is unusable on the Mac as of "kicad_osx_v3056_DEV". There's some display problem which causes part moves leave trails, requiring you to hit refresh all the time. There was another problem which made parts disappear until you hit refresh. In summary, not usable.
As it happens, Kicad works pretty well on Linux, and VMWare Fusion makes Linux (Ubuntu) work pretty well on my Mac, so now I've got a VM running Ubuntu running Kicad. I don't even have to use GNOME -- I just ssh into the VM and remote-display Kicad back to the Mac's X server. Coordinating files between the two machines is a bit of a pain, but I've started checking everything into Mercurial, so I use a master repository to coordinate between the two. I'll certainly be happy if/when Kicad starts working properly on the Mac, but until then I've got a perfectly workable solution.
A couple of downsides, now that I've switched, and have spent some time working with Kicad:
- The documentation is spotty. Not entirely surprising, but annoying just the same. The documentation that does exist is fine, but there are lots of gaps. I tried Kicad once before, back before I settle on Eagle, and had discarded it because the docs were so incomplete that I had a hard time getting my head around it. Without the Teho Labs tutorial I mentioned above, I wouldn't have tried it again.
- The part libraries are nowhere near as extensive as those for Eagle. Or perhaps they are, but they just don't contain what I want. I found a site which has auto-conversions of Eagle parts, but they weren't really what I wanted either. I've resigned myself to having to design the occasional symbol and many footprints. This doesn't turn out to be as bad as I had at first thought, especially since I don't use that many parts. The first schematic/board involves the design of several parts, but subsequent ones just reuse those created for the first. This part generator, in particular, is very useful.
Subscribe to:
Posts (Atom)