Shop OBEX P1 Docs P2 Docs Learn Events
Clean slate with Demoboard - Page 2 — Parallax Forums

Clean slate with Demoboard

2

Comments

  • jazzedjazzed Posts: 11,803
    edited 2012-02-22 11:48
    Said parameters being either the pin numbers or baud rate. In Ray's case, he's trying to change the baud rate. Isn't freopen more appropriate than fopen in this case?

    At some level, all else being equal, such a decision is a matter of style. I'm open to different perspectives of course.
  • Dave HeinDave Hein Posts: 6,347
    edited 2012-02-22 13:13
    Rsadeika wrote: »
    I think I just solved my problem with SSER thing. This is what I had in the program:
    FILE *port = fopen(serdevice, "+r");
    This line compiled without any errors or warnings. As soon as I changed the "+r" to "r+" then everything started to work as expected. I would of thought that the compiler would of picked up on that. This would of been a horrible thing to try to find if it was in a two or three hundred line program, and the program compiled without any complaints.
    Ray,

    Some errors can only be caught at run time. In theory, a "+r" parameter to fopen could be caught at compile time, but I don't think the compiler checks for this. What value did the fopen return. It should have returned a NULL, and you could have tested the value of port to see if it was zero. If it didn't return a zero there might be a bug in our stdio library code.

    Dave
  • denominatordenominator Posts: 242
    edited 2012-02-22 14:13
    Steve,
    jazzed wrote: »
    At some level, all else being equal, such a decision is a matter of style. I'm open to different perspectives of course.

    True, given unlimited resources. Probably true in most cases.

    Things to consider:

    Using fopen leaves the old stdin and stdout around, which on the Propeller uses an extra two of your 5 remaining file slots. This may not be a concern if you don't need the file slots, but why keep the zombie stdin and stdout around if you don't need them and they don't function properly?

    In a typical UNIX system, this would also typically leave unused buffers allocated - ostensibly this could cause trouble if you're running out of Propeller memory space. However, I just checked the code, and the PropGCC library does not allocate buffers for serial devices, so the buffer memory leak that I was worried about does not exist if you use fopen() instead of freopen(). (Note here that I mean any extra memory allocated with malloc(), not the extra two file slots in the limited file descriptor pool, which *are* wasted).

    Note this inconsistency: the default stdin uses a static 80 character buffer (statically allocated in init_io.c); however any fopen or freopen of a serial stream appears to use only an 8 character buffer (native to the fp). This means two things: (1) you can call ungetc on the original stdin more than you can ungetc on a reopened stdin, and (2) if you reopen, then you have just stranded an 80-character buffer that serves no further purpose. After noticing this, I'm thinking about filing an issue on it (though this is a very minor, low priority issue).

    Last, if you leave the old stdin and stdout lying around at the wrong baud rate, then printf(...) and fprintf(newFpThatStandsForStdout, ...) have different results (ditto getchar() and fgetc(newFpThatStandsForStdin). So, while using freopen instead of fopen be a stylistic, a style that reduces errors is worthy of consideration.

    Ray:

    I think opening/reopening with "r+" shouldn't be necessary, because you're not going to write on stdin; therefore, using "r" alone should be sufficient.
  • jazzedjazzed Posts: 11,803
    edited 2012-02-22 14:41
    So as long as we don't fopen stdin and stdout we're OK right?
    I did mention this constraint before in different words.

    Thanks for digging in on the buffer stuff. That's good info.
    A smaller buffer other than on stdin/stdout is a reasonable trade.

    So we only have 5 slots? Gee, so much for my 8 port terminal server plan :<

    Cheers.
    --Steve
  • RsadeikaRsadeika Posts: 3,837
    edited 2012-02-24 05:11
    In the program below, when I press 'a' I get the case 'a' statements and the case 's' statements displayed, at the same time. I was expecting just the case 'a' statements to be displayed when you pressed 'a'.

    In this particular program I used the form of 'void main(void)' which seems to run just fine, how many different forms of main() can be used in PropGCC? If 'void main(void)' works just fine, why would you want to use something like 'int main()'?

    Ray

    /*
    * fds3.c
    */
    
    #include <stdio.h>
    #include <propeller.h>
    #include "misc.h"
    
    void main(void)
    {
        int byte;
        waitMS(150);
        printf("Hello. World! I'm a Propeller programmed with PropGCC!\n");
    
        while(byte = fgetc(stdin))
        {
            switch(byte)
            {
                case 'a':
                    printf("You pressed 'a'\n");
                    high(16);
                    printf("LED 16 is ON\n");
                case 's':
                    printf("You pressed 's'\n");
                    low(16);
                    printf("LED 16 is OFF\n");
                default:
    
                    break;
            }
            
        }
    }
    
  • LeonLeon Posts: 7,620
    edited 2012-02-24 05:52
    The main function is required to return an int, indicating status, even if the function does not terminate. It's part of the C standard. Many C compilers generate a warning if it is omitted. Just use return 0.
  • RsadeikaRsadeika Posts: 3,837
    edited 2012-02-24 06:01
    I think I resolved the problem with the previous program. The proper form of the case statement is:
    case 'x':
    .
    .
    .
    break;

    You need a 'break' for every case statement. I noticed in some of the docs online 'break' does not appear in the 'case' statement, so different versions of C handle 'case' in a different manner?

    Ray
  • mindrobotsmindrobots Posts: 6,506
    edited 2012-02-24 06:04
    Ray,

    Unless you explicitly break out of a case, the switch statement continues on to the next case. The break ends the current block of code and in the case of a switch goes to the end of the switch statement. Add the break statements as indicated below.
    switch(byte)
            {
                case 'a':
                    printf("You pressed 'a'\n");
                    high(16);
                    printf("LED 16 is ON\n");
                    break;    // ADD THIS
                case 's':
                    printf("You pressed 's'\n");
                    low(16);
                    printf("LED 16 is OFF\n");
                    break;  // ADD THIS
                default:
    
                    break;
            }
    
  • mindrobotsmindrobots Posts: 6,506
    edited 2012-02-24 06:06
    Rsadeika wrote: »
    I think I resolved the problem with the previous program. The proper form of the case statement is:
    case 'x':
    .
    .
    .
    break;

    You need a 'break' for every case statement. I noticed in some of the docs online 'break' does not appear in the 'case' statement, so different versions of C handle 'case' in a different manner?

    Ray

    More a matter of the quality of the documentation than the way C handles it. I would think the switch/case/break operation goes way back into the C standard.
  • RsadeikaRsadeika Posts: 3,837
    edited 2012-02-24 12:07
    In the program below, for some odd reason, do_toogle3(), is not working. I thought we had up to eight threads that could be run?

    Ray

    /*
    * thread1.c
    */
    
    #include <stdio.h>
    #include <propeller.h>
    #include <pthread.h>
    #include <unistd.h>
    #include "misc.h"
    
    
    void *do_toggle1(void *argv)
    {
        pthread_set_affinity_thiscog_np();
    
        while(1)
        {
            high(18);
            usleep(500000);
            low(18);
            usleep(500000);
        }
    }
    
    void *do_toggle2(void *argv)
    {
        pthread_set_affinity_thiscog_np();
    
        while(1)
        {
            high(19);
            usleep(35000);
            low(19);
            usleep(35000);
        }
    }
    
    void *do_toggle3(void *argv)
    {
        int y;
        pthread_set_affinity_thiscog_np();
    
        while(1)
        {
            for (y = 20; y <= 23; y++)
            {
                high(y);
                usleep(10000);
                low(y);
                usleep(10000);
            }
            for (y = 23; y >= 20; y--)
            {
                high(y);
                usleep(10000);
                low(y);
                usleep(10000);
            }
        }
    }
    
    int main(int argc, char* argv[])
    {
        int byte;
        pthread_t thr;
        pthread_t thr1;
        pthread_t thr2;
    
        pthread_create(&thr, NULL, do_toggle1, NULL);
        pthread_create(&thr1, NULL, do_toggle2, NULL);
        pthread_create(&thr2, NULL, do_toggle3, NULL);
    
        usleep(150000);
        printf("Hello, World! I'm a Propeller programmed with PropGCC!\n");
    
        while (byte = getchar())
        {
            switch(byte)
            {
                case 'a':
                    high(16);
                    printf("LED 16 is ON.\n");
                    break;
                case 's':
                    low(16);
                    printf("LED 16 is OFF.\n");
                    break;
                case 'z':
                    high(17);
                    printf("LED 17 is ON.\n");
                    break;
                case 'x':
                    low(17);
                    printf("LED 17 is OFF.\n");
                    break; 
                default:
                    break;
            }
        }
        return 0;
    }
    
  • jazzedjazzed Posts: 11,803
    edited 2012-02-24 13:23
    Rsadeika wrote: »
    In the program below, for some odd reason, do_toogle3(), is not working. I thought we had up to eight threads that could be run?

    This is a little disturbing. Eric or someone else please check this too

    I changed the do_toggle3 first usleep(10000); to usleep(100000); and it worked. No idea why though.

    You can run as many threads as you have room for in memory. The pthreads_toggle demo runs 30 threads - one per pin.
  • ersmithersmith Posts: 6,099
    edited 2012-02-24 16:59
    Rsadeika wrote: »
    In the program below, for some odd reason, do_toogle3(), is not working. I thought we had up to eight threads that could be run?

    What do you mean by "not working"? Most of the time the LEDs will be off. That's because you turn them each on for only 10 milliseconds and then turn them off (and leave them off while you go on to the other pins). So thread 3 will do the following:
    turn pin 20 on for 10 milliseconds
    turn pin 20 off
    turn pin 21 on for 10 milliseconds
    turn pin 21 off
    etc...
    
    I doubt you'll be able to visually notice that activity -- most people's eyes are probably not that sensitive. If you hook the pins up to a scope you should see them being briefly toggled though. As Steve noted, if you change the usleep to a longer value you'll be able to see them.

    Eric
  • RsadeikaRsadeika Posts: 3,837
    edited 2012-02-25 04:14
    I got do_toggle3() working(visible) again, and I added a 'q' command. I looked at the pthread.h file and did not find any commands to kill a pthread. Below I used pthread_detach(), but that did not accomplish what I want to do. Is there a way to kill the pthreads when you want to exit the program?

    Ray

    /*
    * thread1.c
    */
    
    #include <stdio.h>
    #include <propeller.h>
    #include <pthread.h>
    #include <unistd.h>
    #include "misc.h"
    
    
    void *do_toggle1(void *argv)
    {
        pthread_set_affinity_thiscog_np();
    
        while(1)
        {
            high(18);
            usleep(500000);
            low(18);
            usleep(500000);
        }
    }
    
    void *do_toggle2(void *argv)
    {
        pthread_set_affinity_thiscog_np();
    
        while(1)
        {
            high(19);
            usleep(35000);
            low(19);
            usleep(35000);
        }
    }
    
    void *do_toggle3(void *argv)
    {
        int y;
        pthread_set_affinity_thiscog_np();
    
        while(1)
        {
            for (y = 20; y <= 23; y++)
            {
                high(y);
                usleep(87000);
                low(y);
                usleep(87000);
            }
            for (y = 23; y >= 20; y--)
            {
                high(y);
                usleep(87000);
                low(y);
                usleep(87000);
            }
        }
    }
    
    int main(int argc, char* argv[])
    {
        int byte;
        pthread_t thr;
        pthread_t thr1;
        pthread_t thr2;
    
        pthread_create(&thr, NULL, do_toggle1, NULL);
        pthread_create(&thr1, NULL, do_toggle2, NULL);
        pthread_create(&thr2, NULL, do_toggle3, NULL);
    
        usleep(150000);
        printf("Hello, World! I'm a Propeller programmed with PropGCC!\n");
    
        while (byte = getchar())
        {
            switch(byte)
            {
                case 'a':
                    high(16);
                    printf("LED 16 is ON.\n");
                    break;
                case 's':
                    low(16);
                    printf("LED 16 is OFF.\n");
                    break;
                case 'z':
                    high(17);
                    printf("LED 17 is ON.\n");
                    break;
                case 'x':
                    low(17);
                    printf("LED 17 is OFF.\n");
                    break;
                case 'q':
                    pthread_detach(&thr),
                    pthread_detach(&thr1),
                    pthread_detach(&thr2);
                    return 0; 
                default:
                    break;
            } 
        }
        return 0;
    }
    
  • RsadeikaRsadeika Posts: 3,837
    edited 2012-02-25 06:20
    In my thread1.c program I was playing around with using different variations of sleep(), and usleep(). For my msleep() I was using:
    void msleep( int ms)
    {
        usleep(ms*1000);
    }
    

    I did notice that there is a difference from usleep(), in timing, from a conversion of msleep() as it pertains to a pthread(). Same thing occurs when I use:
    void msleep(int ms)
    {
        sleep(ms/1000);
    }
    

    I thought the timing would be more equivalent, even as you did the conversions?

    The other thing that I noticed was that if you start to increase the usleep() values in do_toggle1() or do_toggle2, it has an affect on do_toogle3(), makes it non visible.

    I guess there is a lot of consideration that has to be calculated before attempting any serious pthread() usage. Also, you have to consider if you really want to do any conversions of usleep(), or sleep().


    Ray
  • RsadeikaRsadeika Posts: 3,837
    edited 2012-02-25 07:52
    This program is a variation of the previous program, I am starting a pthread() from a command. The program works almost as expected, except that when I start the pthread() both LED 18, and LED 19 are flashing, that is not expected. I am not sure what is triggering LED 19 to start flashing.

    With this program I am hoping that I can learn how to start and stop a pthread from a command. Still have not figured out how to kill a pthread().

    Ray
    /*
    * thread2.c
    */
    
    #include <stdio.h>
    #include <propeller.h>
    #include <unistd.h>
    #include <pthread.h>
    #include "misc.h"
    
    void *ptask1(void *argv)
    {
        pthread_set_affinity_thiscog_np();
    
        while(1)
        {
            high(18);
            usleep(500000);
            low(18);
            usleep(500000);
        }
    }
    
    int main(int argc, char* argv[])
    {
        int keyin;
    
        pthread_t thr;
    
        usleep(150000);
        printf("Hello, World! I'm a Propeller programmed with PropGCC!\n");
    
        while (keyin = getchar())
        {
            switch(keyin)
            {   
                case '1':
                    pthread_create(&thr, NULL, ptask1, NULL);
                    break;
                case 'q':
                    return 0;
                default:
                    break;
            }
        }
    
        return 0;
    }
    
    
  • jazzedjazzed Posts: 11,803
    edited 2012-02-25 12:02
    Ray, did the program compile without errors?

    Normally one would pthread_create a thread, let it do it's job then and then pthread_exit, then collect thread status with pthread_join. A good example is in the toggle/pthread_toggle demo. There is a cancel function defined in linux pthreads, but it is not supported in propeller GCC.

    A Good resources for pthread functions is here:
    http://www.kernel.org/doc/man-pages/online/pages/man3/pthread_create.3.html
    The example given is for a full implementation of pthreads - Propeller GCC's implementation is "pthread lite".

    Thanks,
    --Steve
  • RsadeikaRsadeika Posts: 3,837
    edited 2012-02-26 04:57
    I want to revisit this again, not sure if this was ever resolved. With the program below, I am using PST, and I am not getting anything in the PST window. The program compiles without any complaints.

    Ray

    /*
    * fds2.c
    */
    
    #include <stdio.h>
    #include <propeller.h>
    #include <unistd.h>
    
    int main()
    {
        char *serdevice = "SSER:9600,31,30";
        FILE *port = fopen(serdevice, "r");
    
        sleep(3);
        fprintf(port,"This is some texrt.\n");
    
        while(1)
        {
            fprintf(port, "Again, and again.\n");
            sleep(2);
        }
        fclose(port);
        return 0;
    }
    
  • kuronekokuroneko Posts: 3,623
    edited 2012-02-26 06:09
    You open the serial device for reading. Then you write something to it. What do you expect to happen?
  • RsadeikaRsadeika Posts: 3,837
    edited 2012-02-27 06:22
    I thought I would try something different, integration of .spin code to make a functional C program. I thought I would go with the simplest form, the assembly example from the Propeller Manual. Of course when I compile the program below, it throws up an error:
    ... (.text+0xc): undefined reference to `_start'.

    After looking at some of the examples, I am still missing the point as to how the .spin gets linked to blink16.c? In other words, what am I missing, in terms of code, to get access to 'PUB start' in the .spin program?

    Ray

    blink16.side
        blink16.c
        blink16.spin
    

    /*
    * blink16.c
    */
    #include <propeller.h>
    
    int main(void)
    {
        start();
    }
    
    

    ''blink16.spin
    
    CON
      _clkmode = xtal1 + pll16x
      _xinfreq = 5_000_000
    
      'ipin = 17  
    
    VAR
      
    
    PUB start
    
      cognew(@Toggle, 0)
      'cognew(@Toggle, ipin)
    
    DAT
            org 0
    Toggle  mov dira, Pin
            mov Time, cnt
            add Time, #9
    :loop   waitcnt Time, Delay
            xor outa, Pin
            jmp #:loop
    
    Pin  long  |< 16
    'Pin  long |< ipin
    Delay long 6_000_000
    Time  res 1
    
  • jazzedjazzed Posts: 11,803
    edited 2012-02-27 07:03
    Rsadeika wrote: »
    After looking at some of the examples, I am still missing the point as to how the .spin gets linked to blink16.c? In other words, what am I missing, in terms of code, to get access to 'PUB start' in the .spin program?

    Hi Ray,

    The IDE transforms the Spin PASM code into an object for C by running some tools.
    You'll have to provide a start function in C to start the COG. Something like this:
    /*
     * C stub function to start the PASM routine.
     * Any PAR control variable must be declared as volatile
     */
    void start(void)
    {
        extern unsigned int *binary_blink16_dat_start;
        cognew(&binary_blink16_dat_start, 0);
    }
    
    


    The key variable is: binary_blink16_dat_start. Notice that it contains your Spin program name blink16 - without .spin of course. The PASM symbol will always be binary_*_dat_start.

    What happens is that the spin compiler bstc or Roy's Spin converts blink16.spin to blink16.dat, and propgcc converts blink16.dat to a C object with the name blink16_firmware.o. Then everything gets compiled and linked together.
  • RsadeikaRsadeika Posts: 3,837
    edited 2012-02-27 07:43
    Now it works. When the compiler runs the code it creates a blink16.dat file, and the 'extern unsigned int *binary_blink16_dat_start;' makes that available for 'cognew(&binary_blink16_dat_start,0);' to run the blink16.spin program. For some reason I thought that it would be more involved than this. I guess from this point on it will get very complicated.

    Ray


    /*
    * blink16.c
    */
    #include <propeller.h>
    
    void start(void)
    {
        extern unsigned int *binary_blink16_dat_start;
        cognew(&binary_blink16_dat_start,0);
    }
    
    int main(void)
    {
        start();
    }
    
  • RsadeikaRsadeika Posts: 3,837
    edited 2012-03-01 06:13
    I just got a uSD card reader from Parallax, and I connected it on the Demoboard, does anybody have a functional program that will run on the Demoboard. I can not find Dave Hine SD program anymore, but I am not sure if it was setup to be run on a Demoboard. I am not ready or able to make fsrw.spin run with a C program.

    Ray
  • Dave HeinDave Hein Posts: 6,347
    edited 2012-03-01 08:39
    You can download filetest.c and printf.c from Google code. filetest.c is located at http://code.google.com/p/propgcc/source/browse/demos/c3files/src/filetest.c?r=76c1ed9617082eb85eb10a0ec455fce2cba676c8 . Click on the "View raw file" link in the lower right of the screen and you can view it or save it. You will also need the small version of printf.c to run it as an LMM program. It is located at http://code.google.com/p/propgcc/source/browse/demos/c3files/src/printf.c?r=06438bad14449d45f2692c7b930410aa91412dc2&spec=svn06438bad14449d45f2692c7b930410aa91412dc2 .

    I provided an example of using filetest.c with the Spinneret card. Look for "#ifdef SPINNERET_CARD" in the main routine in filetest.c. Change this to "#if 1" and change the pin numbers to match the ones that you will be using. I've only tested this on the Spinneret card, so I'll be interested in seeing how it works for you with the Parallax uSD card reader on the demo board.
  • RsadeikaRsadeika Posts: 3,837
    edited 2012-03-01 09:50
    The program compiled, but when I typed in 'ls' I did not get anything. Below is what I changed it to, not sure about MISO, and MOSI. My connections are:
    DO - p0
    SCLK - p1
    DI - p2
    CS - p3
    CD - p4

    Ray

    #ifdef __PROPELLER_LMM__
    /*#ifdef SPINNERET_CARD
        // Mount file system on a Spinneret card */
    #ifdef DEMOBOARD
        buffer[0] = 0; // SD MISO PIN
        buffer[1] = 1; // SD CLK PIN
        buffer[2] = 2; // SD MOSI PIN
        buffer[3] = 3; // SD CS PIN
        LoadSDDriver(buffer);
    #else
        // Mount file system on a C3 card
        LoadSDDriver(0);
    #endif
    #endif
    
  • Dave HeinDave Hein Posts: 6,347
    edited 2012-03-01 10:07
    You would also need to define DEMOBOARD for that to work. Or you could change "#ifdef DEMOBOARD" to "#if 1".
  • RsadeikaRsadeika Posts: 3,837
    edited 2012-03-01 10:17
    Yes, that worked. I had #ifdef 1, which came back with an error. What can be done with 'CD - p4', is that a card detector? Since the card reader does not have an LED that shows that the card is in, maybe the CD should be implemented.

    Ray
  • Dave HeinDave Hein Posts: 6,347
    edited 2012-03-01 10:39
    I'm glad you got it to work. I don't think CD is a standard signal, but the documentation for the uSD card reader says it's a chip detect signal. Based on the schematic, it's probably a switch in the connector that is enabled when a card is in. You could add your own code to detect whether a card is in, such as
        if (INA & (1 < 4))
            printf("Card detected\n");
        else
            printf("No card detected\n");
    
    It may be an active low signal, which means you would have to invert the logic I used in my example.
  • RsadeikaRsadeika Posts: 3,837
    edited 2012-03-01 13:36
    The card detect code does not work as expected. The way it is below, it shows "Card Detected." when I have the card in and out. When I inverse the logic, it shows "No Card Detected." when I have the card in or out.

    I am not following the logic of "#if 1", is that some kind of designation for the DEMOBOARD? Also, having a little problem with (INA & (1 < 4)). What does that 1 refer to?

    Ray

    #ifdef __PROPELLER_LMM__
    /* Mount file system on a Parallax uSD card. */
    #if 1
        buffer[0] = 0; // DO
        buffer[1] = 1; // SCLK
        buffer[2] = 2; // DI
        buffer[3] = 3; // CS
        LoadSDDriver(buffer);
    #else
        // Mount file system on a C3 card
        LoadSDDriver(0);
    #endif
    #endif
    
    /* Let the terminal catch up. */
        sleep(1);
    
    /* Check for chip select. */
        if (INA & (1 < 4))
            printf("Card Detected.\n");
        else
            printf("No Card Detected.\n");
    
        dfs_mount();
    
    /*    printf("\n");  */
        Help();
    
    
  • Dave HeinDave Hein Posts: 6,347
    edited 2012-03-01 14:49
    Sorry, I meant (1 << 4), which would be 0x10. It would be used to mask off bit 4 in INA. (1 < 4) would produce a value of 1, which would incorrectly mask off bit 0. Sorry about that.

    #if and #ifdef are pre-processor directives that will conditionally include lines of code. #if uses a logical argument, so that "#if 1" means to include the following code up to an #else or #endif. #ifdef means to include the following staments if the argument is defined. Since DEMOBOARD is not defined the lines that set up the pin numbers and called LoadSDDriver(buffer) were not used, and LoadSDDriver(0) was used instead. This would have set the SD driver up for the C3 card.

    Note, you could have used the "#ifdef DEMOBOARD" if you did a '#define DEMOBOARD" in an earlier line. You could also define DEMOBOARD on the command line at compile time by using the -D option. At this point it's easier for you to just use "#if 1" until you are more familiar with the pre-processor.
  • RsadeikaRsadeika Posts: 3,837
    edited 2012-03-02 03:44
    I created a function for checking the state of pin 4, I am not sure why it works, but it works as expected. When I have the card in, "Card Detected.", when I have the card out, "Card Not Detected.". I guess I got lucky?

    When the program gets compiled it shows "Writing 31048 bytes to Propeller RAM.". I guess that does not leave a whole lot of RAM left to play with.

    A thought occurred to me, maybe the CheckCD() function should be placed in its own thread, that way it would always be checking the state of the card reader. But, now I am not sure if there is enough room do it, and how would I create a global variable for the state, and have that variable be read in a function like 'list' while the thread is running in the background?

    Ray

    void CheckCD()
    {
        if ((INA & (1 << 4)) == 0)
            printf("Card Detected.\n");
        else 
            printf("No Card Detected.\n");
    }
    
Sign In or Register to comment.