www.robowars.org

RoboWars Australia Forum Index -> Technical Chat

Reading / Generating RC Pulses with microcontrollers


Post new topic   Reply to topic
  Author    Thread
marto
Experienced Roboteer


Joined: 08 Jul 2004
Posts: 5459
Location: Brisbane, QLD


 Reply with quote  
Reading / Generating RC Pulses with microcontrollers

So one thing which is generally common to most RC microcontroller projects is the need to generate and read RC pulses. The simplest solution to this is the:

while(1){
ReadPulse(pin 0); //Waits for pin to go from low to high then times the high section
GeneratePulse(pin 1, 1500); //Sets pin high, has delay of 1500 then sets it low again
}

These are both sub optimal solutions to the problem. The better solution is to use interrupts.

To read in pulses you trigger an interrupt on the high transition of a RC signal and then you read in a timer value and read it again when the low transition occurs. By subtracting the two timer values you get the pulse length.

To generate pulses the standard approach is to use a timer. A free running timer which counts at a known rate is used. This gets set high everytime and then get set low when it reaches a value which is set in the hardware comparator. By varying this value in the comparators registers we can change the duration of the pulse.

These two need very little processing time to implement. Which is why they are much better than the previous approach which waits for the pulse to be read in or be written out.

I was wondering if anyone has any libraries which implement these approaches well? In particular I am looking for something which ideally only uses one time and is flexible enough to be ported to most AVR platforms. If not I will continue this as as a sort of tutorial.
_________________
Steven Martin
Twisted Constructions
http://www.botbitz.com

Post Thu Sep 09, 2010 7:41 pm 
 View user's profile Send private message Send e-mail MSN Messenger
Spockie-Tech
Site Admin


Joined: 31 May 2004
Posts: 3160
Location: Melbourne, Australia


 Reply with quote  

http://www.avrfreaks.net/index.php?name=PNphpBB2&file=index
Is probably a good place to start looking if you are looking for generic AVR code.

"Sub Optimal" is dependant upon the final design requirements.

If low cost for high volume units is a prime requirement, then you write an assembly state machine with specific timers that implement the functions you need and no more to extract maximum computrons/$ from the cheapest hardware possible.

If fast/easy development with plenty of computrons left over for auxillary tasks is important, then you buy a beefier chip that has as much as possible already implemented for you in hardware. Multi-Channel Edge Triggered Interrupt/Timers, hardware PWM outputs and so on.

Its the grey zone in the middle that is where things get fuzzy and the "write the magic code yourself" vs "pickup someone elses work and wedge it into place" question begins to have several "correct" answers depending on the motivation of the constructor.

or in short, YMMV Smile

I probably have some old RC pulse decoding AVR Assembly squirelled away somewhere in the dusty corners of the hard drives from back when I was tinkering with AVR1200's (IBC brain) and ATTiny's, but its not likely to be in a nicely documented modular form that would just drop into any modern code base.

If you are looking general purpose modules to be incorporated into a larger code base then maybe investigating an RTOS project would be worthwhile ?
_________________
Great minds discuss ideas. Average minds discuss events. Small minds discuss people

Post Fri Sep 10, 2010 12:47 am 
 View user's profile Send private message Send e-mail Visit poster's website MSN Messenger
marto
Experienced Roboteer


Joined: 08 Jul 2004
Posts: 5459
Location: Brisbane, QLD


 Reply with quote  

So trawled through 80 pages of AVR projects on AVR-Freaks found one project which was relevant just on using interrupts with free running timer.

So I have been using Teensy and B328 my plan is to setup the PCINT so that it can be used on most of the pins as timer inputs. This is a much better way of doing it than the current read pulse way atm. Probably doesn't make a lot of difference in the performance as the speed of the chip is fast enough that it doesn't matter but it is how it really should be done.

I am also wondering whether the same timer used for this can also be used to output RC signals. Current plan is to use the comparator on the timer to trigger an interrupt. This could be used to serially send out signals which should ~100Hz or I was wondering if the comparator could be reset to a new value each time the interrupt is triggered. Might get round to writing this today if I can find some motivation.
_________________
Steven Martin
Twisted Constructions
http://www.botbitz.com

