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.

Back from the store.

No H-bridges or half bridges or anything.  Ended up picking up a ULN2803A. But I'll need to do something to prevent shoot-through.

Scratch that.  Went and bought a servo for $10. Makes much more sense.


goal for tonight:

1) make servo move w/ pot control
2) make joystick serial program on PC
3) interface those two.

goal for tomorrow:
OpenCV stuff.

General architecture and risks

So, the general idea is that the computer vision will occur on the host PC, and that the host pc will send some sort of command to the atmega.  And then the atmega will handle the real-time control. Some of these details are a bit sketch at the moment.

Breakdown of the tasks:
  1. Computer:
    1. Minimum function: handshake w/ atmega
    2. Basic functionality A:  console "joystick" program
    3. Basic functionality B:   run face-detection example, and generate distance from center
    4. Basic functionality B2: if the face thing doesn't work, try using a light.
    5. Full functionality:  Combine these two!
    6. super functionality:  auto-tuning.
  2. Atmega:
    1. Basic functionality A: handshake with PC
    2. Basic functionality B: loop that blinks LEDs based on PC commands
    3. Basic functionality C: timed open-loop moves in response to PC commands
    4. Full function: PID control? feed-forward control?
    5. other test case: light detector
  3. Hardware design:
    1. Blink LEDs
    2. Motor drivers
    3. Motor physical mounting etc, camera mounting

Knowledge risks:
  1. OpenCV. Haven't used it before.   Hoping to mitigate this by largely using samples.  
  2. USB on Windows. really haven't used this before. before I've used linux and everything was just file-like and life was fairly easy, although there's probably some details I'm forgetting. Same for MATLAB.  Looks like some good info here: http://playground.arduino.cc/Interfacing/CPPWindows
  3. Reading from serial on the atmega. 
Technical risks:
  1. Loop speeds.  The vision loop is going to be limited to probably 10-60hz. The motor control may need to be faster than that. I might want the motor to make small movements and then wait for new as opposed to doing a naive PID.
  2. Motor power.  I remember my motor being a 12V motor. Not sure how much current it will pull at 5v. Probably more than the USB can handle. I have 9V battery too, not sure if that will power the 5V side. I think it does. There's a 1A regulator, ON 117-5 on the board.
  3. Affixing camera to motor, affixing motor to something rigid.  We'll start with tape.

Friday, February 22, 2013

Weekend Project #1




Pretty much, tie webcam to top of motor, run OpenCV to do a face tracker. Face tracker finds face and sends an error signal to Arduino, Arduino controls h-bridge, moving the motor.

The only H-bridge I have is an L298 in an annoying package, so I'll see if Fry's has one, or just make one from transistors. Or else, I do know Fry's has some cheap servos which wouldn't be a big deal either.

Computer side, haven't used OpenCV, but face tracking is one of the pretty common demos.