Explaining assembly by playing SHENZHEN I/O

Explaining assembly by playing SHENZHEN I/O

Machine-readable: Markdown · JSON API · Site index

Поделиться Telegram VK Бот
Транскрипт Скачать .md
Анализ с AI

Оглавление (33 сегментов)

Segment 1 (00:00 - 05:00)

выгнать нас. Привет всем, добро пожаловать обратно на очередной стрим. Эм, мне кажется, я уже говорил об этом на довольно большом количестве стримов. Этот будет немного отличаться от остальных. Но этот раз будет совсем другим. Итак, в этом стриме я собираюсь поиграть в игру, но это не будет стандартное видео с прохождением, хотя в итоге оно может в него и превратиться. Кто знает? Цель здесь — рассмотреть игру под названием Shenzen IO. Shenzen IO — это, в общем, игра-головоломка, но суть головоломки в том, что вы, по сути, инженер-электрик, пытаетесь собрать компоненты и написать для них код на языке ассемблера для выполнения различных задач. Это не совсем ассемблерный код, но достаточно близок к нему. Это не настоящие аппаратные устройства, но достаточно близко. А тот факт, что в неё ещё и весело играть, означал, что, когда я играл в неё сам, я думал: « Это похоже на то, что я мог бы использовать для обучения». Программирование на ассемблере — это, или по крайней мере может быть, довольно сложная задача. В общем, даже самые простые вещи могут оказаться довольно сложными, потому что вы работаете на очень низком уровне в рамках компьютерной архитектуры. Эмм, и хотя я думаю, что мы могли бы провести стрим, посвященный непосредственно программированию на ассемблере и, вероятно, кодированию на ассемблере на Rust, я не против провести подобный стрим. Для вступления мне хотелось чего-то немного более мягкого. С этим вы тоже можете поиграть сами. Эм, я хочу сразу подчеркнуть, что это не настоящий язык ассемблера. Если вы это сделаете, это не значит, что вы теперь знаете ассемблер. Но я думаю, что если вы достаточно много занимаетесь решением подобных головоломок, где вам действительно нужно изучать справочные материалы по аппаратному обеспечению и инструкции по ассемблеру, а также уметь работать с небольшим количеством регистров и памятью. Эм, я думаю, это полезный, эмм, путь к настоящему программированию на ассемблере. Чтобы самостоятельно программировать на ассемблере, вам потребуется изучить эти и многие другие вещи, но это своего рода способ начать. Эм-э, и поэтому мы собираемся это попробовать. Посмотрим, как это сработает. По сути, мы начнём игру с самого начала. Я удалил свой файл сохранения, чтобы мы начали всё с нуля. Эм, а потом посмотрим. Посмотрим, окажется ли эта игра в итоге... ну, или это окажется хорошим способом обучения, или нам просто будет весело, или это окажется просто скучно, и мы все разойдёмся по домам и больше так не будем делать. Частично мне предстоит оценить это, а частично, или, я думаю, в значительной степени, вам предстоит оценить, насколько это оказалось хорошей идеей задним числом. Эм, мы попробуем. Все в порядке. Итак, без лишних слов, давайте начнём игру. А теперь следующий интересный вопрос: произойдет ли это, когда я это сделаю? Вы теперь видите игру? Ага, вы же понимаете суть игры. Отлично. Итак, это вступление. Завязка игры довольно интересная. В общем, суть в том, что, похоже, люди перестали писать низкоуровневые программы для компьютеров в этом мире, например, на языке ассемблера или вообще программировать, по крайней мере, в западном мире, как мне кажется, такова ситуация. И поэтому человек, который хочет работать с низкоуровневыми средствами, пытается понять, куда двигаться дальше. Они пытаются понять, как мне продолжать заниматься этой работой? И в итоге они получают предложение о работе в Китае в какой-то случайной компании, которая занимается производством микроэлектроники. Эм, это, по сути, главный экран игры. Давайте посмотрим. Итак, мы получили электронное письмо с благодарностью за присоединение. Эм, я думаю, в этой игре я не буду читать весь текст. В игре много предыстории, и она довольно интересная. Эм, но я не собираюсь зачитывать это вам до конца. Но суть в том, что нас встречает множество людей, которые приветствуют нас в этой новой компании. И знаете, в лучших традициях мы ожидаем от вас усердия, связей и деловой хваткости, хотя я даже не знаю, что это значит. Эмм, и вроде бы нас приветствовали несколько сотрудников, некоторые из которых были более сомнительными, чем другие. Нам пришло напоминание о необходимости прочитать инструкцию, которую я буду находить всякий раз, когда она нам понадобится. А потом нас просят создать наше первое устройство, создать нашу первую маленькую инженерную штуковину. Поэтому мы откроем его в ConceptCAD, это небольшая программа, в которой можно создавать макеты. О, я собираюсь удалить эти файлы, потому что, кажется, они остались с моего последнего

Segment 2 (05:00 - 10:00)

прохождения. Э-э, значит, они будут... Что? Почему я не могу? О, кажется, мои сохранения не стерлись. Потому что это не старое. Нет, это достаточно старая информация. А может быть, эти компоненты изначально присутствуют в этом продукте? Итак, задача состоит в том, чтобы создать имитацию камеры видеонаблюдения. Итак, здесь, внизу, находится информационный экран. Эм, э-э, и здесь у нас есть активные и сетевые выходы. А цель состоит в том, чтобы управлять ими с помощью фиксированного повторяющегося сигнала, как указано на вкладке "Проверки". Верно? Итак, у нас здесь небольшой микроконтроллер, на котором мы собираемся написать немного ассемблерного кода. Эмм, и для управления этими контактами, которые подключены к плате. Как видите, у нас есть X0, X1, P0 и P1. А эти маленькие зелёные штучки — это провода. Таким образом, мы подключили P0 к активному выходу. Символы слишком мелкие. Эм, я, наверное, смогу сделать это побольше. Посмотрим, получится ли у меня это сделать. Эм, мы это сделаем. Мы попробуем сделать вот это, потом вот это, а потом я сделаю вот это и посмотрю, смогу ли я увеличить это для вас. Преобразовать под размер экрана. Как вам это? Полагаю, мы скоро это узнаем, когда я открою этот. Посмотрим, откроем ли мы его снова. Так легче увидеть? В этой игре текст довольно мелкий. К сожалению, увеличить его размер довольно сложно. Но разве так проще понять? Если я напишу move 100 two. Отлично. Прохладный. Итак, схема здесь такова: мы должны выводить данные на каждый из этих двух портов. Каждый из них управляет светодиодом. А значения, которые мы можем здесь записать, — это любые значения от 0 до 100, где 0 — выключено, 100 — включено, а промежуточные значения — это различные варианты затухания свечения светодиода, которые нам здесь не понадобятся. Как видите, на вкладке проверки наша цель — написать код в этом верхнем микроконтроллере, чтобы активный светодиод то выключался, то включался, то выключался, и так далее, и чтобы сетевой светодиод тоже работал в таком же режиме. Так что это немного другой вариант. А для этого нам нужно руководство. Поэтому я открою здесь инструкцию. Эм, в руководстве объясняется, как выглядят программы на этих микроконтроллерах. И вот здесь мы начинаем сталкиваться с чем-то, что немного напоминает сборку. В частности, каждая строка программы здесь имеет необязательную метку, условие; к тому, как это будет выглядеть в ассемблере, мы вернемся позже. Потому что обычно там не бывает таких же состояний. Инструкция, а затем любые наши комментарии. Эмм, э-э, код может выглядеть так: вот пример программы, верно? Вот цикл, вот пример метки. В Rust 2 они есть, верно? Вы можете пометить фрагмент кода и сослаться на него позже, например, чтобы перейти к этой части кода, э-э, комментарии или комментарии. А вот пример инструкции. Это инструкция TikTok. Она принимает два операнда: здесь act, который является регистром (мы вернемся к ним позже), и 10, который является значением. Эта инструкция будет сравнивать значение в этом регистре с этим литеральным значением. У вас есть инструкции типа jump, которые переходят к метке по имени. У вас есть операция, которая берет значение первого операнда и помещает его в регистр или вывод второго операнда. Вам нужно добавить это число в аккумулятор. Итак, у вас есть только один кассовый аппарат. Эмм, вот как это нужно читать. К плюсу мы вернемся чуть позже, потому что сейчас он нам не нужен. А если прокрутить страницу дальше, вы увидите, что здесь есть несколько инструкций. Здесь есть основные инструкции, такие как движение и прыжок. Существуют арифметические инструкции, такие как сложение, вычитание, умножение, ноль. Эти цифры, которые определяют и устанавливают определенную цифру числа. Обратите внимание, что в отличие от ассемблера, этот компьютер, на котором мы работаем, использует десятичную систему счисления, а не двоичную. Это числа в десятичной системе счисления, в том виде, в котором мы, люди, привыкли представлять себе числа. Например, если у вас есть число 596, и вы спрашиваете о нулевой цифре, вы получаете шестерку, которая является самой правой цифрой. В общем, индексация цифр начинается с конца. Эмм, или, если хотите, ближайший к тому месту. Это что-то вроде побитовых операций, которые используются

Segment 3 (10:00 - 15:00)

в ассемблерном коде, но они позволяют рассматривать числа примерно так же, как и в случае с числами, рассматривая их как составные части, а не как их конечное значение. Хотя вы можете рассматривать это и таким образом, если используете такие вещи, как добавление. Есть еще несколько более сложных инструкций, к которым мы вернемся позже. Но есть еще один аспект, о котором нам следует знать, — это инструкция к тесту. Таким образом, инструкции к тесту позволяют сравнивать значения в двух регистрах, или значения двух разных регистров, или значение с регистром. Как видите, здесь используется обозначение r/i. R/i — это регистр, или, я не знаю, что означает сокращение i, но это целое число. Ага. То есть это регистр или целочисленный литерал. Например, программа сравнит это с этим и выполнит определенные действия в зависимости от условных аннотаций сбоку. В зависимости от того, равны ли эти два параметра при сравнении или нет. Я пока не буду объяснять, как это связано с ассемблерным кодом, потому что это более сложный вопрос. Но как только мы начнём их использовать, я начну рассказывать об этом. Но пока что знайте, что такие операции, как сложение, вычитание и умножение, вы увидите и в ассемблере на стандартных процессорах, то есть на настоящих процессорах. Эм, вы не увидите таких вещей, как цифра и сброс, но вы увидите, например, манипуляции с отдельными битами, верно? Итак, операции И ИЛИ на битовом уровне, или, например, EXOR, битовый сдвиг и все эти различные операции, которые мы в итоге не будем использовать в этой игре, и я думаю, это нормально. Аналогично, команда `move` — очень распространенная инструкция, которую вы встретите в ассемблере. То же самое и с прыжками: в подобных инструкциях обычно бывает гораздо больше вариантов. Например, я думаю, что операция перемещения на архитектуре x86 имеет десятки вариантов. Мне кажется, там около 30 разных вариантов этого приёма. Мне кажется, кто-то действительно доказал, что одной лишь инструкции перемещения достаточно для выполнения полного цикла в архитектуре x8664. Эм, это довольно дико. Эм, у нас есть команда `jump`, которая переходит к метке. Также очень часто встречается в сборочных узлах. Обычно в ассемблере также используются такие конструкции, как " переход", если значение равно. Мы вернемся к этому, когда будем говорить об операциях тестирования. А еще у нас есть сон, который длится несколько циклов. При работе с ассемблером ситуация иная. Как будто обычно инструкции по сну не бывают именно такими. Эм, но это достаточно простой способ, эм, это даже достаточно простая инструкция для понимания, даже если в ассемблере вы бы ее не поняли. Хорошо. А ещё есть регистры. Итак, когда вы работаете с программированием на ассемблере, есть два места, где можно хранить информацию. Ой, я забыл про ручку. Операция Knop не проводится. Это инструкция, которая ничего не делает. Это просто занимает место. На самом деле, это можно использовать, например, для создания амортизирующих прокладок. Иногда вам нужно, чтобы определенные инструкции находились по определенным адресам в памяти, и команда `knop` позволяет сдвинуть их дальше по двоичному коду, не влияя на семантику выполняемой программы. Когда вы работаете с памятью на языке ассемблера, есть два способа это сделать. Вы можете получить доступ к данным, находящимся в оперативной памяти, например, в DRAM. Эмм, а способ доступа к данным, защищенным DRM, обычно заключается в использовании адресов, которые представляют собой своего рода индексы данных, хранящихся в выделенной памяти. Эмм, эти вещи, вероятно, не будут рассматриваться нами подробно, по крайней мере, в начале, но позже появятся компоненты, которые начнут напоминать память, используемую в обычном ассемблере. А затем обычно используются инструкции типа «переместить», где можно либо переместить значение по этому адресу памяти, то есть переместить значение, на которое указывает указатель. А вот так вы извлекаете данные из оперативной памяти или возвращаете их обратно в оперативную память, то есть записываете данные обратно в оперативную память. Но есть еще и такие вещи, как регистры. А о регистрах мы обычно не задумываемся, когда пишем программы, потому что в таких языках, как C, Rust или Go, обычно нет понятия регистра. Этот термин обычно не используется, поскольку такие ячейки обычно выделяются и управляются компилятором, когда он преобразует ваш код в ассемблерный. Мы же говорим о главной памяти, верно? Как и в Rust, в Rust есть указатель. Эмм, но регистрирует не так уж много. Регистры — это биты памяти фиксированного размера, которые, по сути, встроены в центральный процессор. Эм, их обычно относительно немного. Хотя, в зависимости от платформы, вы можете получить гораздо больше. Мне кажется, что современные процессоры ARM имеют сотни регистров. Эм, но в старые добрые времена их было, например, восемь. Как будто у вас их не так уж много. А журналы регистрации — это своего рода ваш блокнот. Вот вам место для временных файлов процессора. Например, если вы хотите, скажем так, умножить

Segment 4 (15:00 - 20:00)

