Dec 28

Oops. I said the FB was slow with it’s 10us time required for an IO call and that the AT90CAN128 would always be waiting for it to change states. Well, I stand corrected. 

I was testing the code with a dummy command that takes 16 bytes that the FB sends to the AT. I noticed that about 3 to 4 out of the 16 bytes were well received and the rest got lost. It also did not receive 0..3 and than stopped, not the AT received 0, 4, 8 or something like that. Stepping trough the code on both sides showed everything was working and all 16 bytes were well received, this typically means there is a timing problem (stepping is working at low speed, executing is non-working at high speed).

Monitoring /WR and the LED output using a scope revealed that when the first byte is received on the AT it takes 17us to process the falling edge on /WR. The next ones go faster but it already takes 3us from a level change to start of ISR. With an 8MHz crystal each cycle takes 0.125us, so apparently there are 24 instructions needed to push everything on stack and execute the ISR. A 16Mhz crystal would reduce everything by 2 but the 17us would than still take 8.5us. True, this is in O0 mode for the compiler, so non-optimized, but it still worries me. Later the RoboCAN interface will be running as well, there is a big chance now that bytes will be missed if one of the CAN ISR’s is executed which are even more complex than the ones for interfacing with the FOXBoard. Also the optimizer can be enabled, a small test with -O2 shows a small us improvement but nothing drastic. I also don’t like to work with the optimizer during development since it makes debugging complex.

That leaves only one option, data transfer must use a handshake method. An extra input on the FB is required for this called /ACK were 1 means the AT is ready and 0 means it is busy.

For writing from the FB to the AT this means:

  • Set the data bus to output (if not done already)
  • Set the 1’s of the command (address) and data pins
  • Set the 0’s of the command and data pins including the low level of the /WR pin
  • Wait until ACK is low
  • Raise /WR
  • Wait until ACK is high
  • Repeat this process for each data byte of the command

For reading by the FB from the AT this means:

  • Set the data bus to input (if not done already)
  • Set the 1’s of the command (address)
  • Set the 0’s of the command including the low level of the /RD pin
  • Wait until ACK is low
  • Read the data pins and store them
  • Raise /RD
  • Wait until ACK is high
  • Repeat this process for each data byte of the command

In both cases the rising flank of /WR or /RD will still trigger the interrupt in order to indicate the byte in finished.

Some testing later I have to conclude that this works perfectly. A write or read command that uses 16 bytes of data takes about 1.4ms, this is not very fast but I expect some improvements by increasing the crystal of the AT bring it to about 1.0ms.

The frequency of the AT90CAN128 can not go up to 16MHz since the voltage is 3V3, at this voltage the maximum frequency can be ((8/1.8)*(3.3-2.7))+8=10.6MHz. This is over the full temperature range, typically a 12MHz crystal should be OK, further testing at that moment.

Downloads:

Dec 23

Processing a command that writes data from the FB to the PMC is very straight forward:

  • Set the data bus to output (if not done already)
  • Set the 1’s of the command (address) and data pins
  • Set the 0’s of the command and data pins including the low level of the /WR pin
  • Repeat this process for each data byte of the command

Since the falling edge of the /WR pin on the PMC triggers an ISR and the write speed of the FB is limited to 10.8us (46kHz*2) per set or clear cycle there is no need to worry that data will be lost. The moment the ISR is triggered, the data is available on the data bus, so the ISR does not have to include any delay which will block the whole system (including the RoboCAN interface). 

Since the command is received in an ISR and ISR’s should consume the least possible amount of time, processing the command received can only be part of the ISR if it sets or clears some variables. Any action that would require (for example) communication using the RoboCAN interface should be executed outside the ISR as part of the main application. Remember that a command over the RoboCAN interface might take several milli seconds in case the interface is busy or the received does not respond.

Processing a command that reading data is by definition more complex since there is a “slow” device (the FB) that needs to copy the data from the databus.  

  • Set the data bus to input (if not done already)
  • Set the 1’s of the command (address)
  • Set the 0’s of the command including the low level of the /RD pin
  • Read the data pins and store them
  • Repeat this process for each data byte of the command

Since it takes 10.8us for the FB to read the data (if not blocked by other processes) it means the PMC is stuck in the ISR linked to the falling edge of the /RD signal. One option is to leave the data on the bus and simply exit the ISR. This can (will) however create bus conflicts the moment the FB decides to write data.

In a normal bus system the slave places data on the bus at the falling edge of /RD and removes it on the rising edge of /RD. The best solution than is to trigger the ISR on each edge and decide what to do the moment the ISR is executed. The AT90CAN128 supports this behavior, it can trigger the interrupt on any logical change.

As a result, a falling edge on /RD will place the data on the bus and exit the ISR. At the rising edge of /RD the data is removed, final question to answer is when to generate the rising edge? If it is part of the read function on the FB it will require an additional IO call at the end of the read function (reading more bits will automatically raise /RD when the 1’s of the command are set). If this is not done, than the /RD will remain low until the next read or write command. This is not prefer ed since if a write follows the /RD pins is raised but the data lines are already reversed before this raise and so creating a bus conflict.

The big question is how much time the AT90CAN128 needs to prepare the data and set the first byte of this data on the bus. If this is not finished before the FB copies the data from the data lines than we have a problem. Based on the 10.8us for an IO call on the FB I don’t for see any problem as long as (again) filling the data to return does not include any calls on the AT90CAN128 other than copying existing known data into the buffer. Also the interrupt used is the second highest available interrupt source on the AT90CAN128. The highest is for the /WR signal, since the FB can not read and write at the same time there is no difference in the priorities, if /RD is lowered (or raised) the ISR will be called directly unless it is stuck into another ISR. This will be the first thing to check if strange data is read back.

For the /WR signal it is good enough to trigger only on the falling edge, but it makes sense to trigger on any level change as well to enable of disable the communication LED. To prevent reacting on undefined states when the FOXBoard is still booting or when the SnakeBot application is not started or starting the PMC needs to ignore any change until it detects a stable state (/RD is high and /WR is high).

While implementing the first command to set a power mode on the PMC I already struggle with processing this command inside the ISR. It will require commands send to the various actuators, so the command must be buffered and executed as part of the main program and not inside the ISR. Sending a command from the PMC to the FB is not interrupt based, so only received commands need to be buffered.

Claiming an array of command structures is not memory friendly, instead a simple circular buffer will do the trick in which the command (2 bytes), the amount if data bytes (1 byte) and the data (x) bytes can be stored. Two command pointers (one points to the next to read and one points to the first free position) do the maintenance of the buffer. This buffer is called CommandBuffer and made 128 bytes long. Time will tell if this is enough. Two functions are defined; StoreCommand and GetCommand. The first is now called when a full command is received in the /RD ISR. The application can call the function CommandReceived to check if a command is available in the buffer or simple call GetCommand which will fail if no command is available.

Here is the latest version of the PMC code. Here is the latest version of the FB test program.

Dec 22

The ATDVK90CAN1 board requires a power supply of 6 to 15V, the FOXBoard requires 5V. When using two power supplies I fear problems with power leakage when one of the two is not powered up. To prevent this, I made a very simple 5V power supply as shown below based on this schematic that takes anything above 8V. Don’t forget to mount a small heat sink to the regulator to keep it nice and cool. Using this power supply both boards can be powered from my power supply using 8V, when something goes wrong a single switch will turn off all power at ones.

As mentioned, two pieces of software are required. The ATDVK90CAN board will be the Primary Motor Cortex (PMC for short), this link will download the latest version. It does not include the RoboCAN code, it you don’t have it, downloaded it here. For now, all it does is it initializes the RoboCAN interface, setup all IO pins and provide two interrupt service routines connected to a falling edge on /RD and a faling edge on /WR. Program this version in the ATDVK90CAN board before connecting it to the FB so you can be sure no IO pin conflicts will occur.

For the FB, a small test program is written that provides the first level of code to send and receive a command to the PMC. This code does not have to be compiled before the connection is made, although it does not hurt to verify if indeed you can compile it.

Connect every thing together and triple check all connections for shorts and verifying using a multimeter that each wire is indeed connected as listed in the schematic. Than switch on the power supply, the current at 8V should be around 250mA. If higher (even 280mA is higher…) than switch of the power supply because there is a problem somewhere. If it is indeed close to the 250mA it might be there is an IO conflict or both boards are not at the same ground level, if it is a lot higher than probably there is a mix somewhere in the plus and minuses of the power supply. In any case (even if the current is correct) verify each pin level with a voltage meter, they should all be 3.3V +-5% or 0V.

In AVR Studio you should be able to compile, download and execute the code step by step. Place a break point in the ISR (INT0_vect) procedure and run the code. On the FB, run the test program and step trough it in Kdbg until you reach the WriteByte command in the main function. Using F8 step into this function and when you reach the final command that clears the bits on the FB verify that the break point  in AVR Studio is indeed triggered. If so there is a good change everything is working.

Dec 21

Before designing the add-on board of the primary motor cortex I will first connect the ATDVK90CAN1 board to the FOXBoard in order to verify if the defined parallel interface will work using the selected pins. Below image shows how the interface signal are mapped over the two expension connectors J6 and J7.

