ATTINY402

Go To Last Post
15 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I am setting up a project that requires the use if eeprop. I think that I am pretty close but would appreciate a second set of eyes. My project is a battery cutoff and will use the ADC, eeprom, and external interrupts. For now I am focusing on just the eeprom part. I need to save the number of cells that the user selects and be able to load that number on every boot. This value is stored whenever it is changed in the external interrupt ISR. My parameters are to limit this number to between 2 and 6 and increments on every falling edge of the button (I have used a software debounce).

 

In the main loop all it does is flash an LED the number of times that the variable is set to. 

 

This all works but will not load the eeprom value when it boots, in the debugger it shows that the value is being saved into the eeprom but not returned. Please Help!

 

#define F_CPU 20000000UL
#define CellVoltage 131                        // voltage of about 3.2V
#define CellCount  0x00

#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>
#include <math.h>
#include <avr/eeprom.h>

uint16_t CutVoltage = 0;
uint8_t CellCountv = 0;                //Needs to be saved to EEPROM
uint16_t ADC_val = 0;
uint16_t Voltage = 0;

void clock_init(void){
    _PROTECTED_WRITE(CLKCTRL_MCLKCTRLB, 0x00);
}

void IOinit(void){

    PORTA.DIRCLR = PIN2_bm | PIN6_bm;                    //Set PA2 and PA6 as inputs
    PORTA.DIRSET = PIN1_bm | PIN7_bm;                    //Set PA1 and PA7 as outputs

    PORTA.PIN2CTRL = PORT_INVEN_bm | PORT_PULLUPEN_bm;
    PORTA.PIN2CTRL |= PORT_ISC_FALLING_gc;        //enable the PIN2 External Interrupt on falling edge, and enable the pullup
    
}

void ADC_init(void){
    
    ADC0.CTRLA = ADC_FREERUN_bm;                    //Set ADC to free running Mode
    ADC0.CTRLB = ADC_SAMPNUM_ACC64_gc;                //SET the ADC to sum 64 samples
    ADC0.CTRLC = ADC_SAMPCAP_bm | ADC_REFSEL1_bm;    //Select VDD as the reference
    ADC0.CTRLD = ADC_INITDLY_DLY256_gc;                //Set the init delay before first sample    
    ADC0.MUXPOS = ADC_MUXPOS_AIN6_gc;                //SET PA6 as the input for the ADC
    ADC0.INTCTRL = ADC_RESRDY_bm;                    //eneable the result ready inturrupt
    
    ADC0.COMMAND = ADC_STCONV_bm;                    // Start the conversion

}

ISR(ADC0_RESRDY_vect){
    /*
    ADC_val = ADC0.RES;
    Voltage = (ADC_val/64) * 5;
    
    if(Voltage < CutVoltage){
        PORTA_OUTCLR = PIN7_bm;                        // turn off the main power Mosfet
    }*/
}

ISR(PORTA_PORT_vect){                    //Triggered on PA2 push button
    _delay_ms(5);
    
    if((PORTA_IN & PIN7_bm) == 0){                    //if the Power Mosfet is on and power is flowing we want to set the cell count
        
        if(CellCountv == 6){
            CellCountv = 1;
        }else{
        CellCountv++;
        }
        
        eeprom_write_byte((uint8_t*)CellCount, CellCountv);

    
        
    }else{    
        PORTA_OUTSET = PIN7_bm;                        //Turn Power back on
    }
    
    
    
    PORTA.INTFLAGS = PIN2_bm;                        //CLear the int Flag
    
}

int main(void)
{

    clock_init();            //Set Clock to 20MHz
    IOinit();                //set all IO
    sei();
    
    CellCountv = eeprom_read_byte((uint8_t*)CellCount);
    

    
    //CutVoltage = CellCountv * CellVoltage;
    
    //CellCountv = 0;
    
    while (1) 
    {
                
        for(uint8_t i = 0; i < CellCountv; i++){                    //Blink the number of times as the cell count

            PORTA_OUTSET = PIN1_bm;                                //SET the LED
            _delay_ms(125);
            PORTA_OUTCLR = PIN1_bm;                                //Clear the LED
            _delay_ms(125);
            
        }
        
        _delay_ms(500);

    }
}
 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

'eeprom_write_byte' is for a non 0/1 series avr. You have a tiny402, so it will not work.