число, хранящееся в оперативной памяти. Вы бы сделали следующее: переместили бы значение из основной памяти в регистр, использовали бы инструкцию для изменения этого значения в регистре, а затем записали бы его обратно в основную память. Вам это не обязательно делать. Иногда можно напрямую работать со значением в оперативной памяти. Но обычно, если вам нужно выполнить несколько операций, например. Ой, извините. Позвольте мне уменьшить громкость фоновой музыки. Отличное решение. Так. Иногда встречаются инструкции, позволяющие работать непосредственно с основной памятью, минуя регистры, но очень часто для выполнения каких-либо действий необходимо перенести инструкции в регистры. Также часто встречаются инструкции, которые работают с конкретными регистрами, даже без вашего предварительного указания. В качестве примера в этом конкретном наборе инструкций можно привести инструкцию сложения. Таким образом, инструкция add, как вы видите, складывает значение первого операнда со значением регистра действия. То есть вы не говорите "сложить", например, 1 + 2 или 1 плюс это значение в памяти. Достаточно сказать «добавить единицу», и система поймет, над каким регистром она работает. Регистры также часто используются, например, для обозначения соглашений о вызове функций. Таким образом, если одна функция вызывает другую, существует два способа передачи аргументов этой другой функции, той функции, которая должна быть вызвана. Один из вариантов — поместить значение этого аргумента в стек, который находится в оперативной памяти. Но для этого вам потребуется обратиться к основной памяти, чтобы вызвать эту функцию. Иногда, если аргументов не слишком много или они не слишком сложны, можно просто изложить их в письменном виде. Допустим, это целое число. Вы можете взять это значение, записать его в регистр, вызвать функцию, и функция, как правило, имеет соглашение о вызове, предполагающее, что определенный набор аргументов находится в конкретных регистрах. Таким образом, вам не придётся обращаться к оперативной памяти для вызова функции. Эмм... в этой игре, насколько я понимаю, не может быть самомодифицирующегося кода. Так что, если вы подумаете о работе программы на Linux, например, на архитектуре x86, ваша программа также находится в оперативной памяти, и поэтому теоретически у вас могут быть инструкции, которые изменяют тот фрагмент памяти, который содержит саму программу, и поэтому при следующем выполнении этот код будет делать что-то другое. Это и есть так называемый самомодифицирующийся код. Это действительно круто, но мы не будем этого делать. Я не думаю, что это как-то повлияет на игру. Эмм, да, я не думаю, что это имеет значение в этой игре. Эм, хорошо, а какие у нас есть кассовые аппараты? Итак, в этом наборе инструкций и с этими микроконтроллерами, которые мы получаем в этой игре, набор регистров различается в зависимости от модели. Это характерно и для всех процессоров. Не все процессоры имеют абсолютно одинаковое количество регистров. Эм, не только в числовом выражении, эм, но и в других названиях. В зависимости от платформы, они называют вещи по-разному. Как и в случае с ARM 64, они называются иначе, чем в x8664. Эм, они также иногда бывают разного размера, верно? Так что одни регистры могут быть предназначены для хранения чисел с плавающей запятой, а другие — например, чисел типа double, верно? Таким образом, это 64 бита. В некоторых случаях, например, может быть очень небольшое количество регистров, способных хранить 128-битное целое число, верно? Что-то вроде большого целого числа. На старых процессорах доступ к регистрам может быть только 32-битным, верно? Иногда при работе во встроенной среде доступ к 16-битным регистрам может быть ограничен. Именно здесь становится ясно, что вам действительно нужно хорошо знать свою платформу, потому что компоненты, доступные вам даже на уровне сборки для взаимодействия, могут меняться. Но давайте вернемся к тому, что у нас здесь есть. Таким образом, в микроконтроллерах Shenzen у нас есть регистр ACT, который является основным регистром общего назначения, используемым для внутренних вычислений. Все арифметические операции неявно используют и изменяют значение действия. Эм, это тот тип документации, который вы увидите, если начнете читать документацию к наборам инструкций. Обычно в таких текстах гораздо больше деталей, но это же именно такой язык, верно? Они часто используются неявно. Они будут говорить, например, что это регистр общего назначения, в отличие от некоторых регистров, которые, как известно, используются всегда, таких как регистр EIP, который управляет указателем на текущее состояние выполнения программы. Так что этот регистр не является регистром общего назначения, потому что, если вы что-то в него сохраните, вы измените место выполнения программы. Таким образом, это не реестр общего назначения. Это второй регистр, доступный на некоторых микроконтроллерах. Начальные регистры инициализируются значением ноль. А ещё есть регистры контактов. Итак, это те выводы, которые мы рассматривали ранее на микроконтроллере и которые используются при чтении или записи данных на контакты. Итак, здесь можно отправлять сигналы, например, для включения

Segment 5 (20:00 - 25:00)

светодиода, что мы сейчас и сделаем, или для считывания входных данных, например, с кнопки. Подобные контакты существуют и в реальных процессорах. Эм, обычно они работают не совсем одинаково. Например, вы можете проверить, высокие они или низкие, или установить для них высокие или низкие значения. И в этом случае тоже, как и в этом мире, в который мы собираемся войти, здесь тоже есть контакты, но они выполнены в виде регистров, что не всегда бывает на процессорах. Здесь есть некоторые приблизительные оценки, но представление о том, что контакты процессора — это способ взаимодействия с внешним миром, очень точное. Вопрос о том, точно ли это отражает реальность, довольно расплывчатый. Эм, мы поговорим о различии между регистрами Pregister и X- регистрами чуть позже, когда начнём их использовать. Итак, давайте перейдем к нашему первому заданию. Так что я вернусь к игре. Так что, надеюсь, теперь вы видите игру. Да, вы. Фантастика. Итак, нам нужно, чтобы этот микроконтроллер выдавал сигналы на вывод P0 таким образом, чтобы они соответствовали этому шаблону. и сигналы на этом микроконтроллере таким образом, чтобы они соответствовали нижнему шаблону. В данном случае, я думаю, все довольно просто. Если присмотреться, то можно увидеть, что... что там находится? 1 2 3 4 5 шесть образцов между ними. Так что мы просто поспим шесть часов. Затем мы переместим значение 100 в P 0. Потом мы будем спать один, два, три, четыре, пять, шесть. Возможно, я ошибся в подсчете одного из них. А затем мы перемещаем ноль в P0. Если я сейчас попробую это смоделировать, вы увидите, что здесь оранжевым цветом обозначена целевая точка. Красный или оранжевый свет означает, что именно это мы и производим. Как видите, если навести курсор, отображается следующее: ноль, фактическое значение – ноль, ожидаемое значение – 100, фактическое значение – 100. Таким образом, это был правильный код для активации верхнего контакта. Но если вы посмотрите на нижний график для сети, поскольку мы там не писали никакого кода, вы увидите, что для сети мы все время находимся на нуле. Красный цвет здесь указывает на то, что мы выводим сигнал, равный нулю, хотя ожидалось, что мы выведем 100. И да, в этом случае инструкции микроконтроллера автоматически зацикливаются. Поэтому, когда вы достигаете дна, вы автоматически возвращаетесь наверх. Эм, если бы это было не так, верно? Тогда мы могли бы написать здесь цикл и здесь цикл перехода, и эффект был бы тот же. Но в данном случае нам это не нужно, потому что, хм, цикл автоматически начинается, когда доходит до конца. Эм, переместимся в точку P0. Правильно? Так что с сетью это тоже будет очень просто, верно? Давайте посмотрим. Итак, мы проведем сеанс сна, чтобы выяснить, что это такое? Четыре. Затем мы переместимся на 100 точек в точку P0. Потом поспим два часа, потому что это длина этого пика. Тогда мы переместим ноль в точку P0. Затем поспим час. Переместите единицу в точку P0. Извините, переместите 100 в точку P0. А поспите одну минуту. А затем переместим ноль в P0. А потом поспим 1 2 3 4. Может быть, это пять. Давайте посмотрим. Ага. Нет, похоже, это так. Здесь вы можете увидеть, как выглядит светодиодная подсветка на устройстве. Итак, именно это мы и пытаемся воспроизвести. Видите ли, оно продолжает работать. Это своего рода выполнение множества тестов, но на самом деле это просто последовательность операций, которые выполняются для того, чтобы убедиться, что для каждого временного шага, и в конечном итоге для каждого временного шага и каждого входного параметра, вы получаете ожидаемый выходной результат. А затем в конце мы получаем результат проверки, который показывает нам себестоимость производства, то есть сколько мы заплатили за компоненты (в данном случае всего два микроконтроллера), энергопотребление (количество выполненных инструкций) и количество строк кода (количество написанных строк кода). Если присмотреться, то можно увидеть, что здесь, это своего рода глобальная таблица результатов. Здесь вы увидите, что некоторым людям удалось решить задачу с гораздо меньшим количеством строк кода. Когда вы переходите к более поздним этапам игры, вы получаете доступ к некоторым инструкциям, которых нет в руководстве, что также не совсем не соответствует реальному миру. Эм, но я не думаю, что мы будем за ними гоняться, по крайней мере, не сейчас. Итак, мы выполнили наше первое задание. Мы сделали эту фальшивую камеру видеонаблюдения. Итак, мы возвращаемся к нашей электронной почте. И теперь они говорят: «Спасибо за нашу работу». Э-э, опять же, я пропускаю часть, касающуюся лора, но лор здесь очень интересный. Эм, давайте тогда перейдем к следующему вопросу. Я удалю своё старое решение. Итак, здесь у нас есть усилитель управляющего сигнала

Segment 6 (25:00 - 30:00)

. Таким образом, управляющий вход представляет собой простой вход, подключенный к заводскому оборудованию. Это здесь. Выход управления — это простой выход, подключенный к другому заводскому оборудованию. Так что, управление здесь. Сигнал с управляющего входа следует умножить на два, а затем скопировать на управляющий выход. Хорошо. Этот вариант тоже кажется довольно простым. Итак, мы хотим переместить значение с вывода P0 в аккумулятор. Мы хотим умножить значение аккумулятора на два, затем переместить содержимое аккумулятора на P1, после чего перейдем к следующему циклу. Понимаете ли вы, почему это, вероятно, и есть решение в данном случае? Опять же, эти начальные задания очень простые, но они нужны, чтобы познакомить нас с языком игры и её структурой, а затем они становятся всё сложнее по мере продвижения. Итак, если мы это смоделируем, надеюсь, я ничего не испортил. Это кажется многообещающим. Эм, функция сна необходима, потому что иначе вы окажетесь в бесконечном цикле. Эм, и вы бы никогда не перешли к следующему временному шагу. Так что это, пожалуй, немного менее реалистичная часть игры, где в текущей конфигурации игры предполагается, что проверка происходит в течение определённых промежутков времени. И ваша программа должна явно указывать, когда она хочет перейти к следующему периоду времени. В реальных процессорах обычно управление временем не происходит, это, по крайней мере, менее распространено. Обычно в реальных процессорах управление осуществляется через прерывания, то есть оно запускается внешним сигналом от чего-то вроде устройства, сообщающего о наличии данных, или нажатия кнопки. Но это также может быть выделенный чип времени на плате, который отправляет инструкции или устанавливает входной сигнал на выводе процессора, чтобы сообщить ему, что время идет. Но можно использовать и более простой вариант, когда используется только чип, с помощью которого можно запросить текущее время, и он вам его покажет. Эм, всё это зависит от конкретной конфигурации, которую вы хотите использовать. Я думаю, что наиболее распространенный способ работы со временем в микроконтроллерах — это считывание текущего времени с какого-либо чипа. А ещё можно указать время, когда вы хотите, чтобы вас разбудили, верно? Итак, это можно использовать, например, для составления расписаний. Представьте, что у вас есть программа, работающая, например, в Linux, и в ней одновременно запущено несколько потоков, выполняющих несколько параллельных программ. Поэтому давайте пока не будем говорить о потоках. Они по- своему сложны. И вы хотите иметь возможность сказать: «Я позволю этой программе работать 20 миллисекунд». Итак, что вы бы сделали? Вы бы установили своего рода таймер на этом тактовом чипе, сказав: «Через 20 миллисекунд отправьте мне прерывание», а затем запустили бы обработку этого процесса. Вы планируете выполнение программы и позволяете ей работать, но по истечении 20 миллисекунд контакт прерывания устанавливается, а это значит, что вместо текущего выполняемого кода запускается какой-то специальный код в процессоре. прерывает работу процессора. И тогда вы бы подумали: «О, время, отведенное на этот процесс, истекло». Сейчас я отключу это прерывание. Я собираюсь приостановить эту программу. Я собираюсь выбрать, какую программу запустить следующей. А затем я установлю еще один таймер на 20 миллионов, чтобы он мог продолжать работать. Эм, значит, у нас здесь нет такого рода программирования, основанного на прерываниях. Хотя для срабатывания определенных триггеров необходимо, все это по-прежнему основано на понятии времени. Эм, хорошо. Таким образом, и в этом случае нам это удалось. Мы сделали всё в точности так, как и ожидалось. Средний. Большой. Этот вариант тоже не слишком сложный. Давайте вернемся назад. Что нас ждёт дальше? Мы успешно выполнили свою миссию. Эм, это просто игра в какой-то странный пасьянс. Мне понравилось. Было весело пройти игру, но мы не будем делать это на стриме. Э-э, генератор импульсов. Нам нужен генератор импульсов, отвечающий определённым требованиям. Однако вместо того, чтобы покупать готовый продукт по рыночной цене, мы собираемся создать его сами. Нам потребуется использовать условное выполнение. Фантастика. Хорошо, давайте откроем этот чертеж. Я от них избавлюсь. Вы ничего не видели. Я не понимаю, почему при удалении файла сохранения прогресс перезапускается, но при этом сохраняются все решения, которые, вероятно, можно найти, но, знаете, я всё равно это сделаю. Я просто удалю их до того, как вы их увидите. Прохладный. Итак, у нас есть генератор диагностических импульсов. У него есть кнопка и импульс. Кнопка — это простой вход, подключенный к кнопке. Импульс — это простой выходной сигнал, подключенный к электронному устройству. При нажатии кнопки

Segment 7 (30:00 - 35:00)

генерируйте импульсы в соответствии с указанными параметрами до тех пор, пока кнопка не будет отпущена. Хорошо. Итак, что мы будем делать здесь, когда кнопка будет нажата, мы будем показывать эти попеременные щелчки. А когда кнопка будет выключена, мы ничего не будем генерировать. Прохладный. Итак, именно здесь нам и понадобится инструкция по работе с тиковым деревом. Итак, это инструкция по сравнению. Обычно в ассемблере это называется cmp. А здесь это будет называться тик. Эмм, и мы будем сравнивать p 0 со 100. То есть, это будет истина, если кнопка нажата, что будет значением 100, и ложь в противном случае. В Shenzen это реализовано следующим образом: в начале строки ставится знак плюса, и эти строки выполняются, если предыдущая инструкция сравнения вернула значение true. Если предыдущее сравнение было верным. Также можно добавить префикс " минус" к строке, если предыдущая инструкция сравнения не верна. Итак, если мы вернемся к руководству, то увидим, например, инструкцию проверки на усталость, которая проверяет, равно ли значение первого операнда значению второго операнда. Если a равно b, то инструкции сложения разрешаются, а инструкции вычитания — отключаются. Аналогично, если значение не равно B, то инструкции «плюс» отключаются, а инструкции «минус» включаются. Итак, они привели пример ранее, верно? Таким образом, этот TE будет, по сути, устанавливать, было ли предыдущее сравнение истинным или ложным. Эм, и если это установлено, то эта инструкция выполняется. Если этот параметр не задан, данная инструкция не выполняется. И нет инструкций, которые выполнялись бы только в случае ложного сравнения. Это не так уж сильно отличается от того, что вы делали бы на языке ассемблера, за исключением того, что на ассемблере у вас бы не было этого плюса. Вместо этого у вас будет участок кода, обычно с меткой, указывающей на то, что должно произойти, если инструкция верна, если сравнение истинно, и другой участок, указывающий на то, что должно произойти, если оно ложно, а затем метка для кода, следующего за условным оператором. Поэтому это следует выполнить после того, какой вариант развития событий был выбран. Эм, а потом, позвольте мне снова запустить игру. Я могу примерно показать вам, как это будет выглядеть. Итак, в стандартном ассемблере, допустим, мне нужно что-то вроде: если это 100, то переместите три в P1, иначе переместите четыре в P1. Допустим, я хотел написать вот это на языке ассемблера. В обычном ассемблере это можно было бы записать примерно так: сравнить P0 со 100. А затем написать что-то вроде: переместиться на 3 P1, а затем перейти дальше. Нет, это будет движение 4, прыжок P1 после. И на самом деле вам здесь не нужен переход, потому что после этого будет здесь, и это будут инструкции, следующие за условным переходом. Так что вы видите разницу. Вот так это выглядит по- шэньчжэньски. Вот как это работает. Похоже, что традиционный способ аналогичен традиционному, за исключением того, что после перехода здесь также был бы переход, равный true, а в противном случае — переход к false. Верно? Таким образом, структура здесь немного отличается: вы переходите к нужному сегменту, а не аннотируете сборку фразой " сделайте это, если истинно, сделайте это, если ложно". Эм, и это потому, что в реальном компьютере нет осмысленного смысла, в котором можно было бы сказать, что эти инструкции выполняются только в том случае, если бы существовали способы, которыми это могло бы работать, но именно к такому способу пришел стандартный ассемблер. Здесь есть интересный нюанс: как это обычно работает, по крайней мере, оператор сравнения на самом деле является оператором подстановки, поэтому давайте я на секунду уберу эти символы. Итак, что же на самом деле представляет собой сравнение? Оно вычитает P0 и 100, и если результат равен нулю, то устанавливает флаг нуля, а если результат меньше нуля, то устанавливает отрицательный флаг, и происходит проверка флага перехода (jump). На самом деле, процессор проверяет флаг нуля, который является своего рода внутренним состоянием процессора, своего рода однобитовым флагом, и

