Wednesday, August 17, 2016

tiny45 Programming through an Arduino, Part 3

...and the last lap?  We would like to use an Arduino as a generic AVR programming device: leverage the low-end tools that come with Linux and the Arduino IDE to do stuff the gods of high-level programming never intended.

---------------

And there are other benefits of programming on the bare metal this way, if one's willing to do a little homework.  It is certainly cheaper:  a bare Mega328 costs $5 or less, as compared to $10 for the cheapest possible Arduino; and the tiny45 can be bought in volume at $0.99 a device!  And knowing a little bit more about how AVRs work, and how the programming tools do their job, can't hurt.

One of the problems with the Arduino IDE is it DOES bloat the code a bit; this isn't a problem on most Arduinos, which have a relatively large amount of flash and static RAM compared to the size of the bootloader and the more routine sample programs.  On the older and smaller devices, though, program and static RAM space is a major issue.

  /*
Blink for tiny45
 */
 //
 // NOTES for uploading:  use the ATtinyCore Universal library
 // (http://drazzy.com/package_drazzy.com_index.json)
 //   with ATtiny x5 series, then the chip, speed, etc. and use the
 //  Arduino/Leo as ISP (ATtiny) as the programmer.
 //
 //   You can also change the internal clock, B.O.D., voltage
 //  standard fuses by choosing the option
 //  you want and choosing 'Burn Bootloader'.  Be Careful!
 //
 //
#define BLINKPIN 1

// the setup function runs once when you press reset or power the board
void setup() {
  // initialize ATtiny45 PB1 as an output.
  pinMode(BLINKPIN, OUTPUT);

}

// the loop function runs over and over again forever
void loop() {
  delay(500);
  digitalWrite(
BLINKPIN, HIGH);   // turn the LED on (HIGH is the voltage level)
  delay(500);              // wait for a second
 
digitalWrite(BLINKPIN, LOW);
}

This is about as simple a program as you can write on an AVR, no matter how you do it....and with the IDE you still end up using 808 bytes of program space and 9 bytes of RAM.  I suspect that the delay() function takes up the bulk of that.
-----------
A few days earlier, I'd spent about half a day installing and cramming the WinAVR package on an old Dell Inspiron 7000 (Windows 2000, 128 MB of RAM, 4GB HD!), because it was one of the few PC's my junk pile furnished with a useful parallel port.  Up until I discovered the Arduino I had been using AVR assembler, Atmel's AVR Studio on a PC, and an old homebuilt SP12 programmer for all my ancient AVR devices.  I have several AT90S2313's and 8515's, a mega16, a mega8515, and a number of tiny26's (ancestor of the 261/461/861), all of which I wanted to continue to use.

I didn't have too much trouble working with WinAVR, and in the process managed to muddle through the rather complicated 'demo.c' program on the AVR-libc support site.....which example DOES (more or less), implement the 'fader' program in my previous post on this subject.  I modified the demo, working over the various macros embedded in the 'iocompat.h' file, and got it to work with the PWM stuff on the tiny26.  All good....

But...I didn't want to lug out that old clunk programmer and yet another computer when I wanted to use my old uC's, dammit!  And I wanted to use Linux as well...so what next?

While using the WinAVR stuff, I used the command line, as illustrated in the AVR-libc tutorials for 'demo.c' above, to do everything by hand, like so:

Insp7000 c:\code\> avr-gcc -g -Os -mmcu=attiny26 demo.c -o demo.o
Insp7000 c:\code\> avr-objcopy -j .text -j .data -O ihex demo.o demo.hex

This built me a 300+ byte file in 'demo.hex', and with the SP12 dongle I uploaded it like so:

Insp7000 c:\code\> avrdude -p t26 -c sp12 -U flash:w:demo.hex

(Notice that avr-gcc and AVR-libc know the ATtiny26 as 'atttiny26', while avrdude believes it is called 't26'.  Gotta keep our wits about us, kids...)

Which was probably no more that 70-80 bytes on the tiny26....Impressive!  And more to the point, it worked.  And note that the much simpler 'blink' program for the tiny45 above, letting the Arduino IDE do the heavy lifting, was 10 times the size.

Size matters; the tiny26 has 2K of flash, while the Mega328P (the core of the Arduino Uno) has 32K to waste.  We can do better, for not much in the way of pain.

--------------

So, this last example should give us some proper hackerly ideas, right?  Avr-gcc and avrdude are already there with the Arduino IDE installed; and unlike the IDE, avrdude has the full pin descriptions and functions defined for EVERY AVR clear back to the microcontroller stone age.  In theory, at least, if we have the ArduinoISP software loaded up on the Uno, we should be able to make the Arduino program ANY AVR device, as long as AVR-libc and avrdude know the device exists.

First, let's look in 'programmers.txt'  in the Arduino IDE install folder (<folder>/hardware/arduino/avr/) and see what kind of directives are sent to avrdude for the 'Arduino as ISP' programmer defined there:

arduinoasisp.name=Arduino as ISP
arduinoasisp.communication=serial
arduinoasisp.protocol=stk500v1
arduinoasisp.speed=19200
arduinoasisp.program.protocol=stk500v1
arduinoasisp.program.speed=19200
arduinoasisp.program.tool=avrdude
arduinoasisp.program.extra_params=-P{serial.port} -b{program.speed}


And so we see.  Unlike when hitting the Arduino directly through the IDE, we must talk DIRECTLY to the serial.port (not with the pseudodevice 'usb'), as well as define a baud rate to talk through the Arduino (as if it was a serial port itself rather than the destination for the code we want to write).

So that's one piece of the pie.  The other piece is....which programmer do we use on the avrdude command line if we aren't going out on the parallel port with the SP12 dongle?  Entering 'avrdude -c ?' gives us a list of programmers that it knows about:

