Перейти к основному содержимому

PAGED память на HCS08 / HCS12

Зачем эта статья

С AC96/AC128 в MultiProg всё в порядке — чтение, запись, verify и TRIM работают «из коробки», MultiProg сам разберется со страничной памятью. Никаких ручных действий с PPAGE для повседневной работы не требуется.

Эта статья — обучающая для тех, кто хочет разобраться, как устроена страничная память на HCS08, почему дампы USBDM и MultiProg выглядят по-разному, и как при необходимости конвертировать прошивки между двумя форматами.

Наверное, каждый мастер-электронщик взрывал мозг, изучая Memory Map и устройство страничной памяти (PAGEd memory) при работе с семействами МК (МК — микроконтроллер) HCS08 / HCS12.

И хотя эти МК уже старенькие и их эпоха ушла, мы всё равно периодически с ними сталкиваемся. Поэтому решили (в том числе для себя, чтобы не забыть) основательно разобрать вопрос в этой статье.

Все мы привыкли к линейной памяти: по адресу 0x0000 лежит один-единственный байт, по 0x0001 — следующий, по 0x0002 — следующий за ним, и так подряд, пока память не кончится. Как в обычной книге: на странице 5 всегда то, что написано на странице 5, и чтобы туда попасть, вы просто открываете книгу на этой странице. Адрес — и есть указатель на байт, никаких посредников между ними.

Линейная организация памяти логична и последовательна, поэтому психологически воспринимается проще.

0. Что это такое и зачем оно здесь?

10 лет назад, когда я впервые столкнулся с МК MC9S12C128, я думал, что страничная память — это какая-то крутая фича, смысл которой понятен только избранным. Ну то есть как это работает — понятно, а зачем — понимают только супер-разработчики.

Занимаясь этим семейством МК последние месяцы, могу с уверенностью сказать: это костыль, который по сути закрывает проблему производителя, никаких фичей здесь нет.

В этой статье будем часто обращаться к Application Note AN3730 — собственно, там производитель прямо и пишет:

«Memory paging provides digital systems with greater memory addressing capabilities without expanding basic architecture resources, such as bus size or address pointers — AN3730 §1, первый абзац.

В переводе: «чтобы не пришлось расширять шину и менять указатель — то есть не менять архитектуру, — но выпустить МК с большим объёмом памяти, мы придумали интересную технологию». Решение было продиктовано исключительно задачами производителя.

1. Суть-то в чём?

Суть — в двойной системе координат. У страниц (PAGE) нет реального адреса, обратиться к ним можно только через специальный механизм. Одна система координат — обычная, линейная: там лежат RAM, регистры, и какие-то страницы в неё тоже попадают (кусками). Во второй системе страницы существуют, но в общей карте памяти их фактически нет — есть только их части, выглядывающие через окно PPAGE.

Из-за этого каждый разработчик программатора-софта может по-своему задать этим разделам адреса, разбивку и т.п. Отсюда и проблемы с файлами прошивок — единого стандарта по факту нет.

Если вы хоть раз читали MC9S08AC128 разными программаторами, то заметили: файлы дампов выглядят совершенно по-разному. МК один, содержимое одно, а адреса в файле — другие.

«Классная» картинка Memory Map

Из-за двойной системы координат начинаются приколы. Следите за руками: в линейном пространстве с 0x20F0 идёт кусок 7952 байта из страницы 0.

Это рисунок 4-1 из MC9S08AC128 Reference Manual. Половина электронщиков, впервые на него глядя, делает вывод, что Page 0 начинается с 0x20F0. Ошибка в том, что Page 0 существует отдельно — у неё, как и у остальных страниц, 16 КБ. Просто в линейном пространстве у неё «торчит» хвост на 7952 байта (видимо особенности производства).

Карта памяти AC128 — RM, рисунок 4-1

Самая близкая бытовая аналогия — застройка. Живут 3 семьи (по 16 КБ на семью) в своих домах: всё у них хорошо, есть номера домов и отдельный подъезд — это линейная память. А ещё 5 семей (тоже по 16 КБ на семью) застройщик поселил в одну многоэтажку, к которой ведёт одна дорога и пара тропинок. По бумагам живёт 8 семей — всё здорово.