Segment 8 (35:00 - 40:00)

переход происходит только в том случае, если этот бит установлен в единицу, в противном случае переход не происходит. Это дает некоторое представление о более глубоких аспектах сборки и о том, как это на самом деле работает. Ага, значит, это не так. Эти флаги устанавливаются в так называемом регистре флагов. Это всего лишь регистр общего назначения, но это набор битов, которые процессор использует для хранения части своего внутреннего состояния. А при желании вы можете также проанализировать эти флаги и получить необходимую информацию. Прохладный. Хорошо. Итак, вернемся к тому, что мы на самом деле пытались сделать. Мы сравним P0 со 100. Если P0 равно 100, то мы перенесем 100 в аккумулятор. Мы немного поспим. Мы переместим ноль, извините, не в аккумулятор, а в P1. Поспим немного. Мы переместим ноль в P1. А потом немного поспи. Итак, это немного странно. Сначала убедитесь, что это работает, и что я вам не солгал. Прохладный. Итак, что же мы здесь делаем? Итак, J — это встроенная инструкция в ассемблере. Я не думаю, что ассемблер это делает. Думаю, это указано в инструкции по сборке. Ага, круто. Эм, в руководстве по ассемблеру указано что-то вроде x86, но способ фактической реализации этой инструкции процессором... Итак, это то, что часто называют микроодом. например, внутренний код процессора для выполнения ассемблерного кода. Эм, суть в том, что, как мне кажется, если вы почитаете руководство Intel для x86, там будет сказано, что инструкция GE перейдет к нужному месту, если установлен флаг нуля, и в противном случае не перейдет ни к чему. Эм, хорошо, а что мы здесь делали? Итак, мы сравнили P0 со 100. И если это так, то мы устанавливаем значение P1 равным 100. Мы делаем паузу на секунду или нет, делаем паузу на один цикл, а затем возвращаем значение к нулю. А потом, в конце, мы засыпаем. Но обратите внимание, что сон здесь не имеет преимущества, потому что мы хотим спать независимо от того, установлена ​​ли кнопка на 100 или нет. Этот узор немного странный, не правда ли? Итак, существует два способа представить себе решение этой головоломки. Один из них выглядит так: если значение равно 100, то вы выполняете два шага, верно? У нас здесь две задержки, мы выполняем два цикла вычислений, прежде чем проверить снова. Другой вариант — вы пытаетесь реализовать вот этот шаблон, используя аккумулятор. Итак, если значение равно 100, то вы проверяете значение аккумулятора, которое вывели в последнем цикле, и изменяете его, используя, например, инструкцию NOT, чтобы изменить значение аккумулятора, выводите это значение и затем засыпаете. Таким образом, ваш цикл итераций состоит всего из одного цикла, в котором вы каждый раз переоцениваете значение кнопки, но то, что вы выводите, зависит от того, что вы выводили ранее, и для хранения этого значения вы используете регистр накопления. Так что мы могли бы попробовать написать и такое решение. Так что это будет выглядеть так: вы бы этого не сделали. Таким образом, регистр накопления переключается между значениями 0 и 100. То есть, если бы он был равен нулю, в данном случае он бы переключился на 100, что нам и нужно. Затем мы переместим аккумулятор в P1. А если это не так, то мы переместим ноль в P1, а также переместим ноль в аккумулятор, а затем остановимся на единицу. Думаю, это сработает. Итак, схема здесь следующая: если кнопка включена, мы постоянно переключаем аккумулятор и перемещаем содержимое аккумулятора в положение P1, а затем переходим в спящий режим. Если кнопка не включена, то мы устанавливаем P1 в ноль, устанавливаем аккумулятор в ноль, а затем переходим в спящий режим на один шаг. А причина, по которой нам приходится это делать, заключается в том, что всякий раз, когда мы выходим из системы, например, когда кнопка переходит в положение «ноль», мы должны убедиться, что состояние сбрасывается, чтобы оно снова перешло в положение «единица». Надеюсь, это тоже даст тот же результат Прохладный. Как видите, это

Segment 9 (40:00 - 45:00)

можно сделать двумя разными способами. Можно использовать один цикл, выполняющий работу, эквивалентную двум циклам, или же сохранять состояние между выполнениями. Но вы заметите, что здесь больше строк кода. Этот вариант также потребляет больше энергии в том смысле, что выполняется больше инструкций. Причина в том, что вы смотрите сюда в том случае, если кнопка неактивна. Мы выполняем эти два действия, но делаем это в каждом цикле. Таким образом, каждый раз, когда кнопка нажимается в каждом цикле, когда кнопка выключена, мы вычисляем несколько инструкций, хотя на самом деле нам нужно вычислить их только в первый раз, когда кнопка была выключена, например, в момент, когда ее значение изменилось со 100 на ноль. Таким образом, мы могли бы пойти еще дальше и сказать: если это не 100, то сравнить аккумулятор со 100 и со 100, и только если это 100, то мы установим их в ноль, и тогда нам не придется выполнять это многократно. Это важный урок, и он применим и к программированию на ассемблере. Это флаг нуля в процессоре, и в данном случае выбор того, будет ли вычисляться плюс или минус, основан на последней выполненной инструкции сравнения — не на той, что есть в исходном коде, а на той, которая была выполнена. Представьте, что мы проводим это сравнение. Это сравнение вполне справедливо. Итак, мы выполняем эти две строки. Теперь вопрос в том, не будем ли мы выполнять эту строку. Выполним ли мы эту строку? Да, это так, потому что последнее проведенное сравнение было именно этим. Таким образом, это означало бы, что мы будем выполнять эту строку всякий раз, когда проверка верна, а это не то, чего мы хотели добиться. Мы хотим, чтобы это выполнялось только в том случае, если мы взяли эту ветку, и теперь мы также берем, например, минусовую ветку, и собираемся взять ветку от нее. Так что, либо вы идёте домой и плачете, либо вы всё переворачиваете с ног на голову. Итак, вы проверяете, не является ли это значение тем, которое вам нужно, — в данном случае, я думаю, мы могли бы сделать так, чтобы оно равнялось нулю. А затем здесь возникает минус, потому что теперь минус здесь не зависит от того, что это плюс, и истинная проверка, потому что все они в любом случае отключаются, если это истинно. А это значит, что они будут выполнены только в том случае, если вы выполните это действие, и это действие также окажется ложным. Давайте посмотрим, поможет ли это. Ага. Теперь вы заметите, что мы не выполняем эти инструкции, когда кнопка выключена. Мы выполняем их только один раз, когда кнопка обнуляется, а значение аккумулятора установлено на 100. Поэтому его необходимо сбросить до нуля. Теперь вы видите, что потребление электроэнергии немного меньше, чем было раньше. Думаю, да, раньше у нас было 240. Теперь 226, потому что мы выполняем меньше инструкций. Но у нас всё ещё осталось много строк кода. Это всё равно хуже, чем первоначальное решение, которое у нас было. Поэтому использование состояния зачастую сложнее. Итак, давайте вернемся к тому, что у нас было: если P0 равно 100, то переместите 100 в P1, затем подождите один, затем переместите ноль в P1 и затем подождите один. Это была наша оригинальная версия: Беги, беги, беги. Прохладный. Теперь мы вернулись туда, где хотели быть. Вернуться к электронной почте. Эй, выглядит неплохо. Подходит для своей цели. Отличная ободряющая речь от босса. Подсветите вывески. Выдающаяся личность в мире киберспорта. Хорошо, нам нужно сделать светящуюся вывеску, которая будет анимироваться. Прохладный. открыть. Итак, мы снова откажемся от моего предыдущего решения. Мне грустно от того, что они здесь. И снова, здесь вы можете видеть, что происходит гораздо больше. И будем надеяться, что, возможно, мы сможем найти более разумное решение, чем то, которое я придумал в прошлый раз. Эм, вы заметите вот здесь, справа, а точнее, вот здесь. Итак, в анимированном киберспортивном логотипе у нас есть «клик ноль» и « клик один», которые являются выходами, подключенными к сегментам дисплея, соответствующим анимации клика. Как это выглядит? Э-э, ну, если посмотрите сюда, то вот здесь, внизу, что-то вроде руки на мыши. Итак, это будет анимация щелчка. «Выпить ноль», «выпить один» и «выпить два» — это простые выходные сигналы, соединенные между собой и отображающие сегменты, соответствующие анимации питья. А вот

Segment 10 (45:00 - 50:00)

здесь, внизу, нам нужно найти тот самый узор, которому мы должны соответствовать. Хорошо. Итак, как же мы это сделаем? Ну, нажать ноль и нажать единицу кажется довольно простым делом, не так ли? Это просто P1 и P0. Обратите внимание, что те, что слева, не являются входами, а те, что справа, — выходами. Это всего лишь штифты, которые можно установить в любом направлении. Эм, это в некоторой степени зависит от реальной жизни. Некоторые значки продаются только снаружи, некоторые — только внутри. Но есть контакты, которые находятся внутри или снаружи. В данном случае, этот узор я могу даже сдвинуть еще ниже, чтобы сэкономить место на макете, что очень удобно. Итак, что же мы хотим здесь сделать? Итак, мы хотим переместить число 100 в точку P0, верно? Итак, в самом начале, точка P0 должна быть равна 100, а точка Z соединена с точкой P0. Поэтому мы перемещаем 100 в точку P0. Затем мы перемещаем 0 в точку P1. Затем мы спим один час. На самом деле, мне кажется, мы могли бы сделать это получше. Мы могли бы переместить аккумулятор в положение P1, затем не перемещать аккумулятор в положение P0, и после этого не завязывать узел. Таким образом, значение, которое ранее было равно P0, в следующий раз перейдет в P1, а затем изменится на P0. Думаю, это позволит получить правильные значения. Прохладный. Таким образом, это правильно сработало для нулевого и первого щелчков, но, очевидно, остальные неверны, потому что мы их еще не выполнили. Итак, кто-то задал вопрос: чему равны X0 и X1? Ах, значит, X1 и X0 мы еще не использовали, но они работают по другому протоколу. Таким образом, выводы P0 и P1 здесь представляют собой своего рода непрерывные цепи. Суть в том, что вы устанавливаете значение на контакте, и теперь по этому контакту просто протекает питание, или, скорее, мощность, соизмеримая с установленным вами значением. x1 и x0 — это пакетная система. Таким образом, вы можете отправлять сигналы, которые представляют собой значение, полученное один раз, а затем больше не устанавливаемое. А вот если я установлю это значение на 100, и кто-то это прочитает, то будет 100. Если они прочитают это снова, то всё равно будет 100. Верно? Если его нет, значит, он не упакован. В то время как x0 и x1 упакованы в пакеты. Итак, если я перемещу, скажем, значение 100 в x0, это отправит один пакет, содержащий целое число 100, и принимающая сторона, когда будет считывать данные из своего хранилища, получит значение 100 при первом чтении. При втором чтении процесс заблокируется до тех пор, пока не придет другое сообщение. Сейчас они нам не понадобятся, но здесь, внизу, они могут пригодиться. Хорошо. Итак, выпейте ноль, выпейте один и выпейте два. Вот забавная история. Итак, этот напиток "Ноль" то включается, то выключается. Первый напиток готовится по такому рецепту. Выпивайте две порции — это именно такой порядок действий. Итак, я думаю, есть несколько способов это сделать. Мы могли бы использовать для каждого из них отдельный микроконтроллер, но это кажется нецелесообразным. Мы могли бы иметь микроконтроллер для напитков номер ноль и один, но не для двух. Но если присмотреться, то здесь прослеживается определённая закономерность. Верно? Итак, если взять нулевой напиток и второй напиток, то первый напиток — это произведение этих значений. Значит, выпить один бокал можно только если... Нет, это тоже неправда. Выпить один раз — NAND из этих значений. Таким образом, это верно только в том случае, если ни одно из этих утверждений не верно. Верно? Итак, этот включен, следовательно, этот выключен. Этот включен, а этот выключен. Поэтому мне интересно, не используем ли мы здесь какой-нибудь шаблон, чтобы улучшить ситуацию. Или ноль — это или нет, ноль не является или да, так что NAND и NORS взаимозаменяемы по порядку, так что один является инверсией другого. Итак, вопрос в том, как лучше всего это представить? Думаю, мы поступим немного

Segment 11 (50:00 - 55:00)

хитро, и посмотрим, получится ли у нас это хитро или нет. Но, думаю, мы будем использовать один микроконтроллер, который будет генерировать только сигнал "напиток ноль". Просто рассматривайте первый напиток как тот же генератор импульсов, что и в предыдущей задаче, вместо того, чтобы второй напиток можно было обработать с помощью логического сравнения нулевого и первого напитков. Да. Однако, и вот здесь начинаются сложности. Итак, если мы вернемся к руководству пользователя, то увидим, что единственные инструкции, которые у нас есть, по крайней мере, единственные инструкции, о которых мы знаем на данный момент, это движение, прыжок и сон. Это режим сна, пока вы не получите данные при экспорте, затем выполните сложение, вычитание, умножение, а затем вычисление цифр. Таким образом, у нас нет операторов «и» и « или», а также логических значений. У нас есть только целые числа. Однако, я думаю, мы можем сделать это, используя целочисленные значения, вычитание и точку сравнения. Так что мы могли бы сделать Каков шаблон для этого? Это как переместить 100 в точку P0, затем подождать 1, 2, 3, 4, 5, 6, подождать 6, а затем переместить ноль в точку P0. Но я думаю, мы также можем сделать так, чтобы это генерировало « О, бум». Теперь вы можете посмотреть игру. Эм, я думаю, мы могли бы сделать это и с помощью этого. Я не уверен, что это проще. Да, мне кажется, здесь вполне можно назвать напиток. Нет, здесь нужно немного больше места. Напиток, который мы можем смоделировать, как что? 1 2 3 4 5 6 7 справа. Ага. Переместить ноль в 7, переместить 100 в P1, подождать два. А потом я думаю: а можно ли это реализовать в физических логических элементах? Эм, у нас нет физических логических элементов, но есть вот эта штука, которую мы получаем через некоторое время, или, скорее, можем использовать сейчас, но пока рано её представлять. Хотя это всё ещё не совсем логические элементы, даже несмотря на наличие на ней символов, которые создают впечатление, что это логические элементы. Эм, и вот что, как мне кажется, является одной из интересных особенностей этой игры, верно? Именно это отличает её от игр на языке ассемблера, и именно это делает её скорее головоломкой, чем игрой, основанной исключительно на ассемблере: набор инструкций довольно ограничен. Обычно, когда вы программируете на ассемблере, у вас гораздо больше возможностей в плане используемых инструкций. Такие вещи, как, например, и, да, в итоге оказываются чрезвычайно полезными в подобных ситуациях. Но когда у вас чего-то нет и вам нужно проявить изобретательность. Эм, но мне кажется, в этом есть что-то реалистичное, не в смысле отсутствия инструкций, а в том смысле, что бывают ситуации, когда работаешь с микроконтроллерами, и у них просто нет необходимых инструкций. Поэтому вам нужно придумать другой способ это сделать. Это не совсем редкость. Эм, я вот о чём подумал: а можем ли мы это сделать? Таким образом, мы связываем этот экспорт с этим, а этот порт — с этим. Таким образом, когда это значение стремится к нулю, мы также отправляем, скажем, пока 100, на X1. Эм, верно. И, если присмотреться, это на самом деле означает, что я не выспался. В нём должны спать четыре человека. А потом, когда оно переместится сюда, я думаю, оно сможет перейти от нуля к x1. Э-э, это не доводит компиляцию до 100. Я не совсем понимаю. А, дальше находятся логические элементы. Ах, да. Это логические элементы. Хороший. Таким образом, мы сможем использовать их позже. Теперь вопрос в том, сможем ли мы сделать с этим что-нибудь интересное. Я думаю, мы можем сделать следующее: мы можем использовать очень простой трюк — подождать, пока что-нибудь не окажется на X0, и именно тогда мы сможем переместить

