AVRs and Linux are the stuff of the Universe...Dr. Tyson just don' wanna admit it yet....
Showing posts with label Arduino. Show all posts
Showing posts with label Arduino. Show all posts
Sunday, October 9, 2016
The five minute Fitbit hack
And this is what you get for bragging...
My 40-year running buddy JM, distressed and beleaguered former Iraq vet that he is, was recently beset with a Fitbit...corporate America has burdened him with the task of proving his manhood by walking at least 10,000 steps a day.
After listening to his combination of guilty-conscienctiousness and frustrated bitching for a few weeks, I begin to brag...'I can, in five minutes, write-a -program/wire-up-some-hardware that will obviate your physical participation in this ridiculous encumberation.'
And after 10,000 legitimate steps on the river walk, several beers on the porch, and a fantastically unusual Oregon Indian summer Sunday afternoon, I realized I would be forced to walk the walk. Fair enough! Results are below. My hardware setup consisted of an Arduino Uno, three jumper wires to the servo (Radio Shack 'standard'), a 5" long 1/8" wood dowel, some cellophane tape...and the logger module from JM's fitness torture device.
And, voila...the quintessential 19-line Arduino program that will allow you to win that case of Gatorade at the company pool next week.
/* servo slew 1 */
//
//
#include <Servo.h>
Servo myservo;
void setup() {
pinMode(13, OUTPUT);
myservo.attach(3);
}
void loop() {
int x, y;
digitalWrite(13, HIGH);
x = random(650, 1200);
myservo.writeMicroseconds(x);
y = random(350, 600);
delay(y);
digitalWrite(13, LOW);
x = random(1000, 2050);
myservo.writeMicroseconds(x);
y = random(350, 600);
delay(y);
}
The blinks of the LED on pin 13 aren't strictly necessary, just there for debugging purposes. I randomized the delays as well as the right- and left- slew to simulate the uneven gait of most walkers. My first attempt was even shorter, simply slewing between 20 and 120 degrees and back, once per second, but the Fitbit module didn't log near as many steps that way...
Forthwith, JM and I adjourned to the local brew pub for some quality hours with our favorite blend of mere Irishness and outright beligerence; two hours and ten minutes, four 22-ounce pints, and several thousand jokes in questionable taste later....
11,500 steps!
We do NOT feel guilty about this. It is as if Everest were in our back yard, and Sir Edmund Hillary had asked us if we would like to tag along for a little hike. JM ended up with 25,000 for the day and quite a bit of good will with the waitresses...
Sunday, September 25, 2016
Using and abusing the GNU assembler for ATtiny programming...
One of the big troubles with programming AVRs on Linux, as you might have gathered from previous posts, is that you have to use the GNU compiler family and the AVR-libc library, along with your favorite editor and avrdude and/or the ArduinoISP to finish the job.
ALL this is tolerable, even comfortable, if you are a Linux geek and a command line junkie. At least if you are writing in C; you can even use the Arduino IDE and libraries, if your target μc is supported in it. Or you can do it the 'hard way', with straight AVR-libc, gcc, and the command line. Not too painful, as I've covered previously....
GNU 'as' docs are sh****
Once you decide you want to start doing some straight assembly, though...not so much fun. I have spent the last week just reading The GNU Assembler documentation and various forum posts, looking for 'best practice' or 'just plain works' code examples. Because of the vagaries of FSF 'free software' license policies (which apply to the GNU compiler docs, fortunately or unfortunately as Alice might say), the support for avr-as is stark, at best, and there is VERY little code to examine out in the wild.
The attitude seems to be....you REALLY should be programming in C, or at worst using the (awful) inline assembler. REAL assembler is just there in case someone like to torture themselves with AVR-libc's comprehensive but extremely obscure symbol/macro naming conventions and complex header file hierarchy.
Atmel's attitude toward support of other assemblers is worse, as is to be expected. They would rather you use AVR Studio and avrasm, which has entirely different preprocessor syntax. Atmel does assert a minimum of support for the GNU AVR toolchain, and even have some documentation on using it...but it is even sketchier than the GNU docs, outright wrong about certain preprocessor directives, and completely out to sea on code and data sectioning among other things.
Even though I despise coding under Windows, I LIKE avrasm and Atmel's take on assembly. I have ALWAYS liked it. The syntax was VERY well documented, it is very clear where everything goes, and when, and why wouldn't it be, since Atmel had a lot to do with developing it? Unlike AVR-libc, Atmel took a more incremental approach, assuming that anyone doing assembly would be more likely to have a 'bag-of-tricks' set of macros for common tasks (like timed delays) and would generally write software toward a specific subset of the AVR family (in my case the tiny26, tinyx4, tinyx5, and tinyx61 devices) rather than everything.
The Atmel/avrasm device 'include' files are particularly clearly put together; it occurred to me right away that, with a bit of search and replace, I could convert them to avr-as syntax and get the best of both worlds. Or the best of THREE worlds, since I wouldn't have to rely on the AVR-libc header files either, which are really oriented toward C rather than AVR assembler anyway.
And the last hurdle, therefore...
AVR-libc header files are gibberish...
The latter is only a personal opinion, but....I do NOT plan on using assembler for the big ATmega's and the Arduinos. Those devices have a LOT of memory and peripheral support, so why not be comfortable and do it all in C? For the kind of tasks it makes sense to implement on an Arduino, we can sit back and just include the libraries we need and hope the magic works, not worrying too much about what's going on in there unless we run into serious problems.
But there's a good reason that Arduino C and the Arduino IDE exist in the first place; bare-metal C programming on an AVR is not for the faint of heart. Despite the abundance of excellent code examples out there and the well-intentioned effort by the maintainers to create a device-agnostic environment, even the the simplest tasks (such as the 'demo.c' program in the AVR-libc docs) require a knowledge of the hardware that few beginners are likely to have.
With assembly, using the AVR-libc envirnoment is even more painful. In assembly, you can't ignore the device-compatibility gibberish in all those files. The gyrations needed to get one set of header files to work with the dozens of different AVR's, even in C, require a LOT of esoteric macro witchcraft that I may understand...eventually. In the next year or so. Or maybe when I retire.
But...we aren't talking about ATmegas, anyway, and why not? The AVR ATtinys might not be able to do a hardware multiply in four clock cycles, but do we really need that to start with? The whole idea behind using a microcontroller for these tasks is that they are more space- and resource- and power-efficient than a large TTL or analog network, in addition to being more flexible, programming-wise. But going too far with this, making an MCU do everything, can defeat the purpose of using them in the first place. With 32K of memory there is little incentive (beyond the limitations of the hardware itself) to code efficiently. More, there are only so many pins and so many counters and so many processor cycles available on an AVR. Why not develop a sensible multiprocessor control system, with good communications between sub-systems as a priority?
IN other words...why not use ATmegas or small Linux platforms like the Pi to do the supervisory tasks that require space, flexibility and horsepower, and the small devices like the excellent stable of ATtinys to do the low-end sensor and motor control gruntwork? C is perfectly fine for the big picture baton-waving...but assembly might be a very handy tool to have if we want the best results out at the ragged edge of reality, where every clock cycle and bit counts.
If we want all this, and Linux too, we need a simple set of programming tools that work fairly seamlessly with the Arduino and AVR-libc environments, but don't inherit the narrow device support of the former or the one-size-fits all limitations of the latter. We want a set of device-centric headers, some powerful macros, and a functional code base that will allow us to easily write EXACTLY to the device and the purpose we need. And we want as much automation as we can devise so that we CAN concentrate on those goals.
In short:
1. Device-specific include files, not generic ones.
2. Familiarity with the minimum subset of assembler directives that the GNU assembler furnishes to get the job done, so that our code is clear and easy to adapt.
3. A set of rules and scripts that allows us to deploy our code quickly.
Firstly...
To get started, I needed to convert a subset of the Atmel '<device>def.inc' files to a format avr-as likes. AVR Studio/avrasm use now use an XML format for newer versions of the Atmel programming environment, but I found a set of older files here.
With some chopping, cutting, search-and-replace, I was is business. Below, ferinstance, are some snips from my massaged 'tn26def.inc' include file:
-----------------
... (other stuff)
...
.equ PINA, 0x19
.equ PORTB, 0x18
.equ DDRB, 0x17
.equ PINB, 0x16
.equ USIDR, 0x0f
.equ USISR, 0x0e
.equ USICR, 0x0d
.equ ACSR, 0x08
.equ ADMUX, 0x07
.equ ADCSRA, 0x06
.equ ADCH, 0x05
.equ ADCL, 0x04
; ***** BIT DEFINITIONS **************************************************
; ***** AD_CONVERTER *****************
; ADMUX - The ADC multiplexer Selection Register
.equ MUX0, 0 ; Analog Channel and Gain Selection Bits
.equ MUX1, 1 ; Analog Channel and Gain Selection Bits
.equ MUX2, 2 ; Analog Channel and Gain Selection Bits
.equ MUX3, 3 ; Analog Channel and Gain Selection Bits
.equ MUX4, 4 ; Analog Channel and Gain Selection Bits
.equ ADLAR, 5 ; Left Adjust Result
.equ REFS0, 6 ; Reference Selection Bit 0
.equ REFS1, 7 ; Reference Selection Bit 1
...
... (and so on...)
...
; ***** CPU REGISTER DEFINITIONS *****************************************
.equ XH, r27
.equ XL, r26
.equ YH, r29
.equ YL, r28
.equ ZH, r31
.equ ZL, r30
; ***** DATA MEMORY DECLARATIONS *****************************************
.equ FLASHEND, 0x03ff ; Note: Word address
.equ IOEND, 0x003f
.equ SRAM_START, 0x0060
.equ SRAM_SIZE, 128
.equ RAMEND, 0x00df
.equ XRAMEND, 0x0000
.equ E2END, 0x007f
.equ EEPROMEND, 0x007f
.equ EEADRBITS, 7
--------
All relatively clear, as far as mud goes. In Atmel's assembler, the '.equ' directive uses an '=' as a delimiter rather than a comma, so a simple search-and-replace fixed 95% of the file; I removed all the # (C-style) directives and fixed some tabs for cosmetic reasons. All of the dozen files I needed for the ATtinys took me a total of an hour to fix up for avr-as.
So at this point, I could at least have all the shortcut symbols I would need to get at the I/O ports and registers, plus some handy constants. So what's next?
Of the dozens of preprocessor directives that the GNU docs describe (sort of), you really only need a small subset:
.equ - to define symbolic constants
.include - to include other files
.section - to define non-code sections with non-standard names, like...
.section .bss - to reserve space in the SRAM data
.text - to define the code area
.global - to make sure an external program can see a label
.macro/.endm - to define fancy code shortcuts
.if/.endif - more fancy stuff in macros, condtional assembly, and....
...and a gaggle of data description things like:
.byte, .word, .quad, .ascii, .asciz, .octa, .space
...for space reservation in data areas. See below for a working example; a simple tiny26 program that uses a macro to generate an inline 'microsecond_delay' function, and reads a array of bytes from the tail end of program memory to sequentially blink an LED in a set pattern:
The blink26.S program
;
; blink26.S
;
; ** LOAD IO DEFS FOR THE RIGHT DEVICE
;
.include "avrinc/tn26def.inc"
;
; ** GET ME SOME HANDY MACROS **
;
.include "avrinc/macro.inc"
;
; *** DEFINES HERE ***
;
.equ F_CPU, 8000 ; 8Mhz clock, for msec_delay define in 'macro.inc'
.equ OUTPIN, PA7 ; for t26
.equ IO_OS, 0x20 ; for lds/sts with i/o ports, e.g 'lds r2, (IO_OS + TCNT0)'
;
; *** SRAM RESERVES HERE ***
;
.section .bss
; FOR example:
;<datalabel>: .byte 1 ; to reserve 1 byte in SRAM for <datalabel>
;
; *** RESETS, IVECTORS, AND MAIN PROGRAM ***
;
.text
.global reset ; don't know if '.global' needed but...
reset:
rjmp main; *** tn26 interrupt vector names, just fer handy ***
reti ; 0001 INT0_vect
reti ; 0002 PCI0_vect
reti ; 0003 TIMER1_OC1A_vect
reti ; 0004 TIMER1_OC1B_vect
reti ; 0005 TIMER1_OVF_vect
reti ; 0006 TIMER0_OVF_vect
reti ; 0007 USI_START_vect
reti ; 0008 USI_OVF_vect
reti ; 0009 EERDY_vect EEPROM Ready
reti ; 000A ACOMP_vect Analog Comparator
reti ; 000B ADC_vect ADC Conversion Complete
;
;
.global main
main:
setupsp r16, RAMEND ; use the macro to set up the stack
sbi DDRA, OUTPIN ; set pin 'OUTPIN' on port A to high for OUTPUT
ldi ZL, lo8(array)
ldi ZH, hi8(array)
blink:
sbi PORTA, OUTPIN ; then drive it HIGH
lpm r20, Z
b2:
msec_delay 1, r17, r18, r19 ; 1 ms wait * r20 times
dec r20
brne b2
cbi PORTA, OUTPIN ; then LOW...
lpm r20, Z
b3:
msec_delay 1, r17, r18, r19 ; 1 ms wait * r20 times
dec r20
brne b3
adiw Z, 1
lpm r20, Z
tst r20
breq end
rjmp blink;
;
end:
rjmp end;
; *** MAIN PROGRAM END ***
;
; *** SUBROUTINES OR INTERRUPT SERVICE ROUTINES HERE ***
;
; (don' have none o' that at the moment)
;
; *** PROGRAM MEMORY DATA HERE ***
;
array: .byte 10,250,20,240,30,230,40,220,50,210,60,200,70,190,80,180,90,170,100,160,110,150,120,140,130,0
;
; end of test.s
--------------------
Everything you really need is right there, unless you are going to start moving code blocks around and deal with a boot loader; if you need to load something into EEPROM you could either hand-code a hex file that would work, or...more when I know more.
Among other things, note the very tail-end of the program, where the 'array:' label is used to mark off a list of byte values. Nothing special is done with directives (other than .byte to make it clear what size and type the data are) to reserve this area; it's just slapped right to the end of the program after the last of the code.
You can even use '.org <addr>' before the label to move the memory location this label is pointing at around, but....why not just leave it where it is?
A few minor caveats with putting data in program memory....among other things you can only use the Z register (r31:r32 jammed together as a 16-bit register) to access it indirectly, using the LPM instructions. LPM also has a very limited number of addressing modes, depending on the device, and takes three cycles to get the data to the register. Use the 'lo8()' and 'hi8()' built-in macros to carve the 16-bit program address into something you can load into the two halves (ZL and ZH) of the Z register.
A more interesting problem...program memory is organized 'word-wise', in other words 16 bits at a time, so any data you put out there in program space has to align with word boundaries; if you have an odd number of bytes in a chunk of data, you may need to add a '.space 1' directive before or after it to line the label address up with right...avr-as will complain if you don't.
----------------
Here is a snip from my 'macro' file, just so you get a taste of how those work:
;
; define SPL if only SP is defined
;
.ifndef SPL
.equ SPL, SP
.endif
;
; setup stack pointer
;
.macro setupsp r,x
ldi \r, \x % 256
out SPL, \r
.if \x > 256
ldi \r, \x / 256
out SPH, \r
.endif
.endm
;
;
;
;***
; 8-bit hex to ascii-coded hex (subroutine)
;
; use 4 registers above r15!!
;
.macro hex2ascii_m in, tmp, o1, o2
hex2ascii:
mov \tmp, \in
andi \tmp, 0x0f ; get bottom nybble
ldi \o1, 48
add \o1, \tmp
cpi \o1, 58
brlo h2ach1
subi \o1, (-7)
h2ach1:
mov \tmp, \in
swap \tmp
andi \tmp, 0x0f ; get top nybble
ldi \o2, 48
add \o2, \tmp
cpi \o2, 58
brlo h2ach2
ldi \tmp, 7
subi \o2, (-7)
h2ach2:
ret
.endm
;
;
; ***
; *** inline millisec delay loop
; ***
; *** max is about 11000
; *** !!and be sure to define F_CPU in main program for both like below!!
;
;.equ F_CPU, <CPU mhz * 1000> (e.g. for 8Mhz use 8000)
;
; NOTE: label '1' is necessary if you are going to use this macro more than
; once in a give program. The '1b' branch is shorthand for 'branch to the
; '1' label BEFORE this instruction'. '1f' would mean to go to the '1' label
; FOLLOWING the branch.
;
.macro msec_delay delay, ra, rb, rc
ldi \ra, ((((\delay * F_CPU - 1) % 327680) % 1280) / 5)
ldi \rb, (((\delay * F_CPU - 1) % 327680) / 1280)
ldi \rc, ((\delay * F_CPU - 1) / 327680)
1:
subi \ra, 0x01
sbci \rb, 0x00
sbci \rc, 0x00
brne 1b ; see note above
nop
.endm
----------------------------
There are a few different things going on here...
'as' macros are actually quite sophisticated, and I've used them above in two different ways. If you want to just to define a subroutine:
; 8-bit hex to ascii-coded hex (subroutine)
;
; use 4 registers above r15!!
;
.macro hex2ascii_m in, tmp, o1, o2
hex2ascii:
mov \tmp, \in
andi \tmp, 0x0f ; get bottom nybble
ldi \o1, 48
add \o1, \tmp
...
...shows the way. First, notice that this macro has four parameters; and it is pretty obvious from the way the 'mov' instruction is used below that 'tmp' and 'in' refer to two of the AVR registers. And it should also be pretty obvious that to get at a parameter inside the macro you need to put a slash in front of it, e.g. '\tmp'.
But you can also pass constants, and use C-style expressions to calculate new ones:
;
.macro msec_delay delay, ra, rb, rc
ldi \ra, ((((\delay * F_CPU - 1) % 327680) % 1280) / 5)
ldi \rb, (((\delay * F_CPU - 1) % 327680) / 1280)
ldi \rc, ((\delay * F_CPU - 1) / 327680)
'delay' is passed in, and then a BUNCH of math is done to figure out how to set the three registers used for the cascading countdown.
'msec_delay' generates inline code, rather than a subroutine...and therfore you may notice some weirdness farther down:
... (continued from above)
...
ldi \rc, ((\delay * F_CPU - 1) / 327680)
1:
subi \ra, 0x01
sbci \rb, 0x00
sbci \rc, 0x00
brne 1b ; see note above
nop
.endm
How the hell does the branch get to the '1' label if it refers to it as '1b'? What the hell does THAT mean? Hmmm...
Okay. We are going to use this 'msec_delay' more than once, presumably, in a fairly complicated program, so when this macro gets expanded by the compiler...it AIN'T going to like seeing multiple '1:' labels. SOOO, the rule is that, if you use a digits only label in a macro, you reference either the 'next' or the 'previous' instance of that label, and when the macro is expanded the actual labels are removed and numerical offsets put in on the branches and jumps in their place. Therefore...
brne 1b
..means 'branch if not equal to the first label '1:' BEFORE this location (thus the 'b'). If you need to branch or jump FORWARD, you would say 'brne 1f', ferinstance.
And what about conditional things? Try this out:
; setup stack pointer
;
.macro setupsp r,x
ldi \r, \x % 256
out SPL, \r
.if \x > 256
ldi \r, \x / 256
out SPH, \r
.endif
.endm
;
Almost self explanatory. The macro is intended to set up the stack pointer. It is named 'setupsp' and has two parameters, 'r' (which should be a register), and 'x', which looks to be used as a constant. This line:
.if \x > 256
...looks an awful lot like it is CHECKING that constant to see if it is just so large. If RAMEND is plugged into 'x', you are probably guessing that we are trying to see if the SRAM size for the device needs a 16-bit or only an 8-bit register to store the stack pointer...and that is what this does. If RAMEND is larger than 256, then two extra instructions are generated to init SPH as well as SPL, with the high bytes of RAMEND.
One of the few things that the GNU docs DO include are a VERY few examples of macros, so those pages are worth a perusal; and the many variations of '.if' are also worth a look.
Make? Ugh? Ahh....
Lastly...it would be nice to have an automated way to compile/assemble our programs, so we aren't stuck memorizing a lot of stupid and easy-to-screw-up compiler options...how about a nice Makefile?
--------------------
P=test MCU=attiny26
BUPATH=./back
TS=`date +%H%M%S-%Y`
ifdef VER
TS:=$(VER)
endif
ifdef UC
MCU:=$(UC)
endif
ifdef PRG
P:=$(PRG)
endif
CFLAGS=-g -mmcu=$(MCU) -Os
AFLAGS=-g -mmcu=$(MCU)
LDLIBS=-I/home/zenas/mylib/
CC=avr-gcc
OC=avr-objcopy
AS=avr-as
ASL=avr-ld
DIS=avr-objdump
$(P).ao: $(P).S
cp $(P).S $(BUPATH)/$(P)_$(TS).S; $(AS) $(AFLAGS) $(LDLIBS) $(P).S -o $(P).ao
$(P).elf: $(P).ao
$(ASL) -o $(P).elf $(P).ao
$(P).o: $(P).c
cp $(P).c $(BUPATH)/$(P)_$(TS).c; $(CC) $(CFLAGS) $(LDLIBS) $(P).c -o $(P).o
$(P).ahex: $(P).elf
$(OC) -j .text -j .data -O ihex $(P).elf $(P).ahex
$(P).hex: $(P).o
$(OC) -j .text -j .data -O ihex $(P).o $(P).hex
$(P)-eeprom.hex: $(P).o
$(OC) -j .eeprom --change-section-lma .eeprom=0 -O ihex $(P).o $(P)-eeprom.hex
dump: $(P).o
$(DIS) -h -S $(P).o > $(P).lst
dumpa: $(P).elf
$(DIS) -h -S $(P).elf > $(P).lst
ahex: $(P).ahex
hex: $(P).hex
eeprom: $(P)-eeprom.hex
touchc:
touch *.c
toucha:
touch *.S
clean:
rm *.o; rm *.ao; rm *.ahex; rm *.hex; rm *.elf;
--------------------
Just plug in PRG, UC, and VER variables on the command line if you want to use this file generically; by default it compiles/assembles 'test.S' or 'test.c' for the ATtiny26.
Note that there is an extra 'linker' stage (invoked with avr-ld) needed to get the final uploadable code! One of my early mistakes took a very long time to fix: I was generating both the final '.ahex' file (to burn to the device) and the disassembly '.lst' file from the output object '.ao' file from avr-as, NOT the '.elf' file that the linker produces....while all of the symbolic constants and macros unrolled right, none of the jumps or branches or labels did anything useful.
The 'make dumpa' routine is particularly handy (once you are dumping the right thing anyway); you can see exactly what the assembler has done after all the macros and constants have been resolved, and where everything will end up when you upload to your device. It will save you an immense amount of headache if you can grit your teeth and familiarize yourself with the disassembled product of avr-gcc and avr-as. Among the bonuses: you can discover some very handy code shortcuts by poaching the output of avr-gcc...just write up something simple in C, let avr-gcc have it, then use the 'make dump' routine and see what the final product looks like. avr-objdump very helpfully includes the C code segment used to generate each piece of object code, and as long as the task isn't too complicated you can tease out the jewels.
Incidentally, the 'hex' and 'ahex' routines backup the .c/.S file before it's compiled, shoving a time stamp on it in case you want to go back and see what worked before...the backed up files ends up in the 'back' folder by default.
The makefile works with both avr-libc 'C' files and assembler 'S' files:
make clean --- gets everything ready
make hex PRG=cblah --- compiles, links, and genrates a .hex file of cblah.c
make ahex PRG=ablah --- assembles, links, and genrates a .hex file of ablah.S
make dumpa PRG=ablah --- creates a disassembled version of ablah.S
make dump PRG=cblah --- ditto for object file of cblah.c
..and so on. I still upload stuff to the device by hand with 'avrdude',
avrdude -p t26 -P /dev/ttyACM0 -c arduino -b 19200 -U flash:w:ablah.ahex
...but that could probably go in there too, eventually.
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.
---------------
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;
}
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;
}
}
(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)
(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
---------------------------------
Subscribe to:
Posts (Atom)