Tachometer

General discussion on mikroPascal.
Author
Message
wwwhelp
Posts: 33
Joined: 17 Jan 2005 13:11

Tachometer

#1 Post by wwwhelp » 12 Sep 2005 10:14

Hello, can anyone help me? I want to make a tachometer myself with 16F877 but I don't have big knowledges about the uC. Can you post some code example please.

Thank you!

User avatar
zristic
mikroElektronika team
Posts: 6608
Joined: 03 Aug 2004 12:59
Contact:

Re: Tachometer

#2 Post by zristic » 12 Sep 2005 20:01

wwwhelp wrote:Hello, can anyone help me? I want to make a tachometer myself with 16F877 but I don't have big knowledges about the uC. Can you post some code example please.
You will need a pulse counter or a frequency meter. We are preparing the examples for both for the next release. If someone else has a handy example, please do post it.

Rotary_Ed
Posts: 756
Joined: 26 Dec 2004 23:10
Location: Matthews, NC, USA
Contact:

Tachometer Code for PIC18F452

#3 Post by Rotary_Ed » 13 Sep 2005 00:16

Here is tachometer code that measures the period between pulses (assuming one pulse per revolution - but can be easily modified for a different number). It uses the CCP modules to measure the period and then converts it to RPM and displays the results. The LCD initialization needs to be changed to match your set up. It has been adapated from my Electronic Fuel Injection monitoring system.

It uses Unit Tach_Acq to actually acquire the pulse train. Also uses the PIC 18F452 default configuration of MP3 compiler. Shouldn't be too hard to convert to your chip if it has the CCP module. Could be made simpler - but this is what I had handy. Hope it helps.

Code: Select all

program Tach;

//Program measures the rotation period based on a pulse input and calculates RPM
//NOTE: Interrupt Procedure is NOT  used as the CCP2 interrupt flag is set regardless
//of whether interrupt is enabled.  Since the flag - CCP2IF is check in the CCP2_Call loop, there is no
//need for the MP3 Interrupt Procedure.

uses Tach_Acq;  //Determines rotation period time based on Timer TMR1
               //Note this works for an 18F452 with a 4 Mhz crystal
                //MP3 Default Configuration  for 19F452 chip was used for chip configuration
               ////Note:  18F452 Chip is configured so that CCP2 is on pin RC1
               // rather than RB3  __CONFIG _CONFIG3H_OFF
               //Pulse train must be +5V (Pluse logic High) and 0 volts (Pulse Logic Low)

Procedure Initialize_LCD;   //This must be tailored for your particular LCD set up
  begin

       LCD8_Config(PORTC,PORTD,6,4,5,7,6,5,4,3,2,1,0); //RS,EN,RW  Data Use in Real Fuel board Initialze LCD through PORTs
       LCD8_Cmd(Lcd_Clear);//Clear LCD display
       LCD8_Cmd(Lcd_Cursor_Off);//Turn visible cursor off

  end;

Procedure Initialize_Ports;
  begin

      TRISD:= $00;  //Set all PORTD for output
      PORTD:= $00;
      TRISC:= $02;  //   0000 0010  Set  bit RC1 for CCP2 input, RC6,4,5(Rs/En/RW) LCD control Output

  end;

Procedure Initialize_Timer_CCP;
   begin

      INTCON := $00;  //Disable all Interrupts for initialization of other registers
      T3CON  := $00;  //0000 0000  was $C0 Sets Timer1 as source clock for CCP1 and CCP2 leaves timer3 off
      CCP2CON:= $07;  //Initially Set CCP2 for 16th Rising Edge Capture
      T1CON  := $01;  //set for 8 bit time read, prescaler of 1 and turn timer 1 on
      TMR1L  := $00; //zero timer registers
      TMR1H  := $00;
      
   end;
Procedure Display_RPM(RPM:word);
    var Txt: array[6] of char;
        Txtstr: array[8] of char;
    begin
    
         TxtStr := 'RPM = ';
         LCD8_OUT(1,1,Txtstr);
         Wordtostr(RPM,Txt);
         LCD8_OUT(1,9,Txt);
         
    end;
         
