Twee flowmeters (puls bronnen) tegelijk uitlezen met PIC

Ik heb twee flow meters welke pulsen genereren en 3 digitale (DS18B20) temperatuursensoren die ik allemaal min of meer tegelijk uit wil lezen met een PIC, bij voorkeur een 18F2520. Programmeren doe ik in PIC-basic met proton compiler.

De flow meters genereren pulsen waarbij de frequentie min of meer evenredig is aan de flow die er door heen gaat, frequentie gebied zal iets van 5Hz-50Hz zijn. Beide flowmeters zullen tegelijk actief zijn. Het heeft dus de voorkeur dat de PIC de pulsen van beide flowmeters tegelijk kan tellen. Omdat de sensoren niet helemaal lineair zijn kan beter de tijd tussen de pulsen bepaald worden.

De temperatuur uitlezen is minder belangrijk, als elke 5-10 seconden de 3 temperatuur sensoren uitgelezen worden is het prima. Bij voorkeur worden de pulsen van de flow sensor ook gevolgd tijdens het uitlezen van de temperatuur sensoren.

Met het PULSIN commando in basic kan je lengte van de puls bepalen, dit kan als basis dienen om de frequentie van de flowmeter te bepalen. Dan kan ik bijvoorbeeld elke seconde de pulslengte van de flowmeter bepalen om zodoende te kijken of er nog iets door heen stroomt en zo ja hoeveel. Alleen is dit niet een erg nette manier, dan veronderstel je dat die gemeten puls representatief is voor de gehele seconde dat je gewacht hebt. Tevens duurt het uitlezen van de DS18B20 vrij lang in hoogste resolutie waardoor je hier ook al een paar seconden mogelijk op moet wachten, wat er dan met de flow meters gebeurd mis je dan.

Met timers en interrupts, waar ik nog niet eerder mee gewerkt heb, is er vast veel meer mogelijk.

Hou zouden jullie het programma in hoofdlijnen opzetten, en wellicht kan ik ook beter een andere PIC gebruiken?

Je geeft het antwoord zelf al: timers & interrupts.

Ga in de datasheet eens op zoek naar de PortB Interrupt en de verschillende Timers.
Dit device heeft geen OnChange interrupt, maar de PortB change komt het dichtste bij.

PulseIn EN ORead in 1 programma kon wel eens heel rare resultaten opleveren.

Ik heb zoiets een paar jaar geleden voor een AVR gemaakt. Niet spannend, mits die flowmeters zich netjes gedragen.
Deze zitten in de olie van een dieseltje. Dat gedraagt zich niet netjes (in het kort: het is geen flow, maar meer een aaneenschakeling van schoksgewijze verplaastsingen. En helaas zie je die maar al te duidelijk terug in de output van de flowmeters). Ofwel: ik heb er in software nog wel wat aan moeten sleutelen om er een fatsoenlijke waarde uit te krijgen. In mijn context is de absolute waarde niet zo belangrijk. Relatief en reproduceerbaarheid wel (dat absoluut is dan een kwestie van mathematisch recht trekken).

Als ik het wel heb, dan laat ik een timer rennen en die doet zo nu en dan een increment van een stel variables (ik denk op interrupt basis). Door gebruik van die timer kan ik handig 'mikken' hoe vaak die variables incremented worden. Zo kan ik een redelijke resolutie combineren met voldoende 'lengte' - ben meer onafhankelijk van het gebruikte Xtal.
Ik gebruik een interrupt op de AVR om die counters weer uit te lezen als er een flank voorbij komt (die kan dat gewoon per pin; geen idee meer of ik beide flanken pak of alleen de rising / falling). De uitgelezen waarde is een maat voor de flow.
Dan nog wat recht trekken voor 'onrealistische' waarden... Wat middelen... en dan klopt het opeens heel behoorlijk.

De reden dat het op die manier wat gekunsteld in elkaar zit, is dat ik zo erg veel vrijheid had in 'spelen' met het spul om de uitgelezen waarden 'goed' te krijgen. Ik kan zo ook vrij makkelijk een timeout detecteren - als een sensor geen pulsen geeft (als je rechtstreeks een timer uitleest, is dat wat lastiger...).

