Dynamically get the model of Arduino

Hi! I'm working in a application to communicate with my Arduino from Python (using USB interface). It's working on the Arduino UNO. Now I want to make it work on a Mega, and I'm facing a problem: the Mega has many more things that the Uno. If a Python program is written for a Mega, but connected to an Uno, it should report an error!. Or work until the Python program try to use something that the connected Arduino doesn't have (I mean, let it work if the program try to use digital pin 1 to 13, but raise an Exception if try to use digital pin 20).

One possible solution: is there any way to check if the hardware has a given port? I would like to do something like:

if(digitalPortExists(99)) {
  // OK. It's safe to use digital port 99
} else {
  // Send ERROR message to PC.
}

Another way would be to send, from the Arduino, what kind of Arduino the program is running on. With this information, I would be able to do the check in Python. And I think this would be better, since knowing the Arduino model, I would be able to check digital and analog pins, what pins have support for PWM, how many interrupts, etc. Something like:

char* model = getArduinoMode();
Serial.write(model);

The project I'm working on is at https://github.com/hgdeoro/py-arduino-proxy. The project is a "low level" communication library, but I've crated a simple UI to better illustrate the possibilities:

http://www.youtube.com/watch?v=yM1ZaTFAZwc.

Thanks in advance! Horacio

When python is talking to your Arduino you have defined an protocol between these two. You should add a getType request in the protocol and your application on the Arduino can return its type as follows:

#include    "avr_cpunames.h"

char * getType()
{
  return _AVR_CPU_NAME_;
}
void setup()
{
  Serial.begin(115200);
  Serial.println(getType());
}
void loop() {}

check - C:\Program Files (x86)\arduino-0022\libraries\ArduinoTestSuite -

That won't necessarily work - the DIP package ATMega328P does not include some of the pins that the SMD package does, so the Arduino Nano has a few extra pins than the Arduino UNO or Duemilanove, while returning the same value for AVR_CPU_NAME. Of course, you could just sacrifice these pins and live with it =).

@Aeternalus Probably your right, an alternative is to define a string yourself, drawback is that you might need to change it if your code is uploaded to a slightly different platform.

#define _TYPE_ "ATMega328P MHZ16 ANA6 DIG13 PWM4 HWS1 I2C1 SPI1"   

char * getType()
{
  return _TYPE_;
}
void setup()
{
  Serial.begin(115200);
  Serial.println(getType());
}
void loop() {}

Or even more work

struct ArduinoType
{ 
  char name[20];
  uint8_t analogPins;
  uint8_t digitalPins;
  uint8_t pwmPins;
  uint8_t voltage;      // whole part only
  uint16_t RAM;         // KB
  uint16_t EEPROM;   // KB
  uint8_t SerialPorts;
  uint8_t clockSpeed;
 // ..
} myArduino = { _AVR_CPU_NAME_, 6, 13, 4, 5, ... }

Thank you for your replies! I like the AVR_CPU_NAME option (less work!), but I'm looking for a way to avoid sacrifice any port. I'll test the struct option.

Thanks again! Horacio

For digital pins, I've found how to validate if a given pin is valid (pinMode() function in arduino-0022/hardware/arduino/cores/arduino/wiring_digital.c):

uint8_t port = digitalPinToPort(pin);
if (port == NOT_A_PIN) return;

digitalPinToPort() and NOT_A_PIN are defined in pins_arduino.c, I think I should be safe to use it.

I've tested digitalPinToPort() and it isn't working...

    int pin = atoi(received_parameters[1]);
    uint8_t port = digitalPinToPort(pin);
    if (port == NOT_A_PIN) {
        send_invalid_parameter_response(1);
        return;
    }

I've sent "99" as pin value, and no error was reported :-(

./bin/ipython_session.py /dev/ttyACM0 

Launching IPython shell...

Available variables:
 - proxy: the ArduinoProxy instance.
 - options, args: parsed argument options.

To import ArduinoProxy class:
>>> from arduino_proxy import ArduinoProxy

Enter 'quit()' to exit.

In [1]: proxy.ping()
Out[1]: 'PING_OK'

In [2]: proxy.digitalRead(12)
Out[2]: 1

In [3]: proxy.digitalRead(99)
Out[3]: 1

Returning AVR_CPU_NAME to Python was very easy to implement. Will use that at least for doing some controls. Thanks again!

