Friday, March 8, 2013

State Machines in Python

State machines are a pretty popular design pattern for designing reliable systems.  The general concept is that you have a system which is in one particular state at a time. The state a system is in should completely describe the system; i.e. a system should not have storage that isn't represented by the state.

State transitions occur in response to events. The system can only change states when an event occurs that makes it change. After the transition, the system will remain in the new state until an event occurs that causes it to move to another state.

The nice thing about state machines is that you can very easily make a diagram explaining the complete logic of your system, and then implement this diagram in code without making errors. And you have an easy way to break down the logic and look for errors. It's easy to instrument events and state transitions.

In C, it is very common to make the states and events be enums. Then your state transition logic can be handled by switch/case constructs.

so you'd likely have:
typedef enum State{
INIT = 0;
GO_FORWARD = 1;
GO_BACK =2;
TURN_LEFT = 3;
TURN_RIGHT =4;
} State;

typedef enum Event{
None  = -1;
Init_Done =0;
Hit_Wall =1;
Left_Key = 2;
Right_Key =3;
Timer_Done= 4} Event;
And then your state machine would be something like:
while(1)
event = checkForEvents();
switch state{
case INIT:
if (event == Init_Done)
    state = enterState(GO_FORWARD);
break;
case GO_FORWARD:
And so on. There are other ways to organize it--you could switch on the event for example.


Now of course Python doesn't have switch and it doesn't have enum. There are a lot of kludges for both, most contained within stackexchange reponses to frustrated C programmers. We could of course combine a kludgy enum with a kludgy switch.

If you're a shitty programmer like me, this is what you end up with at first cut:

class StateEnum():
    _vals=['no_change','Init','WaitLong','Acquired','WaitL','WaitR','ScanL','ScanR']
    def __init__(self,val):
        if self._vals.__contains__(val):
            self.val = val
        else:
            raise ValueError
    def __str__(self):
        return self.val
    def __eq__(self,y):
        return self.val == y


And down in our loop:

       while(self.runSM.isSet()):
            if self.state == StateEnum('Init'):
                event = EventEnum('done_init')
            elif self.state == StateEnum('WaitLong'):
                if self.hasFace:
                    event = EventEnum('Acquired')
                if self.timer[0]:
                    if self.timer[1] < time.time():
                        event=EventEnum('DoneTimer')
                else:
                    RuntimeError
            elif self.state == StateEnum('Acquired'):
                if self.hasFace:
                    cx = self.pidx.update(self.errx)
                    self.yaw = self.yaw - cx
                    self.servo.setyaw(self.yaw)
                    self.errx_last = self.errx
                    cy = self.pidy.update(self.erry)
                    self.pitch = self.pitch - cy
                    self.servo.setpitch(self.pitch)
                    self.erry_last = self.erry
                   else:
                    if self.errx_last <= 0:
                        event=EventEnum('LostL')
                    if self.errx_last > 0:
                        event=EventEnum('LostR')
            elif (self.state == StateEnum('WaitL')) or (self.state == StateEnum('WaitR')):
                self.yaw = self.yaw - cx
                self.servo.setyaw(self.yaw)
                cx=cx/2
                self.pitch = self.pitch - cy
                self.servo.setpitch(self.pitch)
                cy=cy/2
                if self.hasFace:
                    event = EventEnum('Acquired')
                if self.timer[0]:
                    if self.timer[1] < time.time():
                        event=EventEnum('DoneTimer')
                else:
                    RuntimeError
# (and so on)

Yeah, this is pretty horrible. Note that that i'm combining event checking and state transition logic, and that I have to use string compares to compare states, and that really everything is just a giant mess of elifs.


Next: A better way to do things!

2-D works (as of last week)

Haven't really  done much lately, but I'm back at it for the weekend. Anyway, got it working in 2-d:


Pretty sweet?



Code is a little messy, especially the state machine implementation. Also, there's an infrequent glitch in comms, and for some reason they don't work at higher baud rates--this is probably due to the interrupt being called for every character.

First thing is probably rewriting the state machine. I think I have a way to use inheritance to REALLY clean things up (will cover in another post).

After that comms. Adding a check byte for sure, and extending to add some new features.  Kind of toying with adding some features like slew, report position, etc.

Friday, March 1, 2013

Face Tracker working pretty well (in 1-d)


Ended up using Python. Pyqt4 + opencv.  Cool thing is that the code is 100% cross-platform so far. I don't even have to check what platform I'm on.

I guess the biggest problem is the latency through the camera and the image-processing pipeline.  I'm thinking of some ways to feed that into the PID controller.

Tuesday, February 26, 2013

Coms are like super-duper working now

Got python code talking to the board, have a joystick working and sending commands over the final protocol.


basically all that remains is putting together the CV code.


also, I just realized I could do a pan-tilt fairly easily. The only tricky part is having to connect sideways. Maybe I could glue something onto the side of my little webcam.






Monday, February 25, 2013

Slower Progress

Ended up doing crap yesterday while I figured out how I wanted to handle the PC side of things. Decided that I didn't really want to learn win32 so I ended up switching to python.

Meanwhile I write a new serial driver, complete with debug mode. Generally the idea is that the micro will ask the computer for locations, and the computer will respond in a somewhat timely manner. That way I don't have to worry about the computer overwhelming the micro or the serial bus. The messages will be fixed length, and i'll just pad them out.
The micro message is just 'g', for 'get data'.
There are two computer>micro messages, of 6 bytes length:

  • "pXX000" where XX is a 12 byte number spread over two bytes and masked with 01xxxxxx01xxxxxx. This is the position and legal values will just be 0-1024.
  • "s00000": Stop. 
edit: no this is dumb, I will just use begin/end message characters, and escape characters. way way easier.



Debug mode occurs when the handshake fails and is just a terminal interface.  For some reason it seems a little slow/buggy, but I think it's just the terminal program in AVR Studio.

Since I can now move the servo from the computer, I can also check how well the camera goes on the servo. Realized I can just tape the flat bottom of the webcam to the servo flange, at least as a basic method. Then I moved it around. Works fine, except the servo needs to be taped down too, and the cord on the webcam is a mini-issue.  I think I can just get some double-sided poster tape and have a nice solid connection for now, and tape on a strain relief for the cord.

Saturday, February 23, 2013

PWM on the Atmega328P




OK, this was kind of confusing, but not too bad really. The PWM works via the timers. You set a timer and it starts counting until it hits its max (ICR). You set a threshold (OCR), and then if the timer is under the threshold, the output pin will go high. Otherwise it goes low. There's some finicky stuff about phase correctness but that's about it.



The timers have a frequency that's going to be a divider of the main clock rate. We're at 16Mhz. I use the f/8 divider. Putting it in phase correct mode means it counts up and then down again, so for a full cycle it's another half that.





TCCR1B|=(1<<WGM13)|(1<<WGM11)|(1<<CS11); //PRESCALER= MODE 10(phase correct)

ICR1=20000; //50Hz (Period = 20ms).

DDRB|=(1<<PB1); //PWM Pins as Out

}




