Измеритель температуры и давления на AVR – Часть 1

June 27, 2010 by admin Комментировать »

Прежде чем непосредственно заняться этой относительно сложной конструк­цией, нам придется углубиться в теорию и понять, как в восьмиразрядном контроллере производить арифметические действия с многобайтовыми чис­лами, и к тому же получать результат в десятичной системе счисления. Без этого никакой измеритель с индикацией спроектировать будет невозможно, так как АЦП контроллера выдает абстрактные численные результаты, а нам нужны физические величины. Подгонять выходную шкалу с помощью регу­лирования соотношений опорного и измеряемого напряжения, как мы это делали в цифровом термометре из главы 17, при наличии процессора— не просто глупое, но и крайне неудобное занятие: для термометра нужна одна шкала, для датчика давления — совсем другая (а если бы мы еще пару датчи­ков других величин придумали вставить?).

Поэтому для начала поучимся оперировать в контроллере большими числами и представлять их в десятичной форме.

Арифметика многобайтовых чисел в МК

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

add RL1,RL2 adc RH1,RH2

Здесь переменные rli и rl2 содержат младшие байты слагаемых, а rhi и RH2 — старшие. Если при первой операции результат превысит 255, то пере­нос запишется во все тот же флаг переноса С и учтется при второй операции. Общий результат окажется в паре rhi : rli. Совершенно аналогично выгля­дит операция вычитания. Примеры операций с большим числом слагаемых вы найдете в тексте программ далее.

А вот с умножением и делением несколько сложнее. Выполнение типовых операций на AVR для чисел с различной разрядностью, вообще говоря, при­водится в фирменных руководствах по применению: «аппнотах» (Application Notes, в данном случае номер 200). Но эти процедуры для наших целей все равно придется творчески переработать (тем более что в них встречаются ошибки). Поэтому мы не будем на них останавливаться, а сразу воспользуем­ся тем обстоятельством, что для контроллеров семейства Mega определены аппаратные операции умножения. Тогда и алгоритм сильно упрощается, и легко модифицируется для любого размера операндов и результата. Вот так выглядит такой алгоритм для перемножения двух 16-разрядных сомножите­лей с получением 24-разрядного результата (в названиях исходных перемен­ных отражен факт основного назначения такой процедуры — для умножения неких данных на некий коэффициент^):

.def dataL = г4 ;младший байт данных

.def dataH = г5 ;старший байт данных

.def KoeffL = г2 /младший байт коэффициента

.def koeffH = гЗ /старший байт коэффициента

.def temp = г1б /байт О результата (LSB – младший разряд)

.def temp2 = rl7 /байт 1 результата

.def teirp3 = rl8 /байт 2 результата (MSB – старший разряд)

/умножение двух 16-разрядных величин, только для Меда /исходные величины dataH:dataL и KoeffH:KoeffL /результат 3 байта temp2:tempi:temp /

Mul6xl6:

clr temp2 /очистить старший

Сокращения LSB и MSB означают least (most) significant bit — младший (старший) зна­чащий разряд, по-русски МЭР и СЭР, соответственно.

mul dataL,KoeffL /умножаем младшие

mov teirp,rO ;в rO младший результата операции mul

mov tempi,rl ;в rOl старший результата операции mul

mul dataH,KoeffL /умножаем старший на младший

add tempi,rO ;в rO младший результата операции mul

adc temp2,rl ;в rOl старший результата операции mul

mul dataL,KoeffH /умножаем младший на старший

add tempi,rO ;в rO младший результата операции mul

adc temp2,rl ;в rOl старший результата операции mul

mul dataH,KoeffH /умножаем старший на старший

add temp2,r0 ;4-й разряд нам тут не требуется, но он – в г01

ret

Как видите, если нужно получить полный 32-разрядный диапазон, просто добавьте еще один регистр для старшего разряда (temp3, к примеру) и одну строку кода перед командой ret: adc temp3,r01

