Forum: Mikrocontroller und Digitale Elektronik Zeitverzögerung beim PIC16F84A


von Marcel H. (marcel_2004)


Lesenswert?

Moin, sitze gerade vor einem PIC16F84A welcher mit Assembler 
programmiert werden soll. Nun ist die Aufgabe mit dem RISC Befehlssatz 
eine Funktion zu schreiben in welcher eine Verzögerung von etwa 1s 
vorkommen soll. Bin allerdings etwas aufgeschmissen und weiß nicht genau 
wie ich die Verzögerung von etwa 1s hinbekomme.
Vielleicht kann mir ja jemand einen Denkanstoß oder ähnliches geben!
Danke im vor raus!

von STK500-Besitzer (Gast)


Lesenswert?

Marcel H. schrieb:
> Bin allerdings etwas aufgeschmissen und weiß nicht genau
> wie ich die Verzögerung von etwa 1s hinbekomme.

Du weißt die Taktfrequenz deines PIC?
Dann bastelst du dir Zählschleifen, die den Controller für eine Sekunde 
blockieren.
Um auf die "genaue" Zeit zu kommen, musst du natürlich wissen, wie lang 
jeder Befehl deiner Schleifen braucht.

Oder du nimmst eine Kamera, die auf eine Uhr (mit Sekundenzeiger) guckt, 
die dann meldet, dass der Sekundenzeiger sich bewegt hat.

von Teo D. (teoderix)


Lesenswert?

Entweder stupide ne For-Schleife mit NOP(s) oder den Timer benutzen um 
damit ne Var hoch|runter zählen.

von Peter D. (peda)


Lesenswert?

Für so extrem lange Zeiten nimmt man einen Timerinterrupt, z.B. alle 
1ms.
Darin zählt man dann eine Variable runter und wenn sie 0 erreicht, führt 
man einen Callback aus oder setzt ein Flag, was dann in der Mainloop die 
Aktion ausführt.
Demm in real will man nicht, daß so lange die CPU überhaupt nichts 
macht, sondern weiterhin andere Aufgaben bearbeitet.

Eine elegante Lösung ist auch ein Scheduler, in den man z.B. bis zu 10 
verzögerte Aufgaben reinstellen kann und der sie dann einmalig oder 
zyklisch aufruft. Den Scheduler schreibt man natürlich in C, in 
Assembler wird das zu unübersichtlich.

von Teo D. (teoderix)


Lesenswert?

Peter D. schrieb:
> Den Scheduler schreibt man natürlich in C, in
> Assembler wird das zu unübersichtlich.

Wie so sollte das?
Ist doch nur ne Liste von Calls.

von Peter D. (peda)


Lesenswert?

Teo D. schrieb:
> Wie so sollte das?
> Ist doch nur ne Liste von Calls.

Assembler kennt keine Variablentypen und erst recht keine Structs und 
Arrays. Die ganzen Indirectionen, Pointer und Displacements zu Fuß zu 
berechnen, ist nur was für extreme Masochisten.
1
typedef void (*funcp)(void);
2
typedef uint16_t t_res;
3
4
typedef struct
5
{
6
  uint8_t next;                                         // next in list or end mark
7
  t_res delta;                                          // delta to previous entry
8
  t_res period;
9
  funcp func;
10
} t_ctrl_struct;
11
12
static t_ctrl_struct t_ctrl_lst[MAX_SCHEDULE];
13
static t_res t_delay;                                   // count down until next service
14
static uint8_t t_first;                                 // point to first entry

von STK500-Besitzer (Gast)


Lesenswert?

Peter D. schrieb:
> Denn in real will man nicht, dass so lange die CPU überhaupt nichts
> macht, sondern weiterhin andere Aufgaben bearbeitet.

Die Aufgabe klingt nach einer Grundlagenveranstaltung der 
Mikrocontroller-Programmierung (zumindest kenne ich das sowohl aus der 
Ausbildung, als auch aus dem FH-Studium).
Dabei geht es darum, die Zählparameter einer Warteschleife zu bestimmen, 
wenn man die Taktanzahl der benötigten Befehle kennt.

Natürlich kann man es anders machen, aber jemand, der nicht mal solchen 
Pippifax hinbekommt, soll einen Scheduler programmieren / verwenden?

von Teo D. (teoderix)


Lesenswert?

Peter D. schrieb:
> Assembler kennt keine Variablentypen und erst recht keine Structs und
> Arrays.

