Tuesday, May 17, 2011

My own microprocessor

I’ve decided to build the microprocessor described in CODE. In that book, Petzold describes the development of a processor pretty much from scratch. He describes everything but the control logic, so that’ll give me a nice exercise to start out with. Follow along for the non-control logic (with a few tweaks along the way), and build the control logic from scratch. Once I do that, I’ll feel confident enough to build the second version from scratch, to my own design.

Petzold describes his circuits in terms of relays, with a control panel made of switches. Obviously I’m going to use TTLish ICs for the circuits, but I still need some sort of control panel. If the 7-segment LED circuit taught me anything, it’s that switches need debouncing if they’re going to have a prayer of working properly. The most complicated front panel in Petzold’s book has 27 switches on it, each of which would presumably need its own debouncing circuit. No thank you. Furthermore, that front panel is eventually used for loading in programs. Toggling programs in may have been fun in 1972, but I really don’t have the patience for it today.

Instead, I’m going to use an AVR for the front panel. I’ll have a program which displays a UI running on my Mac. It’ll talk to the AVR via RS232, and will tell the AVR which ports to enable/disable/read. Eventually I’ll write something which uses that same interface to “toggle” in new programs. All will be sweetness and light.

Monday, May 16, 2011

Gran Fondo NYC

I did the inaugural Gran Fondo NYC last weekend, and had a great time.  I'd been more than a little nervous about whether the organizers would be able to pull it off, but they did an amazing job with the parts that count.

We started on the lower deck of the GWB, in the NJ-bound lanes.  They had initially capped the race at 8,000 riders, but fell well short of that.  According to the announcer, just under 2,500 people lined up on the bridge.  So, a bunch of people on the bridge, but a manageable number.  Not so many that I wasn't on my bike, pedaling, by the time we crossed the starting line.  Over the GWB, exit to the right, an overpass over the highway, and then down into Palisades Park.  There were police at every intersection, both here and throughout the ride, ensuring that bikes had right of way.  I only had to stop twice for traffic signals during the entire ride.  It would be difficult to overstate how wonderful it was to have the police directing traffic for us.


I brought a bunch of GU and two Clif Bars, but was planning to rely primarily on the aid stations for nutrition and hydration.  The aid stations were nicely spaced and nicely stocked, allowing me (with one exception) to refill both bottles before they ran dry.  The one exception was the last aid station, which was a mile or two past when I ran dry.  If nothing else, this has convinced me that I need a seatpost-mounted bottle cage just like I have on my P2C.  Two bottles just isn't enough for a long ride.  Either you're stopping every 40 miles or less for a refill (more if it gets hot, as it did for this ride), or you're constantly in danger of running dry.  Riding dry is no fun at all, nor is riding in fear of it.  With three bottles, I'd be able to do 50 miles between refills, which would suit me just fine.  I like to eat something real (i.e. more than a Clif Bar or a GU) every 50 miles, so having my hydration stops match up with my nutrition stops would be ideal.

But back to the race.  We headed out on River Road.  So many people passed me on River Road, but I caught several on the hills.  I was keeping my speed down on the inclines, as I knew there'd be plenty of misery ahead.  Once we popped out on 9W I started to push it a bit (20mph until Nyack) until I was able to truly convince myself to stop trying to keep up with groups on the flats.  Thanks to the traffic control and my penchant for riding solo, I had State Line Hill to myself, and was able to take the entire descending traffic lane.  I've never had such a care-free full-speed descent of State Line Hill.