Post Sat Sep 11, 2010 1:13 pm 
 View user's profile Send private message Send e-mail MSN Messenger
Spockie-Tech
Site Admin


Joined: 31 May 2004
Posts: 3160
Location: Melbourne, Australia


 Reply with quote  

Ive not implemented this on the AVR, but In general, if you want to time events without serious hold-ups, you setup a free-running rollover timer with a "terminal count "end of count" interrupt, AND and an external-input interrupt.

Then you just need a few registers to keep track of "what value the timer was at when event started" and read the timer again (+rollover counts) on event end. Do some math, and you get the elapsed time.

Things to watch for with this tech are nested interrupts and subsequent delays - you want to have your "tick/rollover interrupt handling routine as quick as possible, or your external event might occur while you're dealing with the rollover routine, causing delayed processing and timing jitter.

of course you need to be careful with your variables, stack, state machine processing flags and so on, since multiple interrupt levels can get confusing for new players.

Thats why I mentioned that an RTOS might be a better option if you want to get going quickly, although they do suck up some computrons that could go towards user-app if hardware is limited.

Debugging interrupt assembly code sucks too. Wink
_________________
Great minds discuss ideas. Average minds discuss events. Small minds discuss people

Post Mon Sep 13, 2010 12:32 am 
 View user's profile Send private message Send e-mail Visit poster's website MSN Messenger
Valen
Experienced Roboteer


Joined: 07 Jul 2004
Posts: 4436
Location: Sydney


 Reply with quote  

another option you might look at is having a timer that fires of very rapidly, then just poll the pins.
The resolution won't be as good, but if you have need to do stuff at a high rate anyway (thinking melty stuff here) then it may be a good compromise.

I *think* thats how the IBC works, it does its PWM in software, so it polls for changes on the RC pins on the same interrupt it uses for turning PWM on and off.

It doesn't leave the MCU much time to do stuff outside the interrupt but hey, theres faster chips now ;->
_________________
Mechanical engineers build weapons, civil engineers build targets

Post Mon Sep 13, 2010 7:37 am 
 View user's profile Send private message Visit poster's website AIM Address Yahoo Messenger MSN Messenger ICQ Number
marto
Experienced Roboteer


Joined: 08 Jul 2004
Posts: 5459
Location: Brisbane, QLD


 Reply with quote  

Reading in is fine there is plenty of pin change interrupt pins. Its the outputting time pulses which is the issue.
_________________
Steven Martin
Twisted Constructions
http://www.botbitz.com

Post Mon Sep 13, 2010 8:08 pm 
 View user's profile Send private message Send e-mail MSN Messenger
Spockie-Tech
Site Admin


Joined: 31 May 2004
Posts: 3160
Location: Melbourne, Australia


 Reply with quote  

Load the timer with a calculated count-down value relating to the length of the pulse you want to output,
turn the output on
start the timer with a zero-count interrupt
<do other stuff>
on TC interrupt - turn the output off

Fairly straightforward, is there a problem with that method ?
_________________
Great minds discuss ideas. Average minds discuss events. Small minds discuss people

Post Mon Sep 13, 2010 8:55 pm 
 View user's profile Send private message Send e-mail Visit poster's website MSN Messenger
marto
Experienced Roboteer


Joined: 08 Jul 2004
Posts: 5459
Location: Brisbane, QLD


 Reply with quote  

Yep that was what I suggested above however, I was wondering if it could be done in parallel. My thoughts were order pins/pulse lengths. Then set next highest value each time the interrupt got called. I may be getting a little too adventurous. But if I want to output @ 400hz (which some brushless ESCs will accept) on multiple channels this could be useful.

I am prolly just making it hard for myself, but JIC.
_________________
Steven Martin
Twisted Constructions
http://www.botbitz.com

Post Mon Sep 13, 2010 9:06 pm 
 View user's profile Send private message Send e-mail MSN Messenger
