Embedded. Fail to write to register

Hello all.

I have an embedded project on the ATTiny816 which is almost done. I have gotten all peripherals working that I need and testing and certification is going fine and dandy. However, I’ve not been successful in getting the internal EEPROM working. There is something funny going on. The theory is fairly simple, you write to a page register, unlock the control register and issue the write command.

The vendor example code is as such:

 nvmctrl_status_t FLASH_0_write_eeprom_block(eeprom_adr_t eeprom_adr, uint8_t  *data, size_t size)
 {
 	uint8_t  *write = (uint8_t *)(EEPROM_START + eeprom_adr);
 	/* Wait for completion of previous write */
 	while (NVMCTRL.STATUS & NVMCTRL_EEBUSY_bm)
 		;
 	/* Clear page buffer */
 	ccp_write_spm((void *)&NVMCTRL.CTRLA, NVMCTRL_CMD_PAGEBUFCLR_gc);
 	do {
 		/* Write byte to page buffer */
 		*write++ = *data++;
 		size--;
 		// If we have filled an entire page or written last byte to a partially filled page
 		if ((((uintptr_t)write % EEPROM_PAGE_SIZE) == 0) || (size == 0)) {
 			/* Erase written part of page and program with desired value(s) */
 			ccp_write_spm((void *)&NVMCTRL.CTRLA, NVMCTRL_CMD_PAGEERASEWRITE_gc);
 		}
 	} while (size != 0);
 	return NVM_OK;
 }

Where EEPROM_START address is at 0x1400, EEPROM_PAGE_SIZE is 32.

What I’m doing in Ada is (for writing 1 byte, I have also tried it with an array with same result)

 procedure Put (Address : EEprom_Address; Data : Unsigned_8) is
     Buffer : Unsigned_8 with Volatile;
     for Buffer’Address use MCU.E2Start + Address;
 begin
     Busy_Wait;
     Clear_Page_Buffer;
     Buffer := Data;
     -- Unlock
     CCP := 16#9d#;
     CtrlA.CMD := Erase_And_Write_Page;
 end Put; 

The problem is that the value in Buffer is never updated and stays at the initial value. If i write a value or data to it in gdb it will update and write to the EEPROM correctly.

vr.nvmctrl.put (address=0, data=0) at /home/.../AVRAda_Lib/src/avr-nvmctrl.adb:69
69          Buffer := Data;
(gdb) 
71          CCP := 16#9d#;
(gdb) p buffer
$3 = 255
(gdb) p data
$4 = 0
(gdb) set buffer := data
(gdb) p buffer
$5 = 0

Anyone with any ideas why?
Thanks
Henrik

Could you reformat the presented code?
For the C stuff use ```C+[New_Line]+[TEXT]+[New_Line]+```; Ada is the default here (obviously) so you don’t need to specify it, but some good indentation helps with readability. (Also, I don’t think there’s any need to use escape-characters in your [TEXT], just directly paste it.)

Sorry for that. I tried to clean it up now. Thanks for the help.

I can’t see any obvious problems with the code you provided. The overlay looks correct. Is MCU.E2Start the correct address? Could the problem maybe be that implementation of Clear_Page_Buffer is not working correctly?

Clear page buffer is simply writing a 4 to the Ctrla register and I’m not entirely sure that it is strictly necessary either.

procedure Clear_Page_Buffer is
  begin
    CCP := 16#9d#;
    CtrlA.CMD := Page_Buffer_Clear;
  end Clear_Page_Buffer; 

The page buffer is memory mapped


and
E2Start : constant := 16#1400#;
Examining the address off Buffer in gdb shows the correct address. (GDB shows addresses in the 0x800000 space)

(gdb) p buffer'address
$3 = (system.address) 0x801400

If i break the program before the CCP unlock and set the Buffer variable to something else and continue, the EEPROM contents is updated correctly to that value. So the unlock and write should work fine.