Du denkst zu komplex.
Mann muss nicht gleich einen ganzen Walt planen, wenn man nur ne 
Verkehrsinsel begrünen will.

von Peter D. (peda)


Lesenswert?

Teo D. schrieb:
> Du denkst zu komplex.

War jetzt auch nur mal als Ausblick gedacht, wenn man erfahrener ist. Ab 
der 2. Zeitaufgabe geht es mit dem Scheduler einfacher.

Ein Timerinterrupt hat den Vorteil, daß man die Dauer direkt einstellen 
kann und nicht umständlich Befehlszyklen abzählen muß. Man kann also das 
Programm leicht erweitern, ohne daß sich die Zeiten durch die 
zusätzlichen Befehle verlängern.

von Peter D. (peda)


Lesenswert?

Als ich 1990 mit dem 8051 angefangen habe, habe ich sehr schnell schon 
die Timer benutzt.
Ich war einfach zu faul, Befehle abzählen zu müssen, denn dabei kann man 
sich auch schnell verzählen und die Zeiten stimmen nicht.

von STK500-Besitzer (Gast)


Lesenswert?

Peter D. schrieb:
> Als ich 1990 mit dem 8051 angefangen habe, habe ich sehr schnell schon
> die Timer benutzt.
> Ich war einfach zu faul, Befehle abzählen zu müssen, denn dabei kann man
> sich auch schnell verzählen und die Zeiten stimmen nicht.

Timer erfordern i.d.R. auch Interrupts. Die werden vielleicht noch 
später im Unterricht behandelt.

von Teo D. (teoderix)


Lesenswert?

Peter D. schrieb:
> Teo D. schrieb:
>> Du denkst zu komplex.
>
> War jetzt auch nur mal als Ausblick gedacht, wenn man erfahrener ist. Ab
> der 2. Zeitaufgabe geht es mit dem Scheduler einfacher.

Tja, rein aus Forentradition, sollten wir uns jetzt drei Tage und 
Nächte, die Köppe darüber einhauen, was denn nu genau als "Scheduler" 
gelten darf und ob das ab dem 2. oder 5. Slot sinnvoller ist... ;D

von W.S. (Gast)


Lesenswert?

Peter D. schrieb:
> War jetzt auch nur mal als Ausblick gedacht, wenn man erfahrener ist.

Nein, mein Lieber. Deine Ratschläge setzen nicht nur einen C-Compiler 
voraus, sondern auch noch genügend Platz im Zielcontroller. Bedenke mal, 
daß recht viele PIC16 mit 2K an Befehlsworten auskommen müssen und 
obendrein auch nur rund 200 Byte an RAM zur Verfügung haben (wenn man 
mal durchscheinende RAM-Bereiche und Register abzieht). Und da fängst du 
großspurig an von Zeugs zu schreiben, was definitiv zu fett ist für die 
Zielhardware. Ein Scheduler in C, und als nächstes kommt ein voll 
ausgebautes printf und eine Weile später ein komplettes Linux mit 
grafischer Oberfläche?

Man sollte ein bissel auf dem Teppich bleiben. Ich erinnere mich da an 
einen Jung-Ex-Kollegen, der bei einem kleinen PIC10 im SOT23 Gehäuse und 
nur maximal 512 Befehlsworten meinte, er wüßte keinen Grund, solche 
Winzlinge NICHT in C zu programmieren. Nun ja, manche Leute brauchen 
unbedingt einen V6, um beim Bäcker um die Ecke ihre Frühstücks-Semmel zu 
holen.

Und hier ein kleines Beispiel, wie man sowas in etwa machen kann:
1
; Quarzfrequenz= 20 MHz
2
; 1 ms = 5000 Befehlstakte (etwa 256 * 19.5)
3
; 1 Befehlstakt = 200 ns
4
5
; so etwa 1 ms warten
6
Wait_1: MOVLW  2
7
8
; W mal so etwa ne halbe ms warten  (W=0 --> 128 ms)
9
Wait:  MOVWF   Hudl
10
       MOVLW   0
11
                         ; Taktverbrauch
12
_wzy:  NOP               ; 1
13
       NOP               ; 2
14
       CLRWDT            ; 3
15
       NOP               ; 4
16
       NOP               ; 5
17
       ADDLW   1         ; 6
18
       SKIP    Z         ; 7
19
       GOTO    _wzy      ; 8+9
20
       DECFSZ  Hudl,F
21
       GOTO    _wzy
22
       RETURN

W.S.

von Peter D. (peda)


Lesenswert?

W.S. schrieb:
> Man sollte ein bissel auf dem Teppich bleiben.