Valid programmers are:
  2232HIO          = FT2232H based generic programmer
  4232h            = FT4232H based generic programmer
  89isp            = Atmel at89isp cable
  abcmini          = ABCmini Board, aka Dick Smith HOTCHIP
  alf              = Nightshade ALF-PgmAVR, http://nightshade.homeip.net/
  arduino          = Arduino
  arduino-ft232r   = Arduino: FT232R connected to ISP
  atisp            = AT-ISP V1.1 programming cable for AVR-SDK1 from <http://micro-research.co.th/>
  atmelice         = Atmel-ICE (ARM/AVR) in JTAG mode
  atmelice_dw      = Atmel-ICE (ARM/AVR) in debugWIRE mode
  atmelice_isp     = Atmel-ICE (ARM/AVR) in ISP mode
  atmelice_pdi     = Atmel-ICE (ARM/AVR) in PDI mode
  avr109           = Atmel AppNote AVR109 Boot Loader
  avr910           = Atmel Low Cost Serial Programmer
  avr911           = Atmel AppNote AVR911 AVROSP
  avrftdi          = FT2232D based generic programmer

...... (and so on for a page or two) ......

The only one the looks even close is, well 'arduino', so let's use the command line options we found above in combination with what we've got here, and just try talking to the gadget and see:

mymachine:$avrdude -p t45 -c arduino -P /dev/ttyACM0 -b 19200
avrdude: AVR device initialized and ready to accept instructions
Reading | ################################################## | 100% 0.01s
avrdude: Device signature = 0x1e9206
avrdude: safemode: Fuses OK (E:FF, H:DF, L:E2)
avrdude done.  Thank you.


So wow, it just works!  That is, indeed, the correct sig for the ATtiny45.  The Arduino Uno passes our request right through using the ArduinoISP software (with the jumpered on capacitor across the Reset line) to get to our 'legacy' device.

So now, how about a new version of the blink program?  Let's see here:

/*
   test blink program to make sure know what am doing with
avr-gcc and such.

   for tiny45 through Arduino as programmer?

*/
#include <inttypes.h>
#include <avr/io.h>

#define F_CPU 8000000UL   // 8Mhz on t45 - this needs to be defined BEFORE including util/delay.h

#include <util/delay.h>        //  for _delay_us(double) and _delay_ms(double)

        // handy stuff from Arduino.h
#define HIGH 0x1
#define LOW  0x0

#define INPUT 0x0
#define OUTPUT 0x1
#define INPUT_PULLUP 0x2
#define lowByte(w) ((uint8_t) ((w) & 0xff))
#define highByte(w) ((uint8_t) ((w) >> 8))

#define bitRead(value, bit) (((value) >> (bit)) & 0x01)
#define bitSet(value, bit) ((value) |= (1UL << (bit)))
#define bitClear(value, bit) ((value) &= ~(1UL << (bit)))
#define bitWrite(value, bit, bitvalue) (bitvalue ? bitSet(value, bit) : bitClear(value, bit))

#define BLINKPIN 0x01   // assume port B for t45


    // sorta like setup()
void ioinit(void) {
    bitSet(DDRB, BLINKPIN);  // set B1 as OUTPUT  -- like DDRB |= (1 << BLINKPIN);
}

    // and can more or less pretend this is loop()
int main(void) {

    ioinit();

    for (;;) {
        _delay_ms(500.0);
        bitClear(PORTB, BLINKPIN);  //..or PORTB &= ~(1 << BLINKPIN);
        _delay_ms(500.0);
        bitSet(PORTB, BLINKPIN);    //..or PORTB |= (1 << BLINKPIN);
    }
}

// end testblink.c


Compile it with avr-gcc and use avr-objcopy to create the hex file, then avrdude can write it.  100 bytes used total!

So how did I concoct the above?  Among other things, I looked for (and at) the main header files that the IDE loads when you create a new program...i.e. at the Arduino.h file, which is stored at <IDE install folder>/hardware/arduino/avr/cores/.  LOTS of handy macros in there, and a nice list of other files that are included in all IDE builds by default.  Since we are trying to program on the bare metal, making as few assumptions as possible about the device, we can muck and pare at that list a bit, drill down through a few #includes to find what they do and what we need, guess what we don't need.  Eventually we can just go with:

#include <inttypes.h>
#include <avr/io.h>

#define F_CPU 8000000UL   // 8Mhz on t45 - this needs to be defined BEFORE including util/delay.h

#include <util/delay.h>        //  for _delay_us(double) and _delay_ms(double


There was one gotcha in my first attempt at this:  avr-gcc bitched incessantly that F_CPU was NOT something it knew about, thus the #define for the CPU frequency there....I tried just including util/delay.h without it, and immediate had a wake up call.  OBVIOUSLY (right?  right?) the delay functions wouldn't work right if they were based on the clock speed of the device, and didn't know what that number WAS.  An arbitrary AVR device can't be assumed to have an RTC or a PLL clock built-in, so the only other way to have a time base is to run it off the CPU clock, and....

IN any case...paring the #includes down to a bare minimum shows us what we can safely leave out, while still giving us some nice short cuts, and a handy template for building more complicated stuff later on.


Tuesday, August 9, 2016

Tiny45 programming with the Arduino IDE - ver 2

This post is Round Two of my quest to use my older AVR chips using the Arduino IDE as a programming platform and an Arduino Uno as a programmer ..

Originally, I was using a library (written originally for Arudino IDE 1.0.x) by David Ellis, from back in 2005.  This DID work, for the fader program below and in the previous post, but that library only supported a few chips, and I was worried that as I tried to do more advanced things (using the ADC, SPI, or I2C stuff) I would run into problems.  And I would like to buy/use the tiny44/84, 2313/4313, 861, and others eventually as well.

In any case...Spence Konde has created a 'modern' ATtiny library that has a lot more chips and functional support.  To get it, open the Arduino IDE and then hit File/Preferences.  Click on the little 'edit' button to the right of the 'Additional Boards Manager URLs:' field, and cut and paste the following...

