Открытый проект универсального зарядника

roadster

Уважаемый R2D2, а вы читали Ричарда Баха, книгу Миссия (ударение на второе и). У него возникает мысль, что мы здесь просто из-за того, что жить прикольно. Именно в этом теле, в конкретной стране. Т.е мы ведь играем в компьютерные игры (World of Warcraft к примеру). В этих играх принимаем любой образ, который выберем. Так же и в этом мире. Выбрали конкретное тело, конкретную судьбу и играем. Бах интересен тем, что он предложил совершенно новую концепцию религии. Ведь до него было всего 2 верования. Первое - делай хорошее, иначе тебя накажут (попадешь в ад). Второе - будешь кого то обижать, снова будешь рождаться и уже тебя будут также обижать, пока до тебя не дойдет, что так делать плохо (закон кармы) Прошу меня не банить, т.к я про программирование компьютерных игр, что относится к теме С и C++ 😃

R2D2

Когда я пишу программу, я все время спрашиваю себя: А стоит ли такая цель таких сложностей программирования, может есть другой более простой более красивый подход.
Эйнштейн тоже при создании ОТО и СТО думал А так ли все сложно задумал Создатель? Бог не мог создать мир избыточно сложно. Все создано максимально просто и в тоже время есть симметрия и подобие частей и целого, как подсказки, как способ приспособиться, как способ получать ответы там, где нам не дотянуться своим разумом. Продвинувшись так далеко в логике, он видел закономерности и подсказки и наверняка задумывался как рождаются мысли. Как рождать новые мысли?

Как в Хрониках Амбера, я очнулся в этом мире человеком. Когда нибудь я умру. Совершенно закономерный вопрос, А зачем все это? Какой смысл? Ответ есть и в космосе и в каждом атоме и в моей программе и в каждом занятии. Нет ответа только там, где его не ищут.

Наверное мы не помним своей вечной предыдущей жизни, чтобы быть свободными от груза ошибок и достижений и проявить себя на чистовую. Все в равных условиях. Все родятся и все умрут в теле. Есть супер пример чистоты - Исус. Есть ли противоречие в его поведении и словах ведь это как говорят сверх разум?

Заканчиваем с философией. Продолжаем прерывания высокого уровня.

Урок №13 (прерывания)
Прерывания - это еще один удобный способ разветвить нашу программу в зависимости от событий которые происходят с ЗУ.

Где то ранее я утверждал, что при включении процессор начинает читать программу машинных кодов начиная с адреса 0. В процессоре ATMEGA32 все устроено несколько иначе. В начале памяти располагаются “ВЕКТОРА ПРЕРЫВАНИЙ” приблизительно 15 штук векторов по 2 байта на вектор. Каждый вектор - это адрес программы для обработки прерывания, т.е. адрес программы которая будет запущена при наступлении прерывания. Точно не уверен, для нас это не важно, чем ближе к началу вектор тем выше статус прерывания. Скорее всего процессор при наступлении какого нибудь события проверяет от нуля все прерывания может кто еще сработал и первым выполняет первое попавшееся от нуля, поэтому оно исполнятся первым.

Включение компьютера или RESET - это тоже событие которое имеет свой вектор. Компилятор сам впишет адрес в соответствующий вектор и расположит главную программу по указанному адресу.

В нашей программе на Си мы просто сообщаем компилятору, что мы задействовали то или иное прерывание и пишем подпрограмму для обработки. Компилятор все расставит сам.

Программа прерывания выполняется неожиданно при наступлении прерывания. Основная программа выполняла сложнейшие вычисления храня данные как в ящиках стола, так и в карманах и в руках, но тут кто то приходит и говорит: Бросай все ты мне поможешь чистить канализацию, переоденься. Процессор все распихивает по углам, набирает новые данные из вашей программы обработки прерывания и выполняет совершенно другую программу. Все это занимает очень много времени и памяти.

Прерывания были придуманы чтоб улучшить жизнь процессору а тут такие напряги, поэтому программы обработки прерывания должны быть максимально маленькие и работать с минимальным количеством памяти и переменных. Хотя опять же бывает по разному. Некоторые прерывания просят вообще все забыть и начать заново.

После окончания обработки прерывания процессор восстанавливает свое душевное и материальное состояние и продолжает как ни в чем не бывало и почти не потратив времени на прерывание.

Для того чтобы быстро распихать все свои данные перед прерыванием и восстановиться после, процессор использует СТЕК.
СТЕК вообще используют многие. При вызове каждой подпрограммы используется стек. Чем глубже вызовы подпрограммы из подпрограммы из подпрограммы из подпрограммы.

СТЕК - это память где то в ОЗУ (обычно в конце) где ее выделит компилятор. СТЕК работает по правилу последний вошел - первый вышел. Зная порядок как процессор запихивал в стек, процессор знает что он оттуда достает. Стек нужен для экономии быстродействия.

Стек может переполняться по мере использования он может разрастись и залезть на область где хранятся переменные и тогда будет непредсказуемое поведение переменных и все повиснет и понадобится собака.

R2D2

Рас уж начали говорить про стек, немного поговорим про компилятор, чтобы иметь общее представление и знать что от него ждать.

Урок №14 (компилятор)
В предыдущем тексте очень много раз упоминалось слово компилятор. Не побоюсь повторения и еще раз опишем его образ:

  1. Компилятор это обычная программа, которая преобразует мысли программиста, сформулированные на каком нибудь языке в программу на машинных кодах, понятную процессору. Далее программа прошивается в память процессора (такое бывает только в SoC ru.wikipedia.org/wiki/SoC как наш АТМЕГА32) и работает самостоятельно в нашем ЗУ.

  2. Компилятор имеет очень много настроек, которые передаются через строку параметров или в тексте программы, поэтому его удобно использовать через оболочку, которая красиво отображает программу и позволяет рулить настройками компилятора, а также может показывать результаты работы компилятора, использоваться в качестве отладчика программы, может прошивать процессор, если в комплекте с компилятором идет соответствующая утилита.

  3. Компиляторы бывают от разных производителей и бывают разные сборки у одного производителя. Все они работают по разному и имеют разные глюки.

  4. За компилятор как и за любую программу ни один автор не дает гарантий, поэтому, учитывая все многообразие возникающих проблем при программировании процессоров для различных устройств, вся ответственность лежит на нас и мы ее никогда не сможем оправдать на 100% как бы мы не старались, поэтому наше программирование должно быть максимально надежным и предусмотрительным.

  5. После компиляции программы всегда надо смотреть на результаты работы компилятора. На сколько удачно ему удалось выполнить свою работу и разместить в памяти программу, переменные, стек, постоянные данные. Нужно создать запас памяти и предусмотреть наиболее простую структуру программы и в тоже время все еще удобную для чтения и писания.

Как же работает компилятор?

Это приблизительное описание:

  1. Берется главный файл проекта и выполняются все команды препроцессора, т.е. внутрь главного файла набивается вся программа целиком со всеми файлами утилит и всеми файлами описателей и получается огромный файл всей программы.
  2. Выполняется первый проход по файлу выявляя все ошибки - лексический анализ, правильность расстановки разделяющих знаков, проверка скобок и т.д. проверяются простейшие правила, которые можно проверить не вникая в глубинный смысл программы.
  3. Выполняется синтаксический анализ. Вся программа превращается в древовидную структуру (как реестр в винде) или в нечто подобное с целью вынуть смысл программы из текста, разбить все по понятиям, кто кому приходится детьми и родителями и проверить соответствие типов, количества параметров, установить взаимосвязи между описанием и вызовом. Древовидный тип считается самым удобным для описания сложных логических построений.
  4. Оптимизация. Все это дерево анализируется на предмет лишних, подвешенных в воздухе веток и они отбрасываются или не отбрасываются (не всегда компилятор может понять все взаимосвязи) Происходит упрощение обобщение кода.
  5. Из этого голого смысла формируется текст программы в машинных кодах. Сначала мелкие подпрограммы превращаются в код известной длины. Потом все маленькие подпрограммы расставляются в памяти линковщиком, который вставляет перекрестные ссылки на подпрограммы. На этой стадии тоже происходит оптимизация по размеру или скорости. Возможно некоторые куски программы жмутся архиватором и гдето в тексте добавляется разархиватор.