Естественно, можно просто обозвать rOi через temp3, тогда и добавлять ни­чего не придется.

Деление — значительно более громоздкая процедура, чем умножение, требу­ет больше регистров и занимает больше времени. Операции деления двух чисел (и 8- и 16-разрядных) приведены в той же «аппноте» 200, и даже без ошибок, но они не всегда удобны на практике: часто нам приходится делить результат какой-то ранее проведенной операции умножения или сложения, а он нередко выходит за пределы двух байтов.

Здесь нам потребуется вычислять среднее значение для уточнения результата измерения по сумме отдельных измерений. Если даже само измерение укла­дывается в 16 разрядов, то сумма нескольких таких результатов уже должна занимать три байта. В то же время делитель — число измерений — будет от­носительно небольшим и укладывается в один байт. Но мы не будем здесь заниматься построением «настоящих» процедур деления (интересующихся отсылаю к моей книге [18]). Многие подобные задачи на деление удается решить значительно более простым и менее громоздким методом, если зара­нее подгадать так, чтобы делитель оказался кратным степени 2. Тогда все деление сводится, как мы знаем, к сдвигу разрядов вправо столько раз, како­ва степень двойки.

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

/деление на 64

clr count_ciata /счетчик до 6 div64L:

Isr dataH /сдвинули старший

ror dataL /сдвинули младший с переносом

inc count_data

cpi count_data,б

brne div64L

He правда ли, гораздо изящнее и понятнее? Попробуем от радости решить задачку, которая на первый взгляд требует по крайней мере знания высшей алгебры — умножить некое число на дробный коэффициент (вещественное число с «плавающей запятой»). Теоретически для этого требуется предста­вить исходные числа в виде мантисса-порядок, сложить порядки и перемно­жить мантиссы. Нам же неохота возиться с этим представлением, так как мы не проектируем универсальный компьютер, и в подавляющем большинстве реальных задач все конечные результаты у нас есть целые числа.

На самом деле эта задача решается очень просто, если ее свести к последова­тельному умножению и делению целых чисел, представив реальное число в виде целой дроби с оговоренной точностью. В десятичной форме это выгля­дит так: представим число 0,48576 как 48 576/100 ООО. И если нам требуется на такой коэффициент умножить, к примеру, число 976, то можно действо­вать, не выходя за рамки диапазона целых чисел: сначала умножить 976 на 48 576 (получится заведомо целое число 47 410 176), а потом поделить ре­зультат на 10^ чисто механически перенеся запятую на пять разрядов. Полу­чится 474,10176 или, если отбросить дробную часть, 474. Большая точность нам и не требуется, так как и исходное число было трехразрядным.

Улавливаете, к чему я клоню? Наше ноу-хау будет состоять в том, что мы для того, чтобы «вогнать» дробное число в целый диапазон в микроконтроллере, будем использовать не десятичную дробь, а двоичную — деление тогда све­дется к той же самой механической процедуре сдвига разрядов вправо, ана­логичной переносу запятой в десятичном виде.

Итак, чтобы умножить 976 на коэффициент 0,48576, следует сначала послед­ний вручную умножить, например, на 2^^ (65 536), и тем самым получить числитель соответствующей двоичной дроби (у которой знаменатель равен 65 536) — он будет равен 31 834,76736, или, с округлением до целого’, 31 835. Такой точности хватит, если исходные числа не выходят, как у нас, за преде­лы 3—4 десятичных разрядов. Теперь мы в контроллере должны умножить исходную величину 976 на константу 31 835 (см. процедуру перемножения ранее), и полученное число 31 070 960 (оно оказывается 4-байтовым — $01DA1AF0, потому нашу процедуру Ми1бх1б придется чуть модифициро­вать, как сказано при ее описании) сдвинуть на 16 разрядов вправо:

;в dclHH:ddH:ddM;ddL число $01DA1AF0, ;его надо сдвинуть на 16 разрядов