If you break the program before the CCP, but then let it resume, does it work then? If so, it might be a timing issue. Otherwise, I can’t see why the write would work in GDB but not in Ada. It might be worth looking at the assembly code for clues.

No difference. I can see the contents of the page buffer with my debugger and it does not change if I just let the program run. If i stop and write to it with GDB it changes immediately.

:edit:

Dump of assembler code for function avr__nvmctrl__put:
   0x00000f5c <+0>:     push    r28
   0x00000f5e <+2>:     mov     r28, r22
   0x00000f60 <+4>:     rcall   .-14            ;  0xf54 <avr__nvmctrl__busy_wait>
   0x00000f62 <+6>:     rcall   .-46            ;  0xf36 <avr__nvmctrl__clear_page_buffer>
   0x00000f64 <+8>:     sts     0x1400, r28     ;  0x801400
=> 0x00000f68 <+12>:    ldi     r24, 0x9D       ; 157
   0x00000f6a <+14>:    out     0x34, r24       ; 52
   0x00000f6c <+16>:    ldi     r30, 0x00       ; 0
   0x00000f6e <+18>:    ldi     r31, 0x10       ; 16
   0x00000f70 <+20>:    ld      r24, Z
   0x00000f72 <+22>:    andi    r24, 0xF8       ; 248
   0x00000f74 <+24>:    ori     r24, 0x03       ; 3
   0x00000f76 <+26>:    st      Z, r24
   0x00000f78 <+28>:    pop     r28
   0x00000f7a <+30>:    ret

Based on my limited knowledge of AVR assembly, this instruction looks like it does the actual write to Buffer:

   0x00000f64 <+8>:     sts     0x1400, r28     ;  0x801400

Are you also able to disassemble the C code so we can compare? I wonder if, for some reason, a different instruction should be used to write to that part of the address space. This is just a guess though, I don’t know why there would be any difference in this regard between the C and Ada code.

I got it fixed with some help from avr-freaks. The write command needs to be written within 4 clock cycles from unlock so I wrapped the write/unlock function in avr-libc for now which is written in pure assembler.

 procedure Protected_Write_SPM
   (ioaddr : access Protected_Address; Value : Unsigned_8)
  with Import => True, Convention => C, External_Name => "ccp_write_spm";

and then quick and dirty to test it

 procedure Put (Address : EEprom_Address; Data : in Nat8_Array) is
    CtrlA_A : aliased Protected_IO.Protected_Address;
    for CtrlA_A'Address use 16#01000#;
  begin

    Busy_Wait;
    Clear_Page_Buffer;
    for I in Data'Range loop
      Page_Buffer (I) := Data (I);
    end loop;
    -- Unlock
    Protected_IO.Protected_Write_SPM
     (ioaddr => CtrlA_A'Access, Value => 16#03#);
  end Put;

Thank you very much for you help.

2 Likes

Just FYI (though you probably already know it),

Access in Ada is different to Address. Address is the actual memory address, which is generally the same as the access, but it may not be :slight_smile: I mean, I know you know because of

But still :wink: Best regards,
Fer

1 Like

I generally don’t know anything. I just cobble things together. :smiley: What is really the “accepted” way to pass a pointer to a c function in ada? Access is sort of a pointer and a pointer is an address in c and an access to an object needs to be aliased to have an address. Confusing coming from a c background.

Ada uses “access” rather than “pointer” to avoid constraining implementations. Consider an array allocated on the heap. In Ada, that array may also need to have bounds information. Where does that go? The language doesn’t say. You can put the bounds on the heap with the data:

[bounds][data]

in which case the access value is simply an address somewhere in there, usually either the start of the bounds or the start of the data. But you can also choose to put the bounds in the access value, and only store the data on the heap. In that case the access value is roughly equivalent to

type Access_Value is record
   Bounds : Bounds_Info;
   Ptr    : System.Address;
end record;

Similar choices exist for variant records. Both approaches are used. I have seen expensive porting projects that could have been a simple recompilation if the developers had not relied on the original compiler having addresses and access values being the same.

1 Like