Man kann auch mit 2kB Flash ne ganze Menge machen, z.B. float habe ich 
auch auf dem AT89C2051 benutzt, braucht 1kB Flash.
Atmel hat aber auch die kleinen 8Pinner gut ausgerüstet, z.B. ATtiny85: 
8kB Flash, 512B RAM.

Mit PIC kenne ich mich nicht aus.
Ich hab hier noch ein Delaymacro für den 8051 gefunden:
1
mdelay  macro value, rx, ry     ;cycle, used register(s)
2
          local m____1
3
          if( value < 5 )
4
            rept value
5
              nop
6
            endm
7
          elseif( value < 515 )
8
              mov rx, #low((value-1)/2)
9
              djnz rx, $
10
            if((value and 1) = 0)
11
              nop
12
            endif
13
          else
14
              mov rx, #high(value/2+253)
15
              mov ry, #low(value/2-2)
16
m____1:       djnz ry, m____1
17
              djnz rx, m____1
18
            if(value and 1)
19
              nop
20
            endif
21
          endif
22
        endm

0..4 Zyklen werden mit NOP gemacht, 5..514 Zyklen mit einem DJNZ und 
515..65535 Zyklen mit 2 DJNZ.
Der alte A51 Assembler kann leider nur 16Bit rechnen.

von Mario M. (thelonging)


Lesenswert?


von H. H. (Gast)


Lesenswert?

W.S. schrieb:
> Bedenke mal,
> daß recht viele PIC16 mit 2K an Befehlsworten auskommen müssen und
> obendrein auch nur rund 200 Byte an RAM zur Verfügung haben (wenn man
> mal durchscheinende RAM-Bereiche und Register abzieht).

Der 16F84A hat nur 1K Befehlsworte und 68 Bytes RAM.

von Jan (Gast)


Lesenswert?

W.S. schrieb:
> Ich erinnere mich da an einen Jung-Ex-Kollegen, der bei einem kleinen
> PIC10 im SOT23 Gehäuse und nur maximal 512 Befehlsworten meinte, er
> wüßte keinen Grund, solche Winzlinge NICHT in C zu programmieren.

Recht hatte er.

von W.S. (Gast)


Lesenswert?

H. H. schrieb:
> Der 16F84A hat nur 1K Befehlsworte und 68 Bytes RAM.
Ist eben noch enger als ich so allgemein geschrieben habe.


Jan schrieb:
> Recht hatte er.
Damit daß er es nicht weiß. Tja, das isses wohl.


Peter D. schrieb:
> Man kann auch mit 2kB Flash ne ganze Menge machen, z.B. float habe ich
> auch auf dem AT89C2051 benutzt,
Benutzt. Hmm. Für die PIC16 hatte ich die GK-Arithmetik selber 
geschrieben. Sowohl für 4 Byte GK als auch eine Variante mit 5 Byte. 
Damit die Mantisse groß genug ist, um das auch im Frequenzzähler 
benutzen zu können.

Ansonsten weiß ich, was man alles in Assembler hinkriegt, auch wenn man 
keine Riesenvorräte an RAM+Flash hat. Alles andere sind die leider 
vielgehörten Ausreden von C-Programmierern, die nur vertuschen wollen, 
daß sie es eben nicht können.

W.S.

von Teo D. (teoderix)


Lesenswert?

H. H. schrieb:
> Der 16F84A hat nur 1K Befehlsworte und 68 Bytes RAM.

Ich hab ne kleine Dosierpumpe gebaut, wo ein 16F84 (ohne A!:) mit C 
vergewaltigt wurde und da sollte man solche "Klimmzüge" nur in 
homöopathischen Dosen anwenden.
Man muss da auch nicht wirklich geizig mit den Ressourcen sein, aber 
wenn der "Jagdinstinkt" einmal geweckt ist... :D

Hab kürzlich erst ein neues Update gemacht.
Eigentlich wollte ich ja nur "Globale Dosis-Ende Tasten-Sperre" 
einfügen....
/*
48 byte RAM; 864 Words
Globale Dosis-Ende Tasten-Sperre hinzugefügt (+10 Words)
Buzzer Init Macro, auf Funtion Aufruf geändert (-5 Words)
Buzzer(void) ohne Parameter, buzzer Struct auf Global geändert (-16 
Words)
Buzzer_Init() an_1bit Parameter raus geworfen (-16 Words)
 * Geplant
 * 20 Byte RAM noch frei -> Strucht Bitfeld (wieder) auf Char umstellen 
-> weniger Code?
 * ERLEDIGT 49 Bayte RAM; 858 Words (-6 Words)
 */