Segment 12 (55:00 - 60:00)

нулевой курсор. На самом деле, это можно сделать гораздо проще. Мы могли бы просто спросить: « Где мое сообщение? Оно нам не нужно». Мы просто это делаем. Поэтому мы спим, пока не получим сообщение здесь. Когда мы получаем сообщение, мы понимаем, что количество выпитых напитков сократилось до нуля. Итак, затем мы переходим от 100 к P0, то есть выпиваем одну порцию, а затем спим одну минуту. Затем мы перемещаем 0 в точку P0, а 100 в точку P1. Затем мы спим вдвоём. Так что это будет именно этот вариант. Затем мы перемещаем единицу в точку P0, а 0 — в точку P1. О, нам этого будет недостаточно. Поэтому я не могу добавить сюда больше строк, потому что микроконтроллеры устроены таким образом, что у них ограниченный объем памяти для инструкций. В любой микроконтроллер можно включить ограниченное количество инструкций. Этот предел очень мал, не так ли? Повторюсь, это игра-головоломка. Но дело в том, что, как и в настоящих встроенных системах, у вас будет ограниченное количество ПЗУ для размещения кода. Есть очень забавная история про, кажется, Crash Bandicoot для PlayStation 1, где у них, очевидно, было много графических ресурсов и всего такого, и игра была ровно такого размера, как и доступное им пространство. Таким образом, проблема фактически представляла собой задачу, подобную задаче упаковки в контейнеры, например, задачу о рюкзаке: как разместить все предметы в памяти, используя, по сути, tar-архив, как собрать их так, чтобы они поместились на имеющемся образе диска. И этот процесс был, по сути, случайным. Поэтому они перемешивали элементы, как перемешивали секции двоичной системы, и смотрели, подойдут ли они друг к другу. Если бы это не помогло, они бы перетасовали карты и попробовали снова. А ещё, за несколько дней до релиза, они поняли, что нужно изменить некоторые графические элементы. В результате упаковка перестала работать. Поэтому им пришлось изменить какую-то другую, не связанную с этим часть кода, чтобы снова выполнить перестановку дисков и уместить его в ПЗУ. Опять же, это, очевидно, крайняя версия этой проблемы, но это просто для того, чтобы показать, что в реальном мире у вас действительно ограниченное количество инструкций. Но да, я имею в виду, что в наше время, когда все так ограничено, на этом уровне нет никаких особых ограничений. Эм, хорошо. Думаю, нам не стоит здесь ночевать. Думаю, мы переместим X0, что будет равно 100. Таким образом, мы сократим некоторые инструкции, объединив их. Итак, мы знаем, что X0 будет равно 100. Поэтому мы можем перенести это значение в P0. Таким образом, нам не нужна эта инструкция. Тогда мы поспим один раз. Затем мы перемещаем ноль в P0, а 100 — в P1. Затем мы перемещаем единицу в точку P0. Мы перемещаем ноль в точку P1. Мы спим в одиночку. А затем перемещаемся. О, это должно быть перемещение на 100. Затем мы перемещаем ноль в точку P0. А потом возвращаемся к ожиданию X. Думаю, на этом всё, верно? Итак, что касается зацикливания, нам на самом деле не нужен сон на такой длительный период, потому что за этот сон отвечает этот компонент. Таким образом, это устройство будет отправлять нам сообщения только тогда, когда придет время проснуться и подать какие-то сигналы. Поэтому мы подождем, пока это произойдет, потому что чтение данных из X0 будет блокироваться до тех пор, пока это не случится. Когда это произойдёт, мы переместим значение 100 в P0, включив P0, что означает выпить один напиток. Оно подключено к этому контакту. Затем мы спим один час, то есть вот такой промежуток времени, верно? Затем мы их переворачиваем. Таким образом, мы перемещаем 0 в точку P0, а 100 в точку P1. Мы спим вдвоём, то есть вот такой промежуток времени. Затем мы переводим 100 в положение P 0, то есть снова включаем этот прибор. Чтобы снова отключить это, мы докажем значение от нуля до P1. Мы поспали один раз, а потом выключили этого парня. А затем мы ждём, пока не получим сигнал, чтобы повторить всё заново. Все в порядке. Смоделировать. Часть не спит. Действительно ли мне нужен сон? Потому что тогда я не смогу разместить инструкцию. Можно ли как-нибудь сократить здесь инструкцию? Э-э, я пока не вижу способа. Здесь установлен более крупный микроконтроллер, который также имеет больше портов и место для большего количества инструкций, но

Segment 13 (60:00 - 65:00)

он немного дороже. В итоге мы получим решение, которое окажется неоправданно дороже. H. Мы могли бы сделать это с помощью дополнительного микроконтроллера, но это кажется излишним. Да, я думаю, нам действительно нужен сон. Итак, это просто для того, чтобы показать, что подобная логика в целом обоснована, верно? Если мы будем спать до этого момента, то проведём симуляцию. Но да, проблема в том, что мы оставим P 0 включенным. Но я так и думал, если мы вернемся к инструкции по эксплуатации и посмотрим на то, что мы впервые используем экспорты. Где объяснение экспортов? Регистры. Подробности см. в соответствующих технических описаниях. Некоторые функции закрепления. Все в порядке. Ага. Итак, здесь обсуждается разница между режимом ввода режимом вывода. Эм, да. Таким образом, проблема заключается в том, что у более крупного чипа по-прежнему всего два вывода. У него нет трёх контактов. Просто у него больше экспортных возможностей, что нам здесь не поможет, потому что мы не можем использовать экспорт для питания светодиодов. Эм, но я пытаюсь найти... Да. Как видите, здесь внизу находится целая куча всего. И опять же, это еще раз подтверждает тот факт, что это игра-головоломка, и все эти головоломки в итоге оказываются именно такими. Эм, на самом деле, это скорее начало, когда я думаю: это полезно для обучения. Вот так Xbus X — это дискретные пакеты данных в диапазоне от -99 до 9999. Отлично. Expose — это синхронизированный протокол. Передача данных по каналам XO осуществляется только тогда, когда одновременно есть читатель, пытающийся прочитать данные, и писатель, пытающийся записать данные. Если попытка чтения или записи выполняется без соответствующей операции на подключенном устройстве, операция будет заблокирована. Но это, в общем-то, то, чего мы хотим. Но, как я понимаю, блокировать с помощью команды перемещения нам нельзя. Ага. Итак, вот большая часть MC, чтобы вы могли это увидеть. Таким образом, большой микроконтроллер по-прежнему имеет только P0 и P1, но у него есть X0, X1, X2, X3. Так что мы могли бы использовать этот файл, чтобы получить дополнительные инструкции. Но меня это немного огорчает. Кажется, в этом нет необходимости. Поэтому мне интересно, есть ли какой-нибудь трюк, который мы могли бы здесь провернуть. Например, отправка дополнительного груза по автобусу Xbus нам не поможет, потому что для этого всё равно потребуется дополнительный SLX. Хорошо, ладно. Мы сделаем это, мы сделаем дополнительный микроконтроллер. Мне грустно, но мы можем это осуществить. И мы переместим ноль в точку P0 — это было последнее, чего не хватало. Давайте сейчас избавимся от этих проводов, потому что нам все равно придется их снова подключать. И это, на самом деле, тоже довольно реалистично. Итак, когда вы начинаете заниматься монтажом проводов на платах, одна из самых сложных задач — правильно расположить провода на плате, чтобы они не пересекались, и разместить все микросхемы. Так что, в принципе, не так уж и нереалистично иметь возможность делать подобные вещи. А что, если мы используем кнопку P1 на верхнем контроллере для управления напитком номер два? Эм, мы могли бы это сделать. Эм, у нас, пожалуй, для этого найдется место. Это неплохо. Хорошо, давайте попробуем это на практике, раз уж у нас есть вот этот вариант. Давайте проведём симуляцию. Убедитесь, что это работает. А вот так теперь выглядит анимация после того, как

Segment 14 (65:00 - 70:00)

мы её настроили. Эм, но давайте тогда, э-э, теперь, когда он завершит работу. Итак, теперь у нас есть своего рода ориентир. Таким образом, у нас стало на одну строку кода меньше, чего, судя по всему, добилось большинство людей. Энергопотребление осталось прежним, но себестоимость производства немного выросла, что, вероятно, объясняется необходимостью использования более крупного чипа. Хорошо, теперь давайте попробуем отредактировать это еще раз. Давайте попробуем это вытащить. Таким образом, предлагается следующее: P1 будет заниматься вторым напитком, а первый напиток будет обрабатываться здесь же. А теперь это будет: сон один, ход 100 в P1, сон два, ход ноль в P1 и сон один, верно? И этот парень всё равно будет спать X0 ходов X0. Более того, теперь мы можем изменить и время, когда это произойдет. Нет, всё верно. Ага. Итак, получив это сообщение, мы поместим 100 в переменную P0. Затем мы поспим два часа. Тогда мы переедем. Нет, мы поспим один. Мы перенесём ноль в точку P0. Мы поспим два часа. Мы переместим 100 в точку P0. Затем мы А потом просто поспим. Нет, тогда мы поспим один раз. Затем мы переместим ноль в P0. О, вы можете проложить провода под платами. мило. Так что я не знаю, действительно ли это лучше. Мне кажется, в итоге мы написали больше кода, правда? Почему именно этот вариант? Да, потому что теперь нам придётся повторять цикл сна. Так что, я думаю, это всё-таки больше кода, пусть и с меньшими производственными затратами, верно? Это как меньше оборудования, но больше кода. Похоже, что все идет правильно, ведь количество строк кода осталось таким же, как и у большинства, а себестоимость производства та же, но потребление электроэнергии выше. Таким образом, мы выполняем больше инструкций, чем следовало бы. Как мы можем их сократить? Таким образом, это превращается в задачу оптимизации, и вопрос заключается в том, продолжать ли оптимизировать или переходить к следующему этапу. И я думаю, что это не совсем нереалистично и для встроенного программирования, где обычно есть энергетический бюджет или вычислительный бюджет, например, нужно выполнить что-то за определенное время, например, за определенное время на такт, иначе анимация начнет тормозить, или, если это игра, возникнут задержки. Или же есть бюджет на объем занимаемого пространства, или бюджет на инженерное время? Эм, но вы всегда можете вернуться и спросить: «А можно ли избавиться от этого чипа или объединить эти два чипа? » Э-э, вы можете поспать шесть часов, а один напиток не зависит от напитка ноль. Да, возможно, нам это больше не нужно Это немного грустно. Но я думаю, вы правы. Думаю, нам хватит на шестерых. Пропустите это. Я не знаю, насколько это существенно снижает мощность, но, возможно, и снижает. Эм, но в принципе, не так уж и неразумно столкнуться с компромиссным решением: потребление энергии немного увеличивается, но при этом она немного снижается по сравнению с предыдущим периодом. Знаете, когда прекращается оптимизация? Э-э, и я не думаю, что на этот вопрос есть правильный ответ ни в реальном мире, ни в игре. Хм, но интересно, что, как кто-то отметил в чате, иногда здесь возникают ограничения, которые обусловлены не только желанием сделать плату максимально эффективной, но и, например, получением оптовой скидки при покупке большего количества чипов. Так что на самом деле использовать четыре дешевых чипа выгоднее, несмотря на то, что это дороже, чем использовать несколько разных, потому что вы получаете большую скидку, купив больше таких чипов, верно? Таким образом, существуют всевозможные интересные ограничения на сам способ проведения оптимизации. Думаю, в рамках целей игры мы перейдём к следующему шагу. Хотя здесь, как вы, очевидно, также видели в

Segment 15 (70:00 - 75:00)

результатах проверки, существуют способы оптимизации этой конструкции, которые использовали другие люди. Длина проводов в этой игре тоже не имеет значения, хотя в реальном мире это важно, и зависит от того, что вы пытаетесь сделать и на каком расстоянии находитесь. Эм, хорошо. Итак, вернёмся к нашей электронной почте. Э-э, назад. Ура. Они очень счастливы. Выведите барона. Ах, нам нужно обеспечить их личными секретарями для этой небольшой игры. Давайте откроем его. А теперь, если мы действительно захотим, мы сможем использовать и новое устройство. Все в порядке. Итак, что же мы здесь имеем? У нас есть витрина. Нажатия кнопок "point" и "foul" — это простые действия, выполняемые с помощью кнопок. Это дисплей Xbus. Это означает, что используется пакетный протокол, соответствующий цифровому ЖК-экрану. Счетчик должен отслеживать значение, начиная с нуля, и обеспечивать постоянное отображение текущего значения на дисплее. При нажатии на точку счетчик должен увеличиваться на единицу. При совершении фола счет следует уменьшить на два, но не ниже нуля. Итак, давайте начнём с медленного, но простого способа, а именно: У нас два входных контакта. У нас есть вот этот и вот этот. У нас есть один выход X, который выводится на экран. И на каждом цикле мы будем проверять, какая из кнопок подсвечена. И мы скорректируем счет соответствующим образом. Обратите внимание, что на этом чипе два регистра. У нас есть регистр накопления, и у нас есть, э-э, регистр DAT. О, вы правы. Ведь прямо здесь можно создать новый дизайн, не так ли? Ага. Нет, мне следовало так сделать. Теперь вы ошибаетесь. Ну и ладно, ничего страшного. Итак, что мы можем сделать? Мы хотим сравнить птиц, или, давайте сначала нажмем кнопку "Набрать очки". Мы хотим сравнить значение кнопки "P1" со значением 100. Если значение этой кнопки равно 100, то мы хотим добавить единицу в накопитель. Эм, тогда мы хотим сравнить P0, которое является кнопкой "птица", со 100. Эм, и здесь мы вычесть два. А затем мы хотим сравнить, меньше ли текущий балл нуля, и если он меньше нуля, то мы обнуляем его. Итак, если мы снова вернёмся к руководству, к инструкциям по проведению теста, вы увидите, что там есть «tak», которое мы уже использовали, а также « больше» или «меньше», так что мы можем использовать один из них. Мы возвращаемся сюда и говорим: «Э-э, меньше, чем аккумулятор, меньше нуля». Если это так, то переместите ноль в аккумулятор. А затем мы хотим переместить аккумулятор в X3, а потом хотим перевести его в спящий режим на одну секунду. Это, так сказать, не хочу показаться глупым, но это очень простой способ сделать это, верно? Мы ведем учет результатов в накопителе и каждый цикл проверяем, что происходит. Ура, мы это сделали! Итак, это восемь строк кода. Знаете, кажущееся среднее энергопотребление и себестоимость производства немного выше, чем у некоторых других людей. Им удалось сделать это за четыре иены вместо пяти. Эм, я думаю, в Foren это делается так: вместо одного чипа, выполняющего относительно повторяющиеся вычисления, используется другой чип. Так что, это также относится и к встроенному программированию, например, более простые микросхемы дешевле. Итак, посмотрите сюда, этот микрочип, на котором я могу писать код, это третий, а вот эта логическая штука, которая просто... ну, мы объясним, что она делает, чуть позже. Этот стоит всего один. Значит, это намного дешевле, верно? Потому что в нём нет настраиваемой логики. Это будет гораздо более простая микросхема, и, как следствие, её производство обойдётся дешевле. Что же это за чип? Это DX300. Давайте вернёмся сюда. И это как раз то, что постоянно приходится делать в сфере встроенного программирования: " Давайте посмотрим справочное

