Студопедия Главная Случайная страница Обратная связь

Разделы: Автомобили Астрономия Биология География Дом и сад Другие языки Другое Информатика История Культура Литература Логика Математика Медицина Металлургия Механика Образование Охрана труда Педагогика Политика Право Психология Религия Риторика Социология Спорт Строительство Технология Туризм Физика Философия Финансы Химия Черчение Экология Экономика Электроника

Система команд микроконтроллера AVR MEGA128




Базовый набор команд языка ASSEMBLER для микроконтроллеров AVR содержит 120 инструкций, которые можно разделить на 4 группы: команды пересылки данных; арифметические и логические команды; инструкции для работы с битами; команды управления ходом исполнения программы.

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

Команда загрузки констант ldi R, K применяется для записи непосредственного значения К в регистр – приемник R. В качестве регистра – приемника могут использоваться регистры общего назначения R16 – R31. Если константа представлена в двоичной или шестнадцатеричной системах счисления, то перед значением константы К необходимо указать спецификатор системы счисления 0b – для двоичной, 0х – для шестнадцатеричной соответственно. Примеры:

ldi R16, 125загрузка в R16 десятичного числа 125;

ldi R20, 0xFFзагрузка в R20 шестнадцатеричной константы FFh;

ldi R23, 0b11011001загрузка в R23 двоичной константы 11011001.

Команда пересылки данных между регистрами mov Rd, Rs используется для пересылки значения из регистра-источника Rsв регистр-приемник Rd. Операнды в команде являются исключительно регистрами общего назначения R0 – R31.

Примеры:

mov R16, R0загрузка в R16 значения из регистра R0;

mov R17, R20загрузка в R17 значения из регистра R20.

В командах пересылки данных между регистром и ячейкой памяти используется механизм косвенной адресации, при котором адрес ячейки памяти заносится в один из 16-разрядных регистров Х,Y,Z (cм. рисунок 1.3). Форматы команд:

ld R8, (R16) – загрузка данных из ячейки памяти, адрес которой находится в 16-разрядном регистре R16, в регистр общего назначения R8;

st (R16), R8 – загрузка данных из регистра общего назначения R8 в ячейку памяти, адрес которой находится в 16-разрядном регистре R16;

ldd R8, (R16+Q) – загрузка данных в регистр общего назначения R8 из ячейки памяти, адрес которой находится как сумма значения, находящегося в 16-разрядном регистре R16 , и смещения Q;

std (R16+Q), R8 – загрузка данных из регистра общего назначения R8 в ячейку памяти, адрес которой находится как сумма значения, находящегося в 16-разрядном регистре R16 , и смещения Q;

ld R8, (R16) – загрузка в регистр общего назначения R8 данных из ячейки памяти, адрес которой находится в 16-разрядном регистре R16;

 

Примеры:

ld R2, Xзагрузка в R2 значения из памяти по адресу, указанному в Х;

st Y, R5загрузка значения из регистра R5 в память по адресу, указан-
ному в Y.

ldd R5, Z+1загрузка в R5 байта из памяти по адресу Z+1;

std Y+4, R7загрузка байта из регистра R7 в память по адресу Y+4.

Для обращения к портам ввода/вывода в микропроцессоре предусмотрены специальные команды in и out:

in R, P ввод данных из порта с адресом Р в регистр общего назначения R;

out P, R вывод данных из регистра общего назначения R в порт с
адресом Р;

Примеры:

in R10, 0x15 ввод данных из порта с адресом 15h в регистр общего назначения R10;

out 0x2F, R8 вывод данных из регистра общего назначения R8 в порт с адресом 2Fh;

Арифметические и логические команды.Для работы с целыми двоичными числами целочисленное АЛУ микроконтроллера AVR MEGA128 поддерживает более десятка арифметических и логических команд.

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

Основные команды для выполнения операций сложения, вычитания и умножения (для чисел без знака):

add Rd, Rs команда сложения (addition), действие: Rd=Rd+Rs;