Procedure CCP2_Call;   //Calls CCP2_RPM to get pulse interval of rotation period then calculates and displays RPM
     Var
           Rot_P_L  :byte; //Bytes to hold rotation period time from CCP2_RPM
           Rot_P_H  :byte;
           R_Period :Word; //Holds rotation period time calculated
           RPM      :word; //Holds RPM value calculated

   Begin

           Rot_P_L := 0;
           Rot_P_H := 0;
     If testbit(PIR2,CCP2IF) = 1   then //If CCP2 flag is set then pulse has triggered CCP2 module
      begin

         ccp2_RPM( Rot_P_H,Rot_P_L); //Calls to Data_Acq unit for pulse measurements
            if (CCP2CON = $07) AND (CCP2IF = 0) then    //    update viariable values after finished with entire time sequence
             begin

                  R_Period   := 00;
                  R_period    := Word(Rot_P_H shl 8) OR Rot_P_L; //combined hi and low byte of rotation period into word
                  RPM         :=  60000000 div R_Period ;  //Calculate RPM (period is in usec this turns it into revs/min)                                                  // 6.25 lbs auto gasoline/gallon US  100 LL Aviation fuel maybe 5.97 lbs
                  Display_RPM(RPM); //Displays Operational Data

              end; //If (CCP2CON =7 .....


     ClearBit(PIR2,CCP2IF);//Clear ccp2 Flag for next cycle
      end;

   end;

begin  //Main part of program
    Initialize_Ports;
    Initialize_LCD;
    Initialize_Timer_CCP;

    Repeat     //Loop forever calling CCP2_Call to determine rotation period and calculate RPM
    
      CCP2_call;
      
    until true = false;
end.





unit Tach_Acq;


implementation



Procedure ccp2_RPM(Var Rot_P_H,Rot_P_L:byte);//CCP2_Fuel;

  //This procedure is to measure the period of a pulse train to pin RC2.
 // The rotation period of the engine is determined by the duration of pulse Low Logic + Pulse High Logic.

 // Note:  The procedure is set up to interrupt on the 16th rising edge of a pulse  CCPXCON := 07

 //****************************  Start of timing sequence *********************************************************
//                     <-----------------------------  16th RE -----------_->
// '''''''''''|       |'''''''''''|       |'''''''''''        '''''''|       |'''''''
//            |.......|           |.......|                          |.......|
//                    16thRE     FE       RE                                  16thRE Repeating
//                  start  Time          Stop Time

//****************************************************************************************************************



  //*********************************************************************
  //Note:  18F452 Chip is configured so that CCP2 is on pin RC1 rather than RB3  __CONFIG _CONFIG3H_OFF
  //*********************************************************************



  Var

	   Pulse_HSL  :byte; //Hold Rise Edge time for start of timing sequence
	   Pulse_HSH  :byte;
	   Pulse_LSL  :byte; //Hold Falling Edge time  for start of Injector Pulse timing
	   Pulse_LSH  :byte;
	   Pulse_EL   :byte; //Rising Edge End of Time Sequence = Pulse High + Pulse Low time
	   Pulse_EH   :byte;
     TPulse_EL  :byte; //Rising Edge End of Time Sequence = Pulse High + Pulse Low time
	   TPulse_EH  :byte;  //Save time for calculations;


