Using an Arduino Due as a mini-QC controller

My Nissan Leaf Forum

Help Support My Nissan Leaf Forum:

This site may earn a commission from merchant affiliate links, including eBay, Amazon, and others.
klapauzius said:
If you want to do this for 10 units, we need to multiplex some of the ADCs. Looks like we can get a single reading in ~ 4 us, so at a 1 kHz sampling rate, you could read from 250 sensors (with multiplexing).
There's no need to do 10 units. Since each supply has a binary fail (and probably PG) output, just monitor the overall voltage and then these binaries. Most of these supplies also seem to have SPI or I2C as well, so if it's protocol is figured out, this as an even lower pin count option. It's possible that voltage may even be controllable via this manner.

As far as an analog, You'd really only need the DC voltage sum of all power supplies (DC out), and DC current. I could see adding some temp sensors, but not absolutely needed, as the power supplies themselves will simply shut down if they overheat. It might also be interesting to monitor AC voltage/current, which would enable efficiency calcs and make possible max AC power limiting. But again, not needed for a basic system.

I honestly don't see that a powerful uP is needed here. Anything with CAN that can respond to the 100ms adjustment requests should suffice. (That means pretty much anything will work!)

-Phil
 
Ingineer said:
klapauzius said:
If you want to do this for 10 units, we need to multiplex some of the ADCs. Looks like we can get a single reading in ~ 4 us, so at a 1 kHz sampling rate, you could read from 250 sensors (with multiplexing).
There's no need to do 10 units. Since each supply has a binary fail (and probably PG) output, just monitor the overall voltage and then these binaries. Most of these supplies also seem to have SPI or I2C as well, so if it's protocol is figured out, this as an even lower pin count option. It's possible that voltage may even be controllable via this manner.

As far as an analog, You'd really only need the DC voltage sum of all power supplies (DC out), and DC current. I could see adding some temp sensors, but not absolutely needed, as the power supplies themselves will simply shut down if they overheat. It might also be interesting to monitor AC voltage/current, which would enable efficiency calcs and make possible max AC power limiting. But again, not needed for a basic system.

I honestly don't see that a powerful uP is needed here. Anything with CAN that can respond to the 100ms adjustment requests should suffice. (That means pretty much anything will work!)

-Phil


It would be very elegant if we could control the server power supply voltage through SPI or I2C. Do you have a specific unit in mind?


100 ms should be easy to manage.
I think there were also some concerns with the PWM resolution, but in the end the Arduino Due board just costs $40. I guess a realistic estimate of the build cost, even if "only" using the server power supplies, will be much higher than that?
 
garygid said:
Short CAN intro:

Send messages with 0 to 8 bytes of data, along with the
message ID (000 hex through 799 hex, or 11 bits) and
a count of the data bytes to be included (0 through 8).
I use some of the Fxx MsgIDs for pseudo-messages,
which are not transmitted on the CAN bus.

So, think of it as N MID D1 D2 D3 D4 D5 D6 D7 D8, which
I usually store in 10 bytes as ID NM D1... D8 where
I set/force the "unused" data bytes to 0xFF.

When receiving messages, I usually store two more
bytes for a (Seconds*1024 + milliseconds) Time Stamp,
with each message, using SS MM for notation.

If I an logging from two sources, I add another byte
for Origin (RR), with EV-CAN bus = 1, CAR-CAN = 2,
AV-CAN = 3, and QC-CAN = 4. I use zero (0) for
Date-Time messages, text messages, and other
pseudo-messages that apply to any(all) channel(s).

I add a check-byte (BB), for a total of 14 bytes for the
Multi-CAN file format, or 13 bytes for the individual
CAN Log files.

So, a Single-CAN (.evc, .can, .avc, or .qcc) file has fixed length
records, as BB ID NM D1... D8 SS MM ... (as I recall), 13 bytes each.