Segment 16 (75:00 - 80:00)

описание этого оборудования". Итак, DX300, цифровой расширитель ввода-вывода, — уникальная и полезная деталь, которая может считывать или записывать до трех сигналов включения или выключения одновременно по простому интерфейсу ввода-вывода или шине Xbus. А значит, он может записать трёхзначное число на любой вывод шины Xbus, чтобы изменить состояние простых выводов ввода-вывода. Верно? Таким образом, мы можем ввести трехзначное число в любой из этих входных сигналов Express. и это изменит состояние выходных сигналов ввода-вывода. Три цифры записанного значения будут использоваться для включения или выключения простых контактов в зависимости от того, является ли эта цифра единицей или нулем. Таким образом, если вы напишете 100, то P0 будет равно нулю. P1 будет равно 0. P2 будет равно 100. Таким образом, P2 включено, что равно 100. Это P1 равно нулю, что равно нулю. А это P0, то есть 0. То есть ноль, но также можно считывать данные с любого из выводов XOS, чтобы получить трехзначное число, отражающее состояние простых выводов ввода-вывода. В зависимости от используемых выводов простого ввода-вывода, цифры в полученном значении XUS будут установлены либо в единицу, либо в ноль. Таким образом, эту таблицу можно прочитать и в обратном порядке. Если это состояние контактов, то именно это вы и получите из шины Xbus. Итак, как это может выглядеть? Итак, если мы возьмём это и перевернём так, чтобы P-значения оказались слева, то это означает, что здесь мы можем прочитать только одно значение x. Поэтому мы остановимся на x0. И вот что мы получим: если это значение равно, если оно включено, то мы получим число, и верхний параметр равен P2. Тогда в первой цифре будет единица. Это будет единица во второй цифре. Это был бы один из них. Нет, это П что? P 0. Таким образом, в самой правой цифре будет единица, в средней цифре — единица, в самой левой цифре — единица. Итак, теперь нам остается только подождать, пока мы получим отсюда одно значение. Таким образом, нам достаточно прочитать данные один раз, чтобы получить оба значения одновременно. Итак, мы можем переместить x0 в аккумулятор. А вот теперь начинается самое интересное. Итак, мы получаем это значение, и наша задача — выяснить, как оно влияет на общий балл. Вместо того чтобы отдельно вычислять плюсы и минусы, мы можем просто посмотреть, как это общее значение меняет балл. Здесь мы можем сравнить значение в накопителе со значением, находящимся посередине. Итак, давайте сделаем следующее: если P1 включен, то это будет 0 1 0. Если P 0 включен, то это будет 001. Правильно? Это будет считаться нарушением. Это был бы важный момент. А теперь вот здесь мы можем сделать кое-что действительно хитрое. Итак, если мы вернемся к руководству, то инструкция CP сравнивает значения двух параметров, и в зависимости от ситуации мы либо включаем инструкции «плюс», либо «минус», либо ни то, ни другое. Если они равны, то ничего не произойдет. Таким образом, по сути, одно сравнение дает нам три различных условия ветвления. Итак, если мы сравним это с 001, то увидим, что в случае, когда мы получили точку, инструкции не выполняются, а это значит, что нам не нужно перемещать это в вспомогательное устройство. Мы хотим добавить еще одну инструкцию, потому что если возникнет такая ситуация, то мы больше не сможем ее выполнить. Поэтому мы всегда будем добавлять один. Теперь, если это... если это плюс, то плюс будет... давайте посмотрим для сравнения... плюс — это если первый операнд больше этого значения. То есть, если аккумулятор больше этого значения, я думаю, что это должно быть x0. Если x0 больше единицы, это означает, что значение установлено. Тогда нам нужно вычесть три, потому что мы уже добавили единицу. О, вы не можете посмотреть игру. Извини. А как насчет сейчас? Теперь вы можете посмотреть игру. Итак

Segment 17 (80:00 - 85:00)

мы сравниваем x0 с единицей, то есть устанавливаем именно эту точку. Если значение x0 больше единицы, это означает, что мы получили это значение из шины xbus, верно? Потому что это значение здесь будет 0 1 0, что больше 0 1. Нам нужно подставить три, потому что мы уже добавили единицу, чтобы правильно обработать случай, когда эти два значения равны, то есть мы получили точку, а затем мы можем сделать то же самое в другом случае. Таким образом, в данном случае точка не установлена, и фол не установлен. В таком случае добавлять или удалять баллы не следует. Поэтому нам нужно отменить добавление. Поэтому нам нужно заменить один из них. Извини. Вычтите единицу. А затем мы можем сделать следующее: если P 0 равно нулю или меньше нуля, то мы перемещаем 0 в аккумулятор, а затем перемещаем аккумулятор в X1. Таким образом, это более сложный и запутанный код, но зато компоненты дешевле. Давайте посмотрим, что будет дальше. Ладно, по какой-то причине это сломалось. спит. Почему эта часть не спит? Там можно поспать. Мне нужно поспать один раз? Полагаю, что так. Ну, это не сработает. Почему это не работает? Итак, мы можем пройти сюда. Ага. Таким образом, в начале мы правильно вывели ноль. Потом мы засыпаем. Мы снова поднимаемся. Мы снова делаем то же самое. По-прежнему ноль. Вернемся к ex0. Ага, понятно. Вот почему. Это потому, что этот компонент, когда вы из него считываете данные, всегда доступен. Оно работает скорее как булавка, чем как пакет. Так что, по сути, нам здесь нужно просто сравнить данные, потому что каждый раз, когда мы считываем их, это текущее состояние, а затем мы останавливаемся внизу. Хорошо, но подождите. Мы по-прежнему всегда выдаем ноль. Так почему же так происходит? Давайте вернемся назад. Давайте перейдём к следующему шагу. Пройдите один шаг вперед. Итак, как видите, здесь 100 очков за фол и ноль за попадание в цель. Итак, следующий шаг должен заключаться в том, что в итоге мы получили положительный результат, если сравнить эти два значения. В итоге мы получили значение выше точки, что неприемлемо. Итак, мы заходим сюда и вычитаем три. Таким образом, значение аккумулятора теперь равно минус 2. Затем мы сравниваем значение аккумулятора с нулем. А, вот в чем проблема. Это потому, что у меня здесь не было бонуса. Поэтому это всегда выполнялось. В то время как нам нужно, чтобы мы сбрасывали аккумулятор только в том случае, если его значение меньше нуля. Отлично. Это кажется многообещающим. Ага. Теперь вы видите, что себестоимость производства составляет 4 иены. Предыдущим было пять. Наше потребление электроэнергии примерно одинаково. На самом деле, оно немного выше, потому что в итоге мы выполняем больше инструкций. Предыдущий вариант был гораздо более условным. Однако наши производственные затраты по-прежнему выше, чем у кого-то другого. Таким образом, кому-то удалось это сделать с помощью трёх микроконтроллеров, а это значит, что они использовали всего лишь небольшой микроконтроллер. Это интересно. А, значит, вы хотите, чтобы игра была громче? Хорошо, я сделаю игру немного громче. Вот и всё. Возможно, мы сможем сделать это даже без моста, то есть P1, подключим это к P0, добавим... я думаю, вы бы сделали что-то вроде сравнения точек. Возможно, вы бы сравнили P0 с P1. Мне кажется, есть способ сделать это так: сравнить два сигнала и посмотреть, какой из них выше. А если ни один из них не выше другого, то

Segment 18 (85:00 - 90:00)

аккумулятор трогать не нужно. Итак, если P0 выше, то мы подставляем значения, а затем вычитаем два. Если P1 выше, то мы добавляем единицу и затем проводим сравнение. Это поможет? Да, даже лучше. Мы сохранили компонент. Ну как вам? Хорошо, себестоимость производства составляет три. Наше энергопотребление лучше, чем, по-видимому, у большинства людей. И семь строк кода. Ага. Эм, и я думаю, это также является показателем особенностей программирования в целом, а не только ассемблера, верно? Иными словами, к лучшему решению можно прийти только в том случае, если сначала попробовать неудачное решение, а затем разобраться, как перестроить систему таким образом, чтобы постепенно приблизиться к более удачному результату. Хороший. Хорошо, давайте вернемся к нашей электронной почте и посмотрим, что мы здесь получили. Они счастливы. Вы счастливы? Ужасная звуковая система. А теперь я нажму кнопку «Новый дизайн». Большой. Хорошо. Аудиовход — это простой вход, подключенный к источнику звука. Аудиовыход — это простой выход, подключенный к аудиоприемнику. Функция Maximize — это простой вход, подключенный к переключателю. Сигнал с аудиовхода следует скопировать на аудиовыход, применив алгоритм максимизации гармоник. Когда переключатель «Развернуть на весь экран» включен, в разделе дополнительных данных руководства пользователя вы найдете рекламу, демонстрирующую этот алгоритм. Хорошо, давайте вернемся к нашему руководству. Дополнительная информация о гармонической ошибке максимизации данных звучит нереально. Нет, это звучит как гармоническая максимизация. Хорошо, значит, этот алгоритм является строго конфиденциальной информацией. Никому об этом не рассказывайте. Аудиовход - 50 * 4 + 50. Отлично. Все в порядке. Похоже, это, скорее всего, что-то вроде крупного микроконтроллера. На самом деле, здесь сложнее, потому что у нас два контакта, которые нужно подключить, и аудиоканал — это... если вы перейдете к проверке, то увидите, что значение аудиоканала здесь не равно нулю или ста. Оно фактически меняется в пределах этих двух значений. Итак, что нам здесь нужно будет сделать, это немного спланировать расположение контактов, что, опять же, очень распространено в программировании встроенных систем. Вам нужно запомнить, сколько у вас значков, для чего их можно использовать и какие из них вы уже использовали для чего-то другого, потому что это должен быть именно значок. Это должно быть креплением, и это тоже должно быть креплением. Ну, ни у одного из них нет трёх штифтов. Поэтому я думаю, что в данном случае нам либо понадобятся два микроконтроллера, например, два маленьких, либо этот единственный микроконтроллер, который будет использоваться только для развертывания на весь экран, чтобы преобразовать его в экспортируемый файл. Таким образом, преобразовав его в экспорт, мы сможем прочитать его через экспорт здесь, а затем прочитать это здесь. Проблема с экспортом в том, что нам нужно поспать, чтобы их прочитать, но когда у нас есть эта штука, мы этого делать не можем. Думаю, это вполне допустимо. Таким образом, теперь значение X0 будет устанавливаться равным 0 1 0 всякий раз, когда удерживается кнопка развертывания. А P0 будет фактическим аудиосигналом. А затем мы будем выводить звук сюда. Итак, как это будет выглядеть? Итак, мы сравним X0 со 100. Извините, с 0 1 0, что соответствует среднему порту. Если он включен, то есть устанавливается соответствующая настройка, то, по сути, я думаю, мы всегда будем добавлять p 0 в аккумулятор без перемещения. В этом случае мы используем алгоритм: подставляем 50, умножаем на четыре и прибавляем 50. А если это не так, то просто оставляем исходное значение. Затем мы перемещаем аккумулятор в P1. Посмотрим, что из этого выйдет. Ах да, и нам нужно поспать. симулировать. Выглядит неплохо. В целом, меня всё устраивает. Таким образом, мы могли бы довольно легко сделать это с помощью более крупного микроконтроллера или, точнее, всего лишь с помощью двух меньших микроконтроллеров, но меньший микроконтроллер в итоге просто дублировал бы то, что делает вот этот микроконтроллер.

Segment 19 (90:00 - 95:00)

Эм, хорошо, вот это у нас есть. Мы неплохо справились. Посмотрите на это. Это на самом деле лучшее решение, чем то, которое я использовал в первый раз, когда проходил этот уровень. Хороший. Хорошо, вернитесь к электронной почте. Эта штука потрясающая. Инфракрасный датчик открыт. Что мы здесь видим? Время — это простой входной сигнал, подключенный к тактовому генератору DT 2415, который отображает текущее время. Датчик представляет собой простой вход, подключенный к инфракрасному датчику. Так что это будет именно этот вариант. Сигнализация — это простой выход, подключенный к простой сигнализации. Когда текущее время совпадет со временем включения, устройство следует активировать. выключения, устройство следует снять с охраны. Всякий раз, когда устройство находится в режиме охраны и показания датчика равны или превышают значение 20, должна срабатывать сигнализация. Время включения и выключения устанавливается оператором с помощью регуляторов, показания которых считываются на входе Xbus и представляют собой числовые значения, совместимые со временем. Итак, нам нужно что-то, что определит, включена ли в данный момент сигнализация или нет. А затем мы начали делать это, и при этом значение датчика превышало 20. И вот здесь некоторые порты могут оказаться действительно полезными. Но давайте посмотрим. Эм, нам определенно нужен компонент, который будет сравнивать эти числа. И я думаю, нам нужен именно этот. Это небольшой микроконтроллер, который имеет только экспортируемые функции. О, это не экспорт. Прохладный. Тогда нам это не нужно. Тогда нам нужен стандартный вариант. Итак, мы собираемся это сюда занести. Затем мы получим X отсюда и X отсюда. А мы хотим переместить точку X0. Полагаю, мы хотим поспать ради X0. На самом деле, это интересный вопрос. Мне кажется, время включения и выключения музыки не меняется. Думаю, мы можем прочитать их всего один раз. В таком случае мы переместим точку X0. На самом деле, мы даже можем просто сравнить их напрямую. Нет, нам нужно как-то сохранить это время. Интересно, может, нам понадобится модель побольше? На самом деле, я думаю, что более крупный вариант позволит нам сделать все это, потому что тогда мы сможем подключить датчик и сюда. Нет, мы не можем, потому что нам понадобится три булавки, верно? Нам понадобится булавка отсюда, булавка отсюда и булавка для этого. Хорошо, давайте посмотрим, чего мы добьемся в этом деле. Итак, это будет наш P1. Это будет наш P 0. Нет, это не сработает. Вот так всё и произойдёт. произойдёт. Хорошо. Итак, что же мы здесь ищем? Мы хотим дождаться наступления момента включения, а затем хотим подать сигнал. И вот здесь, как вы думаете, мы сможем легко проложить проводку снизу? Это непросто. Нет. Хорошо. Но мы можем перекинуть сюда мост. Таким образом, мост — это просто соединительный элемент, позволяющий разместить это пространство здесь. И тогда, я думаю, нам будет сложно преодолеть оба этих препятствия. Возможно, придётся перенести этот экземпляр подальше. Думаю, в этом и будет вся хитрость. Поэтому мы перемещаем этот экземпляр дальше. просто чтобы оставить немного места для проводов. Э-э, чтобы это наконец исчезло. Возможно, мост. И, собственно, здесь мы можем просто построить мост. Таким образом, этот штырь станет нашим выходным штырем. О, а можно перенести время? О, вы можете изменить время. Ах, да. Но это нам лишь отчасти помогает, верно? Итак, мы можем переместить этот. Это мило. Полагаю, датчик, а затем использовать мост для передачи данных с него. Конечно.