begin          //Start measurement with pulse going low (falling), end measurement with pulse going high (rising)
  ClearBit(INTCON,GIE);                       //clear GIE to prevent further interupts while handling this one

	if TestBit(PIR2,CCP2IF) = 1 then            //Is it CCP2 Interrupt?
	    begin    //Yes, it is CCP2 Interrup.   This section determines which module processes the request, Start_Time, Fall_Edge or Rise_Edge

          case CCP2CON of
          $04:  //if falling edge of pulse then this is the beginning of the measurement of the Pulse Low duratio
             begin

                   Pulse_LSL	:= CCPR2L;              //Get  start time of low Logic zero
                   Pulse_LSH	:= CCPR2H;
                   SetBit(CCP2CON,CCP2M0);	         //ReSet for Rising Edge Capture on next entry into ISR
                   CCP2CON := $05;
                   ClearBit(PIR2,CCP2IF);           //Clear in case of false interrupt when changin mode
             end;

          $05:   //If 1st Rising edge after Fallin edge then timing sequence is complete - calculate time internvals
                  //Find Rotation Period - Rot_PX
                  //Find Injector Pulse time - PW_X
            begin

                  Pulse_EL := CCPR2L;   //Get time of second Rising Edge
                  Pulse_EH := CCPR2H;
                  TPulse_EL:=Pulse_EL;  //Hold time value
                  TPulse_EH:=Pulse_EH;

                  Rot_P_L  := Pulse_EL - Pulse_HSL;  //Calculate Rotation Time = Time Pulse HI + Time Pulse Lo
                  If TestBit(STATUS,C) = 0 then
                       Pulse_EH := Pulse_EH - 1;
                  Rot_P_H  := Pulse_EH - Pulse_HSH;


                  CCP2CON  := $07;  //was $07 Set for 16th Rising edge Interrupt
                  ClearBit(PIR2,CCP2IF);  //Clear possible supurious Interrupt caused by changing CCP interrupt mode
            end;

           //This completes timing sequence - Leave and wait for next 16th Rising Edge of pulse train
           $07: //*******  Captures Start time of pulse sequence (Rising edge  *****************************************************=
              begin

                  Pulse_HSL := CCPR2L;      //Get captured pulse high start time low byte     *******Change back to CCPR1L *****
                  Pulse_HSH := CCPR2H;       //Get captured time high byte

                  CCP2CON := $04;          //These two register configuration operations accomplish the same purpose
                  ClearBit(PIR2,CCP2IF);   //Reset to preclude False Interrupt

              end;
           
          end;//case

       end;  // end of IF Statement CCP2_Inj

  end;

end.
Rotary_Ed
Matthews, NC USA
Rv-6A N494BW Rotary Powered

User avatar
zristic
mikroElektronika team
Posts: 6608
Joined: 03 Aug 2004 12:59
Contact:

Re: Tachometer Code for PIC18F452

#4 Post by zristic » 13 Sep 2005 08:56

Thanks Ed, I will steal this code of yours for an example and put it in the next release. :twisted:

(I will write the reply to that email of yours - I am just away from my PC too often.)

Charlie
Posts: 2744
Joined: 01 Dec 2004 22:29

#5 Post by Charlie » 13 Sep 2005 10:44

HI Rotery_Ed,

Thats is SOme Great Code.I tried it however and I could not get it to compile.I got a slurry of Error messages.I only have the demo ersion ,but I never got the Demo limit eror. any suggestions?
Regards Charlie M.

Bob Lawrence
Posts: 300
Joined: 18 Aug 2004 11:55
Location: Lwr Sackville, Nova Scotia, Canada

Works

#6 Post by Bob Lawrence » 13 Sep 2005 12:17

Great! work Ed. Thanks! for sharing. I tried it and everything compiled fine (second try). Charlie, did you make a seperate unit for Tach_Acq? Also, when you save that unit ensure it got saved in the right location with the main project. The first time I tried it , my unit got saved with some example that I ws working on a few days ago. Once I noticed that glitch I just saved it to the current project and it worked GREAT!!!!!!!!!!!!!!!!!!! 8)

Rotary_Ed
Posts: 756
Joined: 26 Dec 2004 23:10
Location: Matthews, NC, USA
Contact:

Tachometer Code for PIC18F452

#7 Post by Rotary_Ed » 13 Sep 2005 13:44

Hi Charlie (Neighbor), As Bob points out (but I failed to), the Tach_Acq portion needs to be compiled as a separate Unit. Although, with a bit of work, it could be combined into one segment of code in the main program.

I recompiled it this morning and it worked on my machine. I have a pulse generator that I use to simulate the tach pulse signal and it responds with the rpm value being displayed. I might mention that if you do not have a signal hooked up to your chip, the LCD will not have anything to display. I probably should have coded in a text message line such as "MP Tachometer" so that you would know it had programmed the chip properly - but just didn't think of it at the time.

Also, this is written for a 18F452 chip - so different chips might cause some compiler errors.

Thanks Bob, glad to share it, I've enjoyed learning to program the microchip using MikroPascal.
Rotary_Ed
Matthews, NC USA
Rv-6A N494BW Rotary Powered

Rotary_Ed
Posts: 756
Joined: 26 Dec 2004 23:10
Location: Matthews, NC, USA
Contact:

Tach for MP -Zristic

#8 Post by Rotary_Ed » 13 Sep 2005 13:47

Hi Z,

Yes, feel free to adapt it as an example. As you know my coding can be somewhat unorthdox, so do not hesitate to change it to make it clearer or conform with good programing practices.

I realize you are very busy, so no need to reply to my personal message. Just wanted you to know you had not been forgotten.

Best Regards
Rotary_Ed
Matthews, NC USA
Rv-6A N494BW Rotary Powered

Rotary_Ed
Posts: 756
Joined: 26 Dec 2004 23:10
Location: Matthews, NC, USA
Contact:

Improved Tachometer Code for PIC18F452

#9 Post by Rotary_Ed » 13 Sep 2005 15:39

I realized that I had a deal of unneeded code and comments from my original program included in the Tach code, so I cleaned it up a bit to make it clearer. I made two functional changes. I changed the CCP2 initialization so that it captures and calculates rotation period based the 4th rising pulse edge(CCP2CON := $06) rather than the 16th (CCP2CON := $07). I also added a Title line to the code which displays on LCD whether or not you have a pulse signal hooked to CCP2 (PORTC.RC1). This should indicate the program has compiled properly. RPM will not be displayed unless you have a pulse signal to CCP2.

As noted before, the code has two sections, a main program section Tach.ppas and a Unit Tech_Acq.ppas.


Code: Select all

program Tach;

//Program measures the rotation period based on a pulse input and calculates RPM
//NOTE: Interrupt Procedure is NOT  used as the CCP2 interrupt flag is set regardless
//of whether interrupt is enabled.  Since the CCP2IF is checked in the Main repeat loop, there is no
//need for the MP3 Interrup Procedure.

uses Tach_Acq;  //Determines rotation period time based on Timer TMR1
               //Note this works for an 18F452 with a 4 Mhz crystal
                //MP3 Default Configuration  for 19F452 chip was used for chip configuration
               ////Note:  18F452 Chip is configured so that CCP2 is on pin RC1
               // rather than RB3  __CONFIG _CONFIG3H_OFF
               //Pulse train must be +5V (Pluse logic High) and 0 volts (Pulse Logic Low)

Procedure Initialize_LCD;   //This must be tailored for your particular LCD set up
  begin

       LCD8_Config(PORTC,PORTD,6,4,5,7,6,5,4,3,2,1,0); //RS,EN,RW  Data Use in Real Fuel board Initialze LCD through PORTs
       LCD8_Cmd(Lcd_Clear);//Clear LCD display
       LCD8_Cmd(Lcd_Cursor_Off);//Turn visible cursor off

  end;

Procedure Initialize_Ports;
  begin

      TRISD:= $00;  //Set all PORTD for output
      PORTD:= $00;
      TRISC:= $02;  //   0000 0010  Set  bit RC1 for CCP2 input, RC6,4,5(Rs/En/RW) LCD control Output
                          //Note: this may need to be changed to match your board wiring
  end;

Procedure Initialize_Timer_CCP;
   begin

      INTCON := $00;  //Disable all Interrupts for initialization of other registers
      T3CON  := $00;  //0000 0000  was $C0 Sets Timer1 as source clock for CCP1 and CCP2 leaves timer3 off
      CCP2CON:= $06;  //set for 4th rising edge, $07 Initially Set CCP2 for 16th Rising Edge Capture
      T1CON  := $01;  //set for 8 bit time read, prescaler of 1 and turn timer 1 on
      TMR1L  := $00; //zero timer registers
      TMR1H  := $00;
      
   end;
   
Procedure Display_Title;
   var  Txtstr:array[16] of char;
   
   begin
        TxtStr := ' MP3 Tach Code';  //Displayed even when Tach pulse train is not ON
        LCD8_OUT(1,1,TxtStr);
   end;
   
Procedure Display_RPM(RPM:word);
    var Txt: array[6] of char;
        Txtstr: array[8] of char;
    begin
         Txt := '  '; //Intialize Txt
         TxtStr := ' RPM = ';   //Displayed when Pulse train to CCP2 is active
         LCD8_OUT(2,1,Txtstr);
         Wordtostr(RPM,Txt);
         LCD8_OUT(2,9,Txt);
         
    end;
         