Get out the datasheet and write your own eeprom write function.

 

edit-

disregard, i don't know what I'm talking about.

Last Edited: Wed. Sep 18, 2019 - 04:12 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

It should look something like this, I think:

 

#define CellCount  (*(uint8_t*)(MAPPED_EEPROM_START + 0x00))
//       eeprom_write_byte((uint8_t*)CellCount, CellCountv);
	 // Clear page buffer, so that we for sure only write one byte
	 _PROTECTED_WRITE_SPM(NVMCTRL.CTRLA, NVMCTRL_CMD_PAGEBUFCR_gc);
	 // write to page buffer (jusr a normal store!)
	 CellCount = CellCountv;
	 // write the page (only writes bytes that have been written)
	 _PROTECTED_WRITE_SPM(NVMCTRL.CTRLA, NVMCTRL_CMD_PAGEERASEWRITE_gc);
	 // (optional) Wait for the write to complete:
	 while (NVMCTRL.STATUS & NVMCTRL_EEBUSY_bm)
	    ;   // spin

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

'eeprom_write_byte' can be used from AVR0 / 1 considerably early.
I don't have tiny402, so I tested with mega4809 with similar functions. eeprom can be accessed normally by your code.
The screen shot shows a state where 05h was written by the port interrupt and the power was turned on again. It is loaded into the variable by eeprom_read_byte by the initial operation.
When viewing eeprom in the debugger, look in sram mode.

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

If “Preserve EEPROM” is not checked in the AS7 settings, eeprom will be erased when debugging starts.

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

My bad. It looks like they have prototype functions in eeprom.h, and a libattiny204.a libattiny402.a file to get them from.

 

I'm using MPLABX, and also using a mega4809 for the test, and nothing shows in the eeprom memory view after the write, but reading the value to a var gets a correct value so must be working.

 

You could use the USERROW address (is one more page/32bytes of eeprom, and would be at address 128). Then you don't have to worry about programming always messing with your values (depending on fuse settings, etc.).

Last Edited: Wed. Sep 18, 2019 - 02:32 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0


This prompted me to look at how the EEPROm works in AVR-0/1. So it's mapped to appear in the linear memory map at 0x1400 onwards so reading it is little more than adding 0x1400 to the requested address and reading from there:

	volatile uint8_t foo;
	
	foo = eeprom_read_byte((uint8_t *)7);

yields

	foo = eeprom_read_byte((uint8_t *)7);
  50:	87 e0       	ldi	r24, 0x07	; 7
  52:	90 e0       	ldi	r25, 0x00	; 0
  54:	06 d0       	rcall	.+12     	; 0x62 <eeprom_read_byte>
  56:	89 83       	std	Y+1, r24	; 0x01

in turn that does:

00000062 <eeprom_read_byte>:
  62:	03 d0       	rcall	.+6      	; 0x6a <eeprom_mapen>
  64:	80 81       	ld	r24, Z
  66:	99 27       	eor	r25, r25
  68:	08 95       	ret

0000006a <eeprom_mapen>:
  6a:	fc 01       	movw	r30, r24
  6c:	e0 50       	subi	r30, 0x00	; 0
  6e:	fc 4e       	sbci	r31, 0xEC	; 236
  70:	08 95       	ret

Subtracting 0xEC00 is the same as adding 0x1400. So this just a LD via Z from 0x1407

 

As for writing:

	eeprom_write_byte((uint8_t *)1, 0x55);

creates:

	eeprom_write_byte((uint8_t *)1, 0x55);
  58:	65 e5       	ldi	r22, 0x55	; 85
  5a:	81 e0       	ldi	r24, 0x01	; 1
  5c:	90 e0       	ldi	r25, 0x00	; 0
  5e:	09 d0       	rcall	.+18     	; 0x72 <eeprom_write_byte>

which is supported by:

00000072 <eeprom_write_byte>:
  72:	26 2f       	mov	r18, r22

00000074 <eeprom_write_r18>:
  74:	af 93       	push	r26
  76:	bf 93       	push	r27
  78:	e0 e0       	ldi	r30, 0x00	; 0
  7a:	f0 e1       	ldi	r31, 0x10	; 16
  7c:	32 81       	ldd	r19, Z+2	; 0x02
  7e:	31 fd       	sbrc	r19, 1
  80:	fd cf       	rjmp	.-6      	; 0x7c <eeprom_write_r18+0x8>
  82:	dc 01       	movw	r26, r24
  84:	a0 50       	subi	r26, 0x00	; 0
  86:	bc 4e       	sbci	r27, 0xEC	; 236
  88:	2c 93       	st	X, r18
  8a:	2d e9       	ldi	r18, 0x9D	; 157
  8c:	24 bf       	out	0x34, r18	; 52
  8e:	23 e0       	ldi	r18, 0x03	; 3
  90:	20 83       	st	Z, r18
  92:	01 96       	adiw	r24, 0x01	; 1
  94:	bf 91       	pop	r27
  96:	af 91       	pop	r26
  98:	08 95       	ret

In this significant addresses are that 0x34 is the CCP register (and 0x9D is the "unlock" value), 0x1000 is the address of NVMCTRL and the first register is CTRLA:

 

So a write of 0x03 is an "ERWP"

 

Oh and once again a SUB 0xEC00 is the equivalent to an ADD 0x1400 to map to the EEPROM space in the linear address map.

 

All quite interesting in fact!

 

(pretty sure there must be open source for this somewhere!)

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

>pretty sure there must be open source for this somewhere

 

You can download the XC8 source from mchip. The ee functions are in libc/misc, and seems to be similar enough to xmega that they only needed to made a few changes.

 

 

Last Edited: Wed. Sep 18, 2019 - 02:33 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Your'e looking at the CellCountv, this is the variable used while the system is booted. when this value is changed then it is saved into CellCount which is eeprom.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0


I can't help thinking your solution lies in post #5 ;-)

 

Also I don't know these new models of AVR but do they have anything like the EESAVE fuse of old AVRs?

 

EDIT: just answered my own question:

 

Last Edited: Wed. Sep 18, 2019 - 03:02 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I repeat. Your program works correctly as you intended.

 

However, every time you start debugging, the EEPROM is initialized to FFh by chip erase.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

#define CellCount 0x80 //userrow

 

If you want to limit to 2-6, you should check for >6 or <2 on startup to eliminate the 0xff, 0, 1 cases.

 

//startup

CellCountv = eeprom_read_byte((uint8_t*)CellCount);
if( CellCountv < 2 || CellCountv > 6 ){

  CellCountv = 2;
  eeprom_write_byte((uint8_t*)CellCount, CellCountv);

}

 

//isr
if(++CellCountv > 6) CellCountv = 2;    
eeprom_write_byte((uint8_t*)CellCount, CellCountv);

Last Edited: Wed. Sep 18, 2019 - 04:29 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

This prompted me to look at how the EEPROm works in AVR-0/1. So it's mapped to appear in the linear memory map at 0x1400 onwards so reading it is little more than adding 0x1400 to the requested address and reading from there:

	volatile uint8_t foo;
	
	foo = eeprom_read_byte((uint8_t *)7);

yields

	foo = eeprom_read_byte((uint8_t *)7);
  50:	87 e0       	ldi	r24, 0x07	; 7
  52:	90 e0       	ldi	r25, 0x00	; 0
  54:	06 d0       	rcall	.+12     	; 0x62 <eeprom_read_byte>
  56:	89 83       	std	Y+1, r24	; 0x01

in turn that does:

00000062 <eeprom_read_byte>:
  62:	03 d0       	rcall	.+6      	; 0x6a <eeprom_mapen>
  64:	80 81       	ld	r24, Z
  66:	99 27       	eor	r25, r25
  68:	08 95       	ret

0000006a <eeprom_mapen>:
  6a:	fc 01       	movw	r30, r24
  6c:	e0 50       	subi	r30, 0x00	; 0
  6e:	fc 4e       	sbci	r31, 0xEC	; 236
  70:	08 95       	ret