Segment 20 (95:00 - 100:00)

И в итоге мы получим: « О, вообще-то, это неловко». Это тоже не сработает. Думаю, это должно быть что-то важное, потому что это схема расположения контактов, но это также и схема расположения контактов. И здесь всего два штифта. Поэтому я думаю, что нам понадобится один из тех, которые имеют толстую мякоть И теперь здесь можно использовать экспорт, здесь — P-порт, а там — P-. Это касается времени, а это — всего остального. Но я думаю, для этого нужен большой штырь, иначе штырьков будет недостаточно. Итак, как это будет выглядеть? Ну, я думаю, мы собираемся сравнить... перенести X1, то есть время включения, в аккумулятор. Мы переместим X2 в А затем, э-э, соедините их оба с помощью и. Таким образом, мы могли бы соединить их оба с помощью и, но тогда нам понадобилось бы два микроконтроллера и, а этот большой микроконтроллер — это всего лишь еще два. О, может быть, это спасёт нам один шанс. Давайте сначала попробуем так, а потом посмотрим, сможем ли мы это упростить. два маленьких и один, и это дороже, верно? Шесть, семь. О нет, это семь против восьми. Давайте сначала попробуем так. Хорошо, теперь мы сохраним здесь время включения и выключения. А затем мы скажем что-то вроде того, что подождем, и сравним текущее время, которое равно P0, с аккумулятором. А если это не равно, то мы поспим один раз, а потом перейдем к ожиданию. Итак, это цикл в ассемблере, верно? Вы просто выполняете какие-то действия, а затем возвращаетесь к чему-то, что проверяет условие. Теперь, когда это сделано, это означает, что мы прибыли вовремя. И я думаю, что теперь, когда мы достигли времени включения, мы переместим 100 на x3, а затем снова сделаем то же самое, то есть выключим, при этом p 0 будет равно времени выключения, которое мы сохранили в другом регистре. Если значение не равно, то мы делаем паузу на один шаг, затем переходим в состояние "выключено", а в конце возвращаемся в состояние "ждать", потому что теперь мы будем обрабатывать все это заново, хотя между этими шагами нам нужно переместить ноль в состояние x3. Итак, здесь нам нужно подождать, пока нам не сообщат, что система поставлена ​​на охрану, затем мы переместим значение x0 в состояние x100, потому что мы хотим выполнять эти действия только тогда, когда срабатывает сигнализация. Когда срабатывает сигнализация, если данные датчика показывают, что P0 больше 19, верно? Если 20 или больше, то мы перенесем 100 в P1, верно? Это значит, что нужно включить сигнализацию. В противном случае мы переместим ноль в P1. А вот ещё один интересный момент: этот знак минус означает, что это ветвь else как для этого равенства, так и для этого равенства. Но ведь это, в общем-то, то, чего мы хотим, верно? Потому что мы хотим установить значение на ноль, если мы сейчас ниже порогового значения тревоги или если мы сейчас находимся в нерабочем режиме. В обоих случаях мы хотим, чтобы P1 вернулся к нулю, верно? Давайте посмотрим. Посмотрим, что из этого выйдет. О, мы никогда не выключаем будильник. О, мы его здесь выключили. Таким образом, мы отключаем его в момент выключения, но не отключаем, если уровень сигнала опускается ниже порогового значения. Почему нет? Давайте пойдём сюда.

Segment 21 (100:00 - 105:00)

Давайте посмотрим. Итак, это больше 19. Поэтому мы переходим на 100. Затем мы возвращаемся к началу. А теперь сюда никто не поступит, верно? Вся эта система процессора просто ждет момента выключения. На самом деле, мы могли бы сделать это умнее, не так ли? При желании мы можем заставить его спать до самого выключения. В настоящее время он как бы приостанавливает выполнение на один шаг и снова проверяет условие. На самом деле это не обязательно, потому что оно и так знает, сколько времени ему нужно спать. Но это потребует больше кода, хотя и более эффективно. Эмм, вот в чём вопрос. Ой-ой, это потому что... Да, я знаю, почему так происходит. Это происходит потому, что, когда мы добираемся до сути, мы зацикливаемся, пока не получим еще один сигнал на X0. Мы получаем ещё один сигнал, по сути, ещё одно прерывание, только когда это происходит снова, то есть когда кнопка H выключается. Да, именно здесь нам и нужен аналог кнопки выбора, верно? Мы хотим выбирать сигнал, срабатывающий либо через секунду, например, после истечения цикла или при наличии чего-либо на x0, либо непрерывно, но тогда у нас недостаточно портов для этого. Ага. Так что, возможно, в итоге мы окажемся в ситуации, когда произойдет нечто вроде "и то, и другое". Хорошо, мы выберем предложение, которое, по сути, довольно простое с логической точки зрения, верно? Это просто сравнение значений P0 и P19. Если значение больше 19, то вы перемещаете 100 в P1, в противном случае вы перемещаете ноль в P1, а затем засыпаете на один ход. А затем мы возьмём ещё один документ, и я вернусь к руководству. А где находятся эти "и"? Нет, это... 08 — логический элемент И. Хорошо. Так что мы вернемся сюда. Мы возьмём 08, это логическая операция И, и закончим на P1, а этого парня выведем сюда. А что же делает логический элемент И? Логический элемент И будет включен только в том случае, если оба его входа активны. Итак, мы оба вооружены, и... датчик сработал. Теперь же мы переместим ноль туда, а Z — сюда. Посмотрим, что из этого получится. В общем, это неплохая визуализация, правда? В целом, можно увидеть, что именно происходит. Впрочем, это кажется немного расточительным. Итак, потребление энергии довольно низкое. Производственные затраты немного высоковаты, не так ли? Опять же, потому что у нас было больше компонентов, которые нам действительно были нужны. Количество строк кода тоже довольно велико. Создаётся впечатление, что логический элемент И должен был бы, если уж на то пошло, сэкономить нам хоть какой-то объём кода, но, похоже, этого не произошло. В связи с этим возникает вопрос: стоит ли нам это улучшать или нет? Я покажу вам, что я делал в прошлый раз. Вот этот. Поэтому здесь я использую это в качестве выходных данных. Таким образом, я использовал это только для преобразования экспорта в порт, и это сработало довольно хорошо. И вы заметите, что код очень похож. Итак, мы двигаемся и считываем два значения времени: включено и выключено. Затем у нас есть один цикл, который ожидает наступления времени включения. Затем мы сравниваем значение датчика с 19. И если оно установлено, то мы просто включаем эту функцию. А потом мы засыпаем. А потом мы это отключим. А затем мы снова переходим сюда, где проверяется время отключения. А затем снова проверяет датчик. Получается, что выбор довольно ограничен, верно? Сначала проверяют, не наступило ли время окончания акции? Если мы дойдём до контрольного времени, то перейдём к скачкам веса. Итак, мы снова поднимаемся наверх. В противном случае, мы проводим сравнение с датчиком. Мы делаем это в течение одного цикла, а затем проверяем снова. Таким образом, если вы видите это, то в итоге получается вот это

Segment 22 (105:00 - 110:00)

где множество сообщений передается сюда каждый цикл, если срабатывает сигнализация. Мы отправляем сообщение с просьбой включить его. И, как видите, это на самом деле довольно неплохо работает. Хм, наверное, есть способ сделать это более эффективным. Я почти уверена, но в итоге получается что-то похожее. Перенесите время в отдельную небольшую ячейку, а датчики и инфраструктуру можно разместить в другой небольшой ячейке. Думаю, это, вероятно, правда. Тогда стоимость также составит шесть. Но это просто говорит о том, что это совершенно другой макет. Хотя код довольно похож, это совершенно другая конфигурация по сравнению с той, что была в другом решении, верно? Итак, что же мы будем делать дальше? Звуковой сигнал виртуальной реальности. Новый дизайн. Думаю, это первый случай, когда я этого ещё не сделал. Итак, что же у нас здесь? Вход Radio RX представляет собой неблокирующий вход Xbus, подключенный к радиоприемнику. Зуммер — это простой выходной сигнал, подключенный к электромеханическому зуммеру. При получении пакета данных по радиосвязи прочтите его и выполните соответствующую команду из приведенной ниже таблицы. Ноль означает выключение питания, поэтому выключите звуковой сигнал. Первый вариант означает включение питания и включение звукового сигнала. Кажется, всё должно быть довольно просто, не так ли? Знаменитые последние слова. Итак, мы ждём, когда что-нибудь появится в наличии на X0. Эм, мы перемещаем X0 в... Возможно, нам даже не понадобится это делать. Мы сравниваем X0 с пакетом данных, который в этот раз равен нулю или единице. Итак, если оно равно единице, то мы перемещаем 100 в P1. В противном случае мы переместим ноль в точку P1, верно? А еще, кажется, нам нужен прибор для сна. Ага. Хорошо. Вот почему всё так сложно: это сообщение приходит нам только в случае изменений. Поэтому мы не можем спокойно об этом думать, потому что тогда мы просто перевернём всё с ног на голову. А ещё потому, что звуковой сигнал должен включаться и выключаться. В противном случае, это был бы не совсем звуковой сигнал, не так ли? Так что, я думаю, да, именно так. Мы делаем это только один раз, когда получаем сигнал включения. Хорошо. Вот почему все так сложно. Прохладный. Думаю, нам здесь нужна память, верно? Нам нужно помнить, что для каждого цикла должен быть включен или выключен звуковой сигнал, и нам нужно это отмечать для каждого цикла. Давайте воспользуемся большим файлом, а затем, если нам не понадобится весь код, мы сможем перенести его в меньший. А теперь, я думаю, да. Итак, вот что мы собираемся сделать. Это интересно, потому что у него есть такое свойство, верно? Порт, который мы считываем, нам нужно переключить, потому что если на нем ничего нет, то мы просто хотим изменить сигнал и продолжить. Но здесь, по сути, нет условного прочтения, что меня удивляет, хотя и утверждается, что эти части нам не нужны. Как видите, нерекомендуемая часть игры как бы указывает на то, что проблему можно решить и без использования этих частей, что меня и заинтересовало. Думаю, это можно сделать, используя всего две микросхемы. Значит, у вас есть устройство, которое преобразует это в непрерывный сигнал, верно? Таким образом, оно получает вот это. Думаю, мы могли бы сделать это лучше, но, знаете, да, это, наверное, небольшая проблема, поэтому мы будем ждать x0. Оно переместится и просто сравнит x0 с единицей. Если это единица, то мы перемещаем 100 в p1. В противном случае мы перемещаем ноль в

Segment 23 (110:00 - 115:00)

p1. Таким образом, теперь всё должно заработать, и программа должна перейти в спящий режим. И теперь эта функция сводится к тому, чтобы определить конец отсчета времени и включен ли зуммер. Возможно, мы сможем сделать это еще проще. с помощью оператора «и». Мне нравятся ворота, верно? Таким образом, 08 — это «и». Таким образом, мы сможем сделать это с помощью оператора `and`. А ещё есть отдельная вещь, которая представляет собой, по сути, чип времени. Ах, но это не сработает, потому что часы должны начинать отсчет времени, когда они начинаются. Эм, мы можем это сделать, если вдруг до нас дойдет мысль: « Ага, мы можем», потому что я узнала каркасных конструкциях. Итак, мы можем убрать это отсюда, а затем сказать: «Когда... ». Тогда у вас снова возникнет проблема с выбором, потому что это, по сути, просто решение проблемы с выбором, верно? Простое преобразование дискретного импульса в непрерывный. Проблема здесь в том, что это не может быть просто выделенный тактовый сигнал, поскольку ему нужно сообщать, когда начинать тактирование. Ага. Поэтому я думаю, что это, по крайней мере, не может быть рудой. Я думаю, что в таком случае это должно быть именно это и это. Так чего же мы здесь хотим? Мы хотим, чтобы, если P0 равно 100, мы не обращали внимания на то, что находится в аккумуляторе. Затем мы хотим переместить этот аккумулятор в P1. Это примерно то же самое. О, нет. Это печально. Действительно? Хорошо, ладно. Нам придётся сделать это по-другому. Неужели они мне действительно не подойдут? Хорошо, ладно. Значит, всё будет именно так, верно? Мне это делать нельзя. Вот так. Это соединяет контакт? Такое ощущение, что контакт не соединяется. Хорошо, тогда пойдем этим путем. Это соединяет контакт? Кажется, это соединяет контакт. Если это не соединяет контакт, я не знаю, что ещё может это сделать. Хорошо. Эм, значит, мы перемещаем аккумулятор в P1. Таким образом, этот узел дает нам тактовый сигнал. или переменный тактовый сигнал. А если иначе, то да. И здесь мы можем применить тот же приём, что и в предыдущих коллекторах, чтобы сэкономить энергию: мы сбрасываем состояние только в том случае, если аккумулятор равен нулю. Хотя это на самом деле ничего не меняет, потому что проверку на равенство всё равно приходится выполнять каждый раз. Так что, я думаю, мы по-прежнему просто переносим ноль в аккумулятор, а затем засыпаем на единицу. Посмотрим, что из этого выйдет. Это совершенно неверно. Почему? Пройдите сюда. Итак, мы получаем пакет данных по радио. Мы проверяем, равно ли оно единице. Оно не равно единице. Таким образом, выходной сигнал равен нулю. Итак, мы возвращаемся назад. Мы перечитали это еще раз. Ничего нет. Теперь это один. Поэтому мы проверяем, является ли это так. Это. Таким образом, на P1 выводится значение 100. А теперь вот это радио такое же, как это. Они как будто не синхронизированы, верно?

Segment 24 (115:00 - 120:00)

