Главная » Bascom-8051, Basic, Железо » Программирование последовательного порта Bascom-8051

0

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

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

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

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

Следующий важный компонент программирования системы с последовательным портом – выбор протокола связи  и  формата  данных.  Считается,  что  чем  короче  передаваемое  сообщение,  тем  лучше.  В  принципе  это правильно, но при одном условии, что это сообщение  должно  быть  понятным, и, самое главное, оставаться понятным  в  искаженном  виде.  Имеется  в  виду,  не  то,  что  в  сообщении  должна  содержаться  избыточная информация, позволяющая восстановить потерянное, а только то, что принимающая сторона должна обнаружить факт сбоя.  Особенно опасно применение протоколов с чисто двоичными данными. Искажение таких  данных может оказаться не только незамеченным, но и привести к потере синхронизации (опознаванию начала и конца пакетов данных). Автор продолжает настаивать на необходимость  применения в протоколах только текстовых структур с разделителями в виде символов 0Dh («ВК» – возврат каретки) и 0Ah («ПС» – перевод строки). Передача данных в виде чисел, также должна производится символами. Особенно это важно для открытых систем. Причем, чем более система открыта, тем понятней должен быть язык передаваемых сообщений. За применение протоколов с    текстовым   форматом   данных   приходится   платить   приблизительно   двукратным    увеличением    длины передаваемых сообщений и физическим усложнением канала связи (чтобы при необходимости скомпенсировать потерю  скорости).  Однако  это  окупает  себя  надежностью  работы,  простотой  мониторинга  (наблюдения)  за каналом связи, отличной совместимостью со  стандартными языками программирования, в том числе Bascom- 8051.

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

тактовой  частоты  процессора.  Опыт  программирования  и  эксплуатации  микропроцессорных   систем  связи

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

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

игнорирование  реальных  временных  соотношений  в  системе  могут  привести  к  значительному  усложнению

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

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

стационарное  состояние,  декодирования  и  исполнения  принятых  сообщений,  генератора  событий  реального

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

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

‘———————————————-

‘Демонстрационная программа буферированного ввода-вывода данных через

‘последовательный канал. Программа принимает сообщения и анализирует

‘———————————————- ‘Если обнаруживается следующие сообщения, то:

‘"R" – выдается строка с данными состояния системы

‘"Lxxxxx" – загружаются данные в ЦАП

‘"Sx" – устанавливается режим

‘Формат строки состояния системы: "SxLxxxxxTxxx" ‘                        режим     ^ ^     ^

‘               Напряжение ЦАП       |     | ‘                Текущее время             |

‘———————————————-

$large                          ‘большая модель памяти

Dim B_nd As Bit                 ‘бит "Приняты новые данные" Dim B_sd As Bit                 ‘бит "Передать новые данные" Dim B_entx As Bit               ‘бит "Передача разрешена"

Ri Alias Scon.0                 ‘бит RI Ti Alias Scon.1                 ‘бит TI

Dim Rang As Byte                ‘регистр состояния системы Dim Tmp As Byte                 ‘временные байтовые данные Dim Cnt As Byte                 ‘счетчик времени

Dim R_ch As Byte                ‘принятый или передаваемый символ

Dim R_bui As Byte               ‘указатель буфера ввода Dim R_buo As Byte               ‘указатель буфера вывода Dim Udac As Integer             ‘регистр данных ЦАП

Dim Temp As String * 6          ‘временная строка

Dim Inp_buf As String * 10      ‘буфер ввода Dim Out_buf As String * 32      ‘буфер вывода ‘———————

‘подключение ЦАП AD766

B_datu Alias P1.0 : B_clku Alias P1.1 : B_ldu Alias P1.2

‘———————————————-

Config Timer0 = Timer , Gate = Internal , Mode = 1 : Start Timer0 ‘———————

‘TIMER2 в режиме 16-бит. таймера с внутр. тактир. для синхронизации UART Config Timer2 = Timer , Gate = Internal , Mode = 2