Procedure CCP2_Call;   //Calls CCP2_RPM to get pulse interval of rotation period then calculates and displays RPM
     Var
           Rot_P_L  :byte; //Bytes to hold rotation period time from CCP2_RPM
           Rot_P_H  :byte;
           R_Period :Word; //Holds rotation period time calculated
           RPM      :word; //Holds RPM value calculated

   Begin

           Rot_P_L := 0;//Variables for Rotation Period Time
           Rot_P_H := 0;
     If testbit(PIR2,CCP2IF) = 1   then //If CCP2 flag is set then pulse has triggered CCP2 module
      begin

         ccp2_RPM( Rot_P_H,Rot_P_L); //Calls to Data_Acq unit for pulse measurements
            if (CCP2CON = $06) AND (CCP2IF = 0) then    //update viariable values after finished with entire time sequence
             begin

                  R_Period   := 00;
                  R_period    := Word(Rot_P_H shl 8) OR Rot_P_L; //combined hi and low byte of rotation period into word
                  RPM         :=  60000000 div R_Period ;  //Calculate RPM (period is in clock cycles this turns it into revs/min)
                                                           //Conversion value valid for a 4 Mhz oscillator frequency ONLY
                  Display_RPM(RPM); //Displays RPM Data

              end; //If (CCP2CON =7 .....


     ClearBit(PIR2,CCP2IF);//Clear ccp2 Flag for next cycle
      end;

   end;

begin  //Main part of program
    Initialize_Ports;
    Initialize_LCD;
    Initialize_Timer_CCP;
    Display_Title;  //Display title of program on LCD

    Repeat     //Loop forever calling CCP2_Call to determine rotation period and calculate RPM

      CCP2_call;    //Call for Data and display RPM based on CCP2 module response to a pulse train
      
    until true = false;
end.



//Create a separate UNIT for the following code

unit Tach_Acq;


implementation



Procedure ccp2_RPM(Var Rot_P_H,Rot_P_L:byte);//CCP2_Fuel;

  //This procedure is to measure the period of a pulse train to pin RC2.
 // The rotation period of the engine is determined by the duration of pulse Low Logic + Pulse High Logic.

 // Note:  The procedure is set up to interrupt on the 16th rising edge of a pulse  CCPXCON := 07

 //****************************  Start of timing sequence *********************************************************
//                     <----------------------- 4th or 16th RE -----------_->
// '''''''''''|       |'''''''''''|       |'''''''''''        '''''''|       |'''''''
//            |.......|           |.......|                          |.......|
//                    16thRE     FE       RE                                  16thRE Repeating
//                  start  Time          Stop Time

//****************************************************************************************************************



  //*********************************************************************
  //Note:  18F452 Chip is configured so that CCP2 is on pin RC1 rather than RB3  __CONFIG _CONFIG3H_OFF
  //*********************************************************************

 //The rising edge CCP2 capture may be set as either CCP2Con := $06 (every 4th rising edge) or
 // CCP2Con := $07 (every 16th rising pules).  Must also reset value in main pgrom in CCP2 Initialization procedure
 //and CCP2_Call Procedure (where If statement signifies end of timeing sequence)
  Var

	   Pulse_HSL  :byte; //Hold Rise Edge time for start of timing sequence
	   Pulse_HSH  :byte;
	   Pulse_EL   :byte; //Next Rising Edge for End of Time Sequence
	   Pulse_EH   :byte;