...onto the list of sources for third-party board descriptions.  Hit OK, then OK again on the Preferences dialog.  Then go to the Tools/Board.../Board Manager (at the very top of the list)....scroll around until you find the ATtinyCore Modern package by Spencer Konde (ver 1.1.1 is the latest).  Highlight and install it, and then quit the Board Manager.

Now, BEFORE you start writing a program for an ATtiny, make sure the Ardiuno ISP software is loaded on the Arduino, which is Example 11 in the File/Examples menu.  Burn that, and then open/write your ATtiny program.  When you are ready to burn your ATtiny....

You'll now have LOTS of new options under Tools/Board below the standard Arduinos....Choose the board series (ATtinyx5 in my case) off from the ATtiny list, and then you will have the B.O.D./power options (disable if yer not sure),  Timer 1 Clock (not sure so I dint mess with it), Chip selection (tiny45), and Clock (lots of new options, but mine was already set on 8Mhz internal).

To burn a program (like 'fader' below)...wire up the ISP pins from the Arduino to the chip in question.  Spencer Konde also recommends a 0.1 uF capacitor across the VCC/GND on the chip just for safety sake.  Next, make sure you choose the Arduino/Leo as ISP (ATtiny) off the programmer list.  Other options may work, but I know that one does the job.

Lastly...if you need to know all about this library, Spencer Konde has an excellent README for it here:

ATTinyCore/README.md at master · SpenceKonde/ATTinyCore · GitHub

  /*
 LED Fader for tiny45
 */
 //
 // NOTES for uploading:  use the ATtinyCore Universal library
 // (http://drazzy.com/package_drazzy.com_index.json)
 //   with ATtiny x5 series, then the chip, speed, etc. and use the
 //  Arduino/Leo as ISP (ATtiny) as the programmer.
 //
 //   You can also change the internal clock, B.O.D., voltage
 //  standard fuses by choosing the option
 //  you want and choosing 'Burn Bootloader'.  Be Careful!
 //
 //
#define FADEPIN 1

unsigned char fcnt = 0;
char fpause = 7;and
char updn = 1;

// the setup function runs once when you press reset or power the board
void setup() {
  // initialize digital pin 13 as an output.
  pinMode(FADEPIN, OUTPUT);
}

// the loop function runs over and over again forever
void loop() {
  fcnt += updn;
  analogWrite(FADEPIN, fcnt);   // turn the LED on (HIGH is the voltage level)
  delay(fpause);              // wait for a second
  if (fcnt > 254) updn = -1;
  if (fcnt < 1) updn = 1;
}

Sunday, August 7, 2016

i2c 2 Pi v0.6 - Arduino i2c Slave

i2c 2 Pi - Arduino i2c Slave

(Go back to i2c article)

// new i2c to Pi transceiver
//
// 8/2/16:0837 - version 0.51
// 8/3/16:0832 - version 0.6
//

#include <Wire.h>
#include <Servo.h>
#include <string.h>

#define SLAVE_ADDR 0x08
#define END_OF_STR '\x00'
#define SIG_BASE 0xA0
#define MAXSTR 20
#define CMD 1
#define CEND 2
#define CGET 4
#define CDATA 5
#define CDEND 6
#define WSLCT 7
#define WDATA 8
#define WDEND 9

#define DEBUG 1

const int flashpin = 13;

char recvStr[MAXSTR + 1];

char cdat[MAXSTR + 1];
int ccnt = 0;
unsigned char cmode = CMD;
    //
    // pin/function maps
    // make sure pins are excluded from operations that are NOT good
    // there MUST be a -1 at the end of each list unless ALL possible pins are included
    //
    // note: can really only control three analog devices at once!
    //    if pins 5 and 6 are used for PWM, the delay() and millis() functions won't work right.
    //    ...so pins 5,6 must be same freq; 9,10 same; 3,11 same
    //
    //Timer output  Arduino Uno pins
    //OC0A          6  
    //OC0B          5  
    //OC1A          9  
    //OC1B          10   
    //OC2A          11   
    //OC2B          3  
    //
const unsigned char alimit = 6, dlimit = 14;
unsigned char algList[6] = {0, 1, 2, 3, -1};      // can be pins 0-3 (avoid SCL/SDA)
unsigned char digiList[14] = {2, 4, 7, 8, 10, 12, -1};// can be pins 2-13 (avoid serial ports)
unsigned char digoList[14] = {13, -1};            // can be pins 2-13 (avoid serial ports)
unsigned char mtrList[6] = {6, -1};               // can be any of 3,5,6,9,10,11 but see above
unsigned char srvList[6] = {9, -1};               // can be any of 3,5,6,9,10,11 but see above
unsigned char tonList[6] = {11, -1};              // can be any of 3,5,6,9,10,11 but see above
                                                  // ...but no pwm on 3 or ll if tone is on
Servo servo[alimit];

void setup() {

  if (DEBUG == 1) {
    Serial.begin(9600);
  }
    // setup I2C stuff
  Wire.begin(SLAVE_ADDR); 
  Wire.onRequest(sendData);
  Wire.onReceive(receiveData);
  strcpy(recvStr, "K");
  cdat[0] = '\0';
  
    // init servos and wake them up
  for (int i = 0; i < alimit; i++) {
    if (srvList[i] == -1) break;
    else {
      servo[i].attach(srvList[i]);
      servo[i].write(5);
      delay(500);
      servo[i].write(150);
      delay(500);
      servo[i].write(90);
    }
  }
    //init signal LED
  pinMode(flashpin, OUTPUT);
  digitalWrite(flashpin, LOW);
  delay(200);
  digitalWrite(flashpin, HIGH);
  delay(200);
  digitalWrite(flashpin, LOW);
}

