Ассемблер MSP430 LaunchPad MSP430G2231, Учебник

Ассемблер MSP430 LaunchPad MSP430G2231, Учебник


В этом руководстве используется LaunchPad с включенным MSP430G2231 процессором, ведется программирование на языке ассемблера MSP430. Для наглядности разработаем небольшую програмку, которая считывает состояние нажатия кнопки LaunchPad. Пока кнопка не нажата, красный светодиод Launchpad включен. Если кнопка нажата - включается зеленый светодиод .



Хотя это первая программа не большая - всего 20 строк кода - она охватывает фундаментальные возможности .


В дополнение к обзору основного синтаксиса и структуры MSP430 ассемблера, мы рассмотрим, как:



-Настроить порт в на вход или выход

-Прочитать цифровой вход

-Перейти в другое место в программе

-Реализовать простое условие IF / THEN

-Реализовать простой цикл LOOP

-Реализовать простую задержку DELAY


Синтаксис, представленный здесь, основан на Code Composer Studio компании TI (CCS), поэтому для выполнения этого упражнения вам нужно будет иметь CCS загруженый и установленый на вашем компьютере. Бесплатная версия CCS доступна на веб-сайте TI.


Основной синтаксис ассемблера

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


  • Метка - выступает в качестве маркеров в исходном коде, полезна в качестве указателя разветвленности обьявлений и вызовов
  • подпрограмм
  • Мнемоника - это машинная инструкция, директива ассемблера или макрос
  • Операнд List - одно или несколько значений, которые будут использованы инструкцией, директивой или макросом
  • Комментарий - примечание вставляется программистом, текст не входит в сборку программы



Следующий пример кода содержит все эти элементы:



метка мнемоника операнды коментарий
MainLoop bit.b # 00001000b, & P1IN ; Читать состояние кнопки в P1.3



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


Например, инструкция:


add.w # 10, R 5



берет число 10, добавляет его к значению, сохраненному в регистре R5 и сохраняет результат обратно в R5.

В то время как :


JMP Delay



передает выполнение программы в точку, отмеченную меткой Delay.


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


- число 10,

- регистр общего назначения R5,

- и определенная пользователем метка Delay.



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



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

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




.End



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


Метки не являются обязательными.


При использовании, они должны быть первым символом строки. Они не могут начинаться с цифры или содержать пробел, но в противном случае может включать буквенно-цифровые символы AZ, AZ, 0-9, _, и $.


Метки чувствительны к регистру по умолчанию, но могут быть запрограммированы как нечувствительные к регистру, в качестве опции.


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


Комментарии также необязательны. Они могут использовать любой ASCII символов, включая пробелы. Когда комментарий начинается с первого символа в строке, он может начинаться со звездочки (*) или точки с запятой (;), если она не начинается с первого символа может начаться только с точкой с запятой.

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







Об архитектуре


Регистр является чрезвычайно важным понятием в программировании микроконтроллеров. В MSP430 все операции программы, за исключением потоковіх инструкций, реализованы в виде операций с регистрами.


Для того, чтобы установить частоту таймера, значение записывается в регистр.

Чтобы получить статус входного порта, значение считывается из регистра.

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


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


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


Задача чтения и записи в регистры в виде дискретных ячеек памяти значительно упрощается за счет использования символических псевдонимов в сборке программе . Вместо того чтобы использовать числовые адреса, регистры и биты, как правило, ссылаются на более или менее понятные имена, такие как SP для «Stack Pointer» или P1OUT для «Порт 1 выходной регистр порта P1.»



Пример программы


Пример обучающей программы приводится ниже.

Как указывалось выше, это простая программа, которая привязана к встроенной аппаратной части LaunchPad.


Если кнопка нажата- зеленый светодиод включен;

если кнопка не нажата красный светодиод.


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