Вспомним что в АТМЕГА32 три вида памяти FLASH (здесь лежит программа и константы: таблицы символов, схема меню, все текстовые фразы), ОЗУ (здесь переменные, массивы, стек), ПЗУ (настройки пользователя, настройки аккумуляторов и химии). Компилятор делает файл с программой на машинных кодах, который выглядит как последовательность байтов, которая будет записана во FLASH. Компилятор также может сделать файл для ПЗУ с начальными установками аккумуляторов и химии, но компилятор ничего не делает для ОЗУ. ОЗУ при выключении полностью теряет все и при включении там неизвестно что - мусор. Процессор выполняет программу которая лежит во FLASH и никогда не меняется, а вот в ОЗУ творится жизнь, там непрерывно все меняется: меняется содержимое глобальных переменных, возникают и исчезают временные переменные, а в конце памяти растет и убывает стек. Верх и низ ОЗУ никогда не должны пересечься. А в ПЗУ что-то меняется только, если юзер сделал изменение параметров.

Компилятор очень точно просчитывает где в ОЗУ и что будет храниться. Сначала идет область переменных и массивов глобальных (видных из всех функций и подпрограмм и живут всегда), потом идет область временных переменных (живут только на время выполнения функции), которая растет в сторону конца, потом идет пустота и весь конец ОЗУ используется под СТЕК. СТЕК начинает расти от конца к началу. То растет, то убывает. Это зависит от глубины ныряния процессора внутрь подпрограмм и от количества передаваемых параметров через функции, но самая глубина и самый большой размер стека, когда в самой глубине подпрограммы вызывается прерывание, которое спасает все состояние процессора в стек и занимает пространство ОЗУ под временные переменные.

А ОЗУ у нашего процессора всего 2048 байт и это ну прям впритык мало, и мало 32768 байт под программу. Вот почему я такой жадный, потому что глупый, и не могу вместить удобство и функциональность, которые мне так важны.

После работы компилятора winavr avr-gcc (GCC) 4.2.2 (WinAVR 20071221rc1) выдается сообщение:

Program: 30316 bytes (92.5% Full) (.text + .data + .bootloader)
Data: 1041 bytes (50.8% Full) (.data + .bss + .noinit)
EEPROM: 914 bytes (89.3% Full) (.eeprom)

Это означает что программа работать будет, потому что места хватит.
А вот после работы последней версии компилятора и 1 и 2 строка зашкаливают за 100%, такую программу даже не пытайтесь прошивать.

Одновременно с созданием машинного кода компилятор создает файл main.lst и main.lss в которых вы можете найти программу на ассемблере и таблицы размещения функций, переменных и констант.

R2D2

Мы приблизительно знаем как работает “инструмент” - язык Си. Теперь надо включить фантазию и увидеть какой будет наша программа для ЗУ (максимально лучшее, что мы можем пожелать). Заметьте, вся информация будет взята нами из опыта, все наше творчество заключается в выборе лучшего (как нам кажется) из того что знаем. Попробуйте найти какую мысль породил я или вы при составлении программы. Что конкретно новое? Где творчество?

Урок №15 (Рецепт как написать программу для зарядного устройства)

Наша программа для ЗУ должна уметь следующее (в порядке мысленного взгляда на чужие ЗУ):

  1. Понятное и безопасное меню без всяких там инструкций (на сколько позволит размер программы).
  2. Быстрый и удобный запуск зарядки.
  3. Исчерпывающая информация о результатах, которая никуда не пропадет при нажатии на не ту кнопку.
  4. Доступ к настройкам.
  5. Простота.
  6. Крутой и гибкий алгоритм зарядки. Проверка на неестественное поведение аккумулятора.
  7. Связь с большим компом и возможность отдать ему вообще всю инфу из ЗУ онлайн.
  8. Возможность без большого компа проводить настройку и отображение ошибок и всяческих переменных.

Не смотря на то, что главный смысл ЗУ - качественно заряжать, большая часть программы будет посвящена интерфейсу с юзером. Сам алгоритм зарядки - это проверка текущего состояния датчиков, вычисление необходимой коррекции и посылка команд на исполняющее устройство (установка текущего тока зарядки) займет очень мало места.

Если бы мы располагали исследованиями в области аккумуляторов да на русском языке, да имели экспериментальную базу, можно было бы нагородить огород с использованием скоростей температуры, напряжения или производных dV/dI dT/dV и т.д. И все равно существенно это не заняло бы места, если только там нет волшебных таблиц с критическими точками.

Имея дело с широким списком различных производителей и будучи ограниченными в памяти процессора, путь развития алгоритма зарядки остается возможным, но пока не приоритетным.

А вот имея “широкие массы” пользователей не гоняющиеся за максимальной отдачей аккумулятора и часто делающие ошибки, мы имеем приоритетное направление удобный интерфейс. Кроме того, удобный интерфейс поможет отлаживать нашу программу без отладчика.

Вывод: Первое чем займемся - это интерфейс на все случаи жизни с минимальной загрузкой процессора и с минимальными затратами на его использование.

Должна получиться заготовка, которую можно будет легко использовать для любого устройства на АТМЕГА с прицепленным ЖКИ и с очень простыми функциями отображения инфы на ЖКИ.

Для использования нашей заготовки надо всего лишь переопределить ножки процессора к которым подключено ЖКИ и писать программу с отображением отладочной информации на экран для самоконтроля.

Как при создании программ для ЗУ так и при создании программ для передатчика или любых других, можно заметить что на экране появляются одни и те же изображения, в которых есть много общего. А так как мы экономим место и время, то знание общего нам сильно поможет.

Лирическое отступление как делаются архиваторы:
Когда то давно мне понадобилось сократить трафик между моими двумя программами, которые были удалены друг от друга. И тогда я начал читать про способы архивации и опять наткнулся на заумные рассуждения. Как везде все можно объяснить на пальцах. Во всех методах сжатия информации используется один и тот же принцип: очень умные дяди внимательно смотрят на эту информацию и ищут закономерность. Если закономерность есть, значит данные сжать можно, если закономерности нет - нет. Если какие то фрагменты данных повторяются, значит их можно один раз запомнить, а в то место где они стоят (в архивированном файле) поставить ссылку на одну эту копию (ссылка должна быть меньше по размеру чем повторяющийся фрагмент).

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

Как архиваторы смотрят на данные, точно также программист смотрит на свою программу и ищет где он повторился и нельзя ли это место обобщить и один раз описать и потом многократно использовать.

Точно также каждый человек должен подумать про устройство мира. Если есть одинаковые события их надо обобщить и придумать правило/закономерность, чтобы работать не с самими событиями, а с их закономерностью.

Не надо запоминать устройство каждого магазина, а надо знать их общее. Надо найти закономерности жизни - правила жизни - правила успешной жизни. Надо найти цель жизни. Надо использовать правила для достижения цели или того места где она приблизительно находится а там сориентироваться. Надо отмести все лишнее и вредное, надо перестать врать себе и замалчивать и отворачиваться от ясных рассуждений. Надо следовать ясным добрым рассуждениям иначе нормальной программы не написать.

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

R2D2