void loop() {
      //
      // see if there is a command to process
      //
  if (strcmp(recvStr, "K") != 0) {
      // let us know somethings happening
      //
      DebugOut("Data rcvd:");
      DebugOut(recvStr);
      //
    //do something with received command
    if (recvStr[0] == 'S' && strlen(recvStr) >= 4 ) {
      char rtmp[6];
      int svnum = ((int) (recvStr[1])) - 48;
      unsigned char pinOK = pinCheck('s', svnum);
      if (pinOK != -1) {
        int slew = 90, mflag = 0;
        for (int i = 0; i < 6; i++) {
          rtmp[i] = recvStr[i+3];
          if (rtmp[i] == '\0') break;
          if (rtmp[i] == 'm' || rtmp[i] == 'M') {
            rtmp[i+1] = '\0';
            mflag = 1;
            break;
          }
        }
        rtmp[5] = '\0';
        slew = atoi(rtmp);
        if (mflag && slew > 500 && slew < 2100 ) servo[svnum].writeMicroseconds(slew);
        else if (!mflag && slew >= 5 && slew <= 160) servo[svnum].write(slew);
      }
    }  // end of servo slew

    if (recvStr[0] == 'M' && strlen(recvStr) >= 4 ) {
      char rtmp[6];
      int mtnum = ((int) (recvStr[1])) - 48;
      unsigned char pinOK = pinCheck('m', mtnum);
      if (pinOK != -1) {
        int mspeed = 0;
        for (int i = 0; i < 6; i++) {
          rtmp[i] = recvStr[i+3];
          if (rtmp[i] == '\0') break;
        }
        rtmp[5] = '\0';
        mspeed = atoi(rtmp);
        if (mspeed >= 0 && mspeed <= 255) {
            pinMode(pinOK, OUTPUT);
            analogWrite(pinOK, mspeed);
        }
      }
    }  // end of motor speed

    if (recvStr[0] == 'T' && strlen(recvStr) >= 4 ) {
      char rtmp[7];
      int newtpin, freq = -2;
      unsigned char i = 1, j = 0;
      while (j != 99) {
        switch (freq) {
        case -2:
          rtmp[j++] = recvStr[i++];
          break;
        case -1:
          rtmp[j] = '\0';
          newtpin = atoi(rtmp);
          freq = 0; j = 0;
          break;
        case 0:
          if (recvStr[i] == '\0') {
            rtmp[j] = '\0';
            if (strlen(rtmp) > 0) freq = atoi(rtmp);
            j = 99;
          } else rtmp[j++] = recvStr[i++];
          break;
        }
        if (recvStr[i] == '-') {i++; freq = -1;}
      }
      unsigned char pinOK = pinCheck('t', newtpin);
      if (pinOK != -1) {          
        
        char dummy[10];
          DebugOut("pin:");
          DebugOut(itoa(newtpin, dummy, 10));
          DebugOut("freq:");
          DebugOut(itoa(freq, dummy, 10));
        
        if (freq == 0) { pinMode(newtpin, OUTPUT); noTone(newtpin); }
        else { pinMode(newtpin, OUTPUT); tone(newtpin, freq); }
      }
    }  // end of write to digital pin

    
    if (recvStr[0] == 'F' && strlen(recvStr) >= 4 ) {
      char rtmp[6];
      int newfpin;
      for (int i = 0; i < 6; i++) {
        rtmp[i] = recvStr[i+3];
        if (rtmp[i] == '\0') break;
      }
      newfpin = atoi(rtmp);
      unsigned char pinOK = pinCheck('o', newfpin);
      if (pinOK != -1) { 
        pinMode(newfpin, OUTPUT);
        if (recvStr[1] == '0') digitalWrite(newfpin, LOW);
        else digitalWrite(newfpin, HIGH);
      }
    }  // end of write to digital pin
      // and reset the data for next time 'round..
    strcpy(recvStr, "K");
  }
}

void DebugOut(char *dbgstr) {
  if (DEBUG == 1) {
    Serial.println(dbgstr);
  }
}

unsigned char pinCheck(char ptype, unsigned char thepin) {
  unsigned char res = -1;
  switch (ptype) {
    case 'i':             // digital read
      for (int i = 0; i < dlimit; i++) {
        if (digiList[i] == -1) break;
        else if (digiList[i] == thepin) { res = i; break; }
      }
      break;
    case 'o':             // digital write
      for (int i = 0; i < dlimit; i++) {
        if (digoList[i] == -1) break;
        else if (digoList[i] == thepin) { res = i; break; }
      }
      break;
    case 't':             // tone output
      for (int i = 0; i < dlimit; i++) {
        if (tonList[i] == -1) break;
        else if (tonList[i] == thepin) { res = i; break; }
      }
      break;
    case 'a':             // analog read
      for (int i = 0; i < alimit; i++) {
        if (algList[i] == -1) break;
        else if (algList[i] == thepin) { res = i; break; }
      }
      break;
    case 'm':             // pwm output
      for (int i = 0; i < alimit; i++) {
        if (mtrList[i] == -1) break;
        else if (thepin == i) {res = mtrList[i]; break; }
      }
      break;
    case 's':             // servo output
      for (int i = 0; i < alimit; i++) {
        if (srvList[i] == -1) break;
        else if (thepin == i) { res = i; break; }
      }
      break;
    default:
      res = -1; break;
  }
  return res;
}

int getSensor(int isens, char sdata[MAXSTR+1]) {

  char tstr[5];
    // debugging
      itoa(isens, tstr, 10);
      DebugOut("Data req:");
      DebugOut(tstr);
    //
  if (isens > 96 && isens < 103) {
    // read analog pin senscode - 97
    unsigned char pinOK = pinCheck('a', isens - 97);
    if (pinOK != -1) {
      char szdat[MAXSTR + 1] = "\0";
      strcpy(sdata, "AV:");
      int isdat = analogRead(algList[pinOK]);
      itoa(isdat, szdat, 10);
      strcat(sdata, szdat);
    }
  } else if (isens > 64 && isens < 85) {
    unsigned char pinOK = pinCheck('i', isens - 65);
    if (pinOK != -1) {
      int isdat;
      char szdat[2];
      strcpy(sdata, "DV:");
      pinMode(digiList[pinOK], INPUT);
      isdat = digitalRead(digiList[pinOK]);
      itoa(isdat, szdat, 10);
      strcat(sdata, szdat);
    }
  } else {
    strcpy(sdata, "NOP");
  }
  return strlen(sdata);
}