sub Rd, Rs команда вычитания (subtraction), действие: Rd=Rd–Rs;

mul Rd, Rs команда умножения (multipl.), действие: R1, R0=Rd*Rs.

Команды изменяют флаги переноса С, переполнения V, знака N, S, и нуля Z. При выполнении операции умножения n-значных чисел местонахождение результата разрядностью 2n фиксировано и не указывается в команде: при умножении двух байтов результат размером в слово заносится в регистровую пару (R1, R0), в R0 – младшее слово, в R1 – старшее слово.

Примеры:

add R10, R15R10 = R10 + R15;

sub R2, R7R2 = R2 – R7;

mul R5, R16R1,R0 = R5 * R16.

Команды положительного и отрицательного приращения (инкремента и декремента):

inc Rинкремент (increment), действие R=R + 1;

dec Rдекремент (decrement), действие R=R – 1.

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

Примеры:

inc R20действие R20 = R20 + 1;

dec R16действие R16 = R16 – 1.

Основными логическими командами микроконтроллера AVR MEGA128 являются:

or Rd, Rs логическое “или”; действие: Rd = Rd or Rs;

and Rd, Rs логическое “и”; действие: Rd = Rd and Rs;

eor Rd, Rs “исключающее или”; действие: Rd = Rd eor Rs.

Данные команды выполняют операции поразрядного логического “или”, логического “и”, “исключающего или” (см. таблицу 1.2) над операндами, находящимися в регистрах общего назначения, причем результат записывается по адресу первого операнда. Примеры:

or R7, R11 действие: R7 = R7 or R11;

and R10, R11 действие: R10 = R10 and R11;

eor R25, R30 действие: R25 = R25 eor R30.

 

Таблица 1.2 – Таблицы истинности логических операций or, end, eor

 

оr (или) and (и) eor (исключ. или)
Вход Выход Вход Выход Вход Выход
А В Q A B Q А В Q

 

Указанные команды используются для выполнения операций поразрядного маскирования: or – для установки единиц в заданных разрядах, and – для установки нулей, еor – для выяснения совпадений значений битов первого операнда с маской. Команды изменяют флаги нуля, Z, знака N и переполнения V. Примеры поразрядных логических операций, иллюстрирующие применение механизма маскирования битов, приводятся в таблице 1.3.

Таблица 1.3 – Примеры поразрядных логических операций

Пример поразрядного маскирования or Пример поразрядного маскирования and Пример поразрядного маскирования eor
Rd хххххххх Rd хххххххх Rd
Rs Rs Rs
Rd=Rd or Rs ххх1хх1х Rd=Rd and Rs х0хх0х0х Rd=Rd eor Rs

 

Команда поразрядного инвертирования:

com R логическое отрицание; действие: R = 0b11111111 – R,

выполняет изменение значений двоичных разрядов операнда (регистр общего назначения) на противоположные.

Пример:

com R3 действие: R3 = 0b11111111 – R3.

Полный перечень арифметических и логических команд микроконтроллера AVR MEGA128 приводится в Приложении Б.

 

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

Команды сброса cbi P, n и установки sbi P, n битов предназначены для присваивания значений 0 и 1 отдельным битам портов ввода/вывода соответственно. Первым операндом в этих командах является адрес порта ввода/вывода, вторым – номер бита (от 0 до 7).

Примеры:

cbi 0x17, 5 действие: 0x175 = 0;

sbi 0x40, 1 действие: 0x401 = 1.

Команда логического сдвига lsl R осуществляет сдвиг влево на одну позицию всех битов операнда, а в младший разряд добавляется нуль. Старший бит операнда поступает в флаг переноса С. В качестве операнда могут использоваться только регистры общего назначения. Команда lsr R выполняет сдвиг вправо на одну позицию всех битов операнда, а в старший разряд добавляется нуль. Младший бит операнда поступает в флаг переноса С. Механизм работы и синтаксис аналогичен команде lsl. Примеры использования команд логического сдвига:

