Need help with SPI on AVR - Page 1
Forum section: electronics / projects

author: MrAureliusR
author_rank: Unknown
timestamp: September 03, 2013, 07:51:34 pm
content: Hi there. I'm making my first foray into SPI communication between chips. I'm using an ATmega32A and a Microchip 25AA640A Serial EEPROM. At this point, I'm literally just trying to get communication working. I'm trying to send junk data back and forth. My C is okay, I definitely am a learner though. Here's the code I've written (keep in mind, I'm trying to keep it basic -- please feel free to point out the many obvious errors I've made)Code:#include <avr/io.h>int main(void){int data; // declaring junk variableDDRB |= (1<<4)|(1<<5)|(1<<7); // I'm fairly sure I've got these pins right, setting MOSI, SCLK and SS as outputsDDRB &= (~1<<6); // and setting MISO as an inputSPCR |= (1<<SPE); // enable SPISPCR |= (1<<SPIE); // not sure if both of these are necessary as the correct fuse bit is setSPCR |= (1<<MSTR); // setting the ATmega as masterSPCR |= (0<<CPOL)|(0<<CPHA); // setting clock polarity and phaseSPCR |= (1<<SPR0)|(1<<SPR1); // setting oscillator frequency (currently clk/128)data = 00000110;// write enable on the 25AA640A?? I picked this at random, but also because it's supposed to be the write enable bytewhile(1){SPDR = data;while(!(SPSR & (1<<SPIF)));}}After loading up the AVR (I can see the write process from my AVRISP mkII) nothing happens. I wanted it to infinitely loop so I could watch it with my scope/LA.I'm sure it's something painfully obvious, and I've been Googling for hours but there's not much good info with using SPI on an AVR. Lots of theory but very little practical implementation of coding...

author: TerminalJack505
author_rank: Super Contributor
timestamp: September 03, 2013, 08:07:47 pm
content: Try replacing this line:Code:DDRB &= (~1<<6); // and setting MISO as an inputWith this:Code:DDRB &= ~(1<<6); // and setting MISO as an inputOr, just comment-out the line.

author: c4757p
author_rank: Super Contributor
timestamp: September 03, 2013, 08:10:10 pm
content: TJ's got it. ~1<<6 is 11000000. By and'ing this with DDRB you're clearing the output bits you just set.DDRB starts out as zero after reset, so you don't need to clear bits that aren't already set. (If it makes you feel safe, put a DDRB=0 before the assignments to make sure.)Additionally:You do not needSPIEunless you plan on using interrupts. With that flag set, if interrupts are turned on, the chip will generate an interrupt when a transfer completes (you can use this to buffer your data transfers to do a "background" transmission instead of having to busy-loop checkingSPIF- save this for after you get basic SPI running). And keep in mind that you won't much like the results of having an interrupt fire for which no handler exists (the chip will reset).Also, you need to manually toggle #SS - pull it low before you transmit and high after. Otherwise, the data will be transmitted, but the EEPROM will ignore it.

author: MrAureliusR
author_rank: Unknown
timestamp: September 03, 2013, 08:14:13 pm
content: Okay, I fixed that line, but there's still no output after the chip is programmed. Is my while loop actually infinite?

author: c4757p
author_rank: Super Contributor
timestamp: September 03, 2013, 08:23:03 pm
content: Yes, it is.Temporarily replace the SPI transmission with a pin toggle, and stick a scope on that pin. Make sure you haven't messed up the fuse settings and the clock is running.

author: TerminalJack505
author_rank: Super Contributor
timestamp: September 03, 2013, 08:25:17 pm
content: Make sure to get rid of this instruction:Code:SPCR |= (1<<SPIE);Like c4757p says, it can cause your MCU to reset since you don't have an interrupt handler setup.

author: c4757p
author_rank: Super Contributor
timestamp: September 03, 2013, 08:27:32 pm
content: It's not the problem, though, unless he's hiding ansei()from us; the chip boots with interrupts disabled.

author: MrAureliusR
author_rank: Unknown
timestamp: September 03, 2013, 10:51:32 pm
content: Hmm I'll be back home in a bit. What I want to do to keep it simple and to learn is to write a number into the EEPROM and then read it back and display it with LEDs or something. Maybe assign a button to each LED so I can input the number.Does anyone know of good online resources for programming ISP for AVRs? I can't find the right info. I'm trying to find a simple project like I mentioned to get the basics down.Sent from my HTC Desire C using Tapatalk 4

