Вариантов много Если писать именно
unsigned char III[128][64];
то это компиляторно зависимо и однозначного ответа нет.
Например для GCC ты можешь описать свой сегмент данных и указать
его начало и размер в опциях компилятора/линкера, а в исходнике
соответственно ссылаться на этот сегмент всякими там __attribute__()
А компиляторно независимо я б написал примерно так:
#pragma pack(push(1))
typedef struct my_struct_of_64_sharc
{
unsigned char buff[64];
} my_struct_of_64_chars_t;
#pragma pack(pop())
#define EXTERNAL_MEMORY_START_ADDR 0x80000000 // Тут твой адрес
my_struct_of_64_chars_t * pMyExtRamArray = (my_struct_of_64_chars_t*) EXTERNAL_MEMORY_START_ADDR;
.....
// ну и далее обращения типа:
pMyExtRamArray[x].buff[y];
из минусов такого подхода видимо будут вычисление и контроль начального
адреса и длины в случае когда таких массивов тебе понадобится несколько/много,
в этом случае видимо намного удобнее использование дополнительного сегмента
данных.
Также многие компиляторы основываются на размерах массивов для генерации
более оптимального кода. Например для 51-го проца код для доступа к элементу
массива объявленному как:
unsigned char code buff[256];
и
unsigned char code buff[257];
или
unsigned char code buff*;
у franklin-а генерился разный, т.к. у него есть команды для адресации
на основе 8-битного смещения (DPTR+A).
Но для ARM-ов это наверное не существенно.