Вывод информации на ЖКИ будет как бы состоять из 3 уровней:

  1. Самый высокий уровень (программиста) в тексте программы происходит формулирование требований к шаблону (передача параметров) и вызов самого шаблона по его номеру. Все шаблоны нами будут перенумерованы и заполнены заранее. Шаблон может обновляться сам, отслеживая изменения переменных.

  2. Уровень функции заполнения шаблона данными. При вызове шаблона с параметрами функция отрисовки шаблона заполняет шаблон текущими данными (фразами из словаря и переменными I,V,T…). Прорисованный шаблон помещается в видеопамять в ОЗУ в виде букв и символов для всех строчек ЖКИ. А также сигнализирует, что произошло обновление видеопамяти.

  3. Самый низкий уровень. Уровень прерывания производит передачу видеопамяти в ЖКИ со скоростью не более 1 экран целиком за 0.5 секунды. Причем прерывание передает за 1 сеанс не более 1 команды (ЖКИ) или буквы (ЖКИ) так, чтобы задержка отработки ЖКИ попадала на время работы основной программы. Все ожидания реакций ЖКИ наш процессор не ждет а работает с основной программой. Вот она феничка экономии. ЖКИ после приема буквы или команды задумывается, это время ему нужно для записи буквы в свою память. Так вот наш процессор должен ждать (пока там ЖКИ скушает букву), чтобы передать следующую. А мы ждать не будем. Передадим букву и закончим прерывание. Пока ЖКИ жует, мы выполняем основную программу.

Т.о. нам нужно вызывать “видео-прерывание” 4строки*20символов*2раза_всекунду*2раза_прозапас=320 раз в секунду.

Т.о. как во взрослых компьютерах у нас есть видеопамять и видеопрерывание (драйвер видюхи), только видеопамять мы используем из ОЗУ и самой прорисовкой видеопамяти занимается сам процессор а не посторонняя видеокарта. Но вцелом все идеи те же. Мы выполняем нашу второстепенную задачу: глобальная экономия всего. За счет усложнения структуры программы мы сэкономили время (ожиданий) и память программы (на многократных вызовах функции посылки фраз в ЖКИ).

R2D2

Чуть по подробнее расскажу про шаблон. Например при зарядке аккумулятора на экране отображается информация о ходе процесса зарядки. Такая информация постоянно отображается и обновляется в течение нескольких часов как для 1 канала так и для 2 канала. Циферки меняются а форма шаблона одна и та же, значит форму шаблона можно жестко описать.

Те же рассуждения относительно всей отображаемой информации на ЖКИ. например экраны настройки или экраны отражения итогов зарядки или само главное меню, несмотря на то что разное, имеет в себе нечто общее.

Чтобы описать шаблон мы придумаем коды-команды шаблона например такие:

  • отобразить в 1 строке в 5 столбце переменную номер 5 на 6 знакоместах причем последние 3 знакоместа отвести под дробную часть;
  • отобразить в 2 строке в 9 столбце фразу из списка фраз номер которой лежит в переменной номер 17

// Шаблоны экрана
// Описываются байтами - командами
// 0xFF - Конец шаблона
// 0 fc - Поставить курсор
// fc=0xff -Курсора нет нигде CURSOR_OFF
// 7b=1 -Курсор моргает квадратом CURSOR_BLINK_ON
// 7b=0 -Курсор горит подчерком CURSOR_LINE_ON
// 10b -Номер строки где стоит курсор
// 65432b -Номер столбца где стоит курсор
// 100 fc=0xff -Курсора нет нигде CURSOR_OFF

// 1 - Очистить весь экран
// 2 - Очистить первую строку
// 3 - Очистить вторую строку
// 4 - Очистить третью строку
// 5 - Очистить четвертую строку
// 6 XY NN - Очистить строку начиная с XY длинной NN
// 7 XY NN KK - Напечатать переменную KK начиная с XY целым десятичным числом длинной NN
// 8 XY NN KK - Напечатать переменную KK начиная с XY целым шестнадцатиричным длинной NN
// 9 XY MN KK - Напечатать переменную KK начиная с XY FLOAT числом длинной M включая N знаков после запятой
// 10 XY NN KK - Напечатать бинарную-переменную KK начиная с XY
// 11 XY KK - Напечатать время-переменную KK начиная с XY
// 12 XY KK NT - Напечатать KK текст начиная с NT
// 20 XY KK - Напечатать название аккума
// 21 XY KK - Напечатать название типа аккума
// 14 XY na tt - Напечатать байт na из временного массива акк
// 15 XY nt tt - Напечатать байт nt из временного массива типов акк

// 16 XY TT - Напечатать текст TT начиная с XY
// 22 XY f - Напечатать название акк(f=1) или типа акк(f=0) из временного массива
// 17 XY MM - Напечатать меню MM начиная с XY
// 18 - Изображаем 3-х уровневое меню
// 19 - Изображаем 1-но уровневое меню

Выбор принципа шаблонов - это достаточно сложный метод. Если ваша программа из 5 разных шаблонов, то возможно никакой экономии не произойдет, но если у вас шаблонов много…

С вызовом рисования шаблона все ясно - ЖКИ все время что то да рисует, чтобы юзеру не было скучно. Заведем глобальную переменную BYTE, которая в себе содержит код текущего шаблона.

Если вы покопаетесь в моей программе вы увидите, что для того чтобы вызвать рисование шаблона я использовал функцию, заведенную мной для препроцессора SH(). Например мне надо, чтобы в каком то месте программы начал выводиться шаблон номер 5, тогда я в тексте программы пишу SH(5) и даже без обычной для Си “;” в конце. Препроцессор перед компиляцией изменяет мою указивку на следующее {nSh=5; pSh=255; cSh=2; sSh.first=1;} это делает директива препроцессора #define SH(n) {nSh=n; pSh=255; cSh=2; sSh.first=1;}

nSh=5; - это тот самый код текущего шаблона номер 5.
pSh=255; - этот шаблон нарисовать 1 раз, если бы <255, то перерисовывать шаблон каждые pSh/10 секунд.
cSh=2; - счетчик для отсчитывания pSh со временем уменьшается, ему специально присваивается ненулевое значение, т.к. прерывание могло его уже уменьшить.
sSh.first=1; - перерисовать шаблон полностью как в первый раз.

Для управления шаблонным механизмом и вообще всем ЖКИ из основной программы я использую всего несколько команд, указанных ниже, все осталоное делает за меня функция рисования шаблонов в видеопамять и прерывание, отправляющее видеопамять в ЖКИ.
Не пытайтесь вникнуть в Си этих команд просто почитайте коментарий и обратите внимание на первое слово после #define

// Пометь строки в которых были изменения
#define XYL switch((XY)&7){case 0: fv.s1=1; break; case 1: fv.s2=1; break; case 2: fv.s3=1; break; case 3: fv.s4=1; break;}
// Сделать текищим шаблон n период перерисовки изменяемой части p
#define SHABLON(n, p) {nSh=n; pSh=p; cSh=2; sSh.first=1;}
// Сделать текищим шаблон n полностью прорисовать 1 раз
#define SH(n) {nSh=n; pSh=255; cSh=2; sSh.first=1;}
// Отобразить шаблон меню
#define MENU nSh=1; pSh=255; cSh=2; sSh.first=1;
// Сейчас перерисовать текущий шаблон полностью
#define REFRESH_SHABLON cSh=2; sSh.first=1;
// Сейчас перерисовать только изменяемую часть шаблона
#define REFR_SHABLON cSh=1;
// Поставить курсор и заставить моргать или неморгать
#define CURSOR(x, y, type) (type==CUR_OFF)?fc=255:fc=((y)&3)+(((x)&31)<<(2))+(type==BLINK_ON?128:0);
// Установка курсора при редактировании названия аккума или типа аккума для 4х строчного ЖКИ
#define FC(x) ((x)<<(2))+3+128;
// Рассчитать XY из x и y
#define XYN(x, y) ((x)<<(3))+((y)&7)

А вот как выглядит настоящий шаблон номер 3 (одна строка одна команда)
// Шаблон отражения зарядки/разрядки первого канала (название аккума ток напряжение температура заряд в процентах время)
FCHAR _s3[]=
{
1,
12, 0, 108, 16,
16, 1, 52,
9, XYN(6, 1), 0x52, 101,
16, XYN(12, 1), 92,
9, XYN(15, 1), 0x51, 109,
16, 2, 46,
9, XYN(6, 2), 0x52, 102, 11, XYN(12, 2), 104,
16, 3, 90,
9, XYN(7, 3), 0x52, 103,
100,
255
};

Берем первую циферку из скобок и смотрим что она означает в командах чуть выше описанных

R2D2