void receiveData(int thebytes) {
  
  char tstr[2];
  tstr[0] = (char) (48 + cmode);
  tstr[1] = '\0';
    // debugging
      DebugOut("I2C recv:");
      DebugOut(tstr);
    //
  
  while(Wire.available()) {
    char c = Wire.read();
    int ic = ((unsigned char) c);
    switch (cmode) {
      case CMD:
        switch (c) {
          case '1':
            cmode = CMD;  // check status, sort of a NOP
            break;
          case '4':
            cmode = CGET;  // data is coming
            break;
          case '7':
            cmode = WSLCT;  // data is wanted from a pin or something
            break;
        }
        break;
      case WSLCT:
        getSensor(ic, cdat);
        ccnt = 0;
        cmode = WDATA;
        break;
      case CGET:
        cdat[0] = c;
        cdat[1] = '\0';
        ccnt = 1;
        cmode = CDATA;
        break;
      case CDATA: {
        if (c == '\0') {
          ccnt = 0;
          strcpy(recvStr, cdat);
          cmode = CDEND;
            /* flash so we know something worked
          digitalWrite(flashpin, LOW);
          delay(100);
          digitalWrite(flashpin, HIGH);
          delay(100);
          digitalWrite(flashpin, LOW);
            */
        } else {
          cdat[ccnt++] = c;
          cdat[ccnt] = '\0';
        }
        break; }
      default:
        break;
    }
  }
}

void sendData() {
  
  char tstr[2];
  tstr[0] = (char) (48 + cmode);
  tstr[1] = '\0';
    // debugging
      DebugOut("I2C send:");
      DebugOut(tstr);
    //
  switch (cmode) {
    case WSLCT:
    case CDATA:
    case CGET:
    case CMD:
      Wire.write(cmode + SIG_BASE);
      break;
    case WDATA: {
      int clen = strlen(cdat);
      Wire.write(cdat[ccnt]);
      ccnt++;
      if (ccnt == clen) {
        cmode = WDEND;
        strcpy(cdat, "\0");
        ccnt = 0;
      }
      break; }
    case CDEND:
    case WDEND:
      Wire.write(cmode + SIG_BASE);
      cmode = CMD;
      break;
  }
}

Ardxcv ver 0.21 - I2C Master for Raspberry Pi

Ardxcv ver 0.21 - I2C Master for Raspberry Pi

(Go back to i2c article)

/* Peter Mount's I2C Pi Master..
 modified 7/20/16 by Pat Struthers

 general purpose i2c transceiver to Arduino
 all good so far, 15:31 7/20 adding general purpose send  
 ver0.2 (7/30) - modularization
  definition of i2ccom, etc.
 
*/
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <linux/i2c-dev.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <unistd.h>

// Arduino address
#define ARD_ADDRESS 0x08
#define MAX_STR 20
#define SIG_BASE 0xA0
#define ARD_SLEEP 10000


//proto for i2ccom function
//
// devid = i2c device address
// devname = i2c bus name 
// cmd = command string e.g "4 F1-13" to turn on PIN 13
// res = received bytes
// dbg = debug flag
//
int i2ccom(int devid, char *devname, char *cmd, char *res, int dbg);
 // return values:
 // -9: no command string given, bus and device ok
 // -1: bus not available
 // -2: device not available
 // >0, x: command x completed
 // -3: unrecognized command 

// the main deal
int main(int argc, char** argv) {
  //I2C bus: for Pi V1 use i2c-0
 char *busname = "/dev/i2c-1";
  // other inits
 char cmd[MAX_STR + 1];
 char sbuf[MAX_STR + 1] = "\0";
 int dummy;

 int debugflag = 0;
 int ardaddr = ARD_ADDRESS;

  // any command line args?
 if (argc == 1) {
  printf("ardxcv - Pi to Arduino I2C transceiver\n");
  printf("--------------------------------------\n");
  printf("get status  : ardxcv 1\n");
  printf("query pins  : ardxcv 7 <pincode>\n");
  printf("   pincode - A-F (analog), a-z (digital)\n");
  printf("send command: ardxcv 4 <data>\n");
  printf("   data - no more than 20 characters, ASCII only\n\n");
  printf("option -D      : debug\n");
  printf("option -A<addr>: address of I2C devide\n");
  printf("option -B<bus> : I2C bus number (0 or 1)\n");
  exit(1);
 } else {
  int i = 1;
  while(i < argc) {
   if (argv[i][0] == '-') {   //--handle options
    switch (argv[i][1]) {
     case 'D': 
      debugflag = 1;
      break;
     case 'A':
      ardaddr = (int)(argv[i][2]) - 32;
      break;
     case 'B':
      if (argv[i][2] == '0') busname[strlen(busname)] = '0';
      if (argv[i][2] == '1') busname[strlen(busname)] = '1';
      break; 
    }
    i++;
   } else { 
    strcpy(cmd, argv[i++]);
    if (i < argc && argv[i][0] != '-') {
     strcat(cmd, " ");
     strcat(cmd, argv[i++]);
    }
   }
  }
 } 
 if (ardaddr < 3 || ardaddr > 13) {
  printf("I2C: device address 0x%x out of range (3-13).", ardaddr);
  exit(1);
 }

  // do whatever command...
 dummy = i2ccom(ardaddr, busname, cmd , sbuf, debugflag);

  // print result
 printf("I2C: %s sent to 0x%x: status %d|result", cmd, ardaddr, dummy);
 if (strlen(sbuf) > 0) {
  printf(" %s\n", sbuf);
 } else printf(" NIL\n");
 exit (dummy);
}

// i2ccom