$baud = 9600                    ‘скорость 9.6 кБ

$crystal = 12000000             ‘при кварце 12 МГц

‘инициализируем регистры, определяющие режим последовательного канала

Scon = &H52                     ‘режим

Rcap2h = &HFF : Rcap2l = &HD9 : Start Timer2  ‘скорость, только таким образом

‘———————

On Timer0 Timer_0_int Nosave    ‘вектор прерывания

On Serial Ser_int Nosave        ‘вектор прерывания

Enable Interrupts               ‘вообще разрешить прерывания Enable Timer0                   ‘разрешить прерывания таймера 0 ‘!!! ————————————–

B_entx = 0 : Reset Ri : Reset Ti

Enable Serial                   ‘разрешить прерывания посл.интерфейса

Out_buf = "Test_com" + Chr(13) + Chr(10)

R_buo = Varptr(out_buf)         ‘указатель буфера на начало

Tmp = Peek(r_buo) : Sbuf = Tmp  ‘первый символ на передачу Incr R_buo                      ‘укажем на второй символ ‘ВНИМАНИЕ! Следующая строка принципиально важное место!

Waitms 100                      ‘ждем пока стартовое сообщение будет передано

‘теперь буфер пуст и его уже нельзя испортить

‘инициализировать систему

B_entx = 1 : B_nd = 0 : B_sd = 1 : Cnt = 0 : Rang = 0 : Udac = 0

R_bui = Varptr(inp_buf)         ‘указатель буфера на начало

Mc:

Do                              ‘главный цикл

If B_nd = 1 Then             ‘активизируется по приходу сообщения

Reset B_nd : Goto N_dat End If

If B_sd = 1 Then             ‘активизируется при необходимости передачи

Goto S_dat End If

‘Idle Loop

‘—————————————— ‘обработка принятых данных

N_dat:

Temp = Left(inp_buf , 1)  ‘выделим первый символ для анализа

‘—–

‘—–

If Temp = "R" Then       ‘запрос состояния?

Set B_sd : Goto Mc    ‘строка обработана – в главный цикл

End If

If Temp = "L" Then       ‘данные ЦАП?

‘—–

бр. строку в число с полярностью

Gosub Sload_766       ‘загрузим ЦАП

Goto Mc               ‘строка обработана – в главный цикл

End If

ые режима?

бразуем

строку в число

P2 = Rang             ‘загрузим в порт P2

Goto Mc               ‘строка обработана – в главный цикл

End If Goto Mc

‘—————————————— ‘обработка данных на вывод

S_dat:

If B_entx = 1 Then              ‘начнем, если передача разрешена

Reset B_sd                  ‘сбрасываем бит, вызв. передачу

Reset B_entx                ‘и запр. передачу до полн. Выв. буфера ‘Формируем строку состояния системы: "SxLxxxxxTxxx,ВК,ПС" Out_buf="S"+Str(rang)+"L"+Str(udac)+"T"+Str(cnt)+Chr(13)+Chr(10) R_buo = Varptr(out_buf)     ‘указатель буфера на начало

Tmp = Peek(r_buo) : Sbuf = Tmp ‘первый символ на передачу ‘этой операцией инициализируем систему вывода по прерыванию ‘остальные символы буфера будут выводиться автоматически

Incr R_buo          поставим указатель буфера на второй символ

End If Goto Mc

‘———————

‘подпрограмма загрузки данных в ЦАП AD766, AD1851

Sload_766:

‘выдвинуть данные из Udac в режиме 0 (ст. сначала , -_-) Shiftout B_datu , B_clku , Udac , 0

Reset B_ldu : Set B_ldu     ‘загрузить данные

Return

‘———————————————-

‘обработка прерывания таймера 0

Timer_0_int:

$asm

Mov Th0 , #&HD8

Mov Tl0 , #&HFD              ;уст. периода прерыв. 10 мс

$end Asm

Incr Cnt                     ‘считаем прерывания

Return

‘———————————————- ‘обработка прерывания последовательного интерфейса Ser_int:

$asm

$end Asm Return

$asm

Intsr:

Ints1: Ints2:

Intse: Ints3:

Ints4: Ints5:

Intst:

Ints6:

Jbc {Ri} , Intsr  ;ищем источник прерывания

Jbc {Ti} , Intst  ;заодно и сбрасываем бит вызвавший прерывание

; прерывание приемника

Push Psw Push Acc

Mov {R_ch} , Sbuf ;сохранить принятый символ

Mov A , {R_ch}

Cjne A , #&h0d , Ints3

Setb {B_nd}       ;0Dh – принята строка

Mov A , {R_bui}   ;указатель буфера

Xch A , R0        ;сохранить R0

Mov @R0 , #&h00   ;записать в буфер конец строки

Xch A , R0        ;восстановить R0

Mov {R_bui} , #{inp_buf} ;переинициализировать указатель буфера

Pop Acc Pop Psw Reti

Cjne A , #&h0a , Ints4

Sjmp Intse        ;0Ah – игнорировать

Mov A , #{inp_buf + 10} Cjne A , {R_bui} , Ints5

Sjmp Intse

Mov A , {R_bui}   ;указатель буфера

Xch A , R0

Mov @R0 , {R_ch}  ;все остальное записывать в буфер

Xch A , R0

Inc {R_bui}       ;если буфер незаполнен – изменим указатель

Sjmp Intse

; прерывание передатчика

Push Psw Push Acc

Mov A , {R_buo}     ; считаем символ из буфера вывода

Xch A , R0

Mov {R_ch} , @R0

Xch A , R0

Mov A , #{out_buf + 10} ;если это произошло за пределами буфера

Cjne A , {R_bui} , Ints6 ;заканчиваем сообщение

Sjmp Ints7

Mov A , {R_ch}      ; очередной символ равен 0?

Jz  ints7

Mov Sbuf , {R_ch}

Inc {R_buo}         ; изменим указатель буфера вывода

Ints7:

$end Asm

Sjmp Intse

Setb {B_entx}       ; установим указатель разрешения передачи

Sjmp Intse          ; вначале символа "возврат каретки"

‘——————————————

Для   многих   систем   оказывается   достаточным   компромиссное   решение,   когда   по   прерыванию

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

становятся ненужными переменные B_entx, R_bui и Out_buf. Упрощается фрагмент в стартовом блоке программы:

Enable Serial                   ‘разрешить прерывания посл.интерфейса

Print "Test_com" ‘инициализировать систему

Программа формирования и вывода строки состояния также может быть построена проще:

‘—————————————— ‘обработка данных на вывод

S_dat:

‘сбрасываем бит, вызвавший передачу и печатаем строку состояния системы: ‘                  "SxLxxxxxTxxx,ВК,ПС"

Reset B_sd : Print "S" ; Rang ; "L" ; Udac ; "T" ; Cnt Goto Mc

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

‘—————————————— ‘обработка прерывания последовательного интерфейса Ser_int:

$asm

Intsr:

Ints0: Ints1:

Ints2:

Intse:

Jbc {Ri} , Intsr Reti

Push Psw          ;прерывание приемника

Push Acc

Mov {R_ch} , Sbuf ;сохранить принятый символ

Mov A , {R_ch}    ;преобразуем малый символ в большой Add A , #&h9f     ;код символа ‘a’? (нижняя граница) Jnc Ints0         ;(.not.’a’+1)

Mov A , {R_ch}    ;код символа ‘z’? (верхняя граница) Add A , #&h85     ;(.not.’z’)

Jc Ints0

Mov A , {R_ch}    ;скорректировать

Anl A , #&hdf Mov {R_ch} , A Sjmp Ints1

Mov A , {R_ch}

Cjne A , #&h0d , Ints3

Setb {B_nd}       ;0Dh – принята строка

Mov A , {R_bui}   ;указатель буфера

Xch A , R0        ;сохранить R0

Mov @R0 , #&h00   ;записать в буфер конец строки

