Sida 1 av 2

Styra RC-servo med Timer? (PIC12F675. ASM)

Postat: 14 maj 2007, 05:00:24
av JimmyAndersson
Eftersom projektet bara består av ett servo och fyra knappar så har jag valt en PIC12F675 som kommer köras med den interna klockan. I projektet vill jag kunna styra ett RC-servo med två knappar. (Mer om det nedan.)

Tanken är att servoaxeln ska vridas "ett steg" (så litet som möjligt) varje gång man trycker på en knapp. Trycker man inte så står servoaxeln stilla. Håller man in knappen så vrids servoaxeln sakta tills det når något av ändlägena. (Det ska finnas två knappar, en för att styra medsols och en för motsols.)

Tidigare har jag gjort detta med delayloopar i MikroBasic, men nu vill jag göra det i assembler och timer.

Problemet är att jag inte kommer på hur.

Med prescalern kan man ju ändra hur fort man får Timer-interrupt, men varje prescaler 1:2 , 1:4 , 1:8 osv gör ju att tiden fördubblas. Jag vill att servoaxeln ska vridas lika mycket vid varje knapptryckning.

Så hur gör man? Eller är timern fel "verktyg" för detta?
Om så är fallet så undrar jag hur man gör det med t.ex delayloopar. I MikroBasic var det så smidigt eftersom man bara behövde skriva vdelay_ms(variabeln) och så kunde man enkelt ändra tiden. Men hur gör man för att enkelt ändra tiden för delayloopar i assembler?

Med delayloopar menar jag t.ex:
delay_10ms
movlw 0x14
movwf CNT4
movlw 0x0D
movwf CNT5
movlw 0x08
movwf CNT6
dly10ms_loop
decfsz CNT5
goto dly10ms_loop
movlw 0x02
movwf CNT5
decfsz CNT4
goto dly10ms_loop
decfsz CNT6
goto dly10ms_loop
nop
return




Koden ska alltså enbart styra servot efter vad man tryckt på någon av fyra knappar:
Knapp 1: Motsols
Knapp 2: Medsols
Knapp 3: Först motsols 90 grader och sedan medsols 90 grader.
Knapp 4: Sätter ett "stopp" vid aktuell position så att servoaxeln inte kan vridas motsols förbi den aktuella positionen.

Först hade jag tänkt använda Interrupt-on-change för att registrera knapptryckningar, men det blir ju problem om man håller inne knappen, vilket man ska kunna göra för att servoaxeln ska kunna vridas sakta åt något håll utan att man ska behöva trycka upprepade gånger.

Servot ska kunna vridas 90 grader på ungefär fyra sekunder. (Det är ok om det tar mellan 2 - 5 sekunder.)


Tror att jag fick med allt där. :)

Postat: 14 maj 2007, 05:15:02
av bos
Istället för interrupt-on-change så kan du i mainloopen manuellt polla (läsa av) input-pinnarna och sen agera efter vad de har för värde. Timern kan du sen använda som komplement för att ta tiden över de fyra sekunder du eftersträvar.

Här är ett utdrag ur min egen kod där jag gör samma sak:

Kod: Markera allt

mainloop
        call    check_switches
        call    check_alarm

        goto    mainloop


check_switches
        btfsc   PORTA, SW_RESET                  ; is the reset-switch pressed?
         goto   switch_reset

        btfsc   PORTA, SW_START                  ; is the start-switch pressed?
         goto   switch_start

        btfsc   PORTA, SW_STOP                   ; is the stop-switch pressed?
         goto   switch_stop

        btfsc   PORTA, SW_UP                     ; is the up-switch pressed?
         goto   switch_up

        btfsc   PORTA, SW_DOWN                   ; is the down-switch pressed?
         goto   switch_down

        return


switch_start
        movlw   1
        movwf   BEEN_INIT
        bsf     T1CON, TMR1ON                   ; Start timer1-oscillator

        call    timers_to_bcd                   ; refresh the segments
        return

        ...