Up Old Mountain Rd, over Toga/Rockland hill -- basically the standard Little Tor / Orchards route.  The first timed climb of the day was Little Tor.  I'd been practicing this one, and was ready for it.  It's 3/4mi long, with just over 400' of climb.  There's a sharp incline at the start, and then constant misery for the rest of the way up.  This was my third nonstop time over (I stopped twice on my first time over Little Tor, so that time doesn't really count).  There's a picture of me grimacing my way to the top of Little Tor, with Christophe Vandaele (of SBR fame) right on my wheel.  I don't know whether he passed me or not, but it's nice to know that I was faster than someone getting over that hill.  Even if he was on a single speed, and likely working twice as hard as I was.  I finished the climb in 8:26.96 according to the chip, and 9:58 according to MyTracks/Strava, which uses a different starting point.  9:58 is a PR for me by just over a minute.

After Little Tor, on to Buckberg Mountain -- a hill I hadn't climbed before this race.  Buckberg Mountain Road is an absolute bastard.  The specs aren't that crazy -- at 367', it's about 50' shorter, and it's almost 0.5mi longer (1.2mi vs 0.75mi) -- but the profile is evil.  Rather than being a continuous climb a la Little Tor, it's a series of steep windy sections (with increasing grade) interleaved with less-steep (but still ascending) sections.  Yeesh.  It might be easier the second time around (and there most definitely will be a second time around), but it's always going to be hard.  Official time: 10:12.51, MyTracks/Strava time: 11:10.

The approach to Buckberg was through parts of Stony Point that I've never ridden before, and which are a nice change from the NYS Bike Route 9 route that goes east of 9W, through Haverstraw.  Buckberg Mountain Road can also be viewed as a way to bypass 9W through Tompkins Cove, but it's a hell of a way to do it.

With Buckberg Mountain done, off we go to Bear Mountain -- the toughest climb on the ride.  Strava rates the other climbs on the ride as category 4.  Bear gets a 2.  The grade isn't as brutal as, say, Little Tor -- the rating comes from the total climb (1200' from 9W) and the distance (4.5mi, again from 9W).  There's also the matter of just getting to Bear.  There's a lot of up and down during the few miles from Buckberg to Bear, including one category 4 climb.

But eventually we get to Bear Mountain (hooray for the police blocking traffic so we could get onto 7 Lakes from 9W without even slowing down).  I'm used to counting distance and height from 9W, but the GFNY organizers put the timed climb start at the traffic circle, which is about 3/4mi of the way in, and about 200' up.  Anyway, I decided I wanted to do the entire thing nonstop, from 9W all the way to the summit, so I skipped the aid station.  This made my ride unpleasant, as my bowels were feeling a bit compressed, but I'd probably do it again given the chance, as it let me knock out the whole 1200' in one go -- something I'd never done nonstop before.  This was only my second time up Bear Mountain.  The first time I did it, I had to stop twice on Perkins to catch my breath.

Climbing Bear was a "simple" matter of grinding through the climb, counting off the tenths of a mile until the top.  My power meter says I was doing an average of 200 watts for 35 minutes up that hill.  First, there's 7 Lakes Drive, which is about 2.25mi from 9W.  7 Lakes is relatively easy, and even has some oh-so-very-brief flat bits.  Then comes Perkins, which is narrower, steeper in sections, and goes all the way to the top.  And oh, what a blessed top.  Pictures, resting, and then almost 4 miles of descent to the aid station.  Real bathrooms, and my newfound favorite ride food: PB&J bagels.  I started eating those at each aid station from Bear Mountain on, and was very pleased with the results.  I basically did the ride on those and GU, with a few Stacy's Pita Chips thrown in now and again for salt.

In the distant future (about 20mi away) is the fourth timed climb -- southbound over Little Tor -- but first there was a bit of a diversion over to Gate Hill Rd.  The route to Gate Hill took us back over the Bear Mountain approach climbs (a 1mi cat 4 being the most notable), to the base of Buckberg Mountain.  We didn't retrace back to the summit, thank goodness, but we did climb over the side, on Mott Farm Rd.  I think we probably did at least half of the Buckberg climb, over some truly evil inclines.  Once past the intersection with Buckberg Mountain Road, the route was flat to descending (ish) to the base of the Gate Hill climb.  Very pretty scenery back there.

Gate Hill begins about a mile before the Parkway, and climbs about 700' to its intersection with Willow Grove.  Why this wasn't a timed climb, I'll never know.  Its only saving grace was the elevation profile.  Gate Hill is a series of climbs interleaved with flat sections (perhaps they only felt like flat sections by comparison).  The motivator for that climb is the knowledge that you're going to get to descend Willow Grove.  And what a glorious descent.  About 325' in 1mi with a gradual turn in the road, and nobody entering on the right.  That hill never gets old.  Let me rephrase.  Descending Willow Grove never gets old.  Ascending Willow Grove is a different story entirely.  A very sad story.

Once we descended Willow Grove, we had about 2mi to go to the aid station near the base of Little Tor.  Rehydrate, re-nutrate(?), and up over Little Tor we go.  This side is pretty easy.  There are some individual climbs leading up to it, followed by a "big" constant climb that ascends about 200'.  Nowhere near as difficult as doing Little Tor in the other direction.  The race organizers were able to close Little Tor in our direction of travel -- northbound on the way out, and southbound on the way back.  As such, I was able to descend the south face of Little Tor without fear of traffic.  This was the first time I've ever enjoyed a trip down the south side of Little Tor.  With traffic, you normally have to ride down on a sketchy shoulder.  Without, I was able to do the whole thing in the traffic lane, which was heavenly.

We returned along South Mountain to Ridge, but kept going on Ridge to Strawtown, rather than going back to 9W via 304.  In West Nyack, we jogged over to Western Highway, and then to King's Highway, which took us to Piermont.  This return was largely flat, with some light rollers, and was a nice change from the brutality of the preceding miles.  At the end of King's Highway, a quick jaunt down Orangetown led us to Valentine, Highland, and back to 9W.  This was the first time I'd gone from King's Highway to 9W without going over the bridge.  It's a nice detour to know, even though it means you have to climb Highland to get back to 9W.

The organizers didn't see fit to give us police protection along 9W, which was reasonable, since we weren't on it for very long.  The final aid station was in the dip before IBM, in the parking deck under the unoccupied office building.  That final station was only 20 miles after the one that preceded it, but nonetheless I'd somehow managed to go through both bottles during that 20 miles.  I didn't refill after eating at the Little Tor stop, so maybe I did the 20 on a bottle and a half or less.  Regardless, I ran dry about a mile before this final aid station.  Had the last station not been there, I would've been forced to stop at the deli before State Line to refill -- no way was I going to to State Line and friends on dry bottles.

Refilling at the final aid station, even though it was only 12mi from the finish, turned out to be a wise decision for other reasons as well.  Due to apparent permitting reasons, the organizers had to change the finish line location from somewhere in NJ (Edgewater-ish?) to Palisades Park.  In order to get permission to do that, they had to have a "simple" (their words) finish line.  I had assumed that "simple" would at least mean water.  Turns out not so much.  The only thing at the finish line was a timing mat.  I know at least one person who skipped the final aid station, assuming that the finish line would have water, and ended up riding dry back to the city.  My stop saved me from that (I've done it before -- it's not fun), and left me with enough to drink all the way home.