author: Psi
author_rank: Super Contributor
timestamp: September 03, 2013, 11:02:18 pm
content: - This line is wrong tooSPCR |= (0<<CPOL)|(0<<CPHA); // setting clock polarity and phase(0<<X) will always produce zeroSo you're going SPCR = SPCR bitwise-or 0 bitwise-or 0 which will have no effect on SPCR.This isn't your problem though because CPOL and CPHA are zero already. (they're initial state is zero)- This line doesn't do what you intendeddata = 00000110;That will set the data variable to 110 in base 10. You need to add 0b if you want to enter binary like that.data =0b00000110;( For hex you can use 0x etc.. data = 0x06 )- I would move the SPI enable line until after you finish setting up the SPI registers. Sometimes things get lost if you try and configure hardware while its running.

author: MrAureliusR
author_rank: Unknown
timestamp: September 04, 2013, 01:17:07 am
content: Looking better?Code:#include <avr/io.h>int main(void){int data;DDRB |= (1<<4)|(1<<5)|(1<<7);DDRB &= ~(1<<6);SPCR |= (1<<MSTR);SPCR |= (0<<CPOL)|(0<<CPHA);SPCR |= (1<<SPR0)|(1<<SPR1);SPCR |= (1<<SPE);data = 0b00000110;// write enable on the 25AA640A??while(1){SPDR = data;while(!(SPSR & (1<<SPIF)));}}Or should I drop the SPCR |= (0<<CPOL)|(0<<CPHA); entirely?

author: c4757p
author_rank: Super Contributor
timestamp: September 04, 2013, 01:19:02 am
content: Yup. Looks good to meYes, I think you should drop that line, just because it looks kind of... well, stupid.It doesn't do anything.In fact, the whole thing could become:Code:#include <avr/io.h>int main(void){DDRB = (1<<4) | (1<<5) | (1<<7);SPCR = (1 << MSTR) | (3 << SPR0) | (1 << SPE);while (1) {SPDR = 0b00000110;while (!(SPSR & (1 << SPIF)));}return 0;}

author: Psi
author_rank: Super Contributor
timestamp: September 04, 2013, 02:03:41 am
content: Just to make it easier to read, he could also replaceDDRB = (1<<4) | (1<<5) | (1<<7);withDDRB = (1<<PB4) | (1<<PB5) | (1<<PB7);I prefer using =| rather than = when changing registers.Just because it's a good habit to get into to avoid overwriting bits. However for this code it doesn't matter and = ensures the other bits are zero