För att fånga exempelvis 4 sekunder så kan du göra en ISR som varje sekund kollar om en bit/variabel är satt och ökar sen ett visst värde varje gång den anropas, och i knappkoden läser du sen av detta värdet och agerar därefter.

Det låter nog rörigt, men jag menar såhär (pseudokod):

Kod: Markera allt

buttonisdown  res 1
secs          res 1

ISR_once_a_sec
          if buttonisdown = 1
             incf secs, f
          else
             clrw
             movwf secs
          return

check_switch:
         if pressed
           buttonisdown = 1
         else
           buttonisdown = 0

         if secs = 4
            call button_was_pushed_for_4_secs

Postat: 14 maj 2007, 09:36:49
av sodjan
Först är det ett par viktiga saker som du måste bestämma!

Det viktigaste är att kvantifiera "ett steg". Hur mycket är det ?
Hela "swinget" är ju 1.5 +/- 0.5 ms (alltså 1.0 - 2.0 ms), så det
går bra att utrycka "steget" i delar av ms t.ex. Eller i delar av
hela rörelsen (100 steg, 200 steg o.s.v). Detta bestämms utfrån
de krav som din applikation ställer på servorörelsen.

Att ha ett designkrav som bara säger "så litet som möjligt" är ganska
värdelöst. Då vet man ju *aldrig* om man har uppnått kravet, det är
ju alltid "möjligt" att göra steget lite mindre... :-)

*SEN* kan man börja fundera på hur man ska lösa de hela med
programvara (eller hårdvara)...

Men, i princip så får man ha en bas-timer i programmet som
motsvarar upplösningen på rörelsen. D.v.s om du vill ha 200 steg
så blir det 1 ms (hela "spannet") delat på 200 = 5us. Sen är det
"bara" att konfigurera timers m.m... :-)

Jag kan tänka mig att man använder timer1 i 16-bitas mode för
att få bra upplösning, sedan bestämmer lägsta och högsta värde för
att få 1 rep 2 ms. Sedan hur mycket som varje "steg" motsvarar i
ändring på timer1. Sedan "syr ihop" det hela tillsammans med lite
pollning av knapparna. Knapparna kan ju lämpligen
pollas varje 20 ms då servot i alla fall ska ha en puls (d.v.s att det
i alla fall ska hända något varje 20 ms i koden, så då kan man
lika bra lägga knapp-pollningen där med...).

Eftersom du i alla fall har något som "händer" i koden med lämpligt
intervall (20 ms), så finns det ingen stor anledning att använda
interrupt för knapparna.

Postat: 14 maj 2007, 10:34:31
av v-g
Om du vill göra det med loopar så har du bara ett värde som sparas i en variabel som ingår i loopen. Denna variabel synkas så att den tex vid värdet 100 ger 1,5 ms som output. Hur loopen skall se ut räknar du ju enkelt ut i MPLAB. Glöm dock inte att variabeln som ändras på annan plats (lämpligen under knapprutinen) inte får minskas utan bara skall "avläsas" och överföras till loop variabeln.

Dock tycker jag Timers är bättre. Enkelt sagt så kan man göra såhär: försök få timern att "rulla runt" på startvärdet 128(för 8 bits räknare) vid 1,5 ms, vips så är det bara att öka eller minska "startvärdet" för önskat utslag. Startvärdet finns såklart i en variabel.

Kan vara så att jag tänker för enkelt men men.

Edit:När jag läser Sodjans inlägg så är detta samma sak fastän med andra ord.

Postat: 14 maj 2007, 10:41:36
av sodjan
> försök få timern att "rulla runt" på 128(för 8 bits räknare) vid 1,5 ms,

Visst, försutsatt att just *den* upplösningen uppfyller Jimmys krav på
en "så liten som möjligt" steglängd. :-)

Igen, så länge som Jimmy's krav inte är kvantifierade så kan en
lösning bara diskuteras i generella termer.

Men visst, generellt sett så ska man antagligen undvika alla slags
"loopar" för timingen, det blir väldigt stelbent att modifiera applikationen
senare...

Postat: 14 maj 2007, 10:49:41
av v-g
sodjan:Iofs _KAN_ det ju fungera hyffsat om man har interupt för knapparna och inte tänker göra så mycket mer än så.