begin          //Start measurement with pulse going low (falling), end measurement with pulse going high (rising)
  ClearBit(INTCON,GIE);                       //clear GIE to prevent further interupts while handling this one

	if TestBit(PIR2,CCP2IF) = 1 then            //Is it CCP2 Interrupt?
	    begin    //Yes, it is CCP2 Interrup.
         // The CASE section determines which code section processes the request, Start_Time, Fall_Edge or 2nd Rise_Edge

          case CCP2CON of   //Note: CCP2CON := $06 (4th Rising edge) could also use  $07 (16th rising edge)
          $06:  //This STARTS TIMING SEQUENCE, get start time of every 4th rising edge - Leave and wait for next falling ($04) Edge of pulse train
               //*******  Captures Start time of pulse sequence (Rising edge  *****************************************************=
                              begin

                  Pulse_HSL := CCPR2L;      //Get captured pulse high start time low byte     *******Change back to CCPR1L *****
                  Pulse_HSH := CCPR2H;       //Get captured time high byte

                  CCP2CON := $04;          //Set to capture next Falling edge
                  ClearBit(PIR2,CCP2IF);   //Reset to preclude False Interrupt

              end;
          $04:  //This detects the falling edge simply to know to reset CCP2 module to intercept next Rising edge ($05)
                //This is next falling edge of pulse, so  reset for next rising edge($05 - which will end period measurement)
                //All this does, on detection of the falling edge, is to reset the CCP2 module for next Rising Edge

             begin

                   CCP2CON := $05;          //ReSet for Rising Edge Capture on next entry into this CCP2_RPM Routine
                   ClearBit(PIR2,CCP2IF);   //Clear in case of false interrupt when changin mode
             end;

          $05:    //This ENDS TIMING SEQUENCE
                  //  2nd Rising edge (1st after Fallin edge) so timing sequence is complete - calculate time internvals
                  //Find Rotation Period - Rot_P_X

            begin

                  Pulse_EL := CCPR2L;   //Get time of second Rising Edge
                  Pulse_EH := CCPR2H;


                  Rot_P_L  := Pulse_EL - Pulse_HSL;  //Calculate Rotation Time = Time Pulse HI + Time Pulse Lo
                  If TestBit(STATUS,C) = 0 then
                       Pulse_EH := Pulse_EH - 1;
                  Rot_P_H  := Pulse_EH - Pulse_HSH;


                  CCP2CON  := $06;  //Set to start next timing cycle on 4th rising edge was $07 Set for 16th Rising edge Interrupt
                  ClearBit(PIR2,CCP2IF);  //Clear possible supurious Interrupt caused by changing CCP interrupt mode
            end;
           
          end;//case

       end;  // end of IF Statement CCP2_Inj

  end;

end.
Rotary_Ed
Matthews, NC USA
Rv-6A N494BW Rotary Powered

Charlie
Posts: 2744
Joined: 01 Dec 2004 22:29

#10 Post by Charlie » 13 Sep 2005 20:55

Hi Ed,and Bob,

Thanks for the explaination.Ed thanks for the updated version.
Regards Charlie M.

Charlie
Posts: 2744
Joined: 01 Dec 2004 22:29

#11 Post by Charlie » 13 Sep 2005 21:42

Hi Ed,

I must be Thick Headed.I don't know how to creat a seperate Unit.I tyied to cut and paste where it says to creat a seperate unit and compile it seperately from the other code but I can't get to compile. :oops:
Regards Charlie M.

Bob Lawrence
Posts: 300
Joined: 18 Aug 2004 11:55
Location: Lwr Sackville, Nova Scotia, Canada

How to Create a new unit

#12 Post by Bob Lawrence » 13 Sep 2005 23:32

Charlie,

One way to do it:
Open your Tach project. Then select "File" "new" You will see from the tabs at the top of the MP software that you have a new unit. (it may be called unit2 or 3 etc.. From there you can paste in Ed's code for the Tach_Acq unit. Then go to File save as. Save the name as Tach_Acq (change ot from the unit name given when it's created)


You can save it to your project folder so that the compiler knows where it is.

Now you can compile it.

Charlie
Posts: 2744
Joined: 01 Dec 2004 22:29

#13 Post by Charlie » 13 Sep 2005 23:39

Hi Bob,

Thanks for explaining how to do it.I will try and see what happens.:)
Regards Charlie M.

Charlie
Posts: 2744
Joined: 01 Dec 2004 22:29

#14 Post by Charlie » 13 Sep 2005 23:56

Well Im getting there but not quite yet.Now I just get" Identifer ccp2_rpm not declared" error messsage. any thoughts on this?
Regards Charlie M.

Bob Lawrence
Posts: 300
Joined: 18 Aug 2004 11:55
Location: Lwr Sackville, Nova Scotia, Canada

Ensure you included

#15 Post by Bob Lawrence » 14 Sep 2005 00:12

You have to put "uses Tach_Acq; " (without the quotes ) in the main unit.

===============================================
program Tach;

///begin
//////program Tach;

//Program measures the rotation period based on a pulse input and calculates RPM
//NOTE: Interrupt Procedure is NOT used as the CCP2 interrupt flag is set regardless
//of whether interrupt is enabled. Since the flag - CCP2IF is check in the CCP2_Call loop, there is no
//need for the MP3 Interrupt Procedure.

uses Tach_Acq; //// //Determines rotation period time based on Timer TMR1
//N

Post Reply

Return to “mikroPascal General”