CANquitto

Status
Not open for further replies.

tonton81

Well-known member
Greetings all. Pushing out a library in development for a teensy to teensy communication over CANbus. It may probably contain bugs that will hinder it's operation, but looking into ressolving them over time...

The library is capable of transferring up to 8000 byte payload over canbus in sequential ordering with ACK responses and callback firing. It uses one of the raw outputs of IFCT including background events() operation.
The library supports multiple nodes and is multi-master design

Two demos have been uploaded as a simple demo, and a few videos of it in action:

https://www.youtube.com/watch?v=XGUBXl2psck

https://www.youtube.com/watch?v=UP4KQlDcsoc

https://www.youtube.com/watch?v=YpxmuLBO8to

https://www.youtube.com/watch?v=tXqmFIKOsGc

Github repo: https://github.com/tonton81/CANquitto

Tony
 
The event handling for CANquitto was redesigned to allow the ISR to be the only consumer/producer for both buffers while not processing, and while processing only the primary buffer was handled by the ISR. Next while not busy, the ISR was in charge of unloading the primary buffer to the secondary one. There is no more buffer shuffling in events, the specific frames are now searched and appended to the local array and the node is purged on completion. Alot of code has been reduced this way. An attempt of a 3 node communication was established during current tests:

https://m.youtube.com/watch?v=hD-NOlcDQCI

The Teensy 3.6 (far right) is blinking fast due to it’s sending a 77 byte char array to the 3.2 and 3.5, so it’s getting a double ACK, 1 from each node causing the led to blink on and off rapidly. All 3 consoles are displaying the node transfers including the 3.2 which shows 2 callbacks firing (lower window in video).

Still looking to improove the code as sometimes a frame could not be completed successfully, although it recovers, it’s still looking to be ressolved
 
Hello.
I have one Teensy 3.6 Board... so I try connect between CAN0 and CAN1...

but not work.....I think only work Fixed CAN0 and node....
( so I try modify code....but not easy )

If you can, I hope support to the next versions.


I Try test example code....


#ifndef __MK66FX1M0__
#error "Teensy 3.6 with dual CAN bus is required to run this example"
#else
#include <IFCT.h>//modify code
#include <CANquitto.h>
#endif

#include <Serial_Printf.h>

CANquitto Node0( FLEXCAN0_BASE ), Node1( FLEXCAN1_BASE );//test

void setup()
{
pinMode( 13, OUTPUT ); // enable led pin

Serial.begin( 2000000 );
while( !Serial && ( millis() < 100 ) ); // wait for Arduino Serial Monitor
Serialprint0( "Setup Start 0\r\n" );

Can0.setBaudRate( 1000000 );
Can0.enableFIFO();
Can0.enableFIFOInterrupt();
Node0.onReceive( myCallback_0 );
Node0.begin( 112 );
Can0.intervalTimer(); // enable queue system and run callback in background.

Can1.setBaudRate( 1000000 );
Can1.enableFIFO();
Can1.enableFIFOInterrupt();
Node1.onReceive( myCallback_1 );
Node1.begin( 123 );
Can1.intervalTimer(); // enable queue system and run callback in background.

Serialprint0( "Setup End 0\r\n\r\n" );
}

void loop()
{
static elapsedMillis time_NodeSend = millis(), time_NodeSend_Info = millis();
uint8_t nNode0_Send = 0, nNode1_Send = 0;
// put your main code here, to run repeatedly:
uint8_t buf_node0[ 200 ];
char buf_node1[] = "Hello World! Hello Teensy! Hello IFCT!";

if( time_NodeSend >= 200 )
{
for( uint32_t i = 0; i < sizeof( buf_node0 ); i++ )
{
//buf[ i ] = random( 0, 255 ); // i + 1;
buf_node0[ i ] = i + 1;
}

if( Node0.write( buf_node0, sizeof( buf_node0 ), 123, 2000, 3000 ) == 0x06 )
{
digitalWrite( 13, !digitalRead( 13 ) );
nNode0_Send++;
}

if( Node1.write( (const uint8_t*)buf_node1, sizeof( buf_node1 ), 112, 11, 2000, 3000 ) == 0x06 )
{
digitalWrite( 13, !digitalRead( 13 ) );
nNode1_Send++;
}
}
//delay( 100 );

if( time_NodeSend_Info >= 1000 )
{
time_NodeSend_Info = 0;
Serialprint0( "NodeSend_Info %lu, %lu \r\n", nNode0_Send, nNode1_Send );
}
}

