Shop OBEX P1 Docs P2 Docs Learn Events
My Ducati 996 project and problems encountered — Parallax Forums

My Ducati 996 project and problems encountered

TheDarkWraithTheDarkWraith Posts: 7
edited 2014-06-11 10:49 in Propeller 1
First a little background on my project:
I reverse engineered a Ducati 996 1.6M ECU (hardware and software) so that I could add extra 'stuff' to it. One of the new features I added to the ECU's code was a high speed data logger. This high speed data logger, when enabled, spits out 60 bytes every 20ms over the ECU's serial line. In those 60 bytes is all the relevant engine info (RPM, throttle position, fuel pulse widths, temps, some flags, spark advance, etc.). I added two wideband O2 sensors to the bike (one in the front cylinder's exhaust and 1 in the rear cylinder's exhaust) so that I can tune the fuel and spark maps in real time. I currently have an Innovate SSI-4 mounted on the bike with 2 of the channels connected to 2 X Innovate LC-2s (controllers for the wideband O2 sensors). The LC-2's control the wideband O2 sensors and put out a voltage that relates to the current AFR (lambda) the sensor 'sees'. This voltage is encoded in the SSI-4. The SSI-4 accepts and passes serial data using Innovate's serial protocol. I plan on adding a wireless headunit to the bike that will display all the relevant engine info in real-time including the AFR from both wideband O2 sensors from the serial data out of the SSI-4. The reason I add the 60 bytes of ECU data to the serial data packet from the SSI-4 is so that it's logged on the SD card by the Pocket Logger (and is viewable using their software). No sense in re-inventing the wheel!