Младшие модели (всё, у которых флеша до ~64 КБ) укладываются в диапазон 16-битного указателя ядра (адресует до 0xFFFF) и работают как обычная линейная память. А старшие — те, у которых 96 или 128 КБ флеша: AC96/AC128, QE128, DZ128 и им подобные — выходят за то, что покрывает указатель, но память-то всё-таки есть. Вот тут страничная память и закрывает для производителя задачу адресации.

Получается, нельзя просто пройти подряд. В обычной линейной памяти можно читать байт 1, 2, 3, … одним непрерывным движением, как страницы книги. А со старшими, страничными так не получится — нужно сначала настроить окно на выдачу конкретной страницы.

У современного микроконтроллера (32-битный Cortex-M0+ со 128 КБ флеша) этих проблем нет — вы адресуете байт 47 000 как «байт 47 000», точка. Страничный доступ сегодня жив разве что в EEPROM-чипах; в современных МК такой дизайн уже не встречается.

2. Как она организована

Полная карта памяти 9S08 — AN3730, рисунок 8

Что мы видим на этой картинке:

  • 0x8000–0xBFFF — это окно PPAGE (помните аналогию выше про одну дорогу?). После PPAGE = N CPU видит в этом окне физическую страницу N (16 КБ).
  • Фиксированные окна 0x4000–0x7FFF (страница 1) и 0xC000–0xFFFF (страница 3)комбинированные: страницы 1 и 3 одновременно «висят» в обеих системах координат — их видно и напрямую через свои фиксированные окна, и через окно PPAGE (при PPAGE = 1 и PPAGE = 3 соответственно). Поэтому всё, что традиционно живёт в верхней области — вектор сброса 0xFFFE/F, байт защиты NVOPT@0xFFBF, байт подстройки 0xFFBE — физически принадлежит странице 3, но CPU видит эти байты «напрямую», как в обычной линейной памяти. В сумме в прямом CPU-виде на AC96/AC128 «торчат» 2,5 страницы: хвост страницы 0 (0x20F0–0x3FFF, нижние 8432 байта затенены RAM и регистрами), вся страница 1 и вся страница 3. Остальные — только через окно PPAGE.

PPAGE и страницы памяти — AN3730, рисунок 3

Регистр PPAGE — это «селектор страницы»: записали в него номер, и в окне PPAGE видно ту страницу. Сменили номер — увидели другую. На рисунке выше: PPAGE = 1 → CPU видит страницу 1, PPAGE = 7 → CPU видит страницу 7.

PPAGE на AC96/AC128 — это 3-битное поле (значения 0..7), то есть максимум адресуется 8 × 16 КБ = 128 КБ флеша. AC96 просто короче: страницы 0..5 = 96 КБ.

3. Про совместимость прошивок

МК — это одно, а прошивки и форматы с учётом всей этой уникальной технологии — отдельная головная боль. Существуют два конкурирующих формата раскладки paged-флеша в S-record, и оба используются.

«S19 с PPAGE в старшем байте» — USBDM Memory Dump

USBDM Memory Dump при сохранении страничной памяти генерирует 24-битные адреса: старший байт = PPAGE, младшие 16 бит = вид со стороны CPU внутри окна PPAGE:

address_in_file = (PPAGE << 16) | cpu_addr // cpu_addr в 0x8000..0xBFFF

Страница N уходит в файл по адресам 0xN8000..0xNBFFF. Родной USBDM использует тот же формат при дампе MC9S08AC128 — на выходе получается S19, у которого 8 page-slot'ов по 64 КБ размазаны по адресам 0x08000..0x7BFFF (≈ 475 КБ занятого диапазона из ≈ 512 КБ потенциальных), притом что реальных данных в файле всего 128 КБ. В каждом слоте только верхние 16 КБ (0xN8000..0xNBFFF) — данные, остальные 48 КБ — пустота: нижние 32 КБ (0xN0000..0xN7FFF) и верхние 16 КБ (0xNC000..0xNFFFF, которые пересеклись бы с фиксированным окном 0xC000..0xFFFF).

