Servo-ohjain

Monikanavainen servo-ohjain 8051:lle

Esimerkkiohjelma, miten voi generoida servopulsseja usealle RC-servolle käyttäen vain yhtä kontrollerin ajastinta.

/***************************************************************************
 
 servocont.c
 
 Monikanavainen servo-ohjain 8051:lle.
 
 Ohjelma generoi servopulssia 1...8 servolle käyttäen vain yhtä timeria (T0).
 Kunkin pulssin leveyttä voi säätää toisistaan riippumatta. Ohjelma ei käytä
 mitään perus-8051:n ulkopuolisia resursseja.
 
 Varsinainen pulssien generointi tapahtuu Timer0-keskeytyksessä. Pulssien 
 leveydet ovat pulse_len-taulukossa. Tässä esimerkissä on mukana yksinkertainen 
 pääohjelma, joka lukee sarjaportista komentoja, joilla servojen asentoja voi 
 asetella.
 
 Komennot ovat muotoa 
 
    <servon nro>,<pulssin leveys>
 
    servon nro = 0...4
    pulssin leveys = mikrosekunteja, 1000...2000
 
 esim. komento 1,1200 asettaa servon 1 pulssin leveydeksi 1.2 ms.
 
 Sarjaportin nopeus on 9600 b/s 24 MHz kiteellä.
 
 Servojen määrää voi säätää vakiolla MAX_SERVO_NR. Tämä on suurimman servon numero, 
 numerointi alkaa nollasta. Koodissa arvo  on 4, eli servoja on 5.
 Pulssin leveyden rajat asetellaan vakioilla SERVO_MIN ja SERVO_MAX (mikrosekunteja). 
 SERVO_MAX pitää olla alle 2500!
 
 Servojen ohjaukset lähtevät vakion SERVO_PORT ilmoittaman portin pinneistä (koodissa P2),
 servo 0 = P2.0, servo 1 = P2.1 jne.
 
 Vakiolla CYCLES_PER_US valitaan, onko käytössä 12 vain 24 MHz kide. Muut taajuudet 
 eivät käy. Huom. 12 MHz kiteellä sarjaportin nopeus putoaa 4800 b/s:iin!
 
 Ohjelma kääntyy SDCC:llä, komennolla
 
   sdcc servocont.c
 
 Testattu NXP:n P89CV51RD2:lla 24 MHz kiteellä. 12 MHz kiteellä ei testattu.
 
 JK 2.11.2008
 
 ***************************************************************************/
 
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <8051.h>
 
/* =====================================================================
------------------------ Vakiot ja makrot --------------------------- */
 
/* --- Tyypit */
typedef unsigned char BYTE;
typedef unsigned short WORD;
typedef __bit BIT;
 
// Tämän vakion määrittelyllä ohjelman saa toimimaan joko 12 tai 24 MHz kiteellä
//#define CYCLES_PER_US       1          // 12 MHz kide
#define CYCLES_PER_US       2          // 24 MHz kide
 
#define MAX_SERVO_NR        4          // ohjattavia servoja 5 kpl
#define SERVO_MIN           1000       // min servon ohjausarvo (us)
#define SERVO_CENTER        1500       // servon keskiasennon ohjausarvo (us)
#define SERVO_MAX           2000       // max servon ohjausarvo (us)
#define SERVO_INTERVAL      2500       // peräkkäisten pulssien väli (us)
 
#define INBUF_LEN           10
 
/* =======================================================================
------------------------ I/O ------------------------------------------ */
 
/* Servot portin P2 pinneissä P2.0 .. P2.MAX_SERVO_NR */
#define SERVO_PORT    P2
 
/* Timerit 16-bittisinä rekistereinä, helpompi käsitellä */
__sfr16 __at (0x8C8A) TMR0;
__sfr16 __at (0x8D8B) TMR1;
 
/* =====================================================================
------------------------ Tietotyypit -------------------------------- */
 
/* =====================================================================
------------------------ Globaalit muuttujat ------------------------ */
 
/* Pulssien leveydet mikrosekunteina */
int pulse_len[MAX_SERVO_NR+1];
 
/* Sarjaportin vastaanottopuskuri */
char inbuf[INBUF_LEN];
BYTE buf_idx = 0;
 