lsl R17 выполнить логический сдвиг влево всех разрядов в R17;

lsr R9 выполнить логический сдвиг вправо всех разрядов в R9.

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

ldi R19, 0b01001101 загрузить константу 0b01001101 в регистр R19;

swap R19 в результате исполнения команды swap в регистре R19 будет сохранено значение 0b11010100.

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

Команды сравнения, условного и безусловного перехода. Команда сравнения cp Rd, Rs – осуществляет действие Rd–Rs и устанавливает флаги нуля Z, отрицательного результата N, переполнения V, переноса C и дополнительного переноса H. Результат не сохраняется по адресу первого операнда, а только формируются флаги. Операндами могут быть только регистры общего назначения.

Команды условного перехода вызываются сразу после команд сравнения (или других инструкций, вызывающих изменения битов регистра состояния SREG) и на основе анализа флагов осуществляют переход по указанному адресу (метке) в памяти команд.

Наиболее распространенными среди команд этой группы являются:

breq M переход на М, если равно;

brne M переход на М, если неравно;

brlo M переход на М, если меньше;

brsh M переход на М, если больше или равно.

 

Пример совместного использования команд сравнения и условного перехода:

cp R1, R5 сравнить значения в регистрах R1 и R5;

breq lbl1 выполнить переход на метку lbl1, если значения в регистрах R1 и R5 равны (R1–R5=0).

Команда rjmp М осуществляет безусловный переход по указанному
8-разрядному адресу (метке, label) в памяти команд. Пример:

rjmp lbl2 безусловный переход на метку lbl2.

Команда jmp М осуществляет безусловный переход по указанному
16-разрядному адресу (метке, label) в памяти команд. Пример:

rjmp lbl3 безусловный переход на метку lbl3.

Полный перечень команд сравнения и перехода приводится в Приложении Б.

 

1.1.4 Синтаксис и основные операторы языка С

Сложные программные проекты можно более компактно описывать (по сравнению с языком Assembler) с помощью языка программирования С, который обладает возможностями языков низкого и высокого уровня, а также большой библиотекой функций.

Алфавит языка С состоит из строчных и заглавных букв латинского алфавита, цифр (0 – 9) и специальных символов. Причем, при записи идентификаторов и ключевых слов необходимо учитывать регистр символов. Так, идентификаторы sysreg и Sysreg не являются одинаковыми. Все ключевые слова должны быть набраны строчными буквами. Разделителем между операторами является символ “;” Закомментированные строки начинаются с идущих подряд двух символов “//”.

Константы в языке С декларируются с помощью директивы #define в соответствие с синтаксисом:

#define имя константы значение.

При работе с аппаратными средствами удобно записывать константы в двоичной и шестнадцатеричной формах. Перед значением констант ставятся символы 0b и 0x для двоичного и шестнадцатеричного представлений соответственно. Регистр символов при записи шестнадцатеричных констант не имеет значения. Примеры объявления констант:

#define k 25; // объявлена десятичная константа k=25;

#define KX 0xF5; // объявлена шестнадцатеричная константа KX=F5h;

#define k2 0x6e; // объявлена шестнадцатеричная константа k2=6Eh;

#define KB 0b1010; // объявлена двоичная константа KB=0b1001.

Базовыми целыми типами данных в языке С являются: сhar (размер 1 байт, диапазон значений – 128 ¸ 127) и int (размер 2 байта, диапазон значений – 32768 ¸ 32767). Модификатор unsigned, записывыемый перед именем базового типа, позволяет интерпретировать значения приведенных выше типов данных как числа без знака: unsigned сhar (размер 1 байт, диапазон значений 0 ¸ 255), unsigned int (размер 2 байта, диапазон значений 0 ¸ 65535). При этом старший разряд является битом данных, а не знаковым битом числа.

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

тип данных имя переменной [=начальное значение].

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

char A=10;

int B, C, D;

unsigned int E, F.