«Плоская линейная» MultiProg

Мы используем другой формат: сложить страницы плотно, одну за другой, начиная с 0x8000, без дыр:

address_in_file = 0x8000 + (PPAGE * 0x4000) + (cpu_addr - 0x8000)

Дамп AC128 в этом формате — это ровно 128 КБ непрерывных данных от 0x8000 до 0x27FFF в порядке физических страниц. MultiProg использует именно его, чтобы прошивки байт-в-байт совпадали с Orange 5: записал в одной программе — прочитал в другой, и получаешь те же байты по тем же смещениям.

Сравнение адресов

Один и тот же MC9S08AC128, прочитанный двумя разными программами, даёт:

СтраницаРодной USBDM (PPAGE в старшем байте)MultiProg (плоский)
00x08000 – 0x0BFFF0x08000 – 0x0BFFF
10x18000 – 0x1BFFF0x0C000 – 0x0FFFF
20x28000 – 0x2BFFF0x10000 – 0x13FFF
30x38000 – 0x3BFFF0x14000 – 0x17FFF
40x48000 – 0x4BFFF0x18000 – 0x1BFFF
50x58000 – 0x5BFFF0x1C000 – 0x1FFFF
60x68000 – 0x6BFFF0x20000 – 0x23FFF
70x78000 – 0x7BFFF0x24000 – 0x27FFF
Адресный диапазон файла≈ 512 КБ (48 КБ пустоты между блоками данных)128 КБ (плотно)
Содержимое одинаковое — раскладка разная

Файлы не байт-в-байт совпадают до конверсии, но описывают ровно одни и те же байты на физическом кристалле. Перевод адреса из USBDM-формата (с PPAGE в старшем байте) в плоский:

linear = 0x8000 + ((usbdm >> 16) * 0x4000) + (usbdm & 0x3FFF)

…и обратно:

usbdm = (((linear - 0x8000) / 0x4000) << 16) | 0x8000 | ((linear - 0x8000) % 0x4000)

4. Вопрос-ответ

В. Совместимы ли S19 от MultiProg и USBDM напрямую в обе стороны? Нет. У нас флеш-регион описан с start_addr = 0x8000 и непрерывным размером — загрузчик про дыры USBDM-раскладки ничего не знает. И наоборот: USBDM/CodeWarrior ждут адреса с PPAGE в старшем байте, а MultiProg отдаёт плоский диапазон. В любом направлении файл нужно сначала переконвертировать — Lua-скриптом ниже или любым hex-редактором с парой строк скрипта.

В. Есть готовый скрипт конвертации в формат MultiProg? Вот пример Lua-скрипта, который переводит дамп из USBDM-формата (PPAGE в старшем байте — левая колонка таблицы сравнения) в плоский формат MultiProg (правая колонка). Lua-движок встроен в MultiProg — см. справочник API.

-- USBDM S19/HEX (PPAGE в старшем байте) → плоский S19 для MultiProg
-- Парсим вход вручную через mp.file.read_text — без mp.file.load,
-- чтобы не трогать буфер активного таргета и не упереться в read-only.

local in_path = mp.ui.open_file_dialog("USBDM dump (PPAGE в старшем байте)", nil, "S-Record (*.s19 *.hex)")
if in_path == "" then return end
local out_path = mp.ui.save_file_dialog("Плоский S19 для MultiProg", nil, "S-Record (*.s19)")
if out_path == "" then return end

local text, err = mp.file.read_text(in_path)
if err and err ~= "" then mp.log.error(err); return end