void initPWM()


{


TCCR1A|=(1<<COM1A1)|(1<<COM1B1)|(1<<WGM11); 

}

ICR 1 contains basically what the timer will count up to. So at 2 Mhz, we're going to count to 20,000 and back down again. That means 20ms per cycle. Finally, turn the pin on.







I had hoped to use timer 2, but it doesn't seem to have the ICR feature. Will have to figure that one out later.
Setting the PWM duty is pretty easy too.  OCR1A is the output compare register, and since things have worked themselves out, the units will just be a microseconds again.
OCR1A = on_time;    
So that's a PWM timer with 1usec resolution, on a 50hz cycle, with no processor overhead.


Initializing the ADC on an ATMega328



Decent tutorial on AVRFreaks, however his micro gets put into free-run a different way. I used ADATE instead, which is in ADCSRA.
void initADC0()
{
ADCSRA |= (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0);  // set prescale to 128; (table 23-5)
ADMUX |= (1 << REFS0);    // reference to vcc (table 23-3)
//ADCSRA |= (1 << ADFR);    // put into free-running mode. (not in 328??)
ADMUX |= (1 << ADLAR);    // put into 8-bit mode.

ADCSRA |= (1 << ADEN); 
ADCSRA |= (1 << ADSC);  // start taking measurements
ADCSRA |= (1 << ADATE); // Auto-trigger
ADCSRA |= (1 << ADIE);  // Enable ADC Interrupt
}
Then the interrupt handler is just:

ISR(ADC_vect)
{
ADCRead = ADCH;
}


Make sure interrupts are actually on! (use sei();)

ADCRead is a static, not sure if that's the best idea. But it makes the getter easy:

uint8_t getADC()
{
return ADCRead;
}


The only thing I'm not sure about is how often ISR gets called. It kind of seems like it would get called a lot.