Dock kan man komma till samma fundering som jag tänker på ibland och inte vet om jag "löst" på smidigaste sätt:
-Säg att man kör på 20 MHz då går även 16 bits räknare med pre/post-scaler runt väldigt fort. Bör man då använda timers med tex en räknevariabel eller hur bör man lösa detta?

Postat: 14 maj 2007, 11:04:49
av sodjan
Man kan sätta upp en timer så att den "slår runt" med ett intervall som
motsvarar "upplösningen" på servot. För att öka på upplösninge
(fördubbla den) så kan man låta den första ms av pulsen (som ju är
"fast" i alla fall) vara hårdkodad och bara låta den andra delen (0-1 ms)
stydas av sina variabler.
Sedan (i timer ISR'en) har en ett par räknare (d.v.s vanliga register, GPRs)
som håller redan på var man är i pulsen.
Dock så blir det ganska täta interrupt från timern med denna lösning.

En annan lösning är att låta en timer ge interrupt, och sedan sätter man den
till ett värde som motsvarar önskad pulslängd. Varannan gång får man
naturligtsvis sätta den till ett fast värde som motsvarar 20 ms pausen
mellan två pulser. På så sätt är det lättare att få en "fin" upplösning
utan att få så himla många interrupt, det blir två interrupt per 20 ms.
Samtidigt kan man passa på att checka knapparna när man ända har
ett interrupt med lämpligt intervall...

Det är lite trix med att sätta om en timer som är free-running, men
det är lösbart. Man måste ju ta hänsyn till att timern har gått en
bit mellan senasre interruptet och stället i koden där ett nytt
värde ska sättas. Det löser man med add/sub av värdet istället för
att bara gör MOVxx av ett värde till timern (då man ju bara skriver
över aktuellt timervärde).

Postat: 14 maj 2007, 11:07:28
av Marta
Ett RC-servo brukar vilja ha en pulsrepetitionsfrekvens på ungefär 50Hz, sätt upp en timer till att ge interrupt med detta intervall. 61Hz går nog det också, det är T1 varvet runt utan precaler vid 4MHz.

Edit2: Oj, där skrev jag fel. Det är ju bara F/4 som går till timern, så den måste skrivas om vid interruptet. 78 i den höga byten ger sådär ungefär 50Hz, den låga behöver inte skrivas.

Skapa pulsen i mjukvara genom en delayloop, med endast 1 servo finns det massor av tid över efter att delayen är gjord.

Läs av knapparna i interruptet och räkna ned en timer som ger stegtiden när en knapp hålles ner för att långsamt ändra pulstiden.

Lägg också hanteringen av de andra knapparna i interruptet. Det blir antagligen mycket enklare så.

Edit: Gör två delayloopar, en som ger en bastid och en variabel för att få maximal upplösning med 1 byte på den variabla. Använd NOP's för att ge den rätt "varvtid" så en förändring av loopräknaren på 1..255 blir en förändring på en millisekund ungefär.

Postat: 14 maj 2007, 14:15:33
av JimmyAndersson
sodjan:
"Att ha ett designkrav som bara säger "så litet som möjligt" är ganska
värdelöst. Då vet man ju *aldrig* om man har uppnått kravet, det är
ju alltid "möjligt" att göra steget lite mindre..."



Sant. :)

Servot jag ska använda är ett Traxxas 2055. (Troligen ganska bekant servo.) :)
Servots "steg" (minsta möjliga rotering av axeln) sätter ju då gränsen för vad som är "så litet som möjligt".

Min tanke var att vrida servoaxeln med ett "steg" i taget för att få en så mjuk rörelse som möjligt.

Hur stort är varje "steg" på dem?

Postat: 14 maj 2007, 14:23:07
av Icecap
Dessa servos har ingen "steg" som sådan, de är analoga. Att det finns en minste avvikelse de accepterar är nog sant men hur liten den är kan jag inte svara på och det lär nog variera lite från servo till servo.

Alltså är all snack m "step" på servon felaktigt, de steg som det ska snackas om är DITT behov.

Ska den ställa sig i en av 3 positioner behöver du alltså 3 steg, ska den vara "steglös" behövs en högre upplösning.

Postat: 14 maj 2007, 14:56:32
av sodjan
Dessutom beror det mer på det som ska styras
än på servot i sig. D.v.s. vilket mekaniskt
"glapp" har du i det som du styr ? Det finns ju
ingen anledning att styra servot väldigt fint om
det krävs 10 "steg" på servot för att det ska märkas
på det som man styr...

> Hur stort är varje "steg" på dem?

De är "steglösa"... :-)

Postat: 14 maj 2007, 15:17:32
av JimmyAndersson
Sodjan och du (Icecap) har pratat om *mitt* behov. Jag har svarat att jag vill få en så mjuk rotation av servoaxeln som möjligt. Det vet ni redan om ni har läst mina inlägg.


"Alltså är all snack m "step" på servon felaktigt"

Så om jag vill veta hur litet varje steg kan vara så ställer jag en felaktig fråga?
Hur ska jag då fråga för att få redan på hur mycket pulslängden ska ändras för att få en så mjuk rotation av servoaxeln som möjligt?

Det är lite märkligt att den här forumdelen har en sån enorm förmåga att bli en diskussion i det svenska språket. I de övriga forumdelarna går det utmärkt att förstå hur folk menar, men här måste man tydligen uttrycka sig försiktigt och väga varje ord för att bli förstådd.

Märkligt....

Men för att vi ska slippa slösa bort varandras tid så lägger jag ner diskussionen här.

Trial & Error - Here we go ..again.

Postat: 14 maj 2007, 15:34:56
av sodjan
> Sodjan och du (Icecap) har pratat om *mitt* behov.

Vi bryr oss... :-)
Känner du inte den vara känslan upp efter ryggraden ? :-)

> Jag har svarat att jag vill få en så mjuk rotation av servoaxeln som möjligt.

Och det "beror på".
När servot inte är kopplat till något ? Eller med "last" på servot ?
Ett servo är en makanisk liten sak och har bl.a en egen växellåda
som i sig ger ett visst "glapp".

Men OK, om du vill ha en vågad gissning, så skulle jag tro
att man skulle kunna få ut ca 100-200 "steg" ur ett vanligt
standard RC servo. Sedan tar det interna glappet överhanden.
Om det uppfattas som en "mjuk" rörelse eller inte, vet jag inte.

*Personligen* tror jag fortfarande att diet minsta "steget" kommer att
begränsas av det (vad det nu är) som servot ska styra/kontrollera.

Jag tycker inte att det handlar som en språkfråga, för övrigt.

Nöjd ? :-)

Postat: 14 maj 2007, 15:43:04
av oJsan
Hur ska jag då fråga för att få redan på hur mycket pulslängden ska ändras för att få en så mjuk rotation av servoaxeln som möjligt?
Inte för att vara taskig, men mitt svar skulle vara "så liten ändring som möjligt".
Eftersom servot i sig är analogt så har det ingen steglängd. Du får istället fråga dig själv hur mjuk du vill ha din rörelse.
Låt t.ex. säga att du har en 1m-pinne fäst på servoplattan. låt säga att du vill att pinnens spets inte ska röra sig mer än 1mm varje gång DU tar "ett steg". Om rotationen är 180 grader så är halva omkresten:
diameter x pi x (1/2) = (1 x 2) x pi x (1/2) = ~3.14m
3.14/0.001 = 3140 olika steg.

Förmodligen så är servots mekanik mycker mer glapp än så... Jag har för mig att jag bara kunde se en rörelse om jag flyttade mig mer än 2 steg. Detta när jag hade delat upp servots rörelse i 256 steg.

Postat: 14 maj 2007, 15:57:24
av v-g
Det beror ju på återmatningen och dess mekanik är hurpass stor nogrannheten kan bli.

Har tidigare testat med en potentiometer på A/D omvandlaren(1024 steg) och ett "tänk" är ett steg ungefär :wink: