Ad-social Bot

Smmok Bot

Vkserfing Bot

Vkstorm Bot

Vktarget Bot

Все программы

Запись опубликована: 23.02.2018

Привет, меня зовут Pavel Germanika

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


 

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

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

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

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

Мой Блог — это реалити-шоу обычного заурядного парня, который пытается добиться успеха с полного нуля. Никакого начального капитала, никаких знакомств, никаких особых способностей. Только идеи, анализ и компьютер. Я хочу быть максимально открытым и делиться с вами всеми своими мыслями и результатами работы.

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

Любой из вас может связаться со мной. Я рад каждому! Пишите на мой Telegram.

Метки: ,





Запись опубликована: 21.07.2019

Изобретаем библиотеку vusb

Введение

После прочтения названия может возникнуть закономерный вопрос: зачем в наше время изучать программную реализацию low-speed USB, когда существует куча дешевых контроллеров с аппаратным модулем? Дело в том, что аппаратный модуль, скрывая уровень обмена логическими уровнями, превращает протокол USB в своеобразную магию. Для того же, чтобы прочувствовать как эта «магия» работает, нет ничего лучше, чем воспроизвести ее с нуля, начиная с самого низкого уровня.

С этой целью попробуем изготовить на основе контроллера ATmega8 устройство, прикидывающееся USB-HID’ом. В отличие от распространенной литературы, мы пойдем не от теории к практике, от самого нижнего уровня к верхнему, от логических напряжений на выводах, и закончим «изобретением» той самой vusb, после каждого шага проверяя, работает ли код как ожидалось. Отдельно отмечу, что не изобретаю альтернативу этой библиотеке, а напротив, последовательно воспроизвожу ее исходный код, максимально сохраняя оригинальную структуру и названия, поясняя, для чего служит тот или иной участок. Впрочем, привычный для меня стиль написания кода отличается от стиля авторов vusb. Сразу же честно признаюсь, что помимо альтруистического интереса (рассказать другим сложную тему) имею и корыстный — изучить тему самостоятельно и через объяснение выловить для себя максимум тонких моментов. Отсюда же следует, что какой-то важный момент может быть упущен, или какая-то тема не до конца раскрыта.

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

Также отмечу, что рассматривается только low-speed USB, даже без упоминания, чем отличаются более скоростные разновидности.

Шаг 0. Железо и прочая подготовка

В качестве подопытного возьмем самодельную отладочную платку на основе ATmega8 с кварцем 12 МГц. Схему приводить не буду, она вполне стандартна (см. официальный сайт vusb), единственное что стоит упомянуть, так это используемые выводы. В моем случае выводу D+ соответствует PD2, выводу D- PD3, а подтяжка висит на PD4. В принципе, подтягивающий резистор можно было соединить и с питанием, но ручное управление им кажется чуть более соответствующим стандарту.

С разъема USB подается питание 5 В, однако на сигнальных линиях ожидается не более 3.6 В (зачем так было сделано для меня загадка). Значит нужно либо понизить питание контроллера, либо поставить стабилитроны на сигнальные линии. Я выбрал второй вариант, но по большому счету это не принципиально.

Раз уж мы «изобретаем» реализацию, было бы неплохо видеть, что же происходит в мозгах контроллера, то есть необходима хоть какая-то отладочная информация. В моем случае это два светодиода на PD6, PD7 и, самое главное, UART на PD0, PD1, настроенный на 115200, так что болтовню контроллера можно будет слушать через обычный screen или другую программу для работы с COM-портом:

$ screen /dev/ttyUSB0 115200

Также полезной утилитой при отладке USB окажется wireshark с соответствующим модулем (он, правда, не всегда запускается с «из коробки», но решение подобных проблем вполне успешно находится в интернете и не является задачей данной статьи).

Здесь можно было бы потратить еще килобайт текста на описание программатора, makefile`ов и прочего, но вряд ли это имеет смысл. Точно так же не буду акцентировать внимание на настройках периферии, не связанных с USB. Если кто-то не может разобраться даже с этим, тем, быть может, и в недра программного USB лезть рано?

Исходный код всех шагов доступен на Github: https://github.com/COKPOWEHEU/vusb_dev/tree/master/vusb_dev

Шаг 1. Принимаем хоть что-то

Согласно документации, USB поддерживает несколько фиксированных скоростей, из которых AVR потянет только самую низкую: 1.5 мегабита в секунду. Она определяется по подтягивающему резистору и последующему общению. Для выбранной нами частоты резистор должен соединять D- с питанием 3.3 В и иметь номинал 1.5 кОм, но на практике можно соединять и с +5 В, и номинал немного варьировать. При частоте контроллера 12 МГц на один бит приходится всего 8 тактов. Понятно, что такая точность и скорость достижимы только на ассемблере, так что заведем файл drvasm.S. Отсюда же следует необходимость использования прерывания для отлова начала приема байта. Радует, что первый байт, передаваемый по USB, всегда одинаковый, SYNC, так что если попадем не в начало, ничего страшного. В результате с момента начала байта до его конца проходит всего 64 такта контроллера (на самом деле запас еще меньше), так что использовать другие прерывания, не связанные с USB не стоит.

Сразу же вынесем конфигурацию в отдельный файл usbconfig.h. Именно там будут заданы выводы, отвечающие за USB, а также используемые биты, константы и регистры.

Теоретическая вставка
Передача по протоколу USB осуществляется пакетами по нескольку байт в каждом. Первым байтом всегда идет байт синхронизации SYNC, равный 0b10000000, вторым — байт-идентификатор пакета PID. Передача каждого байта идет от младшего бита к старшему (это не совсем так, но в vusb эту тонкость игнорируют, учитывая в другом месте) при помощи кодирования NRZI. Этот способ заключается в том, что логический ноль передается изменением логического уровня на противоположный, а логическая единица — не-изменением. Кроме того, вводится защита от рассинхронизации (которой мы пользоваться не будем, но учитывать должны) источника и приемника сигнала: если в передаваемой последовательности есть шесть единиц подряд, то есть шесть тактов подряд состояние выводов не меняется, в передачу добавляется принудительная инверсия, как будто передается ноль. Таким образом размер байта может составлять 8 или 9 бит.
Еще стоит упомянуть о том, что линии данных в USB дифференциальные, то есть когда на D+ высокий уровень, на D- он низкий (это называется K-состояние) и наоборот (J-состояние). Это сделано для лучшей помехозащищенности на высокой частоте. Правда, есть и исключение: сигнал конца пакета (он называется SE0) передается притягиванием обеих сигнальных линий к земле (D+ = D- = 0). Есть еще два сигнала, передаваемые удерживанием на линии D+ низкого напряжения, а на D- высокого, в течение различного времени. Если время небольшое (длина одного байта или чуть больше) то это Idle, пауза между пакетами, а если большое — сигнал сброса.

Итак, передача идет по дифференциальной паре, не считая экзотического случая SE0, но его пока рассматривать не будем. Значит для определения состояния шины USB нам достаточно одной линии, D+ или D-. По большому счету, разницы какую выбрать, нет, но для определенности пусть будет D-.

Начало пакета можно определить по приему байта SYNC после длительного Idle`а. Состоянию Idle соответствует лог.1 на линии D- (оно же J-состояние), а байт SYNC равен 0b100000, но он передается от младшего бита к старшему, да еще закодирован в NRZI, то есть каждый ноль означает инверсию сигнала, а единица — сохранение прежнего уровня. Значит последовательность состояний D- будет следующей:

байт Idle SYNC PID
USB 1..1 00000001 ????????
D- 1..1 01010100 ????????

Начало пакета проще всего детектировать по спадающему фронту, на него и настроим прерывание. Но вдруг во время начала приема контроллер будет занят и не сможет войти в прерывание немедленно? Чтобы в такой ситуации не сбиться со счета тактов, воспользуемся байтом SYNC по прямому назначению. Он сплошь состоит из фронтов на границах битов, так что мы можем подождать один из них, потом еще пол-бита, и попадем прямо в середину следующего. Впрочем, ждать «какого-нибудь» фронта — не лучшая идея, нам ведь надо не просто попасть в середину бита, но и знать в какой по счету бит мы попали. И для этого SYNC также подходит: у него в конце идут два нулевых бита подряд (они же K-состояния). Вот их и будем ловить. Итак, в файле drvasm.S появляется кусок кода от входа в прерывание до foundK. Причем за счет времени на проверку состояния порта, на безусловный переход и прочее, в метку мы попадаем далеко не в начале бита, а как раз к середине. Но проверять этот же бит бессмысленно, мы ведь и так знаем его значение. Поэтому ждем 8 тактов (пока что пустыми nop’ами) и проверяем следующий бит. Если он тоже нулевой, то мы нашли конец SYNC’а и можем переходить к приему значащих битов.

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

Теперь можно скомпилировать полученный код и посмотреть, какие же байты принимает наше устройство. Лично у меня последовательность следующая:

4E 55 00 00 4E 55 00 00 4E 55 00 00 4E 55 00 00 4E 55 00 00 

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

4E
NRZI 01001110 0 (пред. бит)
байт 00101101
2D

55
NRZI 01010101 0 (пред. бит)
байт 00000000
00

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

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

Шаг 2. Демо-версия NRZI

Чтобы не перекодировать вручную, можно поручить это самому контроллеру: операция XOR делает именно то, что нужно, правда результат у нее получается инвертированный, поэтому после нее добавим еще одну инверсию:

mov temp, shift
lsl shift
eor temp, shift
com temp
rcall uart_hex

Результат вполне ожидаем:

2D 00 FF FF 2D 00 FF FF 2D 00 FF FF 2D 00 FF FF 2D 00 FF FF

Шаг 3. Избавляемся от цикла приема байта

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

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

Шаг 4. Читаем в буфер

Читать в отдельные регистры это, конечно, быстро и красиво, но когда данных становится слишком много, лучше использовать запись в буфер, расположенный где-нибудь в оперативке. Для этого в мейне объявим массив достаточного размера, а в прерывании будем туда писать.
Теоретическая вставка

Структура пакетов в USB стандартизована и состоит из следующих частей: байт SYNC, байт PID+CHECK (2 поля по 4 бита), поле данных (иногда 11 бит, но чаще произвольное количество 8-битных байтов) и контрольная CRC сумма размером либо 5 (для 11-битного поля данных), либо 16 (для остальных) бит. И наконец признак конца пакета (EOP) — два бита паузы, но это уже не данные.

Перед работой с массивом надо еще настроить регистры, а свободных nop`ов перед первым битом для этого недостаточно. Поэтому придется вынести чтение двух первых битов в линейный участок кода, между командами которого вставим код инициализации, а потом прыгнем в середину цикла чтения, на метку rxbit2. Кстати о размере буфера. Согласно документации, в одном пакете нельзя передавать больше 8 байт данных. Добавляем служебные байты PID и CRC16, получаем размер буфера 11 байт. Байт SYNC и состояние EOP записывать не будем. Контролировать интервал запросов от хоста мы не сможем, но и терять их не хочется, поэтому для чтения возьмем двойной запас. Пока что мы не будем использовать буфер целиком, но чтобы не возвращаться в будущем, лучше сразу выделить нужный объем.

Шаг 5. Работаем с буфером по-человечески

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

>03 2D 00 10 >01 FF >03 2D 00 10 >01 FF >03 2D 00 10 >01 FF >03 2D 00 10 >01 FF >03 2D 00 10 >01 FF

Шаг 6. Добавляем добавку добавочных нулей

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

unstuff0:                   ;1 (за счет breq)
    andi    x3, ~(1<<0)     ;1 [15] стираем 0-й бит маски. Он инвертирован не будет
    mov     x1, x2          ;1 [16] подменяем предыдущий принятый бит текущим (добавочным)
    in      x2, USBIN       ;1 [17] <-- 1-й бит оказался добавочным. Считываем вместо него настоящий
    ori     shift, (1<<0)   ;1 [18] выставляем 0-й бит в лог.1 чтобы добавка не срабатывала повторно
    rjmp    didUnstuff0     ;2 [20]
;<---//--->
rxLoop:
    eor     shift, x3       ;1 [0]
    in      x1, USBIN       ;1 [1]
    st      y+, shift       ;2 [3]
    ldi     x3, 0xFF        ;1 [4]
    nop                     ;1 [5]
    eor     x2, x1          ;1 [6]
    bst     x2, USBMINUS    ;1 [7] записываем считанный бит в 0-й бит регистра shift
    bld     shift, 0        ;1 [8]
    in      x2, USBIN       ;1 [9] <-- считываем 1-й бит(возможно, добавочный)
    andi    x2, USBMASK     ;1 [10]
      breq  se0             ;1 [11]
    andi    shift, 0xF9     ;1 [12]
didUnstuff0:
      breq  unstuff0        ;1 [13]
    eor     x1, x2          ;1 [14];
    bst     x1, USBMINUS    ;1 [15] записываем считанный бит в 1-й бит регистра shift
    bld     shift, 1        ;1 [16]
rxbit2:
    in      x1, USBIN       ;1 [17] <-- считываем 2-й бит (возможно, добавочный)
    andi    shift, 0xF3     ;1 [18]
      breq  unstuff1        ;1 [19]
didUnstuff1:

Для удобства навигации адреса описываемых команд буду отсчитывать по меткам справа. Обратите внимание, что вводились они для отсчета тактов контроллера, поэтому идут не по порядку. Считывание очередного байта начинается на метке rxLoop, проводится инверсия предыдущего байта и запись в буфер [0, 3]. Далее на метке [1] считывается состояние линии D-, по XOR’у с предыдущим принятым состоянием декодируем NRZI (напоминаю, что обычный XOR добавляет свою инверсию, для исправления чего мы вводим регистр маски x3, инициализируемый единицами 0xFF) и записываем в 0-й бит регистра shift [7,8]. Дальше начинается самое интересное — проверяем не был ли принятый бит шестым неизменным. Неизменному биту, принятому с D- соответствует запись нуля (а не единицы! Менять на единицу будем в конце, XOR’ом) в регистр. Поэтому нужно проверить не являются ли биты 0, 7, 6, 5, 4, 3 нулями. Остальные два бита значения не имеют, они остались от предыдущего байта и были проверены раньше. Чтобы от них избавиться, обрежем регистр по маске [12], где выставлены в 1 все интересующие нас биты: 0b11111001 = 0xF9. Если после наложения маски все биты оказались нулями, ситуация добавления бита зафиксирована и идет переход на метку unstuff0. Там считывается еще один бит [17] взамен считанного ранее, в промежутке между другими операциями, лишнего [9]. Также меняем местами регистры текущего и предыдущего значений x1, x2. Дело в том, что на каждом бите значение считывается в один регистр, а потом XOR`ится с другим, после чего регистры меняются местами. Соответственно, при чтении добавочного регистра эту операцию тоже нужно произвести. Но самое интересное, что в регистр данных shift записываем не ноль, который получили честно, а единицу, которую пытался передать хост [18]. Связано это с тем, что при приеме следующих битов значение нулевого тоже придется учитывать, и если бы мы записали ноль, проверка по маске не могла бы узнать что добавочный бит уже учтен. Таким образом, в регистре shift все биты инвертированы (относительно передаваемого хостом), а нулевой — нет. Чтобы такая каша не была записана в буфер, обратную инверсию будем проводить по XOR’у не с 0xFF [0], а с 0xFE, то есть регистром, в котором соответствующий бит будет сброшен в 0 и, соответственно, не приведет к инверсии. Для этого на отсчете [15] и сбрасываем нулевой бит.