; ------------------------------------------------- -----------------------------
; Digital I Пример / O для LaunchPad
; Читаем статус кнопок на порту - P1.3
; (Обратите внимание , что P1.3 равен «1» , когда кнопка не нажата
, и «0» , когда кнопка нажата)
; Красный светодиод горит , если кнопка
- P1.0 не нажата

; Зеленый светодиод горит , если кнопка - P1.6 нажата

; Откомпилируйте в Code Composer Studio
; -------------------------------------------- ----------------------------------

.cdecls C, LIST "msp430g2231.h" ; cdecls говорит ассемблеру , чтобы подключить

; заголовочный файл
; --------------------------------------------- ---------------------------------
; Основной код
; ----------------------------------------------- -------------------------------

.text; старт программы

.global _main; определение точки входа

_main mov.w # 0280h, SP; инициализировать указатель стека

mov.w # WDTPW + WDTHOLD, & WDTCTL; остановить сторожевой таймер

bis.b # 01000001b, & P1DIR; настроить P1.0 и P1.6 на выход


; все остальные потрты по умолчанию настроены на вход


Main
Loop bit.b # 00001000b, & P1IN; прочитать кнопку в порту P1.3

Jc Off; если кнопка на P1.3 не нажата, тоесть=1 переход по метке в Off

On bic.b # 00000001b, & P1OUT; записать 0 в P1.0 (выключить красный светодиод)

bis.b # 01000000b, & P1OUT; записать 1 в порт P1.6 (включить зеленый светидиод)

JMP Wait; безусловный переход к процедуре задержки

Off bis.b # 00000001b, & P1OUT; записать 1 в P1.0 (включить красный светодиод)


bic.b # 01000000b, & P1OUT; записать 0 в порт P1.6 (выключить зеленый светидиод)

Wait mov.w # 1834, R15; нагрузки со значением R 15 для задержки

L1 dec.w R15; декремент R15

JNZ L1; если R15 не равен нулю, переход к L1

JMP MainLoop; перейти к метке MainLoop

; -------------------------------------------- ----------------------------------
; Interrupt Vectors
; ----------------------------------------------- -------------------------------
.sect ".RESET"; MSP430 Вектор прерывания по сбросу
.short _main
.end





Вы можете (или не можете) если хотите загрузить и собрать код до его изучения.

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


В программе CCS выберите File -> New -> CCS проект

Введите имя проекта, нажмите и задайте тип проекта для MSP430

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

Выберите подходящий вариант устройства,

установите флажок «Настройка в качестве сборки только проект» и нажмите кнопку Готово

Выберите File -> New -> Source File

Введите имя файла с суффиксом .asm.

Введите или вставьте текст программы в файл.

Когда вставите код, выберите Project -> Build Active Project

Выберите Target -> Debug отладка активного проекта

Выберите Target -> Run, чтобы запустить приложение.



Разберем подробнее участки нашего кода



.cdecls C, LIST "msp430g2231.h" ; cdecls говорит ассемблеру , чтобы он подключил

; заголовочный файл с
; --------------------------------------------- ---------------------------------
; Основной код
; ----------------------------------------------- -------------------------------
.text ; запуск программы
.global _main ; определить точку входа
_main mov.w # 0280h, SP ; инициализировать указатель стека
mov.w # WDTPW + WDTHOLD, & WDTCTL ; остановить сторожевой таймер




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


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


Первые три выражения - это директивы ассемблера в перемешку с комментариями,.


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

Соответствующий файл заголовка для MSP430G2231 в Launchpad указывается в кавычках - msp430g2231.h.


.text директива указывает на начало блока кода.

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



Последние две строки этого кода содержат первую машинную инструкцию программы - mov.w # 0280h, SP .


Здесь мы используем команду mov. дважды;

один раз, чтобы установить начальное значение указателя стека

и второй, чтобы отключить сторожевой таймер.


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


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