Рассмотрим основные операторы языка С.

Оператор присваивания имеет следующий синтаксис:

идентификатор = выражение.

В выражениях над операндами могут использоваться следующие арифметические и логические операции языка С:

Арифметические операции:

+ сложение;

– вычитание;

* умножение,

/ деление.

Логические операции:

|| логическое ИЛИ;

&& логическое И;

! логическое НЕ;

| побитовая операция ИЛИ;

& побитовая операция И;

^ побитовая операция исключающее ИЛИ;

~ побитовая операция НЕ;

<< логический сдвиг влево;

>> логический сдвиг вправо.

Язык С относится к строго типизированным языкам программирования: переменным одного типа нельзя непосредственно присваивать значения другого типа данных. Для однозначного определения приоритета операций в выражениях необходимо использовать круглые скобки ( ). Пример оператора присваивания:

A = (B+C)*D.

Оператор условия if/else позволяет выполнять одно из двух действий в зависимости от условия. Синтаксис оператора:

if (условие) выражение1;

[else выражение2].

Условие представляет собой выражение, заданное с помощью операций отношения:

== равно;

!= не равно;

< меньше;

<= меньше или равно;

> больше;

>= больше или равно.

Если условие истинно, то выполняется выражение1, если ложно – выражение2. Часть else может отсутствовать. Если, в зависимости от условия необходимо выполнить фрагмент программы, состоящий из нескольких операторов, то их необходимо поместить в фигурные скобки { }. Пример использования оператора условия:

if (A==B) {C=D+E; I=N+5};

else {C=D–E; I=N–5;}.

Оператор выбора switch/case позволяет избирательно выполнить фрагмент программного кода, в зависимости от значения выражения. Формат оператора:

switch (целочисленное выражение) {

case константа1: выражение1;

break;

case константа2: выражение2;

break;

. . .

case константаN: выражениеN;

break;

[ default: действия по умолчанию;] }

Оператор break должен находиться во всех ветвях, в противном случае нарушится выборочное выполнение команд в ветвях после case. Ветвь default можно не указывать. Пример использования оператора выбора:

switch (num) {

case 0: A=B+C; C=D+2;

break;

case 1: A=B–C; C=D+10;

break;

case 5: A=B*C; C=D+15;

break; }

Оператор цикла с параметром for используется в тех случаях, когда заранее известно количество итераций цикла. Синтаксис оператора цикла forприведен ниже:

for(инициализирующее выражение; условное выражение; модифицирующее выражение)

{

операторы тела цикла;

}

Рассмотрим работу цикла for: инициализирующее выражение при первом запуске цикла присваивает начальное значение счетчику цикла; затем анализируется условное выражение (цикл выполняется пока условие истинно). Каждый раз после всех строк тела цикла выполняется модифицирующее выражение, в котором происходит изменение счетчика цикла. Выход из цикла произойдет, как только условное выражение получит значение false. Пример оператора цикла:

s=0;

m = 1;

for (i=1; i<=10; i++)

{ s=s+i;

m=m*i; }

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

while (условное выражение)

{

операторы тела цикла;

}

Оператор начинается с ключевого слова while, за которым следует логическое выражение, возвращающее значения false или true. Операторы, заключенные в фигурных скобках, образуют тело цикла. Пример использования оператора цикла while:

a=0;

while (a<10)

{ b=(c+d)*a–f;

a=a+2; }

Подпрограммы на языке С оформляются в виде функций. Описание функции имеет следующий синтаксис:

[Тип возвращаемого значения] имя функции ([список параметров])

{

[декларации локальных переменных]

операторы тела функции;

[returnвыражение;]

}

Если функция не возвращает значения, то тип возвращаемого значения не указывается и секция return не используется. При отсутствии параметров после имени функции обязательно указываются пустые круглые скобки ( ). Тело функции заключается в фигурные скобки { }. Переменные, объявленные в теле функции, являются локальными (видимыми только в пределах тела функции). Функции, возвращающие значения, можно использовать в правой части операторов присваивания.

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