Xch A , R0        ;восстановить R0

Mov {R_bui} , #{inp_buf} ;переинициализировать указатель буфера

Pop Acc Pop Psw

Ints3: Ints4:

Ints5:

$end Asm

Reti

Cjne A , #&h0a , Ints4

Sjmp Intse        ;0Ah – игнорировать

Mov A , #{inp_buf + 10} Cjne A , {R_bui} , Ints5

Sjmp Intse

Mov A , {R_bui}   ;остальное записывать в буфер

Xch A , R0

Mov @R0 , {R_ch} Xch A , R0

Inc {R_bui}

Sjmp Intse

‘——————————————

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

‘————————————————————– ‘Демонстрационная программы монитора, работающего

‘через последовательный канал.

‘————————————————————–

$large                          ‘большая модель памяти

Dim Tmp As Byte                 ‘временные байтовые данные

Dim R_ch As Byte                ‘принятый или передаваемый символ

Dim L_adr As Word               ‘начальный адрес Dim H_adr As Word               ‘конечный адрес Dim S_adr As Word               ‘текущий адрес

Dim Big_buf As String * 64      ‘большой буфер ввода-вывода

‘———————

‘TIMER2 в режиме 16-бит. таймера с внутр. тактир. для синхронизации UART Config Timer2 = Timer , Gate = Internal , Mode = 2

$baud = 9600                    ‘скорость 9.6 кБ

$crystal = 12000000             ‘при кварце 12 МГц

‘———————

Print "Monitor"                 ‘выведем стартовое сообщение

Goto Begin                      ‘на начало

_error:                         ‘точка входа по ошибке

Gosub Dis_err                   ‘перевод строки и сообщение об ошибке

Begin:

‘ выведем приглашение "BC>" без разделителей ВК и ПС

Gosub _promt

R_ch = Waitkey                  ‘ждать ввода символа

If R_ch = &H0D Then             ‘это символ ВК?

Goto Ignore                 ‘да – его нельзя печатать

End If Beg1:

Printbin R_ch                   ‘вывести введеный символ в той же строке!

Select Case R_ch                ‘выбрать программу обработки

Case &H44 : Goto Dump_mem     ‘это символ D? Case &H4C : Goto List_mem     ‘это символ L? Case &H48 : Goto Help         ‘это символ H?

Case Else : Goto _error       ‘все остальное воспринимается как ошибка

End Select

‘ внимание! важный момент – обработка символа ВК.

‘ он может приходить в паре с символом ПС, который нужно игнорировать

‘ а может быть это уже следующая команда или опять символ ВК

Ignore:

Waitms 50                     ‘ждем прихода второго символа 50 мс

R_ch = Inkey                  ‘опрашиваем входной буфер

If R_ch = 0 Then              ‘если считали нуль, значит буфер пустой

Goto _error               ‘можно индицировать сообщение об ошибке

Elseif R_ch = &H0A Then       ‘пришел ПС

Goto _error               ‘тоже считаем, что ошибка

Elseif R_ch = &H0D Then       ‘если опять пришел ВК

Goto Ignore               ‘значит все повторить

End If                        ‘остальное – считаем это простым вводом Gosub Dis_err                   ‘перевод строки и сообщение об ошибке Gosub _promt                    ‘вывод приглашения

Goto Beg1                       ‘и на обработку новой команды

‘————————————————————–

‘вывести содержимое памяти в HEX-коде

Dump_mem:

Print " – Damp of memory"     ‘расшифровать название команды

Gosub Inp_ladr : Gosub Inp_hadr       ‘введем адреса нач. и конца блока

S_adr = L_adr                 ‘текущий адрес начнем с начального

D_mem0:

Big_buf = Hex(s_adr) + " "    ‘адрес начала строки данных

Goto D_mem2                   ‘переход при первом входе

D_mem1:

Tmp = Low(s_adr)              ‘анализируем: младшие разряды адреса?

If Tmp = 0 Then               ‘равны нулю – начать новый блок Gosub E_cnk               ‘запросить: продолжать или выйти? If R_ch = &H1B Then       ‘если введен ESC,

