|
|
|
|
185506 119 0 0 |
|
Опции темы | Поиск в этой теме |
11.11.2010, 12:34 | 21 | ||
Увлеченный
Регистрация: 21.06.2010 Последняя активность: 01.08.2015 23:26
Сообщений: 180
Сказал(а) спасибо: 0
Поблагодарили: 0 раз(а) в 0 сообщениях
|
Цитата:
Цитата:
Цитата:
Код такого рода элементарно пишется без прерываний (и более того, при желании можно гарантировать точную частоту отсчетом по тактам, что дает джиттер на такт меньше, чем прерывания). Пишется это на конечных автоматах. Код разбивается на несколько коротких (меньше 166 тактов) частей, выполняющих кусочки обработки протокола карты. Поллинг карты делается по аналогии с подходом poll/select в UNIX. В цикле выбираем очередное действие по обработке данных, затем паузу (можно даже просто nop добить, если знать число тактов) и вывод сэмпла. Никакой проблемы здесь нет, кроме проблемы понимания одной идеи - программу можно "вывернуть наизнанку", оперируя не последовательностью действий, а состояниями и событиями. Почему-то этот простой факт всегда вводит в ступор не только начинающих, но даже многих довольно опытных программистов, хотя по сути это мощный прием УПРОЩЕНИЯ программы. Если возникает необходимость строго считать такты, то для написания таких программ лучше использовать самодельные вспомогательные утилиты (программы, которые пишут программу). На таком подходе в свое время кем-то был сделан драйвер LCD-экрана, отсылавший в реальном времени байты из видеопамяти на матрицу. Там между выводом байтов оставалось всего по 1 пустому такту, и то не всегда, и программа была написана "через строчку", как стасованная колода карт - чередующиеся команды от разных нитей исполнения. |
||
11.11.2010, 12:56 | 22 | |||
Увлеченный
Регистрация: 21.06.2010 Последняя активность: 01.08.2015 23:26
Сообщений: 180
Сказал(а) спасибо: 0
Поблагодарили: 0 раз(а) в 0 сообщениях
|
Цитата:
Цитата:
При таком подходе прерывания НИКОГДА НЕ ЗАПРЕЩАЮТСЯ. Может, в этом ответ на все вопросы? Цитата:
В серьезной программе на подходе конечных автоматов 90% работы программиста - это правильное построение набора состояний и автоматов. В простой программе это можно сделать в уме, в программе чуть сложнее - вручную на листке бумаги. Сложные программы, такие как парсеры сетевых протоколов, могут иметь сотни состояний, и построить их руками нереально. В этом случае используются специальные инструменты - генераторы конечных автоматов (ragel, apertium, sfst, lex/yacc, antlr) и программы машинной алгебры (mace4, prover9), которые по заданным требованиям пишут почти готовую программу. Их можно использовать и для проверки программ на прерываниях - для поиска фатальных сочетаний событий, рушащих программу (это сложно вручную, но легко для машины). Прелесть подхода конечных автоматов в том, что всего лишь 8 байт памяти дают 2^64 возможных сочетаний флажков. Это позволяет писать сложнейшие алгоритмы, используя всего лишь от 2 до 8 регистров (8-битных) для хранения промежуточных состояний. Вместе с несколькими десятками команд на анализ состояний это позволяет написать очень компактный и быстрый обработчик сложнейших протоколов обмена с периферией. В частности, для раскодирования любого (!) протокола серийно выпускаемых ИК-пультов достаточно 1 регистра. Примерно такую же сложность имеют раскодировщики spdif и подобных вещей, а анализатор XML с порта RS232 уложится в 8 регистров. Теоретически, в mega128 должен влезть оптимизирующий компилятор Си... |
|||
11.11.2010, 19:50 | 23 | |||
Завсегдатай Фонарёвки
|
Цитата:
Цитата:
А я люблю делать всё руками, зачем мне такие сложности? Можешь на примере написать, как это делается? Если это то, что я думаю - там на одну проверку всех флагов уйдёт много тактов, а их надо проверять между каждыми семплами, чтобы определить, что надо делать, верно? А между семлпами может оказаться всего десяток тактов. Цитата:
|
|||
12.11.2010, 22:07 | 24 |
Увлеченный
Регистрация: 21.06.2010 Последняя активность: 01.08.2015 23:26
Сообщений: 180
Сказал(а) спасибо: 0
Поблагодарили: 0 раз(а) в 0 сообщениях
|
"Самостоятельное написание кода" подразумевает даже не Ассемблер, а машинный код. Уже на Си код пишет все-таки компилятор. То, о чем я говорю - это просто использование языков еще более высокого уровня, чем Си - настолько высокого, что они Си на выходе вместо машинного кода дают.
Написать, как выглядит сгенерированный код, я могу, но понять, как именно ЭТО работает - нереально. А про ОЗУ - вот что. В "обычной" программе при ближайшем рассмотрении в памяти хранится очень много лишнего. Флажки можно хранить не дополнительно к строкам и счетчикам, а ВМЕСТО них. Объяснить, как это происходит, проще на примере программы для обработки текста. (Для микроконтроллера такой пример будет слишком сложен и обязательно сопряжен с чем-то вроде работы с IDE-CDROM). Допустим, нам надо разобрать файл конфигурации достаточно стандартного формата: Код:
# комментарий а = 12 b = 456 name = test longname = "test test" "Вариант новичка" номер 1. Читаем из файла строку (то есть, читаем файл по байтам, пока не наткнемся на символ перевода строки). Определяем, является ли строка комментарием. Ищем в ней знак равенства. Определяем, есть ли кавычки. Выделяем имя (слева от знака равенства) и ищем его в массиве перебором, в лучшем случае - в дереве. Заполняем переменную. Накладные расходы: строка в памяти, список всех переменных. "Вариант новичка" номер 2. Читаем файл много раз, каждый раз ища одну переменную. Без комментариев. И, наконец, оптимальное решение задачи. Входной файл в нем читается ровно один раз, можно даже по одному байтику. Никаких промежуточных строк не храним. Все, что требуется - это одна целочисленная переменная "номер состояния". Вот как это работает. Изначально мы находимся в состоянии "начало строки". Когда приходит байт, если это не пробел, мы меняем состояние. После символа "#" мы переходим в состояние "комментарий", в котором остаемся до символа "конец строки". После знака равенства - в состояние "значение", а если мы уже в состоянии "значение" и видим кавычку - то в состояние "значение внутри кавычек". И так далее. Таким образом мы можем получить полное знание о содержимом файла, не видя более 1 байта за раз. Как мы опознаем имена перед знаком равенства? Очень просто. Каждому имени соответствует состояние, попасть в которое можно только из состояния "одной буквы не хватает до полного имени" - и так далее. Аналогично обрабатываются и числа. По одной цифре читаем параллеьно с вычислением результата. На первый взгляд может показаться, что программа получается длиннее и медленнее. Это не так. Наоборот, накапливается очень серьезная экономия, связанная именно с обращениями к памяти - программа не делает копирование строк, не выделяет и не освобождает память, а на микроконтроллерах не вызывает инструкций обращения к ОЗУ. Тем самым можно ускорить программу и сократить расход памяти за счет некоторого увеличения длины кода. Но место под код обычно гораздо "дешевле" ОЗУ, особенно на микроконтроллерах, где каждый байт ОЗУ на счету. На указанном подходе в нашей группе и мною лично были разработаны очень серьезные программы, одна из которых (парсер потоков синдикации Яндекс.Видео), по-видимому, является самой быстрой в мире в своем классе. Статьи из Википедии по теме: Автоматное программирование Событийно-ориентированное программирование |
12.11.2010, 22:19 | 25 | |||
Завсегдатай Фонарёвки
|
Цитата:
ЗЫ. Думал ты про генератор кода, которые встроенны в некоторые редакторы для мк. Кликаешь в визарде, а он создаёт шаблоны на Си для разной периферии. Цитата:
Может в итоге, эта скорость и не нужна. В этом плане, парсинг конфига - неудачная задача, разовая. И освободившуюся после этого процесса ОЗУ можно использовать для других целей. Цитата:
Цены у них от памяти гуляют очень сильно! attiny261 - 4.33 евро, attiny861 - 7.03 евро. Ощущение, что память на вес золота, а всё остальное ничего не стоит. |
|||
13.11.2010, 11:12 | 26 |
Ветеран Фонарёвки
Регистрация: 15.02.2010 Последняя активность: 05.09.2022 18:18
Сообщений: 1034
Сказал(а) спасибо: 0
Поблагодарили: 0 раз(а) в 0 сообщениях
|
ИМНО: Программа(функция) на автоматах действительно получается экономичнее и проще.
Например: если завести один регистр состояния и один switch, можно избежать совершенно жуткого ветвления в программе(функции) с кучей флагов и вложенных друг в друга if...else. Но вот отлаживать такую програмку(функцию) сложнее. Но тут выручает Cи, отладку и тестирование ПО можно выполнить и на обычном ПК. Если конечно заранее работу с железом вынести в отдельные файлы. В этом случае эмулятор девайса на ПК делается элементарно |
13.11.2010, 12:58 | 27 | |||
Увлеченный
Регистрация: 21.06.2010 Последняя активность: 01.08.2015 23:26
Сообщений: 180
Сказал(а) спасибо: 0
Поблагодарили: 0 раз(а) в 0 сообщениях
|
Цитата:
Не пишите программу. Пишите программу, которая напишет программу. Только в простейших случаях программа пишется руками. Цитата:
Код:
int main() { printf("Hello World!\n"); return 0; } Код:
int main() { return (printf("Hello World!\n") == 13) ? 0 : 1; } Цитата:
Спасет самодельный компилятор. Написать его можно на чем угодно, самые подходящие языки - perl, php или функциональные (lisp, haskell). Каждая нить исполнения (на каждый светодиод, на проигрывание wav и т.п.) пишется руками на псевдоассемблере отдельно. "Псевдоассемблер" от настоящего ассемблера отличается тем, что там вместо имен регистров ставятся буквы. Затем с помощью самодельной программы все это сшивается воедино - проставляются имена регистров, подсчитываются такты (по таблице из даташита), перетасовываются команды из разных нитей. Вручную это фиг сделаешь, а с помощью самодельной программки - тьфу, ерунда. Это называется "метапрограммирование". Именно оно позволяет писать самые простые и оптимальные программы. Пример: на входе - нить 1: Код:
#loop sbi PORTA, 6 #delay 3 // такта cbi PORTA, 6 #delay 1 #endloop Код:
#loop sbi PORTB, 1 #delay 1 cbi PORTB, 1 #delay 3 #endloop Код:
L0: sbi PORTA, 6 sbi PORTB, 1 nop cbi PORTB, 1 cbi PORTA, 6 rjmp L0 Добавлено через 4 минуты Можно пойти еще дальше - не тестировать и не отлаживать вообще. Существует определенный набор приемов написания (больше относится к C++, но можно применить и к Си), который практически гарантирует правильность программы, если она вообще скомпилируется. Я никогда не пользуюсь эмулятором или отладчиком при программировании мироконтроллеров и почти никогда - при программировании компа. Их функции берет на себя компилятор. |
|||
13.11.2010, 13:17 | 28 | |
Ветеран Фонарёвки
Регистрация: 15.02.2010 Последняя активность: 05.09.2022 18:18
Сообщений: 1034
Сказал(а) спасибо: 0
Поблагодарили: 0 раз(а) в 0 сообщениях
|
Цитата:
Хотел было одно время поиграться с JTAG-ом но руководство деньгу зажало |
|
13.11.2010, 13:25 | 29 | |||
Завсегдатай Фонарёвки
|
Цитата:
Цитата:
И, боюсь спросить, а как сравнения, циклы и условные переходы получатся после переплетения? Цитата:
Никакой приём не поможет, если не так понял протокол устройства, принцип работы какой-то переферии, и т.п. |
|||
13.11.2010, 13:59 | 30 | |
Ветеран Фонарёвки
Регистрация: 15.02.2010 Последняя активность: 05.09.2022 18:18
Сообщений: 1034
Сказал(а) спасибо: 0
Поблагодарили: 0 раз(а) в 0 сообщениях
|
Цитата:
Например: если логика в какой либо функции становится слишком сложной для понимания, она разбивается на несколько функций(макросов) с простой и понятной логикой. Меня например конструкции больше двух-трех уровней вложения if..else приводят в шоковое состояние. Насчет переферии. Исследование непонятно как работающей переферии- это отдельная задача, лучше выполняется на спец. стенде или на худой конец на спец. прошивке. Хотя при наличии нормального даташита проблем обычно не возникает. |
|
13.11.2010, 20:34 | 31 | |||
Увлеченный
Регистрация: 21.06.2010 Последняя активность: 01.08.2015 23:26
Сообщений: 180
Сказал(а) спасибо: 0
Поблагодарили: 0 раз(а) в 0 сообщениях
|
Цитата:
Цитата:
Цитата:
Именно это рождает событийный стиль программирования. Вместо того, чтобы пытаться понять, какие ситуации программа может создать, нужно изначально придумать список всех возможных ситуаций. Затем в программе просто перечисляются все эти ситуации, и для каждой указывается действие. Это избавляет от извечной головной боли при переделке программ. Выяснив, что я неправильно понял документацию, я исправлю в программе всего две-три строчки, и эти две-три строчки полностью перекроят логику программы. В результате я потрачу 5 минут на то, что могло бы занять несколько дней. Такой подход одновременно избавляет от ветвлений как таковых (вот ответ про ветвления). Большие ветвления if...else заменяются максимум на однострочные - вида if(x) do(); - а справиться с ними гораздо легче, чем с большим переплетением инструкций. То же самое происходит и с циклами. В Хаскеле, например, вообще нет циклов, и это не мешает на нем программировать. В прошивке всегда есть главный цикл, и использовать можно его. Здесь возможно справедливое замечание - а не будет ли полученная программа неэффективной? В принципе при написании на Ассемблере это вполне возможно. На Си такой проблемы обычно нет, там есть оптимизатор. Но иногда программисту хочется избавить себя от лишней работы настолько, что оптимизатору не справиться. В этом случае надо опять использовать метапрограммирование - написать программку, которая построит ветвления из списка состояний. Использовать для этого можно любой язык, хоть бейсик, но наиболее удобны python, ruby, perl, может быть php и m4. Вообще программирование микроконтроллеров (аналогичный ему пример с больших компов - написание сетевых демонов, серверов) - достаточно частный пример, в котором именно событийный подход и метапрограммирование оказываются наилучшими. Другие задачи решаются иначе. Банальная, но очень важная рекомендация на этот счет звучит так: ни в коем случае нельзя привязываться к какому-то одному языку и к какому-то одному подходу в написании. Если уж совершенно не хочется учить много разных языков, то есть необходимый минимум - это Haskell (или Lisp) и часть Perl - минимальный набор команд + регулярные выражения. Из литературы - неплохо прочитать "Искусство программирования" Кнута и совершенно необходимо - "Алгоритмы и структуры данных" Вирта. Еще я бы сказал, что для программиста главное - чутье на математические теории. Вовремя понять, что в задаче затронут какой-то раздел математики (конечные автоматы, теория оптимального управления, теория графов в программировании микроконтроллеров встречаются сплошь и рядом) и почитать по нему хотя бы Википедию, прежде чем писать код. К сожалению, многие (особенно начинающие) программисты страдают излишней самоуверенностью. Так рождаются тормозные и глючные программы. Вот пример задачки. К микроконтроллеру ATmega32 подключен стандартный текстовой дисплейчик, скажем, 2x16 символов, и клавиатурка от телефона (тупо кнопки на порты). Никакой другой периферии (в том числе памяти) нету. Требуется реализовать ввод текста T9 со словарем на 10-20 тысяч слов. Для усложнения задачки положим, что процессор работает на очень низкой частоте - например, 32768 Гц от часового кварца. Разрешима ли задача, и если да, то как? (Я давал эту задачу студентам на лекциях, когда преподавал в УПИ, и в принципе первокурсники после моих лекций справились с нею.) |
|||
13.11.2010, 21:48 | 32 | |||
Завсегдатай Фонарёвки
|
Цитата:
Цитата:
Если уж речь пошла обо всех языках (не только для AVR), то ещё я знаю php, STL (контроллеры сименса S7-200), ST (контроллеры Festo), FBD (контроллеры сименса LOGO). Но 50 языков - это жесть. Я бы лопнул, изучить столько Цитата:
Ладно, знаешь, с чем я не согласен? С тем, что ты в теме про Си расписываешь, что надо пользоваться любыми языками, кроме самого Си |
|||
13.11.2010, 22:43 | 33 |
Ветеран Фонарёвки
Регистрация: 15.02.2010 Последняя активность: 05.09.2022 18:18
Сообщений: 1034
Сказал(а) спасибо: 0
Поблагодарили: 0 раз(а) в 0 сообщениях
|
|
14.11.2010, 13:59 | 34 | |
Увлеченный
Регистрация: 21.06.2010 Последняя активность: 01.08.2015 23:26
Сообщений: 180
Сказал(а) спасибо: 0
Поблагодарили: 0 раз(а) в 0 сообщениях
|
Цитата:
Этот алгоритм называется "DAWG" (Directed Acyclic Word Graph). Помимо T9, он широко используется в программах проверки правописания, разгадывания и составления россвордов. Впервые он был предложен в 1988 году как алгоритм для игры в Scrabble в статье http://www.cs.cmu.edu/afs/c... . Мои студенты после объяснений алгоритма trie смогли самостоятельно разработать алгоритм dawg. |
|
14.11.2010, 14:19 | 35 | ||
Увлеченный
Регистрация: 21.06.2010 Последняя активность: 01.08.2015 23:26
Сообщений: 180
Сказал(а) спасибо: 0
Поблагодарили: 0 раз(а) в 0 сообщениях
|
Цитата:
Перл - замечательный язык для написания генрированного кода на Си. PHP (в режим командной строки, а не веба) - тоже. Если возникает необходимость написать для МК сложную программу, то можно сделать программу на PHP, которая напишет программу на Си. Что касается большого многообразия языков, то тут важно, чтобы языки были совершенно разными по идеологии. PHP слишком похож на Си - да, в нем есть eval и можно организовать лямбда-выражения легче, чем на Си, но тем не менее написанию лямбда-выражений на нем не научишься. А жаль. Ведь на Си тоже можно сделать лямбду, но лямбда на Си делается так неочевидно, что ее "по наитию" никто никогда не напишет. Надо целенаправленно хотеть эту лямбду сделать. А для этого надо для начала владеть ею. Цитата:
Мозг взрывается тогда, когда первый функциональный язык учишь. Язык, в котором нет переменных и циклов, в котором действия не выполняются подряд и вообще действий как таковых нет, а есть алгебра. Это, на мой взгляд, в изучении программирования самый сложный шаг - после написания первой работающей программы на Бейсике, конечно Цитата:
Еще хотелось бы подчеркнуть, что изучение разных языков (хотя бы поверхностное, на уровне Hello World) дает представление о том, КАК ЕЩЕ можно написать ту же программу. Языки ведь разные есть. В Haskell нет циклов, например, вместо них рекурсия, и нет исполнения строка за строкой - там программа пишется в алгебраическом виде. В Brainfuck всего 6 команд, тем не менее он тьюринговски полон. Есть язык, в котором программа представляет собой bitmap-картинку, по которой надо ходить, а очередное действие определяется цветом. Некоторые из этих языков практические, некоторые - чисто эзотерические (то есть просто для демонстрации, что так тоже можно). Но в любом случае их изучение полезно для раскрепощения сознания, а то ведь если из инструментов владеть только молотком, то все проблемы будут казаться гвоздями... |
||
14.11.2010, 14:51 | 36 | |
Завсегдатай Фонарёвки
|
Цитата:
А такой автомат можно обучить новым словам? Если уж говорить о полноценном Т9 - это ещё и добавление новых слов. |
|
14.11.2010, 20:11 | 37 |
Ветеран Фонарёвки
Регистрация: 15.02.2010 Последняя активность: 05.09.2022 18:18
Сообщений: 1034
Сказал(а) спасибо: 0
Поблагодарили: 0 раз(а) в 0 сообщениях
|
Так я и не говорил что это не возможно Задача не решается через базу.
Варианты с несколькими словами запросто, а добавление новых слов- нет. Хотя если попытатся самообучаемый автомат сделать... |
14.11.2010, 20:46 | 38 | |
Завсегдатай Фонарёвки
|
Цитата:
|
|
14.11.2010, 21:21 | 39 | |
Увлеченный
Регистрация: 21.06.2010 Последняя активность: 01.08.2015 23:26
Сообщений: 180
Сказал(а) спасибо: 0
Поблагодарили: 0 раз(а) в 0 сообщениях
|
Цитата:
Обучение новым словам как правило делается за счет ОТДЕЛЬНОГО словаря. Дело в том, что в момент ПОСТРОЕНИЯ оптимальных графов DAWG требуется около 200 мегабайт памяти, и сделать это на микроконтроллере нельзя. Поэтому в мобильниках держат два словаря - собственно DAWG в прошивке и отдельный словарь обучения, а поиск просто делается параллельно по обоим. Словарь обучения во многих мобильниках не DAWG, а просто trie, его проще строить, а пользователь вряд ли вобьет много слов. К чему все это было. К тому, что с бухты-барахты такую штуку не придумать. Она требует целенаправленного изучения. Таких вещей в программировании много. И самое неприятное, что они зачастую встречаются даже в "простейших" программах (см. выше про ошибку в классической "Hello World"). Самое главное - вовремя заглянуть в справочник, если знаний математики не хватает на самостоятельное построение алгоритма. А знаний не хватит почти наверняка - их у выпускников матмеха с красным дипломом и то далеко не всегда хватает. |
|
16.11.2010, 19:14 | 40 |
Завсегдатай Фонарёвки
|
Кстати про -O2 (UPD: да нет, даже с -Os прошивка толстая выходит).
Почему в листинге я вижу все функции клонированными? Код:
void spi_send(char cData){ /* Start transmission */ SPDR = cData; 5e: 80 e8 ldi r24, 0x80 ; 128 60: 8e bd out 0x2e, r24 ; 46 /* Wait for transmission complete */ while(!(SPSR & (1<<SPIF))){} 62: 0d b4 in r0, 0x2d ; 45 64: 07 fe sbrs r0, 7 66: fd cf rjmp .-6 ; 0x62 <lcd_resetpos+0x6> SPCR = (1<<SPE)|(1<<MSTR); } void spi_send(char cData){ /* Start transmission */ SPDR = cData; 68: 80 e4 ldi r24, 0x40 ; 64 6a: 8e bd out 0x2e, r24 ; 46 /* Wait for transmission complete */ while(!(SPSR & (1<<SPIF))){} 6c: 0d b4 in r0, 0x2d ; 45 6e: 07 fe sbrs r0, 7 70: fd cf rjmp .-6 ; 0x6c <lcd_resetpos+0x10> Я понимаю, что так быстрее, но не до такой же степени, чтоб 13 раз spi_send вставлять! Что ещё удивительнее - а зачем он в листинг вставил spi_init два раза, если он вообще только 1 раз вызывается? Код:
00000046 <spi_init>: #include <avr/io.h> void spi_init(void){ /* Set MOSI and SCK output */ PORTB = PORTB | (1<<3)|(1<<5); 46: 85 b1 in r24, 0x05 ; 5 48: 88 62 ori r24, 0x28 ; 40 4a: 85 b9 out 0x05, r24 ; 5 /* Enable SPI, Master, set clock rate fck/4 */ SPCR = (1<<SPE)|(1<<MSTR); 4c: 80 e5 ldi r24, 0x50 ; 80 4e: 8c bd out 0x2c, r24 ; 44 } 50: 08 95 ret <.........> 000000e8 <main>: #include <avr/io.h> void spi_init(void){ /* Set MOSI and SCK output */ PORTB = PORTB | (1<<3)|(1<<5); e8: 85 b1 in r24, 0x05 ; 5 ea: 88 62 ori r24, 0x28 ; 40 ec: 85 b9 out 0x05, r24 ; 5 /* Enable SPI, Master, set clock rate fck/4 */ SPCR = (1<<SPE)|(1<<MSTR); ee: 80 e5 ldi r24, 0x50 ; 80 f0: 8c bd out 0x2c, r24 ; 44 f2: ff cf rjmp .-2 ; 0xf2 <main+0xa> |