int sum (int A, int B, char C)

{

if (C>=0) return A+B;

else return A–B;

}

Пример функции, не имеющей аргументов и не возвращающей значения:

init_data ( ) {

A=10;

B=100+А; }

 

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

#asm

операторы языка Assembler

#endasm ;

Пример фрагмента программы на языке Assembler:

#asm

mov r1,r5

ldi r17,0xf5

#endasm ;

Одиночный ассемблерный оператор в теле С – программы может быть записан в виде директивы #asm("Оператор языка Assembler”);.

Пример:

#asm("out 0x12,r16"); // выполнить команду out 0x12,r16.

Функции, написанные на ассемблере, возвращают значения через регистр R30 для типов char и unsigned char, и регистровую пару (R31, R30) для типов int и unsigned intR31 – старший, а вR30 – младший байты). Параметры функции передаются через стек, на вершину которого указывает регистр Y, причем старший байт слова записывается по старшему адресу. Механизмы вызова и возврата из функции осуществляются средствами компилятора С. Директива #pragma warn- запрещает компилятору генерировать предупреждения о том, что функция не возвращает результат стандартным способом (с помощью оператора return).

В качестве примера рассмотрим ассемблерную функцию, выполняющую суммирование двух целых чисел и возвращающую значение типа int (пояснения к ассемблерному коду прилагаются):

int summa (int A, int B)

{

#asm

ldd R27, Y+3 загрузить старший байт параметра А;

ldd R26, Y+2 загрузить младший байт параметра А;

ldd R25, Y+1 загрузить старший байт параметра В;

ld R24, Y; загрузить младший байт параметра В;

add R24, R26 выполнить суммирование младших байтов А и В;

adc R25, R27 выполнить суммирование старших байтов А и В

c учетом флага переноса С;

mov R30, R24 записать младший байт суммы в регистр R30;

mov R31, R25 занести старший байт суммы в регистр R31;

#endasm

}

Вызов данной функции может осуществляться следующим образом:

. . .

int С;

C=summa (10, 15);

. . .

Часто в ассемблерных подпрограммах возникает необходимость получить доступ к значениям переменных, объявленных в С–программе. Размещение переменных (в регистрах процессора или памяти данных) можно определить из файла с расширением *.map, имя которого совпадает с именем файла исходного кода программы. Данный файл генерируется компилятором и находится в одном каталоге с исходным модулем программы.

Рассмотрим структуру программы на С. Текст исходного модуля программы начинается с директив препроцессора #include <имя файла.h>, с помощью которых в программный код проекта подгружаются файлы заголовков, содержащие объявления различных констант, идентификаторов и прототипы функций. Пример использования данной директивы, подгружающей заголовочный файл mega128.h:

#include <mega128.h>