MTS chain: This is the chain of objects connected in series for the serial data (following Innovate's serial protocol).

Proposed MTS chain:

Wideband O2--->Innovate LC-2--->|
.......................................................|--->Innovate SSI-4--->Propeller based wireless transmitter--->Innovate Pocket Logger (logs serial data to SD card so data can be viewed using Innovate's LogWorks software)
Wideband O2--->Innovate LC-2--->|

Propeller based wireless transmitter:
Responsible for starting/stopping ECU's high speed data logger (and other features I added to the ECU's code and PCB). Accepts the 60 bytes of data sent from the ECU. Added to the MTS chain between SSI-4 and the Pocket Logger. It accepts the serial data packet of the SSI-4 and extracts the AFR data for both LC-2s from that serial data packet, adds the 60 bytes of ECU data to that serial data packet (following Innovate's serial protocol), and sends the revised data packet to the next MTS chain object. It also transmits the 60 bytes of ECU data and the extracted AFR data to a wireless head unit mounted at the front of the bike using an XBee.

Thus I have an object that needs 3 serial ports. One accepts the data from the SSI-4, modifies it, and transmits the modified data to the next MTS chain object. Another transmits/receives data to/from the ECU. Another transmits/receives data to/from the XBee (communicates with headunit).

My spin objects for the propeller's code:
top level object code
4 channel serial port driver
XBee driver
MTS serial data driver
Parallax LCD driver (used for debugging currently)

What I am trying to do, without much success, is pass a pointer to the 4 channel serial port driver to the XBee driver, MTS serial data driver, and the Parallax LCD driver.
I want to define the 4 channel serial port driver in my top level object and since it can handle 4 serial ports pass it to other objects that need a serial port. Thus only one object is needed for all serial ports and one object controls all the serial ports.
I don't want to create other instances of the 4 channel serial port driver in every object that needs it (which, from what I understand, happens when you declare it in an OBJ block). My understanding of the OBJ block is multiple objects will share the same code but have different variables. I want them all to use the same variables. How do I do this?

I could combine everything into the top level object but that defeats OOP and is messy.

Comments

  • Mike GMike G Posts: 2,702
    edited 2014-06-10 11:28
    I'm sure someone will come along and present a more elegant solution...
    I could combine everything into the top level object but that defeats OOP and is messy.
    This kind of issue existing even in the PC world which is solved with delegates or events. When data is a available in the serial port an event is fired. The event handler processes the serial data.

    You can do the same kind of thing with the prop. Use a traffic cop to manage the serial data. When serial data is available, a subscriber object is notified by the traffic cop. The subscriber processes the data and sends an acknowledgement. Subscribers send data by buffering the data and notifying the traffic cop that data is ready for destination port X at memory location @.
  • TheDarkWraithTheDarkWraith Posts: 7
    edited 2014-06-10 11:50
    Yes I'm very familiar with delegates and events in the PC world. I usually write code in assembler, C++, or C# for the PC world. I'm just now learning this Propeller because it's a perfect fit for my project. Very familiar with the SX and Basic Stamps also. An SX will be running my head unit.

    I figured out a way to do what I was wanting to do. In the 4 port serial driver I moved all the variables from the VAR block to the DAT block. My testing showed that items declared in the DAT block persist when new instances of the object are created whereas items declared in the VAR block do not persist (they are reset - new instances of the variables). Thus even though I define 'new' instances of the 4 port serial driver in every object they are all the 'same' 4 port serial object. The top level object does all the initialization on the 4 port serial driver (calls Init) and defines all the ports. Then when the top level object creates the LCD driver, the XBee driver, etc. it passes the port # each one is supposed to use when it calls the Init method of each one. Then when the LCD driver, XBee driver, etc. needs to send/receive data it calls the methods in the 4 port serial driver passing it's assigned port # also.

    Variables/items declared in DAT blocks are analogous to static variables in a C# class
    If all your variables are declared in the DAT block (nothing defined in the VAR block) then the methods of the object are analogous to static methods in a C# class
  • TheDarkWraithTheDarkWraith Posts: 7
    edited 2014-06-10 13:22
    I see my next problem now. There doesn't appear to be a way to synchronize method access like you can in C# (multi-threading - in this case would be multi-cogging). There are functions in the 4 port serial driver that have local variables and it appears those local variables are static local variables (if an object calls one of these functions and it assigns a local variable some value and then another object calls the same function the local variable is overwritten). I think the easy solution to this is convert the local variables to object variables (VAR block) unless anyone knows how to synchronize method access?

    Also I changed how the top level object initializes all the serial ports. I had the top level object doing all the initialization including setting up all the serial ports needed. Now the top level object initializes it's serial port needed (LCD driver for debugging) and then when it calls the init method of the XBee driver, MTS driver, etc. it passes the constants defined for them so that those objects can each call the method in their 4 port serial driver (same as top level object's) to initialize their serial port needed. This proves my theory in the post above and also makes the defining of everything related to serial ports in the top level object.
  • Mike GMike G Posts: 2,702
    edited 2014-06-10 14:58
    Or you could change your way of thinking per post #2.
  • David CarrierDavid Carrier Posts: 294
    edited 2014-06-10 17:16
    The 4 channel serial port driver most likely has an assembly language program with a Spin wrapper that communicates through one or more buffers. If the wrapper places the buffer in a DAT section, every object calling it will use the same buffer pointers, so you can open the serial ports from one object and send data from others. If the wrapper buffers the data in a VAR section, then each object that calls the serial port object will create a new set of buffers and they will not be able to share serial ports. You may be able to change VAR sections to DAT sections, in the serial port object, to get it working.

    — David Carrier
    Parallax Inc.
  • max72max72 Posts: 1,155
    edited 2014-06-10 23:48
    I use the multiple seria port this way:
    In the main object
      uarts.Init
      uarts.AddPort(3,10,9,-1,-1,-1,0,115200)      ' LCD
      uart.addport(1,4,5,-1,-1,0,%10,uart#BAUD9600) ' GPS
      uart.addport(0,17,16,-1,-1,0,%10,uart#BAUD9600) ' Xbee
      
      uarts.Start                                           'Start the ports
    
    

    then from the gps object i define the same uarts obj, and read from there like that:
    Rx := uart.rx(1)
    

    You can (should) pass the port number at the object startup in place of hardcoding it like I did, but it works smoothly..
    Remember in the 4 ports serial object you can define the individual buffers.

    Massimo
  • msrobotsmsrobots Posts: 3,709
    edited 2014-06-11 02:03
    Hi Dark One.

    Welcome to the forum!

    Reading your Posts I see that you already dug deep into those Manuals. And you are mostly right with your deductions. Keep on, this is a wonderful chip and spin is able to expose it very nice. You just have to relax and try to understand the simplicity of it.

    You are right with VAR and DAT of Objects. DAT is shared between instances (like static) and VAR is unique to each instance. As you stated in post #3 just ONE instance should call the Init and start the PASM driver of 4portserial. Lets say the main program (COG0 running the spin interpreter).
    I see my next problem now. There doesn't appear to be a way to synchronize method access like you can in C# (multi-threading - in this case would be multi-cogging)... There are functions in the 4 port serial driver that have local variables and it appears those local variables are static local variables (if an object calls one of these functions and it assigns a local variable some value and then another object calls the same function the local variable is overwritten). I think the easy solution to this is convert the local variables to object variables (VAR block) unless anyone knows how to synchronize method access?...

    Local var's inside of methods are created on the stack. So there is no conflict running the same code in different COGS, each providing its own stack. If you need to change GLOBAL (static?) variables (DAT) while running multiple cogs with the same code, you are usually fine as long as you read or write a single byte, word or long in HUB memory. These actions are atomic.

    As you said this is not multi-threading, it is multi-cogging. It is not semi-parallel (as in threads), it is really parallel. But access for HUB memory is given in a round-robin way one cog after the other. So HUB memory access is atomic if byte, word or long.

    If you need more control, you can use LOCKS.
    Also I changed how the top level object initializes all the serial ports. I had the top level object doing all the initialization including setting up all the serial ports needed. Now the top level object initializes it's serial port needed (LCD driver for debugging) and then when it calls the init method of the XBee driver, MTS driver, etc. it passes the constants defined for them so that those objects can each call the method in their 4 port serial driver (same as top level object's) to initialize their serial port needed. This proves my theory in the post above and also makes the defining of everything related to serial ports in the top level object.

    This is overkill in my opinion. Just init, add ports and start in main cog and pass the port# to your sub-objects. Its all they need.

    so::

    Even if you know this already, I will state some things:

    - there is no relationship between sub-objects and COGS. Some need a cog to run PASM. But not all.
    - objects in SPIN are not comparable to objects in 4.gen languages. Better compare them to Libraries, not objects as in Object Orientated. It's just a micro-controller....
    - you need to decouple your mind from C# to micro-controller. As sad as It sounds. A small world, but predictable...

    Enjoy!

    Mike
  • Duane DegnDuane Degn Posts: 10,588
    edited 2014-06-11 09:21
    While I generally try to keep all serial communications limited to a single cog, while I'm initially debugging a program I will occasionally use my version of a serial object which keeps the buffers in the DAT section so multiple instances of the driver use the same buffers.

    As Mike suggested, I use locks to keep the various cogs from stomping on each other.

    I add additional methods to the serial driver with postfixes added to the method names. "s" is used to indicate the "start" of a communication and these "s" methods set the lock. Methods with the postfix of "e", end the communication and clear the lock.

    The method "DebugCog" is the method all the "s" methods call to set the lock. Once the lock is set, this method makes sure the new communication starts on a new line and adds an identifier to indicate which cog is sending the message.
    PUB DebugCog'' display cog ID at the beginning of debug statements
    
    
      repeat until not lockset(debugLock)
      tx(13)
      dec(cogid)
      tx(":")
      tx(32)
      
    

    Here are some example methods.
    PUB rx : rxbyte
    
      '' Receives byte (may wait for byte)
      '' rxbyte returns $00..$FF
    
    
      repeat while (rxbyte := rxcheck) < 0
    
    
    PUB rxs : rxbyte
    
    
      strs(string("(rx)"))
      rx
    
    
    PUB rxe : rxbyte
    
    
      rx
      lockclr(debugLock)
      
    PUB rxse : rxbyte
    
    
      rxs
      lockclr(debugLock)
      
    PUB str(stringptr)
      
      '' Send zero terminated string that starts at the stringptr memory address
    
    
      repeat strsize(stringptr)
        tx(byte[stringptr++])
    
    
    PUB strs(stringptr)
    
    
      DebugCog
      if byte[stringptr] == 13
        stringptr++
      if strsize(stringptr) 
        str(stringptr)
    
    
    PUB stre(stringptr)
      
      str(stringptr)
      lockclr(debugLock)
      
    PUB strse(stringptr)
    
    
      strs(stringptr)
      lockclr(debugLock)
    

    Since the "DebugCog" method adds a carriage return to the start of the message, the method "strs" checks to make sure the message doesn't already start with a carriage return. Methods ending with "se" as "strse" both set the lock and clear the lock.

    Again, I generally only use this object while debugging my initial code. Once the code is working, I keep all the serial communication limited to a single cog with the other cogs setting flags to indicate when a message should be sent.
  • TheDarkWraithTheDarkWraith Posts: 7
    edited 2014-06-11 10:02
    Duane Degn wrote: »
    As Mike suggested, I use locks to keep the various cogs from stomping on each other.

    I add additional methods to the serial driver with postfixes added to the method names. "s" is used to indicate the "start" of a communication and these "s" methods set the lock. Methods with the postfix of "e", end the communication and clear the lock.

    Excellent idea. Thanks!


    Maybe someone can shed some light on this that I don't fully understand:
    The binary image of all objects/code has to reside in 32Kb space (main RAM). I get that. Let's say I write a simple object that just toggles a pin repeatedly and this is the only object in the project. I compile it and send it to the Propeller's RAM. It uses very little RAM so there is lots of RAM left over. Cog 0 is started to run my simple object - spin interpreter is loaded into Cog 0 which then fetches byte codes from main RAM to run (since my simple object was written in Spin). A couple of questions on this:
    - Where is the stack located for my simple object? I never defined it. How do I access it? Why are there no PUSH or POP instructions to place and remove items to/from the stack (x86 assembly is my favorite language)?
    - What is the size of this spin interpreter loaded into a cog? If it's not the size of the cog's memory (512) then could one think of the memory left over in the cog as free?
    - Since the Spin interpreter is fetching byte codes from main RAM to run isn't it susceptible to the same HUB access limitations (the whole round robin thing)?
    - The BYTE, WORD, LONG, BYTEFILL, BYTEMOVE, etc. commands for reading/writing memory access main RAM or cog RAM? If main RAM then how do I access cog RAM?
    - How do I know where the free space starts in main RAM and cog RAM?
    - How do I copy RAM from main RAM to cog RAM (and vice-versa)?
    - How do I access the internal registers of the micro (think x86 AX, EAX, BX, SP, ESP, etc.) and what are their names?

    The Propeller manual is not very specific on these items (at least to me). I've been doing many experiments just to 'learn' this thing and how to exploit it. As I don't fully understand the hardware I can't exploit it or make it do what I want it to do.

    Where is the manual for the compiler used? The very first rule of reverse engineering software is know thy compiler used. The compiler will tell you many things about the hardware.
  • Mike GMike G Posts: 2,702
    edited 2014-06-11 10:49
    Where is the stack located for my simple object?
    The remaining HUB RAM
    The BYTE, WORD, LONG, BYTEFILL, BYTEMOVE, etc. commands for reading/writing memory access main RAM or cog RAM? If main RAM then how do I access cog RAM?
    Think of a COG as a processor. The COG is loaded from HUB RAM using a command like cognew. You don't really access COG RAM. COG RAM is loaded with a program/service kinda' like a TSR.
    How do I know where the free space starts in main RAM and cog RAM?
    Again a COG (little processor) executes code. Please see the Propeller Manual and Architecture.
    How do I copy RAM from main RAM to cog RAM (and vice-versa)
    This information is covered in the manual and the commands depends on if you are writing in SPIN or PASM . COGNEW copies code-to-run to a COG. If PASM is copied to a COG then wrlong is used to write a LONG from the COG to the HUB. If we're talking SPIN then BYTEMOVE or any SPIN command. There's a lot more info - see the manual. Then you should be able to ask a more specific question.

    More Info:
    http://forums.parallax.com/showthread.php/111166-Propeller-Resource-Index
    How do I access the internal registers of the micro
    This type of info is in the PASM section of the manual.
Sign In or Register to comment.