int i2ccom(int devid, char *devname, char *cmd, char *res, int dbg) {
 int retval = 0;
 int file;
 char sbuf[MAX_STR + 1] = "\0";
 int ccnt = 0;
 int cmode = 0;
 char cparm[MAX_STR + 1] = "\0";
 char ccmd[2];

  // see if bus is reachable
 if ((file = open(devname, O_RDWR)) < 0) {
  fprintf(stderr, "I2C: failed to access bus %s\n", devname);
  return(-1);
 } 
  // make sure device is there
 if (ioctl(file, I2C_SLAVE, devid) < 0) {
  fprintf(stderr, "I2C: failed to talk to device %x\n", devid);
  return(-2);
 }
  // get command string stuff;
 if (strlen(cmd) > 0) {
   cmode = (int)(cmd[0]-48);
  ccmd[0] = cmd[0];
  ccmd[1] = '\0';
  if (strlen(cmd) > 2) strcpy(cparm, cmd + 2);
 } else {
   // no command to process, exit and assume bus/device OK
  return(-9);
 }

 while(cmode != 0) {
   // write a byte, even if it's wrong
   //
  if (dbg != 0) printf("Mode %d before send.\n", cmode);
  switch(cmode) {
   case 1:
    write(file, ccmd, 1);
    cmode = 2;
    break;
   case 4:
    write(file, ccmd, 1);
    cmode = 5;
    break;
   case 5: {
    char stmp[2];
    stmp[0] = cparm[ccnt++]; 
    stmp[1] = '\0';
    write(file, stmp, 1);
    if (ccnt > MAX_STR || ccnt > strlen(cparm)) {
     ccnt = 0;
     cmode = 6;
    } 
    break; }
   case 6:
    write(file, "\0", 1);
    break;
   case 7:
    write(file, ccmd, 1);
    cmode = 8;
    break;
   case 8:
    write(file, cparm, 1);
    cmode = 9;
    break;
   case 9:
    write(file, "K", 1);
    break;
   default:
    cmode = 0;
    break;
  }
  usleep(ARD_SLEEP); 
   //
   // read a byte, now
   //
  char buf[2] = "\0";
  if (dbg != 0) printf("Mode %d before recv.\n", cmode);
  switch(cmode) {
   case 0:
    retval = -3;
    break;
   case 2:
    if (read(file, buf, 1) == 1) {
     if (dbg != 0) printf("Received (hex) %x\n", buf[0]);
     retval = 1;
     cmode = 0;
    }
    break;
   case 5:
   case 6:
    if (read(file, buf, 1) == 1) {
     if (dbg != 0) printf("Byte Send Acked (hex) %x\n", buf[0]);
     if (buf[0] > SIG_BASE + 5 || cmode == 6) {
      if (dbg != 0) printf("Received %x, All data sent!\n", buf[0]);
      retval = 4;
      cmode = 0;
     }
    }
    break;
   case 8:
    if (read(file, buf, 1) == 1) {
     if (dbg != 0) printf("Received (hex) %x\n", buf[0]);
    }
    break;
   case 9:
    if (read(file, buf, 1) == 1) {
     if (buf[0] >= SIG_BASE) {
      if (dbg != 0) printf("Received %x, Done getting bytes.\n", buf[0]);
      retval = 7;
      cmode = 0;
     } else {
      strcat(sbuf, buf);
      if (dbg != 0) printf("Received %x, %s so far.\n", buf[0], sbuf);
     }
    }
    break;
/*   default:
    if (read(file, buf, 1) == 1) {
     printf("Not sure why here, but..(hex) %x\n", buf[0]);
     werr = 9;
    }
    break;
*/
  }
  usleep(ARD_SLEEP);
 }
 close(file);
 if (retval == 7 && strlen(sbuf) > 0) strcpy(res, sbuf);
 return(retval); 
}



// end of ardi2c3.c

I2C communciation between Pi and Arduino/AVR - Part 1


Transfer at 100Kb/sec with two wires?  Supervise 100 Arduinos or AVRs with one Pi?  Those are the sugar plums the started dancing in my head when I began my first experimenting with Arduinos and robot stuff.

There are things the Pi does well (massive processing power, 'Jessie' Debian, very cheap) and things that the Arduino does well (very easy to program, analog sampling, motor/servo/stepper control)...so all we need is a division of labor and a good reliable way for the Pi to wave the baton, yes?

Thank you, Peter Mount..

So my first idea was to use the Gordon Henderson's wiringPi library and the I2C or SPI libraries there...but I was leaning toward I2C in any case and my various attempts at using it didn't work well.  A little Googling, though, found me this:

https://blog.retep.org/2014/02/15/connecting-an-arduino-to-a-raspberry-pi-using-i2c/

This gave me a good reason to dive into C heavy once again; it uses the i2c-tools and i2c-dev packages available with Raspbian (in 'Jessie' you have to apt-get them, though).  It's a more low-end approach, but it was a lot easier to debug and after a few weeks off-and-on, I got some good basic Pi-2-Arduino transceiver code working on both ends.

Attached below is my Docs writeup of my progress, so far....

----------------------------------------------------------------------

Introduction: I started this project because I need a good practice program to teach myself the ins-and-outs of Pi GPIO and Arduino stuff; I ultimately want to create a tracked robot that can be controlled and monitored from a web-interface. For that to work, I need a CGI program that will send commands to a navigation program, which will run a command line utility to control the motors and servos and query sensors. Since the Arduino is best for motor control, and the Pi has the horsepower to do the more complicated robot navigation and sensor processing, I decided to learn how to slave an Arduino via I2C to the bus on a Pi. Obviously I wanted a classic command-line tool that would send ONE command/query to the Arduino and receive the status/results, and print them out to STDOUT, for another program (a Ruby robot navigation script or a TCP/IP client) to use.

This system is intended as a general purpose command line tool (the Pi side) to query data pins and send arbitrary strings (that can be interpreted as commands on the Arduino side). Multiple Arduinos can be ‘slaved’ to a single I2C bus and queried separately, so that it is theoretically possible for Arduino-to-Arduino transfers to be facilitated (initiated by the Arduinos) if some additional interrupt lines were connected up and polled by the Pi to detect the requests.