void myCallback_0( const uint8_t* buffer, uint16_t length, AsyncCQ info )
{
Serial.print( "node0 PAYLOAD!!: " );
for( uint32_t i = 0; i < length; i++ )
{
Serial.print( buffer[ i ] ); Serial.print( " " );
} Serial.println();
Serial.print( "From Node: " );
Serial.println( info.node );

Serial.print( "Length: " );
Serial.println( length );

Serial.print( "Millis(): " );
Serial.println( millis() );
}

void myCallback_1( const uint8_t* buffer, uint16_t length, AsyncCQ info )
{
Serial.print( "node1 PAYLOAD!!: " );
for( uint32_t i = 0; i < length; i++ )
{
Serial.print( buffer[ i ] ); Serial.print( " " );
} Serial.println();
Serial.print( "From Node: " );
Serial.println( info.node );

Serial.print( "Length: " );
Serial.println( length );

Serial.print( "Millis(): " );
Serial.println( millis() );
}

Thank you very much. Tony(tonton81)
 
Yes can0 only supported for now until stability is better, then will add Can1 :)

the problem is sharing the buffer, we need it stable first on one bus before combining both busses into same buffer for sorting
 
Last edited:
oduyong, I added bus reception support and transmit as well.

You can setup up to 127 nodes globally, NOT per bus.
To identify the bus in the callback from where the node originated, check the info.bus flag if it's 0 or 1.
You can write to the node's bus if you add the Can1 overload at end of write function

Still testing a few things, its working good in thread environment and loop at 10ms rates for ~ 400 byte total payloads flooding accross the bus.
Results and configuration may vary per application :)
 
CANquitto now uses new feature of CBA which allows an updated list of active nodes to be kept track of. ::write uses it to check if a node is available before writing to it, otherwise it exits with a return value of 0. This way the writes don't spend time in timeout area waiting for a node's response when it will never get one if it's not online.
 
Good evening everyone. In an effort to pull more performance and stability into CANquitto multi-master protocol, it's been in it's second redesign which is running pretty well. The nodes are written in 0ms loops payloads to each other while receiving ACKnowledgements that the payload transfers were a success. The burn in tests has been running 0ms loops over the past 2 days and looks pretty good.

The new version has:

* redesigned protocol
* single Circular_Buffer buffering
* node detection
* keep-alive and buffer size adjustments in the H file
* user code bool isOnline(node), to check if node is available based on active list
* multi-master node network. nodes can control each others like zombiework. There are no slaves :)
 
Now that tonton81 has announced the newly redesigned and improved CANquitto I can tell you that I have been assisting him with testing with my test setup with real time sensor data acquisition. I used a T3.6 as the master node that received GPS (UBLOX M8N) messages over the CAN bus from a T3.2 and IMU/Pressure (BNO055 and BMP280) data from a T3.5. All the sensors run in their own teensy thread as well.

I can tell you that I have run different CANquitto versions for hours and this version is the most stable and reliable version.
 
CANquitto has been revamped with a new redesign! It's faster, lots less overhead, and hardware capabilities are now growing.

The new version also allows:
GPIO controls, currently digitalRead/digitalWrite,toggle.
Serial controls: Currently you can write()/print()/println() to any of the usbserial or 6 uarts available on teensy. The size of the write is also returned!

A beneficial use of CANquitto is having multiple nodes controlling the same resource of another teensy. This allows 2 or more different nodes the ability to write to USBSerial of another node.
design is still and will always be multi-master. You may access these new features when constructing a node.

Node control construction:

Code:
CANquitto myNode = CANquitto(118); // <-- node you wish to control resources

to toggle the led:
Code:
myNode.toggle(13);

digitalRead/Write access:
Code:
myNode.digitalRead(13);
myNode.digitalWrite(13,HIGH);

Serial/UART writing:
Code:
  myNode.write(0x55);
  myNode.write(buf,len);
  myNode.print("Hello world");
  myNode.println("Hello world");

EDIT: pinMode added.
Code:
myNode.pinMode(pin,OUTPUT);

EDIT2: analogRead added.
Code:
myNode.analogRead(pin);

EDIT3: analogReadResolution added.
Code:
myNode.analogReadResolution(12);