Это очень сложный кусок объяснений был. Не все удалось расставить как следует и не везде исправил ошибки - не успел.

Не стесняемся, спрашиваем. Все просто. Главное понять идею. Не парьтесь, что все до тонкости не ясно. Поймете идею, копайте глубже, спрашивайте.

Прошу вас не думайте, что каждый символ из этого бреда надо понять. Вся эта программа делалась постепенно многими ночами, оттачивалась и переписывалась многократно и понять сразу все нереально. Любой специалист просто схватывает основную мысль и глубже не идет читает как философию, хотя если захочет то и глубже может копнуть и ошибок тьму найти.

Все время приходится выявлять главное и второстепенное и в зависимости от времени и возможности отбрасывать все второстепенное и концентрироваться на главном исходя из цели.

R2D2

Хотя вся эта информация глубоко специфичная и почти никому не пригодится, но даже те кто никогда программировать не будут могут понять общий подход и использовать его гдето еще или чуть чуть приблизиться к программированию.
Такой вот последовательностью чисел/байтов кодируется шаблон:

1-й байт команда
2-ой байт адрес строки и столбца ЖКИ XY=X(5 старших битов)+Y(3 бита младших). Ноль левый верхний угол экрана ЖКИ
3-ий и 4 байты, если есть, задают специфику этой команды

XYN() - это самодельная функция препроцессора которая помогает мне X и Y засунуть в 1 байт X(5 старших битов)+Y(3 бита младших)

FCHAR _s3[]=              // (FLASH CHAR) Обявляем указатель на массив байтов, расположенных во FLASH
{                         // потом этот указатель мы включим в массив указателей, чтобы иметь возможность обращаться к шаблону по номеру
1,                        // 1="Очистить весь экран"
                 // Строка 0
12,XYN(0,  0), 108, 16,   // 12="Напечатать текстовую переменную" 108я переменная сдвиг16 "Зарядка/Разрядка"
                 // Строка 1
16,XYN(0,  1), 52,        // 16="Напечатать текст" номер 52("I1(A)=")
9, XYN(6,  1), 0x52, 101, // Напечатать float переменную 101(Ток1) на 5 знаках, два после запятой
16,XYN(12, 1), 92,        // 16="Напечатать текст" номер 92("E%=")
9, XYN(15, 1), 0x51, 109, // Напечатать float переменную 109(энергия) на 5 знаках, 1 после запятой
                 // Строка 2
16,XYN(0,  2), 46,        // 16="Напечатать текст" номер 46("V1(В)=")
9, XYN(6,  2), 0x52, 102, // Напечатать float переменную 102(напряжение1) на 5 знаках, 2 после запятой
11,XYN(12, 2), 104,       // Напечатать время работы канала
                 // Строка 3
16,XYN(0, 3), 90,         // 16="Напечатать текст" номер 46("T1(\x04С)=")
9, XYN(7, 3), 0x52, 103,  // Напечатать переменную 103(температура1)
100,                      // Отключить курсор
255                       // Конец шаблона
};
R2D2

Урок №16 (Работа с памятью)

Сначала небольшое обобщение относительно больших, нормальных компьютеров и программирования на Си. Начну из далека.

Компьютером называется совокупность трех компонентов: процессор, память, периферия, доступная через порты ввода вывода. До нынешних времен под памятью в основном имели ввиду ОЗУ (Оперативное Запоминающее Устройство, SIMM, DIMM, SDRAM, DDR). Эта та самая память, которая теряет все при выключении, эта та самая память в которой ячейка памяти - мизерный конденсатор, который теряет свой заряд за доли секунды если его не подпитывать (регенерация памяти). Зато такая память обладает максимальным быстродействием и одна ячейка занимает на кристалле минимальное место. Плата за такие супер параметры - регенерация. На материнской плате есть специальный контроллер памяти, который периодически читает всю память и записывает (подпитывает) это же значение в память. Это делается параллельно обычной работе компьютера, поэтому вообще не заметно для пользователя.

Зачем же нужна такая память которая все забывает? Зачем нужен народ который не помнит своей истории не помнит своих предков? Не помнит уроков: как делать нельзя и как можно. Плохая аналогия, но что то есть.

Оказывается, что такая память вполне подходит компьютеру для хранения ПЕРЕМЕННЫХ. Ни одна программа больше трех пальцев не может существовать без переменных. Чтобы посчитать а+б=с, необходимо где-то временно хранить а и б. Потом они уже не нужны, нужен результат. Результат будет высечен в мраморе на века, а временное забудется и уйдет. Но без этого временного компьютер работать не может. Любой процессор имеет кусочек ОЗУ прямо внутри себя. Такая внутренняя память называется регистры процессора - это самая быстрая память, самая необходимая, самая главная. Все логические операции совершаются там в голове, а потом хранятся в переменных в ОЗУ в более медленной памяти чем регистры, потому что надо оттуда читать, а на это уходит время. Нынешние процессоры имеют дополнительно к регистрам CASH - особую память в которую закачивают копию фрагмента ОЗУ наиболее часто используемую, чтобы сэкономить время, но это все детали.

Язык Си работает в основном с обычной ОЗУ. Там создаются временные переменные необходимые для работы программы. Обращение к памяти происходит через обращение к переменным и массивам, размещенным в памяти. К памяти также можно обращаться прямо по адресу, но это надо делать осторожно (можно порушить данные системы), поэтому не приветствуется.

Процессор работает не только с ОЗУ, но и с другими устройствами: с клавиатурой, мышкой, видеоплатой, сетевухой, COM-устройства, USB-устройства, винчестером, CD-ROM, DVD, BDROM, принтером, плоттером, ЗУ, дисководом, флэшкой, мобилой, фотиком и т.д. Все это переферия. Большинство этих устройств имеет единый механизм обращения к ним.

Работа всех вышеперечисленных устройств зависит от параметров их работы, а это информация в виде байтов. Например: раскрутите диск до скорости 2000 об/мин, поставьте головку 1 на 145 цилиндр, прочитайте 49 сектор, информацию из сектора дайте мне. Все команды и вся прочая информация побайтово передается через порты ввода/вывода. И тот самый универсальный механизм - это механизм ПОТОКА ДАННЫХ.

С памятью ясно как работать либо переменные либо прямая адресация, а со всеми неизвестными устройствами, через их драйвера открывается поток данных. “Все” драйвера “всех” устройств обязаны поддерживать стандартный набор команд, чтобы виндоуз мог дать доступ через системные процедуры к ним. А Си как раз прицепляется к системе и знает как с ней разговаривать. Си общается только с системой. Система общается через дрова со всей переферией.

Так вот Си дает доступ к любому устройству через стандартный механизм потока с ограниченным набором команд. Вся ненужная информация и детали прячутся от юзера остается самое необходимое передача и получение данных из переферии. Где-то в этих данных может быть и управляющая информация.

  1. Открыть поток и получить указатель на него. Далее все во всех командах обязательно есть указатель на поток.
  2. Получить длину потока в байтах если есть.
  3. Встать на определенный элемент потока.
  4. Прочитать или записать данные в поток с текущего элемента.
  5. Закрыть поток.

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

Это мы говорили про большие компьютеры, чтобы иметь общее представление, теперь вернемся опять к АТМЕГА32.

У АТМЕГА32 три вида памяти ОЗУ(RAM) ППЗУ(EEPROM)(перепрограммируемая постоянное запоминающее устройство) ФЛЭШ(FLASH). Во ФЛЭШ лежит программа и некоторые данные для рисования на ЖКИ, в ОЗУ лежат переменные, в ППЗУ лежат настройки юзера для аккумуляторов. WinAvr Си работает с ОЗУ так же как и в больших компьютерах просто и легко, а вот FLASH и EEPROM имеют особенности. Во первых, 3 вида памяти и все имеют свою адресацию, у каждой памяти есть свой 0 и свой конец. Во вторых, команды чтения и записи разные, поэтому компилятор должен знать кто есть кто чтобы знать как писать туда.