local out = {}
for line in text:gmatch("[^\r\n]+") do
if line:sub(1, 1) == "S" then
local t = line:sub(2, 2)
if t == "1" or t == "2" or t == "3" then
local count = tonumber(line:sub(3, 4), 16)
local addr_chars = (t == "1") and 4 or ((t == "2") and 6 or 8)
local addr = tonumber(line:sub(5, 4 + addr_chars), 16)
local data_start = 5 + addr_chars
local data_chars = (count - addr_chars // 2 - 1) * 2
local data_hex = line:sub(data_start, data_start + data_chars - 1)
local data = mp.utils.hex_to_bytes(data_hex)

local ppage = (addr >> 16) & 0xFF
local cpu = addr & 0xFFFF
local linear
if cpu >= 0x8000 and cpu <= 0xBFFF then
-- окно PPAGE: PPAGE * 16 КБ + смещение в окне
linear = 0x8000 + ppage * 0x4000 + (cpu - 0x8000)
elseif cpu >= 0xC000 and cpu <= 0xFFFF then
-- фиксированное окно — всегда страница 3
linear = 0x8000 + 3 * 0x4000 + (cpu - 0xC000)
else
mp.log.warn(string.format("skip @ 0x%05X (out of paged range)", addr))
goto continue
end
table.insert(out, { address = linear, data = data, size = #data })
::continue::
end
end
end

local ok, e = mp.file.save(out_path, out)
if ok then
mp.log.success(string.format("saved %s (%d blocks)", out_path, #out))
else
mp.log.error(e)
end
примечание

В комплекте встроенных примеров MultiProg этого скрипта пока нет — добавим в одном из ближайших обновлений. Сейчас просто скопируйте код выше в Script Console.

Обратное преобразование (плоский → USBDM) пишется зеркально — формула в плашке §3.

В. После erase verify ругается на байты 0xFFBD, 0xFFBE и 0xFFBF. Это баг? Нет, это особенность этих «живых» байтов. Все три физически лежат в странице 3 и видны через фиксированное окно 0xC000–0xFFFF. Адреса в UI MultiProg (для AC96 и AC128 одинаковые):

БайтАдрес CPUВ буфере MultiProg
NVPROT0xFFBD0x17FBD
ICGTRM0xFFBE0x17FBE
NVOPT0xFFBF0x17FBF
  • 0xFFBE — ICGTRM (trim внутреннего осциллятора). Уникальная для каждого экземпляра заводская калибровка: на стенде производитель подобрал значение, при котором внутренний RC даёт нужную частоту. После полного стирания этот байт превращается в 0xFF, и осциллятор уходит «гулять» — программа на нём может работать на неправильной частоте или вообще не запуститься. В MultiProg для таких случаев есть TRIM-диалог с Test Calibration — он позволяет заново подобрать значение для конкретного экземпляра МК.
  • 0xFFBF — NVOPT (non-volatile option byte). После корректной операции erase этот байт должен быть 0xFE (SEC[1:0] = 10 — защита выключена) — это «открытое» состояние, при котором к МК можно подключаться по BDM. Значение 0xFF (которое получилось бы при стирании всех бит в 1) соответствует SEC[1:0] = 11МК в защищённом режиме, и BDM-сессию к нему открыть уже нельзя. Корректные программаторы (MultiProg в том числе) используют команду Mass Erase Unsecure — она стирает флеш и одновременно приводит NVOPT в нужное 0xFE, чтобы МК не «захлопнулся» на пользователе.
  • 0xFFBD — NVPROT (non-volatile flash protection). После erase = 0xFF, и это нормально — 0xFF означает «защита записи флеша выключена», МК остаётся прошиваемым. Verify «увидит» расхождение, только если в исходном дампе в этом байте было что-то отличное от 0xFF.

В. Где это всё посмотреть в документации? MC9S08AC128 Reference Manual Rev. 3 — §4.1 (модель страничной памяти), §4.4 (последовательность команд флеша), Таблица 4-2 лист 3 (список direct-page-регистров, в т.ч. PPAGE@$0078).

AN3730 — Understanding Memory Paging in 9S08 Devices — документ, который хорошо описывает весь механизм страничной памяти; рисунки выше взяты оттуда.

Итого

На этом всё — разобрались, как устроена страничная память на AC96/AC128, чем отличаются форматы USBDM и MultiProg, как конвертировать одно в другое и почему байт NVOPT (0xFFBF) после стирания = 0xFE.