The payload transfers have also changed, you may access them as follows:
Code:
myNode.sendMsg(buf, sizeof(buf), 36); // buffer, length, packetid

Node object is still used to set the self ID of the node, the constructor for public is used to assign an object to other nodes.

Tony
 
Last edited:
Greetings, more stuff has been added!

Serial available, peek, and read have been added, and some minor misguided bugs have been removed :)
The Serial objects of all the nodes are now part of the Stream class! This allows you to pass them in as references to other libraries, to control uart devices over CAN as if they were attached locally :)

Code:
 <library>.Begin(myNode.Serial1);
 
Hi all, I just finished adding buffered read support to Serial class!
Code:
myNode.Serial.read(buffer,len);
Just like the original function, a value is returned with whatever bytes came in that were available. If you requested 19 bytes, and there were 6 in buffer, the 6 would be updated in your buffer and a return of 6 would be given.
I've made it request up to 5 bytes max per frame so it's very efficient, requesting 19 bytes (if available in the UART) will result in 4 CANbus frames being appended to the user buffer.

I've also added Serial setRX/setTX and begin(115200) methods for CANquitto to control the pins and baudrates of the remote nodes.

As a bonus I decided to setup one of my 4D uart displays to show just what CANquitto can do in terms of running any of teensy's UART over CAN, in this case I randomly chose Serial5 since the pins were at edge, convenient.

The display is physically connected to Teensy 3.5, running ONLY IFCT and CANquitto.
The display's library (genieArduino) is installed on the Teensy3.6 node, no modifications have been done to the library.

I passed the node object into the library:

Code:
genie.Begin(myNode.Serial5);

The scope on the display is updated by incremental counter in teensy loop()

Here's the video of it in action!
https://www.youtube.com/watch?v=UdgZzqAZhjQ

EDIT, keeping an eye on the library debugging, the display code is not getting any missing bytes or NAKs whatsoever! This shows how solid the protocol is. Even the touch events are being reported on the T3.6 remotely :)
PS, don't be fooled by the Mega2560, it's only powering the LCD, T35 is actually controlling the uart lines, and the display's library on the T3.6 is controlling the LCD :)
 
Last edited:
Today's update for CANquitto:

Wire (I2C) has now been integrated into the protocol. It's based off Wire.h from Teensyduino. This week I plan to add i2c_t3 methods as well, the library will give you a choice to run one or the other. You get all the usual benefits as serial previously posted.

The node object is part of the Stream class, so you may pass it's Wire object into other libraries as reference, just like the demo video posted above with Serial.

Code:
myLibrary.begin(myNode.Wire1);
Example of passing the object as reference to another library

All user used functions have been added:

begin, write, write(buf,len), read, peek,setSCL,setSDA,beginTransmission,endTransmission,requestFrom, setClock etc

Next step after i2c_t3 will be the addition of integrating SPI port access over CAN.

Tony
 
Libraries and Performance

tonton81,

Thanks for these great libraries, couple of questions.

1. I'm having library contention issues. Can you document which libraries for CircularBuffer, and IFCT I should be using. (I'm using those from your GIT account. I updated them just now and the compile fails with;

canquitto:55: error: 'class CANquitto' has no member named 'write'
if (Node.write((const uint8_t*)buf, buf[2], EXTNODE, 11, 2000, 3000)==0x06); // digitalWrite(13, !digitalRead(13));

and 2.

I'm sending short payloads of less than 30 bytes at 1,000,000 baud. The messages take about 1.7 msec per byte, each payload taking about 20 msec to send. You mentioned earlier about 0 msec sends ("The nodes are written in 0ms loops payloads" ). How do I do this.

Thanks

Carl
 
Last edited:
you need IFCT, Circular_Buffer, and CANquitto libraries installed in arduino user folders,

You can’t write to Node, it is a singleton class just for the master node config. You need to construct a node to talk to (check last posts for constructor) then use that object for write. This has changed since last demos were created, you should use the new objects for writing (ex. myNode.write(......);)

The github copies are always updated, except for the i2c_t3 version support im currently working on

when i mentioned 0ms loops, i meant the user sketch running the payload writes constantly without delays, please also be aware EXTNODE (i presume the node you are sending to) doesnt exist anymore with the new node objects, it’s already implied which node your sending to when you created that node’s object, should be able to just myNode.write(buffer, length, packetid); and your all set :)
Tony

EDIT, check post #9 here will show you how to construct a node you wish to control to be able to use the write() function
 