On the ATDVK90CAN1 board there are also two 20 pins headers that holds most of the IO pins of the AT90CAN128. Since most pins are also used for other functions on this board it is important to first remove a lost of jumpers and solder shorts. Also the power supply is 3V or 5V while the FB is running at 3V3.

Below the insructions how to modify the ATDVK90CAN1 board:

  • Cut away the rectifier U6, this will create a virtual ground level when both the FB and the ATDVK90CAN1 board are powered using the same power supply.
  • On the backside, patch the + and the – as shown on below image. NOTE: this will set the plus of the power supply to the inner center of the power plug and the minus the the outher body of the power plug. This is OPPOSITE than the power cable that is supplied with the kit. I only do this so since all my projects use the center as the plus and prevent mistakes.
  • Solder a 68E resistor parallel to R14. As a result, the 5V is reduced to 3V3. This voltage can be measured over C19 which is the big yellow capacitor just above switch SW3.

  • Set and remove the jumpers so it matches below image. Also verify the positions of the switches.

  • Cut the below red colered solder strips on the back side of the board:

  • Solder two 2×20 header strips on the bottom side of the board in the footprint of J13 and J14. For this the rubber feets must be removed, I just moved them over a bit to other emty spaces.

For the cable assembly itself, take 1 times 20cm and 1 time 30 cm of 40 pins flatcable for example from a IDE interface cable. On one side, assemble a 40 pins header, this part will be placed on the headerstrip on the ATDVK90CAN1 board. Cut all wires loose and cut away the non-used wires based on this schematic. Please note that since J13 and J14 are soldered on the back of the PCB the pin 1 marking on the headers is no longer valid!

On the other side, strip the wires and solder them to two 2×20 headerstrips that will be inserted in the headers of the FB. I also used some shrinktube to isolate each wire, it happended too many times aready that a test setup caused many problems with shorts or open wires after a couple of days. The amount of time that than must be spend to find the problem is by far more than now used for making a good assembly.

The final assembly should look somthing like below. Do not connect it yet to the FOXBoard, first two pieces of software need to be developed: for the AT90CAN128 and for the FOXBoard.

Dec 15

Some IO pins on the FB have separate pins for input and output. For example, input 1 on port G is available on pin J6.24 while the output 1 of port G is available on pin J6.23. On most controllers all pins are bi-directional, outputs are internally fed back to the input buffer so when you set an output and read the port status it shows the current output state.

On the FB this seems to be different for certain pins, to verify if indeed there is no internal feedback line a small test setup is made. A 220E resistor is connected to J6.40 (Vdd) and to the anode (+) of a low current LED. The cathode (-) is connected to J6.23 (OG1). The LED lights up, so this means the output is low. A separate wire is connected to J6.1 (Gnd) and so far not connected on the other side. This will be uses to apply a low level to J6.24 (IG1).

Using a SSH terminal to the FB, type the command setbits -p g -b 1 -s 1, this will set bit 1 on port g to a high level and the LED will switch off. Using setbits -p g -b 1 -s 0 the low level is put back and the LED will light again.

The command readbits will display a bit pattern of all the available ports a, b and g. The most right one using 32 bits is of port g, MSB on the left, LSB on the right. The 2nd most right indicates the state of IG1, with the wire disconnected this is a high level. When inserting the wire into J6.24 and repeating the readbits command indeed the level is now 0.

The final test is simple (but almost no longer needed), set OG1 to 1 and keep the wire inserted. The LED will switch off, using the readbits command will still show IG1 is 0. Conclusion, no internal feedback so the suggested use for the interface between the FB and the primary motor cortex is possible.

Dec 14

The RoboCAN interface was developed to provide a fast serial link between the Primary motor cortexand the various sensors and actuators acting like a spinal cord. The second interface needed is between the primary motor cortex and the brains: the FOXBoard.

The initial plan was to have a memory mapped interface using available IO pins on the FB. Using an 8-bit address bus would provide 256 unique locations. Each location could indicate a specific command resulting in 256 commands, writing to a specific address could trigger that command using the 8-bit data as a parameter.  Although this looks to be enough I’m not sure it will be for two reasons:

  • Command most likely require more than one data byte
  • The 256 address locations will need to be divided over more than one add-on board

Without a good solution for above two problems, it would be better to use some sort of serial protocol were more bytes are written to a specific location.