Then, the Multi-CAN (.alc) file also has fixed length records,
as BB ID NM D1... D8 SS MM RR ... (as I recall), 14 bytes each.

Look at some of the log files I have online with
a HexEdit program, and then with CAN-Do... at
http://www.wwwsite.com/puzzles/cando/" onclick="window.open(this.href);return false;

Ok, that part is kind of clear. Do you lump the message ID and the number of bytes transmitted into 2 bytes?
In the Due CAN examples it seems the message ID is just a byte? So 1 bye for message ID, one byte for data length and 1-8 bytes of data?
I assume the rest is something that is just added in processing, right?
 
CAN message handling with the Arduino_Due_CAN library:
Seems like there is no interrupt handling of received CAN messages in the library...So the main loop, as in all the examples, has to wait for a CAN message to come in.

while (!(CAN2.mailbox_get_status(0) & CAN_MSR_MRDY)) {
}

This is the troublesome part in the examples, and I am not sure we want the arduino to wait for CAN messages to arrive?
We could just check for CAN_MSR_MRDY, rather than wait for it in a loop. This way however, we might loose messages.

Hopefully, there is a better way...
 
The MsgID is 11 bits, so more than one byte.
Probably an integer parameter in a call to the library.

For logging, yes, I use 3 nibbles of two bytes for the MsgID,
and the remaining nibble for the data-count vaue.

If the CAN library will not support receive interrupts,
we might need to find (or write) other CAN support.

Perhaps read the CAN section in the uP 1467-page datasheet
to see how the hardware works?

And, check in the Arduino Due forum to see if anyone
else is working on CAN interrupts?
 
The difficult part of doing logging, display, CAN communication,
analog and digital control, manual control, is that the process of
controlling and regulating the power supply (perhaps once per
millisecond) must not be interrupted by the lower priority
processes, like logging.

The "loop" is usually reserved for lower priority background
processes, and the power supply control would typically be
on a 1 ms hardware timer.

For safety, an external hardware watchdog circuit might "scram" the
power supply unless "tickled" by a digital-out pulse every 1.5 ms.

So, other interrupts should be very short, with no waiting in them.
 
If anybody gets the CTE32HR touch display and Due Shield
from ColdTears Electronics (see links in the thread's 2nd
post), about $30 plus $13, all assembled, I will send my
Touch-test sketch, which has menus, number input, and
two demos wrapped up together in one sketch.
V04, but basically, a work in progress.

I will try to add a 1ms timer interrupt today,
read the system "clock", and output a look file
through the Native USB to my CAN-Do program.

Note, a longer sketch might be easier to manage as
several ".h" files included in the Sketch, by using the
"tabs" on the IDE 1.5.4 screen. I got things working
in one sketch with many subroutines, but then put
the routines for the Karlsen demo in one file, the
CTE demo routines in another file, and my touch-button
handling in a third file, to de-bulk the main Sketch
file for easier understanding.

NOTE: When you use an
#include "xyz.h"
the referenced text is just inserted at that point,
before compiling is started.
 
garygid said:
The MsgID is 11 bits, so more than one byte.
Probably an integer parameter in a call to the library.

For logging, yes, I use 3 nibbles of two bytes for the MsgID,
and the remaining nibble for the data-count vaue.

If the CAN library will not support receive interrupts,
we might need to find (or write) other CAN support.

Perhaps read the CAN section in the uP 1467-page datasheet
to see how the hardware works?

And, check in the Arduino Due forum to see if anyone
else is working on CAN interrupts?


Ok, that clarifies this... I wasnt sure if a whole byte was used for message length.

I am certain that CAN message handling on the DUE will work through an interrupt, otherwise it would be pretty useless... The library hints at that
with these functions:

CAN.disable_interrupt(CAN_DISABLE_ALL_INTERRUPT_MASK);
NVIC_EnableIRQ(CAN1_IRQn);


I just haven't yet found a way (using the library) to attach a message handler to the message interrupts that must exist. I will explore this on the forums