Nicht geplant
Auf Assembler umschreiben (-650 Words erwartet) :D

von MaWin (Gast)


Lesenswert?

Peter D. schrieb:
> Als ich 1990 mit dem 8051 angefangen habe, habe ich sehr schnell
> schon die Timer benutzt.
> Ich war einfach zu faul, Befehle abzählen zu müssen, denn dabei kann man
> sich auch schnell verzählen und die Zeiten stimmen nicht.

Na ja, man kann auch erst mal eine Schleife bis 100000 zählen lassen, 
abstoppen und dann korrigiert man diesen Wert damit 1 Sekunde bei 
rauskommt.

Man kann auch lernen wie die Inder
https://youtu.be/k_S2gRkUVn8

von Teo D. (teoderix)


Lesenswert?

MaWin schrieb:
> Na ja, man kann auch erst mal eine Schleife bis 100000 zählen lassen,
> abstoppen und dann korrigiert man diesen Wert damit 1 Sekunde bei
> rauskommt.

Wenns wirklich auf ne "genaue" Sekunde ankommt, ist ne Delay-Schleife eh 
zu ~100% der falsche Weg.

von Peter D. (peda)


Lesenswert?

MaWin schrieb:
> Na ja, man kann auch erst mal eine Schleife bis 100000 zählen lassen,
> abstoppen und dann korrigiert man diesen Wert damit 1 Sekunde bei
> rauskommt.

Das ist mit Abstand das umständlichste, was ich je gehört habe. Gerade 
mit ineinander verschachtelten Schleifen wird man damit kaum einen 
minimalen Restfehler erzielen.

von Georg (Gast)


Lesenswert?

Peter D. schrieb:
> Der alte A51 Assembler kann leider nur 16Bit rechnen.

Das ist doch kein reales Hindernis - man kann auch mit einem 
4bit-Prozessor 10stellige Dezimalzahlen verarbeiten, das konnten schon 
die ersten Taschenrechner. Zählen ist noch einfacher, gibt es einen 
Überlauf zählt man die nächsthöhere Stelle um 1 hoch, usw.

Am Assembler liegt es nicht wenn man das nicht hinbekommt.

Georg

von Jens M. (schuchkleisser)


Lesenswert?

Ich hab das immer so gemacht:

Einfache Schleife
1
Delay
2
  movlw  n
3
  movwf  d1
4
Delay_0
5
  decfsz  d1, f
6
  goto  Delay_0
7
  return
Dauert (n-1)*3 + 2 Zyklen, plus 2 (init) plus 4 (call/return) Zyklen.
Benötigt eine RAM-Adresse d1.

Doppelte Schleife
1
Delay
2
  movlw  n1
3
  movwf  d1
4
  movlw  n2
5
  movwf  d2
6
Delay_0
7
  decfsz  d1, f
8
  goto  $+2
9
  decfsz  d2, f
10
  goto  Delay_0
11
  return
Dauert ((n1-1)*5) + ((n2-1)*1280) + 4, plus 4 (init) plus 4 
(call/return) Zyklen.
Damit kommt man schonmal etwas weiter, braucht dafür 2 Bytes RAM.

Dreifache Schleife
1
Delay
2
  movlw  n1
3
  movwf  d1
4
  movlw  n2
5
  movwf  d2
6
  movlw  n3
7
  movwf  d3
8
Delay_0
9
  decfsz  d1, f
10
  goto  $+2
11
  decfsz  d2, f
12
  goto  $+2
13
  decfsz  d3, f
14
  goto  Delay_0
15
  return
Dauert ((n1-1)*7) + ((n2-1)*1792) + ((n3-1)*458752) + 6, plus 6 (init) 
plus 4 (call/return) Zyklen.
Braucht 3 Bytes RAM.

Natürlich kann man alle 3 Versionen auch "inline" benutzen, dann 
entfällt halt das return und die jeweils 4 Zyklen fehlen.
Und: es geht auch, nur eine Schleife "Delay_0" zu nutzen, aber mehrere 
Initialisierungsteile mit verschiedenen Werten, da muss man dann aber 
noch jeweils ein goto einrechnen (2 Zyklen).

Sehr kurze Verzögerungen kann man mit nop (1 Zyklus) oder goto $+1 (2 
Zyklen) machen.

Bei 4MHz Quarz-Takt ist ein Zyklus = 1 µs, auch ein gern genommenes 
Verständnisproblem beim PIC.