While writing above statement, this can actually be a solution for the first problem. If indeed each memory location is a command but it requires more than one data bytes, why not write more than ones to this location and only execute the command when all bytes are received? If the amount of bytes is know for each command than there would not even be a need to include a NumberOfBytes parameter. Simply start counting bytes and when the amount required has been received than execute the command. If on the same address additional write cycles are detected, assume these are for a new command. The same principle can be used for a command that reads data from the add-on board. If two data bytes must be read, two read cycles from that location are required.

Using this solution already reduces the severity of the second reason. Now there are actually 256 individual commands to be divided over several add-on boards. Since most micro controllers that will be on an add-on board are of the AT90CAN series it would be an option to increase the number of address bits since there is plenty of IO pins on such devices.

On Port G on the FB IOG8..IOG15 provide eight consecutive bi-directional pins for the data bus. The next eight IO pins IOG16..IOG23 can be used for the address bus, these are input/output pins but than only used as output. Since from a software point of view you do not want to split the address over various bits on port G additional address pins than should use IOG24, IOG25 and so on. IOG24 is free so this increases the amount of command to 512. Pin 25 is split into I25 and O25. I25 is used for an USB interface, so it would not be wise to use this one. On the other hand we need an output and O25 is free increasing the amount of commands to 1024 which would take away my worry.

I’m not sure how this split between input and output is made, they are on different pins on the connectors (pin J7.14 and J7.13) so it seems that these are two pins on the processor were the read or write access determines which pin is used. But this worries me since it states that software defines which pin is used, so if some software block is added that uses the USB interface you can get conflicts between my software and the USB software each configuring this signal. Checking the schematic of the FB shows that the USB connectors that are soldered on the board use USB1 and USB2 so these pins are in use.

When searching for the schematic, I also found how to make use of the IO pins in software. It seems you can not directly write a value to the IO pins, there is a function to set a pin and a function to clear a pin. I guess this makes sense since there are multiple processes working on the port in parallel. A normal read-modify-write action would not work since during the modify part another process might have changed an output which is than modified back during the write part. 

// Set the lines specified by the bit mask “iomask” to 1
ioctl(fd,_IO(ETRAXGPIO_IOCTYPE,IO_SETBITS),iomask);

// Set the lines specified by the bit mask “iomask” to 0
ioctl(fd,_IO(ETRAXGPIO_IOCTYPE,IO_CLRBITS),iomask);

The IO mask defines which bits are set or cleared, a 1 execute the task, a 0 is ignored. To set “a” specific address on the port would therefor require two calls, one for setting the 0’s and one for settings the 1’s. Assume a 18 bit wide port were the lower 8 are the data bus, the next 8 are the address bus, than the /WR signal and finally the /RD signal. Writing the value 0b00001111 to address 0b00110011 would than result in:

ioctl (some command to set databus to output);

ioctl(fd,_IO(ETRAXGPIO_IOCTYPE,IO_SETBITS), 0b01 00110011 00001111);

ioctl(fd,_IO(ETRAXGPIO_IOCTYPE,IO_CLRBITS), 0b01 11001100 11110000);

Since in both cases the /WR bit is set and first the SETBITS is used this will result in a faling flank on /WR when the second IO call is executed. The receiver can use this flank by connecting it to an interrupt pin and in the ISR copy the date from the address and data part (after a slight delay) and execute the command.

A read from address 0b00110011 would result in:

ioctl (some command to set databus to input);

ioctl(fd,_IO(ETRAXGPIO_IOCTYPE,IO_SETBITS), 0b10 00110011 11111111);

ioctl(fd,_IO(ETRAXGPIO_IOCTYPE,IO_CLRBITS), 0b10 11001100 00000000);

and after waiting a bit:

value=ioctl(fd, _IO(ETRAXGPIO_IOCTYPE, IO_READBITS));

Also here the falling flank on /RD can be used for triggering an ISR to place the data on the bus and remove it after some delay or when the /RD pin become 1 again. For this it might be smart to also set the /RD in the first call for writing a command. Also setting the direction of the databus only needs to be done when the previous command was different (read versus write).

All this brings two conclusions:

  • There is no need to map the address part in an consecutive set of pins
  • It requires two to four IO calls per command

The first conclusion is perhaps not clear, reason for this is that the command will be defined as a constant value in the application (most likely). It is not needed to take an address, split it in multiple parts and using shift functions to place the bits in the right location.

So, using OG3..OG5, IOG8..IOG15 as address lines, IOG16..IOG23 as data lines, IOG0 as /RD and OG1 as /WR would provide a 2048 possible commands using 8 bit data. That should be more than enough!

That leaves the interrupt signal, since there is no true interrupt support in the normal IO driver this might require a custom device driver. All input pins can be used as a interrupt pin, so IG3 will do the trick.

