Пещера отшельного фердопердозника

2009-08-13

ADuC847, его ADC и измерение температуры с термопары (часть 2: практика)

Рубрика: low-level programming — Метки: , , , , — datacompboy @ 19:44:35 | 824 views

Теперь посмотрим реализацию на практике измерения температуры.

Проблема №1: у нас есть PDF с таблицей миливольт через градус, а нам надо таблицы код-от-холодногоспая и термозначение-от-кода.
Итак, сперва превратим PDF в текст с помощью pdftotext, затем пройдём по таблице, и будем превращать каждое значение миливольт таблицы в код (умножая на 1000 чтобы превратить в микровольты, затем на 104.8575875 для кода АЦП)? интерполировать значения между соседними точками, распечатывая значение для каждого кода в 65536.
Не забудем учесть, что в отрицательной части таблица указана по возрастанию модуля — а потому каждую отрицательную строку следует развернуть.
Когда будем проходить мимо чисел от 0..100°, аналогично поступим с кодом холодного спая, только запоминать будем каждые 32768 не температуру, а вычесленный код ацп.
Чтобы избавиться от знака, код АЦП для минимальной температуры с обратным знаком будет смещением, таким образом получим монотонную положительную таблицу.
Итак, мы получаем вот такой конвертер: convert.pl
Итак, мы получили .a51 инклуд, в котором объявлена константа ThermoBase — код, на который смещен ноль градусов в коде ацп, объявлена табличка ThermoTable, заданная через каждые 8000h кодов ацп, константа ThermoMax для проверки на перегрев, и таблица кодов термопары соответствующих холодному спаю ThermoExtTable через каждые 10000h кодов ацп термодатчика.

Обработчик прерывания от ацп будет крайне простым:

;**************************************************************************
; INTERRUPT from ADC
         ORG 0033H
         JBC DoExtReq, ADCExt ; Измеряли опору или термопару?
ADCInt:  MOV DataH, ADC0H ; Термопару сохраняем в DataH/M/L
         MOV DataM, ADC0M
         MOV DataL, ADC0L
         SETB ADCReady    ; И ставим бит ADCReady. Бит ADCBusy сбросит основная программа
         CLR RDY0
         RETI
ADCExt:  MOV ExtH, ADC0H ; Опору измеряем в ExtH/M/L
         MOV ExtM, ADC0M
         MOV ExtL, ADC0L
         CLR ADCBusy    ; И очищаем бит ADCBusy (опора будет учтена для следующего измерения)
         CLR RDY0
         RETI

В зависимости от состояния бита DoExtReq считываем показания АЦП либо в Data, либо в Ext.

Основная часть программы так же проста:

NoWD:            JBC  ADCReady, CalcThermo ; Измерили АЦП?
                 JB   ADCBusy, SeeCMD ; Игнорируем работу с АЦП, если он занят
                     JBC  NeedExtReq, InitExtReq
                     JB   NeedAutoReq, InitAutoReq
SeeCMD: ; Прочая работа

– Если есть свежие показания АЦП — переходим к собственно вычислениям в CalcThermo
– Если показаний нет, ничего не делаем пока АЦП занят
– Если нет показания и АЦП свободен, проверим надо ли переизмерить показания холодного спая? (эти измерения можно проводить довольно редко, по сравнению с измерениями самой термопары. Я обновляю их раз в 2 сек, а термопару 5 раз в сек)
– Если нет показания, АЦП свободен, проверим не пора ли обновить температуру термопары.

Итак, само измерение холодного спая инициируется такой последовательностью:

InitExtReq:      SETB ADCBusy ; АЦП занят
                 SETB DoExtReq ; Измерение будет произведено холодного спая
                 CLR  ExtReady   ; Нужно перевычислить холодный спай
                 CLR  RDY0
                 MOV  ADCSF, #REQ_SF
                 MOV  ADC0CON1, #00100111b ; Измерение 0..1.28V, однополярно
                 MOV  ADC0CON2, #00001011b ; Измерение AIN3-AIN4 с внутренней опорой
                 MOV  ADCMODE, #00100100b ; Калибровка нуля
                 MOV  ADCMODE, #00100101b ; Калибровка максимума
                 MOV  ADCMODE, #00100010b ; Запросить 1 измерение на основном АЦП
                 JMP  MAINLOOP