The ‘test’ hardware setup includes (8/3/16)

Arduino Uno (rev. 3)
Raspberry Pi B+ with Adafruit T-Cobbler and cable
Breadboards and jumpers and things
Sparkfun BOB-1200 4x1 bit bi-directional level shifter on breakout board
Motorola MMA2200W accelerometer and support components (pin A0)
Radio Shack ‘standard servo’ (driven from ArdUno pin 9)
Tamiya planetary gear motor
IRF540 MOSFET PWM driver for above (from pin 6)
‘6V’ NiMH 4xAA battery pack for servo
‘6V’ NiMH 4xAA battery pack for motor
8 ohm speaker w/ 330 ohm resistor (pin 11)

The level shifter is the most important part of the hardware here; RaspPi uses 3.3V TTL levels, which must be converted to 5V for the Arduino. Don’t kill your Pi by connecting 5V signals to the Pi GPIO connector!

Both the Pi and the Arduino Uno have internal pullups on their I2C pins, so no external pullup/debouncing components are needed. SDA and SDL pins (A4 and A5 on the Ard Uno) are connected together on the opposite of the (correctly powered) level shifter, and the rest comes naturally.

The Motorola accelerometer produces 0-5V corresponding linearly to -50G to +50G, with 0G at 2.5V in theory. My particular device reads slightly low, or….my table may be slightly unlevel. The output for the MMA2200W is connected to analog pin 0 on the Ard Uno.

The ‘standard servo’ is connected to digital pin 9, which also has PWM output capability; a separate battery pack is used to power the servo. BE SURE to connect the negative side of the battery pack to the Arduino/Pi common ground plane!

The software (8/3/16)


Raspbian ‘jessie’
      w/ i2c-dev, i2c-tools, and wiringPi packages installed
Arduino IDE 1.6.9
‘Ardxcv<ver>’ - Pi-side C i2c ‘master’ program
‘i2c-2-pi-<ver>’ - Arduino-side ‘slave’ program

Needless to say, the i2c bus must be enabled on the Pi. You can test it with the wiringPi ‘gpio’ tool by entering ‘gpio i2cd’, which should give you a bus map with all the devices on it if everything is setup right. But the ‘ardxcv’ (see below) program is not otherwise wiringPi dependent, and there are other ways to do testing, ferinstance...

You ALSO need to install the i2c-tools package for the Pi, so that you will have the ‘i2c-dev’ library available. You can query the i2c bus on a newer pin with ‘ic2detect -y 1’ if you don’t have wiringPi installed.
------------------------------------------------------

8/2/16 - Currently there are two pieces of software: ‘ardxcv21’ on the RaspberryPi side, and ‘i2c_2_pi_5’ on the Arduino side.

Click here to see ver.0.21 of the code

ardxcv21 : compile like so: ‘gcc ardxcv21.c -o ardxcv21’, and run as root (e.g. ‘sudo ./ardxcv21’). Currently there are three basic commands and three command line options:

‘ardxcv21 1’ - check device status of ‘standard device’ (I2C address 0x08); should return status 1|result NIL’ if it talks to the Arduino ok.

‘ardxcv21 4 <string>’ - send an arbitrary string (limited to 20 characters at the moment) to the Arduino. Two <string> combinations are currently set up to ‘do’ something:

‘Fx-yy’ - sets (x=1) or clears (x=0) digital pin yy.

‘Sy-xxx’ - slew servo y to xxx degrees.

‘Sy-xxxxM’ - slew servo y with xxxx microsecond pulses

Any other string is ignored. Example? ‘ardxcv21 4 F1-13’ to light the onboard LED on the Uno.

‘ardxcv21 7 <pincode>’ - query an analog or digital pin. ‘pincode’ can be ‘a-f’ for analog pins (corresponding to A0-A5) or ‘A-Z’ (for digital pins 0-25). The result string will be something like ‘status 7|result AV:xxxx’ where xxxx is the analog result, or ‘DV:x’ where x is the digital (0 or 1) state of the pin. Example: ‘ardxcv 7 a’ to get the accelerometer reading from pin A0. NOTE: DO NOT query A4 or A5 (e or f) since those are the SDA/SCL pins for I2c! I will add an ‘exclusion table’ to the Arduino-side code so that the Uno will ignore pins you shouldn’t be messing with for certain projects.

option -D: Debug mode. Extra information is printed to STDIO to show the actual signals and data going back and forth during a transfer.

option -Ay: Device ID, default is 8. Talk to I2C device on address y. Currently limited to 3-13, cuz I said so.

option -By: PI I2C bus device. Defaults to 1, can be ‘-B0’ for older revisions of the Pi I2C bus hardware.

Return status codes (non-error, non-debug):

‘I2C: <command> sent to 0x<device id>: status <status code>|result <data>’

-9: bus and device are ok, but no valid (one of 1, 4, 7) command arguments given.
-3: unrecognized command.
-2: i2c device not available at given address
-1: i2c bus not available
1: command ‘1’ (status) returned success
4: string write to Arduino successful
7: pin query successful (result follows)

------------------------------------------------------