With a finish line in Palisades Park (specifically, the southern traffic circle), the end of the route has to involve a trip down River Road.  Specifically, it must enter Palisades Park at Alpine Approach, and must descend River Road.  I feel for the out-of-towners who had to do that descent (NJ hasn't repaved that road in something like 300 years) -- especially for the many who were seen fixing flats near the bottom of it.  Oh well.  We didn't want them to leave having only seen the best of NYC-area cycling -- they should see the worst of it as well.

The congestion on River Road hill wasn't nearly as bad as I had expected, and so I was able to keep to the relatively smooth parts.  A few more miles on River Road, and I rolled across the finish line in style.  102mi.  8700' of climbing.  The hardest century I'd ever done: FINISHED.

Now, to get home.  The finish line is about 1/3 the way up River Road from the water, with an ascent of the remaining 2/3 needed to get to the bridge.  I'd blown that ascent all out of proportion, and had been very nervous that I wouldn't be able to make it after having completed the GFNY.  Boy was I mistaken.  That hill was nothing compared to what I'd been riding, and I sailed (ok, crawled) up it.  A short ride home, and a nice relaxing evening.

A word on training.  I wouldn't have been able to complete this ride without the awesome training plan I got from Brad Gansberg.  That man had me climbing more hills that I thought existed.  But it paid off.  Oh, how it paid off.  Midweek rides which included several repeats of River Road hill plus a trip up Eisenhower.  Weekend rides which routinely topped 6000' of climbing.  So many hard rides.  But I'm oh so happy with the result.

Next stop -- a 10,000' ride.  Maybe one of the ones put on by the PA Randonneurs.

RTS/CTS handshaking and waveforms

I decided to learn about RS-232 signalling and handshaking.  I had a heck of a time finding a single page which summed up everything I would’ve needed to know, so I’m writing this post in the hopes that it’ll help someone else in the same situation.
Here’s what I’m trying to do:  I have a DB-9 serial port hooked up to an ATMega644 microcontroller (MCU), with an SP3232EB RS-232 transceiver in the middle, as shown below:

Three-wire RS-232 (RX, TX, GND) wasn’t sufficient for my purposes, so I’m going to do five (RX, TX, RTS, CTS, GND).

When we talk about RX and TX, we’re going to do it from the MCU’s perspective.  That is, we read from the PC on RX, and we transmit to the PC on TX.  RTS and CTS have non-reversible meaning, so no clarification needed.

How does this all work?  RTS/CTS handshaking is used to ensure that the peripheral (the MCU, in our case) is ready to handle data from the host (the PC).  It doesn’t appear to apply in the other direction (or if it does, I haven’t found the need to care).  The idea is that the PC is powerful enough to handle just about anything you may care to throw at it, but the dinky MCU isn’t so lucky.  The PC can easily overflow any buffers the MCU may care to put in place, so the MCU needs a way to tell the PC when the PC is allowed to send new data.  That’s where the handshaking comes in.

There are two ways to do RTS/CTS handshaking.  The first is request/acknowledgement.  When the PC is ready to send data, it uses RTS (request to send) to tell the MCU that it (the PC) has data to send.  I say “uses” because the logic levels are a bit funny, as described below, but are unimportant for now.  The MCU sees that the PC is ready to send data.  When the MCU is ready to receive it, it uses CTS to tell the PC that the data can be sent.  The PC watches CTS to see a) when it can start sending data, and b) when it must stop.  The second type of RTS/CTS handshaking does bidirectional metering (more details here).  I've chosen to implement request/acknowledgement, but conversion to bidirectional would be straightforward.

When interfacing RS-232 and TTL, we have to deal with two different standards for logic levels.  TTL is 0-to-not-zero.  0-to-5, in the case of my circuit.  5V is asserted; 0V is not asserted.  RS-232, on the other hand, uses positive and negative voltages.  A positive voltage is referred to as a space, while a negative voltage is a mark.  The transceiver’s job is to translate between RS-232 and TTL voltages.  It does so by mapping RS-232 positive to TTL 0, and RS-232 negative to TTL 1.  That is, the TTL voltages are roughly the inverse of the RS-232 voltages.

Here’s the letter p (0x70), being sent across the wire at 9600 baud, 8N1:


Now that we know this, let’s restate the operation of RTS and CTS using the TTL voltages generated by the transceiver.  This is probably the most important paragraph of this post, and took a surprisingly long time to beat into my thick skull.

RTS and CTS are normally held high (TTL 1).  When high, no request (RTS) has been made, and no permission (CTS) has been granted.  When the PC is ready to send data, it brings TTL RTS low.  The MCU sees this.  When the MCU is ready to receive data, it brings TTL CTS low.  The PC sends data as long as TTL CTS is low, and stops when the MCU takes TTL CTS high.  One imagines that the sender is allowed to finish the current symbol (byte), but I haven’t verified that.