./bin/ipython_session.py /dev/ttyACM0 
(...)
Enter 'quit()' to exit.

In [1]: proxy.getAvrCpuType()
Out[1]: 'ATmega328P'

digitalPinToPort(pin) is a macro reading from array/memory, any number greater than the arraysize is meaningless as it reads from somewhere.

Ahhh... thank you! To fix that, I've tried to use sizeof() with the array digital_pin_to_port_PGM (the macro uses this array), to verify if the pin points to an array element, but haven't worked.

I get the error when try to compile: py_arduino_proxy.cpp: In function ‘void setup()’: py_arduino_proxy.cpp:812: error: invalid application of ‘sizeof’ to incomplete type ‘const uint8_t []’

I think I'll stick to the AVR_CPU_NAME solution...

Thanks again! Horacio

In the "avr_cpunames.h" several types are mapped to a single name, you can improve on that.

What do you think of creating an include file "ArduinoType.h" like this? // !! the numbers are not checked !! Maybe ArduinoCapabilities.h is better name? other?

// 
//    FILE: arduinoType.h
// VERSION: 0.1.00
//    DATE: 2011-05-18
// PURPOSE: Specify Arduino Capabilities
//
//   URL: http://arduino.cc/forum/index.php/topic,61528.msg445142.html#msg445142
//
// HISTORY:
// 2011-05-18 initial version
// 
#ifndef ArduinoType_h
#define ArduinoType_h

#include "WProgram.h"
#include "avr_cpunames.h"
#include "inttypes.h"

struct ArduinoType
{ 
  char name[20];
  uint8_t analogPins; 
  uint8_t digitalPins;  // excl analog pins that can be used as digital ones
  uint8_t pwmPins;
  uint8_t voltage;      // whole part only
  uint16_t RAM;         // KB
  uint16_t EEPROM;      // KB
  uint8_t SerialPorts;
  uint8_t clockSpeed;   // *10e6
} 
thisArduino =

#if defined (__AVR_AT94K__)
{  
  _AVR_CPU_NAME_ , 1, 2, 3, 5, 8, 2, 1, 8 }; // not checked

#elif defined (__AVR_ATmega328P__)
{ 
  _AVR_CPU_NAME_ , 6, 13, 4, 5, 32, 2, 1, 16}; // not checked

#elif defined (__AVR_ATmega644__)
{ 
  _AVR_CPU_NAME_ , 6, 13, 4, 5, 32, 2, 1, 16}; // not checked

#elif defined (__AVR_ATmega1280__)
{ 
  _AVR_CPU_NAME_ , 6, 13, 4, 5, 32, 2, 1, 16}; // not checked

#elif defined (__AVR_ATmega2560__)
{ 
  _AVR_CPU_NAME_ , 6, 13, 4, 5, 32, 2, 1, 16}; // not checked

//      
// optional more
//
#else
{ 
  "UNKNOWN" , 0, 0, 0, 0, 0, 0, 0, 0}; //  checked

#endif 

#endif // ArduinoType_h
//
// END OF FILE

so you can do

#include "avr_cpunames.h"
#include "arduinoType.h"

void setup()
{
  Serial.begin(115200);
  Serial.println(thisArduino.name);
  Serial.println(thisArduino.analogPins, DEC);
  Serial.println(thisArduino.digitalPins, DEC);
  Serial.println(thisArduino.pwmPins, DEC);
  // etc
}

void loop()
{}

I'll try the ArduinoType struct, validate the pins in the 'python' side, and make this validation optional (turned on by default)...

Thanks robtillaart!

Hi Horacio,

Beware this is still not 100% but for now I think it is quite usable, once filled in it will fill the struct compile time.

Are there fields missing?

Rob

I'd like to have one more thing: pwmPins "list" instead of the "number of". With this "list", I'd be able to validate analogWrite(). I'll try to do that, maybe with a "bitmap" (for size concerns). With a 64bits, 8 bytes array, it should be doable. Or may be less bits, since PWM support is at the "lower" pins... the Arduino Mega, Uno, etc. have PWM support on pins less or equals to 13... with 2 bytes will be fine. Something like:

// 
//    FILE: arduinoType.h
// VERSION: 0.1.00
//    DATE: 2011-05-18
// PURPOSE: Specify Arduino Capabilities
//
//   URL: http://arduino.cc/forum/index.php/topic,61528.msg445142.html#msg445142
//
// HISTORY:
// 2011-05-18 initial version
// 
#ifndef ArduinoType_h
#define ArduinoType_h