OG4 and OG5 can be used for driving 2 LED’s, these always prove to be handy during development and can indicate data transfer in the final application.

Finally, it would be nice to add an SD-card slot as well. This has nothing to do with the primary motor cortex but if the application becomes big or requires lots of storage for storing collected data it is good to put this on a replaceable SD-card instead of writing the internal FLASH of the FB all the time. There is a schematic of the ZigBee add-on board that also provides an SD-card. Damn! This uses port G as well: IG1, OG1, OG3 and OG4. Exactly the same pins as for my extended address lines, /WR and /INT.

But is does seem input pins are not linked to output pins when they are separately available on the FB. The data from the SD-card is read using IG1 while the clock for the card is generated using OG1. I will do a small test later to easy my mind, but for now that leaves below listed unused pins:

  • IOG0, IG2..IG5, OG5, IOG24, OG25, OG28 and OG29

After a reset the FB will set a specific direction and state to all IO pins. To prevent direction conflicts the AT90CAN will set all IO pins to input except for the /INT output. For this reason it is best to use a dedicated input pin on the FOXBoard, for example IG2. Than the connection could be:

  • /INT on IG2
  • Data 0..7 on IOG8..IOG15
  • Address 0..9 on IOG16..IOG24, OG25
  • /RD on OG28
  • /WR on OG29
  • LED on OG5

As a result: 1024 possible commands that can use 8 bits of data, read and write, interrupt and a debug LED.

Dec 13

In order to quickly add RoboCAN functionallity to any Delphi application I developed a RoboCAN component in Delphi 2007. The code is based on the test program as developed before, but the component itself is more inline with the AT90CAN implementation.

The RoboCAN component provides (next to the name) only one property called Speed. The baudates that are supported are the same as for the CANUSB interface and can be set at design and run-time. There are five events that match the call back functions in the AT90CAN code:

  • OnRoboCANBusError
  • OnRoboCANReceiveError
  • OnRoboCANSendError
  • OnRoboCANSend
  • OnRoboCANReceive

The first three events are to report bus, receive and send errors. The OnRoboCANSend is executed when a message has succesfully been send and the OnRoboCANReceive when a message is received. Both functions hold the RoboCAN message as parameter.

In order to test the RoboCAN component an application RoboCANCompTestApp has been developed. This application does not use a RoboCAN component in design-time. Instead, at run-time a RoboCAN component is created, this allows you to debug each code line of the component.

If you want to install this component, in Delphi 2007 open the RoboCANComp.groupproj project which contains two projects; RoboCANCompTestApp.exe and RoboCANPackage.bpl. If the project manager is not open, then open it using the View | Project manager menu. Double click on the RoboCANPackage.bpl project, right mouse click on the project and select the Install menu. This will add a palette Robotics with the component TRoboCAN in it.

If you want to make modifications to the component, double click om the RoboCANCompTestApp.exe project, if you now run the application you can step through the code of the component.

The source can be downloaded here, it will always be the latest version.

Dec 10

Based on the RoboCAN specification, I implemented the protocol on the AT90CAN and in Delphi using the CAN USB interface. For the AT90CAN it is placed in a second unit that makes use of the CAN.c unit I already wrote in order to process single CAN frames.

The logic behind the code is simple. It starts with a buffer of 1024 bytes called CANBuffer, this buffer is (only) used for storing the data of incoming and outgoing messages. The only clever part of this buffer is that a call can be made to ClaimBufferSpacewith a required number of bytes. This function will find the first consequatative block of bytes in the buffer that can hold these bytes and return a the position of the first bytes in the buffer. If message A takes 10 bytes, it will return BufferPos=0, message B takes 20 bytes, it will return BufferPos=10. If message is is completed and processed, this area could be used again. If a call comes in for message C that takes 12 bytes it will not fit since message B is not processed. As a result, BufferPos = 30 will be returned. The 1024 bytes is not based on any experience. Time will tell if this is too much or too less.

The next part is an array called MessageBuffer that holds 10 records of below data:

  • unsigned char FromAddress
  • unsigned char ToAddress
  • unsigned char Flags
  • unsigned char Command
  • unsigned char NumberOfBytes
  • unsigned char BytesProcessed
  • unsigned int   BufferPosition
  • unsigned char TimeStamp
  • unsigned char MessageID