Полностью аналогично производится инициация запроса показаний термопары:

InitAutoReq:     SETB ADCBusy
                 CLR  RDY0
                 MOV  ADCSF, #REQ_SF
                 MOV  ADC0CON1, #00000011b ; Измерение +-80mV биполярно
                 MOV  ADC0CON2, #00001010b ; Измерение AIN1-AIN2 в внутренней опорой
                 MOV  ADCMODE, #00100100b ; Калибровка нуля
                 MOV  ADCMODE, #00100101b ; Калибровка максимума
                 MOV  ADCMODE, #00100010b ; Запросить 1 измерение на основном АЦП
                 JMP  MAINLOOP

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

CalcThermo:
    MOV DevErr, #0
    MOV DPCON, #00010001b ; Выберем второй дптр с автоинкрементом после MOVC/MOVX
    ORL PSW, #18h ; Выберем банк 3
    JNB ExtReady, CalcExt
    JMP CalcMain

во-1х, при вычислении нам придётся часто менять DPTR и считывать табличные значения, поэтому имеет смысл переключиться на второй DPTR.

Использование автоинкремента особенно удобно при записи запись у 8051 идёт только по DPTR, и потому вместо

  MOVX @DPTR, ...
  INC DPTR
  MOVX @DPTR, ...

получается просто

  MOVX @DPTR, ...
  MOVX @DPTR, ...

Однако же при чтении из памяти данных это не так удобно, вместо

  CLR A
  MOVC A, @A+DPTR
  ...
  MOV A, #1
  MOVC A, @A+DPTR

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

  CLR A
  MOVC A, @A+DPTR
  ...
  CLR A
  MOVC A, @A+DPTR

однако, во-1х CLR A занимает на 1 байт меньше, чем MOV, и во-2х сложнее ошибиться.

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

Сделаем это прямо «в лоб»: отнимем 500мв, проверим что результат в нашей таблице:

CalcExt:
  ; Вычислим Ext-646303h
    CLR C
    MOV A, ExtL
    SUBB A, #03h
    MOV ExtL, A
    MOV A, ExtM
    SUBB A, #63h
    MOV ExtM, A
    MOV A, ExtH
    SUBB A, #64h
    MOV ExtH, A
    MOV DevErr, #1
    JC ThError ; Если меньше 500mV -- температура отрицательная, ошибка (?)
    ; A = ExtH = точка входа в таблицу хол.спая
    CJNE A, #83H, IsOver83H
IsOver83H:
    MOV DevErr, #2
    JNC ThError ; Если >= 83h -- выход, окружающая среда больше 65°

Итак, теперь у нас в Ext лежит сдвинутый код термодатчика холодного спая, из которых ExtH = указатель по таблице (он же сейчас в ACC) и ExtM/ExtL пропорциональность от 0 до 65536. Найдём точку входа в таблицу:

    ; Таблица до 83h, по 3 байта => 83h*3=189h, то есть 1 бит переноса может быть
    CLR C
    RLC A
    MOV F0, C
    ADD A, ExtH
    ORL C, F0 ; A = младший байт, C = перенос. Это надо сложить с ThermoExtTable и положить в DPTR
    MOV DPH, #HIGH(ThermoExtTable)
    JNC NoIncDPH1
    INC DPH
NoIncDPH1:
    ADD A, #LOW(ThermoExtTable)
    MOV DPL, A
    JNC NoIncDPH2
    INC DPH
    CLR C
NoIncDPH2: ; DPTR = указатель на таблицу соответствия холодному спаю

(обратите внимание, может быть только один бит переноса, но операций сдвиг/сложение две, поэтому временно один перенос сохраняем в F0).

Настроив DPTR, считываем из таблицы начальную точку диапазона:

    CLR A
    MOVC A, @A+DPTR
    MOV ExtTL, A
    CLR A
    MOVC A, @A+DPTR
    MOV ExtTM, A
    CLR A
    MOVC A, @A+DPTR
    MOV ExtTH, A ; ExtT=начальная точка интервала