#include "WProgram.h"
#include "avr_cpunames.h"
#include "inttypes.h"

struct ArduinoType
{ 
  char board_name[20];
  uint8_t analogPins; 
  uint8_t digitalPins;  // excl analog pins that can be used as digital ones
  uint16_t pwmPinsBitmap;
  uint8_t voltage;      // whole part only
  uint16_t RAM;         // KB
  uint16_t EEPROM;      // KB
  uint8_t SerialPorts;
  uint8_t clockSpeed;   // *10e6
} 
thisArduino =

#if defined (__AVR_AT94K__)
{  
  _AVR_CPU_NAME_ , 1, 2, 0x00, 5, 8, 2, 1, 8 }; // not checked

#elif defined (__AVR_ATmega328P__)
{ 
  // PWM pins on Uno: 3, 5, 6, 9, 10, 11
  _AVR_CPU_NAME_ , 6, 13, 0x00 | 1<<3 | 1<<5 | 1<<6 | 1<<9 | 1<<10 | 1<<11, 5, 32, 2, 1, 16}; // not checked

#elif defined (__AVR_ATmega644__)
{ 
  _AVR_CPU_NAME_ , 6, 13, 0x00, 5, 32, 2, 1, 16}; // not checked

#elif defined (__AVR_ATmega1280__)
{ 
  // PWM pins on Mega: 2 to 13
  _AVR_CPU_NAME_ , 6, 13, 0x00 | 1<<2 | 1<<3 | 1<<4 | 1<<5 | 1<<6 | 1<<7 | 1<<8 | 1<<9 | 1<<10 | 1<<11 | 1<<12 | 1<<13, 5, 32, 2, 1, 16}; // not checked

#elif defined (__AVR_ATmega2560__)
{ 
  // PWM pins on Mega: 2 to 13
  _AVR_CPU_NAME_ , 6, 13, 0x00 | 1<<2 | 1<<3 | 1<<4 | 1<<5 | 1<<6 | 1<<7 | 1<<8 | 1<<9 | 1<<10 | 1<<11 | 1<<12 | 1<<13, 5, 32, 2, 1, 16}; // not checked

//      
// optional more
//
#else
{ 
  "UNKNOWN" , 0, 0, 0x00, 0, 0, 0, 0, 0}; //  checked

#endif 

#endif // ArduinoType_h
//
// END OF FILE

Good idea the PWM bitmap! however is 16 bits enough?

The largest pin with PWM support is pin 13 on Arduino Mega... 16 bits will work ok, until some Arduino board implements support of PWM on a pin larger than 15, but I'd be easy to change.

I've added the struct to PyArduinoProxy, it's working great!. I've kept the information I need to do the validation... From the linux shell:

$ ./examples/arduino_type.py /dev/ttyACM0 
CPU: ATmega328P
{'analog_pins': 6,
 'digital_pins': 14,
 'eeprom_size': 1,
 'flash_size': 32,
 'pwm_pins_bitmap': '111001101000'}

From a python program:

proxy.getAvrCpuType() -> to get the CPU type
proxy.getArduinoTypeStruct() -> to get a dictionary with the info of arduino_type

Here can see the source code https://github.com/hgdeoro/py-arduino-proxy/blob/master/src-c/arduino_type.h.

Again, thank you!!

Have you tested in under win7 too?

Q: why did you remove parts from the .h file? I can imagine it is not used in your app but it makes the concept less usable, and it will take only a few extra bytes?

(no hard feelings, just want to keep the idea useable for as many apps as possible).

Nop... I haven't tested in any versions on Win (I don't even know how to specify a serial port in Windows from Python)... I you are interested, I could try to make it work on Windows XP (once workig on XP, it should work on 7 too). I think this shouldn't be so difficult...

Is the python script also on git?

Serial in python should be platform independant (OK download the right py_serial)

Yes, everything is on github: 1) all the python code (ArduinoProxy) - on the src directory. 2) a pre-generated sketch (on the pde directory). 3) on the scripts all test cases and the scripts to re-generate the sketch (for facilitate the creation of your own methods, and re-generate the sketch for support of optionales componentes, currently: support for LCD display). Currently, these scripts are shell script, highly tighted to Linux shell.