The FromAddress and ToAddress hold the device address of the sender and the receiver. Flags is a byte that holds settings of the message and can be a combination of below bits:

  • mf_CommandFrame  0x01    b0, 0=acknowledgement frame, 1=command frame
  • mf_NormalFrame      0x02     b1, 0=interrupt frame, 1=normal frame
  • mf_Transmit             0x04     b2, 0=message buffer used for read, 1=for write
  • mf_Finished              0x08     b3, 0=message being processed, 1=finished processed
  • mf_Retry                   0x30     b5/b4, holds the number of retries during send
  • mf_Reserved             0x40     b6 is reserved, should be 0
  • mf_InUse                  0x80      b7, 0=message buffer is free, 1=busy

When a message is being send, the function FindFreeMessageBufferwill scan through the array and checks if the mf_InUse flag is 0. When sending a message, the search starts at record 0 and continuous upwards until a free one is found or when it exceeds number 7. Reason for this is that the record number is use as MessageID for sending the message, this will ensure that always an unique MessageID is used. When receiving the first frame of a message the search will start at record 9 and continuos downwards until a free record has been found. Also here the 10 records are not based on any experience. Time will tell if this is too much or too less.

When a message is offered for sending a free MessageBuffer is found and all the fields in the record will be set. A call is made to ClaimBufferSpace to claim storage capacity based on NumberOfBytesand the returned BufferPos is stored in the record after which all the data to be send is copied into the CANBuffer.

The actual sending of the seperate frames is taken care of by the funtion SendRoboCANFrame. When this function is called it scans through the used records in MessageBuffer  to find the message with the highest priority. When found, it determines how much packages are already send using the BytesProcessed field of the record and it configures a single CAN frame that will be offered to the CAN interface for sending. After this the BytesProcessed field is updated and the application can continue execution. When the byte is send the CAN interface triggers an interrupt and the Interrupt Service Routine will call SendRoboCANFrameagain repeating the process of find the highest priority waiting message. If no messages are waiting this process will stop automaticly since no interrupts will be triggered anymore. For that reason the function SendRoboCANFrame must be called ones by the function that submits a message for sending.

Receiving messages works in a simular manor except that there are 4 MOB’s setup in parallel all filtering for the same priority. This is to prevent frames are lost when more devices decide to communicate in parallel with the device. The number 4 is again not based on any experience.

Any frame that is addressed to the device is execepted in any of the 4 MOB’s. When a frame is received an interrupt is triggered and the frame is read. A function RoboCANFrameReceivedis called that checks if this is a frame using PackageNumber 0. If so, a free MessageBuffer  is found, filled in and memory is claimed using ClaimBufferSpace. Possible data is directly stored in the CANBuffer and the fields BytesProcessed is updated.

If a PackageNumber is used higher than 0 the previously used MessageBuffer  is found using the FromAddress field and the MessageID. Again the data is stored after which a check is done if the fields BytesProcessed  and NumberOfBytesare the same. In such a case the full message is received and an Acknowledgement message is send usign the same function for sending a message as the application will do: SendRoboCANMessage. This will stored the message using a MessageBuffer and store the data in the CANBuffer, the interrupt based SendRoboCANFrame will than take over based on the previous explained algorithm of finding the highest priority.

Since a message was fully received a callback function ProcessRoboCANReceived that holds a structure TRoboCANDataas parameter. The definition of the function is part of the RoboCAN unit, but the actual implementation needs to be included in the host application making use of the RoboCAN protocol. The passed variable RoboCANData holds all the data of the message:

struct TRoboCANData
{
  unsigned char Interrupt;
  unsigned char Acknowledge;
  unsigned char ToAddress;
  unsigned char FromAddress;
  unsigned char Command;
  unsigned char NumberOfBytes;
  unsigned char *Data;                 
};

The function ProcessRoboCANReceived is part of the Interrupt Service Routine that received the last CAN frame. By using such a callback function there is one point in the application were all commands come in at the moment they arrived. The downside is that all other CAN processing is on hold until the function completes so processing of the command may not take much time. Time will tell if this is a smart method, it can easily be converted to a non-interrupt version. When the function is completed, the MessageBuffer is freed and the data stored in the CANBuffer is lost.

The same process is used for reporting that a message has been send and was acknowledged by the receiver. At that time a callback function ProcessRoboCANSend is called as part of the ISR that received the acknowledge frame. Also here the original message is passed as a variable so that any post processing can take place or application status can be updated. When the function completes also here the MessageBuffer is freed and the data stored in the CANBuffer is lost.

During transmission things can go wrong. If so, the RoboCAN protocol will retry maximum 4 times (if possible) before finally reporting the error. For this a timer is used (TIMER2) that is set to trigger an interrupt every 25ms. Every 25ms the TimeStamp field is increased by one for all MessageBuffer s that are in use. If at that time it reaches the value 10 (so 25ms in total) the two mf_Retry bits in the Flags field are tested to see if the number of retries exceeded 4 time. If so, the message is canceled and an error call back function is executed. Whenever a message that is in progress has been modified from (for example) receiving or sending a single CAN frame, the TimeStamp field is set to 0 to show progress has been made.

