Dec 31

Before the electrical design can start some items need to be considered.

I want to stack more add-on boards on top of the FOXBoard, this allows me to re-use some of the boards (or designs) when I start on another robot. The FOXBoard uses 2 headers of 2×20 pins, these are available as female sockets with variable pin length ranging from 3 to 17mm. Examples are the 962-60206-12 from EPT that offers a body height of 11.45mm and a pin length of 12.2mm. Using a PCB thickness of 1.5mm and an inserting of the pin in another female socket of 3 to 6mm this offers a maximum component height of 16.15 to 19.15mm.

If one board is stacked on top of another one connectors, LED’s, buttons and so on are not longer accessible, for this reason all these components should be placed on the side of the board and be a 90 degrees bended version.

For the CAN interface to the actuators, 2 signal wires are needed that must be twisted. Based on the basic electrical architecture of SnakeBot, there are 3 additional wires needed: power, ground and charge. It might be an option to use normal UTP cable, this consists of 4 pairs of 2 twisted wires. In this case one pair can be used for the ground, one for the power, one for the charge and one for the CAN interface.

A wire in an UTP cable is a 24 gauge copper wire, this limits the current per wire to 0.577A or 1.15A per pair. This is not so much, when using 10 or 15 segments for SnakeBot each charging with 250mA as a minimum but with more advanced charging up to 2.5A the total current through the charge wire can be in the range to 10 to 15A. This is indeed not very realistic since the charger must be capable of supplying this current, but nevertheless the 1A is by far to low.

I guess the best solution here is to use normal household appliance power cord cable for the three power lines and use a separate two wire cable (shielded and/or twisted) for the CAN interface. This also “feels” better since drawing high current spikes trough a conductor that is a millimeter away from a communication bus running at 1Mbps is probably begging for problems.

In order to connect the power cables, best option is to use terminal blocks with a screw. Reason for this is that there will be a rather thick core that needs to be inserted. When wiring SnakeBot the (for example) charge line need to go into a segment and leave the segment again to go to the next segment. You do not want to place a connector on the front of the PCB and route the charge line over the board to the other side and place a connector here as well to go to the next segment. If this was done than all the charge current of all segments combined will run though the PCB of the first segment causing serious layout problems. Instead, the charge line will be cut, both ends stripped, twisted together, soldered and than inserted in a single connector that only supplies the current to that segment.

A power cord that can handle 16A uses a core of 2.5mm2, twisting both of them together results in a 5mm2 surface that makes a circle of SQRT(5/3.14)*2=2.6mm in diameter. That would require a terminal block that can handle AWG 10 or less, these are available but the height is 21.5mm which is too high and they are not 90 degrees bended.

Next best solution might be automotive crimp terminals, these can handle high current, are available in many shapes and have PCB terminals. For example this female connector and this pcb connector.

In principle this is the same for the CAN interface except that the cores are much thinner. Remains the problem that the screw can not be fastened since there might be another board on top. There are terminal blocks available that offer both a male and female part. The cable entry part is 2.5mm2, so inserting two wires will not work. As an alternative the power cable cut be cut, stripped and third small piece of about 5cm could be added to make a T-junction using shrinktube for isolation. Than the 5cm long wire could be inserted in the terminal block. For the CAN-wires this is not needed, two wires can be twisted, soldered and inserted without any problem.

For now, I will use the terminal connector using the male-female combination. For some reason this feels the best. I will split the connector in 2 times 3 pins connectors. For one that the PMC will only use the CAN signals and not the power lines but also giving me the option to buy a bit more of them and keep them on stock for other projects (2 and 3 pins connectors are used the most, 6 pins much less).

Dec 29

At this moment the only add-on defined is the Primary Motor Cortex, I can imagine there will be more added in a later phase. For this it is important that the software and the electrical design are defined so that more add-ons can be placed in parallel.

Below the impact on the electronics:

  • Address lines, are all inputs. No special action other than that one add-on board should enable the pull-up resistors while the others don’t. If all add-ons would enable the pull-ups they would all be placed in parallel and as such reduce the value (and increase the current consumption).
  • Data lines, all bi-directional but by default set to input. Also here the pull-ups must be enabled on one add-on only.
  • /WR, /RD lines, are all inputs. Again the action on the pull-ups.
  • /INT line, is an input that each add-on can activate. Should be an open collector in that case . In the test software the output is pulled low. Using an open collector circuit would require a transistor, in this case the output on the AT90CAN128 should be inverted. Also a pull-up to the collector input of the transistor would be required, this should only be enabled on one add-on board again using a jumper.
  • /ACK line, is an input that each add-on can activate so same as the /INT line.

Below the impact for the software:

  • Use a define to enable or disable the pull-up on the address, data and control lines.
  • Apply a filter in the read and write ISR’s to see if the command is for that specific add-on.
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.

« Previous Entries