Door deze constructie (alles is in C geschreven, en er is wel een beetje nagedacht over efficiënt), doet de mainloop eigenlijk niks anders dan op z'n gemakkie de data zo nu en dan eens over RS485 naar buiten boeren. Zelfs dat draait geloof ik interrupt driven (alleen de string die naar buiten moet in een buffer frutten zit nu nog in de mainloop...)
Tijd zat om in de main loop iets met dat 1-Wire spul te doen terwijl de rest gewoon lekker door holt.

[Gedoe met capslock-en toen opeens gepost dus nu een edit]
Je kunt bijvoorbeeld prima de conversie op je sensors een schop geven. Op je dooie gemakkie wat anders gaan doen en 2 seconden later eens vragen wat ze ervan denken. Als je dat nou doet direct na (in mijn geval) het in de buffer mikken van de strings, dan heb je nergens last van.

Er zijn verschillende mogelijkheden.

Pinnen RB4...7 hebben wel een interrupt-on-change. Die kun je samen met een timer gebruiken om pulsen en lengte te tellen.
Je kunt ook RB0...2 gebruiken. (external interrupt pins). Wederom met een timer.
Verder kun je ook nog de Capture functies gebruiken op RG2(CCP1) en RC1(CCP2). Die houden automatisch bij hoe lang een puls is.

Dan wordt het toch maar eens tijd om met interrupts en timers te gaan werken, tot nog toe heb ik altijd geprobeerd ze te vermijden.

De 18F2520 heeft inderdaad 3 external interrupts en 4 interrupts on change. Wat is nu het voornaamste verschil tussen on-channge en external, is dat de externe ingesteld kan worden op van hoog naar laag of omgekeerd, en dat on-change alleen geageerd op status wijziging, is dit correct?

Kan het programma zo ongeveer werken?

Het register moet ingesteld worden dat er een interrupt moet plaatsvinden bij een gewijzigde pin status van b.v b.4.

Wanneer de status van b.4 wijzigt vindt de interrupt plaats: Indien variabele (interrupt teller) 0 is dan een timer activeren (reset). Vervolgens de variable (interrupt teller) ophogen van 0 naar 1 en terug keren naar hoofdprogramma.

Bij een volgende interrupt kijken naar de variabele (interrupt teller), indien deze 1 is dan de variabele (interrupt teller) ophogen naar 2 en terugkeren naar hoofdprogramma. (Deze tussenstap heb ik genomen om er voor te zorgen dat telkens de complete cyclus tijd gemeten word, dit lijkt me nodig indien het signaal geen duty cycle heeft van 50%)

Bij een volgende interrupt kijken naar de variabele (interrupt teller), indien deze 2 is dan: de tijd van timer wegschrijven naar een variabele, timer resetten en variabele (interrupt teller) veranderen naar 1 en terugkeren naar hoofdprogramma.

Elke keer van interrupt naar hoofdprogramma moet ook een bitje ergens reset worden zodat de volgende interrupt weer kan werken, zoiets las ik net.

In het hoofdprogramma moet variabele (interrupt teller) elke keer als deze van 2 naar 1 veranderd de tijd uitlezen en deze omrekenen naar een volume (volume is afhankelijk van freq). Dit volume vervolgens optellen in een totaal variabele.

Zoiets?

waarom tijd tellen?? elke puls staat toch voor hoeveelheid. Gewoon pulsen tellen?

1 puls = 4ml 15 pulsen = 60mL, of zie ik iets over het hoofd?

Op 24 november 2015 21:55:59 schreef Shiptronic:
waarom tijd tellen?? elke puls staat toch voor hoeveelheid. Gewoon pulsen tellen?
1 puls = 4ml 15 pulsen = 60mL, of zie ik iets over het hoofd?

Op 24 november 2015 16:44:36 schreef Bukkum:
Omdat de sensoren niet helemaal lineair zijn kan beter de tijd tussen de pulsen bepaald worden.

EDIT:
En het wordt tijd dat ik leer werken met interrupts en timers :)

Op 24 november 2015 18:22:37 schreef Arco: Pinnen RB4...7 hebben wel een interrupt-on-change.