Инструкция mov. загружает значение, указанное первым операндом (исходный операнд) в место, указанное во втором операнде (операнд назначения). Благодаря прочной и гибкой природе MSP430 адресации, исходный операнд может быть буквальным значением, которое будет загружен в пункте назначения, или он может так же легко относится к значению, сохраненному в регистре или в другом месте памяти. В случае обоих этих линий, источником является значение операнда. Этот режим адресации называется непосредственным режима, и это указывает на хэш (#), предшествующий операнд.




Операция:




mov.w # 0280h, SP




загружает указатель стека номером 280H, с учетом того, что это число в шестнадцатеричном формате.

.W после объявления mov. указывает на то, что это операция со словом.

Все инструкции с одинарными операндами и инструкции с двойными операндами могут быть байтовые (8 бит) или со словом (16 бит) с помощью .b или .w расширения соответственно. Если не используется расширение, инструкция является инструкцией слова, но нет ничего страшного, есои вы добавите .W для ясности. Поскольку SP является одним из 16 регистров, которые являются частью самого процессора, он использует режим адресации регистровый и на него можно ссылаться без префикса.



Следующая команда:


mov.w # WDTPW + WDTHOLD, & WDTCTL


кажется более запутанной на первый взгляд.


Что мы делаем с этой инструкцией mov. ? Мы загружаем регистр управления сторожевым таймером значением, которое отключит таймер.


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


WDTPW и WDTHOLD два имени, которые также были определены заголовочным файлом.

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


WDTHOLD равно 7 бит, который является остановка немного WDTCTL. При сложении вместе и загрузке в WDTCTL они остановят сторожевой таймер.




Настройка входов и выходов

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


Первое, что нам нужно сделать, это настроить выводы микроконтроллера для ввода и вывода (I / O).

MSP430G2231 имеет 10 портов ввода / вывода, каждый из которых может быть индивидуально настроен. Восемь выводов порта P1 выведеы на разъем, а остальные два контактаразъема приходятся на порт P2. Разъемы порта пронумерованы от 0 до 7 и ссылки на комбинации портов построены таким образом, что порт 1 разъем 3 соответствует Р1.3.


Порты P1 и P2 имеют регистр - P1DIR и P2DIR соответственно - который используется для управления портом для настройки на ввод или вывод. Эти 8 битов регистра ввода / вывода,где каждый бит соответствует контакту разъема. Если бит установлен, то контакту соответтствует выход, если бит сброшен, то контакту соответствует вход. Как и номера контактов в разъеме на плате, номера битов в регистре всегда начинаются с 0; 8-битные регистры 0-7 и 16-битные регистры 0-15.

Вам часто нужно управлять битами регистра в операнде машинной команды. При выражении регистров в качестве числовых значений, создается двоичное число, где каждый бит в регистре соответствует цифре числа с наименьшим битом (0) дальше вправо: бит 0 выражается в виде двоичного 00000001, а бит 7 выражается в виде двоичный 10000000. здесь следует отметить, что в то время как двоичное число представляет собой битовую позицию, фактическое значение двоичного числа не равно десятичному числу бит. Binary 10000000 равен десятичному 128 и шестнадцатеричному 80; ассемблер будет принимать 7 бит какого-либо из этих форматов при условии, что они выражаются в правильном синтаксисе. Вы часто будете видеть шестнадцатеричное число которое используется с этими типами команд, но это вопрос выбора и удобство. Бинарные выражения могут быть полезны в визуализации отдельных бит, но они склонны к ошибкам, которые трудно найти - особенно при работе с 16-битными числами.

Для этого проекта мы будем иметь дело с тремя контактами из разъема ввода / вывода.


На LaunchPad, красный светодиод подключен к P1.0, зеленый светодиод подключен к P1.6, и кнопка, которую мы хотим использовать подключена к P1.3. Таким образом, в нашем примере программы биты 0 и 6 регистра P1DIR необходимо будет установить в 1, в то время как P.3 необходимо будет выключить. Мы могли бы сделать это еще раз, используя mov., чтобы загрузить значение в регистр P1DIR, однако есть еще одна инструкции, bis - «бит установка» инструкция, которая полезна для установки отдельных бит и более часто используется для записи в регистры PxDIR :


bis.b # 01000001b, & P1DIR; сделать P1.0 и P1.6 выходом

; все остальные входы по умолчанию


bis ведет себя совсем иначе, чем mov. При использовании bis любой бит, который равен «1» в исходном операнде будет установлен в операнде-адресата, а остальные биты не будут затронуты.

В этом случае весь регистр P1DIR был бы очищен при запуске, так что нет необходимости делать что-либо, чтобы очистить бит для Р.3 и завершения конфигурация контактов .

Рассмотрим синтаксис, используемый в этой последней команде- .b расширение указывает, что это операция с байтом, # префикс исходного операнда указывает на использование немедленной адресации, b суффикс исходного операнда указывает на двоичный формат число, а & префикс операнда назначения указывает на абсолютную адресацию.



Главный цикл

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



Чтение кнопки и принятие решений

Эти первые две строки кода MainLoop будут работать вместе, чтобы получить статус кнопки на Launchpad и использовать этот статус, чтобы определить, следует ли перейти к другому разделу:



MainLoop bit.b # 00001000b, & P1IN; прочитать состояние кнопки в P1.3

Jc Off; если P1.3 не нажата кнопка переход на метку Off




Здесь мы рассмотрим другой регистр для P1. Регистре P1IN , который отражает значение входных сигналов в соответствующих контактах разъема порта ввода / вывода . Инструкция bit или «бит тест» считывает биты в операнде, как указано в операнде-источнике - здесь бит 3.

Однако, в отличие от предыдущих mov инструкции и bis , инструкция bit не изменяет операнд-адресат, но влияет на группу битов регистров состояния ЦПУ, обычно изменяет биты состояния. В этом случае мы будем иметь дело с C битом или битом «переноса» , который устанавливается, когда операция с битом возвращает 1.


Jc или «переход, если перенос» инструкция будет читать бит переноса, и если он установлен будет осуществлять переход по метке. Если бит переноса сброшен, выполнение программы будет продолжено со следующего оператора. Язык ассемблера MSP430 имеет семь различных условных команд перехода, которые полезны при создании таких IF / THEN конструкций.

Обратите внимание, что кнопки LaunchPad соединены так, что бит P1IN установлен в «1», когда они открыты и сброшены в «0», когда онизамкнуты. Это обычная инженерная практика, но делает код немного противоречивым на этапе, когда мы ожидаем 1 при нажатом состоянии.



Если кнопка нажата


Если кнопка нажата и бит 3 из P1IN возвращает 0, то команда Jc не будет переходить к Off метке и выполнение продолжится в следующем блоке кода:



On bic.b # 00000001b, & P1OUT; сброшен P1.0 (красный светодиод выключен)

bis.b # 01000000b, & P1OUT; установлен P1.6 (зеленый светодиод включить)

JMP Wait ; переход к процедуре задержки




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


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


bic или « бит нуля» инструкция является функциональной противоположностью bis - любой бит, который является «1» в исходном операнде очищается в адресате, но остальные биты не будут затронуты.

Вместе эти строки очищают бит 0 Р1 и установят бит 6, который обеспечит включение красного светодиода , и выключение зеленого светодиода.


JMP или «прыжок» инструкция является безусловным переходом, который передает выполнение программы на метку.

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



Если кнопка выключена



Если кнопка не нажата и бит 3 P1IN возвращает 1, то выполнение Ж.К. ветвей инструкции на метке Off:




Off bis.b # 00000001b, & P1OUT; установить P1.0 (красный цвет вкл)

bic.b # 01000000b, & P1OUT; очистить P1.6 (зеленый выкл)




Этот блок кода является по существу зеркальным отображением блока On. Инструкция bis включает красный светодиод и bic инструкция выключает зеленый светодиод.




Loop delay


Независимо от того, выполнены Вкл или Выкл , оба пути ведут к следующему блоку кода, который начинается с определенной пользователем метки Wait:




Wait mov.w # 1834, R15; нагрузки со значением R 15 для задержки

L1 dec.w R15; декремент R15

JNZ L1; если R15 не равна нулю переход к L1

JMP MainLoop; перейти к метке MainLoop



Процедура ожидания включается, чтобы обеспечить элементарный переключатель дребезг. Механические переключатели, как известно, шумные при открытии и закрытии, и они имеют тенденцию к «подпрыгивать» назад и вперед между состояниями кратко, прежде чем осесть. В то время как возможность отскока не представляет большую проблему в этом простом приложении, мы будем рассматривать его для хорошей меры и ввести очень общий сборочный рутина - цикл задержки. Как написано здесь, этот цикл будет обеспечивать задержку около 5 миллисекунд (тысячных секунды) между показаниями как простой способом, чтобы дать переключателю возможность стабилизировать.


Прежде всего, давайте рассмотрим сам код.

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

Первая строка этого блока использует команда mov для загрузки десятичного значения 1834 в R15. MSP430 имеет 12 регистров общего назначения R4-R15, которые могут быть использованы для временного хранения значения в ситуациях, так же, как эта. Это значение будет столько раз, сколько итераций в цикле.

Следующие две инструкции образуют цикл, который будет вычитать 1 из R15 на каждой итерации, эффективный обратный отсчет до тех пор, пока R15 не будет равен нулю.


dec или «декремент» инструкция вычитает 1 из операнда и сохраняет результат обратно в операнд. Кроме того, инструкция dec будет влиять на Z или «нулевой» бит состояния. Z бит устанавливается, если результат байт или слово операции равен 0 и очищается, если результат не равен 0.

JNZ или «переход, если не нуль» инструкция считывает Z , и если он не установлен в "1" программа перемещает выполнение к операнду по метке L1. Когда R15 наконец равен 0, программа двигается вперед к следующей инструкции, где безусловный переход посылает исполнение всего кода обратно к метке MainLoop и весь процесс чтения кнопки и установки светодиодов начнутся снова.


Число итераций для цикла задержки определяется путем расчета, сколько циклов команд в цикле и сколько инструкций циклов потребуется затратить на необходимое время задержки. В своем режиме запуска по умолчанию MSP430G2231 будет работать на частоте около 1.1Mhz, которая дает время цикла инструкции 909 наносекунд (миллиардных долей секунды). Не каждая команда будет выполняться в одном цикле. Количество циклов зависит от режима адресации и конкретной команды. Вот инструкция dec занимает 1 такт и JNZ занимает 2 такта, поэтому общее время каждой итерации этого цикла составляет 3 такта.

Базовая формула будет = Loops = Delay / (время цикла * Инструкция Циклы в цикле), который в этом случае будет 1834 = 0.005sec / (0.000000909sec * 3).

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




Инициализация сброса


; ------------------------------------------------- -----------------------------
; Interrupt Vectors
; ----------------------------------------------- -------------------------------
.sect ".RESET"; MSP430 СБРОС Вектор
.short _main
.end




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

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



В заключение


Если вы еще не загрузили следующие документы с веб-сайта Texas Instruments, то,вероятно,это будет ваш следующий шаг.


  • 1) Ассемблере Руководство Инструменты MSP430 пользователя (slau131e)
  • 2) Руководство MSP430x2xx Family пользователя (slau144e)
  • 3) MSP430G2x21, MSP430G2x31 Спецификация (slas694b)
  • 4) Code Composer Studio Руководство пользователя (slau157n)

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



Написать:
21:41
5555
Нет комментариев. Ваш будет первым!