There are three error callback functions, ProcessRoboCANSendError for reporting send errors, ProcessRoboCANSendError for receiving errors and ProcessRoboCANBusError for interface errors. Main reason for three different functions is that the parameters are different. On a send error the actual message is passed back next to the error. On receive errors the message has not been received, so only the device address that was trying to send a message is passed next to the error. On interface errors there is no message or device known, so only the error is passed.

The RoboCAN units for the AT90CAN can be downloaded here and a small application that shows how to work with the callback functions. It will be replaced by every new version, so you always have the latest one. For the PC a protocol test program has been developed in Delphi 2007 to test the protocol on how it handles errors. You can use it if you like, next to sending and receiving RoboCAN based messages it can also send and receive single CAN frames (of course). The RoboCAN framework will be re-used for writing a component so RoboCAN can easily be integrated in any Delphi based PC application.

Dec 9

It has been a while since the last post. Main reason for this is that I wrote a first implementation of the RoboCAN protocol in Delphi and for the AT90CAN controller. The global idea as defined in previous posts has stayed in tact but there are a few differences so below a new overview of the protocol.

A RoboCAN message consists of below data:

  • Interrupt, boolean
  • Acknowledge, boolean
  • ToAddress, byte
  • FromAddress, byte
  • Command, byte
  • NumberOfBytes, byte
  • Data, array of bytes

If the Interrupt field is set to true this message is send with the highest possible priority over the bus. It can even interrupts messages already in progress from the device itself. Only interrupt messages of devices with a higher priority (so a lower FromAddress) can interrupt this message. Although the number of data bytes can range up to 254 bytes it is good practice to limit the number of data bytes to 6 in which case the message only takes one CAN frame to transmit.

When a message is received by the addressed device, this device has to acknowledge the message by sending a message with the Acknowledge field set to true. In the message one data byte is included that holds the MessageID (will be explained later) that was used for sending the received message. As a result an Acknowledge message is only one CAN frame in size, this message will be send with a lower priority than Interrupt frames but with a higher priority than any other message. This message can also interrupt other messages from the device itself or from other devices.

The ToAddress holds the address of the device to which the message is addressed, it can range from $00 to $FF allowing 256 devices to be connected on the CAN bus at the same time.

The FromAddress holds the address of the device itself so that the receiving device can reply with an Acknowledge message. Also this field can range from $00 to $FF.

The Command field can be defined by the application and can range from $00 to $FF. It is expected that a set of fixed commands will be defined (e.g. GetStatus or GetVersion) that each device must include in it’s application. These commands will start at $FF and decrease with every new command allowing the custom application to start at $00 and increase with every custom command. This will limit the impact if new standard commands are added over time since there is a big chance that the commands are not used yet by the application.

The NumberOfBytes fields holds the amount of data bytes that are stored in the message, this can range from 0 to 254. If more than 6 data bytes are stored in the message the RoboCAN protocol will split the message in multiple frames. Any frame after the first frame can hold 8 data bytes limiting the amount of required CAN frames to a minimum.

The actual data to be transferred is stored in the Data field, this is a consequative buffer of bytes with a maximum of 254 bytes in total.

RoboCAN is a multi-master protocol, any device can send data to any device. If a message is send the receiver must acknowledge the message within 250ms. If this is not done the full message is send again up to 4 times. If a that time there is still no acknowledge, an error is reported to the application.

A message that consists of multiple CAN frames is send in a chronological order so the receiver can store the data of each frame after each other and pass the total buffer of data to the application when all data bytes are received. However, messages that are send from higher priority devices to this device will interrupt the data stream from the previous lower priority sender. Also Interrupt and Acknowledge messages to and from this device will interrupt the data stream. If the time between each CAN frame that is part of the single message exceeds 250ms the sender will stop sending and the receiver will discard the received data so far. The sender will retry to send the message when the CAN bus is not in use up to 4 times.

All messages use the extended 29-bit priority fields. The bits are mapped on to of the various message fields as listed below:

  • b28, 0 = Interrupt, 1 = Normal message
  • b27, 0 = Command, 1 = Acknowledge message
  • b26..b24, Reserved for future extension. Should be 0b100 for now.
  • b23..b16, Toddress
  • b15..b8, FromAddress
  • b7..b5, Message ID
  • b4..b0, Package number