p.s:
Just ordered the shield and 3.2" display.
 
CAN message handling:

Looks like the interrupt handler is part of the library. Specifically I think in
"due_can.cpp" this function

void CANRaw::interruptHandler()

seems to take care of the interrupts. Specifically it relays any mailbox events to the mail_box_int_handler.


Apparently the library supplants the "default CAN interrupt handler" i.e. CAN0_Handler and CAN1_Handler with its own handlers.

I think this works:

1) lets say we want to receive on CAN1: comment out the following function at the end of "due_can.cpp" in the library:

Code:
/*
void CAN1_Handler(void)
{
	CAN2.interruptHandler();
}
*/

2) replace CAN1_HANDLER(void) with own function in the Arduino-sketch

Code:
// Required libraries

#include "variant.h"
#include <due_can.h>
#include <DueTimer.h>

// CAN frame max data length
#define MAX_CAN_FRAME_DATA_LEN   8
#define TEST1_CAN0_TX_PRIO       15
#define TEST1_CAN_TRANSFER_ID    0x07

long rcvd_msg_counter=0; 
long send_msg_counter=0;


// this will be our CAN message handler
void CAN1_Handler()
{
//    // Wait for CAN1 mailbox 0 to receive the data
while (!(CAN2.mailbox_get_status(0) & CAN_MSR_MRDY)) {}
//
RX_CAN_FRAME incoming;
//  // Read the received data from CAN1 mailbox 0
CAN2.mailbox_read(0, &incoming);
 rcvd_msg_counter++;//count received messages
 if (rcvd_msg_counter>999) // for demonstration purposes only
 {
   rcvd_msg_counter=0;
   Serial.println("1000 messages on CAN1");
 }
}

void setup() {
  // start serial port at 115200 bps: 
  Serial.begin(115200);

  // Verify CAN0 and CAN1 initialization, baudrate is 1Mb/s:
  if (CAN.init(SystemCoreClock, CAN_BPS_1000K) &&
    CAN2.init(SystemCoreClock, CAN_BPS_1000K)) {

    // Disable all CAN0 interrupts - CAN0 will act as transmitter 
    CAN.disable_interrupt(CAN_DISABLE_ALL_INTERRUPT_MASK);
    
    //CAN1 will be the receiver
    CAN2.mailbox_init(0);
    CAN2.mailbox_set_mode(0, CAN_MB_RX_MODE);
    //CAN2.mailbox_set_mode(0, CAN_MB_CONSUMER_MODE);
    CAN2.mailbox_set_accept_mask(0, 0x7FF, false);
    CAN2.mailbox_set_id(0, TEST1_CAN_TRANSFER_ID, false);
    CAN2.enable_interrupt(CAN_IER_MB0); //enable interrupts on CAN1, which will be our receiver
    NVIC_EnableIRQ(CAN1_IRQn);
    
    Timer3.attachInterrupt(CAN_msg_generator); //send a random message every millisecond
    Timer3.start(1000); // Calls every  1 ms
  }
  else {
    Serial.println("CAN initialization (sync) ERROR");
  }

}

void CAN_msg_generator()
{

  // send a random CAN message every ms on CAN0
  uint32_t CAN_MSG_l=random(0x100000000);
  uint32_t CAN_MSG_h=random(0x100000000);
  CAN.reset_all_mailbox();
  CAN.mailbox_set_mode(0, CAN_MB_TX_MODE);
  CAN.mailbox_set_priority(0, TEST1_CAN0_TX_PRIO);
  CAN.mailbox_set_accept_mask(0, 0x7FF, false);
  // Prepare transmit ID, data and data length in CAN0 mailbox 0
  CAN.mailbox_set_id(0, TEST1_CAN_TRANSFER_ID, false);
  CAN.mailbox_set_datalen(0, MAX_CAN_FRAME_DATA_LEN);
  //send random 8 byets
  CAN.mailbox_set_datal(0, CAN_MSG_l);
  CAN.mailbox_set_datah(0, CAN_MSG_h);

  // Send out the information in the mailbox
  CAN.global_send_transfer_cmd(CAN_TCR_MB0);
  
  send_msg_counter++; // for demonstration purposes only
  if(send_msg_counter>999)
  {
    send_msg_counter=0;
    Serial.println("1000 messages sent");
  }
}