Je hebt weer eens helemaal gelijk, helemaal over heen gelezen...:-(

ik gebruik de volgende routines om de cop van een warmtepomp te berekenen.
dit gebeurd ook met een flowmeter die pulsen stuurt en drie KWH meters die ook pulsen sturen.

het is in C geschreven maar het idee is bruikbaar denk ik.

er kunnen 4 ingangen aangesloten worden.

een stukje programma.

//   the timer is incremented (25000000/4)/2 or 3125000 times a second (0.32 us).
//   3125 x 0.32us = 1 ms 
#INT_RTCC
void clock_isr() 
  {
   set_rtcc(HIGH_START);
	  if (++PulsCounter1==0) PulsCounter1=0xFFFFFFFF;
	  if (++PulsCounter2==0) PulsCounter2=0xFFFFFFFF;
	  if (++PulsCounter3==0) PulsCounter3=0xFFFFFFFF;
	  if (++PulsCounter4==0) PulsCounter4=0xFFFFFFFF;
	  if (PulsCounter1>PulsTime1) 	PulsTime1=PulsCounter1;
	  if (PulsCounter2>PulsTime2) 	PulsTime2=PulsCounter2;
	  if (PulsCounter3>PulsTime3) 	PulsTime3=PulsCounter3;
	  if (PulsCounter4>PulsTime4) 	PulsTime4=PulsCounter4;
   } 


#INT_RB
void rb_isr (void) 
 { 
   byte changes; 
   output_toggle(pin_A0); 
   changes = last_b ^ input_b();  // Bitwise exclusive or  
   last_b = input_b(); 
   if (bit_test(changes,4 )&& !bit_test(last_b,4))
     { 
      //b4 went low 
	  PulsTime1=PulsCounter1;
	  PulsCounter1=0;
	  Counter1++;
     } 
   if (bit_test(changes,5)&& !bit_test (last_b,5)) 
     { 
      //b5 went low 
      PulsTime2=PulsCounter2;
	  PulsCounter2=0;
	  Counter2++;
     }
   if (bit_test(changes,6)&& !bit_test (last_b,6))
     { 
      //b6 went low 
	  PulsTime3=PulsCounter3;
  	  PulsCounter3=0;
	  Counter3++;
     }
   if (bit_test(changes,7)&& !bit_test (last_b,7))
     { 
      //b7 went low
	  Counter4++;
      if (++Priscale==16)
        {
 	     Priscale=0;
		 PulsTime4=PulsCounter4;
		 PulsCounter4=0;
 	    }        
     }
 }


void main (void){ 

	port_b_pullups(true);
	output_b(0xFF); 
	set_tris_b(0xFF);       // all input
	PulsTime1=0xFFFFFFFF;	// set max pulstime
	PulsTime2=0xFFFFFFFF;
	PulsTime3=0xFFFFFFFF;
	PulsTime4=0xFFFFFFFF;
	PulsCounter1=0xFFFFFFFF;
	PulsCounter2=0xFFFFFFFF;
	PulsCounter3=0xFFFFFFFF;
	PulsCounter4=0xFFFFFFFF;
    Priscale=0;

   set_rtcc(HIGH_START);
   setup_counters(RTCC_INTERNAL, RTCC_DIV_2); 
   enable_interrupts(INT_RTCC); 
   enable_interrupts(INT_RB); 
   enable_interrupts(global); 


   output_HIGH(pin_A1); //Led à 0

Ben nu aan het proberen om met interrupts te werken. Op zich lukt het wel maar ik heb wat problemen wanneer ik de hardware interrupt commando gebruik. Als ik dan veelvuldig en snel de switch indruk die de interrupt moet veroorzaken blijft het programma in de ISR hangen.
Met de software interrupt commando heb ik nergens last van.

In de Proton manual staat ook vermeld:"Altough the most efficient method of using an interrupt is in assembler,hardware innterrupts and Basic are poor bedfellows"

Wordt dit ongewenste gedrag veroorzaakt door mijn code of is dat wat je mag verwachten van een hardware interrupt in Basic?

DEVICE = 18F2520

CONFIG_START                                                           
OSC = HS                                ; xtrnl Oscillator high speed
FCMEN = OFF                             ; Failsafe Clock Monitor Disabled
IESO = OFF                              ; Int/Ext Oscillator Switch Over Disabled 
PWRT = ON                               ; Power Up Timer Enabled
BOREN = OFF                             ; Brownout Reset Disabled
BORV = 0                                ; Brownout Reset Voltage 4.6V
WDT = OFF                               ; Watchdog Timer Disabled
LPT1OSC = OFF                           ; T1 Oscillator Disabled 
CCP2MX = PORTC                          ; CCP2 Mux Muxed with RC1
STVREN = ON                             ; Stack Overflow Reset Enabled
LVP = OFF                               ; Low Voltage ICSP Disabled 
XINST = OFF                             ; XINST Disabled
DEBUG = OFF                             ; Background Debugger Disabled 

CONFIG_END 

DECLARE XTAL 20
                                                                            
DECLARE LCD_COMMANDUS 50000             ;Vereist bij de 'goedkope' 2x16 LCD
DECLARE LCD_DATAUS 250                  ;Vereist bij de 'goedkope' 2x16 LCD
                                                                                           
ALL_DIGITAL = true
;        76543210
PORTA = %00000000                       ;uitgangen laag
PORTB = %00000000                       ;uitgangen laag
PORTC = %00000000                       ;uitgangen laag
TRISA = %00000001                       ;A0 is een ingang, rest is uitgang
TRISB = %00000011                       ;B0, B1 zijn ingangen, rest is uitgang
TRISC = %00001111                       ;C0-C3 zijn ingangen, rest is uitgang
                                                      
CLEAR                                   ;wis RAM geheugen
CLS                                     ;wis scherm

ON_INTERRUPT GOTO ISR                   ;Interrupt service routine (met underscore = hardware interrupt!)
INTCON = %10010000                      ;Enables all unmasked interrupts, Enable external interrupt RB0
INTCON2 = %11000000                     ;All PORTB pull-ups are disabled, External interrupt on rising edge of RB0

SYMBOL Sok = PORTB.0                    ;DEZE GEBRUIKEN VOOR INTERRUPT TRIGGER
SYMBOL Lrood = PORTC.4
SYMBOL Lgroen = PORTC.6

DIM teller_main AS BYTE
DIM teller_isr AS BYTE

WHILE 1 = 1 

GOTO ISR_END
DISABLE                                 ;Disable interrupts
ISR:                                    ;Entry point of the ISR
  TOGGLE Lrood                          ;Change status Red Led
  INC teller_isr
  INTCON = %10010000                    ;Enables all unmasked interrupts, Enable external interrupt RB0
  RESUME                                ;Resume main program
  ENABLE                                ;Enable interrupts
ISR_END:


INC teller_main
TOGGLE Lgroen

PRINT AT 1,1, "MAIN ", DEC teller_main, "  "
PRINT AT 2,1, "ISR  ", DEC teller_isr, "  " 

DELAYMS 1500

WEND
END  

Om welke flow meters gaat het? een DS erbij is wel makkelijk om te weten waar je aan moet voldoen.

Op 28 november 2015 23:36:12 schreef Shiptronic:
Om welke flow meters gaat het? een DS erbij is wel makkelijk om te weten waar je aan moet voldoen.

Ik geloof niet dat een DS (datasheet neem ik aan) op dit moment heel relevant is. Mijn laatste vraag was vrij algemeen en heeft betrekking op een hardware interrupt in Basic.

Interrupts moet je gewoon aan laten staan, niet steeds aan en uit zetten. (en al zeker niet in de interruptroutine zelf!)
Disablen heeft geen nut want de 18Fxxx interrupts zijn toch niet re-entrant.

@Arco:
Grappig dat je dat zegt, want ik vroeg me net af hoe ik dat vroeger (met een AVR) deed. Volgens mij werden intr's daar juist disabled, als eerste actie in de intr-afhandeling.
Maar ik kan me vergissen (al weer een tijdje geleden) en het kan ook heel goed dat avr en pic daarin verschillen. En dat ga ik midden in de nacht niet uitzoeken... ;-)

"When an interrupt is responded to, the global interrupt enable bit is automatically cleared to disable further interrupts.
The return from interrupt instruction, RETFIE, exits the interrupt routine and sets the GIE bit (GIEH or GIEL if priority levels are used), which re-enables interrupts."

@Arco

Als ik het zo doe hou ik hetzelfde gedrag:

GOTO ISR_END
ISR:                                    ;Entry point of the ISR
  TOGGLE Lrood                          ;Change status Red Led
  INC teller_isr
ISR_END:

INTCON = %10010000                      ;Enables all unmasked interrupts, Enable external interrupt RB0

De code die ik gebruikte komt uit een boek die jezelf wel eens aanprijst :)
http://www.h-peters.com/dalg20/top/Electronic-E_books/30_picbasic_proj…
bladzijde 239