Затем считываем следующую точку и сразу вычисляем разность — длину интервала

    CLR A
    MOVC A, @A+DPTR
    SUBB A, ExtTL ; C был очищен выше
    MOV R0, A
    CLR A
    MOVC A, @A+DPTR
    SUBB A, ExtTM
    MOV R1, A ; R1:R0 = разность между верхней и нижней точками интервала
    ; Теперь в ExtM:ExtL находится разница внутри диапазона, в R1:R0 находится длина диапазона

Обратите внимание, что так как длина диапазона влезает в 2 байта, достаточно было считать только два байта.

Теперь нужно вычислить ExtT=ExtT+(ExtM:ExtL*R1:R0/65536)
Вычислим ExtM:ExtL*R1:R0: 16bit+16bit=32bit, нас интересуют только старшие 17бит (17й бит = округление)
Сложим результат в R5:R4:R3:R2 (R2 вообще не считаем)

    MOV B, ExtM ; A=R1 на текущий момент
    MUL AB      ; B:A = R1*ExtM => результат в R5:R4
    MOV R5, B
    MOV R4, A
    MOV A, R1
    MOV B, ExtL
    MUL AB      ; B:A = R1*ExtL => Результат в R4:R3 (к R4 прибавляем)
    MOV R3, A
    MOV A, B
    ADD A, R4
    MOV R4, A
    JNC NoOv1
    INC R5  ; сохраним перенос если был
NoOv1:
    MOV A, R0
    MOV B, ExtM
    MUL AB      ; B:A = R0*ExtM => результат к R4:R3
    ADD A, R3
    MOV R3, A
    MOV A, B
    ADDC A, R4
    MOV R4, A
    JNC NoOv2
    INC R5  ; сохраним перенос
NoOv2:
    MOV A, R0
    MOV B, ExtL
    MUL AB      ; B:A = R0*ExtL => результат в R3:R2, R2 вообще игнорируем, у R3 проверяем только перенос и >128
    MOV A, B
    ADD A, R3
    JNC NoOv3
    INC R4
    CJNE R4, #0, NoOv3
    INC R5 ; Если R4 стал равен нулю, то перенос ушел в R5
NoOv3:
    JNB ACC.7, NoOv4 ; проверим, не следует ли округлить вверх
    INC R4
    CJNE R4, #0, NoOv4
    INC R5
NoOv4: ; Теперь в R5:R4 интерполяционная добавка к текущему значению в ExtT
    MOV A, ExtTL
    ADD A, R4
    MOV ExtTL, A
    MOV A, ExtTM
    ADDC A, R5
    MOV ExtTM, A
    JNC NoOv5
    INC ExtTH
NoOv5:
    ; Теперь ExtT = показания холодного спая в табличных значениях термопары
    SETB ExtReady

Итак, теперь у нас есть эквивалент холодного спая и мы можем получить текущую температуру.

CalcMain:
  ; Вычитаем из кода пьедестал (код становится положительным), добавляем холодный спай и по таблице
  ; интерполируем температуру
    MOV A, DataH
    ADD A, #80H ; Переводим из 8000000 = 0 к 0=0
    MOV R2, A
    ; Добавляем холодный спай
    MOV A, DataL
    ADD A, ExtTL
    MOV R0, A
    MOV A, DataM
    ADDC A, ExtTM
    MOV R1, A
    MOV A, R2
    ADDC A, ExtTH
    MOV R2, A
    MOV DevErr, #3 ; Ошибка "ниже минимума" = "обрыв термопары"
    JB  ACC.7, ErrorMin ; Ниже минимума таблицы?
    ; Теперь проверяем не вылезли ли за таблицу сверху
    CJNE R2, #ThermoMaxH, ChkMax
    CJNE R1, #ThermoMaxM, ChkMax
    CJNE R0, #ThermoMaxL, ChkMax
ChkErr:
    MOV DevErr, #4 ; Ошибка переполнение
jThError: JMP ThError
ErrorMin:
    MOV ThermoH, #0FFh
    MOV ThermoL, #0FFh
    AJMP ThError
ChkMax:
    JNC ChkErr
    ; 0< =R2:R1:R0<ThermoMax = Код с АЦП с холодным спаем и за вычетом базы

Итак, мы получили код, таблица через 32768, так что если мы R2:R1:R0 умножим на два, мы получим в CY:R2 указатель по таблице, а в R1:R0 пропорцию из диапазона 0..65536.

CalcFind: ; Выбрать из таблицы код R2:R1:R0
    CLR C
    MOV A, R0
    RLC A
    MOV R0, A
    MOV A, R1
    RLC A
    MOV R1, A
    MOV A, R2
    RLC A
    ; A = индекс таблицы, от 0..С2, и CY=0
    RLC A ; A=указатель таблицы, C=перенос
    MOV DPH, #HIGH(ThermoTable)
    JNC NIncDPH
    INC DPH
NIncDPH:
    ADD A, #LOW(ThermoTable)
    MOV DPL, A
    JNC NIncDPH2
    INC DPH
NIncDPH2: ; DPTR = ThermoTable+(R2:R1:R0/32768)*2

Итак, теперь аналогично работе с таблицей термопары выберем первую точку и длину интервала:

    CLR A
    MOVC A, @A+DPTR
    MOV R3, A
    CLR A
    MOVC A, @A+DPTR
    MOV R4, A ; R4:R3 = код из таблицы
    CLR C
    CLR A
    MOVC A, @A+DPTR
    SUBB A, R3
    MOV R5, A
    CLR A
    MOVC A, @A+DPTR
    SUBB A, R4
    MOV R6, A ; R6:R5 = разница между следущим и текущим кодом

И сдвинемся внутри интервала:

    ; Теперь в R6:R5 разница по таблице, в R1:R0 пропорция из диапазона 0..65536
    ; Вычислим [ R6:R5*R1:R0 = ThermoH:ThermoL:Tmp1:Tmp2 ] / 32768,
    ; от Tmp1 нужен только старший бит (для определения округления)
    MOV B, R1
    MUL AB ; R6*R1 нужно положить в ThermoH:ThermoL
    MOV ThermoH, B
    MOV THermoL, A
    MOV B, R1
    MOV A, R5
    MUL AB ; R5*R1 складываем в ThermoL:Tmp1
    MOV Tmp1, A
    MOV A, B
    ADD A, ThermoL
    MOV ThermoL, A
    JNC ResNoO1
    INC ThermoH ; Учтем перенос
ResNoO1:
    MOV A, R6
    MOV B, R0
    MUL AB ; R6*R0 складываем в ThermoL:Tmp1
    ADD A, Tmp1
    MOV Tmp1, A
    MOV A, ThermoL
    ADDC A, B
    MOV ThermoL, A
    JNC ResNoO2
    INC ThermoH
ResNoO2:
    MOV A, R5
    MOV B, R0
    MUL AB ; R5*R0 складывается в Tmp1:Tmp2, интересует только старший бит Tmp1 и перенос
    MOV A, B
    ADD A, Tmp1
    JNC ResNoO5
    XCH A, ThermoL
    INC A
    JNZ ResNoO4
    INC ThermoH
ResNoO4:
    XCH A, ThermoL
ResNoO5:
    ; Если ACC.7!=0, увеличим ThermoHL
    JNB ACC.7, ResNoRound
    INC ThermoL
    MOV A, ThermoL
    JNZ ResNoRound
    INC ThermoH
ResNoRound:
    ; Теперь в ThermoH/ThermoL лежит добавка к текущему значению таблицы, добавим к ней R4:R3
    MOV A, ThermoL
    ADD A, R3
    MOV ThermoL, A
    MOV A, ThermoH
    ADDC A, R4
    MOV ThermoH, A
    JNC NoThError ; Перебора тут быть не должно, ибо температура за 2048 градусов вылезать не должна
    MOV DevErr, #7
    AJMP ThError
NoThError:
    ; ---
CalcEnd: ; Конец вычислений. В Thermo* лежит вычисленное значение

Итак, все наши вычисления завершились. Восстанавливаем состояние и переходим к дальнейшей части работы:

    MOV DevErr, #0
    CLR ADCBusy ; АЦП свободен
    ; Вернуться к обычной работе
    ANL PSW, #NOT 18h ; Выбрать банк 0
    MOV DPCON, #0 ; Вернуться к основному DPTR
    JMP MAINLOOP ; Вернуться к основной программе

Вот так, весело и задорно, и довольно компактно, получаем искомую температуру с термопары :)

Комментариев нет »

Комментариев нет.

RSS-лента комментариев к этой записи. URL обратной ссылки

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

Сайт работает на WordPress