void loop() {
  // put your main code here, to run repeatedly: 
}

And now, we can listen on mailbox0 on CAN1 for incoming messages with our very own message handler.
 
Data logging to an SD card:

This works. Without having to transmit data to the USB port, now we can actually sample data e.g. for a light bulb turn on event at a 1 ms and log it to an SD.
 

Attachments

  • arduino_data_log_example1.jpg
    arduino_data_log_example1.jpg
    56 KB · Views: 18
  • arduino_data_log_example2.jpg
    arduino_data_log_example2.jpg
    54.9 KB · Views: 18
Good work.

Now let's get the data on the SD card into an ".alc" file format,
as if the data is in bytes, or two-byte psirs, of a real CAN message.
There is a description of the CAN-Do file formats, and logging data
stream format, in another thread in this sub-forum.

I have the CTE32HR display working, with some "touch-button" subroutines
that I made. Define the screen as R rows by C columns of buttons, with
S pixels of space between them. Buttons may be W columns wide.

Then, a subroutine will draw and label any button you specify (by row, col)
and another routine to see if a touch-point is "on-the-button".
Not yet sophisticated, but it seems to work.

I have a main screen with 3 buttons on it, two run demos, and one
tests the numeric-keypad. I will add more.

I added a timer interrupt, and I have used 1 per second up to
several thousand per second.

Using that interrupt, I manufacture a pseudo CAN message, and
dump it into a circular buffer (if it is not full).

The main loop sends any buffered messages through the Native
USB port (using SerialUSB.write) and CAN-Do receives them.
I tested 100 per second, then 1000, and 2000, and it appears
to work. Apparently the specified baud rate (115200 in this test)
has little or nothing to do with this port, since 2000 x 14 bytes x
10 bits per byte is 280,000 (greater than 115,200).

When you get the display, I can send my sketch, if you wish.

I would like to see the writing to SD card sketch.
more later, Gary
 
In handling the circular buffer, when it is full, the message
is discarded, but I want to add some code that overwrites
the last message in the buffer (so two are lost) with an
error pseudo-CAN "buffer-overflow" message so that
we can easily tell if all the messages got into the buffer.

Using micros(), I derive a minute and second time stamp,
good for up to ??? maybe 70 hours?

Yes, I want to use the RTC to give me at least minutes and hours,
even if the RTC is not set correctly.

I have CAN transceivers now, so I can add a short CAN bus
to test send and receve. Is there a working script for
sending and interrupt-receive? The one in a prior post here?

Cheers, Gary
 
OK, I will get to work on this...probably this weekend, as the week turns out to be a bit more busy.