Sorry, type van de sensor, was ook uit nieuwsgierigheid>
Ben zelf binnenkort ook iets van plan met flowmeters en een PIC/Arduino/Rasberry een goed voorbeeld is dan nooit weg :)

Ik ken dat boek niet? (zou het ook niet aanbevelen)
Dat iemand een boek schrijft, wil nog niet zeggen dat alles wat erin staat ook klopt... :)

@Arco
http://www.circuitsonline.net/forum/view/121716/1/interrupts+basic

@Shiptronic
Momenteel maak ik gebruik van deze (budget) flow sensor (Ebay) https://www.olimex.com/Products/Components/Sensors/SNS-FLOW201/resourc… Alleen deze flow sensor is voor mijn toepassing niet goed geschikt, ik wil onder de 1 l/min kunnen meten. Heb al wel andere flow meters besteld maar deze heb ik nog niet.

Maar is de laatste code die ik plaatste beter/juist? Kan ik concluderen dat je in Basic beter geen hardware interrupts kunt gebruiken?

Inderdaad... ;) (heb het boek niet echt gelezen, want ik heb niks met Picbasic)
Een hardware interrupt is altijd beter. Ik heb trouwens geen idee hoe Picbasic de 18F interrupts behandelt.
(een 18F pic heeft 2 interrupts; een low priority en een high priority)