Вы это видите? Пока тот ждет, этот выполняет приказ. Итак, когда мы доходим до этого момента, когда этот компонент включает P1, этот компонент находится в спящем режиме, потому что тот компонент должен находиться в спящем режиме на самом верху. И я имею в виду, теоретически мы могли бы это исправить, поспав здесь, наверху, но тогда, если первым делом включится радио, мы ошибемся. Но здесь есть еще одна проблема. Да, это как-то неправильно. Чтобы прочитать предварительную версию, перейдите сюда. Поэтому в следующий раз он автоматически включится. Мне кажется, это вызывает сигнал каждый раз. О, я понимаю, что здесь происходит. Я знаю, что здесь происходит. Хорошо, давайте вернемся к руководству по данным. А где же? Все полученные данные передаются во внутренний неблокирующий буфер. При чтении данных, когда данные недоступны, будет получено значение -9999, а не произойдет блокировка, в отличие от типичного поведения шины XBUS. Прохладный. Так что это, в некотором смысле, упрощает нам жизнь, потому что это означает, что нам не нужен этот компонент, единственная задача которого — сделать всю систему синхронной. Итак, давайте все успокоимся. Давайте на секунду отвлечемся от этого и вернемся к этому прямо сейчас. Думаю, это может означать, что мы можем это сделать. Итак, мы воспользуемся тем же приемом, что и раньше: одним сравнением мы сможем проверить соответствие между тремя значениями, сравнив их со средним значением, которое равно нулю. Если значение меньше, это значит, что данных нет, поэтому мы ничего не делаем. э-э, если это правильно. Итак, что нам нужно сделать здесь, и нам, возможно, потребуется два сравнения, потому что нам нужно что-то сделать в случае, если значение равно нулю. Но, возможно, мы сможем сделать это проще. Так что мы просто делаем, мы не двигаемся, вот, я думаю, вот что мы делаем. Мы проверяем, равно ли X0 -9999. Если нет, то перемещаем X0 в DAT. Итак, мы будем использовать регистр DAT в качестве индикатора, определяющего, следует ли нам подавать звуковой сигнал. Эмм, а потом мы... и так вот, DAT будет... мы должны начать жужжать? Накопительный регистр будет представлять собой цикл жужжания. Итак, теперь мы сделаем следующее: если один из вариантов, то мы не будем этого делать, и мы переместимся, на самом деле нам это даже не нужно. Итак, здесь мы можем использовать трюк из самой первой головоломки: мы просто выполняем весь цикл, если значение равно единице, то перемещаем 100 в P1, затем сидим одну секунду, затем перемещаем ноль в P1, а затем возвращаемся обратно. И это не сон, потому что если тета равна нулю, то мы просто будем спать одну секунду. Таким образом, нам вообще не нужно будет ничего не указывать в реестре. Что это такое? Давайте начнём с самого начала. - 999. Мы просто ложимся спать. Сейчас -999. Поэтому мы ложимся Теперь это

Segment 25 (120:00 - 125:00)

один. Итак, мы перемещаем туда один элемент. Это был один из них, верно? Почему это не было настроено? Вот что я хочу узнать. Верно. Я что, слепой? Итак, следующий шаг здесь, вот здесь, готов один. Почему же при чтении x0 в данных появилось значение -9999? Да, ты прав. На данном этапе нам это уже и не нужно, верно? Мы могли бы просто сказать: «Нет, нам это действительно необходимо, потому что, когда мы получаем значения -999, мы не проводим сравнение». Но я полагаю, мы всегда спим. Возможно, существует мир, в котором мы совершаем целый небольшой цикл внутри положительного случая. Поэтому, если x0 равно единице, мы ничего из этого не делаем. Нет, это не сработает. Я думаю, нам необходимо сохранять состояние, независимо от того, находимся ли мы в непрерывном режиме или в режиме остановки. Есть посылка. Таким образом, T=X0 уже считано. О, нет. Проблема не в этом. Проблема в том, что мы не всегда спим один час. Мне кажется, нам всегда нужно поспать один раз, потому что после нуля нам нужно поспать еще один раз, но нам также нужно поспать один раз, чтобы дождаться следующего нуля. О, оно поглощается равными. правы. Вы совершенно правы. Итак, мы переносим x0 в действие. Затем мы сравниваем действия и перемещаем. Как вам это? Впрочем, это кажется расточительным. Такое ощущение, что должен быть более эффективный способ это сделать. О, похоже, у нас неплохо получилось. Очень мало строк кода. Стоимость производства составляет пять единиц, так что с четырьмя это должно быть выполнимо. Да, мы определенно можем это сделать в маленьком помещении, верно? А может, мы и сможем это сделать? Мы можем сделать это с тем, что у нас уже было. На самом деле, мы только что... Ой, но у нас отсутствует кассовый аппарат. На самом деле, это тоже не редкая проблема с микроконтроллерами: если вы написали код, который работает на другом микроконтроллере, но используете больше регистров, чем тот, который есть у микроконтроллера. Как это сделать? Я знаю, как мы это делаем. Это ужасно. Мы собираемся добавить X0 к аккумулятору. Затем мы проверим, стало ли значение аккумулятора меньше нуля. А если теперь сумма в аккумуляторе меньше нуля, это значит, что она была -999. Тогда мы вычитаем, а затем прибавляем 999, чтобы вернуть значение к исходному. В противном случае, мы знаем, что аккумулятор... Да. Вы понимаете, чего я добиваюсь, верно? Таким образом, отрицательное число будет означать, что мы не получили сигнал, поэтому мы

Segment 26 (125:00 - 130:00)

просто отменяем операцию. Но в остальном же кое-что действительно изменилось. В таком случае, что изменилось? Если значение было равно нулю, то мы добавили единицу, и теперь оно стало равно единице. Если бы это был один, то мы бы добавили ноль. Это может оказаться непросто. Это не сработает. Думаю, нам нужно добавить " нет". Мы всегда добавляем единицу. Затем мы добавляем x0, которое может быть отрицательным числом. Затем мы проверяем, не стало ли число отрицательным, что возможно только в том случае, если x0 было очень малым. В таком мы [музыку] отменяем. Нет, это тоже не сработает. Мне кажется, это можно сделать, верно? Разве нельзя вместо того, чтобы ничего не делать, жестко задать значения 10 и 0? У нас уже задано значение 10. Проблема не в этом. Проблема в том, что нам нужно место для чтения x0, и это место должно быть отделено от нашего сигнала о том, был ли последний полученный нами пакет, который не соответствовал значению -9999, единицей. Нет, мне кажется, мы были на верном пути. Я думаю, что если x0 меньше, то нет, если равно -999, то -999. Это значит, что это был ноль. Значит, нам ничего делать не следует. О боже. Если это значение равно -9999, то это означает, что мы были равны нулю. Мы должны продолжать делать то, что вы делаете. Следовательно, мы ничего не будем производить. Если значение не равно -9999, это означает либо то, что изначально была единица, а мы получили ноль, либо то, что изначально был ноль, а мы получили единицу. Сложность заключается в том, что нам нужно различать эти два режима, потому что один из них включает устройство, а другой выключает его. Поэтому я думаю, что и это не сработает Мы могли бы использовать этот вариант. Да, кто-то предложил в чате использовать этот, чтобы получить здесь дополнительную выгоду, но мне кажется, что с этим вариантом это тоже должно быть возможно верно? Но, возможно, дело не в этом. Хорошо, вот интересный вопрос. Что произойдет, если вы введете на это устройство неверный номер? Предполагается, что это устройство должно работать по принципу « каждая цифра должна быть единицей». Что произойдет, если дать -9999? Что оно выдает на выходе? Верно? В противном случае мы можем просто сделать так: если P 0 равно 100, то это означает, что последнее, что мы получили, было единицей. Поэтому мы делаем это, иначе всё отключается. Поэтому мы ничего не делаем. Мы просто спим. Но это зависит от того, не изменит ли 999

Segment 27 (130:00 - 135:00)

выходные данные. О, как это грустно. Я надеялся, что если ввести неверное число, оно просто останется в том же состоянии, что и раньше. Это раздражает. Добавьте 100 к аккумулятору. Таким образом, сигнал 101 включен и выводится. А, вы имеете в виду переставить их в другую сторону, верно? Вы это имеете в виду? Пусть это будет здесь. будет экспортировано сюда. Пусть это принесут сюда. И тогда я получаю сообщение: « Я не понимаю, как это поможет, ведь проблема остаётся той же, верно? » Например, если мы... О, спать не получится, правда? Но если мы действительно сталкиваемся с проблемой, то она заключается в том, что нам нужно сохранить государство, но мы также не можем, как в тот момент, когда вы читаете что-то из другого источника, вы теряете то, что прочитали. Так что, если мы просто сравним, то действительно кажется, что это должно быть выполнимо, просто сравнив X0 с нулем. А затем, перед этим, мы напишем код, который должен выполняться, если входные данные равны нулю, то есть, если они меньше нуля. Ага. Но у нас нет второго реестра, который мы могли бы вести, вот в чем проблема. Мы не можем менять значения между регистрами, потому что у нас всего один регистр. В этот момент возникает соблазн подключить порт к самим себе, но нам это делать нельзя. Эм, я имею в виду, да, это тот случай, когда мы определенно находимся в области чрезмерного обдумывания проблемы, или, скорее, это оптимальное решение, которое нам необходимо для этой конкретной ситуации. Но ведь интересно попытаться выполнить микрооптимизацию именно этой сборки, не правда ли? Кажется, это должно быть осуществимо, если добавить, например, сравнение x0 с нулем — это тот трюк, который позволяет нам разветвляться на три части. Проблема в том, что мы можем выполнить код только для двух из них. В случае -99 мы хотим... О, на самом деле, возможно, именно так мы и поступим. Итак, если аккумулятор равен единице, мы переходим к выводу. Мне кажется, в этом что-то есть. Так что, если мы добавим сюда прыжки, то, думаю, это осуществимо. Так что, если значение равно -999, мы сравним, и, возможно, мы сделаем это независимо от результата. Итак, если теперь значение равно единице, то мы перенесём

Segment 28 (135:00 - 140:00)

100 в аккумулятор. И если это так, то в положительном случае мы переходим к продолжению, а в отрицательном — к продолжению. Но в случае, когда ничего не делается, мы отключаем перемещение нуля в аккумулятор. Но тогда возникает вопрос: сравните аккумулятор с единицей. И я не думаю, что у нас есть инструкции для этого, потому что теперь нам нужно переместить 100 в P1. Ага, конечно. Э-э, переместитесь, поспите один. Переместите ноль в точку P1. И этого не хватит даже на ещё одну строчку. Но я думаю, этого будет достаточно. Я имею в виду, просто для проверки здравомыслия, верно? Мы могли бы сделать это в одном из крупных проектов. Ну, это совсем не сработало. Поэтому я подумал, что если мы сравним это с нулем, то если оно больше нуля, то поместим 100 в аккумулятор и перейдем к циклу, который проверяет, должно ли значение быть равно 100. Вот почему мы могли бы просто сделать это таким образом Ага. Так что это работает. Но это лишняя строчка. И мы найдем способ сэкономить мне строку. Я не вижу способа сэкономить себе строку. К сожалению, нам приходится это дублировать, но я думаю, это единственный способ гарантировать, что мы запустим этот код только в том случае, если получим ноль. И это кажется совершенно бесполезным, правда? Как будто в этом нет необходимости. А есть ли что-нибудь в инструкции? Есть ли какая-нибудь другая инструкция для проверки, которую мы могли бы здесь использовать? Нет. Сравнивать можно только с тем, который это делает. Нет, мы не могли использовать t0ero, потому что tik zero будет рассматривать oh одинаково как 999, так и one, что, по сути, мы и делаем здесь. Нет, это не так. Посмотрите, например, мы также используем эту линию. Мы не запускаем эту линию для -9999. И это важно. Хорошо, я думаю, что оптимизация по этому пункту завершена, но кажется, что есть способ сделать и это. Э-э, сигнал поступает только тогда, когда значение изменяется от единицы к нулю или от 0 к единице. Нет, потому что у нас есть промежуточный цикл. Перемещение нуля для выполнения действия никогда не происходит. Оно запущено. Это запускается, потому что, если вы посмотрите руководство, там написано, что для TCP, если A равно B, то и плюсы, и минусы отключены. Поэтому ни один из них не занят. Именно это мы здесь и используем: код выполняется только в том случае, если значение равно нулю. Но, конечно, если у нас есть что-то действительно

Segment 29 (140:00 - 145:00)

важное, то мы можем просто использовать дополнительный кассовый аппарат. Весь смысл переписывания текста таким образом заключался в том, чтобы мы могли использовать ту мелочь, которой не хватало регистра. Но теперь у нас на одну строку кода больше, чем нужно. Ох, кажется, это уже совсем близко. И если бы мы это сделали, у нас бы получилось действительно здорово, правда? Потому что, если мы посмотрим, если вернемся к этому, то увидим, что мы действительно хорошо разбираемся в строках кода. Можно обойтись и меньшими средствами, но не намного. Мы очень хорошо экономим электроэнергию, и дело только в стоимости, благодаря которой мы достигли пятилетнего результата. Э-э, но мне кажется, здесь их четыре. Если мы выиграем втроём, это будет невероятно. И это будет именно этот вариант Ах, здесь есть и другие вещи. Там есть какие-то узлы и все такое, но нет, я думаю, мы оставим все как есть. В какой-то момент нам придётся уйти. Итак, мы нажали на кнопку. Эм, мне кажется, мы сейчас уже не столько изучаем новые вещи в ассемблере, сколько просто играем в игру, и это нормально. Мне нравится эта игра. Вот почему я начал этим заниматься. Но я думаю, что мы в основном уже прошли этапы сборки. Эм, я хочу вернуться к инструкции, чтобы показать вам некоторые другие вещи, которые... О, мы еще не использовали установленные и неустановленные цифры. Эм, но есть и другие вещи, которые выглядят довольно реалистично, как будто их собирают. А где же он, если мы спустимся дальше? Так что, как правило, технические характеристики оборудования гораздо сложнее, чем это, верно? Обычно все не так просто, но в общих чертах это выглядит примерно так. Одна из причин, почему я так говорю, заключается в том, что, как мне кажется, люди иногда боятся встроенного программирования, потому что думают: «Ах, это так сложно». Да, это менее просто, чем написание кода на языках программирования, где нужно просто писать на полуанглийском языке, но это не так уж и сложно, правда? В основном, дело в том, что у вас есть немного более ограниченный язык для самовыражения, а также у устройств, с которыми вы работаете, есть правила, которым нужно следовать, но они, по сути, просто интерфейсы, верно? Как мы видим, правила для интерфейсов обычно не такие уж сложные. Конечно, бывают случаи, когда оборудование обладает странными свойствами, но я думаю, что это зафиксировано, например, в случае с оперативной памятью, которую мы вообще не использовали. Э-э, логические вентили, которые мы здесь не использовали. часы, которыми мы пользовались. Ага. Например, часы, которые выдают число, соответствующее количеству 15-минутных интервалов, прошедших с полуночи. Так вот, это тот случай, когда у реального оборудования есть очень странные свойства, но вам просто нужно прочитать его технические характеристики, и тогда вы узнаете, что это за свойства. Иногда в оборудовании встречаются ошибки, из-за которых оно просто не выполняет заявленные функции. Это всегда по- своему забавно. Эм, но, как правило, вы можете просто прочитать техническое описание, и это будет похоже на взаимодействие с любым другим кодом, за исключением того, что вы пишете ассемблерный код, который взаимодействует с контактами, а не программный код, который вызывает интерфейс библиотеки. Эм, история с машинописью в Денвере. Э-э, я пытаюсь найти. Да. Другой пример, например, сетевой интерфейс, который, как можно предположить, должен работать с прерываниями, на самом деле таковым не является. Вместо этого вы просто перечитываете, и это всегда возвращает значение, которое считается недействительным, если читать было нечего. В мире аппаратного обеспечения тоже не редкость, когда разработчики не хотят, чтобы устройство работало на основе прерываний, чтобы было проще взаимодействовать с ним в программе, которая не хочет иметь дело с прерываниями. Но в результате получается API, основанный на опросе, вместо API, работающего на основе отправки запросов, который был бы основан на прерываниях. Так вот, что меня действительно зацепило в этой игре, так это то, что, хотя она и упрощена по сравнению с реальным миром, многие идеи, с которыми приходится иметь дело, на самом деле довольно реалистичны, например, хотите ли вы, чтобы ваш низкоуровневый код микроконтроллера работал на основе прерываний или на основе опроса? Это ощущение, что нужно уместить это в инструкции, которые у меня есть в памяти, и в контакты, которые у меня есть. Подобные ограниченные условия очень точно имитируют работу в реальном

