Что то совсем обленился, инфы поднакопилось, а выложить все руки не доходят. Надо потихоньку исправляться. Поэтому начну со статьи, которая была готова чуть ли не год назад.
Изначально хотелось подцепить к микроконтроллеру карту памяти и дисплей. Загрузить кучу картинок на карточку и выводить их по очереди. В итоге, я решил отказаться от карты памяти, всему причина отсутствие железа, картинка будет храниться просто во флеше. Найти и запустить библиотеку FatFs под stm32 не проблема, примеры в сети есть, я запускал ничего сложного нет. Для поклонников AVR можно взять библиотеку из урока 22.2. А вот тонкостям работы с карточками, мне хотелось бы посвятить отдельную статью, поэтому как только материал накопится, так он и появится.
Вообще железо стало довольно большим камнем преткновения. После того, как целый год я паял только заводские платы, делать платы фоторезистом стало сродни мазохизму, требует много времени, внимания и не всегда результат оправдывает ожидания. Того что нужно на прилавках либо нет, либо по заоблачным ценам. В общем пока однозначного решения нет.
Под рукой есть только самопальная платка stm32f103+nokia1100, как ни странно сделанная фоторезистом 🙂 Поэтому выбора камня или дисплея у меня не стояло. Хочу отметить, что глобально больше не планирую развивать тему AVR, может будут отдельные посты, но в целом для меня выбор stm очевиден. Если вы не знаете их, или не понимаете как к ним подойти, то изучайте, задавайте вопросы в соответствующих темах, будем помогать разбираться.
Перейдем к сути вопроса. Можно было просто выложить исходник, который работает и сказать разбирайтесь. Но в этот раз цель иная. Есть задача, вывести картинку из файла на дисплей, предлагаю свое видение решения подобных проблем.
Первое нужно, правильно сформулировать тз. Мне не очень хочется писать такие очевидные вещи, но чтобы избежать глупых вопросов приведу пример. Типичное «плохое» тз: «подключить цветной графический дисплей разрешением 800х600, вывести на него jpeg картику и при этом проиграть mp3 звук с карты памяти». Почему плохое? Из формулировки понятно что человек не имеет представления о сложности данной задачи. Единственное что может помочь либо огромный интерес, либо на «дурачка» взяв готовое решение. В противном случае это обречено на провал.
Разобраться с графическим дисплеем, с форматом jpeg, с mp3 звуками, даже с картой памяти — каждая из этих задач может потребовать большого времени, я не говорю об отдельных уникумах способных сходу все решать. Отметаем глупые вопросы, мы учимся, а это значит нам не позволительно нигде тормозить, основы останутся справедливы и для более сложных вещей, поэтому упрощаем все что только можно и постепенно усложняем. Надеюсь убедил 🙂
Из выше сказанного, почему bmp, а не png, gif, jpeg? Гуглим читаем про каждый из форматов, понимаем что в bmp не используется сжатие. Плюс к этому будем использовать монохромное изображение, т.к. не нужно хранить данные о цветах, поэтому будет проще. Вся информация о форматах добывается в процессе чтения множества сайтов, той же википедии и т.п. На этом этапе читаем как художественную литературу.
С выбором определились как быть с самим файлом? Тут вашим помощником станут сайты описывающие структуру формата, не понятно на одном, смотрим следующий, до просветления. Первое что мы должны понять, откуда начинаются данные и как их читать. Здесь вам очень пригодится какой нибудь Hex редактор, я юзаю бесплатный HexEdit.
Для начала создаем какую нибудь картинку небольшим размеров, открываем файл хексом. Еще раз читаем о том, как устроен заголовок. Например, просто смотрим размер картинки, для этого идем по к offset(смещению) = 0x12, где начинается ширина размером 4 байта, далее смотрим на смещение 0x16 где высота 4 байта. Видно что это 0x60 и 0x41, т.е. размер картинки 96×65. Все верно.
Чем же примечателен адрес 0xA? Тем что он указывает откуда начинаются данные, т.е. данные начинаются с 0x3e и не обязательно это фиксированный адрес. В моем случае данные начинаются с нулей.
Теперь более подробно о том как хранятся данные. Предлагаю картинку размером 10×10 на которой я изобразил нечто похожее на букву F.
Данные начинаются с 7F. Чтобы было более понятно я изобразил такую координатную сетку.
Во первых начало отсчета у нас нижний левый угол, для нас фактически это координата 0, 0. Сами данные хранятся в виде массива бит, подряд, т.е. наше число 0x7F содержит 8 бит и описывает все что находится в координатах 0,0 — 7,0. Заполнение данными идет горизонтально. Очень удобно рассматривать данные в двоичном виде, тогда все становится понятно. Нулем кодируется закрашенный пиксель, единицей не закрашенный. Данные в строках хранятся кратно 8 битам, т.е. даже если вы создадите файл 10×10, то храниться будет все равно 16×10. В оставшиеся, не отображаемые 6×10, будут забиты нулями, но по сути не важно что там записано.
В общем то и все. Не скажу, что понял это моментально, но после нескольких экспериментов, все это стало на свои места. Попробуйте самостоятельно поредактировать, файлы и посмотреть на результат.
На этом можно было бы переходить к написанию прошивки для микроконтроллера, но я уже много раз пытался продвигать эту мысль — везде где возможно отладиться без железа, нужно делать это без него, почему? Потому что не нужный гемморой, толи у тебя железо выдает фокусы, толи алгоритм не правильный. Лучше с каждой из проблем разобраться отдельно. Я уже высказывал свою лояльность в пользу Visual C#, но в принципе это не имеет значения. Что лучше знаете, тем и пользуйтесь.
Какова же моя цель на данном этапе? Прочитать картинку из файла и вывести ее на форму. Не буду вдаваться в подробности, это отличная задача для самостоятельного решения, но идеология простая. Мне нужно научиться читать из файла, для этого есть класс BinaryReader и нужно уметь рисовать 1 пиксель для этого подойдет класс Graphics.
Теперь учусь выводить 1 байт:
//создаю экземпляры классов Stream str; //для работы с указателем BinaryReader br; //для чтения байтов Graphics gr; //для рисования на форме //устанавливаю курсор на начало данных lseek = 0x3e; str.Seek(lseek, SeekOrigin.Begin); //читаю один байт data = br.ReadByte(); for (int x = 0; x < 8; x++) { if ((data & 0x80) == 0) //если 0 то значит рисовать черный пиксель, если нет то пропускать { Rectangle rect = new Rectangle(new Point(x, 64), new Size(1, 1)); //создаю прямоугольник размерами 1 на 1 gr.DrawRectangle(pn, rect); //вывожу на форму созданный пиксель } data = data << 1; // сдвигаю данные чтобы читать следующий бит } |
Данный алгоритм подходит для вывода одного байта, т.е. для отрисовки 8 точек, после того как я с ним закончил, перешел к выводу одной строки, затем двух, затем полностью рисунка. На каждом из этапов пришлось подумать, поэкспериментировать, смотреть отладчиком, но на этом этапе не было раздумии о железе, только алгоритм.
Кому будет интересен этот путь, милости прошу — разбирайтесь
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Drawing.Imaging; using System.Linq; using System.Text; using System.Windows.Forms; using System.IO; namespace WindowsFormsApplication3 { public partial class Form1 : Form { Stream str; BinaryReader br; public Form1() { InitializeComponent(); str = File.Open(@"c:\1.bmp", FileMode.Open); br = new BinaryReader(str); } private void button1_Click(object sender, EventArgs e) { int width, height, lseek; //create new picture Bitmap bmp = new Bitmap(96, 65, PixelFormat.Format32bppRgb); //create new draw tool Graphics gr = Graphics.FromImage(bmp); //clear picturebox gr.Clear(Color.White); //create new pen Pen pn= new Pen(Color.Black); //read widht str.Seek(0x12, SeekOrigin.Begin); width = (int)br.ReadByte(); label1.Text = width.ToString(); //read height str.Seek(0x16, SeekOrigin.Begin); height = br.ReadByte(); label2.Text = height.ToString(); lseek = 0x3e; for (int z = 0; z < height; z++) { for (int y = 0; y < (width / 8); y++) { str.Seek(lseek, SeekOrigin.Begin); int temp = (int)br.ReadByte(); for (int x = 0; x < 8; x++) { if ((temp & 0x80) == 0) { Rectangle rect = new Rectangle(new Point(0 + x + y * 8, 64 - z), new Size(1, 1)); gr.DrawRectangle(pn, rect); } temp = temp << 1; } lseek++; } } //draw bmp on picturebox pictureBox1.Image = bmp; } private void Form1_FormClosing(object sender, FormClosingEventArgs e) { str.Close(); br.Close(); } } } |
В итоге программка заработала, картинка с надписью выводится.
Многие скажут фи, совсем не обязательно было тратить на это время, но суть не конкретно об данном случае, а о идее в целом. Во первых этот алгоритм можно будет использовать с другим контроллером, достаточно заменить функцию отрисовки и чтения. Во вторых это только пример того, что для отладки совсем не обязательно иметь микроконтроллер, суть то не меняется.
Оставим споры и размышления, теперь у нас есть алгоритм, который работает, нам осталось только приладить его к нашей железяке. Как я и думал с железом всплыли свои «нюансы». Первый момент как быть с файлами, где их хранить? Тут вроде все просто, во флеше. Сколько конкретно нужно? Размер экрана 96×65, т.е. (96*65)/8 = 780 байт. Даже на мегу8 с ее 8кБ флеша влезет пара картинок.
Как же быть с самим файлом? Я решил сговнякать программку, которая перегоняет любой файл в массив. Открываем картинку, либо копируем данные из textbox, либо сохраняем в файл. Не знаю наверняка такие программы есть, лень было искать.
Еще одной проблемой стало то, что нельзя просто взять и вывести 1 пиксел, минимум 8 причем только по вертикали. Наш алгоритм без переделки не годится. Жуть как не хотелось ковыряться с дисплеем, но ничего не поделаешь. Вначале пристальный взгляд на имеющуюся библиотеку, затем читать даташит.
Есть упоминания о том, что можно выводить пикселы горизонтально, в нашем случае это бы сильно упростило задачу, даже есть команда, но как бы я не пытался эта штука не работает.
В итоге плюнул и решил сделать с вертикальным выводом, экран у нас 96×65, поэтому придется сначала читать нулевой бит 1 байта, затем проходим мимо первую строку до конца т.е. на 96/8 = 12 байт, читаем нулевой бит первого байта из второй строки, затем идем еще на строку выше и так далее, пока не прочитаем 8 бит. Затем читаем 2 бит, но выводим его уже в координату x=1, y=0. Собственно дальше был долгий процесс адаптации алгоритма, в основном при помощи отладчика.
Для желающих проект кокоса
А почему бы не использовать TFT дисплей с контроллером ST7735. Ведь BMP передает цвет в 24 битном формате. Разрешение дисплея 128х160 и управлять им можно по SPI. Лично мое мнение, монохромные дисплеи по отношению цены к цветным скоро вымрут как динозавры.
Почему не использовать TFT я уже объяснил. Монохромные не вымрут, пока цветные столько жрут тока. По поводу цены полностью согласен, дисплеи вроде WG12864 не должны столько стоить, но если говорить о дисплеях вроде Nokia1100, то они стоят копейки.
Если выключать дисплей пока не используется, то будет экономия питания.
Извиняюсь заранее, если вопрос глупый, но вы в начале статьи указалит stm32f103, а в конце фотография с отлд.платой Nucleo на базе stm32f411.
P.S.кстати можете что-нибудь о ней сказать (ваше мнение), хотел себе такую взять. Спасибо!
Nucleo как программатор и питание. Лично мое мнение брать, только имейте ввиду что 411 это обрезанный камень.
Еще на там есть USB-UART, что тоже не мало важно.
Еще раз извиняюсь за свою недальновидность, но что вы подразумеваете под обрезанным ? То, что у 411 не самые выдающиеся характеристики?
спасибо за ответы, всегда читаю ваши отличные статьи!
если открыть даташит на f411 и тот же f407 и сравнить количество периферии, то сразу все станет понятно.
в винхексе выделил, и выбрал в меню copy hex values
В какой программе пишите на си программки под stm32?
И чем программируете ?
после того как прикрылся кокос, единственной средой остался кейл. в нем и пишу. программирую через stlink любой подвернувшейся отладочной платы, благо плат хватает.
Нашел отличный пример вывода картинки с SD карты на цветной дисплей. Написано в Coocox, купил плату проверил, заработало. Только нужно отключить проверку ID дисплея. В комментариях на писал где. Начал на базе этого примера создавать свой проект. Но очень был-бы рад, и «чертовски» вам благодарен если бы вы рассмотрели его на своем сайте, в «вашем стиле». Ссылка на пример http://cxem.net/mc/mc335.php
И что удобно картинка выводится с SD карты в «родном» формате bmp и jpg, конвертировать сторонними программами в другой формат (бинарник и т. д.) не нужно. Это и реализация ваша цитата: «Загрузить кучу картинок на карточку и выводить их по очереди».
P.S. Мой комментарий можно не печатать.
Андрей, не очень понимаю вопрос, если хочется просто выводить картинку на дисплей, то можно взять f429discovery с демкой или по той ссылке, что вы привели. Исходники есть. Описание формата bmp можете посмотреть в статье или нагуглить их тонны.
В том то и дело что «тонны» разрозненных фактов, примеров и бог знает еще чего. Если бы я был не новичком, а грамотным опытным программистом, мне бы не составило бы труда все «это» объединить. Но поскольку я все же новичок, мне нужен для понимания полностью рабочий пример, и желательно «разжеванный». Поэтому найти подобный пример оказалось не легко. Поверьте я долго искал. К примеру взять ваш сайт. Есть все!!! Кроме примеров работы с цветными дисплеями. Скажете: «Не было нужды»- не верю. Поскольку я начинал изучение микроконтроллеров с вашего сайта, поэтому просто решил поделится находкой. Вот и все.
По поводу тонны фактов, вы явно перебарщиваете. Чтобы дисплей завелся, его всего навсего нужно правильно инициализировать, послать правильную последовательность, которая указана в даташите. Накрайняк выдрать из любого проекта. Есть пример с цветным OLED дисплеем http://avr-start.ru/?p=4487 пользуйтесь. Описывать для каждого конкретного дисплея, вообще никакого смысла, суть никак не меняется. Все остальные грабли не относятся к дисплею как таковому. Вы выбрали сложный путь, тут ничего не поделаешь.
Все, полностью реализовал поставленную перед собой задачу. Вывод картинки с SD карты по SDIO на 7″ экран 800х480 на STM32F407VGT6, с реализацией сенсорного ввода. Результатом остался доволен. На SD карте записано файлы картинок в «родном» формате BMP, конвертировать не нужно. Написано в Keil 5 с использованием библиотеки StdPeriph вместо HAL. Проект полностью работоспособен, реализован алгоритм калибровки сенсорного экрана (нужно раз кометировать нужную функцию для вычисление коэффициентов в режиме отладки). Сама программа, фото, видео и разводка платы находится по ссылке на моем яндекс диске https://yadi.sk/d/Pdkd7xF635RVwm Сам файл через время удалю, материалом распоряжайтесь по своему усмотрению. Я вам благодарен за ваш сайт, он во многом мне помог как новичку. Ссылка на исходник указывал выше в моем более раннем коментарии. Мой комментарий если не хотите можно не публиковать.
Поздравляю, это хорошо, когда добиваешься поставленных целей