Goto Begin            ‘выйти

End If

End If

Tmp = Tmp And &H0F            ‘анализируем:

If Tmp = 0 Then               ‘младший полубайт равен 0?

Print Big_buf             ‘да – печатать буфер

Goto D_mem0               ‘и в начало следующей строки

End If D_mem2:

Tmp = Cpeek(s_adr)            ‘считывание из памяти

Big_buf = Big_buf + " " + Hex(tmp)       ‘и добавим изображение в буфер

Incr S_adr                    ‘изменим текущий адрес

If S_adr > H_adr Then         ‘он достиг верхней границы?

Goto D_mem3               ‘да – на выход

End If

Goto D_mem1                     ‘нет – повторить

D_mem3:

Print Big_buf                 ‘перед выходом отпечатам последнюю строку

Goto Begin

‘————————————————————– ‘вывести содержимое памяти в в текстовом виде

List_mem:

Print " – List of memory"     ‘расшифровать название команды Gosub Inp_ladr : Gosub Inp_hadr       ‘ввести границы просмотра S_adr = L_adr                 ‘текущий адрес в начало блока

L_mem0:

Big_buf = Hex(s_adr) + " "    ‘формируем начальный адрес данных с строке

Goto L_mem2                   ‘переход при первом входе

L_mem1:

Tmp = Low(s_adr)              ‘анализируем: младшие разряды адреса?

If Tmp = 0 Then               ‘равны нулю – начать новый блок Gosub E_cnk               ‘запросить: продолжать или выйти? If R_ch = &H1B Then       ‘если введен ESC, выйти

Goto Begin End If

End If

Tmp = Tmp And &H0F            ‘анализируем:

If Tmp = 0 Then               ‘ младший полубайт равен 0

Print Big_buf             ‘да – печатать буфер

Goto L_mem0               ‘и в начало следующей строки

End If L_mem2:

Tmp = Cpeek(s_adr)            ‘считывание байта из памяти

If Tmp > 127 Then             ‘непечатные символы преобразуем в точку

Tmp = &H2E                ‘с кодами выше 7fh

Elseif Tmp < &H20 Then        ‘и также с кодами ниже 20h

Tmp = &H2E                ‘это пример использования оператора Elseif End If

Big_buf = Big_buf + Chr(tmp)    ‘добавим символ

Incr S_adr                    ‘переходим к следующему адресу

If S_adr > H_adr Then         ‘проверим: не перешли верхнюю границу

Goto L_mem3               ‘да – переход

End If

Goto L_mem1                     ‘нет – повторим

L_mem3:

Print Big_buf                 ‘печать буфера перед выходом

Goto Begin

‘————————————————————– ‘выдать таблицу помощи

Help:

Print ""

Print " —— MONITOR —— " Print "| D – Display memory  |" Print "| L – List memory     |" Print "| H – This help       |" Print " ——————— " Print ""

Goto Begin

‘————————————————————– ‘подпрограммы: все, что повторяется более одного раза

‘——————– ‘ввод начального адреса Inp_ladr:

Inputhex "Begin=" , L_adr : Return ‘——————–

‘ввод конечного адреса

Inp_hadr:

Inputhex "End=" , H_adr : Return ‘——————–

‘запрос: выйти или продолжать?

E_cnk:

Print "Exit – Esc, Continue – any key"

R_ch = Waitkey : Return       ‘ждем ввода символа

‘——————–

‘перевод строки и сообщение об ошибке

Dis_err:

Print "" : Print "Error" : Return ‘——————–

‘вывод приглашение "BC>" без символов ВК ПС

_promt:

Printbin &H42 ; &H43 ; &H3E : Return       ‘запишем коды символов

‘————————————————————– End

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

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

Источник: М.Л.Кулиш, СПРАВОЧНИК ПО ПРОГРАММИРОВАНИЮ BASCOM-8051, Краснодар 2001

По теме:

  • Комментарии