Most fields have been explained except for the last two. Since a message that is being received can be interrupted by frames form a higher priority message the sender includes a Message ID ranging from 0 to 7. This allows the application to send two (or actually eight) messages in parallel to verious devices without waiting for the first message to be completed. It also means that two (or more) messages can be send to the same device without waiting for the first one to complete. If the MessageID was not included the receiver can not see which frame belongs to which message. Please note that Interrupt and Acknowledge messages are included in the maximum of 8 messages.

This situation will be very common, for example if a command has been send from device A to device B requesting a measurement to be executed and send the results when completed, device B will send an Acknowledge message. This message does not mean the command has been execute, it only means the command has been received. Than device B receives a message from a higher priority device C to do something.  When the measurements are completed the results are send from device B to device A. This message may take several frames, during transmission of the results device A may already send a new command to device B. This will result in placing the transmission of the results on hold since the Acknowledgement message has a higher priority. So the result message will use MessageID 0 and the Acknowledgement message will use MessageID 1. After the Acknowledgement message the command from device C is completed and also requires data to be send. Since this is send to a device with a higher priority than device A the message to device A is placed again on hold until the message to device C has been send.

The last field is the PackageNumber, since a message can contain 254 bytes of data this number indicates which part of the data is included in the frame. The first frame uses PackageNumber 0, the first data bytes is the command and the second data bytes is the total amount of data bytes in the message. The last 6 data bytes can already be used for actual data. The receiver will only accept a new message from a sender if the PackageNumber is set to 0. At that time it can calculate how many frames will follow and how much memory must be allocated to store the incoming data. The next frame using the same FromAddress and MessageID must use PackageNumber 1, after this 2 and so on. In total there can be 32 frames, so (32*8)-2=254 bytes of data.

Nov 17

In order to develop the higher level RoboCAN protocol I will need two devices that are connected to the CAN bus. One is the ATDVK90CAN1 board and the other one is a PC using the CANUSB interface. When the drivers are installed a series of demo programs is installed as well including the source code. These demo programs show how to interface with a DLL called canusbdrv.dll which provides the basic functions to send and receive frames using the CANUSB interface.

I’m using Delphi 2005 as my development environment for Windows applications. Opening and compiling the SDIAPP stored in the C:\Program Files\LAWICEL\CANUSB\examples\Delphi2005 folder works, however when executing the code it crashes when I press the openbutton. I wonder if it truly is so difficult to build code that works…

Some debugging later I find that the string used for receiving the version is too small for storing the version. Below is the modified code (the bold parts):

procedure TSDIAppForm.BtnOpenClick(Sender: TObject);
Var VerText : String[255];
begin
  CANUSBOpen(”, CAN_BAUD_500K, CANUSB_ACCEPTANCE_CODE_ALL, CANUSB_ACCEPTANCE_MASK_ALL, CANUSB_FLAG_TIMESTAMP);
  CANUSBVersionInfo(@VerText[0]);
  Label1.Caption := VerText;
  BtnOpen.Enabled := False;
  BtnClose.Enabled := True;
  BtnStatus.Enabled := True;
  BtnWrite.Enabled := True;
  Timer1.Enabled := True;
end;

Next to this, I changed the address used for sending frames to $310 so it works with the code running so far on the ATDVK90CAN1 board. Now at least it does not crash, however when pressing the write button I see that a frame is received with 1 data byte (correct) but it shows two… Some more debugging shows that the loop to show the received data contains another bug as well… Below the modified code (the bold parts). 

procedure TSDIAppForm.Timer1Timer(Sender: TObject);
Var
  CANMessage: CANmsg;
  DummyStr: String;
  tmp: Integer;
begin
  if (CANUSBRead(CANMessage) = ERROR_CANUSB_OK) then
   begin
     DummyStr := Format(‘ID = %x,’, [CANMessage.id]);
     DummyStr := DummyStr + Format(‘ LEN = %d, DATA = ‘, [CANMessage.len]);

     tmp := 0;
     while (tmp < CANMessage.len) do
       begin
         DummyStr := DummyStr + Format(‘ %2x’, [CANMessage.data[tmp]]);
         Inc (tmp);
       end;

     Label3.Caption := DummyStr;
   end
end;

procedure TSDIAppForm.BtnWriteClick(Sender: TObject);
Var CANMessage: CANmsg;
begin
  CANMessage.id := $310;
  CANMessage.flags := 0;
  CANMessage.len := 2;
  CANMessage.data[0] := $55;
  CANMessage.data[1] := $AA;
  CANUSBWrite(CANMessage);
end;

At least it seems the ddl is working so I will use this dll to develop a RoboCAN monitor application.

« Previous Entries Next Entries »