Аналогичная ситуация происходит с битами 1-5. Скажем, 1-й бит соответствует проверке 1, 0, 7, 6, 5, 4, тогда как биты 2, 3 игнорируются. Этому соответствует маска 0xF3.
А вот обработка 6 и 7 битов отличается:

didUnstuff5:
    andi    shift, 0x3F     ;1 [45] проверка битов 5-0
      breq  unstuff5        ;1 [46]
;<---//--->
    bld     shift, 6        ;1 [52]
didUnstuff6:
    cpi     shift, 0x02     ;1 [53] проверка битов 6-1
      brlo  unstuff6        ;1 [54]
;<---//--->
    bld     shift, 7        ;1 [60]
didUnstuff7:
    cpi     shift, 0x04     ;1 [61] проверка битов 7-2
      brsh  rxLoop          ;3 [63]
unstuff7:

Маской для 6-го бита является число 0b01111110 (0x7E), но накладывать ее на регистр shift нельзя, поскольку она сбросит 0-й бит, который должен быть записан в массив. Кроме того, на отсчете [45] уже была наложена маска, обнулившая 7 бит. Значит, обрабатывать добавочный бит надо, если биты 1-6 равны нулю, а 0-й не имеет значения. То есть значение регистра должно быть 0 или 1, что прекрасно проверяется сравнением «меньше, чем 2» [53, 54].
Тот же принцип использован для 7-го бита: вместо наложения маски 0xFC идет проверка на «меньше, чем 4» [61, 63].

Шаг 7. Сортируем пакеты

Раз уж мы можем принимать пакет с первым байтом (PID), равным 0x2D (SETUP), попробуем рассортировать принятое. Кстати, почему это я назвал пакет 0x2D SETUP’ом, когда он вроде бы ACK? Дело в том, что передача по USB от младшего бита к старшему осуществляется в пределах каждого поля, а не байта, тогда как мы принимаем побайтно. Первое значащее поле, PID, занимает всего 4 бита, после чего идет еще 4 бита CHECK, представляющее побитовую инверсию поля PID. Таким образом, первым принятым байтом будет не PID+CHECK, а наоборот, CHECK+PID. Впрочем, особой разницы нет, поскольку все значения известны заранее, и переставить полубайты местами несложно. Вот сразу и запишем в файле usbconfig.h основные коды, которые могут нам пригодиться.
Пока не начали дописывать код обработки PID’а, отметим, что он должен быть быстрым (то есть на ассемблере), но вот выравнивание по тактам не требуется, ведь пакет мы уже приняли. Поэтому впоследствии этот участок будет вынесен в файл asmcommon.inc, который будет содержать ассемблерный код, не привязанный к частоте. А пока просто выделим комментарием.
Теперь перейдем к сортировке принятых пакетов

Теоретическая вставка
Пакеты данных на шине USB объединяются в транзакции. Каждая транзакция начинается с посылки хостом специального маркер-пакета, несущего информацию о том, что же хост хочет делать с устройством: конфигурировать (SETUP), передавать данные (OUT) или принимать их (IN). После передачи маркер-пакета следует пауза длиной в два бита. Дальше следует пакет данных (DATA0 или DATA1), который может быть послан как хостом, так и устройством, в зависимости от маркер-пакета. Далее еще одна пауза в два бита длиной и ответ — HANDSHAKE, пакет подтверждения (ACK, NAK, STALL, их рассмотрим в другой раз).

SETUP DATA0 Handshake
host->device pause host->device pause device->host

OUT DATA0/DATA1 Handshake
host->device pause host->device pause device->host

IN DATA0/DATA1 Handshake
host->device pause device->host pause host->device

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

Итак, мы знаем все типы пакетов, необходимые для обмена. Добавляем проверку принятого байта PID на соответствие каждому. В настоящий момент устройство еще не умеет писать в шину даже такие примитивные пакеты как ACK, а значит неспособно рассказать хосту что же оно такое. Поэтому и команд типа IN ожидать не приходится. Так что проверим только прием команд SETUP и OUT, для чего в соответствующих ветках пропишем включение соответствующих светодиодов.

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

2D|80|06|00|01|00|00|40|00
C3|80|06|00|01|00|00|40|00
2D|80|06|00|01|00|00|40|00
C3|80|06|00|01|00|00|40|00

А кроме того — оба горящих светодиода. Значит, и SETUP и OUT мы поймали.

Шаг 8. Читаем адрес на конверте

Теоретическая вставка
Маркер-пакеты (SETUP, IN, OUT) служат не только для того, чтобы показать устройству, что от него хотят, но и чтобы адресоваться к конкретному устройству на шине и к конкретной конечной точке внутри него. Конечные точки нужны для того, чтобы функционально выделить конкретную подфункцию устройства. Они могут различаться по частоте опроса, скорости обмена и прочим параметрам. Скажем, если устройство представляется переходником USB-COM, его основная задача — принимать данные с шины и передавать их в порт (первая конечная точка) и принимать данные с порта и отдавать в шину (вторая). По смыслу эти точки предназначены для большого потока неструктурированных данных. Но помимо этого, время от времени устройство должно обмениваться с хостом состоянием управляющих линий (всякие RTS, DTR и прочие) и настройками обмена (скорость, проверки четности). А вот тут больших объемов данных не предполагается. Кроме того, удобно, когда служебная информация не смешивается с данными. Вот и получается, что для переходника USB-COM удобно использовать как минимум 3 конечные точки. На практике, конечно, бывает по-разному…
Не менее интересный вопрос, зачем устройству передается его адрес, ведь кроме него в данный конкретный порт все равно ничего не воткнуть. Это сделано для упрощения разработки USB-хабов. Они могут быть достаточно «тупыми» и просто транслировать сигналы от хоста всем устройствам, не заботясь о сортировке. А уж устройство само разберется, обрабатывать пакет или проигнорировать.
Так вот, и адрес устройства, и адрес конечной точки содержатся в маркер-пакетах. Структура таких пакетов приведена ниже:
поле

поле SYNC addr endpoint CRC EOP
Биты USB 0-7 0123456 0123 01234 01
принятые биты 0123456 7012 34567

Из приведенной ранее схемы обмена видно, что сразу после маркер-пакета идет прием (если маркер-пакет содержал PID = SETUP или OUT) или передача (IN) пакета данных, за которым должно следовать подтверждение.

В глобальном смысле это означает, что мы должны от начала транзакции (маркер-пакет) и до самого подтверждения (Handshake) хранить:

  • адрес устройства: если адрес не соответствует нашему, надо не мешать общаться другим, даже NAK не слать
  • тип маркер-пакета: если это SETUP или OUT, идет прием, если IN — передача, причем
  • адрес конечной точки. Пока что неактуально, ведь точка будет всего одна и не будет сильно отличаться от устройства в целом, но в общем случае все же надо знать, с какой именно точкой желает общаться хост

Первый параметр можно оформить как флажок «отвечать — не отвечать» и совместить со вторым. Для второго параметра корректными значениями являются только три PID’а, к которым мы можем добавить четвертый, обозначающий как раз флажок игнора. Красивым значением для такого «PID» будет ноль. Для хранения этого значения заведем переменную usbCurrentTok. А вот PID’ы начала данных (DATA0, DATA1) маркерами не являются, соответственно и сохранять их не будем. Отдельный вопрос что делать с данными, адресованными другому устройству? Варианта два: можно честно их считать, но потом проигнорировать (значение 0 в переменной usbCurrentTok позволит это сделать без проблем), либо даже не считывать, а просто подождать окончания транзакции. Выйти из прерывания до окончания транзакции (событие SE0) мы не можем в любом случае, поскольку хост-то данные передает, и уровни на линиях D+, D- скачут. Значит мы тут же попадем в прерывание снова, но уже не в начало байта SYNC, а неизвестно куда. Теоретически, можно было бы завести отдельный таймер на ожидание окончания обмена по неверному адресу, а после его срабатывания запустить прерывание ожидания начала обмена снова. Но на практике «тупые» хабы встречаются уже редко, и подавляющее большинство посылок будет адресовано именно нам. Да и занимать целый таймер под такое дело не хочется.

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

Как уже упоминалось ранее, передача по USB осуществляется с младшего бита, но в пределах каждого отдельного поля, тогда как наша функция приема работает с байтами и знать не знает про поля. В результате во втором байте, помимо адреса, оказался младший бит конечной точки, а к остальным битам этой точки примешалась контрольная сумма CRC (которую мы все равно не будем считать). Чтобы правильно считать из пакета адрес, просто побитово сдвинем его влево [21]. При этом в бит переноса улетает 0-й бит адреса конечной точки. Чтобы его приклеить на законное место, воспользуемся циклическим сдвигом [26]. Правда, при этом в бездну улетает старший бит CRC, но мы все равно не собирались его использовать.

Шаг 9. Безотказный прием

Пришло время научиться не только принимать данные, но и передавать хосту отчет о приеме, пока что только «пакет принял», то есть ACK. Но для универсальности стоит предусмотреть также передачу и NAK’а, и любого другого байта (храниться он будет в cnt — здесь это не счетчик а просто буфер для данных). Поскольку в USB байты по одному не ходят, сначала сформируем буфер, а потом будем передавать его вместе со всеми обязательными байтами вроде SYNC и PID. Указателем на начало буфера назначим регистр Y, а количество элементов будем хранить в переменной с оригинальным названием cnt (не путать с передачей произвольного байта через тот же регистр). Это все далеко идущие планы, а пока надо передать всего один байт — ACK. Для этого положим его в регистр x3 и сообщим функции что он — буфер размером в 1 байт, ведь к регистрам общего назначения можно обратиться как к обычной памяти. В нашем случае регистр x3 (он же r20) расположен по адресу 20.

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

Как мы помним, передача единицы осуществляется сохранением состояния линий D+, D- (что вообще не требует усилий от контроллера), а передача нуля — изменением. Проще всего это сделать операцией XOR по маске, чтобы не задеть остальные выводы порта, которые, скорее всего, будут использованы под какие-то другие задачи.

Передача байтов, так же как и прием, завязана на точный подсчет тактов, поэтому располагаться будет в части файла, зависящей от частоты. Но, по сравнению с приемом, во время передачи бита свободного времени чуть больше, так что нет нужды проверять добавочные нули на каждом бите независимо. Но и завернуть все в цикл не выйдет. Поэтому авторы vusb применили хитрый трюк: цикл txBitloop обрабатывает по 2 бита за раз ([00], [08]). Прокрутив этот цикл 3 раза, передается 6 бит. Остальные два бита передаются в линейном участке, чтобы можно было разместить код подготовки переменных к передаче следующего байта между строк. Чтобы одновременно уменьшить счетчик на 1 и прокрутить цикл 3 раза применен хитрый способ: от переменной счетчика отнимается число 171. Первые два раза результат получается отрицательным (если счетчик изначально меньше 171, а так как размер буфера не более 11 байт, это условие выполняется всегда), а на третий — неотрицательным, на единицу меньше исходного. Поясню на примере cnt=4:
4 — 171 = -167 = (сколько влезает в байт) 89 (+бит переноса)
89 — 171 = -82 = (сколько влезает в байт) 174 (+бит переноса)
174 — 171 = 3. Столько в байт и влезает, бит переноса не выставляется
то же самое можно доказать и математически, но здесь этого делать не будем.

Таким образом, цикл прокручивается 3 раза, после чего счетчик уменьшается на 1. Добавление добавочных нулей после каждых 6 идущих подряд единиц осуществляется не по маскам, как в случае приема, а просто подсчетом в регистре x4. После передачи всего буфера не забываем переключить D+, D- обратно на вход, восстановить биты прерывания и т. п.
После всех манипуляций наблюдаем зажигание зеленого светодиода и следующий вывод:

2D|80|06|00|01|00|00|40|00
69|00|10|00|01|00|00|40|00

Куда потерялся токен C3 неизвестно. Скорее всего, основной цикл просто не успевает его поймать, ведь весь этот вывод по UART штука довольно медленная. Впрочем, уже то, что к нам начал приходить токен IN означает, что хост подозревает устройство не совсем безнадежным. Значит, мы на верном пути.

Шаг 10. Посылаем хоста NAK

Сама функция передачи NAK у нас уже есть, но пока не было повода ее использовать. А поводом будет наличие принятых данных, которые мы пока не успели обработать. Это хост пусть думает что не успели, на самом-то деле пока что и пытаться не будем.

Но начнем не с посыла хоста, а с двойной буферизации на прием. Мы ведь не хотим, чтобы данные, которые мы только-только приняли и начали обрабатывать, внезапно затерлись другим пришедшим пакетом. Для этого воспользуемся тем же самым буфером usbRxBuf, но разделим его пополам. В одном случае будем писать с нулевого адреса, во втором — с середины, то есть по смещению USB_BUFSIZE. Выбор начала записи будет осуществляться по переменной usbInputBufOffset, которая принимает как раз эти два значения и прибавляется к адресу буфера. Переключение буфера придется добавить в начало функции приема.

Собственно посылка NAK осуществляется из метки handleData в случае если пришел новый пакет, а из предыдущего данные еще не забрали [22]. Факт окончания обработки данных будем отслеживать по равенству нулю счетчика длины (usbRxLen), обнулять который будем в Си-шном коде. А если данных пока не было (задел на будущее — если данные уже обработаны), выставляем количество принятых байт в переменную usbRxLen, а токен, в честь которого данные посланы — в usbRxToken, поскольку разделить SETUP и OUT все-таки надо. Другая особая ситуация: хост может послать пакет с пустым полем данных, в котором обрабатывать вообще нечего, поэтому шлем ACK сразу.

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

2D|80|06|00|01|00|00|40|00