с1г cnt divlGL: ;деление на 65536

Isг ddHH ;сдвинули старший

гог ddH ;сдвинули 3-й

гог ddM /сдвинули 2-й

гог ddL ;сдвинули младший

inc cnt

cpi cnt,16

brne divl6L ;сдвинули-поделили на 2 в 16

В результате, как вы можете легко проверить, старшие байты окажутся нуле­выми, а в ddM:ddL окажется число 474— тот же самый результат. Но и это еще не все — такая процедура приведена скорее для иллюстрации общего принципа. Ее можно еще больше упростить, если обратить внимание на то, что сдвиг на восемь разрядов есть просто перенос значения одного байта в соседний (в старший, если сдвиг влево, и в младший — если вправо). Итого получится, что для сдвига на 16 разрядов вправо нам надо всего-навсего от­бросить два младших байта и взять из исходного числа два старших ddHH:ddH — ЭТО И будет результат. Проверьте — $01DA и есть 474. Никаких других действий вообще не требуется!

Если степень знаменателя дроби, как в данном случае, кратна 8, то действи­тельно никакого деления, даже в виде сдвига, не требуется, но чаще всего это не так. Однако даже в этом случае приведенный принцип может помочь: на­пример, при делении на 2^^ вместо пятнадцатикратного сдвига вправо резуль­тат можно сдвинуть на один разряд влево (умножив число на два), а потом уже выделить из него старшие два байта. В программе далее мы будем де­лить на 2^°= 1024, отбрасывая младший байт (деление на 8), и еще дважды сдвигая результат вправо. Вот такая специальная арифметика в МК.

Операции с числами в формате BCD

о двоично-десятичных числах или числах в формате BCD было подробно рассказано в главе 14, Как ясно из сказанного там, упакованные BCD-числа удобны для хранения данных, но неудобны для отображения и для выполне­ния арифметических операций с ними. Поэтому перед отображением упако­ванные BCD-числа распаковывают, перемещая старший разряд в отдельный байт и заменяя в обоих байтах старшие полубайты нулями. А перед проведе­нием арифметических действий их переводят в обычный формат, после чего опять преобразуют в упакованный формат BCD. Вот этими операциями мы и займемся. Следует отметить, что в системе команд 8051 (а также и знамени­того 8086) есть специальные команды десятичной коррекции, но в AVR их нет, и придется изобретать им замену самостоятельно.

В области двоично-десятичных преобразований (BCD-преобразований) есть три основные задачи:

? преобразование двоичного/шестнадцатеричного числа в упакованный BCD-формат;

? распаковка упакованного BCD-формата для непосредственного пред­ставления десятичных чисел с целью их вывода на дисплей;

? обратное преобразование упакованного BCD-формата в двоич-ный/шестнадцатеричный с целью, например, произведения арифметиче­ских действий над ним.

Некоторые процедуры для преобразования в BCD-формат приведены в фир­менной Application notes 204. Приведем здесь вариант такой процедуры, бо­лее экономичный в части использования регистров. Исходное hex-число со­держится в регистре temp, распакованный результат — в tempi:temp. Процедура довольно короткая:

/•преобразование 8-разрядного hex в неупакованный BCD

;вход hex= temp, выход BCD teirpl – старший; temp – младший

;эта процедура работает только для исходного hex-числа от О до 99

bin2bcd8:

clr tempi ;очистить MSB bBCD8_l:

subi temp,10 ;input = input – 10

brcs bBCD8_2 ;если установлен бит переноса, закончить

inc tempi ;увеличить MSB

rjmp bBCD8_l ;перейти на начало цикла

bBCD8__2:

subi teirp,-10 ; скомпенсрфовать лишнее вычитание ret

Заодно приведем одно из решений обратной задачи — преобразования упа­кованного BCD в hex-число, после чего с ним можно производить арифмети­ческие действия (хотя в программе далее это нам не понадобится). По срав­нению с «фирменной» BCD2bin8 этв процедура хоть и немного длиннее, но понятнее и более предсказуема по времени выполнения:

;на входе в teirp упакованное BCD-значение ;на выходе в temp hex-значение

;tempi – вспомогательный регистр для промежуточного хранения temp

/действительна только для семейства Меда

HEX_BCD:

mov tempi,temp

andi temp,ObllllOOOO /распаковываем старшую тетраду swap temp /старший в младшей тетраде

mul temp,multlO /умножаем на 10, в гО результат умножения mov tenp,tenpl /возвращаемся к исходному andi temp,ObOOOOllll /младший add temp,rO /получили hex ret

Более громоздкая задача — преобразование многоразрядных чисел. Преобра­зовывать BCD-числа, состоящие более чем из одного байта, обратно в hex-формат приходится крайне редко, зато задача прямого преобразования воз­никает на каждом шагу. В программе далее нам понадобится преобразование 16-разрядного hex-числа в упакованный BCD. Реализацию этой задачи мы не будем разбирать подробно (см. разд. «Программа измерителя температуры и давления» в приложении 4, процедура Ь1п2всо1б).

Хранение Данных в ОЗУ

в проектируемом измерителе для всех операций переменных-регистров не хватит, и часть данных придется хранить в ОЗУ (SRAM). Познакомимся с общими принципами обращения к ячейкам этой памяти.

Во всех случаях в чтении и записи SRAM используются регистры х, y и z — то есть пары г27:г2б, г29:г28 И г31:г30, соответственно, которые по от­дельности еще именуют xh:xl, yh.yl, zh.zl— в том же порядке (то есть старшим в каждой паре служит регистр с большим номером). Если обмен данными производится между памятью и другим регистром общего назначе­ния, то достаточно задействовать только одну из этих пар (любую), если же между областями памяти — целесообразно задействовать две. Независимо от того, какую из пар мы используем, чтение и запись происходят по идентич­ным схемам, меняются только имена регистров.

Покажем основной порядок действий при чтении из памяти в случае исполь­зования регистра z (г31:г30). Чтение одной ячейки с заданным адресом Ad­dress, коррекция ее значения и обратная запись производится так:

ldi zh,High(Address) /старший байт адреса RAM ldi ZL,Low(Address) ;младший байт адреса RAM Id temp,z /читаем ячейку в temp inc temp /например, увеличиваем значение на 1 St z,teirp ;и снова записьюаем

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

St -z,teirp ;с преддекрементом, запись в ячейку с адресом Z-1,

/•после выполнения команды регистр Z = Z-1 St z+,tenp ;с постинкрементом, запись в ячейку с адресом Z,

/•после выполнения команды регистр Z = Z+1

Абсолютно аналогично выглядят команды чтения:

Id teirp,-z ;с преддекрементом, чтение из ячейки с адресом Z-1,

/•после выполнения команды регистр Z = Z-1 Id temp,Z+ ;с постинкрементом, чтение из ячейки с адресом Z,

/•после выполнения команды регистр Z = Z+1

А вот как можно в цикле записать одно и то же значение из temp в 16 идущих подряд ячеек памяти, начиная с нулевого адреса старших 256 байт памяти:

ldi zh,1 clr ZL LoopW:

st z+,temp ;сложили в память cpi ZL,16 ;счетчик до 16 brne LoopW

Напомним, что область пользовательского ОЗУ начинается с адреса $60 (96io). При попытке записать что-то по меньшему адресу, вы обязательно по­падаете в какой-то регистр, и результат окажется непредсказуем. Также не следует забывать о том, что последние адреса ОЗУ заняты под стек. Так, в ATmega8535 имеется 512 байт SRAM, потому последний адрес (ramend) бу­дет равен 96 + 512 – 1 = 607 ($25F), но не стоит занимать адреса ОЗУ выше примерно $250 (с запасом).

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

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