i2c_2_pi_5: the Arduino-side code. Currently this has a debugging mode (set the DEBUG #define to 0 to turn it off) that writes data to the serial port, so that you can monitor the Arduino during transfers...but this hogs a considerable amount of code and static RAM space, and it will be cut out eventually.

As described in the intro material, the hardware setup currently has the servo connected to pin 9 for PWM fun, and the accelerometer to A0. Pin 13, the onboard ‘fun’ LED, flashes on and off when data is received with the ‘4’ command, and also flashes very fast when an analog or digital pin is queried with the ‘7’ command.

Click here to see ver 0.6 of the code

------------------------------------------------------

8/3/16 -
Updated Arduino code, new version:

One of the worries, when I got this far, was that I did NOT know if the Arduino would actually be able to handle multiple PWM/analogOut devices at the same time. The Uno/Mega328P has three timers, though, so….and it turns out that this works, with some caveats. Look at the following table

Timer output   Arduino Uno pins:
OC0A              6
OC0B              5
OC1A              9
OC1B             10
OC2A             11
OC2B              3



Thanks to Ken Shiriff for his Arduino PWM writeup here:

http://www.righto.com/2009/07/secrets-of-arduino-pwm.html

What this all means is….pins 5&6 are controlled by timer 0 (which also supplies the timing for the delay() and millis() functions), 9&10 by timer 1, and 3&11 by timer 2. While there ARE six total PWM output pins, you can only generate three truly independent base frequencies. You can play with prescaler settings and things down on the bare-metal AVR level, so you CAN get different duty cycles to two pins off one timer, but...there are other gotchas.  If you want to use 5 or 6 at all, you can’t use delay()/millis() for accurate timing. Luckily, the ADC and the various serial communications interfaces (SPI, I2C, etc.) are not affected by PWM output on any of these pins.

The tone() function additionally MUST use the OC2 timer, so you might as well hook up any sound device to 3 or 11 since those pins won’t work as analog outputs while a tone is being generated anyway.

In any case, and without further ado:

i2c_2_pi_6: added ‘pin lists’ and some error detection and bounds checking so that pins and servos will not be accidently driven that should NOT be. Added the PWM motor control and Tone commands, which can be sent from ‘ardxcv21’. Ferinstance:

‘ardxcv21 4 Mx-yyy’: set motor ‘x’ (NOT pin number) to PWM level ‘yyy’ (0-255)

‘ardxcv21 4 Txx-yyyyy’: send tone on pin ‘xx’ to frequency ‘yyyyy’. (yyyyy = 0 turns off).

I verified that all three analog output techniques work simultaneously: I set a tone of 1000 Hz on pin 11 to the 8 ohm speaker, then started the motor at maximum on pin 6, then slewed the servo back and forth a few times on pin 9...then went back and changed tones and motor speed...no glitches.

Click here to see ver 0.6 of the code

---------------------------------

Tiny45 programming with the Arduino IDE

Right at the outset of my Arduino I2C experiments (details to follow), I discovered that my ambition to control three motors (two servos and one for each track) was barely doable with the Mega328-based devices...no big deal for the current thing, but I had a few tiny45's laying around and I got to wondering...

And YES, it is completely possible to use an Arduino to program a 'bare' AVR!  Here's a link to the basics:

Programming an ATtiny w/Arduino 1.6

One gotcha with these instructions...you have to make sure to load the ArduinoISP software onto the Arduino BEFORE you try to change the board/processor/programmer combo to send anything through the Arduino to the ATtiny chip.  You just load File/Examples/11. Arduino ISP and upload it; I am using an Uno, and after that...all works great.

And to make sure the PWM on pin 0 and 1 (pin 5 and 6 on the actual tiny45) worked as advertised...I wrote the following:

/*
LED Fader for tiny45
Change FADEPIN appropriately for PWM output...
pin 0 maps to pin 5 on tiny45
pin 1 maps to pin 6

*/

#define FADEPIN 1

unsigned char fcnt = 0;
char fpause = 7;
char updn = 1;

void setup() {
pinMode(FADEPIN, OUTPUT);
}

void loop() {
fcnt += updn;
analogWrite(FADEPIN, fcnt); // turn the LED on (HIGH is the voltage level)
delay(fpause); // wait for a second
if (fcnt > 254) updn = -1;
if (fcnt < 1) updn = 1;
}



Works as you would expect; I had to experiment a little with the 'fpause' to get the LED to fade from max to min and back in about 4-5 seconds.

I'd already followed the instructions to set my tiny45 to 8Mhz (you use the rather non-intuitive 'Burn Bootloader' tool once you have the right board, speed, and programmer selected), but this works perfectly well at the default 1Mhz a new ATtiny runs at.

Next up is to get I2C working on the tiny45, which IS supported on the hardware, but was NOT include in the board and pin descriptions I installed from the instructions above.  More when I know more....

Welcome and Links

Welcome to the Burnt Couch Blog!
(get off the couch before I set it on fire, you lazy...)




 (my first attempt at a dual-channel 556 based PWM generator w/ MOSFET drivers)




This space is intended to be a DIY support haven for aspiring maker/hacker/mad scientists; I encourage all participants and commentators to contribute their own resource discoveries, code, wiring diagrams, and project documentation.

This 'Welcome' page is publicly viewable, but PLEASE send an request to burntcouch@gmail.com if you want to comment or contribute.

I (Z Smith) will slowly begin adding my own projects here as I figger this place out, but in the mean time....comment and muck around and try to break stuff, a little....

But...in the meantime here are some nice links for your edification:

Adafruit Learning System -- best place for Pi stuff, and some other goodies too
RPi Tutorial Easy GPIO Hardware & Software - eLinux.org -- embedded Linux
Raspberry Pi web server - GPIO -- and good reference on the Pi GPIO port
H-Bridge Theory & Practice -- Chuck's Robotics Notebook -- A absolutely great H-bridge reference, with a power-Darlington transistor design that is ‘short-safe’ and includes inductive braking!
Robotics Universe - Build Your Own Robot - Free Tutorials, Fun Projects, and More -- home site of Gordon McComb’s Robot Bonanza books
Chuck's Robotics Notebook -- other good stuff from Chuck and the Palo Alto robot guys
Robot Books.com - Robot Kits, Robotics, and Toy Robots
Arduino - Home -- download the compiler/simulator, lots of tutorials, the forums etc.
Robotics & DIY>Robot Parts
Make: | DIY projects, how-tos, and inspiration from geeks, makers, and hackers -- Make Magazine
Purchase info - Free Charge Controller wiki -- The Free Charge Controller project, for solar/wind/exotic 12V battery power systems.
Seeed Studio -- another great DIY place
555 and 556 Timer Circuits
DPRG Dallas Personal Robotics Group
555 Timer Circuits
Electronics in Meccano -- Erector Set automation