Но устройство принимает только первый, отвечая на остальные NAK`ом, поскольку буфер уже занят, а чистить его пока некому.

Шаг 11. Обрабатываем запросы

С приемом и передачей битов, байтов, пакетов и даже транзакций по большому счету закончили. Пора переходить на более высокий уровень — анализировать запросы хоста и пытаться на них адекватно реагировать. Положительный момент заключается в том, что хост не требует немедленной реакции, так что мы можем перестать, наконец, мучить прерывание, и отдать обработку основному коду. Именно для этого мы выносили буфер и тому подобное в глобальные переменные. Теперь мы можем их читать из основного цикла. Вот только в цикле могут быть и другие задачи, помимо работы с USB, так что лучше сразу вынести эту задачу в отдельную функцию usbPoll. Главное — надо проверить пришли ли вообще данные, или это был пустой пакет. И если пришли — передать их в соответствующую функцию. Как мы помним на примере SETUP пакета, к данным примешиваются еще PID и CRC, но если в SETUP использовалась 5-битная контрольная сумма, то данные защищают аж 16-битной. В результате у нас целых 3 «мусорных» байта. «Мусорных» потому что PID мы уже сохранили в переменной usbRxToken, а CRC проверять не обязательно, вот и не будем отъедать ресурсы контроллера такими глупостями, как надежность. И просто передадим полезные данные в функцию usbProcessRx, попутно указав правильное начало буфера приема, с учетом двойной буферизации и игнорирования токена.

Второй момент, который можно обработать, это упомянутое в самом начале особое состояние — сброс шины, то есть длительное удерживание SE0. Если его обнаружили, надо сбросить состояние модуля USB как будто его выключали по питанию.

Вернемся к обработке принятого пакета. Пока что нас интересуют только пакеты типа SETUP, поскольку устройство еще не сумело рассказать хосту что же оно такое. Так что других пакетов ждать все равно приходится. Структура SETUP пакета описана в usbRequest_t и занимает всегда 8 байт. Запросы делятся на два основных класса: стандартные (которые должно обрабатывать любое USB-устройство) и нестандартные, то есть специфичные для каких-то конкретных. В перспективе отдадим обработку нестандартных запросов на реализацию юзеру, а сами будем обрабатывать стандартные. Но пока что просто будем выводить символы чего же нам прилетело.
А прилетает нам куча стандартных запросов, что, в принципе, не удивительно.

Шаг 12. подробности SETUP’а

Итак, мы выяснили, что хост посылает только стандартные запросы. Разберемся что считается стандартными запросами. Для этого напишем функцию usbDriverSetup, которая на основные известные запросы будет выводить соответствующие символы. Впрочем, некоторые запросы мы можем обработать сразу. Например, запрос или изменение текущей конфигурации (некоторые устройства поддерживают больше одной, но нам это не грозит, так что игнорируем и говорим хосту что у нас все конфигурации одинаковые) или установка адреса. Правда, словарный запас у нашего устройства все равно состоит из двух слов: ACK и NAK, так что сообщить хосту об успешности не выйдет.

Шаг 13. Отправляем ответ

Как было показано раньше, хост начинает общение с пакетов SETUP + DATAx, причем в пакете DATAx хранится всегда 8 байт. После этого хост делает запрос на чтение IN и ждет от устройства пакета DATAx, на который готов выслать подтверждение. До настоящего момента ответный пакет мы отправлять не умели, исправлением чего сейчас и займемся. Собственно функция отправки нескольких байт у нас есть, мы использовали ее чтобы послать ACK или NAK. Но посылка именно одного из них годится только для простых случаев, которые могут быть обнаружены ассемблерным куском кода. Самое главное — посылка всего буфера usbTxBuf, точнее не всего, а только usbTxLen байт из него. Стандартом low-speed USB задан максимальный размер буфера на прием и передачу в 8 байт (плюс PID, плюс два байта CRC), значит и переменная usbTxLen никак не может быть больше 11. Сравним это с допустимыми значениями PID, с учетом того, что старший полубайт является инверсией младшего. Получается, что возможных значений всего 16, из которых наименьшее, 0x0F, вообще считается зарезервированным. Так что значения PID с размером массива никак не спутать, чем и воспользуемся дабы не плодить переменные. Отправлять этот байт будем после приема пакета IN, как единственного места, где устройству вообще разрешено слать данные (handshake пакеты не в счет, они не для данных).

То есть обмен данными выглядит так:
Хост шлет SETUP + DATAx, на что получает ACK или NAK в зависимости от состояния буфера приема. Устройство, точнее, функция usbPoll, как дойдут руки, анализирует принятый пакет и заполняет буфер ответа (пока что все заполнение заключается в записи PID=DATA1 (чем отличаются DATA0 и DATA1 расскажу чуть позже, пока что методом тыка выясняем, что хосту нравится только DATA1). Два байта CRC не заполняем. Послать мы их обязаны в любом случае, но раз данных нет, писать туда что-то не имеет смысла. Ну и в конце концов задаем размер буфера для передачи — 4 байта. Обратите внимание, что формируем мы 3 байта, а посылаем 4. Дело в том, что байт SYNC также будет послан и учитывается при подсчете. Во время подготовки ответа хост может периодически попинывать нас «ты там уже IN или еще NAK?» на что следует ответ NAK. Но вот когда данные наконец готовы, ответ меняется на более осмысленный, DATA1 с содержимым буфера.

Попутно обработаем единственный интересующий нас запрос, не требующий развернутого ответа — USBRQ_SET_ADDRESS (остальные либо не интересны, либо требуют подробного ответа). При помощи него хост пытается назначить нам адрес на шине. Но фактическая запись произойдет только при отправке пустого пакета (drvsdm.S, псевдометка make SE0). Раньше этого делать не стоило, поскольку хост бы не поверил что мы приняли адрес и готовы на него отзываться, а вот теперь, когда мы послали ему целый DATA1 с пустым полем данных, он понимает, что перед ним полноценное устройство и соглашается назначить ему адрес. Правда, он не понимает, почему вместо ответа на остальные запросы ему выдается не осмысленная информация, а такие же заглушки, так что делает несколько попыток выяснить класс устройства и прочие параметры, а после получения нереалистичных ответов признает устройство безнадежным. Исправлением этого недостатка и займемся в дальнейшем, а пока можно прошить устройство данным кодом и полюбоваться, как ему назначают адрес на шине.

Шаг 14. Сортируем стандартные запросы

Прежде чем писать ответы, проанализируем что же именно спрашивает у нас хост. Как мы помним, типичными запросами были USBRQ_GET_DESCRIPTOR и USBRQ_SET_ADDRESS, впрочем, второй мы уже обработали. Теперь напишем функцию usbDriverDescriptor, выясняющую интересующий хоста дескриптор. Находится интересующая нас информация в следующем байте запроса, после собственно USBRQ_GET_DESCRIPTOR. Основные дескрипторы, которые мы будем обрабатывать, это:
USBDESCR_DEVICE — общее описание девайса: тип протокола USB (1.1 в нашем случае), тип устройства, производитель, номер устройства и т. п.
USBDESCR_CONFIG — конфигурация, то есть типы конечных точек, потребляемый ток и т. п.
USBDESCR_STRING — строковые описания устройства, производителя и версии.
Прошиваем, запускаем и видим, что хост много раз пытается выяснить USBDESCR_DEVICE, терпит неудачу, после чего даже не пытается задать остальные вопросы.

Шаг 15. Заполняем анкетные данные

Здесь придется единоразово внести большое количество изменений. Во-первых, надо заполнить структуру описания устройства. Описывать все ее поля не буду, лучше возьмем какую-то более-менее стандартную, например, HID, благо для использования не потребуется драйверов, да и сам протокол обмена не слишком сложный. Среди прочего надо указать Vendor ID и Product ID, которые выдаются только разработчиками стандарта USB, причем за весьма солидную плату. К счастью, авторы vusb приобрели несколько штук и разрешили пользоваться всем желающим при соблюдении соответствующих условий.

Местом для хранения структуры возьмем, разумеется, флеш-память контроллера. Как было сказано выше, у одного устройства может быть несколько дескрипторов, плюс еще строковые значения, так что указатель именно на интересующий нас (точнее, хоста) кусок памяти запишем в переменную usbMsgPtr, а размер — в переменную len, которую впоследствии передадим в usbMsgLen. Размер дескриптора устройства у нас (да и у всех остальных устройств) составляет 18 байт, тогда как в буфер помещается только 8. Значит, будем передавать по частям, в 3 этапа. Если же во время чтения произошла какая-то ошибка, пошлем STALL.

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

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

Теоретическая вставка
Отдельные PID’ы для DATA0 и DATA1 используются для дополнительной защиты от ошибок передачи. Эти PID’ы при нормальной передаче должны чередоваться, так что если встречается два нулевых или два первых подряд, значит что-то пошло не так и надо это исправлять.

Когда мы передавали единственный пустой пакет данных, чередование DATA0 / DATA1 нас не интересовало (как не интересовало и не будет интересовать при приеме), но теперь первая же посылка, дескриптор, занимает аж 3 пакета, значит придется добавить чередование. Для этого применяется операция XOR сначала с одним PID’ом, потом с другим. Как можно понять из определения операции, ее двойное применение равнозначно возвращению в исходное состояние, причем несколько XOR’ов друг другу не мешают. Если первый пакет имел PID равный DATA1, то наложение XOR с тем же PID приведет к обнулению, а наложение XOR с DATA0 доведет изменение до конца.
Прошивка устройства этим кодом дает понять, что хоста ответ удовлетворил, и теперь он запрашивает USBDESCR_CONFIG.

Шаг 16. Наконец-то устройство!

Обработка запроса USBDESCR_CONFIG ничем принципиально не отличается от USBDESCR_DEVICE. Просто добавляем новый дескриптор (он уже менее стандартный, но описывать его я все равно не буду) и отсылаем. И наше устройство, наконец-то определяется системой именно как USB-устройство, а не просто непонятная кривулька, дрыгающая линиями D+, D-.

Раз уж добавление дескрипторов оказалось такой простой задачей, добавим еще и все строковые описания: устройства, производителя, версию и язык. Строки различаются по номерам, указанным в трех предпоследних байтах дескриптора устройства (нулевой номер соответствует списку языков, который не совсем строка). Стоит отметить, что для строк используется кодировка UTF-16, то есть каждый символ кодируется двумя байтами. Что помешало разработчикам стандарта USB использовать стандартную и удобную UTF-8 мне неизвестно.

В случае vusb наличие этих строк не просто приятная особенность, позволяющая просто по выводу lsusb определить что же подключено. Доступных свободно пар VID, PID у нас не так уж много, поэтому отличить устройство именно по ним не выйдет. А вот ограничений на формат строк описания мне неизвестно, поэтому искать нужное устройство будем по паре VID, PID, а потом уточнять наше — не наше именно по строковым описаниям.

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

Шаг 17. Устройство становится человечнее (HID)

Теоретическая вставка
HID — human interface device, специальный тип устройств, предназначенный прежде всего для взаимодействия с человеком, с учетом его технических ограничений. То есть для HID важнее скорость реакции, но не объем передаваемых данных или скорость. Кроме того, упор делался на стандартизацию стандартных устройств, чтобы при подключении, скажем, мышки, не пришлось устанавливать специальные драйвера от производителя. Ну и упрощение «мозга» устройств тоже было важным моментом. В результате при разработке стандарта HID решили пожертвовать скоростью обмена (для low-speed она составляет всего лишь около 800 байт в секунду), но отсутствие необходимости в специальных драйверах компенсирует многое.

Для общения по протоколу HID придется завести еще один дескриптор, передавая его по запросу USBDESCR_HID_REPORT. Поскольку реализация конкретного устройства не привязана к ядру библиотеки vusb, ее стоит отдать на откуп пользователю. Так, для функции usbDriverSetup (стандартные запросы) появляется параллельная usbFunctionSetup (юзерские запросы). Кроме того, раньше мы использовали только пакеты SETUP, но теперь надо сделать отдельный обработчик для пакетов OUT. Системные данные через них не передаются, так что принятое будет отправлено напрямую в юзерский код, точнее, в функцию usbFunctionWrite.
Доработать функцию чтения тоже придется, дополнив usbDeviceRead альтернативой usbFunctionRead, которая вместо дескрипторов и тому подобного заполняет буфер передачи юзерскими данными. Чтобы код знал, какую из альтернатив запускать, в функции usbFunctionSetup взводится (или не взводится, если не нужен) специальный флажок USB_FLG_USE_USER_RW, а в usbDriverSetup он безоговорочно сбрасывается.

Полезный функционал устройства — мигалка диодами и опрос кнопки — осуществляются именно в usbFunctionWrite и usbFunctionRead. Просто анализируем пришедшие данные и работаем с кнопкой и светодиодами. Единственная сложность — данные могут прийти в несколько этапов, так что их придется склеивать из кусочков.
Напоследок подчистим прочие стандартные запросы в usbDriverSetup.

Шаг 18. Общаемся с железкой

Устройство готово, но чтобы его проверить до конца, надо научиться посылать ему запросы и получать ответы. Поскольку устройство у нас HID, писать драйвер не придется, можно ограничиться программой, запускаемой с правами обычного пользователя (но вот права доступа к самому устройству в udev прописать все-таки придется). К сожалению, кроссплатформенной библиотеки под эту задачу я не нашел, пришлось писать свою. Подробно расписывать ее работу особого смысла не вижу, кому будет интересно, разберется по коду, либо воспользуется без подробного изучения.
Теперь устройство успешно распознается операционной системой и реагирует на посылаемые байты.

Шаг 19. Сравниваем с vusb

Чтобы получить оригинальный код vusb осталось разделить наши три файла на части по смыслу и густо обмазать механизмами переносимости, защиты и универсальности.
drvasm.S делится на частото-независимые usbdrvasm.S и asmcommon.inc, а также частото-зависимый, один на выбор, usbdrvasm12.inc — usbdrvasm20.inc.
main.c делится на собственно main.c (юзерский код) и usbdrv.c (ядро библиотеки vusb)
usbconfig.h тонким слоем размазывается между предыдущими (некоторые объявления нужны только конкретному файлу), но основная суть, настройка конфигурации, остается в usbconfig.h.

Заключение

В отличие от оригинальной библиотеки vusb, наша версия состоит из одного файла на Си и одного на Ассемблере, не считая констант, общих для обоих файлов. Кроме того, нельзя поменять частоту кварца, конечная точка всего одна и т. п. Впрочем, целью работы являлось не это, а пошаговое написание простого устройства, определяемого системой как USB-HID. Разделение на файлы, добавление других частот, коррекция частоты, добавление других конечных точек и прочие плюшки уже реализованы в оригинальной vusb, либо могут быть допилены самостоятельно, благо теперь, надеюсь, назначение каждого элемента кода стало понятнее.

Использованная литература и полезные ссылки

https://www.obdev.at/products/vusb/index.html (оф сайт vusb)
http://microsin.net/programming/arm-working-with-usb/usb-in-a-nutshell-part1.html
Агуров П.В. Интерфейсы USB: Практика использования и программирования
https://radiohlam.ru/tag/usb/
http://we.easyelectronics.ru/electro-and-pc/usb-dlya-avr-chast-1-vvodnaya.html
http://usb.fober.net/cat/teoriya/

P.S. Из-за особенностей местного редактора таблиц (наверное, это я с ним плохо разобрался) они выглядят гораздо менее информативно, чем хотелось

Теги:

Source: habr1

Метки:





Запись опубликована: 21.07.2019

Общие компоненты силами разных команд. Доклад Яндекса

Создание и сопровождение общих компонентов — процесс, в котором должны быть заняты множество команд. Руководитель службы общих компонентов Яндекса Владимир Гриненко tadatuta объяснил, как их разработка переросла выделенную команду «Лего», как мы сделали монорепозиторий на базе GitHub с помощью Lerna и настроили Canary-релизы с внедрением в сервисы прямо в CI, что для этого понадобилось, а что ещё предстоит.



— Рад вас всех приветствовать. Меня зовут Владимир, я занимаюсь общими штуками в интерфейсах Яндекса. Про них и хочу поговорить. Наверное, если вы не очень глубоко пользуетесь нашими сервисами, у вас может возникнуть вопрос: что мы все верстаем? Что там верстать?





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

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



На самом деле сервисов в Яндексе, действительно, поболее. И их даже чуть поболее, чем на этом слайде. За каждой такой ссылкой стоит куча разных интерфейсов с огромной вариативностью. Они есть для разных устройств, на разных языках. Они работают иногда даже в автомобилях и прочих разных странных вещах.


Яндекс сегодняшний — это не только веб, не только разные товары со складами, доставкой и всем таким. Машинки желтые ездят. И даже не только то, что можно съесть, и не только железяки. И не только всякие автоматические интеллекты. Но все перечисленное объединяет то, что для каждого пункта нужны интерфейсы. Зачастую — очень богатые. Яндекс — это сотни разных огромных сервисов. Мы постоянно создаем что-то новое, каждый день. У нас трудятся тысячи сотрудников, среди которых сотни именно фронтендеров, разработчиков интерфейсов. Эти люди работают в разных офисах, живут в разных часовых поясах, на работу постоянно приходят новые ребята.

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

Это выдача поиска по документам в интернете. Но если мы перейдем на выдачу по картинкам — шапка совпадает, несмотря на то, что это отдельный репозиторий, которым занимается абсолютно отдельная команда, возможно, даже на других технологиях. Казалось бы, что там сложного? Ну сверстали два раза шапку, вроде дело нехитрое. За каждой кнопочкой в шапке тоже свой отдельный богатый внутренний мир. Тут появляются какие-то попапы, там тоже что-то можно понажимать. Все это переводится на разные языки, работает на разных платформах. И вот мы переходим с картинок, например, на видео, и это снова новый сервис, другая команда. Опять другой репозиторий. Но все равно та же шапка, хотя есть отличия. И все это нужно оставить единообразным.

Чего стоит, переключаясь вот так по слайдам, убедиться, что ничего никуда на пиксель не съехало? Мы стараемся, чтобы этого не происходило.

Чтобы еще чуть-чуть показать масштаб, я взял скриншот репозитория, который хранит только код фронтенда для новых браузеров — только выдачи по документам, без картинок и видео. Тут десятки тысяч коммитов и почти 400 контрибьюторов. Это только в верстке, только один проект. Вот список синих ссылок, который вы привыкли видеть.

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

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

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

Или, вот, допустим, есть отдельный сервис Яндекс.Эфир, который чуть менее чем полностью состоит из похожих сниппетов.

Или, допустим, сниппет видео в нотификаторе, который есть на разных страницах портала.

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

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

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

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

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

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

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

Начинали мы в незапамятные времена, еще в SVN, и было лампово и удобно: папочка с HTML, прямо как в Bootstrap. Вы его копипастите к себе. Рядом папочка со стилями, какой-то там JS, который тогда умел что-то просто показать/скрыть. И все.

Как-то так выглядел список компонентов. Здесь подсвечен b-domeg, который отвечал за авторизацию. Возможно, вы еще помните, на Яндексе, действительно, была такая форма для логина и пароля, с крышей. Мы называли «домик», хотя она намекала на почтовый конверт, потому что входили, обычно, в почту.

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

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

Репозиторий сейчас выглядит вот так. Видите, тоже почти 10 тысяч коммитов и более 100 контрибьюторов.

А вот это папка того самого b-домика в новой реинкарнации. Сейчас она выглядит вот так. Там внутри уже собственных папок больше, чем на пол-экрана.

А так выглядит сайт на сегодняшний день.

В итоге, общая библиотека используется в более чем 360 репозиториях внутри Яндекса. И есть разные реализации, отлаженный релизный цикл и т. д. Казалось бы, вот, получили общую библиотеку, давайте ее теперь везде использовать, и все здорово. Проблема внедрения общих штучек куда угодно решена. На самом деле нет.

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

И даже если потом в общей библиотеке появилось общее решение, все равно окажется так, что вам придется теперь заново перевнедрять все то, что успели понаверстать на каждом сервисе. И это опять проблема. Очень сложно обосновать. Вот сидит команда. У нее есть свои цели, все уже хорошо работает. И мы такие говорим — смотрите, наконец-то у нас есть общая штучка, возьмите ее. А команда такая — у нас и так работы хватает. Зачем нам? Тем более, вдруг там что-то не будет подходить нам? Не хотим.

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

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

И начать нужно, как ни странно, не с разработчиков интерфейсов, а с дизайнеров. Они это тоже понимают. У нас внутри происходит несколько одновременных попыток, чтобы этот процесс сошелся. Дизайнеры делают дизайн-системы. Я очень надеюсь, что рано или поздно получится свести их в единую общую систему, которая учитывает все потребности.

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

А один из способов решить проблему донесения информации — позволить разработчикам познакомиться с другими командами, включая ту, которая занимается общими компонентами интерфейсов. Мы ее с этой стороны решаем тем, что у нас появился Буткемп, который при появлении разработчика в Яндексе первым делом позволяет ему в течение восьми недель походить в разные команды, посмотреть, как там что устроено, и потом сделать выбор, где он будет работать окончательно. Но за это время у него заметно расширится кругозор. Он будет ориентироваться, где что есть.

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

Эту версию нам нужно опубликовать в npm, а дальше пойти в репозиторий какого-то проекта, где используется библиотека, и внедрить эту версию. Скорее всего, это поправить какую-то чиселку в package.json, перезапустить сборку. Возможно, еще перегенерировать package-lock, создать pull request, посмотреть, как пройдут тесты. И что мы увидим?

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

Причем это еще хорошо, когда мы внедряем в один сервис, и прямо там сразу все сломалось. Гораздо печальнее, когда мы все это проделали, внедрили в десять разных сервисов. Там ничего не сломалось. Мы уже пошли заваривать смузи, или что там нужно. В это время версия внедряется в 11-й проект, или в 25-й. И там находится баг. Мы возвращаемся по всей цепочке, делаем патч и внедряем его во все предыдущие 20 сервисов. Причем этот patch может взорваться в каком-то из предыдущих. Ну и так далее. Весело.

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

Ладно. На самом деле, схема может быть примерно следующей. Нам поможет автоматизация. Именно об этом, в общем-то, весь рассказ, на самом деле. Мы придумали, что репозиторий с общей библиотекой может быть построен по схеме монорепозиториев. Вы наверняка сталкивались, сейчас много таких проектов, особенно инфраструктурных. Всякие Babel, и тому подобные штуки, живут, как монорепозитории, когда внутри лежит много разных npm-пакетов. Они, возможно, связаны как-то между собой. И управляются, например, через Lerna, чтобы все это удобно паблишить, учитывая зависимости.

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

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

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

Как это выглядит на практике? Вот пулл-реквест с исправлением. Здесь видно, что автоматика призвала туда нужных ревьюверов, чтобы они проверили, что в коде все хорошо. Действительно, ревьюверы пришли и договорились, что все хорошо. И в этот момент разработчик просто пишет специальную команду /canary, прямо в пулл-реквесте.

Приходит робот и говорит — окей, я создал задачу на то, чтобы случилось следующее чудо. Чудо в том, что теперь выпустилась canary-версия с этими изменениями и она автоматически была внедрена во все репозитории, где используется этот компонент. Там запустились автотесты, как и в этом репозитории. Здесь видно, что запустилась целая куча проверок.

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

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

Что мы получили? Общий монорепозиторий, в котором отстроены линтеры. То есть все пишут код одинаково, в нем есть все виды тестов. Любая команда может прийти, положить свой компонент и проверить его JS unit-тестами, покрыть его скриншотами и т. д. Все уже будет из коробки. Умное код-ревью, которое я уже упоминал. Оно у нас благодаря богатым внутренним инструментам и правда умное.

Разработчик сейчас в отпуске? Звать его в пулл-реквест бессмысленно, система это учтет. Разработчик заболел? Система это тоже учтет. Если оба условия не выполнились и разработчик, кажется, свободен — ему прилетит уведомление в какой-то из его мессенджеров, по его выбору. И он такой: нет, сейчас я занят чем-то срочным или на встрече. Он может прийти туда и просто написать команду /busy. Система автоматически поймет, что нужно назначить следующего из списка.

Следующий шаг — публикация той самой canary-версии. То есть нам с любым изменением кода нужно выпустить служебный пакет, который мы сможем проверить на разных сервисах. Дальше нам нужно запустить тесты при внедрении на все эти сервисы. А когда все сошлось — запустить релизы.

Если в изменении затронута какая-то статика, которая должна загружаться с CDN, нужно ее автоматически опубликовать отдельно. Это тоже работает из коробки. При выпуске релиза нам, конечно, нужно знать, что происходило, что менялось. Но так как мы хотим, чтобы все это было автоматически, нужно, чтобы changelog был сформирован автоматом и где-то публиковался.

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

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

Source: habr1

Метки:





Запись опубликована: 21.07.2019

Как передать данные между микроконтроллерами на 100 Mbps

Встала передо мной такая вот проблема — надо передавать данные между двумя микроконтроллерами STM32F407 хотя бы на скорости 100 Mbps. Можно было бы использовать Ethernet (MAC-to-MAC), но вот беда — он занят, именно из него и берутся эти данные…
Из незадействованной периферии есть разве что SPI — но он только 42 Mbps.

Как ни странно, ничего готового в сети не нашлось. И я решил реализовать параллельный тактируемый регистр на 8 бит. А что — частоту можно задать в 10 Мгц (то есть, конечно, собственно такты вдвое быстрее, но и 20 Мгц не есть что-то сложное) — так что с такой невысокой частотой не придется мучиться с разводкой платы. А скорость как раз и будет 100 Mbps.

Сказано — сделано. В общем виде система выглядит так. На передающей стороне используем таймер, один из сигналов сравнения выводим на пин — это будет тактовый сигнал, а второй будем использовать для запуска одной пересылки (burst) для DMA.
Шина у меня на частоте 82 МГц (из-за потребления тока на большей частоте :), таймер на той же частоте: так что при периоде ARR = 8 получается 10 Мгц примерно (стало быть будет около 80 Mbps, ну да и ладно).

DMA будет по такту пересылать один байт из памяти (с автоинкрементом, конечно) прямо в порт вывода регистра — в моем случае подошел PORTE — его первые 8 бит как раз и подходят как адрес приемника DMA.

На приемной стороне тактовый сигнал будем по обоим перепадам использовать для тактирования таймера, с периодом 1, а сигнал update будем использовать для запуска пересылки для DMA, который читает данные из порта (опять подошел порт PORTE) и записывает в память с автоинкрементом.

Теперь осталось правильно настроить все (код ниже) и запустить. Завершение на обеих сторонах определяется по прерыванию от DMA.

Однако, для полноты картины нужно конечно включить в код проверки на задержки передачи и обработку ошибок, но это я опускаю.

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

volatile int transmit_done;
volatile int receive_done;
void DMA2_Stream1_IRQHandler(void) {
    TIM8->CR1 &= ~TIM_CR1_CEN;
    DMA2->LIFCR |= 0b1111 << 8;
    receive_done = 1;
}
void DMA2_Stream4_IRQHandler(void) {
    TIM1->CR1 &= ~TIM_CR1_CEN;
    TIM1->EGR |= TIM_EGR_BG;
    DMA2->HIFCR |= 0b1111101;
    transmit_done = 1;
}
void ii_receive(uint8_t *data, int len) {
    GPIOE->MODER = (GPIOE->MODER & 0xFFFF0000) | 0x0000;
    DMA2_Stream1->PAR = (uint32_t) &(GPIOE->IDR);
    DMA2_Stream1->M0AR = (uint32_t) data;
    DMA2_Stream1->NDTR = len;
    TIM8->CNT = 0;
    TIM8->BDTR |= TIM_BDTR_MOE;
    receive_done = 0;
    DMA2_Stream1->CR |= DMA_SxCR_EN;
    TIM8->CR1 |= TIM_CR1_CEN;
}
void ii_transmit(uint8_t *data, int len) {
    GPIOE->MODER = (GPIOE->MODER & 0xFFFF0000) | 0x5555;
    DMA2_Stream4->PAR = (uint32_t) &(GPIOE->ODR);
    DMA2_Stream4->M0AR = (uint32_t) data;
    DMA2_Stream4->NDTR = len;
    TIM1->CNT = 6;
    transmit_done = 0;
    DMA2_Stream4->CR |= DMA_SxCR_EN;
    TIM1->SR |= TIM_SR_BIF;
    TIM1->BDTR |= TIM_BDTR_MOE;
    TIM1->CR1 |= TIM_CR1_CEN;
}
// tx: TIM1 CH4 on DMA2/stream4/channel6, CH1 on output clock in PE9
// rx: TIM8 CH2 on DMA2/stream3/channel7, CH1 on input clock in PC6
void ii_init() {
    __HAL_RCC_GPIOC_CLK_ENABLE();
    __HAL_RCC_GPIOE_CLK_ENABLE();
    __HAL_RCC_TIM1_CLK_ENABLE();
    __HAL_RCC_TIM8_CLK_ENABLE();
    __HAL_RCC_TIM2_CLK_ENABLE();
    __HAL_RCC_DMA2_CLK_ENABLE();
    GPIOC->MODER |= (0b10 << GPIO_MODER_MODE6_Pos)
            | (0b10 << GPIO_MODER_MODE7_Pos);
    GPIOC->PUPDR |= (0b10 << GPIO_PUPDR_PUPD7_Pos);
    GPIOC->AFR[0] |= (GPIO_AF3_TIM8 << 24) | (GPIO_AF3_TIM8 << 28);
    GPIOE->MODER |= (0b10 << GPIO_MODER_MODE9_Pos);
    GPIOE->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR9 | 0xFFFF;
    GPIOE->AFR[1] |= GPIO_AF1_TIM1 << 4;
    GPIOE->PUPDR |= (0b10 << GPIO_PUPDR_PUPD9_Pos);
    TIM1->ARR = 8;
    TIM1->CCR1 = 5;
    TIM1->CCR4 = 1;
    TIM1->EGR |= TIM_EGR_CC4G;
    TIM1->DIER |= TIM_DIER_CC4DE;
    TIM1->CCMR1 |= (0b110 << TIM_CCMR1_OC1M_Pos);
    TIM1->CCER |= TIM_CCER_CC1E;
    TIM1->EGR |= TIM_EGR_BG;
    TIM8->ARR = 1;
    TIM8->CCR2 = 1;
    TIM8->EGR |= TIM_EGR_UG;
    TIM8->DIER |= TIM_DIER_UDE;
    TIM8->SMCR |= (0b100 << TIM_SMCR_TS_Pos) | (0b111 << TIM_SMCR_SMS_Pos);
    TIM8->CCMR1 = (0b01 << TIM_CCMR1_CC1S_Pos)  (0b110 << TIM_CCMR1_OC2M_Pos);
    TIM8->CCER = (0b11 << TIM_CCER_CC1P_Pos)  TIM_CCER_CC2E;
    DMA2_Stream1->CR = DMA_CHANNEL_7  DMA_PRIORITY_VERY_HIGH  DMA_MINC_ENABLE
             (0b00 << DMA_SxCR_DIR_Pos)  DMA_SxCR_TCIE  DMA_SxCR_TEIE
             DMA_SxCR_DMEIE;
    DMA2_Stream1->FCR = DMA_FIFOMODE_ENABLE;
    DMA2_Stream4->CR = DMA_CHANNEL_6  DMA_PRIORITY_VERY_HIGH  DMA_MINC_ENABLE
             (0b01 << DMA_SxCR_DIR_Pos)  DMA_SxCR_TCIE  DMA_SxCR_TEIE
             DMA_SxCR_DMEIE;
    DMA2_Stream4->FCR = DMA_FIFOMODE_ENABLE;
    HAL_NVIC_SetPriority(DMA2_Stream1_IRQn, 0, 0);
    HAL_NVIC_EnableIRQ(DMA2_Stream1_IRQn);
    HAL_NVIC_SetPriority(DMA2_Stream4_IRQn, 0, 0);
    HAL_NVIC_EnableIRQ(DMA2_Stream4_IRQn);
}

Для тестов была использована одна и та же плата, просто тактовый выход PE9 был соединен с входом PC6. Главный цикл выглядел так:

 ii_receive(rdata, 256);
 ii_transmit(tdata, 256);
 while (!transmit_done);
 while (!receive_done);

По результатам: данные отлично пересылались за 30-31 микросекунду без потерь. Сигналы выглядят как-то так:


здесь белый — выход таймера TIM8, красный — тактовый сигнал (TIM1), ну а оранжевый — это младший бит данных (0-1-0-1-…).

Что не нравится при этом — ну нельзя никак запускать DMA от прерывания от входа GPIO, вот и приходится работать с таймерами. Может, кто-нибудь подскажет другой способ?

Теги:

Source: habr1

Метки:





Запись опубликована: 21.07.2019

Чтение на лето: книги для технарей

Мы собрали книги, которые рекомендуют своим коллегам по цеху резиденты Hacker News. Здесь нет справочников или руководств по программированию, зато есть любопытные издания о криптографии и теоретической информатике, об основателях IT-компаний, есть и научная фантастика, написанная разработчиками и о разработчиках — как раз то, что можно взять в отпуск.


Фото: Max Delsid / Unsplash.com

Наука и технологии

What Is Real?: The Unfinished Quest for the Meaning of Quantum Physics

Ученые и философы долгие годы пытались дать определение тому, что же такое «реальность». Астрофизик и писатель Адам Беккер (Adam Becker) обращается к квантовой механике в попытке привнести ясность в этот вопрос и оспорить популярные «мифы о реальности».

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

The New Turing Omnibus: Sixty-Six Excursions in Computer Science

Сборник увлекательных эссе, написанный канадским математиком Александром Дьюдни (Alexander Dewdney). Статьи охватывают основы теоретической информатики — от алгоритмов до системной архитектуры. Каждая из них построена вокруг головоломок и задач, наглядно иллюстрирующих тему. Несмотря на то, что второе и, на данный момент, последнее издание было опубликовано в далеком 1993 году, информация в книге до сих пор актуальна. Является одной из любимых книг Джеффа Этвуда, основателя StackExchange. Он советует ее программистам-практикам, которые нуждаются в свежем взгляде на теоретическую сторону профессии.

Crypto

В книге «Крипто» журналист Стивен Леви (Steven Levy), еще с 80-х годов раскрывающий в своих материалах вопросы информационной безопасности, попытался собрать сведения о самых важных событиях в становлении цифрового шифрования. Он расскажет про то, как формировалась криптография и соответствующие стандарты, а также про движение «Шифропанков».

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

Фото: Drew Graham / Unsplash.com

Жизнь 3.0. Быть человеком в эпоху искусственного интеллекта

Профессор Массачусетского технологического института Макс Тегмарк (Max Tegmark) является одним из ведущих специалистов по вопросам теории систем искусственного интеллекта. В книге «Жизнь 3.0» он рассказывает о том, как появление ИИ повлияет на функционирование нашего общества и на то, какой смысл мы будем вкладывать в понятие «человечество».

Он рассматривает самые разные сценарии возможного развития событий — от порабощения человеческой расы до утопического будущего под защитой ИИ, и приводит научные доводы. Не обойдется и без философской составляющей с рассуждениями о сущности «сознания» как такового. Эту книгу рекомендуют, в частности, Барак Обама и Илон Маск.

Стартапы и soft skills

Беспроигрышные переговоры с экстремально высокими ставками

Переговоры — нетривиальный процесс. Особенно, если другая сторона имеет над вами преимущество. Бывший сотрудник ФБР Крис Восс (Chris Voss) знает это не понаслышке, так как лично вел переговоры о высвобождении заложников из рук преступников и террористов.
Крис свел свою переговорную стратегию к ряду правил, которые можно применить для того, чтобы добиваться желаемого в повседневных ситуациях: от обсуждения проекта до условий заслуженного повышения. Каждое правило проиллюстрировано историями из профессиональной деятельности автора. Эту книгу рекомендуют сразу несколько резидентов Hacker News, и все они отмечают ее исключительную практическую полезность в рабочих коммуникациях.


Фото: Banter Snaps / Unsplash.com

Как двое парней создали игровую индустрию и воспитали поколение геймеров

Имя id Software — разработчиков Doom и Quake — известно многим. Чего нельзя сказать об истории этой удивительной компании. Книга «Masters Of Doom» рассказывает про взлет проекта и его необычных основателей — тихого интроверта Кармака и импульсивного экстраверта Ромеро.
Написана она умелой рукой Дэвида Кушнера (David Kushner), редактора журнала Rolling Stone и победителя престижных журналистских премий. Вы узнаете, почему подход Кармака, Ромеро и их коллег к разработке игр оказался таким удачным, а сами Doom и Quake остаются «на слуху» уже много лет. Также речь пойдет о тяжелых решениях, принятых в процессе развития компании, и подходе к менеджменту, который позволил id Software добиться такого успеха.

Candid Conversations with the Visionaries of the Digital World

Это сборник интервью с успешными предпринимателями из сферы IT. Среди них как широко известные личности — Стив Джобс, Майкл Делл и Билл Гейтс, так и менее популярные «гиганты» из энтерпрайз-пространства — CEO Silicon Graphics Эдвард МакКракен (Edward McCracken) и основатель DEC Кен Ольсен (Ken Olsen). Всего в книге 16 интервью про ведение бизнеса в IT и технологии будущего, а также небольшие биографии людей, у которых эти интервью взяли. Стоит отметить, что книга вышла в 1997 году, когда Джобс только вернулся на пост CEO Apple, поэтому интервью с ним особенно интересно — уже с исторической точки зрения.

Художественная литература

Вспомни о Флебе

Помимо «Осиной Фабрики» и других постмодернистских романов, признанный шотландский писатель Иэн Бэнкс (Ian M. Banks) работал и в жанре научной фантастики. Его цикл книг, посвященный утопическому обществу «Культуры», обзавелся большим сообществом фанатов, включая, например, Илона Маска и многих резидентов Hacker News.
Первая книга в цикле — «Вспомни о Флебе» — повествует о войне между Культурой и Идиранской империей. А также о принципиальных различиях между социал-анархической, гедонистической жизнью в симбиозе с искусственным интеллектом, с одной стороны, и религиозным мировоззрением противников такой жизни — с другой. Кстати, в прошлом году Amazon приобрели права на экранизацию романа для своего стримингового сервиса.

Периодическая система

Сборник итальянского химика и писателя Примо Леви (Primo Levi) — это 21 рассказ, каждый из которых назван в честь определенного химического элемента. В них идет речь о научной деятельности автора на фоне событий Второй Мировой войны. Вы прочитаете про начало его карьеры химика, жизнь сефардской общины во Франции, заключение автора в Освенциме и необычные эксперименты, которые он проводил на свободе. В 2006 году Королевский институт Великобритании назвал «Периодическую систему» лучшей научной книгой в истории.

Sum: Fourty Tales from the Afterlives

Спекулятивная фантастика авторства видного американского ученого-нейробиолога Дэвида Иглмэна (David Eagleman), сейчас преподающего в Стэнфорде. Дэвид посвятил свою жизнь исследованию нейропластичности, восприятия времени и других аспектов нейронауки. В этой книге он предлагает 40 гипотез о том, что происходит с нашим сознанием, когда мы умираем. Автор рассматривает различные метафизические системы и их потенциальное влияние на нашу смерть. В книге есть и черный юмор, и серьезные вопросы, а в основе материала лежат знания, которые Иглмэн приобрел в процессе профессиональной деятельности. В числе любителей книги — основатель Stripe Патрик Коллинсон и другие фигуры из мира IT.

Фото: Daniel Chen / Unsplash.com

Avogadro Corp: The Singularity Is Closer Than It Appears

Еще один научно-фантастический роман, на этот раз — про потенциальные последствия достижения сингулярности. Дэвид Райан, главный герой книги, занимается достаточно простой задачей — пишет программу по оптимизации почтовой переписки внутри компании. Когда менеджмент ставит под вопрос существование проекта, Дэвид интегрирует в него систему искусственного интеллекта, чтобы их переубедить. На проект выделяют дополнительные ресурсы — человеческие и компьютерные, и, незаметно для всех, простая программа для написания писем начинает манипулировать собственными программистами. Работу одобрили многие видные имена Кремниевой долины. Сам автор книги Уильям Хертлинг (William Hertling) — программист и один из основателей компании по производству решений в сфере кибербезопасности Tripwire. По его словам, события, описанные в книге, с каждым годом становятся все более вероятными.

Что еще интересного у нас есть на Хабре:

Source: habr1

Метки:





Запись опубликована: 20.07.2019

Опыт применения GSM модуля в домашней автоматизации

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

image

Мой план заключался в том, чтобы создать простое и дешевое устройство, оснащенное двумя датчиками температуры, датчиком влажности, GSM модулем, а также твердотельным реле и розеткой для подключения нагрузки. То, что получилось в итоге, можно увидеть на фото. В качестве датчика температуры и влажности был выбран климатический сенсор BME280, его канал давления не используется. На фото его можно увидеть под прозрачным колпачком слева от основного модуля. Такое расположение уменьшает влияние тепловыделения внутри корпуса на показания датчика. В качестве колпачка используется китайская пластиковая пробирка с двумя отверстиями для вентиляции. Второй датчик температуры выносной, сделан на DS18B20. Он расположен внутри металлического зонда, с корпусом соединен кабелем через обычный аудио разъем для наушников. Зонд предназначен для измерения температуры непосредственно отопительной системы. Основной объем корпуса занимает твердотельное реле (я выбрал помощнее) и преобразователь из 220В в 5В для питания схемы. Розетка для подключения нагрузки смонтирована на задней стороне корпуса, на фото она не видна. OLED дисплей на базе контроллера SH1106 отображает показания датчиков, а также показывает, включена ли нагрузка. Для управления всей системой используется модуль Arduino Pro Mini в версии 3.3В 8МГц. Я не большой фанат этой платформы, но обилие библиотек, в том числе заботливо выпиленных автором, делает ее оптимальным выбором, когда нужно быстро сделать что то простое.

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

image

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

В гаражах, где я частенько бываю, недавно поставили на въезде шлагбаум, который открывается, если позвонить на определенный номер. Судя по всему, он сделан на похожем GSM модуле. Меня удивило, как сложно бывает дозвониться по этому номеру, чтобы он открылся. Теперь я знаю множество причин для этого. Это знание стоило мне нескольких месяцев экспериментов и внушительного количества потраченных на них денег. Я надеюсь, что теперь это знание послужит кому то еще. Рассмотрим, на что важно обратить внимание, продвигаясь от очевидных аппаратных проблем к менее очевидным программным.

Первое, что важно сделать правильно, — вставить сим-карту.

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

Чтобы хорошо работать, нужно хорошо питаться.

Требования к питанию у GSM модуля достаточно специфические. Он сделан на базе микросхемы, разработанной для кнопочных мобильных телефонов, и рассчитан на питание непосредственно от литиевого аккумулятора. Поэтому, 5В для него много, а 3.3В — мало. К тому же, в режиме передачи на максимальной мощности он способен потреблять ток до 2А. Если источник питания не способен обеспечить нужный ток, GSM модуль может перезагрузиться при попытке регистрации в сети и продолжить перезагружаться в бесконечном цикле. Периоды пикового потребления обычно длятся меньше секунды, поэтому есть соблазн применить слаботочный стабилизатор с накопителем энергии для периодов пиковой нагрузки. В качестве такого накопителя можно применить литиевый аккумулятор. При этом важно обеспечить возможность его отключения и важно не забыть ей воспользоваться, иначе отключение устройства от сети закончится глубоким разрядом аккумулятора и его необратимым повреждением. Другой вариант — это поставить вместо аккумулятора ионистор (суперконденсатор). Он не боится глубокого разряда. Но у него тоже есть проблемы с надежностью. Одна ячейка ионистора обычно рассчитана на напряжение от 2.5 до 3В. Ионисторы, рассчитанные на большее напряжение, состоят из нескольких ячеек (обычно из 2-х). При этом, однако, дисбаланс напряжения на ячейках может закончится пробоем ячейки. Такой дисбаланс легко получить за счет разницы в емкости ячеек или разницы в токе утечки. Следует также учитывать параметр внутреннего сопротивления ионистора. Ионисторы с большим внутренним сопротивлением на больших токах бесполезны, а ионисторы с малым сопротивлением стоят не дешевле аккумулятора. После того, как у меня ионистор скоропостижно скончался из-за дисбаланса ячеек, я просто применил преобразователь из 220В в 5В достаточной мощности. Чтобы понизить напряжение до нужного GSM модулю, я поставил между преобразователем и модулем обычный кремниевый диод. На таком диоде обычно падает 0.7В, так что модулю достаются необходимые 4.3В. После диода полезно поставить электролитический конденсатор большой емкости. Он сгладит провалы напряжения при внезапном включении передатчика.

От передающей антенны лучше держаться подальше.

Даже после того, как я обеспечил GSM модулю требуемое питание, симптом перезагрузки периодически проявлялся, но на этот раз перезагружалась Arduino. Наблюдение за ее питанием при помощи осциллографа показало, что питание тут непричем. Судя по всему помеху создавал передатчик модуля, поскольку проблема возникала тем чаще, чем хуже были условия приема сигнала базовой станции. Столь радикальный эффект помех от передающей антенны вполне объясним, если вспомнить, что передатчик модуля способен выдать в антенну 2 ватта. Такая мощность может за 5 минут вскипятить миллилитр воды или нагреть ваше ухо на несколько градусов. Для борьбы с этой проблемой были опробованы разные методы. Для начала я подключил внешнюю антенну, которая располагалась снаружи корпуса и соединялась с модулем коротким коаксиальным кабелем. Однако, ожидаемого эффекта это не дало. Тогда я расположил модуль в отдельном металлическом корпусе, к которому снаружи крепилась антенна. Стало лучше, но не сильно. Радикально улучшил ситуацию только вынос антенны на некоторое расстояние от устройства за счет ее подключение коаксиальным кабелем достаточной длины.

Почему так происходит, легко понять из физических соображений. Типичная антенна — это ‘четвертьволновой штырь’, то есть половинка от дипольной антенны. Но, чтобы создать электрическое поле, половинки диполя недостаточно, нужна вторая половинка, тогда между отрицательно и положительно заряженными элементами антенны возникнет электрическое поле. У правильной штыревой антенны второй половиной является либо поверхность земли, либо корпус прибора, либо специальные проводящие ‘противовесы’. Но для маркетологов все это слишком сложно, поэтому нам обычно продают только половинку от нормальной антенны. Как же она работает? Очень просто — второй половинкой является кабель, которым подключена антенна. То, что он экранирован, ничего не меняет. Внешняя поверхность его оплетки играет роль второй половинки дипольной антенны. При этом помеха легко наводится на проходящие по соседству провода несмотря на то, что кабель казалось бы экранирован. Ну а если кабеля нет, например мы спрятали модуль в металлический экран, из которого торчит антенна? Если экран большой (по сравнению с длиной волны), то он работает, как вторая половина излучателя, а если маленький, то излучают прочие провода, которые подведены к этому модулю, совершенно не важно, какие. Следующий рисунок иллюстрирует вышесказанное (плюсы и минусы показаны для наглядности, в реальности заряд элементов антенны меняет знак с частотой несущей).

image

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

Мораль заключается в том, что единственный надежный способ защититься от помех, создаваемых передающей антенной, — унести ее подальше от остальной электроники, подключив коаксиальным кабелем достаточной длины. Какая длина является достаточной? Расстояние естественно соизмерять с длиной волны, в данном случае это максимум 30 см. Это и есть минимальное расстояние на которое следует отнести антенну, но чем дальше, тем лучше.

Не все последовательные порты одинаково полезны.

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

Суть проблемы в том, что программная реализация последовательного порта запрещает прерывания на все время передачи или приема очередного символа. Казалось бы, что в этом плохого, так многие делают. Например, реализация протокола 1-Wire для чтения термометров Dallas Semiconductor тоже запрещает прерывания на время передачи одного бита, то есть на 65 микросекунд. Это конечно тоже не слишком хорошо. Если в системе есть другие обработчики прерываний, они не смогут обеспечить время реакции на прерывание меньше этих 65 микросекунд. Если запрос на прерывание приходит, когда они запрещены, он будет обработан только после того, как прерывания разрешат снова. Например, аппаратный последовательный порт использует прерывания для того, чтобы положить в буфер приемника очередной принятый символ. Если следующий символ придет, пока не обработано прерывание от предыдущего, тот будет потерян. Это значит, что работать со скоростью больше 115200 бит в секунду аппаратный последовательный порт не сможет. В случае программной реализации последовательного порта все хуже. Для его работы нужно, чтобы время реакции на прерывание было меньше времени передачи одного бита. Это ограничивает нас скоростью 9600 бит в секунду.

Более серьезная проблема заключается в том, что программная реализация последовательного порта сама запрещает прерывания. Причем время, на которое она их запрещает (время передачи или приема одного символа) всегда примерно в 10 раз больше, чем максимальное время обработки прерывания, требуемое для корректной работы приемника того же программного последовательного порта. То есть, он всегда мешает сам себе до такой степени, что одновременно не может принимать и отправлять данные. Конечно, в большинстве случаев это и не требуется. В большинстве, но не в нашем случае с GSM модулем. Он таки может неожиданно для нас по собственной инициативе начать передавать данные (например при получении SMS сообщения). И в случае применения программной реализации последовательного порта это легко может привести к сбою протокола обмена с модулем. Поэтому, я просто применил один и тот же аппаратный последовательный порт и для программирования Arduino и для общения с GSM модулем. Неудобно конечно, но это единственный способ сделать надежно работающее устройство.

Асинхронному протоколу — асинхронный обработчик.

Асинхронный протокол — это такой протокол, при котором одна сторона обмена может начать передавать информацию неожиданно для другой стороны, то есть без всякой синхронизации с ее сообщениями. Именно таков протокол обмена с GSM модулем. Он исправно отвечает на запросы со стороны Arduino, но может и начать передавать что то свое, например сообщить о принятом SMS сообщении. И это создает реальную проблему, поскольку ни одна из известных мне библиотек для работы с модулем под Arduino асинхронность протокола не учитывает вообще никак. Представим себе, что Arduino передала модулю команду, а модуль в тот же самый момент передал информацию о принятом SMS сообщении. Эта информация будет принята вместо ответа на команду. В результате в качестве ответа на команду библиотека вернет ошибку (в лучшем случае, в худшем все ‘повиснет’), а сообщение о принятом SMS будет потеряно.

Починить это легко — нужно просто написать свой, асинхронный обработчик протокола. Асинхронный обработчик предъявляет только необходимый минимум требований к ответам модуля на его команды. На каждую команду модуль в итоге отвечает либо OK, либо ERROR. И это все, что нужно для того, чтобы зафиксировать ответ. Все остальные строки, которые приходят от модуля, обрабатываются независимо от того, пришли они в ответ на команду или сами по себе. Смысл этих строк всегда можно определить по их началу. Если строка начинается с +CSQ, то она содержит информацию о качестве сигнала. Если она начинается +CMT, то это информация о полученном SMS, и в ней содержится адрес отправителя. Первая строчка посылается в составе ответа на команду AT+CSQ, авторую модуль присылает по собственной инициативе, но для нас это различие абсолютно несущественно. Принятые SMS сообщения модуль направляет непосредственно в последовательный порт. Это позволяет избежать чтения их из памяти и последующего удаления. Чтобы мы могли распознать SMS сообщения в общем потоке сообщений от модуля, они должны начинаться с символа #, в противном случае сообщение игнорируется.

Созданная автором библиотека, реализующая вышеописанный подход, находится здесь github.com/olegv142/SimpleSIM

Чтобы получать строки, начинающиеся с определенной последовательности символов, клиент создает специальный объект — ловушку. Таких ловушек он может создать любое количество. Полученные от модуля строки, отличные от OK, ERROR, которые не попали ни в одну из ловушек, просто игнорируются. Поскольку такая архитектура не требует полного анализа ответов модуля на множество различных типов команд, код библиотеки в разы компактнее любой из известных мне библиотек.

Что в итоге?

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

image

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

github.com/olegv142/GsmMon

Теги:

Source: habr1

Метки:





Запись опубликована: 20.07.2019

Настройка сервера для развертывания Rails приложения при помощи Ansible

  • Tutorial

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

Первым делом стоит понимать, что ansible предоставляет вам удобный интерфейс для выполнения заранее определенного списка действий на удаленном сервере (серверах) через SSH. Тут нет никакой магии, нельзя поставить плагин и получить из коробки zero downtime деплой своего приложения с докером, мониторингом и прочими плюшками. Для того чтобы написать плейбук вы должны знать что именно вы хотите сделать и как это сделать. Поэтому меня не устраивают готовые плейбуки с гитхаба, или статьи вида: “Скопируйте и запустите, — будет работать”.

Что нам нужно?

Как я уже говорил, для того чтобы написать плейбук надо знать, что вы хотите сделать и как это сделать. Давайте определимся с тем, что нам нужно. Для Rails приложения нам понадобится несколько системных пакетов: nginx, postgresql (redis, e.t.c.). Помимо этого нам нужен ruby определенной версии. Ставить его лучше всего через rbenv (rvm, asdf…). Запускать все это из под root пользователя — всегда плохая идея, поэтому надо создать отдельного пользователя, и настроить ему права. После этого необходимо залить наш код на сервер, скопировать конфиги для nginx, postgres, e.t.c. и запустить все эти сервисы.

В итоге последовательность действий такая:

  1. Логинимся под рутом
  2. устанавливаем системные пакеты
  3. создаем нового пользователя, настраиваем права, shh ключ
  4. настраиваем системные пакеты (nginx e.t.c) и запускаем их
  5. Создаем пользователя в БД (можно сразу и базу создать)
  6. Логинимся новым пользователем
  7. Устанавливаем rbenv и ruby
  8. Устанавливаем бандлер
  9. Заливаем код приложения
  10. Запускаем Puma сервер

Причем последние этапы можно делать при помощи capistrano, по крайней мере она из коробки умеет копировать код в релизные директории, переключать релиз симлинком при успешном деплое, копировать конфиги из shared директории, рестартовать puma и.т.д. Все это можно сделать и при помощи Ansible, но зачем?

Файловая структура

Ansible имеет строгую файловую структуру для всех своих файлов, поэтому лучше всего держать все это в отдельной директории. Причем не так важно, будет она в самом rails приложении, или отдельно. Можно хранить файлы в отдельном git репозитории. Лично мне удобнее всего оказалось создать директорию ansible в /config директории rails приложения и хранить все в одном репозитории.

Simple Playbook

Playbook — это yml файл, в котором при помощи специального синтаксиса описано, что и как ansible должен сделать. Давайте создадим первый плейбук, который не делает ничего:

---
- name: Simple playbook
  hosts: all

Здесь мы просто говорим, что наш playbook называется Simple Playbook и что выполняться его содержимое должно для всех хостов. Мы можем сохранить его в /ansible директории с именем playbook.yml и попробовать запустить:

ansible-playbook ./playbook.yml
PLAY [Simple Playbook] ************************************************************************************************************************************
skipping: no hosts matched

Ansible говорит что не знает хостов, которые бы соответсвовали списку all. Их надо перечислить в специальном inventory файле.

Давайте создадим его в той же ansible директории:

123.123.123.123

Вот так просто указываем хост (в идеале хост своего VPS для тестов, или же можно localhost прописать) и сохраняем его под именем inventory.
Можно попробовать запустить ansible с invetory файлом:

ansible-playbook ./playbook.yml -i inventory
PLAY [Simple Playbook] ************************************************************************************************************************************
TASK [Gathering Facts] ************************************************************************************************************************************
PLAY RECAP ************************************************************************************************************************************

Если у вас есть доступ по ssh к указанному хосту то ansible подключится и соберет информацию об удаленной системе. (дефолтный TASK [Gathering Facts] ) после чего даст краткий отчет о выполнении (PLAY RECAP).

По умолчанию для соединения используется имя пользователя под которым вы залогинены в системе. На хосте его, скорее всего, не будет. В playbook файле можно указать какого пользователя использовать для подключения при помощи директивы remote_user. Так же информация об удаленной системе вам часто может быть ненужна и не стоит стратить время на ее сбор. Эту задачу так же можно выключить:

---
- name: Simple playbook
  hosts: all
  remote_user: root
  become: true
  gather_facts: no

Попробуйте еще раз запустить playbook и убедиться что соединение работает. (Если вы указали root пользователя, то так же надо указать директиву become: true, что бы получить повышенные права. Как написано в документации: become set to ‘true’/’yes’ to activate privilege escalation. хотя не совсем понятно, зачем).

Возможно вы получите ошибку вызыванную тем, что ansible не может определить интерпритатор питона, тогда его можно указать вручную:

ansible_python_interpreter: /usr/bin/python3 

где у вас лежит python можно узнать командой whereis python.

Установка системных пакетов

В стандартной поставке Ansible входит множетсво модулей для работы с различными системными пакетами, благодаря чему нам не приходится по любому поводу писать bash скрипты. Сейчас нам понадобится один из таких модулей для обновления системы и установки системных пакетов. У меня на VPS стоит Ubuntu Linux соответсвенно для установки пакетов я использую apt-get и модуль для него. Если у вас используется другая операционная система то, возможно, понадобится другой модуль (помните я в начале говорил, что надо заранее знать что и как будем делать). Однако синтаксис, скорее всего будет похожим.

Дополним наш плейбук первыми задачами:

---
- name: Simple playbook
  hosts: all
  remote_user: root
  become: true
  gather_facts: no
  tasks:
    - name: Update system
      apt: update_cache=yes
    - name: Install system dependencies
      apt:
        name: git,nginx,redis,postgresql,postgresql-contrib
        state: present

Task — это как раз задача которую ansible будет выполнять на удаленных серверах. Мы даем задаче имя, что бы отслеживать ее выполнение в логе. И описываем, при помощи синтаксиса конкретного модуля, что ему нужно сделать. В данном случае apt: update_cache=yes — говорит обновить пакеты системы при помощи модуля apt. Вторая команда несколько сложнее. Мы передаем в модуль apt список пакетов, и говорим что их state должен стать present, тоесть говорим установить эти пакеты. Похожим образом, мы можем сказать их удалить, или обновить, просто поменяв state. Обратите внимание, что для работы rails с postgresql нам нужен пакет postgresql-contrib, который мы сейчас устанавливаем. Об этом опять же надо знать и сделать, ansible сам по себе этого делать не будет.

Попробуйте запустить playbook еще раз и проверить, что пакеты установятся.

Создание новых пользователей.

Для работы с пользователями у Ansible так же есть модуль — user. Добавим еще один task (я скрыл уже известные части плейбука за комментариям, что бы не копировать его целиком каждый раз):

---
- name: Simple playbook
  # ...
  tasks:
    # ...
    - name: Add a new user
      user:
        name: my_user
        shell: /bin/bash
        password: "{{ 123qweasd | password_hash('sha512') }}"

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

---
- name: Simple playbook
  # ...
  tasks:
    # ...
    - name: Add a new user
      user:
        name: "{{ user }}"
        shell: /bin/bash
        password: "{{ user_password | password_hash('sha512') }}"

при помощи двойных фигурных скобок в плэйбуках устанавливаются переменные.

Значения переменных мы укажем в inventory файле:

123.123.123.123
[all:vars]
user=my_user
user_password=123qweasd

Обратите внимание на директиву [all:vars] — она говорит о том, что следующий блок текста — это переменные (vars) и они применимы для всех хостов (all).

Так же интересна конструкция "{{ user_password | password_hash('sha512') }}". Дело в том, что ansible не устанавливает пользователя через user_add как вы бы делали это вручную. А сохраняет все данные напрямую, из-за чего пароль мы так же должны заранее преобразовать в хэш, что и делает данная команда.

Давайте добавим нашего пользователя в группу sudo. Однако, перед этим необходимо убедиться что такая группа есть потому что за нас этого никто делать не будет:

---
- name: Simple playbook
  # ...
  tasks:
    # ...
    - name: Ensure a 'sudo' group
      group:
        name: sudo
        state: present
    - name: Add a new user
      user:
        name: "{{ user }}"
        shell: /bin/bash
        password: "{{ user_password | password_hash('sha512') }}"
        groups: "sudo"

Все достаточно просто, у нас так же есть модуль group для создания групп, с синтаксисом очень похожим на apt. После чего достаточно прописать эту группу пользователю (groups: "sudo").
Так же полезно добавить этому пользователю ssh ключ, что бы мы могли логиниться под ним без пароля:

---
- name: Simple playbook
  # ...
  tasks:
    # ...
    - name: Ensure a 'sudo' group
      group:
      name: sudo
        state: present
    - name: Add a new user
      user:
        name: "{{ user }}"
        shell: /bin/bash
        password: "{{ user_password | password_hash('sha512') }}"
        groups: "sudo"
    - name: Deploy SSH Key
      authorized_key:
        user: "{{ user }}"
        key: "{{ lookup('file', '~/.ssh/id_rsa.pub') }}"
        state: present

В данном случае интересна конструкция "{{ lookup('file', '~/.ssh/id_rsa.pub') }}" — она копирует содержимое файла id_rsa.pub (у вас название может и отличаться), тоесть публичную часть ssh ключа и загружает его в список авторизованных ключей для пользователя на сервер.

Роли

Все три задачи для создания пользоваться можно легко отнести к одной группе задач, и неплохо было бы хранить эту группу отдельно от основного плейбука, что бы он слишком не разрастался. Для этого в ansible существуют роли.
Согласно указанной в самом начале файловой структуре, роли необходимо положить в отдельную директорию roles, для каждой роли — отдельная директория с аналогичным названием, внутри директории tasks, files, templates, e.t.c.
Cоздадим файловую структуру: ./ansible/roles/user/tasks/main.yml (main — это основной файл который будет подгружаться и выполняться при подключении роли к плейбуку, в нем можно подключать другие файлы роли). Теперь можно перенести в этот файл все задачи относящиеся к пользователю:

# Create user and add him to groups
- name: Ensure a 'sudo' group
  group:
    name: sudo
    state: present
- name: Add a new user
  user:
    name: "{{ user }}"
    shell: /bin/bash
    password: "{{ user_password | password_hash('sha512') }}"
    groups: "sudo"
- name: Deploy SSH Key
  authorized_key:
    user: "{{ user }}"
    key: "{{ lookup('file', '~/.ssh/id_rsa.pub') }}"
    state: present

В основном же плейбуке необходимо указать использовать роль user:

---
- name: Simple playbook
  hosts: all
  remote_user: root
  gather_facts: no
  tasks:
    - name: Update system
      apt: update_cache=yes
    - name: Install system dependencies
      apt:
        name: git,nginx,redis,postgresql,postgresql-contrib
        state: present
  roles:
    - user

Так же, возможно имеет смысл выполнять обновление системы раньше всех остальных задач, для этого можно переименовать блок tasks в котором они определены в pre_tasks.

Настройка nginx

Nginx у нас должен быть уже установлен, необходимо его сконфигурировать и запустить. Давайте делать это сразу в роли. Создаем файловую структуру:

- ansible
  - roles
    - nginx
      - files
      - tasks
        - main.yml
      - templates

Теперь нам понадобятся файлы и шаблоны. Разница между ними в том, что файлы ansible копирует напрямую, как есть. А шаблоны должны иметь расширение j2 и в них можно использовать значения переменных при помощи тех же двойных фигурных скобок.

Давайте включим nginx в main.yml файле. Для этого у нас есть модуль systemd:

# Copy nginx configs and start it
- name: enable service nginx and start
  systemd:
    name: nginx
    state: started
    enabled: yes

Тут мы не только говорим, что nginx должен быть started (тоесть запускаем его), но сразу говорим что он должен быть enabled.
Теперь скопируем конфигурационные файлы:

# Copy nginx configs and start it
- name: enable service nginx and start
  systemd:
    name: nginx
    state: started
    enabled: yes
- name: Copy the nginx.conf
  copy:
    src: nginx.conf
    dest: /etc/nginx/nginx.conf
    owner: root
    group: root
    mode: '0644'
    backup: yes
- name: Copy template my_app.conf
  template:
    src: my_app_conf.j2
    dest: /etc/nginx/sites-available/my_app.conf
    owner: root
    group: root
    mode: '0644'

Мы создаем основной конфигурационный файл nginx (можно взять его прямо с сервера, или написать самостоятельно). И так же конфигурационный файл для нашего приложения в sites_available директоорию (это не обязательно но полезно). В первом случае мы используем модуль copy для копирования файлов (файл должен лежать в /ansible/roles/nginx/files/nginx.conf). Во втором — копируем шаблон, подставляя значения переменных. Шаблон должен лежать в /ansible/roles/nginx/templates/my_app.j2). И выглядеть он может примерно так:

upstream {{ app_name }} {
  server unix:{{ app_path }}/shared/tmp/sockets/puma.sock;
}
server {
  listen 80;
  server_name {{ server_name }} {{ inventory_hostname }};
  root {{ app_path }}/current/public;
  try_files $uri/index.html $uri.html $uri @{{ app_name }};
  ....
}

Обратите внимание на вставки {{ app_name }}, {{ app_path }}, {{ server_name }}, {{ inventory_hostname }} — это все переменные, значения которых ansible подставит в шаблон перед копированием. Это полезно, если использовать плейбук для разных групп хостов. Например мы может дополнить наш inventory файл:

[production]
123.123.123.123
[staging]
231.231.231.231
[all:vars]
user=my_user
user_password=123qweasd
[production:vars]
server_name=production
app_path=/home/www/my_app
app_name=my_app
[staging:vars]
server_name=staging
app_path=/home/www/my_stage
app_name=my_stage_app

Если мы запустим теперь наш плейбук, то он выполнит указанные задачи для обоих хостов. Но при этом для staging хоста переменные будут отличатся от production, и не только в ролях и плейбуках, но и в конфигах nginx. {{ inventory_hostname }} не надо указывать в inventory файле — это специальная перменная ansible и там хранится хост для которого выполняется плейбук в данный момент.
Если вы хотите иметь inventory файл для нескольких хостов, а запускать только для одной группы, это можно сделать следующей командой:

ansible-playbook -i inventory ./playbook.yml -l "staging"

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

Вернемся к настройке nginx. После копирования конфигурационных файлов нам необходимо создать симлинк в sitest_enabled на my_app.conf из sites_available. И перезапустить nginx.

... # old code in mail.yml
- name: Create symlink to sites-enabled
  file:
    src: /etc/nginx/sites-available/my_app.conf
    dest: /etc/nginx/sites-enabled/my_app.conf
    state: link
- name: restart nginx
  service:
    name: nginx
    state: restarted

Тут все просто — опять модули ansible с достаточно стандартным синтаксисом. Но есть один момент. Перезапускать nginx каждый раз не имеет смысла. Вы обратили внимание, что мы не пишем команды вида: «сделать вот это вот так», синтаксис выглядит скорее как «вот у этого должно быть вот такое состояние». И чаще всего именно так ansible и работает. Если группа уже существует, или системный пакет уже установлен, то ansible проверит это и пропустит задачу. Так же файлы не будут копироваться, если они полностью совпадают с тем, что уже есть на сервере. Мы можем этим воспользоваться и перезапускать nginx только если конфигурационные файлы были изменены. Для этого существует директива register:

# Copy nginx configs and start it
- name: enable service nginx and start
  systemd:
    name: nginx
    state: started
    enabled: yes
- name: Copy the nginx.conf
  copy:
    src: nginx.conf
    dest: /etc/nginx/nginx.conf
    owner: root
    group: root
    mode: '0644'
    backup: yes
  register: restart_nginx
- name: Copy template my_app.conf
  template:
    src: my_app_conf.j2
    dest: /etc/nginx/sites-available/my_app.conf
    owner: root
    group: root
    mode: '0644'
  register: restart_nginx
- name: Create symlink to sites-enabled
  file:
    src: /etc/nginx/sites-available/my_app.conf
    dest: /etc/nginx/sites-enabled/my_app.conf
    state: link
- name: restart nginx
  service:
    name: nginx
    state: restarted
  when: restart_nginx.changed

Если один из конфигурационных файлов меняется, то будет выполнено копирование и зарегестрирована переменная restart_nginx. И только если эта переменная была зарегистрирована, выполнится перезапуск сервиса.

Ну и, конечно, нужно добавить роль nginx в основной playbook.

Настройка postgresql

Нам необходимо включить postgresql при помощи systemd точно так же как мы это делали с nginx, а так же создать пользователя, которого мы будет использовать для доступа к базе данных и саму базу данных.
Создадим роль /ansible/roles/postgresql/tasks/main.yml:

# Create user in postgresql
- name: enable postgresql and start
  systemd:
    name: postgresql
    state: started
    enabled: yes
- name: Create database user
  become_user: postgres
  postgresql_user:
    name: "{{ db_user }}"
    password: "{{ db_password }}"
    role_attr_flags: SUPERUSER
- name: Create database
  become_user: postgres
  postgresql_db:
    name: "{{ db_name }}"
    encoding: UTF-8
    owner: "{{ db_user }}"

Я не буду расписывать, как добавлять переменные в inventory, это уже делалось много раз, так же как и синтаксис модулей postgresql_db и postgresql_user. Больше данных можно найти в документации. Тут наиболее интересна директива become_user: postgres. Дело в том, что по умолчанию доступ к postgresql базе есть только у пользователя postgres и только локально. Данная директива позволяет нам выполнять команды от имени этого пользователя (если конечно у нас есть доступ).
Так же, возможно, вам придется дописать строку в pg_hba.conf что бы открыть доступ новому пользователю к базе. Это можно сделать так же, как мы меняли конфиг nginx.

Ну и конечно надо добавить роль postgresql в основной плейбук.

Установка ruby через rbenv

В ansible нет модулей для работы с rbenv, а устанавливается он путем клонирования git репозитория. Поэтому эта задачка становится самой нестандартной. Создадим для нее роль /ansible/roles/ruby_rbenv/main.yml и начнем ее заполнять:

# Install rbenv and ruby
- name: Install rbenv
  become_user: "{{ user }}"
  git: repo=https://github.com/rbenv/rbenv.git dest=~/.rbenv

Мы опять используем директиву become_user что бы работать из под созданного нами для этих целей пользователя. Так как rbenv устанавливается в его home директории, а не глобально. И так же мы используем модуль git для того, что бы склонировать репозиторий, указывя repo и dest.

Далее нам необходимо прописать rbenv init в bashrc и там же добавиьт rbenv в PATH. Для этого у нас есть модуль lineinfile:

- name: Add rbenv to PATH
  become_user: "{{ user }}"
  lineinfile:
    path: ~/.bashrc
    state: present
    line: 'export PATH="${HOME}/.rbenv/bin:${PATH}"'
- name: Add rbenv init to bashrc
  become_user: "{{ user }}"
  lineinfile:
    path: ~/.bashrc
    state: present
    line: 'eval "$(rbenv init -)"'

После чего надо установить ruby_build:

- name: Install ruby-build
  become_user: "{{ user }}"
  git: repo=https://github.com/rbenv/ruby-build.git dest=~/.rbenv/plugins/ruby-build

И, наконец, установить ruby. Это делается через rbenv, тоесть просто bash командой:

- name: Install ruby
  become_user: "{{ user }}"
  shell:
    export PATH="${HOME}/.rbenv/bin:${PATH}"
    eval "$(rbenv init -)"
    rbenv install {{ ruby_version }}
  args:
    executable: /bin/bash

Мы говорим, какую команду выполнить и чем. Однако тут мы наткнемся на то, что ansible не запускает код, содержащийся в bashrc перед запуском команд. А значит, rbenv придется определять прямо в этом же скрипте.

Следующая проблема связана с тем, что shell команда не имеет состояния с точки зрения ansible. Тоесть автоматической проверки, установлена эта версия ruby или нет — не будет. Мы можем сделать это самостоятельно:

- name: Install ruby
  become_user: "{{ user }}"
  shell:
    export PATH="${HOME}/.rbenv/bin:${PATH}"
    eval "$(rbenv init -)"
    if ! rbenv versions  grep -q {{ ruby_version }}
      then rbenv install {{ ruby_version }} && rbenv global {{ ruby_version }}
    fi
  args:
    executable: /bin/bash

И остается установить bundler:

- name: Install bundler
  become_user: "{{ user }}"
  shell:
    export PATH="${HOME}/.rbenv/bin:${PATH}"
    eval "$(rbenv init -)"
    gem install bundler

И опять же добавить нашу роль ruby_rbenv в основной плейбук.

Shared files.

В целом на этом настройку можно было бы закончить. Далее остается запустить capistrano и оно само скопирует код, создаст нужные каталоги и запустит приложение (если настроено все верно). Однако зачастую capistrano необходимы дополнительные конфигурационные файлы, такие как database.yml или .env Их можно скопировать точно так же как файлы и шаблоны для nginx. Есть только одна тонкость. Перед копированием файлов необходимо создать для них структуру каталогов, что-то вроде такого:

# Copy shared files for deploy
- name: Ensure shared dir
  become_user: "{{ user }}"
  file:
    path: "{{ app_path }}/shared/config"
    state: directory

мы указываем только одну директорию и ansible автоматически создаст родительские, если нужно.

Ansible Vault

Мы уже натыкались на то, что в переменных могут оказываться секретные данные такие как пароль пользователя. Если вы создали .env файл для приложения, и database.yml то там, должно быть еще больше таких критичных данных. Их хорошо бы скрыть от посторонних глаз. Для этого используется ansible vault.

Создадим файл для перменных /ansible/vars/all.yml (тут можно создавать разные файлы для разных групп хостов, точно так же как в inventory файле: production.yml, staging.yml, e.t.c).
В этот файл необходимо перенести все переменные, которые должны быть зашифрованы, используя стандартный yml синтаксис:

# System vars
user_password: 123qweasd
db_password: 123qweasd
# ENV vars
aws_access_key_id: xxxxx
aws_secret_access_key: xxxxxx
aws_bucket: bucket_name
rails_secret_key_base: very_secret_key_base

После чего этот файл можно зашифровать командой:

ansible-vault encrypt ./vars/all.yml

Естественно, при шифровании необходимо будет установить пароль для дешифровки. Можете посмотреть, что окажется внутри файла после вызова этой команды.

При помощи ansible-vault decrypt файл можно расшифровать, изменить и потом зашифровать снова.
Для работы расшифровывать файл не надо. Вы храните его в зашифрованном виде и запускаете playbook с аргументом --ask-vault-pass. Ansible спросит пароль, достанет переменные и выполнит задачи. Все данные останутся зашифрованными.

Полностью команда для нескольких групп хостов и ansible vault будет выглядеть примерно так:

ansible-playbook -i inventory ./playbook.yml -l "staging" --ask-vault-pass

А полный текст плейбуков и ролей я вам не дам, пишите сами. Потому что ansible штука такая — если не понимаешь что надо сделать, то и он тебе не сделает.

Теги:

Source: habr1

Метки:





Запись опубликована: 20.07.2019

[Перевод] Консенсус на репутации ноды. Нужен ли?

  • Перевод

Знаю-знаю. Криптопроектов тьма, есть куча консенсусов: на основе труда и владения, золота, нефти, выпеченных пирожков (есть и такой, да-да). Что нам ещё от одного? Это и предлагаю обсудить после прочтения перевода «облегченной» технической документации проекта *Созвездие (Constellation). Конечно, это не полное описание алгоритма, но мне интересно мнение комьюнити хабра, имеет ли место «быть» такому консенсусу или он даром не нужен?

Дальше букв не много, поэтому, если вам просто хочется написать «фу, сколько можно о крипте», то, пожалуйста, воздержитесь. Если вам интересны новые разработки в сфере распределенных систем и есть, чем поделиться в комментариях, то прошу под кат.

P.S. Я не автор технологии, за полную передачу сути ручаться не могу, поэтому буду рад комментариям с поправками, если такие будут.

Эволюция от синхронных консенсусов к асинхронным

Узлы выбираются с использованием детерминированного процесса (того же, который используется в DHT, например, bittorrent), который динамически регулирует обязанности узлов для “облегчения” валидации или, что более понятно, для достижения консенсуса. Мы выбираем группы из 3 узлов и выполняем раунды консенсуса параллельно, чтобы один узел мог быть фасилитатором в нескольких блоках. Это позволяет нам обрабатывать транзакции асинхронно, что, по сути, означает, что у нас одновременно формируется несколько блокчейнов. Процесс подобен паутине, образованной множеством нитей, в отличие от узлов, формирующих одну цепочку с течением времени. Асинхронная или параллельная обработка являются основой масштабируемого программирования, поскольку оно позволяет использовать все ресурсы компьютера, ускоряя общие вычисления. Эта сеть называется ориентированным ациклическим графом или DAG в компьютерных науках.


Ширина канала линейного блокчейна против мультипликативного эффекта DAG, где у нас есть несколько параллельных блокчейнов.


Геометрическая реализация линейного блокчейна против DAG. Черные точки — это блоки, белые точки — это узлы

Мы используем 3 узла в каждом раунде консенсуса, потому что это дает нам некоторые интересные математические процессы для рассуждения о состоянии, формируя «плоскость поверхности» поперек данных в форме треугольников со связями. Затем протокол использует треугольники для «сшивания» оптимальной поверхности, которая не содержит избыточных или противоречивых данных и имеет минимально возможные треугольники. Алгоритмически — это аналогично «минимальному разрезу» графа, а математически — производной или функции оптимизации (из которых функция находит кратчайший путь, который она может пересечь по поверхности). Этот кратчайший путь эквивалентен оптимальному хранению данных (транзакций) в группе обеспечения доступности баз данных. Конфликтующие треугольные “плитки”, чтобы поверхность события была ровной и без от конфликтов.


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

Консенсус, основанный на репутации

В оптимальной децентрализованной p2p системе репутации каждый узел должен иметь возможность самостоятельно определять свое доверие к другим узлам. Наша система использует специальную модель, которая включает транзитивные отношения или отношения, которые узел имеет с другими узлами, при назначении глобальной оценки. “Вы так же хороши, как и ваша компания”. Конечным результатом является “искажение” или градиент, основанный на транзитивном доверии или репутации во всех узлах в $DAG или штатном канале. Это можно рассматривать как кисть или терку для сыра, которая стирает поверх “плоскости поверхности” и выбирает, какие “треугольные плитки” стереть, а какие оставить. Вот как логика конфликта на самом деле удаляет “треугольные плитки”.


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

Частичное/полное масштабирование узла

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


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

Hylochain — Поддержка приложений на основе каналов

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


Два штатных канала, которые ”совместимы” через сеть $DAG. Они могут взаимодействовать или интерпретироваться, поскольку оба они “интегрированы” с $DAG путем развертывания гибридных узлов $DAG + Канал.

Причина, по которой он называется Hylochain, заключается в том, что в нашем подходе к поддержке приложений использовалась функциональная модель программирования Recursion Schemes для создания интерфейса MapReduce. В частности, схемы рекурсии Hylomorphism (Гиломорфическая) и Metamorphism (Метаморфическая) могут быть интегрированы для создания проверяемых запросов и потоковых соединений по штатным каналам путем проверки алгебраических типов данных так же, как проверяются op-коды для умных контрактов. Конечным результатом является функциональный интерфейс MapReduce, который знаком инженерам данных и совместим с существующей технологией больших данных.


Hylomorphic и Metamorphic штатные каналы для контраста. В метаморфическом состоянии данные из двух штатных каналов отправляются в блок в метаканале. В Гило мы берем предыдущее состояние канала и используем его, чтобы запросить (задать конкретный вопрос) два других канала, а затем сохранить результат запроса в блоке.

Токеномика и её связь с Hylochain

Когда штатный канал создан, он может быть интегрирован в канал $DAG, но с использованием интерфейса ACI или Application Chain Interface. Этот интерфейс представляет собой просто объект JSON с информацией о конфигурации и открытым ключом, связанным с самим каналом. Причина, по которой мы связываем открытый ключ со штатным каналом, заключается в создании брокерского механизма для данных штатного канала. Когда штатный канал развернут, разработчики настраивают сами, как платежи из сети $DAG распределяются между узлами и операторами.


Поток для покупки доступа к информации или модификации информации. Запрос направляется в $DAG, средства отправляются на счет канала, результат отправляется покупателю, а контрольная сумма транзакции отправляется в сеть $DAG, которая затем разблокирует средства для штатного канала.

Теги:

Source: habr1

Метки:





Запись опубликована: 20.07.2019

Подборка рабочих примеров обработки данных

Привет, читатель.

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

Схема работы с текущим постом унаследуется от моего поста про лучшие блокноты по ML и DS, а именно — сохранил в закладки → передал коллеге.

+ бонус в конце статьи — крутой курс от ФПМИ МФТИ.

image

Итак, давайте приступим.

Подборка датасетов с рабочими примерами обработки данных:

Suicide Rates Overview 1985 to 2016 — сравнение социально-экономической информации с показателями самоубийств по годам и странам.

Примеры обработки:

Spotify’s Worldwide Daily Song Ranking — ежедневный рейтинг 200 самых прослушиваемых песен в 53 странах с 2017 и 2018 годов пользователями Spotify.

Пример обработки:

Crimes in Boston — записи из Бостонской системы отчетов о происшествиях с преступностью, включающая в себя происшествия, и информацию о том когда и где оно произошло.

Пример обработки:

Google Play Store Apps — категории, рейтинги, размер всех приложений Google Play.

Пример обработки:

Pokémon for Data Mining and Machine Learning — статистика и особенности покемонов;

Пример обработки:

A Million News Headlines — данные заголовков новостей, опубликованных за последние 15 лет.

Пример обработки:

Airplane Crashes Since 1908 — полная история авиакатастроф по всему миру, с 1908 года по настоящее время.

Пример обработки:

News Headlines Dataset For Sarcasm Detection — высококачественный набор данных для задачи обнаружения сарказма.

Пример обработки:

Historical Air Quality — данные о качестве воздуха, собранные на наружных мониторах по всей территории США.

Пример обработки:

Nutrition Facts for McDonald’s Menu — анализ питания каждого пункта меню в McDonald’s США.

Пример обработки:

LEGO Database — детали / комплекты / цвета и запасы каждого официального набора LEGO в базе данных Rebrickable.

Пример обработки:

Global Commodity Trade Statistics — объемы импорта и экспорта для 5000 товаров в большинстве стран мира за последние 30 лет.

Пример обработки:

Crime in India — полная информация о различных аспектах преступлений, совершенных в Индии с 2001 года.

Пример обработки:

Predicting a Pulsar Star — данные по пульсарам, собранные во время обзора Вселенной.

Примеры обработки:

French employment, salaries, population per town — данные, показывающие равенство и неравенство во Франции.

Пример обработки:

United States Census — данные переписи в США.

Пример обработки:

California Housing Prices — цена на жилье в Калифорнии.

Пример обработки:

US Unemployment Rate by County, 1990-2016 — данные по безработице министерства труда США.

Пример обработки:

World of Warcraft Avatar History — набор записей, которые детализируют информацию о персонажах игрока в игре с течением времени.

Пример обработки:

The Gravitational Waves Discovery Data — данные о событиях гравитационных волн GW150914.

Пример обработки:

Бонус!

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

Цель курса — познакомить с основными принципами глубокого обучения (нейронных сетей) в интерактивном формате и на примере практических задач.

Программа курса

  1. Python: основы, Google Colab;
  2. Введение в линейную алгебру. Векторы. Матрицы и операции с ними. Библиотека NumPy;
  3. Библиотеки Pandas и MatPlotlib. Основы машинного обучения;
  4. Элементы теории оптимизации. Градиент. Градиентный спуск. Линейные модели;
  5. Введение в глубокое обучение. Перцептрон. Нейрон с сигмоидой (и другими функциями активации). Основы ООП в Python;
  6. Библиотека PyTorch. Многослойные нейросети;
  7. Обучение нейронных сетей на практике. Cifar10, notMNIST;
  8. Сверточные нейросети. Сверточный слой. Пулинг слой;
  9. Практика обучения нейросетей. Классификация дорожных знаков;
  10. Transfer Дearning. Популярные в Computer Vision архитектуры;
  11. Сегментация картинок. U-Net;
  12. Участие в соревнованиях на Kaggle;
  13. Object Detection. YOLOv3;
  14. Классический GAN. Нейронный перенос стиля;
  15. Базовые методы обработки текста;
  16. Word Embeddings;
  17. Рекуррентные нейронные сети;
  18. LSTM, GRU ячейки;
  19. Языковые модели;
  20. Машинный перевод;
  21. Text2Speech;
  22. SuperResolution.

Также вы можете заглянуть на Youtube-канал Deep Learning School. Там много отличных видео 😉

На этом наша короткая подборка примеров обработки данных подошла к концу. Надеюсь вы узнали для себя что-нибудь новое. Как принято на Хабре, понравился пост — поставь плюс. Не забудьте поделиться с коллегами. Также, если у вас есть то, чем вы можете поделиться сами — пишите в комментариях. Больше информации о машинном обучении и Data Science на Хабре и в телеграм-канале Нейрон (@neurondata).

Всем знаний!

Теги:

Source: habr1

Метки:





Запись опубликована: 20.07.2019

Вот это поворот: почему Apple изменила требования к разработчикам приложений

1cloud.ru
IaaS, VPS, VDS, Частное и публичное облако, SSL

В прошлом месяце Apple опубликовала изменения в гайдлайнах для разработчиков приложений под iOS. Они коснулись, в том числе, приложений для родительского контроля и обеспечения конфиденциальности детей. Причем часть новых рекомендаций идет вразрез с решениями, которые Apple приняла несколькими месяцами ранее. Обсуждаем требования к приложениям в App Store, которые вступят в силу 3 сентября этого года, и разбираемся в причинах — а также возможных долгоиграющих последствиях — этой ситуации.


Фото — Aziz Acharki — Unsplash

Что изменилось — коротко

Помимо презентации нового Mac Pro, Pro Display XDR и iOS 13 в начале июня Apple анонсировала и определенные изменения в гайдлайнах для разработчиков приложений. Короткий пост обо всех этих изменениях вышел в новостном разделе портала Apple Developer 3 июня.

Одно из нововведений, которое вызвало особенно бурную реакцию американских СМИ, — запрет на использование «сторонних аналитических сервисов или рекламы» в приложениях для детей (разделы 1.3 и 5.1.4 гайдлайнов). Как указано в пункте 5.1.4, «сбор и передача данных третьей стороне приложениями из категории «Дети» запрещены». Требование действует для новых приложений, а существующие нужно привести в соответствие до 3 сентября 2019 года.

Кроме того, к детям имеют непосредственное отношение и нововведения пунктов 5.4 и 5.5. По новым правилам, приложения, осуществляющие функции родительского контроля, могут «в ограниченных случаях» использовать технологию управления мобильными устройствами (Mobile Device Management, MDM) и API NEVPNManager. При этом в Apple подчеркивают, что «приложения должны иметь полезную функциональность или развлекательную ценность вне зависимости от возраста пользователя» — будь то ребенок или взрослый.

Почему решили изменить политики — и именно сейчас

В гайдлайнах Apple ссылаются прежде всего на требования американских и европейских законодателей, а именно на Закон о защите конфиденциальности детей в интернете (Children’s Online Privacy Protection Act, COPPA) и GDPR. Но отмечают, что при создании детских приложений нужно учитывать и опыт других стран и их требований к защите персональных данных.

Правда, есть основания предполагать, что сами по себе COPPA и GDPR — не единственная причина ограничений. Некоторые СМИ напрямую связывают изменение политик с резонансным отчетом Wall Street Journal, согласно которому 79 из 80 протестированных ими приложений из App Store использовали в среднем по четыре трекинговых системы для сбора аналитики, показа рекламы или маркетинговых целей.

Журналисты отдельно остановились на одном из «детских» приложений, которое сохраняло имя и возраст ребенка, а также информацию о его действиях в приложении, а затем передавало данные Facebook. Критика Wall Street Journal оказалась особенно болезненной — незадолго до этого Apple запустили рекламную кампанию, слоганом которой стала фраза: «Все, что происходит в вашем iPhone, остается на вашем iPhone».

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

И новые политики, видимо, стали результатом этой работы. Неслучайны и послабления в пунктах 5.4-5.5. Дело в том, что еще в апреле Apple наложила ряд ограничений на приложения, реализующие функции родительского контроля и использующие технологии MDM. Компания тогда объяснила ситуацию тем, что приложения нарушали политики App Store, еще с 2017-го года предписывавшие разработчикам отказаться от MDM-функциональности.


Фото — Tyler Gardon — Unsplash

Однако «репрессиям» приложения подверглись сравнительно недавно, после того, как в iOS 12 появился сходный по функциям сервис Screen Time. Ситуация вызвала вопросы не только у независимых разработчиков приложений, но и у американского правительства — примечательно, что пост об изменении гайдлайнов и отмене «запрета на MDM» вышел в тот же день, что и материал New York Times с информацией о том, что Apple хотят проверить на предмет нарушения антимонопольного законодательства США в связи с ситуацией в App Store.

Кто еще теперь озабочен защитой конфиденциальности детей

Случай с Apple — не единичный. В похожей ситуации чуть раньше оказалась Google, которая два месяца назад также выпустила обновление политик в отношении приложений для детей, доступных в Google Play. На этот шаг компанию побудила пойти разбирательство Федеральной торговой комиссии США (FTC), согласно которому компания не обеспечивала соблюдения разработчиками приложений требований закона COPPA.

Помимо Google Play под прицелом FTC оказался и YouTube. Согласно данным комиссии, сервис также не соответствует требованиям уже упомянутого Закона о защите конфиденциальности детей в интернете. По мнению Washington Post, ситуация с YouTube сигнализирует, что Федеральная торговая комиссия хочет серьезно усилить контроль в этой сфере — он значительно ослаб с момента вступления COPPA в силу в 1998 году.

Правда, и YouTube оказался далеко не первым сервисом, который попал в поле зрения FTC. Комиссия уже успела оштрафовать ряд компаний, выпускающих приложения для детей, на рекордные $ 5,8 млн за несоблюдение COPPA.

Возвращаясь к Apple: что думают разработчики

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


Фото — Rita Morais — Unsplash

Кстати, кое-кто из них уже высказался по поводу новых политик. Одной из компаний, которые подпадают под действие новых требований, стала PBS и ее подразделение PBS Kids (этот канал, к примеру, транслировал «Улицу Сезам» с 1969 по 2016 годы). PBS пояснили, что теперь они не смогут адекватно оценивать эффективность работы приложений и, как следствие, улучшать их функциональность и образовательные возможности.

Как отметила генеральный директор канала Пола Кергер (Paula Kerger), PBS будут вынуждены убрать приложения из App Store, при том, что сейчас ими пользуются миллионы детей. В PBS считают, что принятие таких решений без учета мнения самих разработчиков недопустимо. «Мы хотим донести мысль, что нам нравится работать с этими платформами [такими как App Store], они дают нам потрясающий охват. Сядьте и поговорите с нами», — комментирует ситуацию Кергер.

По поводу обновления политик высказались на Hacker News и форуме Apple Developer. Большинство поддержало Apple в стремлении оградить детей от рекламы, однако некоторые отметили, что сама формулировка, которую использовали в гайдлайне, позволяет трактовать новые правила двояко.

Так, п.1.3 содержит фразу «Apps in the Kids Category may not include third-party advertising or analytics» (дословно «Приложения в категории «Дети» не могут включать стороннюю аналитику и рекламу»). Остается неясно, к чему в этом случае относится прилагательное «сторонний» (third-party) — к рекламе и аналитическим сервисам или только к рекламе.

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

Один из комментаторов в треде на Reddit отметил, что если бы компания хотела дать непротиворечивое указание, то сформулировала бы требование иначе (Apps in the Kids Category must not include analytics or third-party advertising). А официальная формулировка дает Apple право «казнить или миловать» разработчиков по своему усмотрению.

Выводы

Последние события, похоже, вынуждают пересмотреть статус-кво крупных ИТ-корпораций. Что касается Apple, то все чаще звучит мнение о том, что компания должна выбрать сторону, и не может оставаться владельцем App Store и одновременно игроком на рынке приложений. К каким результатам приведет текущая политика компании, предстоит увидеть в ближайшее время.

Посты из наших блогов и социальных сетей:

Персональные данные: особенности публичного облака
Получение OV и EV сертификата — что нужно знать?
Как IaaS помогает франчайзи «1С»: опыт 1cloud
Эволюция архитектуры облака 1cloud
Как обезопасить Linux-систему: 10 советов

F.A.Q. по частному облаку от 1cloud
Мифы об облачных технологиях

В OIN больше трех тысяч лицензиатов — что это значит для открытого ПО
Как оценить производительность СХД на Linux: бенчмаркинг с помощью открытых инструментов

Теги:

Похожие публикации

Source: habr1

Метки: