PropCS - a small project

2»

Comments

  • denominatordenominator Posts: 242
    edited April 2012 Vote Up0Vote Down
    Rsadeika wrote: »
    Actually, I have not looked at Dave Hine's Spinix program, I want to keep an open mind about how I will be coding my small project. I am not sure about what you mean about "memory usage" differences.

    In the early days of PropGCC usage, I had a heck of a time trying to use pins 31,30 to communicate with a terminal program that is not the PropGCC terminal/SimpleIDE terminal. What I am thinking is, I have the C3 start up with PropCS, and on my computer I start a terminal program which is connected to the C3 with whatever port is available. In other words, communicating with the C3 without the use of SimpleIDE.

    When the PropCS is running in stand alone, it would be nice to be able to start up programs that are on the SD card, just to see if something like that can be done. I personally do not think that it can be done, but I thought I would present the idea. That way you could keep the PropCS code as small as possible, and still have the capability to expand the 'commands' list.

    The other idea that I had was trying to implement the UNIX '&' (background task?), so that means using pthreads, that is why I want to keep the PropCS code as small as possible. I am also staying away from thinking in terms of PropCS as a kernel, or an OS, just thinking that PropCS is some tight code that can run some specific commands.

    Ray

    Ray,

    Re: the terminal. I'm assuming that you're using the same USB connection to your computer that SimpleIDE uses, so pins 31,30 are appropriate. I don't know for sure what your trouble was or the tool(s) you used, but I use propeller-load to talk to my boards and it works fine. I think a potential troublemaker is if subsequent serial terminal programs flash the DTR and cause the Prop to reset - so if your serial terminal program does this you have to burn to EEPROM. Besides "propeller-load -t" you could also try the Parallax Serial Terminal.

    Re: stand alone. You should at least check out what Spinix does - it does almost exactly what you want to do, albeit with Spin. I think it may be possible if you're crafty enough. The memory differences I was referring to: C in XMM/XMMC mode has a cache from 0x6000 - 0x7FFF, the stack starts a few bytes below 0x6000, and the heap starts at the top of the data area in RAM (above any hub code and hub data). You have to load the program somewhere in that region safely and manipulate the linking of the subprogram to know about the higher address and also manipulate the kernel for the subprogram to play in that area. Spin has a different set of difficulties, so Spinix is a guide, not the solution.

    Also see this thread: http://forums.parallax.com/showthread.php?139030-Running-binaries-from-Hub-RAM

    - Ted
  • RsadeikaRsadeika Posts: 2,570
    edited April 2012 Vote Up0Vote Down
    As for the terminal program that I was referring to in an earlier post, I created it using Visual C#. What it is doing is, when I start the program, while I have PropCS running, it does a reset/reboot of the C3 board. I looked at all of the settings for SerialPort in the terminal program, and the only thing that I found that could help is an 'enable DTR - true/false, I have this set to false, but I did have it set to true , and the problem still occurs. Other than that, I did not see anything else that would change the way the terminal program works. I was thinking of maybe trying Visual C, but I do not think that I would be able to figure out what to do with SerialPort settings to make the program work correctly.

    Since I have been working with SimpleIDE, and PropCS, I have found that the easiest way to use the IDE is to create a specific project. In this case I have a project name, PropCS_C3F-SDXMMC which has only that particular board selection in the mount() function, and all of the IDE settings needed to compile the program correctly. This way I have minimized the size of the mount() funtion, even when you are using the SD for larger program space you still have to try to keep the main code as small as possible, and it has the correct settings each time I start up the IDE.

    Ray
  • jazzedjazzed Posts: 11,767
    edited April 2012 Vote Up0Vote Down
    Rsadeika wrote: »
    As for the terminal program that I was referring to in an earlier post, I created it using Visual C#. What it is doing is, when I start the program, while I have PropCS running, it does a reset/reboot of the C3 board. I looked at all of the settings for SerialPort in the terminal program, and the only thing that I found that could help is an 'enable DTR - true/false, I have this set to false, but I did have it set to true , and the problem still occurs. Other than that, I did not see anything else that would change the way the terminal program works. I was thinking of maybe trying Visual C, but I do not think that I would be able to figure out what to do with SerialPort settings to make the program work correctly.

    SimpleIDE terminal toggles DTR when it closes the port. If you need another terminal program to run after programming I recommend using BURN or RUN buttons and not RUN Console.
    Rsadeika wrote: »
    Since I have been working with SimpleIDE, and PropCS, I have found that the easiest way to use the IDE is to create a specific project. In this case I have a project name, PropCS_C3F-SDXMMC which has only that particular board selection in the mount() function, and all of the IDE settings needed to compile the program correctly. This way I have minimized the size of the mount() funtion, even when you are using the SD for larger program space you still have to try to keep the main code as small as possible, and it has the correct settings each time I start up the IDE.

    This is good advice. I've had a little trouble by not following it.
  • denominatordenominator Posts: 242
    edited April 2012 Vote Up0Vote Down
    Rsadeika wrote: »
    Since I have been working with SimpleIDE, and PropCS, I have found that the easiest way to use the IDE is to create a specific project. In this case I have a project name, PropCS_C3F-SDXMMC which has only that particular board selection in the mount() function, and all of the IDE settings needed to compile the program correctly. This way I have minimized the size of the mount() funtion, even when you are using the SD for larger program space you still have to try to keep the main code as small as possible, and it has the correct settings each time I start up the IDE.

    Ray

    Ray,

    I agree with you and Steve that it is easy to get confused by having too much stuff going on in the mount() routine. All those different setups are there to show you the options you have, and simplifying them to the board you're using will definitely make it easier for you to see what's going on.

    However, I want to assure you that unless you define multiple board symbols, you are not using up any additional program space. The #ifdef foo .... #endif is a preprocessor directive. This means that if the controlling symbol (foo or whatever) is not defined, then the preprocessor will completely omit the enclosed code before passing the code to the compiler. Thus, because the compiler doesn't see the code, compiler does not generate any extra code - it's like the enclosed code never existed at all, other than taking up lines in the source file.

    - Ted
  • RsadeikaRsadeika Posts: 2,570
    edited April 2012 Vote Up0Vote Down
    What is the accuracy of the software RTC? Yesterday afternoon I decided to let the clock run for about 18 hours, making sure it would run past midnight. This morning, when I checked the RTC clock, which was running within PropCS, it was more than an hour and three minutes off. The real time was 6:30:00, but the RTC time was 7:33:00. I had the daylight savings set on, so I am not sure as to why it is showing an hour difference? Assuming the the hour difference can be fixed, I am not so sure that a three minute discrepancy on an 18 hour run could be of any practical use. So, where are those boards with an hardware RTC, I am not a board manufacturer, so the answer has to be with somebody else.

    Ray
  • Dave HeinDave Hein Posts: 4,532
    edited April 2012 Vote Up0Vote Down
    A crystal oscillator should be accurate to at least 100 PPM, which is 0.01 percent. After 18 hours you should have an error of less than 7 seconds. A 1 hour error is about 6% and a 3 minute error would be about 0.3 percent, which is still well above the expected error. If the error is really 6% you will see it lose 1 second every 18 seconds, or 10 seconds in 180 seconds. Check the time for a couple of minutes and see if you lose a few seconds druing that time.

    If there is some weird daylight savings thing going on, then the error is only 0.3 percent, which may be due to how your oscillator is set up, in addition to how daylight savings is handled. Or maybe the RTC code isn't handling the rollover at midnight correctly. Try setting the time to 23:59:50, and see what happens after the midnight rollover.
  • RsadeikaRsadeika Posts: 2,570
    edited April 2012 Vote Up0Vote Down
    I was just thinking about the Daylight Savings setting, does that mean that the software will automatically: Spring forward/Fall back? Tomorrow I will have to find the exact date for the Spring forward/Fall back, and see what happens.

    Ray
  • jazzedjazzed Posts: 11,767
    edited April 2012 Vote Up0Vote Down
    Rsadeika wrote: »
    I was just thinking about the Daylight Savings setting, does that mean that the software will automatically: Spring forward/Fall back? Tomorrow I will have to find the exact date for the Spring forward/Fall back, and see what happens.

    Ray

    The time library should change time based on this rule: DST goes from the 2nd Sunday in March to the first Sunday in November.
  • RsadeikaRsadeika Posts: 2,570
    edited April 2012 Vote Up0Vote Down
    This evening I will be doing another, possibly ~18 hour test. I will be setting up the date for March 10 2012 in non DST time, this will see what kind of accuracy I get, and if it really does the DST switch.

    Since I am trying to get my board to work in stand alone, I am running into another problem. When I put my board in stand alone, meaning that the USB is unplugged, and running on the power plug. When I plug the USB back in, and start up a terminal program, it is always doing a reset, re-booting PropCS. I have tried this with SimpleIDE, Parallax Serial, and my own terminal program, they all re-boot PropCS when the USB is plugged back in. Now, I am not sure what I have to do to overcome this, let alone understand what is really happening, an explanation would be appreciated.

    Since I have four different boards available I am now thinking about making one of the boards, maybe the QSboard, a development setup. Maybe somebody has a different idea as to what a development setup would look like?

    Ray
  • Dave HeinDave Hein Posts: 4,532
    edited April 2012 Vote Up0Vote Down
    I would suggest doing a shorter test first to determine if you have the 6% error or 0.3% error. In 1000 seconds (about 17 minutes) you will see a 60-second error at 6%, or a 3-second error at 0.3%. If the larger error is due to daylight savings you could set the date/time for March 9, 2012 at 23:59:55, and see what happens after it rolls over to March 10. You have to have daylight savings enabled for it to do the adjustment.
  • jazzedjazzed Posts: 11,767
    edited April 2012 Vote Up0Vote Down
    Rsadeika wrote: »
    ... they all re-boot PropCS when the USB is plugged back in. Now, I am not sure what I have to do to overcome this, let alone understand what is really happening, an explanation would be appreciated.

    Ray, the reset is caused by the computer's USB identification sequence.
  • RsadeikaRsadeika Posts: 2,570
    edited April 2012 Vote Up0Vote Down
    I have just tried the DST again, using Dave Hine's suggestions. Primarily, I used 23:59:55 as the time. I tried 3/9/2012, 3/10/2012, and 3/11/2012, none of these settings showed moving forward an hour. I also tried 11/3/2012, 11/4/2012, and 11/5/2012, none of these settings showed moving back an hour. And still shows on a normal time run, using DST, when it passes midnight it moves time an hour forward. At this point I am not sure if there are problems with the DST setting in the library, or if there is something more to it. I thought I saw an AM/PM setting in the library, but now I am not sure.

    Ray
  • jazzedjazzed Posts: 11,767
    edited April 2012 Vote Up0Vote Down
    Rsadeika wrote: »
    I have just tried the DST again, using Dave Hine's suggestions. Primarily, I used 23:59:55 as the time. I tried 3/9/2012, 3/10/2012, and 3/11/2012, none of these settings showed moving forward an hour. I also tried 11/3/2012, 11/4/2012, and 11/5/2012, none of these settings showed moving back an hour. And still shows on a normal time run, using DST, when it passes midnight it moves time an hour forward. At this point I am not sure if there are problems with the DST setting in the library, or if there is something more to it. I thought I saw an AM/PM setting in the library, but now I am not sure.

    Ray

    It is very unclear, do you enable DST when you set the time?
  • RsadeikaRsadeika Posts: 2,570
    edited April 2012 Vote Up0Vote Down
    Yes, I enable DST.

    Ray
  • ersmithersmith Posts: 1,591
    edited April 2012 Vote Up0Vote Down
    Rsadeika wrote: »
    I have just tried the DST again, using Dave Hine's suggestions. Primarily, I used 23:59:55 as the time. I tried 3/9/2012, 3/10/2012, and 3/11/2012, none of these settings showed moving forward an hour. I also tried 11/3/2012, 11/4/2012, and 11/5/2012, none of these settings showed moving back an hour. And still shows on a normal time run, using DST, when it passes midnight it moves time an hour forward. At this point I am not sure if there are problems with the DST setting in the library, or if there is something more to it. I thought I saw an AM/PM setting in the library, but now I am not sure.

    What time zone do you have it set to? You do know that you have to have a TZ variable set in the environment, right?
  • RsadeikaRsadeika Posts: 2,570
    edited April 2012 Vote Up0Vote Down
    Is the program below still the best way to use the serial device? Has anything changed since the last time I tried this a few months ago? Unfortunately, the program below is not displaying anything out of that port.

    /*
     * @file PropCS_C3_sa.c
     * This is the main PropCS_C3_sa program start point.
     */
    #include <stdio.h>
    #include <propeller.h>
    #include <unistd.h>
    #include <stdlib.h>
    #include <string.h>
    #include <time.h>
    #include <sys/rtc.h>
    #include <cog.h>
    #include <ctype.h>
    #include <sys/stat.h>
    #include <dirent.h>
    #include <sys/sd.h>
    #include <pthread.h>
    
    /*
     * Main program function.
     */
    int main(int argc, char* argv[])
    {
    
    /*                                                   Rx  Tx */
        char *serdevice = "SSER:115200, 23, 22";
        FILE *port = fopen(serdevice, "r+");
    
        waitcnt(CNT + CLKFREQ);
    
        fprintf(port,"This is the PropCS program!\n");
        fprintf(port,"Type 'help' for Commands list.\n");
    
        while(1)
        {
    
        }
        return 0;
    }
    
    
  • Dave HeinDave Hein Posts: 4,532
    edited April 2012 Vote Up0Vote Down
    I've never tried opening a serial port, but maybe you need to specify "w" in fopen to be able to output to it.
  • jazzedjazzed Posts: 11,767
    edited April 2012 Vote Up0Vote Down
    Rsadeika wrote: »
    Is the program below still the best way to use the serial device? Has anything changed since the last time I tried this a few months ago? Unfortunately, the program below is not displaying anything out of that port.

    Ray your code should work as is. It works for me as serdevice "SSER:115200,1,0".

    I was thinking of providing a few library functions for this to simplify things.

    I.E.
    char *serialDeviceString(char *buffer, char *name, int baud, int rxpin, int txpin);
    

    Then you could do this:
    char buffer[40];
    FILE *p = fopen(serialDeviceString(buffer, "SSER:", 115200, 1, 0), "r+");
    
    setbuf(p,0); // turn off buffering
    fprintf(p, "Hello Ray");
    
  • denominatordenominator Posts: 242
    edited April 2012 Vote Up0Vote Down
    jazzed wrote: »
    Ray your code should work as is. It works for me as serdevice "SSER:115200,1,0".

    I was thinking of providing a few library functions for this to simplify things.

    I.E.
    char *serialDeviceString(char *buffer, char *name, int baud, int rxpin, int txpin);
    

    Then you could do this:
    char buffer[40];
    FILE *p = fopen(serialDeviceString(buffer, "SSER:", 115200, 1, 0), "r+");
    
    setbuf(p,0); // turn off buffering
    fprintf(p, "Hello Ray");
    

    Steve,

    I'm a bit confused why you're transmitting serial on pins 1 and 0 unless you have an additional serial link setup between those pins and the PC or other agent. (I'm assuming the antecedent post(s) on this topic mentioned this, but they're not in this thread.)

    Nonetheless, I think we should support directly setting the serial parameters in global memory, and I believe we may want to push this as the most desired way to do it. Basically you include the following block of code in your code and change the values as appropriate:
    /* globals that the loader may change; these represent the default
     * pins to use
     */
    unsigned int _rxpin = 31;
    unsigned int _txpin = 30;
    unsigned int _baud = 115200;
    

    Three notes about this:

    1) This works today with both Simple Serial, Full Duplex Serial and the Tiny library. All that's required is to make it more visible to the users (perhaps on the http://propgcc.googlecode.com/hg/doc/Library.html page).

    2) This takes the least amount of memory. This is important for LMM, because in that mode every byte counts.

    3) Note the comment "globals that the loader may change" - I took this code from lib/driver/serialparam.c! Also note the comment in the library Wiki page:
    Like the simple serial driver, the full duplex serial driver defaults to the values in the board specific _baud, _txpin, and _rxpin variables, and these may be overridden by passing a string containing baud,rxpin,txpin.

    Square this up with Issue 24 I reported a while ago: http://code.google.com/p/propgcc/issues/detail?id=24

    Summary: It's clear that the serial I/O designer(s) did not intend for the average user to even bother with setting the tx/rx pins and baud; instead these parameters were supposed to be taken from the board config and patched by the loader into the executable; therefore your program would automatically start communicating on the proper pins with the proper baud. Additionally, propeller-load's serial terminal (or any other serial terminal proxy) would automatically know the proper baud rate to communicate to your program.

    If realized, this would be the simplest solution and avoid the most problems for the vast majority of users.

    - Ted
  • RsadeikaRsadeika Posts: 2,570
    edited April 2012 Vote Up0Vote Down
    Still can not get serdevice to work. I have tried my C3, and GG PPUSB boards, still no joy. I remember that when I was doing this, using the command terminal, I would have to have something like 'propeller-load ... -t', in the SimpleIDE Build Status window, I do not see anywhere that a '-t' is being used, does that make a difference?

    What I am trying to do is, using a variation of PropCS, use a pthread that would have a serdevice in it, to keep a port open for access through an RS-232 break-out board that I have attached. Since I was not having any luck doing it using pins 31,30, I thought I would give the RS-232 idea a try.

    Ray
  • jazzedjazzed Posts: 11,767
    edited April 2012 Vote Up0Vote Down
    Rsadeika wrote: »
    Still can not get serdevice to work. I have tried my C3, and GG PPUSB boards, still no joy. I remember that when I was doing this, using the command terminal, I would have to have something like 'propeller-load ... -t', in the SimpleIDE Build Status window, I do not see anywhere that a '-t' is being used, does that make a difference?

    In the attachment PST is connected to P0/1 via a PropPlug on COM20. P0 is connected to PropPlug >RX, P1 is connected to PropPlug <TX, PropPlug VSS is connected to board ground (VSS). SimpleIDE loads the board via P30/31 as usual. SimpleIDE does not use the propeller-load -t. Instead it opens a separate serial terminal either by Run Console F8 or pressing the Serial Console button. SimpleIDE console will reset the board when the port is closed. The attachment I show does not use the SimpleIDE console, it uses Run F10 only.
    Rsadeika wrote: »
    What I am trying to do is, using a variation of PropCS, use a pthread that would have a serdevice in it, to keep a port open for access through an RS-232 break-out board that I have attached. Since I was not having any luck doing it using pins 31,30, I thought I would give the RS-232 idea a try.

    If you have a DB9 or other RS232 compatible port on your PC and the breakout board is properly connected it should work. Just remember that minimum 3 wires are required. Your RS232 adapter may have other power requirements.


    @Ted, the code I mentioned is the current library documented and generic way of doing things that Ray wants to do. This does of course require the stdio like driver system which automatically adds to the code.

    I guess what I should be taking from the discussion is that while it's nice to have that standard or even IOCTL manipulation, we don't really have that much memory to spare. Maybe you can help rethink this toward a general but smaller approach. It doesn't seem like redefining the console pin numbers helps Rsadeika's goal.

    BTW, I've been under the weather this last week and not able to go anywhere including the USPS.
    990 x 730 - 182K
  • RsadeikaRsadeika Posts: 2,570
    edited April 2012 Vote Up0Vote Down
    I got the serdevice to work, hardware problems, what a headache. Now I am trying to get the serdevice to work in a thread. The pthread gets started, but for some reason it does not want to fprintf() anything to the designated port. Maybe I am going at this in the wrong way?

    Ray
    /*
     * @file PropCS_C3_sa.c
     * This is the main PropCS_C3_sa program start point.
     */
    #include <stdio.h>
    #include <propeller.h>
    #include <unistd.h>
    //#include <stdlib.h>
    //#include <string.h>
    //#include <time.h>
    //#include <sys/rtc.h>
    //#include <cog.h>
    //#include <ctype.h>
    //#include <sys/stat.h>
    //#include <dirent.h>
    //#include <sys/sd.h>
    #include <pthread.h>
    
    
    
    void *user_1(void *argv)
    {
        waitcnt(CNT + CLKFREQ);
        fprintf(stdout, "This is the pthread\n");
        pthread_set_affinity_thiscog_np();
    
    /*                                                         Rx  Tx */
        char *serdevice = "SSER:115200, 27, 26";
        FILE *port = fopen(serdevice, "r+");
    
        setbuf(port,0);
        fprintf(port,"Port terminal screen.");  /* This does not show in the port terminal. */
        fprintf(stdout,"You are still in the pthread.\n");
    
    //    waitcnt(CNT + CLKFREQ);
      
    //    fprintf(port,"This is the PropCS program!\n");
    //    fprintf(stdout,"Type 'help' for Commands list.\n");
    //    fprintf(port,">");
    
        while(1)
        {
           
    //        int byte = fgetc(port);
     //       fprintf( port,"%c", byte);
    //        if (byte == 13)
    //        {
    //            fprintf(port,"\n");
    //            fprintf(port,">");
                usleep(5000);
     //       }
        }
    //    fclose(port);
    }
    
    
    /*
     * Main program function.
     */
    int main(int argc, char* argv[])
    {
    //    char *serdevice = "SSER:115200, 27, 26";
    //    FILE *port = fopen(serdevice, "r+");
    
    //    setbuf(port,0);
        pthread_t thr;
    
        pthread_create(&thr, NULL, user_1, NULL);
    
       
        waitcnt(CNT + CLKFREQ);
        printf("this is the main\n");   
        while(1)
        {
            usleep(5000);
        }
        return 0;
    }
    
    
  • jazzedjazzed Posts: 11,767
    edited April 2012 Vote Up0Vote Down
    Rsadeika wrote: »
    I got the serdevice to work, hardware problems, what a headache. Now I am trying to get the serdevice to work in a thread. The pthread gets started, but for some reason it does not want to fprintf() anything to the designated port. Maybe I am going at this in the wrong way?

    You can not use waitcnt(...) with pthreads. You must use usleep(...) or equivalent.
    Also, you can not use __simple_printf with pthreads.

    A simpler and better approach would be to use _start_cog_thread (we need a simple version of that).

    Here's an example of using _start_cog_thread blinking leds.
    /*
     * blinkcogs.c
     *
     * Make all propeller cogs blink assigned pins at exactly the
     * same rate and time to demonstrate the precision of the
     * _start_cog_thread method. This program and method uses
     * 8 LMM C program COG "threads" of execution simultaneously.
     *
     * This program should be compiled with the LMM memory model.
     * A printf can be enabled for showing COGs started by changing DPRINTF.
     */
    
    
    /* To enable printf change this to #define DPRINTF 1 */
    #define DPRINTF 0
    
    
    #include <stdio.h>
    #include <propeller.h>
    
    
    #if DPRINTF
    #define printf __simple_printf
    #else
    #define printf
    #endif
    
    
    #define COGS 8
    #define STACK_SIZE 16
    
    
    static int cog_stack[STACK_SIZE][8];
    static _thread_state_t thread_data;
    
    
    volatile unsigned int wait_time;
    volatile unsigned int startcnt;
    volatile unsigned int pins;
    volatile int syncstart;
    
    
    /*
     * Set a pin high without affecting other pins.
     * param WCpin = pin number to set high.
     */
    void high(int WCpin)
    {
        unsigned int bits = 1 << WCpin;
        DIRA |= bits;
        OUTA |= bits;
    }
    
    
    /*
     * Set a pin low without affecting other pins.
     * param WCpin = pin number to set low.
     */
    void low(int WCpin)
    {
        unsigned int mask = 1 << WCpin;
        DIRA |= mask;
        OUTA &= ~mask;
    }
    
    
    /*
     * toggle thread function gets started in an LMM COG.
     * param arg = pin number to toggle
     */
    void do_toggle(void *arg)
    {
        int pin = (int) arg;
        unsigned int nextcnt;
    
    
        while(syncstart == 0) ; // wait for start signal from main cog
        
        nextcnt = wait_time + startcnt;
        while(1)
        {
            high(pin);
            nextcnt = waitcnt2(nextcnt, wait_time);
            low(pin);
            nextcnt = waitcnt2(nextcnt, wait_time);
        }
    }
    
    
    int main (int argc, char* argv[])
    {
        int n;
        int cog;
        int pin[] = { 16, 17, 18, 19, 20, 21, 22, 23 };
        unsigned int nextcnt;
    
    
        wait_time = CLKFREQ/20;
    
    
        syncstart = 0;
    
    
        for(n = 1; n < COGS; n++) {
            cog = _start_cog_thread(cog_stack[n] + STACK_SIZE, do_toggle, (void*)pin[n], &thread_data);
            printf("Toggle COG %d Started\n", cog);
        }
    
    
        startcnt = CNT;
        syncstart = 1;
        nextcnt = wait_time + startcnt;
        while(1)
        {
            high(pin[0]);
            nextcnt = waitcnt2(nextcnt, wait_time);
            low(pin[0]);
            nextcnt = waitcnt2(nextcnt, wait_time);
        }
        return 0;
    }
    
    
    
Sign In or Register to comment.