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
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.)
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?
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.
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;
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 I mean, I know you know because of
I generally don’t know anything. I just cobble things together. 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.