I used the sdFat library (see here http://code.google.com/p/beta-lib/downloads/list" onclick="window.open(this.href);return false; ... it is sdFatBeta)

For inexplicable reasons, the SD library (that is part of the Arduino libraries) does not show up in my IDE.
Anyway, the code is ugly at the moment, needs to be polished a bit.

What it (see below) does is run the ADC in AD0 and AD1 each millisecond and print the data into an ASCII buffer. Every second the buffer is written to the SD card.
After 20 s, the file is closed. The LED is blinking once a second while logging. If you press reset, it starts the acquisition of another 20 s file.

This is just a proof of concept, so for the real thing we would write binary files, append the file, do some error handling etc.
Not sure how this should be handled..What do you want to log?

ADC data (current, voltage, temperature), CAN messages?. If it isnt too much for the due, we can add Ethernet (there are $9 modules on amazon that run fine with the old arduinos), so any data-intensive stuff I would move off the Due asap. Definitely will run into to trouble if we log at 1 kHZ....

So maybe just the circular buffer? Basically dump its contents to the SD once its full?


Yes, the code I posted should work for CAN interrupt message handling. Note that you have to modify the library file as well (see my earlier post on this), as they define their own message handlers.

The ADC logging code. SS on the DUE seems to digital pin 10 (that worked for me). The rest goes through the 6 pin header near the uP.

Code:
#include <DueTimer.h>
#include <SdFat.h>
#include <SdFatUtil.h>  // define FreeRam()

#define BUFFER_SIZE 1000
#define SD_CHIP_SELECT  SS  // SD chip select pin
#define USE_DS1307       0  // set nonzero to use DS1307 RTC
#define LED_PIN 13

unsigned long delta_t=0;
int n=0,nsec=20;
uint16_t b0[BUFFER_SIZE];
uint16_t b1[BUFFER_SIZE];
// file system object
SdFat sd;


// text file for logging
ofstream logfile;
// buffer to format data - makes it eaiser to echo to Serial
char buf[12000];
boolean LED_VAL=HIGH;

void AD_Handler()
{
  unsigned long t1=micros();
  while((ADC->ADC_ISR & 0x80)==0);
  while((ADC->ADC_ISR & 0x80)==0);// wait for two conversions          
  b0[n]=ADC->ADC_CDR[7];// read data on A0 pin
  b1[n]=ADC->ADC_CDR[6];// read data on A1 pin

  delta_t+=micros()-t1; // benchmark
  n++;
  if(n>BUFFER_SIZE-1)
  {
    if(nsec>0){
      LED_VAL=!LED_VAL;  
      digitalWrite(LED_PIN, LED_VAL);
      //SerialUSB.write((uint8_t *)b0,2*BUFFER_SIZE);
      //SerialUSB.write((uint8_t *)b1,2*BUFFER_SIZE);
      obufstream bout(buf, sizeof(buf));
      for(int i=0;i<BUFFER_SIZE;i++)
      {
        bout << b0[i] << ',' << b1[i] << endl;
      }
      logfile << buf << flush;
    }
    else
    {
      digitalWrite(LED_PIN, LOW);
      nsec=1;
      logfile.close();
    }
    nsec--;
    n=0;
    delta_t=0;
  }
}

void setup(){
  //SerialUSB.begin(115200);
  //REG_ADC_MR = (REG_ADC_MR & 0xFFF0FFFF) | 0x00020000; //makes the ADC run fast
  pinMode(LED_PIN, OUTPUT);   
  digitalWrite(LED_PIN, LED_VAL);   // turn the LED on (HIGH is the voltage level)
  if (!sd.begin(SD_CHIP_SELECT, SPI_FULL_SPEED)) sd.initErrorHalt();
  Timer3.attachInterrupt(AD_Handler);
  analogReadResolution(12); // set to 12 bits
  int t=analogRead(0);
  ADC->ADC_MR |= 0x80; // these lines set free running mode on adc 7 and adc 6 (pin A0 and A1 - see Due Pinout Diagram thread)
  ADC->ADC_CR=2;
  ADC->ADC_CHER=0xC0; // this is (1<<7) | (1<<6) for adc 7 and adc 6

  char name[] = "LOGGER00.CSV";
  for (uint8_t i = 0; i < 100; i++) {
    name[6] = i/10 + '0';
    name[7] = i%10 + '0';
    if (sd.exists(name)) continue;
    logfile.open(name);
    break;
  }

  Timer3.start(1000); // Calls every  1 ms  
}

void loop()
{

}
 
If you use SerialUSB to communicate, it runs always at USB speed.
I tested it (one of my first scripts) and the due, with binary write, can do ~ 200 kByte/s or ~1.6 MBit/s over this bus.
 
Since the Due loses the Native USB connection with each Reset,
I want to add a checkbox to CAN-Do's input screen that tells
the input preparation/setup routine to wait until the requested
Comm Port appears (or until the user selects Stop Input).

This would allow starting CAN-Do while doing a Reset, I hope.

Or, maybe I will have to let the CAN-Do input process survive
a loss of the Comm Port, and successfully wait to re-attach.
Using Visual Basic 6 for that project.

Indeed, the high speed of the transfer is quite nice to discover.
I will need to test more to determine if any bytes go missing.

Thanks, Gary
 
I will try to adapt my present test script to run without
a display, and also run with the CTE50 (5" touchscreen),
in addition to the CTE32HR 3.2" touchscreen that I am
currently using.

The CTE Due display shield is about $13, and the displays
are approximately $10 per inch (diagonal).
 
With the proto board (see link in the 2nd post), it is convenient to
add one 8-pin surface mount chip (the CAN transceiver), so
I will probably build two, each with one CAN bus.

Then, one could act as a LEAF QC port simulator, and the
other act as the mini-QC controller.

In a test mode, the car-sim could send bursts of 3 to 5
messages, to better test that the mini-QC will be able
to receive grouped messages successfully.

Cheers, Gary
 
My serial port tester program: The DUE basically receives and sends back a 16 kB buffer. I used MATLAB to handle the PC side, probably SCilab (which is free) python will look somewhat similar


Code for MATLAB:
n=16384;

s=serial('COM7','BaudRate',8*115200,'OutputBufferSize',2*n,'InputBufferSize',2*n);
fopen(s)
di=zeros(100,1);
tt=di;
tic
for i=1:100
data=uint8(rand(n,1)*255);
tic
fwrite(s,data,'uint8');
datain=uint8(fread(s,n,'uint8'));
tt(i)=toc;
di(i)=sum(data-datain);
end
fclose(s);

fprintf('speed=%3.1f kB/s %3.1f %% error\n',n*100/1024/sum(tt)*2,sum(di)/100);

Code for DUE:

Code:
// test serial connection
#define BUFFER_SIZE	16384
char buffer[BUFFER_SIZE]; // read 10000 bytes
int led = 13;

void setup() {
  SerialUSB.begin(115200); //start at 115200
  pinMode(led, OUTPUT);     
  digitalWrite(led, HIGH);   // turn the LED on (HIGH is the voltage level)
  while (!SerialUSB); //wait for port to initialize
  digitalWrite(led, LOW);   

}

void loop() {
  if (SerialUSB.available() > 0)
  {
    SerialUSB.readBytes(buffer,BUFFER_SIZE); //read data from port
    digitalWrite(led, HIGH);
    SerialUSB.write((uint8_t *)buffer,BUFFER_SIZE); // send back
    SerialUSB.flush();
  }
  digitalWrite(led, LOW);
}
 
garygid said:
With the proto board (see link in the 2nd post), it is convenient to
add one 8-pin surface mount chip (the CAN transceiver), so
I will probably build two, each with one CAN bus.

Then, one could act as a LEAF QC port simulator, and the
other act as the mini-QC controller.

In a test mode, the car-sim could send bursts of 3 to 5
messages, to better test that the mini-QC will be able
to receive grouped messages successfully.

Cheers, Gary
The TI SN65HVD233 comes in a DIP package flavor also. I just used two of these on a breadboard. Doesnt look pretty but works.
 

Attachments

  • arduino CAN setup 001 (640x515).jpg
    arduino CAN setup 001 (640x515).jpg
    237.2 KB · Views: 27
Tried to install CAN-DO and help-u-solve to get the missing files in place. Failed due to a bunch of error messages during the help-u-solve installation, basically the installer crashed. Windows 8 64-bit OS. Not sure what went wrong.
Copied COMDLG32.OCX and MSCOMM32.OCX to system32, run as admin, still complaining about COMDLG32.OCX missing...

Anyway, I will try to make a binary file of generated CAN messages.

How do you compute your check byte?
 
Back
Top