Далее следуют объявления констант (с помощью директив #define) и глобальных переменных.

Затем записываются декларации функций, используемых в тексте главной программы, расположенной в теле функции main(), с операторов которой начинается выполнение программы. Тело функции main() расположено внутри фигурных скобок {}.

 

1.1.5 Принципы программного управления светодиодами, подключенными к внешним выводам портов ввода/вывода микроконтроллера AVR ATMEGA128

Согласно технической документации на выходных линиях микроконтроллеров AVR при уровне напряжения, соответствующем “логическому нулю”, ток нагрузки составляет порядка 20 мА. Стандартные светодиоды потребляют ток в пределах 3¸20 мА при рабочем напряжении порядка 1,5¸4 В. Это позволяет непосредственно подключать светодиод к выходной линии порта последовательно с ограничивающим ток резистором (см. рисунок 1.5). Второй вывод цепи необходимо подсоединить к положительному полюсу источника питания (+5 В).

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

 

Рисунок 1.5 – Схема подключения светодиода к выходу порта

ввода/вывода микроконтроллера AVR

 

Для управления уровнями напряжения на выходных линиях микроконтроллера можно применять алгоритмы маскирования или, непосредственно, команды для работы с битами (cbi, sbi), описанные в пункте 1.1.3 Прямое обращение к регистрам портов ввода/вывода на языке Assembler обеспечивается с помощью команд in и out (см. пункт 1.1.3). Компилятор языка С допускает использование идентификаторов регистров ввода/вывода: DDRX – регистр управления направлением передачи данных, PORTX регистр вывода данных, PINX – регистр ввода данных, где Х – обозначение порта ввода/вывода. Например, программно доступные регистры порта А обозначаются как: DDRA, PORTA, PINA (см. таблицу 1.1). Если линия порта ввода/вывода Х программируется на вывод, то в соответствующем бите регистра управления направлением передачи данных DDRXдолжна быть установлена1, если на ввод – то 0. Рассмотрим примеры:

unsigned char a, b;декларация переменных а и b размером в байт;

DDRB=0b0101110;линии 0, 5 и 7 порта В запрограммированы на вывод данных, остальные (1,2,3,6) – на ввод.

DDRD=0xFF;установка всех линий порта D в режим вывода данных;

а=0х12;присвоить переменной а значение 12h;

PORTD = a; вывести данные (значение переменной а) в порт вывода D.

DDRС=0;установка всех линий порта С в режим ввода данных;

b=PINС; записать данные из порта ввода С в переменную b.

Для работы с отдельными разрядами регистров портов ввода/вывода на языке С можно использовать конструкции: POTTX.N и PINX.N, где N – номер бита. Например,

PORTB.2=0; второй бит порта D сбросить в 0;

PORTB.4=1; четвертый бит порта D установить в 1;

PORTD.5=PINF.3;пятому биту порта D присвоить значение третьего

бита порта F;

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

 

1.2 Описание лабораторной установки

 

Лабораторная работа выполняется в индивидуальном порядке. На каждом рабочем месте должны быть установлены: многофункциональный лабораторный макет на базе микроконтроллера AVR ATMEGA 128, ПЭВМ типа IBM PC/AT c инсталлированным программным обеспечением: операционной системой MS–WINDOWS v. 9x, 2000, XP и программатором на основе кросс-компилятора языка программирования C CodeVision AVR.

1.2.1 Описание лабораторного макета

Лабораторный макет представляет собой универсальное устройство на базе 8-разрядного микроконтроллера AVR ATMEGA 128 (fт=11,0592 MГц), в состав которого входят (рисунок 1.6): микроконтроллер AVR ATMEGA128, графический ЖК-дисплей Toshiba T6963C, блок светодиодов, клавиатуры: 3´4 и 3´1 (количество столбцов ´ количество строк), АЦП, внешний аналоговый термодатчик, пьезоизлучатель, последовательный интерфейс RS-232C. Сопряжение лабораторного макета и программатора (ПЭВМ ) обеспечивается с помощью последовательного интерфейса SPI, конструктивно использующего стандартный разъем Centronics DB-25. Общий вид передней панели лабораторного макета приведен на рисунке 1.7. Управление встроенным в макет графичес-
ким ЖК-дисплеем осуществляется через порты А и С. Блок из восьми светодиодов подключен к микроконтроллеру через порт D. Сопряжение клавиатуры 3´4 осуществляется с помощью порта Е. Через порт F, линии которого являются входами АЦП, к микроконтроллеру подключаются: пьезоизлучатель, термодатчик и клавиатура 3´1. Порт В микроконтроллера свободен и предназначен для подключения внешних устройств, таких как цифровые ЖК-индикаторы, датчики и т.д. Расположение выводов микроконтроллера AVR АТMEGA 128 приводится в Приложении А.

Рисунок 1.6 – Структурная схема лабораторного макета на базе







Дата добавления: 2015-09-07; просмотров: 2398. Нарушение авторских прав


Рекомендуемые страницы:


Studopedia.info - Студопедия - 2014-2020 год . (0.033 сек.) русская версия | украинская версия