Shop OBEX P1 Docs P2 Docs Learn Events
Any examples of using objects from the OBEX? — Parallax Forums

Any examples of using objects from the OBEX?

blittledblittled Posts: 681
edited 2011-11-15 13:21 in Propeller 1
I have noticed that the PropGCC has an example using TV Text but I found it a bit vague and was hoping there are other examples I can look at to get familiar with _Driver.H and how to implement the hooks into stdio.

While I'm on this I was wondering about the px.bat file mentioned in tv_text.h. Does this need to be used on any OBEX object? If so where is it located?

Comments

  • jazzedjazzed Posts: 11,803
    edited 2011-11-15 08:48
    The TV Text demo is very old. It's one of the first C demos produced after ImageCraft released their compiler. The script px.bat has been replaced. Today with Brad's blessing we use BSTC to do the same job. There is a better example of using BSTC in propgcc/demos/toggle/pasm_toggle.

    You should be able to take any SPIN demo "driver" from OBEX and convert it to a C program assuming the SPIN/PASM interface is "clean" - that is not interdependent. The Mems Accelerator, Mouse, TV, and VGA demos are all clean as far as I can tell. Some examples where the interface is not clean are the Graphics demo and Keyboard demo.

    Configuring the standard library driver interface is described here: http://propgcc.googlecode.com/hg/doc/Library.html#drivers
    DISCLAIMER:
    I'm trying to provide as much detail as I can below so that folks who care to understand it have a starting point. It is not difficult. It just takes some "close reading" and may not fit everyone's idea of a good explanation. Please post questions if you have any.

    Most likely I will move this to a propgcc googlecode page and clean it up there. In the end Parallax will take all these explanations and put them in to a manual with their own special sauce.

    Using a driver is very simple. Writing a driver is a matter of providing functions like open, read, write, and close.

    One simple example for using a driver is in the xbasic demo.

    I decided to use the full duplex serial driver for xbasic so I could use copy/paste for a demo. The stock 1 COG LMM serial IO has limits because input must be polled - it works fine if you don't do copy/paste.

    To add the full duplex serial driver to xbasic all I had to do was paste this snippet in the main file:
    /* list of drivers we can use */
    #include <sys/driver.h> // more
    extern _Driver _FullDuplexSerialDriver;
    _Driver *_driverlist[] = {
        &_FullDuplexSerialDriver,
    };
    

    Nothing to it. It works because GCC has the ability to overload features. That's just one of several GCC super powers.

    In this case, the first and only location in the list is the full duplex serial driver. The first location is reserved for the console and is the reason you don't have to define special printf functions. If you want smaller printf functions that do not use floating point, etc... you can add the " -D__simple_printf " flag when compiling with the standard library.
    Some day in the grand plan, we will be able to offer a GUI that will allow customers to select the devices they want to use and just plug them in. Today that is not yet possible of course, but the drivers we are adding today are the drivers that will be used when the GUI is available.

    Writing a driver takes more work.

    That process has not been fully explained, but the TV Text demo has example code. As you say, it is vague.

    For a standard library driver, you need to define the functions, and add the functions to a _Driver array.

    One of the simplest possible examples of a driver is the null driver. The null driver is used in some cases where you don't want anything to happen - it is often used in Linux output redirection if you don't want some program to print stuff.
    /*
     * null device driver (like /dev/null)
     *
     * Copyright (c) Parallax Inc. 2011
     * MIT Licensed (see end of file)
     */
    #include <stdio.h>
    #include <time.h>
    #include <cog.h>
    #include <sys/driver.h>
    #include <errno.h>
    
    int
    _null_write(FILE *fp, unsigned char *buf, int size)
    {
      return size;
    }
    
    int
    _null_read(FILE *fp, unsigned char *buf, int size)
    {
      return 0;
    }
    
    int
    _null_fopen(FILE *fp, const char *str, const char *mode)
    {
      return 0;
    }
    
    const char _NullPrefix[] = "NUL:";
    
    _Driver _NullDriver =
      {
        _NullPrefix,
        _null_fopen,
        NULL,
        _null_read,
        _null_write,
        NULL,
        NULL,
        NULL,
        NULL
      };
    
    /*
    +--------------------------------------------------------------------
    ¦  TERMS OF USE: MIT License
    +--------------------------------------------------------------------
    Permission is hereby granted, free of charge, to any person obtaining
    a copy of this software and associated documentation files
    (the "Software"), to deal in the Software without restriction,
    including without limitation the rights to use, copy, modify, merge,
    publish, distribute, sublicense, and/or sell copies of the Software,
    and to permit persons to whom the Software is furnished to do so,
    subject to the following conditions:
    
    
    The above copyright notice and this permission notice shall be
    included in all copies or substantial portions of the Software.
    
    
    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
    EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
    IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
    TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
    SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
    +------------------------------------------------------------------
    */
    

    The null driver does nothing, that's why it's so simple. But it does illustrate the basics of a driver. It provides functions and glues them together in the _Driver array. Here's a brief example where all driver functions are illustrated.
    // Define the driver list
    _Driver _ExampleDriver =
    {
        Example_prefix, // prefix is the driver name -defined as char *some_prefix = "EXAMPLE:";
        Example_fopen,  // setup the driver, maybe start a COG, maybe create a file on a disk
        Example_fclose, // release driver resources
        Example_read,   // the input function ... read from a sensor?
        Example_write,  // the output function ... write to a port?
        Example_fseek,  // typically used for disk drives
        Example_remove, // typically used to remove a file on a disk
    };
    

    The driver functions do anything you want them to do. The _Driver array tells the program what function code to use for the driver to call when you say "fopen", etc... In the case of the full duplex serial driver shown in the xbasic example, any print output translates to the write function automatically because that's what the first _Driver slot is for.

    In the TV example, these functions are defined:
    const char TvPrefix[] = "TV:";
    Tv_fopen(FILE *fp, const char *str, const char *mode)
    Tv_fclose(FILE *fp)
    Tv_write(FILE *fp, unsigned char *buf, int count)
    

    The _Driver array looks like this:
    _Driver TvDriver =
    {
        TvPrefix,
        Tv_fopen,
        Tv_fclose,
        _null_read,
        Tv_write,
        NULL,  /* seek; not applicable */
        NULL,  /* remove; not applicable */
    };
    

    The c3file demo also uses a generic driver. The source code is not in one of our test distributions, but it follows the same idea.
  • blittledblittled Posts: 681
    edited 2011-11-15 09:22
    @Jazzed, thank you for the detailed information. I was thinking of some of the things I want to do and I know that it would be easier to use "drivers" from the obex. This will give me what I need to do that.
  • potatoheadpotatohead Posts: 10,261
    edited 2011-11-15 10:50
    Seconded. Great discussion.

    +1 for "GCC Super Powers" :)

    Jazzed, can I get a few words on what "clean" means? Right now, I'm authoring some PASM, and have some targeted for a project, and am unsure about "clean". Best sort it right now.
  • jazzedjazzed Posts: 11,803
    edited 2011-11-15 11:39
    potatohead wrote: »
    Jazzed, can I get a few words on what "clean" means? Right now, I'm authoring some PASM, and have some targeted for a project, and am unsure about "clean". Best sort it right now.

    @potatohead,

    Basically it means that we should use mail-boxes for sharing information between the host language and the PASM driver. The PASM should stand alone independent of symbols or functions outside of the DAT block.

    For example, the Graphics driver references fontptr which is a long in the PASM, but is given a value in the SPIN start method. In this case, I had to rewrite the Graphics initialization for use with C. I have seen lots of inter-dependencies with SPIN/PASM drivers.

    Now, if the same driver was rewritten in GAS, you could share variables between GAS and C. The demos/toggle/gas_toggle program shows how to do this.

    However, in some ways, I would like to see no inter-dependencies in any ASM at all. That would allow loading PASM code as needed which could save HUB memory space. We do that quite a bit in the loader.

    There are probably other things we've learned as a group about this subject that would apply.

    Thanks,
    --Steve
  • potatoheadpotatohead Posts: 10,261
    edited 2011-11-15 12:01
    Let's say I've a driver that has a block of longs used to communicate. Two methods, apart from the mailbox:

    Method 1:

    Cogstart with block address passed via PAR, longs are read or written to in a periodic way to change modes, or communicate a state.

    Method 2:

    Cogstart with block address in PAR, arguments are dropped into the block of longs, then one in particular is written, triggering some action in that COG, that when complete, results in that same command long being written to again to signal done/ready.

    Both are basically the same mechanics.

    Are you saying GAS has to assemble those to make use of them? Or...?

    **I'll look up the font pointer example. I think you mean "poke it in", where the cog image is modified prior to cog start. If so, agreed. That one isn't good. But do the means above work reasonably well?
  • ersmithersmith Posts: 6,100
    edited 2011-11-15 13:15
    potatohead wrote: »
    Let's say I've a driver that has a block of longs used to communicate. Two methods, apart from the mailbox:
    I think both of your methods amount to a mailbox, since they're passing a pointer to a block of memory in PAR (I believe that's exactly what Steve meant by "mailbox"). They'll work fine.

    To explicitly refer to an external symbol (without passing it in PAR) you need to rely on GAS, which can export the symbol reference in a form the linker can resolve. The gas toggle example shows how to do this.

    Eric
  • potatoheadpotatohead Posts: 10,261
    edited 2011-11-15 13:21
    Great. It appears I overly complicated what is meant by "mailbox". I'll carry on with the PASM then.
Sign In or Register to comment.