: Bearbeitet durch User
von Peter D. (peda)


Lesenswert?

Georg schrieb:
> Das ist doch kein reales Hindernis

Dann erzähle mir mal, wie ich dem Delay-Macro den Wert 65537 übergebe, 
wenn der Assembler nur 16-bittig rechnet.

von Teo D. (teoderix)


Lesenswert?

Peter D. schrieb:
> Georg schrieb:
>> Das ist doch kein reales Hindernis
>
> Dann erzähle mir mal, wie ich dem Delay-Macro den Wert 65537 übergebe,
> wenn der Assembler nur 16-bittig rechnet.

Na so wie alle das machen. Man kann halt nur einen gewissen Bereich 
abdecken und für jeden gibts halt dann ein eigens Macro.

von Peter D. (peda)


Lesenswert?

Teo D. schrieb:
> Na so wie alle das machen. Man kann halt nur einen gewissen Bereich
> abdecken und für jeden gibts halt dann ein eigens Macro.

Man kann sich aber auch die Hose mit der Kneifzange anziehen.

Der AVR-Assembler rechnet intern 64-bittig, d.h. selbst ein Delay von 
24h läßt man ihn bequem selber ausrechnen:
1
.nolist
2
.include "tn12def.inc"
3
4
        .equ    F_CPU = 1200000       ; 1.2MHz
5
6
        .equ    zeit1 = 10            ; 10s
7
        .equ    zeit2 = 60 * 60 * 24  ; 24h
8
9
#define byte5(x)  byte4(x / 256)
10
11
.macro  warte
12
        ldi     r16, byte1( @0 / 7 )
13
        ldi     r17, byte2( @0 / 7 )
14
        ldi     r18, byte3( @0 / 7 )
15
        ldi     r19, byte4( @0 / 7 )
16
        ldi     r20, byte5( @0 / 7 )
17
_warte11:
18
        subi    r16, byte1(1)         ; 1 Zyklus
19
        sbci    r17, byte2(1)         ; 1 Zyklus
20
        sbci    r18, byte3(1)         ; 1 Zyklus
21
        sbci    r19, byte4(1)         ; 1 Zyklus
22
        sbci    r20, byte5(1)         ; 1 Zyklus
23
        brne    _warte11              ; 2 Zyklen
24
                                      ; = 7 Zyklen
25
.endmacro
26
.list
27
.listmac
28
29
main:
30
        sbi     ddrb, pb1
31
        sbi     portb, pb1
32
        warte   F_CPU * zeit1         ; 10s
33
        cbi     portb, pb1
34
        warte   F_CPU * zeit2         ; 1d
35
        rjmp    main

Beitrag "Re: 24h Schalter mit Attiny 12"

von Teo D. (teoderix)


Lesenswert?

Peter D. schrieb:
> Dann erzähle mir mal, wie ich dem Delay-Macro den Wert 65537 übergebe,
> wenn der Assembler nur 16-bittig rechnet.

Peter D. schrieb:
> Der AVR-Assembler rechnet intern 64-bittig, d.h. selbst ein Delay von
> 24h läßt man ihn bequem selber ausrechnen: .....

Wenn dir langweilig ist, such die einen interessanteren Job!

von Martin (Gast)


Lesenswert?

Bei Sprut findest Du Beispiele für Warteschleifen in Assembler, einfach 
etwas weiter runterblättern:

https://www.sprut.de/electronic/pic/programm/lauflicht/lauflich.htm

So ähnlich habe ich das bereits für Projekte genutzt.

Der PIC wartet dann natürlich einfach dumm und ist blockiert für weitere 
Aufgaben. Anders ginge es via Interrupt ähnlich dem millis() Befehl bei 
Arduino.

von Daniel (Gast)


Lesenswert?

Alter Falter, wer programmiert noch Assembler? Nimm MPLAB IDE und dir 
gehts deutlich besser, glaub mir !

von W.S. (Gast)


Lesenswert?

Daniel schrieb:
> Alter Falter, wer programmiert noch Assembler? Nimm MPLAB IDE und dir
> gehts deutlich besser, glaub mir !

Alter Falter, wer kaut noch selber sein täglich Brot? Nimm lieber fertig 
gekauten Instant-Brei und dir gehts deutlich besser, glaub mir !

W.S.

von Hans Werner Schuster (Gast)


Lesenswert?

Was soll das mit dem Taktzahlgefummel? Für sowas nimmt man nen Timer.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.