В компиляторе IAR Си все так и сделали. Описываем переменные, указывая где они лежат, и все, дальше просто работаем с переменными, не думая ни о чем. В WinAVR к сожалению это не так. Не только надо описать переменные, но и работать с ними особым образом, своими функциями записи и чтения для EEPROM(читаем и пишем) и FLASH(только читаем). С ОЗУ все как обычно проблем нет, все как в стандартах Си для любых компов.

Ниже я приведу свои функции работы с EEPROM и FLASH.

Начнем с FLASH:

R2D2

Во FLASH у нас хранится:

  1. Все фразы меню и массив указателей на начало каждой строчки меню
  2. Все фразы шаблонов и массив указателей на начало каждой фразы
  3. Ноты музыки
  4. Таблица перекодировки ASCII символов в кодировку ЖКИ

Со всей этой информацией мы работаем через нижеуказанный механизм чтения из FLASH

// Подключаем стандартный комплект функций WinAVR для работы с FLASH
#include <avr/pgmspace.h>           // Работа с FLASH

// Описание байта во флэш памяти и то и другое по сути одно и тоже
// просто в разных подпрограммах из WinAVR требуется строго один из двух
// приходится подстраиваться под бесплатные дары
// Опять же назвал по своему, т.к. ихнее название некрасивое не логичное
typedef prog_uchar FBYTE;           // Байт во FLASH памяти
typedef prog_char FCHAR;            // Символ во FLASH памяти

// Из флэш памяти только читаем.
// Вот все функции чтения флэш
// Я их немного переобозвал, чтоб все логично и компактно было
#define FRB  pgm_read_byte          // Читаем байт из FLASH
#define FRW  pgm_read_word          // Читаем ссылку из FLASH
#define FRS  memcpy_P               // Читаем кусок из FLASH

// Например так я описал меню
// Сначала описываем указатели на фразы и размещаем сами фразы в памяти
FCHAR _m0[]="I.Канал 1";
FCHAR _m1[]="II.Канал 2";
FCHAR _m2[]="III.Аккумуляторы";
// Потом собираем указатели на фразы в массив указателей
PGM_P PROGMEM Mn[3]={_m0, _m1, _m2};

// Теперь, зная какую фразу нам надо вывести (ее номер), мы по номеру достаем
// указатель, а по указателю(адресу первой буквы фразы) копируем буквы
// например в видеопамять которая будет прерыванием выведена в ЖКИ.
PGM_P fadr=(PGM_P)FRW(&Mn[1]);   // Читаем указатель(адрес) первой фразы
BYTE x=FRB(fadr);                // Можем прочитать первую букву из этой фразы
FRS((BYTE*)V2, (PGM_P)fadr, len);// Копируем len байтов фразы в видеопамять

Как я ранее говорил есть 3 уровня “низости”:

  1. Верхний уровень - работа с шаблонами (очень простой способ полностью разрисовать ЖКИ пятым шаблоном: SH(5))
  2. Второй уровень - прорисовка шаблонов в видеопамять. Как раз этот уровень сегодня обсуждаем.
  3. Низкий уровень - уровень прерывания. Посылка видеопамяти в ЖКИ.

P.S. На нашем нынешнем уровне развития считаем что: Сылка=Указатель=Адрес
P.P.S. Уважаемые начинающие Си программисты, если чего то не ясно не парьтесь, спрашивайте или повторяйте как заклинания буковки, здесь написанные, в своих программах и смотрите как ругается компилятор.

R2D2

Во FLASH у нас хранится:

  1. Все фразы меню и массив указателей на начало каждой строчки меню
  2. Все фразы шаблонов и массив указателей на начало каждой фразы
  3. Все шаблоны и массив указателей на начало каждого шаблона
  4. Ноты музыки
  5. Таблица перекодировки ASCII символов в кодировку ЖКИ

Забыл сказать, что во FLASH хранятся не только фразы шаблонов но и сами шаблоны и массив указателей на начало каждого шаблона.

У нас 3 массива указателей: на начало каждой фразы меню, на начало каждой фразы шаблона и на начало каждого шаблона. Эти массивы указателей нужны нам, чтобы, зная их номер, найти их в памяти. Например SH(5) - рисуем пятый шаблон. Программа берет 5тый указатель на шаблон из массива указателей, а указатель это адрес в памяти, значит программа знает откуда из памяти брать байты 5-го шаблона. Программа берет по очереди байты шаблона, пока не наткнется на 255(конец шаблона). И вот встречена команда 16 (напечатать фразу шаблона 95). Опять программа достает 95-тый указатель из массива указателей на фразы шаблона, а этот указатель это адрес первой буквы фразы. И тогда программа берет по очереди все буквы пока не встретится 0(признак того что фраза закончилась)

Вот таким хитрым образом мы объяснили работу с FLASH, рассказали что мы там храним и немного объяснили второй уровень “низости”.

Теперь EEPROM:

Зачем нужно было городить огород с тремя видами памяти, знает только производитель АТМЕГА32. Вероятно они руководствовались соображениями экономии денег. По большому счету EEPROM и FLASH это одно и тоже. FLASH вероятно подешевле будет, но количество циклов перезаписи меньше и возможно занимаемое место на кристалле меньше. EEPROM более тормозная, больше циклов перезаписи. Для нас для программистов и то и другое это постоянная память и лучше бы она была одна, а не две.

Но деваться не куда придется работать с чем есть.

В EEPROM мы храним:

  1. Настройки химии аккумуляторов. Каждая химия ведет себя одинаково.
  2. Настройки аккумуляторов. Сколько банок и ампер-часов в моих любымых акках.
  3. Настройки интерфейса ЗУ. (Музыка)
  4. Настройки каналов (подстроичные коэффициенты для расчета I,V,T из тех вольт, которые намерены АЦП)

Все эти данные можно читать и писать, но желательно не часто. И скорость чтения и записи будет не быстрая.
Поэтому при включении ЗУ или запуске Канала мы все считываем в ОЗУ и с этими данными работаем. Ну при настройке ЗУ запишем подстроечные коэффициенты. Изредка добавим новый аккум. Короче процедуры записи и чтения EEPROM вызываются редко и медленно.

R2D2
// Процедуры работы с EEPROM от WinAVR стандартная поставка
#include <avr/eeprom.h>             // Работа ЕЕПРОМ

// Описание размещения данных (Химия Акки Коэффициенты)
// На основании его компилятор создает прошивку main.eep для EEPROM
#include "eeprom.h"                 // Описание содержимого ЕЕПРОМ

// Функции для записи и чтения EEPROM переназванные по человечески
#define ERB  eeprom_read_byte       // Читаем байт из EEPROM
#define ERS  eeprom_read_block      // Читаем кусок из EEPROM
#define EWB  eeprom_write_byte      // Пишем байт в EEPROM
#define EWS  eeprom_write_block     // Пишем кусок в EEPROM


EEMEM BYTE ET1=1; // eeprom.h (0-датчик отсутствует, 1-датчик работает)
a=ERB(&ET1);      // Прочитали из EEPROM в обычную переменную BYTE ОЗУ
EWB(&ET1, a);     // Записали обычную переменную BYTE в EEPROM

// float это 4 байта, поэтому читаем его из EEPROM и пишем в ОЗУ переменную
// т.е. копируем 4 байта начиная с адреса в EEPROM в ОЗУ туда где расположена
// нужная нам переменная
EEMEM float E1_k0=-0.302066773176193; // описано в eeprom.h

ERS((BYTE*)&Ch1.k0, (BYTE*)&E1_k0, 4); // Читаем из EEPROM коэфф тока 1 канала
EWS((BYTE*)&Ch1.k0, (BYTE*)&E1_k0, 4); // Пишем в EEPROM коэфф  тока 1 канала

Для новеньких в Си:
Многие функции, как в вышеописанных примерах, для копирования из одного вида памяти в другой вид памяти требуют чтобы им указали адрес откуда куда. Поэтому в Си придуман специальный значок “&” который ставится перед переменной спереди, а компилятор когда делает программу на машинных кодах на место “&переменная” вставляет адрес этой переменной.

