20.0 Internal Flash [T-Series Datasheet]
Overview
T-series devices have non-volatile flash memory. This guide will often refer the flash memory as "internal flash".
Internal flash memory is divided into two regions:
The User Area - For storing user-data.
The Reserved Area - Used to store important device information such as calibration constants.
The size of the internal flash varies by device:
Device | Internal Flash Size | User Area Size | Reserved Area Size |
---|---|---|---|
T4 | 4 MB | 2 MB | 2 MB |
T7 | 4 MB | 2 MB | 2 MB |
T8 | 8 MB | 4 MB | 4 MB |
Subsections
Flash Addresses
Internal flash is addressed by byte, but accessed by 32-bit values. That means that the first value is at address zero and the second is at address 4. (These are flash addresses—not to be confused with Modbus addresses.)
Following are some important flash addresses:
T4/T7
Address Name | Address | Length | Key |
---|---|---|---|
User Area | 0x000000 | 2 MB | 0x6615E336 (1712710454 in decimal) |
Reserved Area | 0x200000 | 2 MB | N/A |
Calibration Constants (in Reserved Area) | 0x3C4000 | 4 kB | 0x43A24C42 (1134709826 in decimal) |
T8
Address Name | Address | Length | Key |
---|---|---|---|
User Area | 0x000000 | 4 MB | 0x6615E336 (1712710454 in decimal) |
Reserved Area | 0x600000 | 4 MB | N/A |
Calibration Constants (in Reserved Area) | 0x687000 | 4 kB | 0xA7863777 (2810591095 in decimal) |
Reading
To read from flash, write the desired address to INTERNAL_FLASH_READ_POINTER and then read an even number of registers from INTERNAL_FLASH_READ:
To read a large number of registers from INTERNAL_FLASH_READ (such as more than ~25 registers), you must split the read into multiple packets while updating INTERNAL_FLASH_READ_POINTER each time. See the LJM explanation for how to perform large reads.
Writing and Erasing
Key
For a region to be written to or to be erased, the key for that region must be written to the INTERNAL_FLASH_KEY register:
The key prevents accidental overwrites. The value in INTERNAL_FLASH_KEY will be cleared when the Modbus packet that wrote it has been fully processed, so INTERNAL_FLASH_KEY must be written in the same packet that does INTERNAL_FLASH_WRITE or INTERNAL_FLASH_ERASE. The LJM Multiple Value functions simplify this. The LJM Multiple Value functions do not correctly split large reads (such as more than ~15 registers), so see the LJM explanation for how to perform large writes.
Writing
To write to flash, the area to be written to must first be erased. Once erased, write the key for the desired region to INTERNAL_FLASH_KEY, write the desired address to INTERNAL_FLASH_WRITE_POINTER, and then write an even number of registers to INTERNAL_FLASH_WRITE:
Erasing
Flash is erased 4 kB at a time. Erasing sets all bits to 1. To erase a 4 kB region, write the key for the desired region to INTERNAL_FLASH_KEY and the desired address to INTERNAL_FLASH_ERASE:
The address will be rounded down to the nearest 4 kB boundary. Boundaries are easy to identify when the address is displayed in hexadecimal because the lower three digits will be zero. 4 kB is hexadecimal is 0x1000.
Endurance
Flash memory can only be erased so many times before bit errors will start to occur; it is important not to erase or write flash needlessly. Typical life of flash memory is at least 10,000 cycles.
Examples
Internal Flash Examples - c#
// Erase address 0
numFrames = 2
aNames[0] = "INTERNAL_FLASH_KEY"
aNames[1] = "INTERNAL_FLASH_ERASE"
aWrites[0] = LJM.CONSTANTS.WRITE
aWrites[1] = LJM.CONSTANTS.WRITE
aNumValues[0] = 1
aNumValues[1] = 1
aValues[0] = 1712710454
aValues[1] = 0
LJM.eNames(handle, numFrames, aNames, aWrites, aNumValues, aValues, errorAddress)
// Write address 0 = 1234,
// write address 4 = 5678
numFrames = 3
aNames[0] = "INTERNAL_FLASH_KEY"
aNames[1] = "INTERNAL_FLASH_WRITE_POINTER"
aNames[2] = "INTERNAL_FLASH_WRITE"
aWrites[0] = LJM.CONSTANTS.WRITE
aWrites[1] = LJM.CONSTANTS.WRITE
aWrites[2] = LJM.CONSTANTS.WRITE
aNumValues[0] = 1
aNumValues[1] = 1
aNumValues[2] = 2
aValues[0] = 1712710454
aValues[1] = 0
aValues[2] = 1234
aValues[3] = 5678
LJM.eNames(handle, numFrames, aNames, aWrites, aNumValues, aValues, errorAddress)
// Read address 0 and address 4
numFrames = 2
aNames[0] = "INTERNAL_FLASH_READ_POINTER"
aNames[1] = "INTERNAL_FLASH_READ"
aWrites[0] = LJM.CONSTANTS.WRITE
aWrites[1] = LJM.CONSTANTS.READ
aNumValues[0] = 1
aNumValues[1] = 2
aValues[0] = 0
aValues[1] = 9999
aValues[2] = 9999
LJM.eNames(handle, numFrames, aNames, aWrites, aNumValues, aValues, errorAddress)
Console.WriteLine(aValues[1], aValues[2])
// Output:
// 1234 5678
For an embedded Lua scripting example see write_read_flash.lua.