Wow.  Really?  Sigh.  I remember when I could count on avr-libc to be less overly-abstracted and bloated than ASF :-(

I would have hoped that eeprom_read_byte(7) would have yielded a single instruction lds r, 0x1407

 

The "everything mapped" strategy IS nice, and the nvmctrl is especially neat and convenient (I've been working on a mega-0/xTiny version of the Optiboot bootloader.  It's significantly smaller and cleaner (with more features) than the normal version!  (alas, enough so that I think it warrants separate source and makefiles.)

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

westfw wrote:
I would have hoped that eeprom_read_byte(7) would have yielded a single instruction lds r, 0x1407
The  joy of open source is that it could be like that ;-)

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

>You could use the USERROW address (is one more page/32bytes of eeprom, and would be at address 128).

>#define CellCount 0x80 //userrow

 

Wrong again, and again. I was just not thinking at the time, although I must have known it as my own nvm functions are using the correct addresses.

 

Although the userrow is 'one extra page' of eeprom, its address is at 0x1300 where the eeprom starts at 0x1400.

 

To use the avr-libc eeprom_write_x functions, I imagine you would want to come up with something like-

#define USERROW_ADDR(addr) (uint8_t*)(addr - 0x100)
eeprom_write_byte(USERROW_ADDR(0),1);

which will get you to the 0x1300 address where userrow lives (define is just to make it more friendly to use).

 

 

I have my own c++ class for nvm, and have been playing around with operator overloading-

#include "Nvmctrl.hpp"
#define EEMEM __attribute__((section(".eeprom")))

template<typename T>
struct EEdata {
  //one val of type T
  //no sram storage, just .eeprom allocated of type T
  //we know our own address (this)
  //with operator overloading, can use Eeprom::read/write
  //  functions on this eeprom var (nvm functions are 0 address based, so (uint16_t)this can be used as-is)
  T val{(T)-1}; //needed to take up storage in .eeprom section, if no init val, then will be 0xFF...
  T operator = (T v) {
      if( *this != v ){ //write only if new value (cost is only a lds,cpi,breq)
        Eeprom::write<T>((uint16_t)this, v);
      }
      return v;
  }
  operator T ()        { return Eeprom::read<T>((uint16_t)this); } //return T (read from eeprom)
  T operator += (int v){ T rv = *this+v; Eeprom::write<T>((uint16_t)this, rv ); return rv; } //var += v returns var+v
  T operator -= (int v){ return *this += -v; } //var -= v returns var-v
  T operator ++ (int)  { T rv = *this; Eeprom::write<T>((uint16_t)this, rv+1); return rv; } //var++ returns var
  T operator ++ ()     { return *this += 1; } //++var returns var+1
  T operator -- (int)  { T rv = *this; Eeprom::write<T>((uint16_t)this, rv-1); return rv; } //var-- returns var
  T operator -- ()     { return *this -= 1; } //--var returns var-1
  //and so on
};

//with EEMEM attribute, will end up in .eeprom section
//(only needed if initial values wanted in hex file)
EEMEM EEdata<uint8_t> myee1; //default init value 0xFF
//EEMEM EEdata<uint8_t> myee1{0x55}; //init value
//EEMEM EEdata<uint8_t> myee1[8]{1,2,3,4,5,6,7,8}; //array initialized
//EEMEM EEdata<uint8_t> myee1[8]; //array not initialized
//EEMEM EEdata<int> myee1[8]; //array of int's not initialized

int main(){
    myee1++;
    for(;;){}
}

00000084 <main>:
  //myee1 + 1
  84:    80 91 00 14     lds    r24, 0x1400    ; 0x801400 <__RODATA_PM_OFFSET__+0x7f9400>
  88:    8f 5f           subi    r24, 0xFF    ; 255

  //ee busy?
  8a:    90 91 02 10     lds    r25, 0x1002    ; 0x801002 <__RODATA_PM_OFFSET__+0x7f9002>
  8e:    92 30           cpi    r25, 0x02    ; 2
  90:    e1 f3           breq    .-8          ; 0x8a <main+0x6>

  //write new val
  92:    80 93 00 14     sts    0x1400, r24    ; 0x801400 <__RODATA_PM_OFFSET__+0x7f9400>

  //ccp
  96:    8d e9           ldi    r24, 0x9D    ; 157
  98:    84 bf           out    0x34, r24    ; 52

  //cmd = ERWP
  9a:    83 e0           ldi    r24, 0x03    ; 3
  9c:    80 93 00 10     sts    0x1000, r24    ; 0x801000 <__RODATA_PM_OFFSET__+0x7f9000>

  a0:    ff cf           rjmp    .-2          ; 0xa0 <main+0x1c>

  

It would take a little effort to complete the class, as it requires some thought as to what operators can apply and get everything correct. I'm not entirely sure its worth it, but it is kind of neat it can be done at all. Arrays also work it seems (without any changes), but structs would be a problem.