Valen
Experienced Roboteer


Joined: 07 Jul 2004
Posts: 4436
Location: Sydney


 Reply with quote  

Its probably easiest to modularise it somewhat.

split your logic up into nice bite size chunks.
build a table of pulseouts you want to send.
if you change them somewhere, set a flag (after making the change (this is IMPORTANT))


then in your mainloop
check a dirty flag to see if you need to update
calculate the delay periods.
order them from shortest to longest
calculate the delay periods, look for any that overlap

then in your interrupt
set the next timeout - the number of instructions needed to do so
update a freerunning counter (that you make yourself so you can use it for stuff like Pulsein counting)
toggle your pins.
_________________
Mechanical engineers build weapons, civil engineers build targets

Post Mon Sep 13, 2010 9:30 pm 
 View user's profile Send private message Visit poster's website AIM Address Yahoo Messenger MSN Messenger ICQ Number
marto
Experienced Roboteer


Joined: 08 Jul 2004
Posts: 5459
Location: Brisbane, QLD


 Reply with quote  

Sounds like a good approach. Thanks
_________________
Steven Martin
Twisted Constructions
http://www.botbitz.com

Post Mon Sep 13, 2010 9:34 pm 
 View user's profile Send private message Send e-mail MSN Messenger
marto
Experienced Roboteer


Joined: 08 Jul 2004
Posts: 5459
Location: Brisbane, QLD


 Reply with quote  

code:

#include <avr/io.h>
#include <avr/pgmspace.h>
#include <stdint.h>
#include <util/delay.h>
#include <avr/interrupt.h>
#include "usb_serial.h"

#define CPU_PRESCALE(n) (CLKPR = 0x80, CLKPR = (n))
#define CPU_16MHz       0x00
#define CPU_8MHz        0x01
#define CPU_4MHz        0x02
#define CPU_2MHz        0x03
#define CPU_1MHz        0x04
#define CPU_500kHz      0x05
#define CPU_250kHz      0x06
#define CPU_125kHz      0x07
#define CPU_62kHz       0x08

//#Definitions
#define rcMax 2.5*8000
#define rcMin 0.5*8000
//Globals
char rcHeartbeat = 0;  //RC Timeout counter
char serialTimeout = 0;
char rcTimeout = 0;
char state = 0;  //Output State

int serOutputs[8] = {0};
char order[8] = {0};
int inputs[8] = {0};
char outputCounter = 0;
char onCounter = 0;

char prevPinState = 0;

int  startTime[8] = {0};

int main(void) {
    /****** Variables *******/

    /****** Initialisation ******/   
    CPU_PRESCALE(CPU_8MHz); //Setup Speed

    //We will use the Pin Change interrupts as Inputs
    //These are all on PORTB
    DDRB = 0; //Set PortB to be inputs.
    PORTB = 0xFF; //Also enable the internal pullups

    //Outputs doesnt really matter but we will use PORTD as they are on the other side.
    PORTD = 0x00;
   
    usb_init(); //Setup USB-UART

    //Setup Timer
        //Timer is initially in free running mode.
    TIMSK1 = 0x0F;    //Enable Timer interrupts

    //Enable Pin Change Interrupts
    PCICR  = 0x01;
    PCMSK0 = 0xFF;

    /****** Main Loop *******/
    while (1) {

        //If Debug output info to USB

        //Read in serial data if available
            //Load data into serial command registers

        //If Sticks in center and been there for timeout then use serial data

       
    }


}

ISR(PCINT0_vect ){
    //Read in Pins
    char Pins = PORTB;
    char temp = Pins ^ prevPinState; //XOR pins

    //Process pins have changed.
    char i = 0;
    while (temp != 0) //Check pins until there is no changes left
    {
        if (temp & ( 1<< i)){
            //Bit i has changed
            if (Pins & (1<<i)){
                //Bit i has gone high
                //Read in timer value and save
                startTime[i] = TCNT1;
            }
            else {
                //Bit i has gone low
                int currentTime = TCNT1;
                currentTime -= startTime[i];
                if (currentTime < rcMax && currentTime > rcMin){
                    //We have a valid pulse
                    inputs[i] = currentTime;
                    rcHeartbeat++;
                }
            }
        }
        i++;
    }
    prevPinState = Pins;  //Save pins for next time
}

