Формат файлов `.mcm` для микросхемы MAX7456 [1] - простой текстовый формат, содержащий данные для знакогенератора — то есть растровые изображения всех 256 символов, которые можно выводить на экран.
Вот ключевые сведения об этом формате из официальной документации Maxim (ныне Analog Devices) и практических руководств:
[Структура и содержание файла .mcm]
● Основное назначение: хранение данных неперезаписываемой(1) памяти знакогенератора (Character Memory, NVM) MAX7456. Каждый файл содержит полный набор из 256 символов.
● Физическая организация: память микросхемы организована в 256 блоков по 64 байта, соответствующих 256 символам. Однако для описания одного символа размером 12x18 пикселей используется только 54 байта данных (оставшиеся 10 байт в блоке не используются).
● Кодирование пикселей: Каждый пиксель кодируется двумя битами:
● Текстовый формат: Файл `.mcm` — это обычный ASCII-текстовый файл. Его можно открыть и просмотреть в любом текстовом редакторе, например, в Блокноте Windows. Данные в файле представлены в виде последовательности шестнадцатеричных или двоичных строк (в зависимости от версии ПО), описывающих каждый байт памяти символа (одна строка содержит 1 байт, кодирующий 4 точки символа).
Примечание (1): память знакогенератора не перезаписываемая, при включении питания её содержимое автоматически переписывается в специальную оперативную память, которая используется для синтеза графики символов на экране. Эту оперативную память можно перепрограммировать через интерфейс SPI микросхемы MAX7456, изменив тем самым символы до следующего сброса или включения питания.
[Как создавать и редактировать файлы .mcm]
Работать с битовыми картами символов напрямую в текстовом виде неудобно. Для этого существуют специальные редакторы:
1. MAX7456 Font Editor — популярная бесплатная программа с открытым исходным кодом. Она позволяет графически редактировать каждый символ, а также конвертировать файлы между форматами `.mcm` и `.h` (используемым в проектах Arduino).
2. Официальное ПО Maxim (Analog Devices) — оценочный комплект (EV kit) от производителя включает программу для создания и загрузки пользовательской графики, которая также работает с файлами `.mcm`.
3. Редактирование через Excel: в официальном руководстве описан метод использования таблиц Excel для пакетного преобразования цветов пикселей в существующем файле `.mcm` с помощью формул.
4. Ниже во врезке приведен код скрипта Python, который позволяет редактировать графику символов знакогенератора.
[Практическое использование]
● Совместно с .mdm файлом: для полной работы OSD также нужен файл `.mdm` (Display Memory File), который определяет, какие именно символы из `.mcm` и в каком порядке выводятся на экран.
● Загрузка в микросхему:
- с помощью оценочного комплекта производителя по последовательному интерфейсу. - с помощью платформы Arduino и специального скетча-загрузчика, который передает данные из файла `.mcm` (часто предварительно сконвертированного в `.h`) в MAX7456 по интерфейсу SPI. - в терминальных программах (например, Tera Term, putty) через последовательный порт.
● Важное замечание о кодировке: символы в стандартной поставке микросхемы не соответствуют кодировке ASCII, что может приводить к выводу "кракозябр" при попытке прямого вывода текста. Поэтому для нормальной работы с латиницей и кириллицей необходимо загрузить пользовательский знакогенератор, где символы расставлены в правильном порядке.
Этот скрипт позволяет конвертировать файл *.mcm в заголовочный файл C, где находятся данные массива знакогенератора. Кроме того, он позволяет интерактивно редактировать графику каждого символа. Запуск скрипта без опций выводит справку по командной строке:
$ python ./MAX7456_font_editor.py
usage: MAX7456_font_editor.py [ОПЦИИ] [ВХОДНОЙ_ФАЙЛ]
Конвертер шрифтов MAX7456 из MCM в C заголовочный файл
positional arguments:
input_file Входной MCM файл
options:
--output, -o OUTPUT Выходной заголовочный файл
--show, -s N Показать символ с индексом N (0-255)
--graphical, -g Показать символ в графическом окне (требуется PyGame)
--help, -h Показать справку
Примеры:
MAX7456_font_editor.py font.mcm Конвертировать font.mcm в MAX7456font.h
MAX7456_font_editor.py font.mcm -o output.h Конвертировать с указанием выходного файла
MAX7456_font_editor.py font.mcm -s 65 Показать символ #65 в консоли
MAX7456_font_editor.py font.mcm -s 65 -g Показать символ #65 в графическом окне
MAX7456_font_editor.py font.mcm -s 65 -o out.h -g Показать символ #65 и конвертировать
MAX7456_font_editor.py --help Показать эту справку
Управление в графическом режиме:
← → Переключение между символами
ESC/Q Выход из программы
Содержимое скрипта (MAX7456_font_editor.py):
importsys importargparse importos
# Константы для символов отображения в консоли
BLACK ='■'# 00 — черный, непрозрачный
TRANSP1 ='⬝'# 01 — прозрачный (темно-серый)
WHITE ='□'# 10 — белый, непрозрачный
TRANSP2 =' '# 11 — прозрачный (пробел)
defdisplay_character_graphical(initial_char_index, data_lines, input_file=None): """Отображает символ в графическом окне PyGame с возможностью редактирования"""
pygame, pygame_available = try_import_pygame()
ifnot pygame_available:
print("Предупреждение: PyGame не установлен. Графический режим недоступен.")
print("Установите PyGame: pip install pygame")
returnFalsetry:
# Инициализация PyGame
pygame.init()
# Размеры окна
pixel_size =24# Увеличиваем для удобства кликов
width =12* pixel_size +300# Больше места для кнопок
height =18* pixel_size +250# Создаем окно
screen = pygame.display.set_mode((width, height))
# Текущий символ
current_char_index = initial_char_index
# Загружаем исходные данные
original_char_data = data_lines.copy()
# Функция для получения данных текущего символаdefget_current_pixel_data():
pixel_data = extract_pixel_data(data_lines, current_char_index)
if pixel_data isNone:
return [['01'] *12for _ inrange(18)]
return pixel_data
# Получаем начальные данные
pixel_data = get_current_pixel_data()
# Максимальное количество символов в файле
max_char_in_file = (len(data_lines) //64) -1if data_lines else0# Состояние редактирования
edit_mode =True
changes_made =False# Соответствие состояний и их смены
state_cycle = {
'00': '01',
'01': '10',
'10': '11',
'11': '00'
}
# Кнопки
button_height =30
button_width =200
button_margin =10# Создаем кнопкиclassButton:
def__init__(self, x, y, width, height, text, color=(70, 70, 70), hover_color=(100, 100, 100)):
self.rect = pygame.Rect(x, y, width, height)
self.text = text
self.color = color
self.hover_color = hover_color
self.current_color = color
self.font = pygame.font.SysFont(None, 20)
defdraw(self, screen):
pygame.draw.rect(screen, self.current_color, self.rect, border_radius=5)
pygame.draw.rect(screen, (120, 120, 120), self.rect, 2, border_radius=5)
text_surf =self.font.render(self.text, True, (220, 220, 220))
text_rect = text_surf.get_rect(center=self.rect.center)
screen.blit(text_surf, text_rect)
defcheck_hover(self, pos):
self.current_color =self.hover_color ifself.rect.collidepoint(pos) elseself.color
returnself.rect.collidepoint(pos)
defis_clicked(self, pos, event_type):
returnself.rect.collidepoint(pos) and event_type == pygame.MOUSEBUTTONDOWN
# Создаем кнопки
y_pos =100
save_button = Button(width - button_width - button_margin, y_pos, button_width, button_height, "Сохранить", (60, 100, 60))
y_pos += (button_height + button_margin)
reset_button = Button(width - button_width - button_margin, y_pos, button_width, button_height, "Сбросить", (100, 60, 60))
y_pos += (button_height + button_margin)
edit_toggle = Button(width - button_width - button_margin, y_pos, button_width, button_height, "Редактирование: ВКЛ", (70, 70, 120))
running =Truewhile running:
# Обновляем заголовок окна
title =f"MAX7456 Символ #{current_char_index} (0x{current_char_index:02X})"if changes_made:
title +=" *ИЗМЕНЕНО*"
pygame.display.set_caption(title)
# Получаем позицию мыши
mouse_pos = pygame.mouse.get_pos()
# Обрабатываем событияfor event in pygame.event.get():
if event.type == pygame.QUIT:
if changes_made:
# Спросим о сохраненииprint("\nЕсть несохраненные изменения!")
print("Закройте окно снова для выхода без сохранения")
changes_made =False# Сбросим, чтобы можно было выйтиcontinue
running =Falseelif event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE or event.key == pygame.K_q:
if changes_made:
print("\nЕсть несохраненные изменения! Используйте кнопку 'Сохранить'")
continue
running =Falseelif event.key == pygame.K_RIGHT:
if changes_made:
print("Предупреждение: есть несохраненные изменения!")
new_index = current_char_index +1if new_index >255:
new_index =0if new_index <= max_char_in_file:
current_char_index = new_index
pixel_data = get_current_pixel_data()
changes_made =Falseprint(f"\nПереключено на символ #{current_char_index} (0x{current_char_index:02X})")
display_character_console(data_lines, current_char_index, pixel_data)
elif event.key == pygame.K_LEFT:
if changes_made:
print("Предупреждение: есть несохраненные изменения!")
new_index = current_char_index -1if new_index <0:
new_index =255if new_index <= max_char_in_file:
current_char_index = new_index
pixel_data = get_current_pixel_data()
changes_made =Falseprint(f"\nПереключено на символ #{current_char_index} (0x{current_char_index:02X})")
display_character_console(data_lines, current_char_index, pixel_data)
elif event.type == pygame.MOUSEBUTTONDOWN:
# Проверяем клики по пикселям (только в режиме редактирования)if edit_mode:
offset_x =50
offset_y =100# Проверяем попал ли клик в область сетки
grid_rect = pygame.Rect(offset_x, offset_y,
12* pixel_size, 18* pixel_size)
if grid_rect.collidepoint(mouse_pos):
# Определяем координаты пикселя
rel_x = mouse_pos[0] - offset_x
rel_y = mouse_pos[1] - offset_y
pixel_x = rel_x // pixel_size
pixel_y = rel_y // pixel_size
if0<= pixel_x <12and0<= pixel_y <18:
# Меняем состояние пикселя
current_state = pixel_data[pixel_y][pixel_x]
new_state = state_cycle.get(current_state, '00')
pixel_data[pixel_y][pixel_x] = new_state
# Обновляем исходные данные
update_binary_data(data_lines, current_char_index, pixel_data)
changes_made =Trueprint(f"Пиксель [{pixel_y},{pixel_x}] изменен: {current_state} -> {new_state}")
# Проверяем клики по кнопкамif save_button.is_clicked(mouse_pos, pygame.MOUSEBUTTONDOWN):
if save_changes(input_file, data_lines, original_char_data):
changes_made =False
original_char_data = data_lines.copy()
print("✓ Изменения сохранены в исходный файл!")
elif reset_button.is_clicked(mouse_pos, pygame.MOUSEBUTTONDOWN):
if changes_made:
# Восстанавливаем оригинальные данные
start_idx = current_char_index *64
end_idx = start_idx +64
data_lines[start_idx:end_idx] = original_char_data[start_idx:end_idx]
pixel_data = get_current_pixel_data()
changes_made =Falseprint("✓ Изменения сброшены!")
elif edit_toggle.is_clicked(mouse_pos, pygame.MOUSEBUTTONDOWN):
edit_mode =not edit_mode
edit_toggle.text =f"Редактирование: {'ВКЛ'ifedit_modeelse'ВЫКЛ'}"
edit_toggle.color = (70, 120, 70) if edit_mode else (120, 70, 70)
print(f"Режим редактирования: {'ВКЛ'ifedit_modeelse'ВЫКЛ'}")
# Проверяем ховер кнопок
save_button.check_hover(mouse_pos)
reset_button.check_hover(mouse_pos)
edit_toggle.check_hover(mouse_pos)
# Очистка экрана
screen.fill((30, 30, 30))
# Отступы для сетки
offset_x =50
offset_y =100# Рисуем сеткуfor x inrange(13):
pygame.draw.line(screen, (80, 80, 80),
(offset_x + x * pixel_size, offset_y),
(offset_x + x * pixel_size, offset_y +18* pixel_size), 1)
for y inrange(19):
pygame.draw.line(screen, (80, 80, 80),
(offset_x, offset_y + y * pixel_size),
(offset_x +12* pixel_size, offset_y + y * pixel_size), 1)
# Рисуем пикселиfor y inrange(18):
for x inrange(12):
if y <len(pixel_data) and x <len(pixel_data[y]):
pixel_type = pixel_data[y][x]
colors = {
'00': (0, 0, 0), # черный'01': (100, 100, 100), # серый'10': (255, 255, 255), # белый'11': (50, 50, 50) # темно-серый
}
color = colors.get(pixel_type, (255, 0, 0))
# Подсветка при наведении (в режиме редактирования)
pixel_rect = pygame.Rect(
offset_x + x * pixel_size +1,
offset_y + y * pixel_size +1,
pixel_size -2,
pixel_size -2
)
if edit_mode and pixel_rect.collidepoint(mouse_pos):
# Подсветка при наведении
highlight_color = (
min(color[0] +40, 255),
min(color[1] +40, 255),
min(color[2] +40, 255)
)
pygame.draw.rect(screen, highlight_color, pixel_rect)
else:
pygame.draw.rect(screen, color, pixel_rect)
# Если пиксель прозрачный, рисуем крестикif pixel_type in ['01', '11']:
pygame.draw.line(screen, (150, 150, 150),
(offset_x + x * pixel_size +3, offset_y + y * pixel_size +3),
(offset_x + (x +1) * pixel_size -3, offset_y + (y +1) * pixel_size -3), 1)
pygame.draw.line(screen, (150, 150, 150),
(offset_x + (x +1) * pixel_size -3, offset_y + y * pixel_size +3),
(offset_x + x * pixel_size +3, offset_y + (y +1) * pixel_size -3), 1)
# Шрифты
font = pygame.font.SysFont(None, 24)
small_font = pygame.font.SysFont(None, 18)
# Отображаем заголовок
title_text =f"Символ #{current_char_index} (0x{current_char_index:02X})"if changes_made:
title_text +=" *"
title_surf = font.render(title_text, True, (255, 255, 255))
screen.blit(title_surf, (width //2- title_surf.get_width() //2, 20))
# Информация о файле
info_text =f"Символов в файле: 0-{max_char_in_file}"
info_surf = small_font.render(info_text, True, (200, 200, 200))
screen.blit(info_surf, (width //2- info_surf.get_width() //2, 50))
# Легенда с интерактивными примерами - В 4 СТРОКИ# legend_y = offset_y + 18 * pixel_size + 20
legend_y =300
legend_items = [
("00: Чёрный", (0, 0, 0), "00"),
("01: Прозрачный", (100, 100, 100), "01"),
("10: Белый", (255, 255, 255), "10"),
("11: Прозрачный", (50, 50, 50), "11")
]
# Отображаем цветные квадраты и названия в 4 строках (по одному в строке)for i, (text, color, state) inenumerate(legend_items):
# Цветной квадрат
rect_x =400# Фиксированная позиция X для всех строк
color_rect = pygame.Rect(rect_x, legend_y + i *25, 15, 15) # Смещаем по Y на 25px для каждой строки
pygame.draw.rect(screen, color, color_rect)
# Текст состояния
legend_text = small_font.render(text, True, (200, 200, 200))
screen.blit(legend_text, (rect_x +25, legend_y + i *25-2)) # Текст правее квадрата# Рисуем кнопки
save_button.draw(screen)
reset_button.draw(screen)
edit_toggle.draw(screen)
# Инструкции
instructions = [
"Клик по пикселю: изменить состояние (00 -> 01 -> 10 -> 11 -> 00)",
"Стрелки влево/вправо: переключение символов",
"ESC или Q: выход (только если нет изменений)"
]
for i, instruction inenumerate(instructions):
instr_text = small_font.render(instruction, True, (180, 180, 180))
screen.blit(instr_text, (width //2- instr_text.get_width() //2, height -100+ i *20))
# Статус редактирования
status_color = (150, 200, 150) if edit_mode else (200, 150, 150)
status_text ="РЕЖИМ РЕДАКТИРОВАНИЯ: АКТИВЕН"if edit_mode else"Режим редактирования: выключен"
status_surf = small_font.render(status_text, True, status_color)
screen.blit(status_surf, (width //2- status_surf.get_width() //2, height -30))
pygame.display.flip()
pygame.quit()
returnTrueexceptExceptionas e:
print(f"Ошибка в графическом режиме: {e}")
importtraceback
traceback.print_exc()
returnFalse
defupdate_binary_data(data_lines, char_index, pixel_data): """Обновляет бинарные данные на основе измененных пикселей"""
start_line = char_index *64# Преобразуем пиксели обратно в бинарные строкиfor row inrange(18):
# Получаем 12 пикселей строки
row_pixels = pixel_data[row]
# Преобразуем в 24 бита (12 пикселей × 2 бита)
binary_str =""for pixel in row_pixels:
binary_str += pixel
# Разбиваем на 3 строки по 8 бит
line1 = binary_str[0:8]
line2 = binary_str[8:16]
line3 = binary_str[16:24]
# Обновляем данные
line_idx1 = row *3
line_idx2 = row *3+1
line_idx3 = row *3+2if line_idx1 <54and start_line + line_idx1 <len(data_lines):
data_lines[start_line + line_idx1] = line1
if line_idx2 <54and start_line + line_idx2 <len(data_lines):
data_lines[start_line + line_idx2] = line2
if line_idx3 <54and start_line + line_idx3 <len(data_lines):
data_lines[start_line + line_idx3] = line3
defsave_changes(filename, data_lines, original_data): """Сохраняет изменения обратно в файл"""ifnot filename:
print("Ошибка: имя файла не указано для сохранения")
returnFalsetry:
# Создаем содержимое файла
content ="MAX7456\n"+"\n".join(data_lines)
# Сохраняем файлwithopen(filename, 'w') as f:
f.write(content)
returnTrueexceptExceptionas e:
print(f"Ошибка при сохранении файла: {e}")
returnFalse
defextract_pixel_data(data_lines, char_index): """Извлекает данные пикселей для символа"""
start_line = char_index *64
end_line = start_line +64if start_line >=len(data_lines):
returnNone
char_data = data_lines[start_line:end_line]
pixel_data = []
# Преобразуем каждые 3 строки данных в строку пикселейfor row inrange(18):
line_idx1 = row *3
line_idx2 = row *3+1
line_idx3 = row *3+2if line_idx1 >=54or line_idx1 >=len(char_data):
# Заполняем прозрачным если данных нет
pixel_data.append(['01'] *12)
continue
line1 = char_data[line_idx1]
line2 = char_data[line_idx2] if line_idx2 <len(char_data) else"00000000"
line3 = char_data[line_idx3] if line_idx3 <len(char_data) else"00000000"# Объединяем три байта
combined = line1 + line2 + line3
# Извлекаем 12 пар битов (12 пикселей)
row_pixels = []
for i inrange(0, 24, 2):
bit_pair = combined[i:i+2]
row_pixels.append(bit_pair)
pixel_data.append(row_pixels)
return pixel_data
defdisplay_character_console(data_lines, char_index, pixel_data=None): """Отображает символ в консоли в текстовом виде"""if pixel_data isNone:
pixel_data = extract_pixel_data(data_lines, char_index)
if pixel_data isNone:
print(f"Ошибка: символ с индексом {char_index} не найден в файле")
print(f"В файле {len(data_lines)} строк данных, максимум {len(data_lines)//64} символов")
returnFalse, None# Получаем полные данные символа (64 строки)
start_line = char_index *64
end_line = start_line +64
char_data = data_lines[start_line:end_line] if start_line <len(data_lines) else []
print(f"\nСимвол #{char_index} (0x{char_index:02X}):")
print(" "+"─"*13+"→"+" 12 пикселей")
# Отображаем пикселиfor row inrange(18):
display_line =""if row <len(pixel_data):
for pixel in pixel_data[row]:
if pixel =="00":
display_line += BLACK
elif pixel =="01":
display_line += TRANSP1
elif pixel =="10":
display_line += WHITE
elif pixel =="11":
display_line += TRANSP2
else:
display_line +="?"else:
display_line = TRANSP2 *12print(f"{row:2d}│ {display_line}")
print(" "+"─"*13)
# Легендаprint("\nЛегенда:")
print(f" {BLACK} - черный, непрозрачный (00)")
print(f" {TRANSP1} - прозрачный (01)")
print(f" {WHITE} - белый, непрозрачный (10)")
print(f" '{TRANSP2}' - прозрачный (пробел) (11)")
# Дополнительная информацияprint(f"\nИнформация о данных:")
print(f" Всего строк данных для символа: {len(char_data)}")
print(f" Используется строк: 54 (18 строк × 3)")
print(f" Не используется строк: 10")
print(f" Пикселей в строке: 12")
print(f" Строк в символе: 18")
# Показываем неиспользуемые данные, если они естьiflen(char_data) >54:
unused_data = char_data[54:]
print(f"\nНеиспользуемые данные (строки 54-63):")
for i, line inenumerate(unused_data):
print(f" [{i+54:2d}] {line}")
returnTrue, pixel_data
defconvert_mcm_to_header(input_file, output_file, show_char=None, graphical=False): """Конвертирует MCM файл в C заголовочный файл"""# Читаем входной файлwithopen(input_file, 'r') as f:
lines = f.readlines()
# Удаляем пустые строки и пробелы
lines = [line.strip() for line in lines if line.strip()]
# Проверяем заголовокiflen(lines) ==0:
raiseValueError("Файл пуст")
if lines[0] !='MAX7456':
raiseValueError("Неверный формат MCM файла. Первая строка должна быть 'MAX7456'")
# Проверяем строки с данными
char_data = []
for i, line inenumerate(lines[1:], 1):
iflen(line) !=8ornotall(c in'01'for c in line):
raiseValueError(f"Неверная строка данных #{i}: '{line}' (должно быть 8 бит)")
char_data.append(line)
# Проверяем полный набор символов
total_data_lines =len(char_data)
lines_per_char =64if total_data_lines % lines_per_char !=0:
print(f"Внимание: файл содержит {total_data_lines} строк данных,")
print(f"что не кратно {lines_per_char} (64 строки на символ).")
# Если запрошен показ символаif show_char isnotNone:
ifnot0<= show_char <=255:
raiseValueError("Индекс символа должен быть в диапазоне 0-255")
# Проверяем, есть ли символ в файле
max_char = (len(char_data) // lines_per_char) -1if show_char > max_char:
print(f"Внимание: файл содержит только символы 0-{max_char}")
if max_char >=0:
print(f"Показываю символ #{max_char} вместо #{show_char}")
show_char = max_char
# Извлекаем данные пикселей
pixel_data = extract_pixel_data(char_data, show_char)
if pixel_data:
# Отображаем в консоли начальный символ
success, _ = display_character_console(char_data, show_char, pixel_data)
# Если запрошен графический режимif success and graphical:
print("\n"+"="*50)
print("Запуск графического режима с редактированием...")
print("Клик по пикселю: 00 → 01 → 10 → 11 → 00")
print("Стрелки: переключение символов")
print("Кнопки: Сохранить, Сбросить, Вкл/Выкл редактирование")
print("="*50)
# Передаем также имя файла для возможности сохранения
display_character_graphical(show_char, char_data, input_file)
# Если только показ символа без конвертации - завершаемif output_file isNone:
returnNone# Если нужна конвертацияif output_file isnotNone:
num_chars =len(char_data) // lines_per_char
char_arrays = []
for char_idx inrange(num_chars):
start = char_idx * lines_per_char
end = start + lines_per_char
char_lines = char_data[start:end]
hex_lines = []
for line in char_lines:
hex_val =format(int(line, 2), '02x')
hex_lines.append(f"0x{hex_val}")
char_array =" "+",".join(hex_lines)
char_arrays.append(char_array)
header_content =f"""// MAX7456 Font Data // Converted from {os.path.basename(input_file)} // Total characters: {num_chars} // Total bytes: {len(char_data)} // Structure: 18 rows × 12 pixels, 2 bits per pixel // Memory layout: 64 bytes per character (54 used, 10 unused)