Перейти к содержанию

ARV

Members
  • Постов

    2 802
  • Зарегистрирован

  • Посещение

  • Победитель дней

    19

Записи блога, опубликованные ARV

  1. ARV
    Привычка изобретать велосипеды не отпускает... Стало мало мне множества доступных терминальных программ для общения по последовательному порту со всякими железочками. Когда что-то делаешь с GSM-модемом, или с GPS-модулем, да чего там - даже с MP3-модулем, приходится многократно посылать команды в эти устройства и заниматься разбором ответов, от них приходящих. И, хотя до сих пор эти вопросы как-то решались, меня не оставляло чувство неудовлетворенности: слишком как-то все не просто, не элегантно, кривовато и не удобно. Ну, вот и решил сделать прямо, элегантно и удобно.
    https://cloud.mail.ru/public/Audn%2F95Vd3Xz1j
    Вот по этой ссылке лежат файлы бета-версии моей терминальной программки. Скачивать надо или все сразу, или, если трафика жалко, только EXE-шник. В последнем случае интерфейс будет на английском. 
    Чем моя программка лучше прочих? Ну, во-первых, она может практически все, что могут другие аналогичные программы. Во всяком случае всё то, что реально за долгие годы радиолюбительства мне было нужно делать. Во-вторых, она обладает рядом мелких приятностей, например, может отображать принятые и отправленные данные разным цветом.
        
    Мелочь, а во многих других терминалках приходится вникать, что есть мои, а что чужие данные, особенно если они не текстовые, а бинарные...
    Еще во время приема текста можно увидеть и невидимые символы, т.е. коды которых меньше кода пробела (табуляции там всякие и прочие переводы строк). Причем увидеть можно в разном представлении, по желанию. Например, вот так, как любят паскалисты (я из них):

    А еще мой терминал умеет находить все имеющиеся в системе последовательные порты без того, чтобы в них срать (прошу пардону за мой французский). Большинство известных мне терминалок либо требуют вручную указать имя порта, а потом в лучшем случае ругнутся, что он отсутствует на самом деле, либо перебирают варианты сами, пытаясь открывать все подряд. Такие переборы почти всегда сопровождаются тем, что в "лишние" порты могут уйти какие-то данные, изменятся уровни на сигнальных линиях порта и т.п. неприятные явления могут возникать. Вот когда-то я делал приспособу, которая использовала сигнал RTS для включения настольной лампы по таймеру, и если кто-то вздумал бы определять наличие COM-порта путем его открывания-закрывания, лампочка бы стала мигать из-за этого.
    Моя же программка может найти даже порты с именами, отличными от COM* (виртуальный нуль-модем com0com умеет такие создавать с легкостью), и при этом не гадит во все порты подряд. Кстати, работать с COM97 для моей программы так же просто, как и с COM1, а многие популярные терминалы вообще дальше COM4 не видят...
    И еще один нюансик: моя программка по умолчанию сразу открывает выбранный порт, не требуя всяких кнопок "Подключить" и т.п., чем грешат абсолютно все другие терминалы. Но ведь если вы собрались с портом работать, он должен быть открыт - зачем же еще кнопки добавлять? Но, случаи бывают разные, и закрыть порт вы всегда сможете.
     
    Чтобы повторить то, что уже было отправлено, не надо "программировать" какие-то макросы, настраивать кнопки или писать скрипты - всё, что вводится пользователем, попадает в историю и выглядит, как кнопка. Нажмешь на такую кнопку - и текст уйдет повторно. Часть данных на кнопке не видно, но что именно там "увнутре" (не неонка!), ясно по начальному кусочку (см. скриншот выше).
    Кстати, заметили, что окошки можно склеивать в одно окно? Было окно текста без истории, а на тут стало с историей... Так это еще и не все - можно понаприклеивать их сколько угодно и куда угодно, забубенив интерфейс по-своему желанию! Хотите, чтобы все-все-все возможности программы были видны в одном окне? Пожалуйста:

    На скриншоте вы видите 99% всех окошек, что существуют в программе, и все они склеены в одно большое "главное". Оставшийся процент - это окна плагинов, которые могут быть, а могут и не быть.
    Вам кажется, что такой интерфейс слишком перегружен? Можно расчистить его, свернув "лишнее":

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

    Если надо отправлять данные пакетами, то вводить их тоже можно предельно комфортно в таком оконце:

    Это не фиксированные поля ввода, а заданные вот такой строкой "формата": Преамбула:\t%2xSender:\t\t%1xReciever:\t\t%1xCommand:\t%1dCRC:\t\t%2xEnd:\t\t%2x
    Если изменить эту строку, например, так: Start:\t%2xSender:\t\t%1xReciever:\t\t%1xCommand:\t%1d, то и поля ввода данных тоже изменятся, соответственно, и пакет тоже будет другой:

    Внимательный читатель наверняка заметит закономерную связь между полями ввода в окне и строкой формата: %1x означает поле ввода 1 байта в шестнадцатеричном формате, а %1u позволит ввести тот же байт в виде десятичного uint8_t. Само собой, int16_t вводится в поле %2d, а uint32_t в поле %4u. Можно и в двоичном виде: %4b. И так далее.
    То есть в программе встроен редактор пакетов данных практически на все случаи жизни. Много ли известно альтернатив?
    А еще ведь есть система фильтров! Каждый байт, поступающий на вход, может быть подвергнут разным проверкам, и пройдет на выход, т.е. появится в окне принятых данных, только в том случае, если все требования фильтров будут соблюдены. Например, классический старт-стопный пакет фиксированной длины выделить очень просто, при этом можно видеть только пакеты, в которых то или иное поле имеет конкретное значение, и не видеть остальные. Можно выделить только строки, начинающиеся с определенной последовательности символов, и т.п.
    Без ложной скромности скажу, что благодаря фильтрам придумать формат презентации данных, который может потребоваться в работе, и который бы не смогла показать моя программка, будет сложновато. При помощи фильтра форматирования, работающего на том же принципе строки-формата, как уже было показано, поступающие на вход данные можно "смешивать" с текстовыми дополнениями, получая очень наглядную картину. Форматировать можно по 8, 16 и 32 бита и представлять это в десятичном (со знаком или без него), двоичном, восьмеричном или шестнадцатеричном виде. Формат строк для составления пакета на отправку и для просмотра принимаемого пакета, практически одинаков (разница в том, что в строке формата символ \n ведет себя по-разному). Вот пример того, как можно наблюдать приходящие байты в двоичном представлении:

    Заметили окошко фильтра c параметром Format? Вот я его меняю и - вуаля! - совсем иной коленкор:

    И все эти фильтры реализованы в виде плагинов, т.е. не нужны - можно удалить и сэкономить несколько сотен килобайт места на диске. Понадобятся - можно в любой момент скачать и добавить.
    Вот такой терминал моей мечты получается. Пока не без багов, но я близок к завершению. Помощников бы в поиске ошибок...
    Такие дела...
    P.S. скриншоты разные, потому что на разных компах делались, на одном еще Win7, на другом уже Win10. И -433 отфильтрованных байта не баг, а фича: форматирующий фильтр не режет, а дополняет данные, вот и выходит, что пришел 1 байт, а ушло 10, значит, отфильтровалось -9  
  2. ARV
    Ну а как еще можно перевести название сайта godbolt.org?
    Очень занятный сайт для программистов-любителей, рекомендую. Позволяет немедля увидеть, как выглядит та или иная конструкция на одном из 19 языков в ассемблерной реализации. Любопытно поизучать, как опции оптимизации влияют на результат, например. Можно сравнить код, генерируемый разными версиями одного и того же семейства компилятора или сравнить код разных компиляторов... Видно, какие ассемблерные команды соответствуют той или иной строке кода... Короче, занятное получается путешествие внутрь кода онлайн. Главное, все происходит быстро, заметно быстрее, чем если делать это на локальном компиляторе, и уж точно много быстрее, если делать то же самое на разных версиях компиляторов.
    Подерживается огромное коичество платформ: от 8-битного AVR до 64-битных ARM, в том числе под винду (включая CE) и линух.
  3. ARV
    Как же меня достали разработчики современного программного обеспечения под Андроид!
    Модель бизнеса "продажи превыше всего" не позволяет делать ПО, которм можно пользоваться - это я вам категорически заявляю. В тот самый момент, когда тебе нужна какая-то функция, о которой ты знаешь, что она есть и даже специальное приложение для этого заранее ты установил, вдруг оказывается, что "Приложение остановлено" или "Приложение не отвечает" - и что делать?! А вся твоя вина в том, что ты не покупаешь каждые 3 месяца новый телефон с самой последней версией Андроида, с объемом озу на 1-2 гигабайта больше предыдущего, с быстродействием на 1-2 гигагерца выше и так далее.
    Все, тебя лишают возможности применить СМАРТфон по назначению, т.е. использовать его УМНЫЕ функции. Умные - значит полезные. Дебилы в разработке ПО решили, что новые иконки, цветовые палитры, анимации меню - все это умные функции, а какое-то тупое редактирование файлов, просмотр PDF или работа с таблицами - это глупые функции. Именно поэтому любой смартфон до поседнего продолжает заниматься анимацией, в то время как от шлака вроде Excel или приличного почтового клиента можно и отказаться. Кому вообще приходит в голову на СМАРТфоне не играть, а работать?! Что вообще эти людишки себе возомнили?! Сматрфон за пару-тройку зарплат нужен для умных занятий умных людей - поиграть в 3D-монстров каких-нибудь, котиков в Вконакте полайкать, в фейсбук тарелку каши запостить... Все умные люди этим занимаются, потому они умные и богатые, что могут себе позволить обновлять сматрфон ежемесячно под новые версии анимации 3D и еще более широкие экраны для котиков.
    А дуракам, которые письма со смартфонов пишут СЕРЬЁЗНЫЕ, или работают с данными, или, не дай бог, расчеты какие делают, так и надо - пусть на арифмометрах считают. Мыслимое ли это дело - применять для этого СМАРТФОН?!
    А ведь всего-то хотелось малого: просто открыть таблицу, просто добавить в нее новые данные и сохранить в облако.
    Скажите мне, разработчики ПО, если уж вы так решили, что каждая программа должна работать только с одним-двумя наперед заданными облаками для хранения файлов (главное - не удобство пользователя, а конкуренция), то почему бы вам не делать вход в это ваше облако автомаически? Почему вы заставляете меня каждый раз видеть дурацкую надпись "время предыдущей сессии истекло, необходимо осуществить вход"? Истекло время, нужен вход - так делайте его автоматически! Храните пароль в куках или где там его положено хранить, берите его оттуда и делайте вход! Гугл делает все автоматически по своей учетке - значит и все другие могут делать. Почему не делают?! 
    Скажите мне, разработчики ПО, куда ваши приложения девают память?! Почему вместо открытия файла и работы я вижу сообщение "недостаточно памяти"?! Почему неделю назад её было достаточно, а теперь - нет?! Почему без обновления ваше приожение отказывается работать?! Что поменялось с прошлого запуска, что вот уже невозможно снова открыть и обновить тот же файл?! Вы обновление выпустили? А я вас просил об этом? Я хочу ПОЛЬЗОВАТЬСЯ функциями вашего приложения, а не обеспечивать этому приложению комфортные условия для сущствования на моем смартфоне! 
    Мало того, что этот самый Excel занимает больше 300 мегабайт памяти в хранилище (боже, куда столько-то?!), так он еще и не работает. Чтобы открыть и отредактировать ЛОКАЛЬНЫЙ файл, я должен ВОЙТИ В ОБЛАЧНУЮ УЧЕТНУЮ ЗАПИСЬ - это какому гению на ум пришло так сделать?! Ну не хотите вы задаром давать работать с ВАШИМ облаком - не надо, подавитесь. Почему с ЛОКАЛЬНЫМ файлом не даете работать без вашей БЕСПЛАТНОЙ учетки?! Суки, если вам надо денег - так потребуйте оплаты открыто, подлянки-то зачем устраивать?! Да и вообще - если учетка бесплатна, то какой смысл вынуждать её заводить вообще?! 
    Это не прогресс, это отстой полный! И полная безнадежность - альтернатив просто нет. От слова совсем. Приложение или на 101% состоит из рекламы, или не работает на 101%. На этом все богатство выбора исчерпано. Будущее наступило - шагнуть без "новых технологий" и шагу нельзя, но и новые технологии вяжут ноги и руки так, что можно только стоять на месте.
  4. ARV
    Смастерил вот на скорую руку такую "цветомузыку":
    Снимал на смартфон, а музыка играла с другого смартфона... Не ожидал, что музыки почти не слышно будет. Зато видно неплохо.
    Электретный микрофон, шесть резисторов один конденсатор и три светодиода, а так же главное - микроконтролер attiny13, - вот и весь рецепт. Чувствительность маловата, смартфон почти вплотную к микрофону придвинул, ну так и громкость там не ватты... Если музон пускать через нормальные колонки, наверное, будет достаточно чувствительности. 
    Планирую еще добавить управление вращением моторчика, который крутит "хрустальный шар", просто мост на MOSFET-ах еще не спаял, и не придумал алгоритм управения вращением... Потому пока что так. В тиньке места еще много, так что буду подстегивать свою фантазию...
  5. ARV
    Поскольку кормят нас повара и санитары, решил повысить уровень обслуживания, сделав меню.

    2020-05-01 21-36-46.mp4 То есть, вдохновившись всем известным MicroMenu, сделал свой вариант "библиотечки" дл создания меню в проектах на микроконтроллерах AVR. На видео показаны основные фишки моего варианта:
    Скроллинг меню, если на дисплее оно целиком не вмещается. Выделение активного пункта в данном случае сделано "прочеркиванием" двумя линиями, но, естественно, можно сделать, как угодно. Выполнение команд меню без завершения самого меню (с завершением тоже можно). Наличие в меню пунктов со значениями параметров. Выделенный параметр сразу может редактироваться в заданных пределах. Возможность вложенных меню. Есть еще ряд фишек, но на видео они не видны... Вот так выглядит код, создающий структуру меню, показанную на видео:
    // главное меню // id name parent prev next func|subm property MENU_CMD( mm_1, "BEEP", NONE, NONE, mm_2, beep); MENU_PROP(mm_2, "TEST", NONE, mm_1, mm_3, NULL, PROP_U8(u8, NOSTORE, 0, 12)); MENU_PROP(mm_3, "MOTOR", NONE, mm_2, mm_4, motor, PROP_BOOL(motor_state, NOSTORE, sf("STOP"), sf("RUN"))); MENU_SUB( mm_4, "SUBMENU", NONE, mm_3, mm_5, sm_1); MENU_PROP(mm_5, "TEMPERATURE", NONE, mm_4, mm_6, NULL, PROP_I16(vfr, NOSTORE, -20, 20)); MENU_CMD( mm_6, "LOAD", NONE, mm_5, mm_7, load); MENU_CMD( mm_7, "SAVE & EXIT", NONE, mm_6, NONE, save); // субменю MENU_CMD( sm_1, "STOP DEMO 1", mm_4, NONE, sm_2, done); MENU_CMD( sm_2, "STOP DEMO 2", mm_4, sm_1, sm_3, done); MENU_SUB( sm_3, "STOP SUB", mm_4, sm_2, NONE, ssm_1); // субменю 2 MENU_CMD(ssm_1, "SUB STOP DEMO 1", sm_3, NONE, ssm_2, done); MENU_CMD(ssm_2, "SUB STOP DEMO 2", sm_3, ssm_1, ssm_3, done); MENU_CMD(ssm_3, "SUB STOP DEMO 3", sm_3, ssm_2, NONE, done); Кто работал с MicroMenu, тому подобный способ описания структуры меню будет знаком. Как видите, главное отличие в том, что пункты имеют разный размер, т.е. количество полей разное. Это позволяет экономить память в некоторых случаях. С другой стороны, добавилась возможность добавить новое поле, которое я назвал по-русски свойством, а по-английски property. Пункты меню, имеющие такое поле, позоляют видеть значение свойства и менять его интерактивно - на видео вы это видели.
    Реализована поддержка следующих свойств:
    целое число (со знаком и без оного, 8 и 16 бит), причем число можно выводить в десятичной или шестнадцатеричной форме; булево значение (т.е. логическое) - оно отображаетс парой соответствующих строк текста; выбор одного варианта из нескольких - каждый вариант так же представляется соответствующим текстом. Относительно просто добавить поддержку и 32-битных чисел... Но надо ли? Вводить их значение при помощи кнопок "навигации" - это удовольствие не из приятных... При желании это можно реализовать по-старинке - написав свою функцию и назначив её нужному пункту меню...
    Таким образом, моя система меню сразу позволяет решить следующие задачи, практически всегда присутствующие в проектах на МК с ЖКИ:
    организовать процесс "настройки" всех параметров при помощи интуитивного меню; сохранять автоматически в EEPROM все свойства, используемые в меню, и загружать их оттуда. То есть теперь достаточно описать пункты меню вместе с соответствующими свойствами, выделить место под переменные для этих свойств (эти переменные затем использовать по назначению в программе), выделить место в EEPROM для сохранени значений этих свойств (все это указывается при создании свойства в пункте меню), и в нужный момент вызвать созданное меню на дисплей - все остальные заботы я уже решил!
    Думаю, многие знают, что не всегда просто решить, напрмер, интерактивную регулировку яркости подсветки дисплея. Часто делают так: в меню выбирают пункт, по активации которого выводится редактор яркости (то ли шкала, то ли число), а потом, когда ввод нового значения яркости завершен, работа с меню завершается, и задается новое значение яркости. Так вот, в моей системе яркость может меняться одновременно с изменением соответствующего свойства! На видео вы могли видеть, как включается и выключается "моторчик" - точно так же можно выполнять какую-то функцию при каждом изменении числа. То есть реализован полностью интерактивный способ изменения всех свойств! И прощайте странные цифры, обозначающие включение или отключение каких-то режимов - здравствуйте понятные слова!
    Надеюсь, моё меню сможет удоветворить самый изысканный вкус любого гурмана...
  6. ARV
    Как ни посмотришь, так все всегда в гонке... Выше, больше, быстрее, потом еще больше, еще выше, еще быстрее... Мегагерцы, Гигагерцы... А потом нервные срывы и - милости просим к нам в гнездо, в комнату с белым потолком, с правом на надежду! И это еще хорошо, если так повезет...
    А кому это надо?
    Мне, например, не надо. Свой последний проект на микроконтроллере AVR я сделал на тактовой частоте в 32768 Гц. Ни больше, ни меньше, а 32 килогерца.
    Само собой, это вышло не специально... Просто решил делать часы на микроконтроллере, в котором нет аппаратного таймера специально под организацию часов реального времени... Ну и самым простым оказалось перевести весь проект на тактирование от часового кварца. 
    А чего такого? Это самая низкая из доступных "по умолчанию" частот (даже тактирование от генератора WDT и то на большей частоте получается - порядка 100 кГц), при том стабильная, ибо кварц. 
    И вышло так, что практически никаких ограничений в процессе написания прошивки я не испытывал в связи с таким низкочастотным тактированием. И stdio в полный рост применял (в смысле использовал функцию sprintf), и прерывания, и умножение/деление (МК у меня без аппаратного умножителя - тинька), и вообще ни в чем себе не отказывал. И оно таки работает! И неплохо работает, скажу я вам: потребление в "ждущем" режиме, т.е. когда индикации нет (я использую LED-индикаторы, потребление которых считать надо отдельно от МК, который просто считает время) примерно 31 мкА! Индикатор, кстати, тот самый, теплый ламповый:

    Так что все эти гигагерцы ваши меня совсем не возбуждают. Нам и на килогерцах неплохо живется. Куда спешить-то? Зачем?
    Как обычно, сам проект будет презентован позже - заказываю платы в Китае...
  7. ARV
    Наша больничка перепрофилировалась на прием клиентов с COVID-19, поэтому основной контингент сейчас переведен на домашнее лечение. Ничего удивительного, сейчас многие самоизолировались и делают вид, что работают удаленно. А некоторые и в самом деле работают.
    Вот и мне пришлось взять домой служебный ноутбук, чтобы продожать выполнять свои рабочие функции не вставая с кровати. Но и домашний ноут никуда не делся - на нем я иной раз что-то программирую, или разрабатываю платы и тому подобное.
    Если у кого на столе больше одного компьютера, ему наверняка знакомы проблемы двух (не дай бог трех!!!) клавиатур и двух (см ранее - не дай бог больше!!!) мышек... Спустя несколько минут такой работы на двух компьютерах одновременно начинаешь жалеть, что ты не повар из Футурамы, и у тебя не 4 руки... Однако, есть выход из этого исхода! И называется он Мышь без границ.
    Устанавливаем на всех домашних и диких компьютерах эту программку, запускаем, настраиваем (при первом запуске будет запрос о том, установлены ли на других компьютерах эти утилиты - надо правильно ответить, и настрока произойдет почти автоматически).

    Интерфейс программки состоит из трех закладок - первая на картинке слева. Можно "повесить" на одну клавиатуру и мышку до 4-х компьютеров, подключенных к одной сети. На том компе, на котором установка утилиты была первой, вводим секретный ключ, который потом вводим на остальных. Собственно, это все - можно работать.
    Вторая закладка выглядит так:

    Вот как на картинке, так можно и оставить галочки. В нижней части перечислены горячие клавиши для управления. На мой взгляд, не очень удобно, что нельзя переназначить "префиксные" кнопки - они всегда Ctrl-Alt или Ctrl-Shift, причем для разных хоткеев по -разному. Но привыкнуть можно, потому что требутся они редко.
    Ну, установили мы все это на все компы, и что? А вот что: теперь вы работаете на своем домашнем компе, а рядышком стоит служебный ноут - бац! - там приходит почта. Вы, не выпуская из рук мышку, передвигаете курсор вбок (см. первую картинку настроек: local mashine - это ваш комп, а служебный или слева или справа, вот в ту сторону курсор и двигаем), и - опанки! - он "переезжает" на служебный ноутбук! Вы открываете почту, читаете её, и, не переставляя руки с "домашней" клавиатуры тут же пишите ответ! С клавиатуры домашнего компа пишите ответ на служебном ноутбуке!!!
    Разумеется, можно и наоборот - все компы равноценны, и клава/мышка каждого может "подключаться" к любому другому. 
    Помимо такого разделени клавиатуры и мышки можно еще получать скриншоты "чужих" экранов, использовать "общий" (точнее, пользоваться чужим) буфер обмена между компьютерами, и даже перетаскивать файлы с одного на другой!!!
    Единственный, но небольшой, недостаток этой программки заключается в англоязычном интерфейсе и отсутствии справочного файла. Хоть эта утилита сделана не кем попало, а Microsoft-ом, даже на их сайте все доступно только на английском.
    Кстати, эта программка начинает работать и с экрана входа, т.е. даже пароль можно ввести на другом компе со своего. И, если захочется, послать на другой комп Ctrl-Alt-Del.
    В общем, теперь домашняя работа не в тягость, а в радость. Не болейте!
  8. ARV
    Почему-то современные вещи не создают ощущение теплоты... а старые (не все, конечно) обладают какой-то притягательной элегантностью, вызывают необъяснимое желание их потрогать и даже заполучить в собственность. Что это - приближающаяся старость или неоспоримый факт?
    На полках магазинов появляются а-ля ретро поделки, этакий закос под винтаж

    Когда прохожу мимо - пробирает дрожь отвращения. А на фотографии "настоящих" древностей засматриваюсь... Стало модно в кафе и т.п. для интерьера расставлять подобные вещи - если попадаю в такое место, забываю есть и пить, глазею по сторонам.

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

    Не имею понятия, зачем они мне нужны, но заполучил-таки их себе, как ранее запасся другими HCMS-2913

    Почему-то просто приятно от того факта, что они у меня есть... Жаль, нет идей, куда их можно реально применить. Под такие раритеты и поделки должны быть соответствующие, делать какой-нибудь "MP3-плейер" с подобными индикаторами кощунство какое-то... А мысль уже зажата в тиски современных тенденций...
    Душа просит тепла и ласки, а жизнь шершавит ее наждаком практичности и функциональности.
     
  9. ARV
    Вынесенный в заголовок записи аформизм Козьмы Пруткова поднимает важный вопрос сортировки групп (чего угодно) по бинарному критерию.
    Поясню примером. Предположим, мы хоитм расположить учеников в классе по росту на две группы - высокие и низкие. Предположим, мы справились, и слева по росту стоят у нас Вася, Петя, Гриша и Маша - высокие ученики, а слева низкие, тоже по росту Аглая, Виссарион, Евстигней и Глафира. И тут приходит Козьма Прутков и задает сакраментальный вопрос: а вот Маша, которая самая маленькая в группе высоких и Аглая, которая самая высокая в группе низеньких - они в "своих" группах находятся? Ведь самый низенький по правилу должен быть в группе низеньких, а самый высокий - в группе высоких... А?
    Или вот еще пример. Превышение скорости на 20 км/ч карается штрафом. А карается ли штрафом превышение на 19, 999999999999 км/ч? А если 20,0000000001 км/ч - карается или нет?
    Или еще: изменение цены на 1 копейку - это рост цен или нет? А если цена уменьшилась на 1 копейку, можно ли считать, что жизнь налаживается и все дешевеет?
    И установка "допусков" проблему не решает от слова вообще: если штрафной порог превышения скорости задан как 20±1 км/ч, то является ли нарушением превышение в 20,99 км/ч?
    От подобных мыслей не то, что над гнездом кукушки можно взлететь, но и самой кукухой можно поехать в это самое гнездо...
    Однако, подобные философские проблемы беспокоят только математиков - они даже целую область этой науки придумали, "нечеткая логика" и "множества с нечеткими границами". А на практике мы всегда имеем дело с волюнтаризмом тех, кто проверяет нашу работу. Именно контроллер ОТК решает, в допуске ли ±0,01 мм находится деталь, выточенная токарем с отклонением в 0,01001 мм, или нет. Инспектор ГИБДД ни секунды не медля определит, было ли превышение скорости или нет, а прокурор не задумываясь начнет шить срок за взятку в 1 копейку. Или не начнет. Или...
    Короче, пора на укол...
  10. ARV
    Силовая техника - это не для слабаков... В прямом смысле - не укупишь компонентов. А если укупишь - так потом сидишь на них, как Кощей на злате, и чахнешь... 
    Накопилось у меня много всякой силушки: транзисторы IGBT и MOSFET, да не какие-нибудь, а ампер этак на 100 и напряжение с киловольт... Само собой, разные, некоторые и послабее, а некоторые и посильнее. И диоды им под стать.
    UFB120FA40 - Ультрабыстрый мощный диод 400В 120А - как вам такое?
    Или вот: PM100CVA120 - IGBT трехфазный мост 1200В 100А

    Ну а мелочёвки всякой вроде IRG4PSH71KD, IRG4PF50WD и т.п. и подавно... И драйверы. Помните еще фирму такую International Rectifier? Были фирмы в наше время... выпускали всякие микросхемки с плавающим питанием для управления мостовыми высоковольтными инверторами типа IR2113 и т.п. У меня их - ну просто завались всяких! Ну как завались... штук по 5-6 разных типов, всего разновидностей 5 наберется. И нижнего плеча, и верхнего, и обоих сразу. И с мертвым временем унутре, и без него, с живым то есть. И даже с оптической развязкой драйвер HCNW3120 - может, кто тоже слыхал.
    Все настоящее, не с алиэкспресса, в давние времена куплено, когда доллар был еще не по 60 рублей, а по ... уж и не помню, почем он тогда был... склероз. А что вы хотите: время идет, IRF уж нет как лет 10, иные и не знают, что была такая фирма... А силовые приборы все еще лежат ждут... Чего ждут... не понятно.
    Перевелись на Руси богатыри, никому силушка не нужна. Ни продать, ни подарить. Уж как я старасля - никто не желает.
    Ну и вот что со всем этим делать-то? Неужто на мусорку дорога?!
     
  11. ARV
    Продолжая свой полет, неожиданно сделал давно задуманную, да почему-то постоянно откладываемую на потом, штуку... А именно: параллельный опрос нескольких термодатчиков семейства DS18x20.
    Дело в том, что у этих датчиков в качестве плюса технологии позиционируется обращение по уникальному адресу, что позволяет повесить на 2 провода хоть сотню датчиков и с каждым работать индивидуально. Плюс-то это плюс, да, как любой плюс, состоит из двух минусов (один вдоль, другой поперек).
    Последовательный опрос несколких датчиков, хоть по адресу хоть без увеличивет общее время опроса пропорционально количеству датчиков. В частности, в моих личных играх опрос 8 датчиков последовательно затягивался почти на 100 мс. С учетом того, что примерно половина этого времени будет требовать запрета прерываний (кусочками по 65 мкс примерно), такая длительность выглядит не очень хорошо.
    Кроме того, при "адресной" работе даже с тремя датчиками возникает проблема иного рода, так сказать, верхнеуровневая. Устройство должно как-то опознавать назначение каждого датчика по его адресу - этот измеряет температуру на улице, этот - в помещении, а этот - воду в системе отопления. И как вы себе представляете процесс присваивания "назначений" адресам этих датчиков? Сами по себе датчики никакой маркировки о собственном номере не имеют, т.е. на глаз их отличить невозможно. И получается геморрой. А когда датчиков восемь - геморроев на 1 человека получается слишком много...
    Нет, если вы делаете для себя, то вы, конечно, можете адреса этих датчиков и вручную прописать в программу, и красочкой их пометить... А если это "на сторону" устройство? А если датчик в процессе эксплуатации выйдет из строя и потребует замены? Вот зачем пользователю все эти проблемы с сопоставлением адреса датчика и функцией устройства? С моей точки зрения - оно ему не нужно.
    Альтернатива - повесить на один порт один датчик, а портов задействовать столько, сколько надо. Минус, конечно, есть, и не маленький - расход пинов микроконтроллера. Зато плюсов существенно больше. 
    Во-первых, нет гемора с адресацией: любой датчик используется в режиме SKIP_ROM, потому как он единственный на своей линии. Достаточно к линии порта, ответственной за температуру на улице, подключить уличный датчик, и "соответствие" автоматически обеспечено.
    Во-вторых, считывать информацию с датчиков, подключенных к одному порту, можно одновременно, т.е. по сравнению с "последовательным опросом" многократно быстрее!
    Идея проста, как колумбово яйцо (правое): управляя не отдельным битом порта, а сразу всеми битами, формируются тайм-слоты чтения (при записи тоже ничто не препятствует, но это менее интересно, т.к. одновременная запись в "обычную" цепочку 1-wire датчиков возможна и так), и считывание битов из 8 линий происходит одновременно. Т.е. вместо 72 битов из одного датчика мы получаем 8х72 бита из 8-и датчиков. Накопив в отдельный массив эти 72 байта за время опроса ОДНОГО датчика, мы затем можем пройтись по этому массиву и выделить информацию каждого из восьми... Ну понятно же, что в 0-ом бите всех байтов массива будут биты из датчика с линии 0, в 1-ом бите - из датчика с линии 1 и т.д.
    Поскольку обработка массива может вестись на предельной частоте микроконтроллера, длиться она будет крайне незначительное время, не смотря на кажущуюся громоздскость. В частности, в своих играх я получаю информацию с 8-и параллельно подключенных к одному порту датчиков за 12 мс (примерно) - ощутите разницу! Ровно (на самом деле нет) в 8 раз быстрее, чем традиционным способом.
    Так что если интересуетесь многодатчиковыми системами контроля температуры - рекомендую.
  12. ARV
    Собственно, не наболело пока еще, но выбесило.
    Столкнулся давеча с необходимостью отредактировать обычный DOC-овский файл на чужом компьютере с Win10. Соответственно, офис мекософтовый тоже там соответствующий - свежий.
    С первых секунд выбесило меня поведение курсора: я жму стрелку влево, а он "плывет неспеша"... Я жму Ctrl-стрелка, чтобы перескочить на слово, а он, зараза, неспеша поплыл к началу следующего слова... Я уж молчу про Home или End.
    Ну как, скажите мне, как можно было до такого додуматься?! Все муки ада на головы тех дизайнеров, которые это придумали, и злых муравьев в трусы тем программистам, которые покорно это запрограммировали!
  13. ARV
    Ох... 
    Сколько бессонных ночей и трудовых дней потребовалось, чтобы завершить работу, которую прилагаю к этой записи... Вот отсюда можно скачать (просто распакуйте архив и запустите exe-шник): https://cloud.mail.ru/public/Co2R%2F3YxQDjR1P
    Что это? Это - система создания скриптов для моего плейера световых эффектов. Сложно рассказать в двух словах, что это и зачем, но если пойти и почитать по ссылке, какое-то представление получить можно. Ну и несколько картинок для интриги:



    Если звезды зажигают, значит, это кому-нибудь нужно?
    Если программы пишут - что это значит?
    P.S. Понимаю, что прошу слишком многого, но очень хотелось бы получить какую-то обратную связь по поводу этого проекта...
  14. ARV
    Биполярное расстройство - это совсем не плохо настроенный усилитель на биполярных транзисторах. Это значительно хуже...
    Но медицина нам поможет.
    Остаются лишь непрерывные броуновские колебания: делать конструкцию с максимальным фаршем или ограничиться необходимым и достаточным минимумом? Тоже в некотором роде биполярность вариантов, которая расстраивает.
    Забожалось сделать GSM-сигнализацию с блэкджеком и женщинами с низкой социальной ответственностью... Напланировал кучу всяких возможностей, вплоть до зачатков "умного дома" (в частности, наличие 8 аналоговых входов для всяких датчиков, 8 дискретных датчиков и 8 линий для датчиков DS18x20, а так же 8 дискретных выходов). Само собой, запланирована рсширяемость системы и т.п. Но... Конкретно мои личные потребности все эти задумки перекрывают с запасом лет на 10, т.е. мне хватило бы раза в 3-4 меньшего количества всех этих входов-выходов. А все эти навароты как бы "для народа" (ибо я никогда из своих поделок тайны не делал и не планирую). Но...
    Из имеющихся микроконтроллеров наиболее подходящим оказался AT90CAN128 - это ничего, что он вообще-то как бы для автомобилей с CAN-шиной, главное, он у меня есть (и не один), и в нем много ресурсов. Однако, я четко отдаю себе отчет, что моя конструкция вряд ли кого-либо заинитересует, т.к. микроконтроллер, прямо скажем, не самый популярный. Тот факт, что на atmega128 код может портироваться практически без модификаций, мало что меняет - и 128-я мега не самая популярная вещь. Но...
    Разгон уже набран: написано 80% кода, нарисовано 50% схемы... Но...
    Надо определяться: стоит ли тратить усилия на никому более, кроме меня, не нужное, или уж рискнуть? Все эти "лишние" возможности отягощают, в основном, печатную плату, т.е. уложиться в "бесплатный" габарит заказа в Китае (10х10 см) будет непросто... А "платный" вариант более крупной платы уже вызывает приступы  амфибиотропной асфиксии...
    В общем, одно расстройство...
  15. ARV
    Есть ли жизнь на Марсе, нет ли её там - науке это не известно. Наука пока не в курсе дела.
    Есть ли жизнь в экосистеме AVR? Или эти мамонты уже вымерли, уступив более теплокровным ARM? 
    По-моему, для неленивого энтузиаста экосистема AVR предоставляет еще множество возможностей. Не смотря на 8 бит и достаточно скромные характеристики, жизнь там не только существует, но и довольно эффектно развивается.
    На видео - небольшая (как кредитка) игрушечка, реализованная на attiny85... Напомню: всего 6 ног, 8К flash и 512 байт RAM. Вот так-то...
  16. ARV
    Ну вот, санитары отпустили, и теперь можно вспомнить, что еще не совсем забыто и сделать, что еще не сделано. Например, рассказать, чего это такое я хотел рассказать ранее, да не успел.
    Собственно вот что я сделал.
    typedef uint16_t timer_sz_t; /// тип функции таймера. если возвращает не ноль, то таймер продолжает работать. /// в качестве параметра получает указатель на структуру timer_struct_t, т.е. на тот самый /// экземпляр таймера, к которому привязана функция. /// вызывается в "безопасном" режиме, т.е. при запрещенных прерываниях /// (атомарно), поэтому из функции можно модифицировать значения полей таймера напрямую, /// хотя для поля \b counter это делать не имеет смысла, т.к. это поле все равно может измениться после /// завершения функции. typedef bool (*timer_callback)(void *t); /// тип структуры, описывающей таймер typedef struct{ timer_sz_t counter; //!< счетчик timer_sz_t period; //!< заданный период timer_callback shot; //!< таймерная функция } timer_struct_t; /// тип для создания экземпляра таймера typedef volatile timer_struct_t timer_t; /// внешняя ссылка, помечающая начало обалсти таймеров в ОЗУ extern timer_t __timer_start; /// внешняя ссылка, помечающая конец области таймеров в ОЗУ extern timer_t __timer_end; /// макрос определения таймера с заданным именем /// @param t идентификатор экземпляра таймера /// @param d период в мс (если не равен 0, то таймер немедленно стартует) /// @param f функция (NULL или 0, если функция не требуется) #define TIMER(t,d,f) static timer_t __attribute__((used, section(".timer_sec"))) t = {.period = d, .counter = d, .shot = f} /** * запуск/перезапуск таймера * @param tmr указатель на экземпляр таймера * @param duration период таймера в мс * @param callback указатель на функцию таймера * \note значение duration=0 фактически останавливает таймер */ void timer_start(timer_t *tmr, timer_sz_t duration, timer_callback callback); /** * проверка таймаута * @param tmr указатель на экземпляр таймера * @return 1, если указанный таймер истек * @return 0, если таймер еще продолжает счет */ bool timeout(timer_t *tmr); /** * остановка таймера * @param tmr указатель на экземпляр таймера */ void timer_stop(timer_t *tmr); Это было содержимое заголовочного файла с описанием программных таймеров. Вроде ничего необычного... А вот так выглядит сам исходник этого модуля таймеров:
    /* * эта функция вызывается каждый системный тик, т.е. каждую милисекунду */ static void Timer_Tick(void){ // обрабатываем программные таймеры for(timer_t *tmr = &__timer_start; tmr != &__timer_end; tmr++){ if((tmr->period) && (tmr->counter)){ // если задан период и счетчик не равен нулю tmr->counter--; // уменьшаем счетчик if(!tmr->counter && (tmr->shot != NULL)){ // как только счетчик обнуляется, if(tmr->shot((void*)tmr)) tmr->counter = tmr->period; // то если указана функция - вызываем её } // и, если она вернула true, переустанавливаем счетчик заново } } } #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wreturn-type" bool timeout(timer_t *tmr){ ATOMIC_BLOCK(ATOMIC_RESTORESTATE){ return tmr->counter == 0; } } void timer_start(timer_t *tmr, timer_sz_t duration, timer_callback callback){ ATOMIC_BLOCK(ATOMIC_RESTORESTATE){ tmr->counter = duration; tmr->period = duration; tmr->shot = callback; } } void timer_stop(timer_t *tmr){ ATOMIC_BLOCK(ATOMIC_RESTORESTATE){ tmr->period = 0; } } #pragma GCC diagnostic pop На что я тут хочу обратить внимание... На пару моментов.
    1. Обратите внимание на директивы #pragma: начиная с какой-то версии avr-gcc появилась возможность временно запрещать компилятору выводить варнинги на некоторые особенности кода, которые на самом деле никакой проблемы не составляют. В частности, в этом коде формируется warning о том, что из функции может быть выход с неопределенным значением - это из-за return изнутри ATOMIC_BLOCK. Но в конкретном случае из ATOMIC_BLOCK не может быть иного варианта выхода, т.е. беспокоиться не о чем. И директива #pragma GCC diagnostic ignored "-Wreturn-type" отключает беспокойство компилятора... Удобно. При помощи этой директивы можно отключить многие warning-и (не любые, но многие) только для той части кода, где вы на 101% уверены в безопасности содеянного.
    2. Обратите внимание на функцию Timer_Tick. В ней четко просматривается цикл перебора записей типа timer_t (или timer_struct_t, что почти то же самое), но в коде модуля нет никакого упоминания какого-либо массива этих структур! Что же перебирается в этом цикле? И именно в этом весь цимес!
    Сначала покажу, как можно пользоваться этими программными таймерами.
    Предположим, Вам надо, чтобы все время мигал светодиод на PORTB. В любом месте вашего исходника, где подключен заголовочный файл таймеров, вы пишите что-то типа такого:
    // пусть светодиод будет на 1-ой линии порта #define LED (1 << PB1) // функция переключения состония светодиода bool led_blink(void *t){ RORTB ^= LED; return true; } TIMER(T_LED, 500, led_blink); Всё! Светоидод замигал. Теперь вам приспичило, чтобы ожидание приема байта по USART было не бесконечным, а длилось, предположим, не больше 1 секунды. Делаете так:
    TIMER(T_USART, 0, 0); char get_usart_byte(void){ timer_start(T_USART, 1000, 0); while(!timeout(T_USART) && bit_is_clear(UCSRB, RXC)); return timeout(T_USART) ? 0 : UDR; } Я не пишу комментариев, т.к. мне представляется, что код полностью очевиден.
    Но что же происходит на самом деле? Макросом TIMER мы определили структуры, описывающие тот или иной таймер, но как они попали в то место, которое обрабатывает Timer_Tick?! Ведь никакого массива (еще раз повторяю) нет! Что же перебирает цикл? Как вообще оказывается возможным "межмодульное" пополнение какого-то неявного списка-массива?!
    А вы не задумывались, как компилятор собирает таблицу векторов? Ведь формально таблица векторов - это массив в памяти программ, но мы все привыкли, что содержимое таких массивов всегда указывается явно и в одном месте целиком!
    // примерно так мы задаем массивы во FLASH const __flash char str[] = "СТРОКА"; // текстовая строка во FLASH const __flash int buf[5] = {1,2,3,4,5}; // массив из 5-и int-ов во FLASH // в древних версиях avr-gcc (WinAVR) это было так PROGMEM char str[] = "СТРОКА"; PROGMEM int buf[5] = {1,2,3,4,5}; И никгда и никак нельзя было сделать так, чтобы в одном модуле мы определили массив, а в 5-и разных модулях затем определили по одному из его 5 элементов! Но ведь таблица векторов прерываний как-то заполняется компилятором именно так, т.е. в любо модуле при помощи макроса ISR мы легко можем задать значение одного из элементов этой таблицы! Как компилятор это делает?!
    Когда я задумался над этим, мне сразу пришла в голову мысль, что подобный подход может сильно упростить написание программных таймеров (и не только). И  разобрался, как компилятор это делает.
    А никак он это не делает.
    Это делает линкер, а не компилятор. Компилятору мы лишь указываем, что тот или иной элемент должен быть помещен в определенную секцию памяти, а уж линкер затем все эти элементы помещает в эту самую секцию. Будь у вас хоть 1000 файлов в проекте, из всех этих файлов элементы одной и той же секции будут размещены рядышком последовательно - чем не массив?! Да ничем! Это и есть массив: упорядоченное последовательное размещение в памяти однотипных элементов данных.
    Секции же могут быть стандартными и пользовательскими. О том, что такое стандартные секции, читайте в документации на avr-gcc, благо, она есть на всех популярных в нашей стране языках. Наполнение стандартных секций линкер делает при помощи стандартного скрипта. А вот чтобы линкер правильно обработал пользовательские секции (в частности, нашу секцию .timer_sec (см. первую врезку кода), надо вручную подправить этот скрипт.
    Стандартные скрипты для avr-gcc находятся в папке avr\lib\ldscripts в папке тулчейна. Можно взять подходящий, поместить его в папку своего проекта, подкорректировать, как надо (ниже покажу, как), и в опциях линкера в makefile дописать директиву закгрузки этого скрипта. Например, наш скрипт называется avr5.x (подходит для "больших" атмег - с памятью 128К, например). Подправленная версия, лежащая в папке нашего проекта, будет иметь название avr5_mod.x, тогда в makefile надо дописать строку LDFLAGS += -T c:\My_prj\avr5_mod.x, и все.
    Ну, а тепрь главное, что же писать в скрипте? А вот что. Сначала найдите место, с которого описывается стандартная секция .data (статические переменные в ОЗУ), а затем допишите в эту секцию команды для добавления нашей секции .timer_sec, а так же заодно определение двух символов __timer_start и __timer_end:
    .data : { PROVIDE (__data_start = .) ; *(.data) *(.data*) /* ++++++++++++++++++++++++++++ */ PROVIDE (__timer_start = .) ; *(.timer_sec) *(.timer_sec*) KEEP (*(.timer_sec*)) PROVIDE (__timer_end = .) ; /* ++++++++++++++++++++++++++++ */ *(.rodata) /* We need to include .rodata here if gcc is used */ Вот так должна выглядеть у вас эта часть файла - остальное не трогайте! Только добавьте то, что между плюсиками.
    Теперь при сборке вашего проекта линкер поместит все структуры timer_t, где бы они ни были определены при помощи макроса TIMER, в одно место, поместит адрес начала этой области (т.е. адрес первой структуры) в символ __timer_start, а адрес следующего за полсденей структурой байта - в __timer_end (а эти два символа, как видно по второй врезке кода, и используются в цикле перебора программных таймеров). Понятно?
    Пока успокоительное не подействовало, мне представляется этот подход просто гениальным. Единожды потрудившись над созданием скрипта линкера и пары небольших файликов, вы избавите себя от массы головной боли: теперь вы можете в любом удобном месте определять любое количество (ну, в разумных пределах, конечно!) программных таймеров, и они будут работать, как будто они аппаратные. Причем с помощью замены call-back функций вы можете менять поведение таймера по ходу работы как угодно. В некотором смысле эти функции имеют много общего с небольшими задачами нормальных RTOS.
    Ах, да! Главное: Timer_Tick вы должны вызывать из обработчика прерывания настоящего аппаратного таймера. В моих примерах подразумевается, что прерывание это возникает каждую миллисекунду, но для многих применений это слишком часто. На практике вполне можно удовлетвориться и 10-миллисекундынми интервалами.
    Надеюсь, эта идея вам понравится, как и мне. Кстати, на таком же принципе очень удобно делать всякие парсеры строк, ну то есть когда вам надо в зависимости от того или иного текста в строке выполнть ту или иную функцию. Если делать все в одном файле - он будет дико объёмным, кто не верит - посмотрите в исходники какого-либо бейсика. Разобраться в таком файле сложно... А применив описанный принцип, т.е. совместив на уровне скрипта определенные в разных модулях элементы общего массива, можно получить очень компактный и понятный код.
    До встречи на процедурах!
  17. ARV
    В моём гнезде прибавление.
    В смысле, алгоритм вылупился. Не скажу, чтобы исключительно новый, вряд ли гениальный, но, мне кажется, заслуживающий внимания.
    Преамбула.
    Что мы понимаем под понятием "таймер"? Ну, не в смысле задатчика времени варки яиц всмятку, а в программировании? Это некая функция, которая "сама по себе" выполняется через заданные интервалы времени. Или же чуть иначе: функция выполнится через заданный интервал времени однократно. Наконец, и третья интерпретация тоже имеет место быть: таймер - это некий счетчик, который сам по себе считает, а мы можем время от времени поглядывать на его значение и принимать какие-то решения.
    Амбула.
    Как обычно реализуются таймеры в микроконтроллерном программировании? Безусловно, наиболее удобно - с задействованием аппаратных таймеров-счетчиков и прерываний от них. Существует вариант реализации и без этого, но сие удовольствие надо оставить пациентам более строгого режима лечения. С прерыванием от аппаратного таймера все понятно, но их количество (аппаратных счетчиков я имею ввиду) ограничено. И поэтому в общем случае используется модель "программных" таймеров на основе одного прерывания от аппаратного. Вот как, например, выглядит один из простейших вариантов:
    #define TIMER_CNT 5 static volatile uint8_t timer[TIMER_CNT]; // обработчик прерывания от таймера, вызывается каждую миллисекунду ISR(TIMER_vect){ for(uint8_t i=0; i<TIMER_CNT; i++){ if(timer[i]) timer[i]--; } } // вот так можно ограничить длительность цикла интервалом времени в 100 мс timer[0] = 100; while(timer[0]){ // что-то длительное if(какое-то-условие-неизвестно-когда-возникающее) break; } if(!timer[0]){ // из цикла вышли по таймауту } else { // из цикла вышли по условию } Вроде бы, все просто и понятно. И даже удобно. Я сам 100 раз так делал!
    Но есть и неприятности. Во-первых, надо постоянно следить за тем, какой "номер" таймера задействован в том или ином участке кода. Когда таймеров два или три - проблемы нет, а когда в разных функциях в разных модлях их по нескольку штук, можно и запутаться. Во-вторых, массив timer должен быть глобальным, что само по себе не страшно, но как-то не комильфо... В-третьих, сделать таймер не однобайтным, а двухбайтным, чтобы иметь возможность отсчитывать большие интервалы времени, уже так красиво не выйдет - следует обеспечивать атомарный доступ к значению счетчика... И главное: этот подход реализует только последний вариант таймера из числа рассмотренных в преамбуле, т.е. гибкость его ограничена.
    Путем нехитрых манипуляций можно заметно улучшить ситуацию. Хотя и несколько усложнив код:
    #include <util/atomic.h> #define TIMER_CNT 5 typedef uint8_t (*tmr_func)(void); typedef struct{ uint16_t counter; uint16_t duration; tmr_func func; } timer_t; static volatile timer_t timer[TIMER_CNT]; ISR(TIMER_vect){ for(uint8_t i=0; i<TIMER_CNT; i++){ if(timer[i].counter){ timer[i].counter--; if((timer[i].counter == 0) && (timer[i].func != NULL)) if(timer[i].func()) timer[i].counter = timer[i].duration; } } } void timer_start(uint8_t t, uint16_t duration, tmr_func f){ ATOMIC_BLOCK(ATOMIC_RESTORESTATE){ timer[t].counter = duration; timer[t].duration = duration; timer[t].func = f; } } uint8_t timer_out(uint8_t t){ ATOMIC_BLOCK(ATOMIC_RESTORESTATE){ return timer[t].counter == 0; } } // вот так можно заставить светодиоды мигать с разной частотой static uint8_t blink_led1(void){ PORTB ^= 1<<0; // светодиод на нулевой линии порта return 1; // для перезапуска функции } static uint8_t blink_led2(void){ PORTB ^= 1<<1; // светодиод на первой линии порта return 1; // для перезапуска функции } timer_start(0, 500, blink_led1); timer_start(0, 300, blink_led2); while(1){ // тут что-то делаем, а светодиоды тем временем мигают каждый по-своему } Разумеется, здесь уже и атомарность доступа к значению счетчика реализована (ценой вызова отдельной функции), и все вариаты из преамбулы тоже. Надеюсь, очевидно, что если переданная в таймер функция вернет 0, она больше не будет вызываться после того, как таймер истечет? Чем вам не RTOS в минимальном виде? Главное условие в применимости такого подхода - предельно быстрое исполнение таймерной функции. Но при использовании автоматов состояний этим способом можно решать большой спектр практических задач.
    Но проблема с "учетом" таймеров осталась. Да и если вы вдруг станете нуждаться в бОльшем количестве таймеров, чем TIMER_CNT, вам придется эту константу менять. И в случае, если вы модифицируете старый проект, и старое количество таймеров вам не нужно, то тоже надо это вручную менять. Мелочь, а неприятно.
    Хорошо было бы, если бы в любом месте кода описал свой отдельный static (т.е. невидимый другим) таймер, и пользуешься им. Не нужен -удалил его описание, и не пользуешься. А "система" сама заботится о том, чтобы таймер "тикал" или не "тикал", если не нужен.
    И обычно для этого используют возможности RTOS.
    Хотя... Хотя максимальное количество выделяемых RTOS таймеров позапросу пользователя тоже ограничено значением какой-то константы... Но и из этого исхода есть выход! Только об этом в следующий раз. Т.е. о самом главном я и не сказал...
  18. ARV
    Нельзя полюбить RTOS и избавиться от волнений. Любовь - это штука, волнующая кровь по определению, так что...
    Стихли первые эмоции на основе эйфории, появилась тревога. 
    Отладка RTOS - та еще песня! Никогда не знаешь точно, что и как происходит не так, если оно не так. Когда одна задача посылает команды по USART в устройство, другая задача принимает от него ответы, а третья занимается управлением, понять, почему третья задача работает не правильно, очень не просто. Непросто потому, что запросы и ответы разделены во времени и в пространстве, и если управляющая задача ждет готовности, надо выснить, из-за неотправленного запроса или же из-за не полученного ответа. А если на все это накладывается еще и не совсем разумное поведение устройства, то вообще все становится загадочно и сташно.
    Пытаюсь совместить модуль плейера MP3-файлов с RTOS. Написал две функции-задачи для приема ответов и отправки команд. Вроде бы все правильно, все корректно. 
    /** * задача приема сообщений от других задач и выдачи команд в плейер * @param p не используется */ static void control_task(void *p){ static int16_t ver; static player_msg_t *msg; ver = get_current_mbox_version(&player_mailbox); while(1){ wait_for_increment_of(&tick, 10); // обработка сообщений if((msg = read_mbox_min_version(&player_mailbox, &ver)) != NULL){ // новое в ящике // разбор сообщения switch(msg->cmd){ case PCMD_RESET: // сброс status = P_NOT_READY; mp3_cmd(MP3_CMD_RESET,0,0); break; case PCMD_SET_VOL: // громкость mp3_cmd(MP3_CMD_SET_VOL, 0, msg->bparam > MP3_MAX_VOL ? MP3_MAX_VOL : msg->bparam); break; case PCMD_STOP: // остановка воспроизведения if(status != P_READY){ mp3_cmd(MP3_CMD_STOP, 0, 0); status = P_READY; } break; case PCMD_S_MSG_QUEUE: // воспроизвести сообщение с ожиданием while((status != P_READY)) yield(); //continue; case PCMD_S_MSG_NOW: // немедленно воспроизвести сообщение // если папка не указана - ищем трек в корне if(msg->bparam) mp3_cmd(MP3_CMD_PLAY_FOLDER, msg->bparam, msg->wparam); else mp3_cmd(MP3_CMD_PLAY, msg->wparam >> 8, msg->wparam & 0xFF); status = P_PLAY; break; case PCMD_L_MSG_QUEUE: // воспроизвести трек из "большой" папки с ожиданием while(status != P_READY) yield(); //continue; case PCMD_L_MSG_NOW: // воспроизвести из "большой" папки немедленно status = P_PLAY; mp3_cmd(MP3_CMD_PLAY_3000, ((msg->bparam & 0x0F) << 4) | ((msg->wparam>>8) & 0x0F),msg->wparam & 0xFF); break; case PCMD_USER: // любая иная команда mp3_cmd(msg->bparam, msg->wparam>>8, msg->wparam & 0xFF); break; } } release_mbox_read(); ver++; } } #include <util/delay.h> /** * Задача приема сообщений от модуля плейера. Осуществляет управление статусом * плейера в зависимости от принятых команд. * @param p не используется */ static void reseive_task(void *p){ static mp3_buf_t packet; static uint8_t old; static uint8_t d; while(1){ // обработка ответов модуля d = 0; // ждем время, достаточное для приема пакета (10 мс) wait_for_increment_of(&tick,10); // ищем стартовый байт if(data_reseived()) d = data_get(); if(d != MP3_START_BYTE) continue; // считываем пакет for(uint8_t i=0; i < (MP3_PACKET_SZ-1); i++){ packet.bytes[i] = d; while(!data_reseived()) to_os(); d = data_get(); } // обрабатываем пакет switch(packet.command){ case MP3_ERROR: // ошибка mprintf("\nError %02X st=%d", packet.param_lo, status); if(status == P_PLAY) status = P_READY; break; case MP3_STAY_USB:// конец воспроизведения case MP3_STAY_SD: // STAY приходит дважды!!!, один раз надо игнорировать //dbg_packet(&packet); if(old != packet.param_lo) status = P_READY; old = packet.param_lo; break; case MP3_DEV_STATUS: // инициализация закончена if((status == P_NOT_READY) && (packet.param_lo == DEV_SD)) status = P_READY; break; case MP3_PLUG_IN: // подключение источника status = P_READY; break; case MP3_PULL_OUT: // отключение источника status = P_NOT_READY; break; default: // все прочие пакеты break; } } } Проверяю функционирование при помощи простой функции, "говорящей время":
    void say_time(uint8_t h, uint8_t m){ player_send_msg(PCMD_S_MSG_QUEUE, FOLDER_MSG, SAY_TIME); if(h==0) h=24; if(m==0) m=60; player_send_msg(PCMD_S_MSG_QUEUE, FOLDER_HOUR, h); player_send_msg(PCMD_S_MSG_QUEUE, FOLDER_MIN, m); } Вызываю эту функцию каждые 5 секунд, имитируя минуты, в отдельной задаче. В итоге система говорит время некоторое количество раз, после чего состояние модуля становится P_PLAY и не исчезает. Если посмотреть на код функций управления и приема ответов, то можно понять, что такая ситуация возможна, если модуль не ответил о том, что файл проигран до конца. НО ОН ОТВЕЧАЕТ! И отвечает 2 раза на каждый файл, о чем в документации нет ни слова!
    Что происходит, как выяснить? Самое удивительное, что если снять ремарку с отладочного вывода содержимого принятого ответа, то все начинает работать! И в терминале я вижу, что на каждый файл приходит подтверждение окончания воспроизведения... И, значит, состояние P_PLAY обязано сбрасываться в P_READY! Но если отладочный вывод в терминал заремарить - рано или поздно все виснет.
    А я-то думал, волноваться больше не придется...
  19. ARV
    Вот вы говорите: AVR слишком убоги, чтобы применять на них RTOS... А я рискнул...
    Сначала попытался рассмотреть имеющиеся варианты, чтобы сделать предварительные выводы. Поиск вываливает примерно с десяток готовых разработок RTOS разной степени крутости, из которых FreeRTOS, естественно, в лидерах. Однако, я оценил свои силы и решил, что вхождение в эту ОС для меня обернется большими сложностями, в основном, из-за большого количества возможностей API, и англоязычным их описанием. Ну не принимает душа русская языка аглицкого, даже со словарем и гуглопереводчиком в больших количествах. А из осей на великом и могучем нашлось только две: кооперативная OSA и присиплюсплюснатая ScmRTOS.
    Опять-таки из-за собственной ограниченности более современная и продвинутая ScmRTOS мне показалась недоступной - С++ пока что понимаю и принимаю исключительно в качестве наказания. Ну, собственно, и вышло, что начать и закончить поиск осей для AVR можно на OSA.
    Попробовал - получается. Не без скрипа, но работает. И даже увлекло меня это. Но вот что мне не понравилось в этом варианте.
    Главная особенность этой ОС, которую следует учитывать при работе (то есть при написании программ), это отсутствие сохранения контекста при переключении задач. Иными словами, если в текущей задаче вызывается сервис операционной системы, переключающий задачи, то все локальные переменные текущей задачи могу потерять свою актуальность. Это означает, в частности, запрет на вызов сервисов системы в циклах по счетчику (значение счетчика будет потеряно). И единственный способ решить эту проблему - вместо автоматических локальных переменных использовать static или вообще отказаться от локальных в пользу глобальных. Сами понимаете, это совсем не гуд.
    Вторая особенность этой ОС, это возможность вызывать сервисы ОС, преключающие задачи, только из тела самой задачи, но не в вызываемых из неё функций. То есть нельзя сделать функцию, например, ожидающую прием символа из USART при помощи системного сервиса OS_Wait, а затем вызывать эту функцию из разных задач, то есть поступать по аналогии с привычным "не-многозадачным" подходом.
    Вот представьте себе ситуацию: задачи формируют текстовые сообщения и выводят их в USART. Кажется логичным сделать функцию, которая занимается отправкой в USART строки посимвольно и использовать эту функцию во всех задачах - а нельзя! Более того, не смотря на то, что все задачи ПООЧЕРЕДНО формируют строки (ОС ведь кооперативная), каждая из задач должна иметь собственный промежуточный static-буфер для формирования своей строки - это ведь явно лишний расход памяти! При обычном подходе мы бы работали с локальным буфером в каждой функции, а локальный буфер, как известно, исчезал бы при выходе из функции... 
    Наконец, архитектура этой ОС (под архитектурой я подразумеваю набор файлов-модулей и порядок работы с ними) такова, что почти все файлы инклюдятся друг в друга, что очень сильно нарушает модульный подход при программировании. Напомню, что модульный подход означает, в частности, возможность компиляции каждого Сишного файла отдельно от других сишных файтов. А в OSA системные сишники "вставляются" в один большой "общий" сишник, который затем и компилируется. В итоге я потратил немало времени, чтобы разобраться, как же настроить проект в Eclipse, чтобы можно было комфортно работать. Eclipse очень привык считать все сишники отдельными модулями проекта, и страстно стремится компилировать их отдельно.
    В общем, знакомство с OSA было увлекательным, недолгим, интересным, но разочаровывающим.
    Другие же ОС, найденные мной, были не кооперативными, а вытесняющими. Вытесняющие ОС имеют много преимуществ перед кооперативными, но один их недостаток сильно ограничивает применение на AVR: они весьма требовательны к объемам ОЗУ. Именно отсюда растут ноги у паникерских мнений, что AVR и "нормальная" RTOS - понятия несовместимые. И это на самом деле так, если мы говорим о микроконтроллерах младше (т.е. слабее) atmega32. Для справки: OSA вполне себе способна быть полезной не только на atmega8, но даже и на attiny2313! 
    Но, к счастью для меня, не одной atmega32 ограничен мир AVR, и, кроме прочего, не ограничен и я сам. У меня в загашнике есть и at90can128, и даже atmega2560! И, спросил я себя, почему я должен переживать по поводу вытесняющей ОС при таких-то ресурсах? В at90can128 целых 4К ОЗУ, а уж flash-памяти по 8-битным меркам просто немеряно - 128К, а у монстра atmega2560 вдвое больше всего! Правда, если первый МК паять вполне комфортно (TQFP64), то второй без микроскопа уже сложно (TQFP100 c шагом выводов 0,5 мм). А тут еще у меня завалялась отладочная платка DVK90CAN1... Ну, вы поняли...
    Итак, решающим теперь для меня стал поиск максимально простой операционки - чтобы мне по силам.
    Их не так мало, как может показаться, но самой простой, по моему мнению, является YAVRTOS (скачать архив с исходниками, примерами и документацией можно по ссылке, но сайт автора уже не существует) - это практически такой же малоизвестный, как OSA, продукт примерно тех же времен (видимо, тогда было можно каждому мастерить свою собственную ОС с блекджеком и девушками низкой социальной ответственности).
    Не смотря на инглиш, эта ось оказалась мне по силам: всего два файла и с полтора десятка системных функций! За один вечер легко расщелкал все необходимое для первого старта.
    Плюсы этой RTOS перед OSA неоспоримы: не надо предпринимать практически никаких усилий по оформлению кода - пишется точно так же, как всегда, с локальными переменными, с вложенными вызовами функций и т.д. Разумеется, надо следить за общими ресурсами и блокировать к ним доступ, если необходимо - но это вообще всегда необходимо в многозадачных системах, и даже в ОSA частично так. Минусы, правда, тоже заметны: минимальное приложение, тупо мигающее двумя светодиодами (каждый в своей задаче) занимает почти 2К flash и порядка 400 байт ОЗУ. На просторах выбранного мной МК это даже и не заметно, но для atmega8 может быть близким к техническому пределу.
    YAVRTOS написана на 99,9% на Си (только сохранение/восстановление контекста реализовано в виде ассемблерной вставки из трех десятков push-pop), всего два файла (task.c и task.h) - все это явный плюс в плане изучения и модификации под себя, если надо (и если хватает ума). Косвенным плюсом (или минусом, если продолжать переживать о ресурсах) является массовое применение malloc в ядре ОС, а значит, и в пользовательском приложении уже вполне оправдано динамическое распределение памяти. 
    И мой энтузиазм просто на взлете от первого опыта! Например, вот как выглядит код задачи и вспомогательных функций для извлечения точного времени из GPS-приемника, подключенному к USART1, и вывода этих показаний на стандартный вывод (stdout, связанный с USART0):
    const __flash char gps_msg[] = "RMC,"; #define GPS_MSG_SZ (sizeof(gps_msg)-1) // поллинг 1 символа от GPS static uint8_t get_char(void){ while(bit_is_clear(UCSR1A, RXC)) wait_for_increment_of(&tick, 1); return UDR1; } // получение 1 цифры из символа static uint8_t get_dig(void){ return (get_char() - '0'); } // собственно сама задача void p2p_usart(void *p){ uint8_t i; uint8_t h,m,s; while(1){ i = 0; // ждем прихода сообщения с точным временем while((i < GPS_MSG_SZ) && (get_char() == gps_msg[i])) i++; if(i == GPS_MSG_SZ){ // разбираем сообщение по символам h = get_dig()*10 + get_dig() + 3; // +3 - это часовой пояс h %= 24; m = get_dig()*10 + get_dig(); s = get_dig()*10 + get_dig(); // пропускаем сотые доли секунды get_char(); // '.' get_char(); // 's' get_char(); // 's' get_char(); // ',' // проверка корректности времени и его вывод if(get_char() == 'A'){ printf_P(PSTR("GPS Time %02d:%02d.%02d\r"),h,m,s); } else { printf_P(PSTR("No GPS, wait... \r")); } } } } Как видите, код крайне "тупой", то есть прямолинейный, как лом: сплошные ожидания и никакой заботы о том, что параллельно должно что-то еще работать. В моем случае просто мигают 2 светодиода - один с длительностью импульса/паузы в 500 тиков, а второй в 501 (кстати, 1 тик = 1 мс, тактовая частота МК = 8 МГц). Но вместо светодиодов может быть еще две (или сколько надо) аналогично прямолинейно написанных задач, и можно быть уверенным, что все будет работать! Приведу данные по итогам компиляции проекта, чтобы продемонстрировать израсходованные ресурсы:
    Не так уж и плохо, учитывая свободное применение printf. В активном режиме используется дополнительно 380 байт ОЗУ под стеки задач и ОС, т.е. примерно 10% всего объема - еще много остается.
    Есть, кроме YAVRTOS, и другие альтернативы, например, FemtoOS, которая поддерживает даже (!!!) attiny25, и при этом тоже является вытесняющей операционкой. Но она существенно "богаче" в плане API, и разобраться с нею будет посложнее, т.к. документирована она явно менее детально. Возможно, я и её попробую на вкус...
    И, скорее всего, теперь это станет для меня основным способом написания программ. RTOS позволяет сильно упростить себе жизнь. Имхо.
×
×
  • Создать...