Segment 30 (145:00 - 150:00)

мире с реальными микроконтроллерами. Да, пределы ниже и несколько искусственнее, но это как если бы пределы всё ещё существовали в реальном мире. Такие вещи, как ограниченное количество значков, вполне реальны. Количество доступных вам кассовых аппаратов, не совсем то же самое, что если бы у вас был только один, но обычно их не так много. Многие из них в конечном итоге используются, например, для вызовов функций. Так что создается впечатление, что вы не научитесь программировать на ассемблере, играя в эту игру, но вы освоите некоторые необходимые модели мышления концепции, которые нужно применять при написании ассемблерного кода для микроконтроллеров для реального взаимодействия с оборудованием. Эм, кто-то спросил в чате о том чувстве, что « чёрт, я знаю, что это должно быть возможно, если бы я сделал это, например, с чуть меньшим объёмом памяти или чуть меньшим количеством кода, или что-то в этом роде». Насколько это распространено во встроенном программировании, и я думаю, ответ в том смысле, что я говорю «в некотором роде», потому что, скажем так, это выводы ввода-вывода. Итак, когда вы работаете с чем-то вроде Raspberry Pi с небольшой платой расширения или Arduino, иногда у вас действительно заканчиваются контакты, или, как правило, дело не столько в отсутствии контактов, сколько в нехватке контактов определенного типа. Итак, я забыл технические характеристики Raspberry Pi, но у него есть несколько универсальных, так сказать, выводов ввода-вывода, простых вариантов, подобных тем, что описаны здесь. А ещё у него есть несколько контактов, которые можно использовать для управления двигателями разных типов. В нём есть несколько разных типов значков, универсальных, но предназначенных для разных целей. Эм, и у вас их ограниченное количество. И поэтому, когда вы начинаете подключать несколько единиц оборудования, вам нужно начать планировать, например, сколько контактов мне понадобится для этого? Нужна ли мне плата расширения, или есть способ использовать меньше контактов или заставить это устройство использовать другой тип контактов, чем тот, который предусмотрен? Вы не так часто сталкиваетесь с вопросом: « Как выразить это с помощью меньшего количества строк ассемблерного кода? ». В наши дни это довольно редкое явление. Но вы также можете столкнуться с проблемой нехватки оперативной памяти, о которой мы здесь особо не говорили, верно? Но знаете, в современных компьютерах, таких как настольные или портативные, объем оперативной памяти составляет гигабайты, а то и больше. Встраиваемые устройства зачастую имеют гораздо, гораздо меньше возможностей. Итак, обычно в Rust именно здесь возникает вопрос: есть ли у вас доступ к выделению памяти ( alloc) или нет? Используется ли динамическое выделение памяти? Если нет, то необходимо статическое управление памятью, в этом случае, возможно, следует использовать распределитель памяти типа Arena или что-то подобное. Но у вас очень ограниченный бюджет памяти, и поэтому вы больше не можете просто произвольно выделять строки, векторы и хэш-карты, чтобы они росли. Вам нужно очень тщательно продумывать, сколько памяти вы используете, и это может стать узким местом, когда вы думаете: «Мне нужно хранить данные за 24 часа». В таком случае вам потребуется разработать более сжатый формат данных. Или, знаете, батарея — это пример такой ограниченной емкости, которая в этой игре не особо рассматривается. Вас оценивают по силе, но власть никогда не ограничивает вас. В реальном мире написание эффективного ассемблерного кода — это разница между тем, прослужит ли батарея неделю, день или час. Верно? Так что, знаете, если вы напишете программу, подобную тем, что мы здесь писали, где есть цикл, в котором между каждым циклом происходит пауза на определенное количество секунд, вы обнаружите, что батарея разряжается довольно быстро. Вам действительно понадобится внешнее устройство, или, я бы сказал, внешнее подключенное устройство, работающее на основе прерываний и активирующее только основной процессор и основной диск, которые, как правило, потребляют больше всего энергии. Оно будит их только тогда, когда им нужно чем-то заняться. Представьте, что вы, например, следите за тем, день сейчас или ночь. Хорошо. Таким образом, существует два способа написать это. Вам, вероятно, понадобится датчик освещенности. Возможно, это можно сделать, исходя из времени суток, но предположим, вы пытаетесь сделать это с высокой точностью. Итак, вы направляете его в окно. Ну, давайте предположим, что вы находитесь в месте, где никогда не бывает облаков и никогда не бывает плохой погоды. Но вы используете датчик освещенности, который выдвигаете из окна. Теперь у вас есть два способа написать эту программу. Один из способов — проверить текущие условия освещения. А если они превышают пороговое значение для дневного света, тогда, ну, я не знаю, можно включить светодиод или что-то подобное. Или, если это не так, выключите светодиод. А потом вы спите минуту, час, или столько, сколько, по вашему мнению, должна быть точность этого таймера. Допустим, вы поспали минуту, проснулись и снова проверили. Теперь вам приходится запускать какие-то

Segment 31 (150:00 - 155:00)

вычисления каждую минуту. Также возникает вопрос о том, в какой глубокий спящий режим может перейти ваш процессор за этот период времени. В итоге оно отключается, например, если у него есть сетевой интерфейс? Отключается ли сетевой интерфейс, если он отключен, даже если это просто флеш-карта? Отключается ли флеш-карта или к ней всё ещё поступает какой-то трафик? Вам нужно разобраться в этих вещах, но вы пробуждаете устройство каждую минуту для выполнения вычислений. Другой вариант — если вам удастся найти датчик освещенности, который обрабатывает прерывания, потому что тогда вы сможете запрограммировать его так, чтобы он срабатывал только при превышении определенного порогового значения освещенности, устанавливал значение на выводе прерывания и пробуждал основной процессор. Теперь вы постоянно питаете только датчик освещенности. Вы же не постоянно включаете процессор каждую минуту. В идеале вы же будите его всего два раза в день, верно? Вы активируете устройство, когда диод сообщает вам: «О, теперь света больше или меньше, чем заданный порог». Таким образом, в результате у вас появляется устройство, которое, вероятно, будет работать от батареи значительно дольше, потому что питание только этого датчика потребляет гораздо меньше энергии, чем питание всего устройства. Вся остальная часть устройства может полностью перейти в спящий режим в промежутках между включениями. Эм, и поэтому в разработке встроенных систем существует множество примеров программирования, основанного на ограничениях. Эм, это не совсем то же самое, что и игра, но схожий колорит, схожие ощущения — это то, чего можно добиться при работе со встроенными системами. Есть какие-нибудь вопросы в последнюю минуту? Думаю, мы сейчас на подходящем этапе, чтобы подвести итоги и сказать людям, что если вы хотите продолжить играть, вы можете это сделать. Я мог бы также сказать вам, что вы можете просто взять, например, ESP32, или небольшой Arduino, или Raspberry Pi, или что-то подобное, например, крошечное встраиваемое устройство, чтобы просто поэкспериментировать. Для большей части программирования этих программ даже не требуется ассемблер, но при желании вы можете его использовать. Эм, как лучше всего изучить ассемблер? На самом деле, наилучшего способа не существует. Эм, обычно самый простой способ изучения ассемблера, как я обнаружил, — это либо просто попробовать использовать его для чего-то вроде, например, взять программу на Rust, написать крошечную функцию, очень-очень простой блок кода, а затем попробовать написать её на ассемблере. У нас есть, э-э, у нас есть стабильная сборка на Rust, верно? Таким образом, вы можете открыть блок ассемблера (asm), восклицательный знак, фигурные скобки в Rust, а затем написать фактический ассемблерный код непосредственно в вашей программе на Rust. Попробуйте написать какой-нибудь встроенный ассемблерный код, который будет делать то же самое, что и ваш исходный код на Rust. Попробуйте. И вы многому научитесь, просто скопировав хотя бы несколько строк кода на Rust, и увидите, насколько интересно, сложно и одновременно полезно пытаться сделать это на языке ассемблера. А если вы застряли, то можете воспользоваться сайтом Godbolt (godbolt. org, или, кажется, другим подобным), где можно ввести программу на Rust или на любом другом языке, и он покажет ассемблерный код этой программы. Вы можете ввести свой код на Rust, и он, скорее всего, подскажет вам правильный ответ, но он будет очень сложным. Причина, по которой я говорю «ответ», а не « решение», заключается в том, что это будет результат работы компилятора Rust, который, возможно, прошел через множество оптимизаций, поэтому он будет делать все правильно, но может быть и более оптимизированным, чем вы могли бы предположить, исходя из собственных данных, хотя часто это и не так. Также можно указать, чтобы оптимизация применялась не так часто, и тогда результаты могут быть более сопоставимыми. Однако имейте в виду, что если вы это сделаете, то иногда вокруг этого появится много стандартного текста. Например, если вы используете команду ` print line` в программе на Rust, то `print line` на самом деле довольно сложная команда. Это генерирует множество фрагментов ассемблерного кода, которые, по сути, маскируют ту часть ассемблерного кода, которая вас интересует. Но в Goldbolt вы можете, хм, вы можете щелкнуть по строке в исходном коде, и, кажется, он подсвечивает, какой ассемблер соответствует этой строке. Интересно, что эта же информация используется, например, профилировщиками для измерения того, на что именно тратится время вашей программы во время работы, используя такие инструменты, как Perf. Эм, по сути, он измеряет, какие ассемблерные инструкции были выполнены чаще всего. Это информация, которую он получает от центрального процессора, и он использует так называемые отладочные символы Dwarf для преобразования этих символов в строки кода. Именно эту информацию Godbolt использует для сопоставления инструкций ассемблера с тем

Segment 32 (155:00 - 160:00)

какая строка кода сгенерировала данный ассемблерный код. И это может быть действительно хорошим способом изучить ассемблер. Попробуйте написать что-нибудь на ассемблере и посмотрите, получится ли у вас. Эм, и вы можете обнаружить, что некоторые вещи, которые вы считали простыми, на самом деле очень сложны, но другие вещи понятны в ассемблерном коде, особенно если вы просто выполняете арифметические операции, базовые циклы, сравнения — вот такие вещи просты. Сложности начинаются, когда начинаешь вызывать другие функции, это может стать действительно непросто. Эм, э-э, другие вещи, например, работа со строками, могут быть довольно сложными, особенно если это строки в кодировке UTF8. Эм, но есть множество вещей, которые легко даются в языках программирования, но сложны в ассемблере. Эм, ещё один способ выучить ассемблер, как мне кажется, — это работать с встраиваемыми устройствами, где некоторые операции приходится выполнять на языке ассемблера. В наши дни обычно можно просто скачать, например, crate. io, пакет, поддерживающий ваш чипсет. И это замечательно. Как и в случае с любым проектом, предназначенным для производства или, по крайней мере, для обеспечения надежности, обычно используются именно такие библиотеки. Это гораздо приятнее. Но если ваша цель — изучить ассемблер, попробуйте заставить устройство, например, загрузиться и заставить мигать светодиод или что-то подобное, используя только ассемблер — это возможно. Всё не должно быть так уж плохо. И у вас есть эталон, то есть та библиотека, которая была разработана для вашего чипсета. Вы сможете прочитать их ассемблерный код, чтобы понять, как они это сделали. Но обычно, особенно для таких устройств, как Raspberry Pi Pico или ESP32, которые, по сути, являются микроконтроллерами, существуют очень хорошие справочные руководства, документация и написанный для них код, потому что они очень распространены. Они ещё и невероятно дёшевы. Это действительно отличный способ начать. Если вы действительно хотите погрузиться в эту кроличью нору с головой, есть третий способ — это что-нибудь сделать. В Массачусетском технологическом институте есть такой курс, который сейчас, я даже не знаю, как он называется. Раньше он назывался 6828. Это был курс по операционным системам, и я попробую найти, как он сейчас называется. Эмм, MIT теперь называется 6828, если вы поищете 6. 828 828. Его все еще можно найти, но это версия 2021 года. Я пытаюсь выяснить, как его переименовали в. Возможно, его так и не переименовали. Я не могу, по крайней мере, я не могу найти ему замену. Таким образом, операционная система 6828 — это отличная лаборатория. Таким образом, все лекционные материалы для этого курса доступны бесплатно онлайн. И что особенно важно, все лабораторные работы доступны онлайн, и в них вы программируете операционную систему практически с нуля, по крайней мере, реализуете многие интересные части операционной системы. А операционная система — это как минимум та область, где для некоторых её частей приходится писать код на языке ассемблера. Например, для настройки процессора или для таких вещей, как переключение процессов. Таким образом, если вам нужно переключиться с одного процесса на другой, потому что необходимо переключиться на другое адресное пространство памяти, для чего требуются инструкции ассемблера. То же самое относится и к настройке управления виртуальной памятью. Часто требуется некоторая сборка. Эмм, этот курс обычно преподают на Rust, нет, извините, на C. Эмм, но есть части, которые написаны исключительно на ассемблере, а лабораторные работы выполняются с помощью автоматической проверки. В комплекте идут инструкции, если вы действительно хотите углубиться в эту тему и заодно узнать кое-что о смежных аспектах операционных систем. Я бы с удовольствием прошел лабораторные работы по этому предмету. Это действительно весело. Прохладный. Мне кажется, это означает, что мы подошли к концу. Надеюсь, вам было интересно и познавательно. Мне, безусловно, было весело, и мне кажется, я много разговаривал. Усвоили ли вы из этого что-нибудь, я предоставлю вам самим оценить ситуацию. Эм, но на этом, пожалуй, мы закончим Мы играли в Shenzen. io. Думаю, сейчас в Steam на неё действует скидка 50%, если вы хотите её купить и поиграть. Если отклик будет

Segment 33 (160:00 - 161:00)

положительным, я с удовольствием буду делать больше подобных видео. Я, наверное, сменю игру. Есть ещё одна игра, которая называется Touring Complete, где вы занимаетесь сборкой, но даже не получаете инструкций по сборке, а только схемы проводов. Итак, игра начинается с того, что у вас есть логический элемент NAND, вы строите все остальные логические элементы, а затем, исходя из этого, создаете схемы задержки, схемы зацикливания, и в конечном итоге вы получаете возможность создать язык ассемблера для своего собственного чипсета. Эм, это еще более простое введение в основные принципы, лежащие в основе этого вопроса. Это сильно отличается от тех видео, которые я обычно делаю, — там гораздо больше внимания уделяется Rust, — но я считаю, что это полезная часть информатики, и мне показалось, что это хороший способ обучения. Но я также продолжу проводить стримы по Rust. Это просто дополнение, это весело. Мы играем в игры. Но дайте мне знать, что вы думаете, и считаете ли вы, что мне следует делать больше подобных постов. А если достаточное количество людей захочет, то я найду другие стримы. В любом случае, увидимся в следующем видео. Спасибо за просмотр.

Другие видео автора — Jon Gjengset

Ctrl+V

Экстракт Знаний в Telegram

Экстракты и дистилляты из лучших YouTube-каналов — сразу после публикации.

Подписаться

Дайджест Экстрактов

Лучшие методички за неделю — каждый понедельник