Last edited:
New update!
Aside from including Wire capabilities, I2C_T3 support has been added to CANquitto as well.
An example of which is:
Code:
node123.Wire.begin(I2C_MASTER, 0x00, I2C_PINS_18_19, I2C_PULLUP_EXT, 100000);

SPI over CAN has been added as well. begin(), beginTransmission, endTransmission, transfer, and transfer16. For the beginTransmission it's same as normal on Teensy, except don't specify SPISettings, the remote node will run it remotely:
Ex.
Code:
node123.SPI.beginTransaction(4000000,MSBFIRST,SPI_MODE0);

We have a new handler! You'll be able to get a callback status when a node is found on your network and when it drops from your network.
The detection is pretty useful as you can use it to "pre-configure" nodes as soon as they go online.

Code:
void myFunc(AsyncCQ info) {
  if ( info.state == NODE_DETECT ) {
    Serial.print("NEW CONNECTION FROM NODE ");
    if ( info.node == 118 ) {
      node118.Serial1.begin(115200);
    } 
   Serial.println(info.node);
  }
  if ( info.state == NODE_LOST ) {
    Serial.print("LOST CONNECTION TO NODE ");
    Serial.println(info.node);
  }
}

Another protective measure of running port accesses without calling begin, if a user never enables the port he is accessing, the clock gate register is checked before processing the request. If the clock gate register value for SPI, WIRE, or SERIAL are not enabled, it's defaults would be loaded (Wirex.begin(), SPIx.begin(), Serialx.begin(115200)).
If the user already enabled the port, the clock gate register would return valid and library wont run the default begin
 
After testing out the MCP23017 attached to the T3.6 while the T3.5 was accessing it, I decided to attach a MCP23S17 (SPI) variant to the Teensy 3.2 node on the network.

Here's the loop code on the T3.5 to talk to the T3.2 SPI bus:
Code:
  delay(1000);           // wait 1 seconds for next loop
  node112.SPI.setMOSI(11);
  node112.SPI.setMISO(12);
  node112.SPI.setSCK(14);
  node112.SPI.setCS(15);
  node112.pinMode(15, OUTPUT);
  node112.SPI.begin();
  uint8_t chipselect = 15;
  node112.SPI.beginTransaction(4000000, MSBFIRST, SPI_MODE0);
  node112.digitalWrite(chipselect, LOW); // Configure hardware enabled addressing for all chips on given chipselect.
  node112.SPI.transfer(0x40); // Write command to init all chips.
  node112.SPI.transfer(0x0A); // Write IOCONA register
  node112.SPI.transfer(0x18); // DISSLW & HAEN on, other bits off.
  node112.digitalWrite(chipselect, HIGH);
  node112.SPI.endTransaction();

  node112.SPI.beginTransaction(4000000, MSBFIRST, SPI_MODE0);
  node112.digitalWrite(chipselect, LOW); // Configure hardware enabled addressing for all chips on given chipselect.
  node112.SPI.transfer(0x4E); // Write command to init all chips.
  node112.SPI.transfer(0x0A); // Write IOCONA register
  node112.SPI.transfer(0x18); // DISSLW & HAEN on, other bits off.
  node112.digitalWrite(chipselect, HIGH);
  node112.SPI.endTransaction();

  node112.SPI.beginTransaction(4000000, MSBFIRST, SPI_MODE0);
  node112.digitalWrite(chipselect, LOW);
  node112.SPI.transfer(0x41 | ((0x20 & 0b111) << 1)); // Read command for addressed chip.
  node112.SPI.transfer(0x12);
  byte data = node112.SPI.transfer(0xFF);
  node112.digitalWrite(chipselect, HIGH);
  node112.SPI.endTransaction();
  Serial.print("SPI DATA: ----------------------------------->>  ");
  Serial.println(data);

output on the T3.5 from accessing the T3.2 SPI bus:
Code:
SPI DATA: ----------------------------------->>  128
This pin (8th on bank1) is tied to VCC and correctly shows values when sweeping accross the pins.

I also added touchRead support for supported teensies:
Code:
  Serial.print("TOUCHREAD ################################ "); Serial.println(node123.touchRead(23));

Output from T3.5 accessing T3.6 touch pin 23:
Code:
TOUCHREAD ################################ 1020
 
Last edited:
Status
Not open for further replies.
Back
Top