Atmel ATmega (ATmega16 / ATmega32) - MMC (Multi Media Card) Flash Memory Extension
UPDATE: Since MMC cards become scarce, and in some shops only SD cards (Secure Digital Card) are available anymore,
I've tried to use a SD card. See: Atmel ATmega (ATmega16 / ATmega32) - SD (Secure Digital Card) Flash Memory Extension
UPDATE: Since the Atmega32 MCU is just an ATmega16 with more Flash, RAM etc. the ATmega16 can be replaced by the ATmega32.
It is easy to interface a MMC (MultiMediaCard) with an Atmel ATmega16 (ATmega series) via the SPI (Serial Port Interface).
This is a very handy data logging circuit with lots of memory for data storage.
I2C RAM's or EEPROM's are hardly available at sizes bigger than 256kb, but this solution with a 64MB Flash MMC is not
to beat in both cost effectiveness and storage volume!
Also see: MicroChip PIC - MMC Flash Memory Extension
Schematic of the hardware: (ATmega16 pins for the DIP package)
This version utilizes an ATmega16, but it's easy to port the schematic and software to other ATmega's.
The MMC is connected to the SPI pins of the ATmega16 via simple resistor voltage dividers to transform the +5V high
levels to about 3.3V used by the MMC. The supply voltage for the MMC (2.7V - 3.6V) comes from a LD1086V33 voltage regulator (3.3V)
or equivalent. The data-out pin from the MMC goes directly to the ATmega16, because 3.3V is high for the ATmega16 anyway.
The MMC Clock can be in the range of 0-20MHz.
The firmware:
Use avr-gcc or WinAVR to compile mmc.c (source code below) and the
Atmel ATmega16 Parallel Port Programmer to program the uC.
"make" compiles the firmware, "make load" programs the uC with "uisp".
Linux software (serterm2) for receiving data over the serial port can be found at the
PIC - MMC (Multi Media Card) Flash Memory Extension page. Also on that page,
the program "ser". It sends three characters ("ABC") via the PC serial port and tries to receive them. These three
characters are returned by the MCU via the interrupt service routine.
Testing:
If your hardware is ready, disconnect power to it and start "serterm2". Now connect power to your
uC-board and you should see the following output on your computer via "serterm2":
# ./serterm
baud=9600
baud=9600
listening...
MCU online
MMC online
Captain was here! Captain was here! Captain was here! Captain was here! Captain was here!
Captain was here! Captain was here! Captain was here! Captain was here! Captain was here!
Captain was here! Captain was here! Captain was here! Captain was here! Captain was here!
Captain was here! Captain was here! Captain was here! Captain was here! Captain was here!
Captain was here! Captain was here! Captain was here! Captain was here! Captain was here!
Captain was here! Captain was here! Captain was here! Captain
512 bytes sent
blinking LED now
The firmware writes 512 bytes of data to the internal RAM and writes that sector to the MMC.
Then those 512 bytes are read back from the MMC and sent via the serial port. After that
the MCU goes into a loop and blinks the LED on pin 19 (similar to avrledtest.c at the
Atmel ATmega16 Parallel Port Programmer page).
Furthermore the interrupt service routine (ISR) returns any character sent over the serial port.
UPDATE:
The while loop in the MMC-read function can hang at high transfer speeds
while(SPI(0xFF) != (char)0xFE);
so you're probably better of with a solution like this:
int i;
uint8_t c;
for (i = 0; i < 100000; i++) {
c = SPI(0xFF);
if (c == (char)0xFE) break;
}
if (c != (char)0xFE) {
// ERROR HANDLING HERE
}
// proceed with MMC-read
(this code snippet has NOT been tested - it's just an idea)
Same goes for the while loop
while(SPI(0xFF) != (char)0xFF);
in the write function.
mmc.c:
/*********************************************
* Chip type : ATmega16
* Clock frequency : 2457600Hz
*********************************************/
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/signal.h>
#include <inttypes.h>
#include <avr/iom16.h>
#define F_OSC 2457600 /* oscillator-frequency in Hz */
#define UART_BAUD_RATE 9600
#define UART_BAUD_CALC(UART_BAUD_RATE,F_OSC) ((F_OSC)/((UART_BAUD_RATE)*16l)-1)
#define SPIDI 6 // Port B bit 6 (pin7): data in (data from MMC)
#define SPIDO 5 // Port B bit 5 (pin6): data out (data to MMC)
#define SPICLK 7 // Port B bit 7 (pin8): clock
#define SPICS 4 // Port B bit 4 (pin5: chip select for MMC
char sector[512];
void delay_ms(unsigned short ms) {
unsigned short outer1, outer2;
outer1 = 200;
while (outer1) {
outer2 = 1000;
while (outer2) {
while ( ms ) ms--;
outer2--;
}
outer1--;
}
}
void usart_putc(unsigned char c) {
// wait until UDR ready
while(!(UCSRA & (1 << UDRE)));
UDR = c; // send character
}
void uart_puts (char *s) {
// loop until *s != NULL
while (*s) {
usart_putc(*s);
s++;
}
}
void init(void) {
// set baud rate
UBRRH = (uint8_t)(UART_BAUD_CALC(UART_BAUD_RATE,F_OSC)>>8);
UBRRL = (uint8_t)UART_BAUD_CALC(UART_BAUD_RATE,F_OSC);
// Enable receiver and transmitter; enable RX interrupt
UCSRB = (1<<RXEN)|(1<<TXEN)|(1<<RXCIE);
//asynchronous 8N1
UCSRC = (1<<URSEL)|(3<<UCSZ0);
}
// INTERRUPT can be interrupted
// SIGNAL can't be interrupted
SIGNAL (SIG_UART_RECV) { // USART RX interrupt
unsigned char c;
c = UDR;
usart_putc(c);
}
void serialterminate(void) { // terminate sent string!!!
while(!(UCSRA & (1 << UDRE)));
UDR = 0x0d;
while(!(UCSRA & (1 << UDRE)));
UDR = 0x0a;
}
void SPIinit(void) {
DDRB &= ~(1 << SPIDI); // set port B SPI data input to input
DDRB |= (1 << SPICLK); // set port B SPI clock to output
DDRB |= (1 << SPIDO); // set port B SPI data out to output
DDRB |= (1 << SPICS); // set port B SPI chip select to output
SPCR = (1 << SPE) | (1 << MSTR) | (1 << SPR1) | (1 << SPR0);
PORTB &= ~(1 << SPICS); // set chip select to low (MMC is selected)
}
char SPI(char d) { // send character over SPI
char received = 0;
SPDR = d;
while(!(SPSR & (1<<SPIF)));
received = SPDR;
return (received);
}
char Command(char befF, uint16_t AdrH, uint16_t AdrL, char befH )
{ // sends a command to the MMC
SPI(0xFF);
SPI(befF);
SPI((uint8_t)(AdrH >> 8));
SPI((uint8_t)AdrH);
SPI((uint8_t)(AdrL >> 8));
SPI((uint8_t)AdrL);
SPI(befH);
SPI(0xFF);
return SPI(0xFF); // return the last received character
}
int MMC_Init(void) { // init SPI
char i;
PORTB |= (1 << SPICS); // disable MMC
// start MMC in SPI mode
for(i=0; i < 10; i++) SPI(0xFF); // send 10*8=80 clock pulses
PORTB &= ~(1 << SPICS); // enable MMC
if (Command(0x40,0,0,0x95) != 1) goto mmcerror; // reset MMC
st: // if there is no MMC, prg. loops here
if (Command(0x41,0,0,0xFF) !=0) goto st;
return 1;
mmcerror:
return 0;
}
void fillram(void) { // fill RAM sector with ASCII characters
int i,c;
char mystring[18] = "Captain was here! ";
c = 0;
for (i=0;i<=512;i++) {
sector[i] = mystring[c];
c++;
if (c > 17) { c = 0; }
}
}
int writeramtommc(void) { // write RAM sector to MMC
int i;
uint8_t c;
// 512 byte-write-mode
if (Command(0x58,0,512,0xFF) !=0) {
uart_puts("MMC: write error 1 ");
return 1;
}
SPI(0xFF);
SPI(0xFF);
SPI(0xFE);
// write ram sectors to MMC
for (i=0;i<512;i++) {
SPI(sector[i]);
}
// at the end, send 2 dummy bytes
SPI(0xFF);
SPI(0xFF);
c = SPI(0xFF);
c &= 0x1F; // 0x1F = 0b.0001.1111;
if (c != 0x05) { // 0x05 = 0b.0000.0101
uart_puts("MMC: write error 2 ");
return 1;
}
// wait until MMC is not busy anymore
while(SPI(0xFF) != (char)0xFF);
return 0;
}
int sendmmc(void) { // send 512 bytes from the MMC via the serial port
int i;
// 512 byte-read-mode
if (Command(0x51,0,512,0xFF) != 0) {
uart_puts("MMC: read error 1 ");
return 1;
}
// wait for 0xFE - start of any transmission
// ATT: typecast (char)0xFE is a must!
while(SPI(0xFF) != (char)0xFE);
for(i=0; i < 512; i++) {
while(!(UCSRA & (1 << UDRE))); // wait for serial port
UDR = SPI(0xFF); // send character
}
serialterminate();
// at the end, send 2 dummy bytes
SPI(0xFF); // actually this returns the CRC/checksum byte
SPI(0xFF);
return 0;
}
int main(void) {
init();
SPIinit();
uart_puts("MCU online");
serialterminate();
MMC_Init();
uart_puts("MMC online");
serialterminate();
sei(); // enable interrupts
fillram();
writeramtommc();
sendmmc();
uart_puts("512 bytes sent");
serialterminate();
uart_puts("blinking LED now");
serialterminate();
// enable PD5 as output
DDRD |= (1<<PD5);
while (1) {
// PIN5 PORTD clear -> LED off
PORTD &= ~(1<<PD5);
delay_ms(500);
// PIN5 PORTD set -> LED on
PORTD |= (1<<PD5);
delay_ms(500);
}
return 0;
}
Makefile:
MCU=atmega16
CC=avr-gcc
OBJCOPY=avr-objcopy
# optimize for size:
CFLAGS=-g -mmcu=$(MCU) -Wall -Wstrict-prototypes -Os -mcall-prologues
#-------------------
all: mmc.hex
#-------------------
mmc.hex : mmc.out
$(OBJCOPY) -R .eeprom -O ihex mmc.out mmc.hex
mmc.out : mmc.o
$(CC) $(CFLAGS) -o mmc.out -Wl,-Map,mmc.map mmc.o
mmc.o : mmc.c
$(CC) $(CFLAGS) -Os -c mmc.c
# you need to erase first before loading the program.
# load (program) the software into the eeprom:
load: mmc.hex
uisp -dlpt=/dev/parport0 --erase -dprog=dapa
uisp -dlpt=/dev/parport0 --upload if=mmc.hex -dprog=dapa -v=3 --hash=32
#-------------------
clean:
rm -f *.o *.map *.out
#-------------------
Last-Modified: Mon, 18 Jun 2007 17:24:53 GMT