ISR(TIMER1_COMPA_vect ){
    //Turn off current pin
    PORTD &= ~(1 << order[outputCounter - onCounter--]);

    if (outputCounter < 8)
    {
        OCR1A = inputs[order[outputCounter++]];
        onCounter++;
    }
}

ISR(TIMER1_COMPB_vect ){
    //Turn off current pin
    PORTD &= ~(1 << order[outputCounter - onCounter--]);

    if (outputCounter < 8)
    {
        OCR1B = inputs[order[outputCounter++]];
        onCounter++;
    }
}

ISR(TIMER1_COMPC_vect ){
    //Turn off current pin
    PORTD &= ~(1 << order[outputCounter - onCounter--]);

    if (outputCounter < 8)
    {
        OCR1C = inputs[order[outputCounter++]];
        onCounter++;
    }
}

ISR(TIMER1_OVF_vect ){
    outputCounter = 0;
    onCounter = 0;
    if (state == 1){ //RC mode
        while(onCounter < 1 && outputCounter < 8){
            if(inputs[order[outputCounter]] > 0){
                OCR1A = inputs[order[outputCounter]]; //put the first timer interrupt into OCR1A
                onCounter++;
            }
            outputCounter++;
        }
        while(onCounter < 2 && outputCounter < 8){
            if(inputs[order[outputCounter]] > 0){
                OCR1B = inputs[order[outputCounter]]; //put the second timer interrupt into OCR1B
                onCounter++;
            }
            outputCounter++;
        }
        while(onCounter < 3 && outputCounter < 8){
            if(inputs[order[outputCounter]] > 0){
                OCR1C = inputs[order[outputCounter]]; //put the third timer interrupt into OCR1C
                onCounter++;
            }
            outputCounter++;
        }
        if (outputCounter > 0){
            PORTD = 0xFF;
        }
    }
    else if (state == 2){ //Serial Mode

        while(onCounter < 1 && outputCounter < 8){
            if(serOutputs[order[outputCounter]] > 0){
                OCR1A = serOutputs[order[outputCounter]]; //put the first timer interrupt into OCR1A
                onCounter++;
            }
            outputCounter++;
        }
        while(onCounter < 2 && outputCounter < 8){
            if(serOutputs[order[outputCounter]] > 0){
                OCR1B = serOutputs[order[outputCounter]]; //put the second timer interrupt into OCR1B
                onCounter++;
            }
            outputCounter++;
        }
        while(onCounter < 3 && outputCounter < 8){
            if(serOutputs[order[outputCounter]] > 0){
                OCR1C = serOutputs[order[outputCounter]]; //put the third timer interrupt into OCR1C
                onCounter++;
            }
            outputCounter++;
        }
        if (outputCounter > 0){
            PORTD = 0xFF;
        }
    }
    else{
        PORTD = 0x00;  //Failure
    }

    //Check & reset heartbeats

}




This is hurting my brain. I think this should work but not 100%. Its not finished yet but should be able to read in pulses @ any rate and should output serial or forward RC data @ ~120 hz when finished.

The loading the output compare registers then turning off the correct pins is what has me atm. I think it should work but that ISR is getting pretty long.

Hmmm just though of a few more issues. Could miss output compare and I am not sure where is the best place to order the pulses as that getting interrupted or the values changing could be an issue...
_________________
Steven Martin
Twisted Constructions
http://www.botbitz.com

Post Fri Sep 24, 2010 11:48 pm 
 View user's profile Send private message Send e-mail MSN Messenger
  Display posts from previous:      

Forum Jump:
Jump to:  

Post new topic   Reply to topic
Page 1 of 1


Forum Rules:
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum

 

Last Thread | Next Thread  >
Powered by phpBB: © 2001 phpBB Group
millenniumFalcon Template By Vereor.