Слово (BYTE*) подсказывает компилятору, что это будет именно адрес последовательности байтов. В нижнем примере мы обязаны добавить это слово потому что сама переменная была описана float, а функция ждет BYTE. Компилятор сразу предполагает что мы сделали ошибку и подсунули ему float вместо BYTE, а мы ему явно говорим: Будь спокоен это то что нужно.

votik

Господа. А зарядку аккумуляторов А123 этот зарядник поддерживает?

R2D2

По идее может все.
max 25v 5a
Если химию в настройках правильно пропишете работать будет.

НО, без опыта пайки смд и некоторых знаний…

maksi1

А возможно ли в этом зарядном простым способом увеличить максимальную ёмкость заряжаемой батареи?Может где нибудь можно сменить тип переменной?Для автомобильных батарей 25,5А/ч маловато.И по току нестыковка получается:например устанавливаю ёмкость батареи 7А/ч,ток заряда 10%,должно заряжать током 700mA.А заряжает током 630mA. И с разрядом тоже самое.Кто собирал зарядное как у Вас с этим?

R2D2

Сделать можно все, т.к. все в наших руках.
Значит необходимо увеличить отображаемую емкость в 2 раза?

Фактически это надо в программе в нескольких местах добавить коэффициенты 2.
При чтении настроек из ЕЕПРОМ в переменную емкости, при записи в ЕЕПРОМ переменной емкости.
И исправить настройки всех остальных аккумов, уменьшив их в 2 раза.
Программа на большом компе будет врать.
Только это ухудшит точность задания емкости до 0.2А, т.к. емкость задается одним байтом. 255 будет соответствовать 51 А/ч, а 1 будет соответствовать 0.2 А/ч

Можно задействовать один из зарезервированных битов настроек, который будет сигнализировать о другом масштабе емкости.

Есть еще один путь - на емкость отвести 2 байта, но это более сложный путь.

Что касается 630 ma это вероятно очередной глюк программы. Посмотрю и исправлю.

R2D2

Урок №17 (прерывания погружение)
Не смотря на то, что на дворе 21 век, до сих пор встречаются программы написанные для DOS и такие программы исходят например из ЦБ РФ (SPRAV.MFO). Вероятно еще не вымерли программисты старой закалки. В таких программах опрос клавиатуры производится на частоте процессора. Представьте себе, что процессор 3 миллиарда раз в секунду опрашивает клавиатуру “не нажата ли кнопка какая”. Это при том, что юзер за весь день лазанья по инету едва ли 100 кнопок нажал. Даже самые продвинутые секретари набирают не более 10 кБ в день, нажимая кнопки в среднем не чаще 1 в секунду. Но если вдруг юзер заметил, что отклик компа на нажатие задерживается на 1/3 секунды, то говорят что клава тупит. Причем здесь клава?

На одноядерных компах такая программа DOS парализует работу всех остальных программ. Для того чтобы в программах не делать множество циклов ожидающих происхождения какого либо события (нажатия кнопки, прихода широковещательного запроса по сетке, звонка по телефону, наступления 1000-ной секунды, движения мышки и т.д.) мы можем использовать прерывания. Прерывания встроены в логику процессора аппаратно. Прерывания прерывают основную программу телевиденья для сообщения важного сообщения, т.е. запускают программу обработки прерывания. Прерывания наступают внезапно или циклически. Прерывания это появление сигналов на определенных ногах процессора или особые “прозрения” процессора. В моменты прозрения в голове процессора открывается новое видение мира, открывается истина, которая настолько фундаментальна, что сиюминутные заботы процессора кажутся ничтожными. Осознание истины очень приятно. Что происходит в этот момент не понятно. Происходит какое то созерцание/восприятие истины, мысли останавливаются, всякие там рассуждения о выгоде и прибыли тем более. Истина приходит в виде цельного образа, который испаряется и в остатках тумана угадывается ответ на давно мучивший вопрос. В следующее мгновение уже ничего нет. И доказательств нет, но ответ как ни странно правильный.

В АТМЕГА32 есть такие прерывания:

  1. Прерывание при сбросе или включении.
  2. Внешнее прерывание 0,1,2. (Программируется работа 3х ног процессора специальным образом) и при возникновения на ногах процессора +5в или 0в срабатывает прерывание.
  3. Счетчик/таймер 0,1,2. Внутри процессора есть счетчики, которые считают кратно тактовой частоте, при достижении счета определенного числа или при достижении максимального значения (переполнения) срабатывает прерывание.
  4. При работе с последовательной передачей данных (например от ЗУ к большому компьютеру или обратно) возникает необходимость подготовить новые данные для отправки или спрятать в память полученные данные, для этого используются прерывания “пришел последний бит очередного байта”, “отправлен последний бит отправляемого байта”, “последовательная передача завершена”, “байт данных при передаче пуст”.
  5. Завершена трансформация Аналогового сигнала в цифровой.
  6. При записи или чтении в ЕЕПРОМ “ЕЕПРОМ готов”.
  7. Сравнение аналогового сигнала и моего сторожа(цифрового) завершено.
  8. Передача последовательных данных по 2 проводам ОК.
  9. Запись во ФЛЭШ завершена.

Как мы уже говорили в первых 40 байтах ФЛЭШ (памяти программы) лежат “вектора прерываний” (адреса подпрограмм обработки). При срабатывании прерывания, выполняется соответствующая подпрограмма.

Нам конечно же понадобится прерывание “АЦП завершено” и прерывания по передаче и получению байтов в СОМ-порт. Но кроме того у нас есть прерывание циклическое (по переполнению счетчика) для передачи низких команд в ЖКИ. Циклическое прерывание нам надо еще чтобы играть музыку и отсчитывать секунды зарядки.

Вот магические слова которые описывают прерывания в программе, чтобы компилятор правильно вписал вектора куда следует.

// Описатели прерываний
#include <avr/interrupt.h>          // Работа с прерываниями

// Прерывание переполнения счетчика 0
ISR(TIMER0_OVF_vect)
{
}

// Прерывание "Преобразование АЦП завершено"
ISR(ADC_vect)
{
}

// Это прерывание обрабатывает поступившие команды с компьютера по COM порту
ISR(USART_RXC_vect)
{
}

// Отправка очередного байта на COM-порт большого компа
ISR(USART_TXC_vect)
{
if(iOutBuf)                    // Если обратный счетчик не пуст
  {
  UDR=OutBuf[nOutBuf-iOutBuf]; // Посылаем следующий байт
  iOutBuf--;                   // Уменьшаем счетчик буфера
  }
else                           // Иначе все уже отправили
  fTran=false;                 // С посылкой последнего байта зак транзакция
}
// Сюда будут направлены все неиспользованные прерывания (может быть)
ISR(BADISR_vect){}

Все тексты вы можете посмотреть в исходниках. Привожу только текст отправочного прерывания, т.к. он прост и показывает, что в прерывании надо действовать быстро.

R2D2

Урок №18 (ЖКИ)

ЖКИ - это доисторическое графическое, текстовое (знакогенерирующее) устройство, оказавшееся очень живучим. Уже давно придуманы экраны мобильников, которые могут несравненно больше, стоят дешевле и вообще… И все же ЖКИ до сих пор пользуются популярностью у разработчиков всего мира из-за своей простоты. Можно было бы доработать эту простоту до еще большей простоты, но этого никто не делает. Лучше иметь что-то постоянное, хотя и немного несовершенное. ЖКИ - это стандарт HD44780.

ЖКИ - это автономный компьютер, в котором есть своя память, свой процессор, свои порты ввода вывода и экран. У ЖКИ есть своя ОЗУ - видеопамять, которая непрерывно отражается на экране аппаратно. ЖКИ работает на частоте 1 МГц. ЖКИ имеет параллельную, 8-ми и 4-х битную шину передачи данных.