author: MrAureliusR
author_rank: Unknown
timestamp: September 04, 2013, 02:31:02 am
content: Hmmm...I even ended up copying and pasting the concatenated version that c4757p posted, still no outputs. I have a probe on MOSI and MISO, and when I hit F5 in Atmel Studio, as I mentioned before, I can still see the AVRISP programming the device, but then after that everything just goes dead.I've tested the chip with another program to make sure it's not the chip that's bad...I feel like I'm missing something obvious. The fuses are programmed as follows:ON:SPIENEESAVE (doesn't really matter if it's on or off, correct?)BOOTSZ = 2048W_3800SUT_CKSEL = INTRCOSC_1MHZ_6CK_64MS_DEFAULT (although I've also tried it with a 20MHz crystal, a 14.7456MHz crystal, and a 6.0MHz ceramic resonator...)Lock bits are off, JTAG disabled... it's programming the chip perfectly. Maybe I'll pop it out and try programming it with my TL866CS???

author: c4757p
author_rank: Super Contributor
timestamp: September 04, 2013, 02:39:50 am
content: Quote from: Psi on September 04, 2013, 02:03:41 amI prefer = in this case, because I'm setting the whole register. I'm configuring which ones are inputs and which are outputs - I want to know right then and there which one is which before I screw up and drive a voltage up some other chip's output. Unless you're bit-banging an open collector or bidirectional interface, you're unlikely to change the DDR registers once the chip is initialized.

author: c4757p
author_rank: Super Contributor
timestamp: September 04, 2013, 02:41:12 am
content: Did you try changing your code from SPI to just flipping a bit? Don't put in a whole other program, you don't want to accidentally fix your problem when you do that - just comment out the SPDR and SPIF lines and put in a bit toggle.

author: MrAureliusR
author_rank: Unknown
timestamp: September 04, 2013, 02:42:33 am
content: @ c4757p I will try that right now and post back in a cpl mins. Also:I don't know if it'll make a difference, but I've also got an ATmega644A, an ATmega324A, PIC18F4685, PIC18F4450... I could try any one of those.EDIT: Here's what I changed it to... nothing on PB4!!Code:#define F_CPU 250000UL#include <avr/io.h>#include <util/delay.h>int main(void){DDRB |= (1<<PB4) | (1<<PB5) | (1<<PB7);// SPCR = (1 << MSTR) | (3 << SPR0) | (1 << SPE);while (1) {// SPDR = 0b00000110;// while (!(SPSR & (1 << SPIF)));PORTB &= ~(1 << PB4);/* bit on */_delay_ms(75);PORTB |= (1 << PB4);/* bit off */_delay_ms(40);}return 0;}I'm going to switch over to the 644A I think... unless I made a glaring error in the above code. Turning an LED on and off is a specialty, so I don't think I screwed that up

author: c4757p
author_rank: Super Contributor
timestamp: September 04, 2013, 02:45:25 am
content: Don't give up and go to another chip. The 32A has perfectly good SPI facilities, it's good troubleshooting practice to get them running.

author: c4757p
author_rank: Super Contributor
timestamp: September 04, 2013, 02:49:18 am
content: Quote from: MrAureliusR on September 04, 2013, 02:42:33 amBingo. OK, what are your full fuse bits?Also... your "bit on" and "bit off" comments are backwards. |= sets the bit, &= ~ clears it.

author: MrAureliusR
author_rank: Unknown
timestamp: September 04, 2013, 02:53:14 am
content: Full fuse bits are HIGH: 0xD1 LOW: 0xE1By the way, thanks a million for taking the time to help me c4757p, I seriously appreciate it!EDIT: Just changed HIGH to 0xD9, turning EESAVE off, no effect.

author: c4757p
author_rank: Super Contributor
timestamp: September 04, 2013, 03:09:16 am
content: Low:0xe1 = 11100001 = internal 1MHz oscillator, no BOD, SUT = 16 k ck/4.1msTry changing that to 16 kck/65ms (SUT = 11, low fuse = 0x11110001).High:0xd1 = 11010001 = SPI programming enabled, no bootloaderOther than the SUT change I suggested, everything looks good. (You'd be surprised how often that can be the cause. That switch matrix I just built with a mega168 needs to wait the full 65ms or else it malfunctions in bizarre ways.)

author: MrAureliusR
author_rank: Unknown
timestamp: September 04, 2013, 03:11:27 am
content: It already is at 6CK_64MS, that's what I'm getting for 0xE1 on the low fuse byte... strange.EDIT: Did you accidentally put 0xD1 for both? That would give 6CK_4MS for the clock.

author: c4757p
author_rank: Super Contributor
timestamp: September 04, 2013, 03:14:13 am
content: I must have mucked up the decoding. As long as you're setting them using some interface rather than manually plugging bits off the "Fuse Bits" table I'll assume you have them right, since you've already quoted to me what the settings are and they sounded good.Hmm.... OK, basics. Are you 100% certain you're looking at the right pin, and your oscilloscope is set up to expect the right voltage and frequency?Try it with a different pin on a different port, I guess there's always the chance that one's blown.Set your probe to AC coupling, zoom in to the highest vertical sensitivity and a timebase near the chip's clock frequency, probe directly on a power or ground pin right at the chip package, and see if you pick up clock noise.Also, do you know how to get the assembly language output from the compiler? (If you're using GCC, give the -S flag. If you're in AVR-Atmel-whateverthehelltheycallitnow-Studio, I can't help you there.) Post it.

author: MrAureliusR
author_rank: Unknown
timestamp: September 04, 2013, 03:16:53 am
content: There's a crap-ton of 1MHz clock noise near the ground pin. I'll try a different port and different pin, just see if I can get an LED blinking.There's also about 40 or so mV of 60Hz mains noise... which seems higher than in my old place. Not sure why that is.EDIT: OMG!!! I unplugged the AVRISP's entire plug from the breadboard, and voila! PB4 is switching just as programmed... that's bizarre. I've never had the programmer interfere that badly! The 60Hz noise almost disappeared afterward.

author: c4757p
author_rank: Super Contributor
timestamp: September 04, 2013, 03:25:19 am
content: I've seen programmers hold the chip in reset, depending on the configuration - not sure about AVRISP. The mains noise is probably a completely unrelated coincidence caused by a ground loop involving the USB port and the oscilloscope. 40mV isn't too much.

author: Psi
author_rank: Super Contributor
timestamp: September 04, 2013, 03:25:33 am
content: Be sure to double check your MCU has a good 5V power and ground connection.It's really easy to assuming your MCU has power because you can reprogram it. However this is a bad assumption.Often you can reprogram the chip with only the programmer connected because power will flow into the i/o lines and partly power up the mcu.Edit:I see you got it working by unplugging the programmer. Yeah, as c4757p said. Reset is probable the issue. The programmer was probably holding it low.Most programmers have an option for the RESET output.This isn't applicable to you but when i program stuff with the printer port using avrdude i have to use "-E noreset" so the mcu is set to "run" state after the reprogram is complete.

author: MrAureliusR
author_rank: Unknown
timestamp: September 04, 2013, 03:28:06 am
content: The MCU definitely has power.Just tried the original program, with the SPI byte, and it's working like a charm with the AVRISP unplugged.About a month ago I accidentally put 120VAC into my breadboard, which had the AVRISP attached at the time. It seemed to be fine afterwards, but I've started to notice little quirks like this. I guess it's time to replace it... I also have a USBASP but I'm not a huge fan of it. I'd rather get another AVRISP mkII, or even a step up, like the AVR Dragon I think it's called?

author: Psi
author_rank: Super Contributor
timestamp: September 04, 2013, 03:33:57 am
content: Quote from: MrAureliusR on September 04, 2013, 03:28:06 amIt could also be the breadboard itself. Even a good breadboards can give connection problems.Did the 120V short out ? it may have coated parts of some holes internally with carbon and be causing bad connections. Or the heat may have removed the "spring"ness of the metal inside the holes

author: c4757p
author_rank: Super Contributor
timestamp: September 04, 2013, 03:36:49 am
content: Don't forget the very likely possibility that the breadboard has always been a giant turd, like mine...

author: MrAureliusR
author_rank: Unknown
timestamp: September 04, 2013, 03:52:59 am
content: It was a different breadboard that took the beating... and I'm using my smallest breadboard, but it's also the best quality one of the three I have. My huge one is the next best, so maybe I'll switch over to that, but it's covered in Z80 et alii...By the way, here's a picture of my sweet, sweet, success!For some reason, the output of the slave is very weak. Here it appears as a really low duty cycle, but on the scope it looks... well.. a bit wonky. Very weak, all logic 1's, and they're at different levels, they look capacitatively loaded (is capacitatively a word?)EDIT: Duh, it's ghosting the clock line.

author: MrAureliusR
author_rank: Unknown
timestamp: September 04, 2013, 05:27:53 am
content: I DID IT!!I successfully wrote to my EEPROM. I know this seems like a small thing but after all the hell I've gone through trying to get it working, this is like seeing the light! No arduino, no raspberry pi, just a breadboard, some wires, and a couple chips! Man I'm proud of myself.

author: Psi
author_rank: Super Contributor
timestamp: September 04, 2013, 06:55:09 am
content: Quote from: MrAureliusR on September 04, 2013, 05:27:53 amWell done. You're now an electronic engineering, please don't use your new powers for evil.I see you have a 150ms delay in your write loop. Your eeprom thanks you for that decision.Without that delay it would wear out the eeprom in no time at all.Better not leave it running though, even 150ms is still 6.66 writes per second.100,000 writes / 6.66 = 15151 seconds = 252 minutes = 4 hours until eeprom dead.

author: MrAureliusR
author_rank: Unknown
timestamp: September 04, 2013, 07:13:58 am
content: I wouldn't say I'm an EE yet. Wait till I have my diploma in 2-3 yrs.Yeah I kept that in mind when running the program. I ran power to the ATmega for a few seconds at a time.Next step is to write only when a button is pressed. I really need to learn how to use 7-segment displays. I want to create a PDP like switching panel for entering data into the Z80/EEPROM/RAM...Typed using Hacker's Keyboard for Android

author: mariush
author_rank: Super Contributor
timestamp: September 04, 2013, 02:09:09 pm
content: Have a look at this page, scroll down and it explains led 7segment displays and other things :http://www.mikroe.com/chapters/view/17/chapter-4-examples/