Wat hiervoor (bij uitstek) handig is: een CCP-blok, de 2520 heeft er twee (komt dat even mooi uit).

Wat zo'n blok doet in capture mode: als er een 'event' is wordt de momentane waarde van een vrij-lopende timer (1 of 3) opgeslagen en een interrupt-bit gezet. Zolang je die interrupt afhandelt (het 'gevangen' getal uitleest) vóór het volgende 'event' is er nix aan de hand, anders krijg je alleen het laatste getal (wat dus fout kan zijn).
Elke CCP interrupt die je krijgt levert een getal af, het verschil met het vorige getal is het aantal 'tikken' wat de betreffende timer heeft doorlopen. Is het verschil negatief is de timer 'door de nul' gegaan, ook eenvoudig te herkennen en op te lossen.

Kwestie van instellen (timer 1 en/of 3, CCP1 en CCP2) en gaan met de banaan...

ik ken de hele PIC niet, maar dit doe ik ook om toerentallen van verbrandings motoren uit te lezen...

Gebruik gewoon de ICP pin van de atmel dan. Interrupt capture pin. pakt de tijd van de timer die dan verstreken is.
Hoef je niet eens eerst een interrupt in te springen, gebeurd door hardware. ICP actief = klap, waarde timer in speciaal register en ISR wordt aangeroepen war je die waarde gaat bewerken

met 2 van die pinnen ben je er gewoon. maar het KAN er natuurlijk ook met 1, maar dan moet je van beide bronnen ene logic or maken en de afzonderlijke pinnen even voeren aan een interrupt pin.

de PIC zal ook wel zoiets hebben toch?
de atmega32, 64 of 128 hebben bijvoorbeeld 2 ICP pinnen, de kleinere zoals de MEGA88, 328 hebben er slechts 1.

Ik weet neit hoe het bij de PIC zit nogmaals, maar zal echt ook wel zoiets hebben anders zou het wel een grote gemiste kans zijn. De pin die ik het meeste gebruik!

Deze pic heeft 2 capture modules (zie mijn 1e post), nieuwere pics hebben er zelfs tot 9...

Toch even opmerken, beetje laat in het topic, maar ok: die sensor levert een puls met een frequentie van max 50Hz. Da's 20ms periode. De doorsnee PIC draait toch al snel op meerder MHz-en cycle time. Maw, je hebt zeeeeeeeeeeeeeeeeen van tijd om die pulsen in een loopje te pollen. Als je daarnaast een timer laat lopen, kun je die testen bij elke opgaande flank, en weet je wat de periode is.
Met interrupts kan, maar is in dit geval absoluut niet nodig.

Zonder interrupt heeft wel als nadeel dat de micro 'dedicated' is, hij kan dan niks anders meer doen tijdens het puls pollen.
Een timer is trouwens een interrupt... ;)