Showing posts with label C. Show all posts
Showing posts with label C. Show all posts

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

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....