ЖКИ - это устройство для отображения текстовой информации и соответственно его функции заключаются в том, чтобы получить эту информацию из шины и отобразить ее на экране. Больше ничего ЖКИ не может. Если бы мы были создателями стандарта общения с ЖКИ - HD44780, то как бы мы его написали?
Вероятно, должна быть команда полный перезапуск и чистка всего. Должны быть команды нарисовать букву там где стоит курсор и передвинуть курсор на следующую позицию, переставить курсор вообще на произвольную позицию. Должны быть команды, управляющие типами курсора и команды работы с псевдографикой. Так оно и есть.

Если мы знаем функции (смысл) устройства, то и как им управлять в общих чертах нам тоже ясно, потому что мы такие же разработчики как и другие. Таким образом, не читая инструкций, а просто пофантазировав или разглядывая названия ножек ЖКИ, можно догадаться о работе чужого устройства и всего мира. Это один из способов пробить стену незнания и непонимания, особенно, если инструкцию переводил переводчик а не технарь. Люди с опытом уже не фантазируют, а знают как работают аналогичные устройства. Но конечно, многое, созданное людьми попахивает нарушением логики и наличием ошибок. Ошибки есть у всех, но это не должно останавливать прогресс. Ошибки надо осознать, прощать и исправлять.

Итак посмотрим что за ножки у ЖКИ:

  1. GND и Vcc питание ЖКИ. Подаем питание и уже ЖКИ работает, правда вместо цивильного теста пол экрана красится в черный цвет пол в белый. Стандарт не предусматривает ни какой тестовой информации а сделать ее было так легко.

  2. V0 - напряжение от 0 до 5в - поляризация (яркость, контрастность) бывает что ярче когда 0, а бывает что когда 5в. По умолчанию притянуто резистором куда надо.

  3. 8 ног данных. Мы не будем использовать все 8 ног, только 4, хотя и будем всегда передавать 8 бит информации за 2 раза. 8 ног это не экономично, это значит будет занято 8 ног от нашего процессора, а мы не можем быть такими расточительными.

  4. RW - (1(5в)-чтение, 0-запись) читаем из ЖКИ или пишем в него. Конечно же мы будем только писать в ЖКИ и не будем контролировать, правильно ли мы вписали, как это предусмотрено стандартом. Мы будем четко и ясно отдавать команды без помех и надеяться что нас услышали. Все это ради экономии.

  5. RS - (0-команда ЖКИ, 1-данные(буквы)) Как работает эта нога подробно описано в протоколе. Всю получаемую информацию ЖКИ воспринимает как команды и данные в зависимости от того какой сигнал на этой ноге. К командам относятся: сброс, очистка экрана, установка курсора, установка типа курсора. К данным относятся сам текст который рисуется на экране.

  6. Е - эта нога сигнализирует ЖКИ что данные/команду можно забирать с шины данных. Обычно такую управляющую ногу называют “строб”. Такая нога всегда есть когда данные передаются по параллельной шине (одновременно 2-8 бит информации).

  7. Катод и анод светодиодной подсветки.

Теперь когда мы описали ноги ЖКИ мы можем рассказать о самом низком уровне передачи данных из прерывания на ЖКИ.

Как мы говорили выше, есть у нас прерывание переполнения счетчика 0, которое настроено так, что оно срабатывает 7812,5 раз в сек. Если мы передаем 80 символов и каждый символ передаем за 2 вызова прерывания по 4 бита, то мы можем перерисовывать ЖКИ 7812,5/160 раз в секунду. Но это немного не так. На самом деле после передачи каждой буквы или команды иногда надо пропустить несколько вызовов прерывания, в зависимости от того, что мы передали, потому что ЖКИ надо дать время выполнить эту команду.

Существует 2 массива данных в моей программе:
V1[8] - низкие команды (понятные ЖКИ, из описания ЖКИ)
V2[80] - высокие команды (видеопамять) текстовое изображение ЖКИ.
Когда программа начинает передавать видеопамять в ЖКИ, она преобразует каждую высокую команду в несколько низких, понятных ЖКИ.

Вот самый низкий уровень передачи данных в ЖКИ. В этом кусочке программы происходит передача одной четверки (тетрады, половинки буквы или команды).

do{                             // Это одинарный цикл на весь ЖКИ для возможности в любой момент из него выпасть, используя break
  if(fv.h)break;                // В данный момент посылка на ЖКИ не работает
  if(wV1){wV1--; break;}        // Если низкая задержка то ждем следующего вызова прерывания
  if(iV1<nV1)                   // Если список низких команд не выполнен
    {
    x=V1[iV1];                  // Это низкая команда для отражения ее на ногах проца
    if(x&0x80){wV1=x<<1;}       // Если старший бит установлен, значит это низкая задержка
    else                        // Все остальные низкие команды
      {                         // превращаются в сигналы на ногах процессора
      sch_RW=bool(x&64);        // Устанавливаем режим чтение/запись
      sch_RS=bool(x&32);        // Команда или данные
      sch_DB4=bool(x&1);        // Младший бит квартета
      sch_DB5=bool(x&2);        // Средний бит квартета
      sch_DB6=bool(x&4);        // Средний бит квартета
      sch_DB7=bool(x&8);        // Старший бит квартета
      sch_E=bool(x&16);         // Фиксация данных (строб)
      }
    iV1++;                      // Переходим к следующей низкой команде
    if(iV1>=nV1){iV1=0; nV1=0;} // Если последняя низкая команда, все обнуляем
    }
  if(iV1<nV1)break;             // Если список низких команд не выполнен все остальные видеодела
                                // делать не надо в этом цикле вызова прерывания

sch_RW, sch_DB4, sch_DB5, sch_DB6, sch_DB7, sch_E - это название из схемы соответствующее ножке процессора и ножке ЖКИ. И при вписывании в этот бит 1 или 0 происходит появление +5в или 0в на соответствующей ноге в схеме. Вот оно чудо! Программа меняет величину напряжения на ноге процессора. Информация управляет реальным миром! Дух управляет телом! Бог есть!

Все остальное это детали.

R2D2

Итак программист в разных местах программы обозначает какой шаблон он хочет видеть, подпрограмма рисования шаблонов нарисует шаблон в видеопамяти, прерывание выведет видеопамять на ЖКИ. Для того чтобы экономить время процессора и ЖКИ предусмотрены механизмы не полной прорисовки шаблона, а только изменяемой части и не всех строчек разом, а только указанных. Прерывание и подпрограмма рисования шаблонов один раз написаны и отлажены, далее программист творчески манипулирует простым вызовом шаблонов в нужных местах программы. На этом мы закончим описывать механизм вывода информации на ЖКИ. Более детально смотрите сам текст программы.

Урок №19 (структуры)

В нашем ЖКИ есть 2 канала зарядки. Так уж сложилось, что у процессора оказалось достаточно свободных ножек, чтобы подключить 2 канала, не привлекая дополнительной логики. И при выборе “2 канала” или “1 канал и балансир” выбор пал на первый вариант. Два абсолютно одинаковых канала управляются одной программой. Нужно написать подпрограмму работы с одним каналом и передавать ей номер канала. Одним выстрелом двух зайцев. Пишем одну подпрограмму, которую применим 2 раза. Разве это не халява.

Для того чтобы это сработало, необходимо описать структуру каждого канала. В языке Си есть такое понятие “структура данных” - это удобный механизм описания некоторого объекта, обладающего определенными свойствами. У нас таких объекта два, а структура у них одинаковая. Один раз описываем структуру и используем ее для 2х каналов. Структура каждого из каналов - это все переменные, описывающие свойства канала, это все рычаги управления каналом, это все данные о канале, о его состоянии.

// Вот таким образом мы упрощенно описываем структуру канала
// Слово struct - это служебное слово Си
// Слово CHANNEL - это произвольное слово, теперь его можно
// использовать как тип для описания представителей структуры
struct CHANNEL
  {
  float I;
  float V;
  float T;
  };

// А в этом месте мы описываем наши два канала и каждый имеет одинаковую
// структуру как указано выше
CHANNEL Ch1, Ch2;

// А вот так мы будем работать со свойствами канала
Ch1.I=5;
Ch2.V=Ch1.V;
Ch1.T=GetTemperatur();

А вот описание настоящего канала:

struct CHANNEL              // Структура, описывающая состояние канала в любой момент времени
{
BYTE A;                     // Номер подключенного аккумулятора из списка
long STime;                 // Время старта канала в десятых долях секунды (для вычисления полного времени работы канала)
long WTime;                 // Время работы канала в десятых долях секунды
long CTime;                 // Максимальное время выполнения одного цикла зарядки или разрядки в 1/10 сек.
long SecsT;                 // Для определения скорости роста температуры в каналах
BYTE Cycl;                  // Текущий цикл (загружается при пуске канала)
        // 0000 - зарядка
        // 0001 - разрядить и зарядить 1 раз
        // 0010 - разрядить и зарядить 2 раза
        // 0011 - разрядить и зарядить 3 раза
float k0, k1, k2;           // Коэффициенты для расчета тока зарядки
float kr0, kr1, kr2;        // Коэффициенты для расчета тока разрядки
float kv0, kv1, kv2;        // Коэффициенты подстройки напряжения
float Rch;                  // Сопротивление шунта зарядки
BYTE f1;                    // Флаги алгоритма зарядки 1 (10й байт типа аккума)
BYTE f2;                    // Флаги алгоритма зарядки 2 (11й байт типа аккума)
BYTE n;                     // Количество последовательных элементов в аккуме (Кол-во послед. банок)
WORD
ftPause:1,                  // Вторая попытка по перегреву
h:1,                        // Зарядка для хранения
Zaryd:1,                    // В настоящий момент вообщето вцелом идет заряд иначе разряд
LZaryd:1,                   // Локально сейчас идет заряд (true-заряд, false-разряд)
                            // Для отслеживания режимов декристаллизации
C:1,                        // 0-Стоп 1-Старт
dT:1,                       // 0-Скорость температуры < 2гр./мин. 1-больше (перегрев)
DP:1,                       // Динамический 0-Нет дельтапика 1-Есть дельта пик
DPs:1,                      // Статический 0-Нет дельтапика 1-Есть дельта пик
First:1,                    // 1-только что запустили 0-давно работаем
Stop:1;                     // 1-получен приказ на выключение 0-не получен
BYTE Speed;                 // 0-медленно 1-нормально 2-быстро
WORD Pause;                 // Необходима для стабилизации на тл494 заказанного тока в 1/10 сек.
BYTE Fasa;                  // Фаза заряда (0-нулевая, 1-основная, 2-капельный или струйный)
WORD WW;                    // Скорость роста и убывания тока изначально
WORD W;                     // Скорость роста и убывания тока может убывать при непревышении напряжения
WORD i;                     // Установленный ток (0-0xfffe) - величина ШИМ
WORD iDec;                  // Установленный ток (0-0xfffe) - величина ШИМ для локального процесса разрядки
BYTE sDec;                  // Стадия 0-Зарядка 1-Пауза 2-Разрядка 3-Пауза
BYTE pDec;                  // Переменная отсчета цикла декристаллизации от ch.PDes до нуля в 1/10 секундах
BYTE PDec;                  // Константа цикла декристаллизации из свойств типа аккума
BYTE TDec;                  // Константа цикла декристаллизации из свойств типа аккума
BYTE Imin, Imax;            // Плавное повышение тока в процессе зарядки
float I, V, T, Told;        // Реальные измеренные ток, напряжение и температура
float Tmax, TT;             // Абсолютная температура и скорость роста температуры перегрева
float Vst;                  // Напряжение статическое
float Vmax;                 // Используется при заряде для определения DP динамического
float Vmaxs;                // Используется при заряде для определения DP статического
float Vmin0;                // Минимальное напряжение для нулевой фазы
float Vmin1;                // Минимальное напряжение для первой фазы
float Vh;                   // Напряжение хранения
float dV;                   // Константа - величина дельтапика (во второй фазе используется для капильного заряда)
float II;                   // Принято решение установить такой ток
float IIDec;                // Принято решение установить такой ток для локального процесса разрядки
float VV;                   // Принято решение не превышать такое напряжение при заряде
float Z;                    // Емкость Ач
float InZ;                  // Полученный интегральный заряд Ач подсчитывается еже 1/10 секундно (при выводе надо поделить на 36000)
BYTE Msg;                   // Код остановки канала
          // 0 - Код не предусмотрен (канал не запускался)
          // 1 - Ток упал до нуля при заряде
          // 2 - Превышение лимита времени
          // 3 - Превышение температуры
          // 4 - Превышение скорости роста температуры
          // 5 - Превышение емкости заряда в 1.2 раза
          // 6 - Напряжение достигло минимума при разряде
          // 7 - Провисло питание до 11 вольт
          // 8 - Обнаружился дельтапик
          // 9 - На заряжаемом аккуме напряжение ниже минимального "это неправильно"
          // 10 - Зашкал тока
          // 11 - Зашкал напряжения
          // 12 - Остановлен пользователем
          // 13 - Напряжение ниже нулевой фазы
          // 14 - Напряжение достигло Vh хранения
          // 15 - Ошибки в настройках типов
          // 16 - Напряжение достигло нужного уровня при заряде
          // 17 - Перегрев схемы
}Ch1, Ch2;

А вот так в главном цикле вызывается обработка каждого канала.
Ищите Ch1 и Ch2.

// Главная программа запускается по ресету
int main(void)
{
fc=255;
#include "init.cpp"       // Инициализация всего
#include "zagruzka.cpp"   // Загрузка начальных переменных
while(true)
  {
  wdt_reset();            // Сброс собаки
  #include "sh.cpp"       // Прорисовка шаблонов
  #include "test.cpp"     // Рассчитываем все переменные по каналам
  if(iK1!=iK0)            // Если есть необработанные кнопки рисуем меню
    {
    #include "menu.cpp"   // Обработка кнопок
    }
  TestMainParam();        // Если какой нибудь канал работает или тестирование
  if(Ch1.C)Go(Ch1);       // Если запущен канал 1
  if(Ch2.C)Go(Ch2);       // Если запущен канал 2
  #include "uart_in.cpp"  // Если надо чтонибудь получить с COM-порта
  #include "uart_out.cpp" // Если надо чтонибудь послать на COM-порт
  }
}
R2D2

Как вы увидели в структуре канала есть переменные в которых отражен желаемый ток и желаемое напряжение. Канал не сразу реагирует и выставляет нужное напряжение и ток на аккумуляторе, а с определенной скоростью реакции. Кроме того в настройках канала отражены текущие фазы зарядки. А также отражены результаты, достигнутые каналом. Как это все учесть?

Основная процедура управлением зарядкой/разрядкой Go() действует следующим образом:

  1. Если это первый запуск Go, то загружаем в настройки канала все что мы знаем об этом аккуме и его химии, обнуляем все что надо и ставим по умолчанию все что надо. Играем музыку старта.
  2. Проверяем все экстренные проверки (зашкалы, проверка на вшивость и т.д.)
  3. Если проверки показали что надо остановиться, то останавливаемся и фиксируем причину. Играем музыку конца.
  4. Если необходима задержка выскакиваем из программы столько раз, пока не истечет задержка, а вызывается Go() более 100 раз в секунду.
  5. Обработка фазы декресталлизации, установка мелкопоместных задач по току, задержки опять ток разрядки или зарядки.
  6. Опять длинная пауза по времени, которая может завершить Go() в этот раз.
  7. Расчет всех токов и напряжений из данных АЦП. Проверка всех условий зарядки, разрядки, достижения требуемых напряжений и токов. Главная цель проверить не конец ли это.
  8. Если закончилась очередная фаза тренировки, то переинициализация для запуска нового цикла.
  9. А вот теперь в зависимости от того что есть в наличии и того чего хочется и от допустимой скорости реагирования, выставляем новый ток через процедуру выставления тока.
1 month later
R2D2

Вышла новая прошивка и макропрога к ней. avrcpp.narod.ru (Все заинтересованные дайте ваших глюков пжлста).
Статью про Си почистил и причесал кому надо там же.