/* =====================================================================
------------------------ Funktioiden prototyypit -------------------- */
 
char getchar( void );
void putchar( char ch );
void putstr( char *p );
void init_hw( void );
 
/* =====================================================================
Pääohjelma
--------------------------------------------------------------------- */
 
void main( void )
{
    __data char *p;
    char ch;
    BYTE servo_nr;
    int plen;
 
    /* Laitteiston alustukset */
    init_hw();
 
    /* Kaikki servot keskelle */
    for (servo_nr = 0; servo_nr <= MAX_SERVO_NR; servo_nr++)
    {
        pulse_len[servo_nr] = SERVO_CENTER;
    }
 
    EA = 1;          // Keskeytysten sallinta
 
    /* --- Pääsilmukka --- */
    while (1) 
    {
        /* Odotetaan merkkiä sarjaliitännästä ja tulkitaan se */
        ch = getchar();
 
        if (ch == '\r')
        {
            /* Komento saatu, tulkitaan */
 
            inbuf[buf_idx] = '\0';   // loppumerkki puskuriin
 
            /* Etsitään pilkku */
            p = (__data char *)strchr(inbuf,',');
            if (p == NULL)
            {
                putstr("Syntax error\r\n");
                buf_idx = 0;
                continue;
            }
 
            /* Erotellaan servon numero ja pulssin pituus */
            *p = '\0';
            servo_nr = (BYTE)atoi(inbuf);
            plen = atoi(p+1);
 
            if (servo_nr > MAX_SERVO_NR)
            {
                putstr("Illegal servo nr\r\n");
                buf_idx = 0;
                continue;
            }
 
            if (plen < SERVO_MIN || plen > SERVO_MAX) 
            {
                putstr("Illegal pulse length\r\n");
                buf_idx = 0;
                continue;
            }
 
            /* Kielletään timer-keskeytys taulukkoon kirjoittamisen ajaksi, 
               jotta mahdollinen timer-keskeytys ei näe keskeneräistä dataa. */
            ET0 = 0;
            pulse_len[servo_nr] = plen;
            ET0 = 1;
 
            buf_idx = 0;  // puskuri tyhjäksi
        }
        else if (isdigit(ch) || ch == ',')  // hyväksytään vain numerot ja pilkku
        {   
            /* Merkki puskuriin */
            if (buf_idx < sizeof(inbuf))
            {
                inbuf[buf_idx] = ch;   
                buf_idx++;
            }
        }
    }
}
 
/* =======================================================================
Lukee merkin sarjaliitännältä.
----------------------------------------------------------------------- */
 
char getchar( void )
{
    char ch;
 
    while (!RI);
    RI = 0;
 
    ch = SBUF & 0x7F;
 
    /* Kaiutus */
    putchar(ch);
 
    if (ch == '\r')
        putchar('\n');
 
   return ch;
}
 
/* =======================================================================
Lähettää merkin sarjaliitäntään.
----------------------------------------------------------------------- */
 
void putchar( char ch )
{
   TI = 0;
   SBUF = ch;
 
   while (!TI);
}
 
/* =======================================================================
Lähettää merkkijonon sarjaliitäntään.
----------------------------------------------------------------------- */
 
void putstr( char *p )
{
    while (*p)
    {
        putchar(*p);
        p++;
    }
}
 
/* =======================================================================
Alustaa laitteiston
----------------------------------------------------------------------- */
 
void init_hw( void )
{
    /* --- Ajastimet --- */
    /* Timer 0: servojen pulssitus */
    /* Timer 1: baudigeneraattori 9600 b/s tai 4800 b/s */
    TMOD = 0x21;    // Tmr 0 = mode 1, timer; Tmr 1 = mode 2, timer
    TCON = 0x00;    // IEx = 0, TRx = 0, TFx = 0 
    TMR0 = 0xFFFF;  // keskeytys heti
    TR0 = 1;        // Timer 0 käyntiin
 
    TH1 = 0xF3;     // 9600 b/s @ 24 MHz tai 4800 b/s @ 12 MHz 
    PCON = 0x80;    // SMOD = 1
    TR1 = 1;        // Timer 1 käyntiin
 
    /* --- Sarjaportti --- */
    SM1 = 1;        // Mode 1
    REN = 1;        // Receive enable
 
    /* --- Keskeytykset --- */
    IP = 0x00;      // Prior.0: kaikki
    IE = 0x02;      // Enable: Tmr0; Disable: muut  (EA=1 vasta pääohjelmassa)
}
 