Now, to wire everything up.  As stated above, we’re using RX and TX from the perspective of the MCU, as that’s how everything is labeled on the MCU’s end (i.e. in the MCU itself and in the transceiver).

SB3232EB (MAX232)
Function Direction DB-9 pin RS-232 pin TTL pin AVR pin
RX PC to AVR 3 13 (R1IN) 12 (R1OUT) 14 (PD0/RX)
TX AVR to PC 2 14 (T1OUT) 11 (T1IN) 15 (PD1/TX)
RTS PC to AVR 7 8 (R2IN) 9 (R2OUT) 17 (PD3)
CTS AVR to PC 8 7 (T2OUT) 10 (T2IN) 16 (PD2)

I messed up the wiring a couple of times until I drew up the above chart.  Then everything went together smoothly.

Here’s the end result, captured by an Open Bench Logic Sniffer via the OLS LogicSniffer client:


We’re looking at an attempt by the PC to send a multi-character string as fast as it can, only to be metered by the MCU.  Please excuse the bubbles -- when zoomed in, they show the byte values being transmitted.  As we can see, the MCU idles with CTS low (ready to receive).  A character comes in, and the MCU raises CTS (stopping further transmissions from the PC) while it processes the received byte.  In this case, the program running on the MCU sends some data back to the PC describing the received byte.  Once that’s done, the MCU lowers CTS to receive another byte, raises CTS once that byte is received, and repeats the process.  By doing this, we receive everything the PC wanted to send -- nothing gets lost due to MCU buffer overflows.

Here's some sample code, written for the ATMega644:

#include <avr/io.h>

#define UBRR_ASYNC_NORMAL(baud) \
  (((((F_CPU * 10) / (16L * baud)) + 5) / 10) - 1)

#define RTS PD3
#define CTS PD2

#define BAUD 9600

static int uart_haschar(void) {
  return (UCSR0A & (1 << RXC0));
}

static int uart_getchar(void) {
  while (!uart_haschar());
  return UDR0;
}

int main(void) {
  UBRR0H = UBRR_ASYNC_NORMAL(BAUD) >> 8;
  UBRR0L = UBRR_ASYNC_NORMAL(BAUD);
  UCSR0B = (1 << RXEN0) | (1 << TXEN0);
  UCSR0C = (0 << USBS0) | (3 << UCSZ00);

  DDRB = 0b11101111;  // PB4 = MISO
  DDRD = 0b11110110;  // PD0 = RX, PD3 = RTS

  // Inhibit CTS by setting it to TTL 1 (RS232 off)
  PORTD |= (1 << CTS);

  for (;;) {
    // Wait for host to set RTS (set it to TTL 0)
    while ((PIND & (1 << RTS)) != 0);

    // Set CTS (TTL 0 == RS232 on)
    PORTD &= ~(1 << CTS);

    char c = uart_getchar();

    // We have a character, so clear CTS (TTL 1 == RS232 off)
    PORTD |= (1 << CTS);

    // Do something with c
  }

  return 0;
}

There, of course, other ways to guard against buffer overflows.  We could read from the UART using an interrupt handler, but that a) complicates the code considerably and b) only works if we can keep up with the uncontrolled data rate over the long run.  Perhaps more realistically, we could structure our commands to the MCU such that they all have responses, offloading the responsibility for guarding against overflows onto the PC.  That won’t work, though, if we want the microprocessor to be able to generate its own messages asynchronously.

Updated: Clarified terminology for ATMega/AVR/MCU, added diagram, fixed T1IN pin number, described the other way to look at RTS/CTS handshaking for bidirectional use.
Updated: Changed initial setting of PORTD so it just sets the CTS bit.  Not absolutely necessary, since there's nothing else in this code that'll be affected by it, but it'll help anyone who tries to cut'n'paste.