/* ======================================================================= */
/* =====================       KESKEYTYSPALVELUT      ==================== */
/* ======================================================================= */
 
/* =======================================================================
 Timer 0: generoi servopulssit.
 
 Keskeytyksessä käydään joka kerta, kun jonkun servon pulssin tilaa pitää
 vaihtaa, eli 2 kertaa per servo. Samalla timeriin alustetaan aika seuraavaan
 tapahtumaan.
 
 Halutut pulssien leveydet mikrosekunteina ovat taulukossa pulse_len[]. 
 Taulukkoa indeksoidaan servon numerolla. 
 
 Keskeytyspalvelussa lasketaan valmiiksi seuraavaa keskeytyskertaa
 varten timeriin ladattava arvo sekä menossa olevaa servoa vastaava maski.
 Näin pulssin aloitus/lopetus ja timerin uudelleen lataus saadaan tehtyä 
 heti, kun tullaan keskeytyspalveluun, tarvitsematta käyttää aikaa arvojen 
 laskemiseen.
 
 Kahden eri servon pulssien alkuhetkien väli on SERVO_INTERVAL (2.5 ms).
 Tällöin jää aikaa tehdä laskenta silloinkin, kun edelliselle servolle 
 generoidaan maksimittaista pulssia (2 ms). 
 
 Viimeisen servon jälkeen pidetään pitempi väli, jotta yhden servon pulssien
 alkuhetkien väliksi tulee 20 ms.
 
 Lipulla pulse_start pidetään lukua, ollaanko generoimassa pulssin alkua
 vai loppua. Joka toinen kesksytyskerta on aina pulssin alku, joka toinen 
 loppu. Servo vaihtuu kahden keskeytyksen välein.
 
 Keskeytyspalvelun suoritusaika 24 MHz kiteellä on n. 30 us.
 
----------------------------------------------------------------------- */
 
void timer0_isr(void) __interrupt(TF0_VECTOR) __using (1) 
{
    static BIT pulse_start = 1;
    static int next_timer_val = -(SERVO_CENTER * CYCLES_PER_US);
    static BYTE next_mask = 0x01;
    static BYTE cur_servo = 0;
    int last_interval;
 
    /* Servopulssi päälle tai pois */
    if (pulse_start)
        SERVO_PORT |= next_mask;
    else
        SERVO_PORT &= ~next_mask;
 
    /* Alustetaan timer 0:aan aiemmin laskettu arvo */
    TF0 = 0;
    TMR0 = next_timer_val;
    TR0 = 1;
 
    /* Uusi pulssi tai pulssien väli on käynnissä, joten nyt on hyvää
       aikaa laskea seuraava maski ja ajastimen arvo */
 
    pulse_start = !pulse_start;  // vaihdetaan lipun tila
 
    if (pulse_start)
    {
        /* Seuraavaksi tulee pulssin alku, joten seuraava servo käsittelyyn. */
        cur_servo++;
        if (cur_servo > MAX_SERVO_NR)
            cur_servo = 0;
 
        /* Lasketaan seuraavan servon pulssin pituus ja servoa vastaava maski */
        next_timer_val = -(pulse_len[cur_servo] * CYCLES_PER_US);
 
        next_mask = 1 << cur_servo;
    }
    else
    {
        /* Seuraavaksi tulee pulssin loppu, joten pysytään samassa servossa. 
           Lasketaan väli pulssin lopusta seuraavan pulssin alkuun. */
 
        /* Jos ollaan viimeisessä servossa, tulee pitempi väli, jotta saadaan
           20 ms täyteen */
        if (cur_servo == MAX_SERVO_NR)
            last_interval = SERVO_INTERVAL*(7-MAX_SERVO_NR);
        else
            last_interval = 0;
 
        next_timer_val = -((SERVO_INTERVAL - pulse_len[cur_servo] + last_interval) * CYCLES_PER_US);  
 
        /* Maski pysyy samana, koska servo ei vaihtunut */
    }
}
 
/* ============================ EOF ====================================== */
Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License