Радиомикрофоны
December 30, 2025

Восстановление исходников глючного GSM микрофона на SIM900 и PIC24FJ64GA004 от Tired/STG

Прошло 14 лет, исписано 30 страниц форума, но этот код с VRTP так и остался кривым.

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

Исходники GSM радиожучков на других модулях: SIM300 (PIC16F648A), SIM800 (PIC12F1822, очень кривой).

Оригинальная статья (без исходников, разные версии прошивки):

https://web.archive.org/web/20221208224213/https://vrtp.ru/index.php?showtopic=20314

Схема последней версии. Добавлены дополнительные охранные шлейфы

Похабная прошивка от Tired (№844) и STG (№176) имеет классические ошибки безопасности. Прочтите комментарии в листинге, чтобы научиться их избегать.

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

Вот только самые грубые косяки этого GSM радиомикрофона на SIM900:

  1. Неправильно разведена печатная плата (уже проходили в «Трёшке»‬)
  2. Нет автоматической перезагрузки GSM модуля при потере сети (виснет в течение суток)
  3. Нет развязки SIM900 по питанию с помощью дросселя (глюки и жужжание)
  4. Контролирующие шлейфы входы не зашунтированы конденсаторами (реагирует на любые импульсные помехи, в том числе от себя)
  5. По достижении BUFSZ (127) символов буфер перезаписывается. Например, подобрав длину SMS (может быть до 160 байт плюс заголовки SIM900) с текстом “RING”, можно заставить устройство подумать, будто идет входящий вызов. Кто раскрутит это до эксплуатируемой уязвимости?
  6. Ответы SIM900 проверяются «приблизительно»: не на равенство, а на вхождение подстроки
  7. Успешность дозвона и отправки SMS не учитывается
  8. Нестандартные ситуации (отсутствует SIM-карта, удалены номера) не отслеживаются
  9. Если ПИН-код неправильный, устройство введет его несколько раз, и карта заблокируется!

Отсутствует резистор R2 для вывода MCLR контроллера (см. стр. 17 даташита).

Кроме того, даже не имея исходников и не зная, что за «шедевр» перед ними, форумчане VRTP выявили следующие недостатки:

  1. При обрыве шлейфа идет звонок за звонком, высаживается батарея
  2. Владелец никак не узнает про обрыв связи в результате зависания GSM модуля, а это случается регулярно
  3. Наличие средств на счету не отслеживается

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

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

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

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

Оригинальный исходник написан на Си.

mic.asm (первая версия прошивки)

; Disassembler: naken_util - by Michael Kohn
;                 Joe Davisson
;     Web: https://www.mikekohn.net/
;   Email: mike@mikekohn.net
;
; Version: May 25, 2025
;
; Loaded mic.hex of type hex / dspic from 0x0000 to 0x157ff
; Type help for a list of commands.
;
; Addr    Opcode     Instruction                              Cycles
; ------- ---------- ----------------------------------       ------

; Compilation:
; naken_asm -o mic_recompiled.hex -type hex mic.asm
; Также см. https://www.watelectronics.com/sim900-module/


; PIC24FJ64GA004
.PIC24


;===============================================================================


#define                 TRUE    1
#define                 FALSE   0
; смещение номера в ответе на AT+CLCC
#define CLCC_PHONE_OFFSET 18
; число символов в телефонном номере
#define PHONE_LEN 39
BUFSZ                   equ     127

; получить адрес переменной в Linear Data Memory
LINEAR_DATA_MEMORY      equ     0x8000
#define LDM(var)        #(var) + LINEAR_DATA_MEMORY

; alternate interrupt vector table (59)
ALTIVT_START            equ     0x0104

; маска для AND
#define MASK(bit)       (1 << (bit))

; если операнд - байт
#define HIREG(reg)      (reg + 1)
#define HIBIT(bit)      (bit - 8)
#define HIMASK(bit)     MASK(HIBIT(bit))


#include "PIC24FJ64GA004.inc"


;===============================================================================
; DATA address definitions

buffer  equ 0x0c11              ; принятый ответ на AT-команду
;after  equ 0x0c90              ; первый байт после буфера

phone1  equ 0x1157              ; номера из записной книги SIM
phone2  equ 0x117f
phone3  equ 0x11a7

callee  equ 0x11cf              ; номер звонящего абонента
;cafter equ 0x11f6              ; первый байт после номера

active1 equ 0x11f8              ; флаги наличия номеров в записной книге SIM
active2 equ 0x11fa
active3 equ 0x11fc
unknown equ 0x11fe

state   equ 0x1202              ; конечный автомат
#define STATE_RESET 4
#define STATE_GSMON 5
#define STATE_PIN_OK 6

bufptr  equ 0x1208              ; индекс свободного байта в буфере

tm4h    equ 0x121C              ; ограничитель времени звонка?

var_a   equ 0x1230              ; ? - только чтение

lowbat  equ 0x1232              ; батарея разряжена
pollcnt equ 0x1234              ; ? - только запись

calling equ 0x1236              ; активен звонок

alert   equ 0x1238              ; шлейф сигнализации в обрыве
charged equ 0x123a              ; аккумулятор полностью заряжен
lbsent  equ 0x123c              ; сообщение о разрядившейся батарее отправлено

temp    equ 0x123e              ; временная переменная для многих операций

STKPTR  equ 0x27f0              ; стек

; indirect
i       equ 20
j       equ 14
k       equ 16
m       equ 10
pbk     equ 18                  ; прочитанный из записной книги номер
cbcpos  equ 12
search_result   equ 6           ; номер позвонившего обнаружен на SIM?
callee_offset   equ 8

;===============================================================================
; PINS usage definitions

LED_PORT            equ PORTB       ; светодиод
LED                 equ RB2

MIC_PORT            equ PORTB       ; управление чувствительностью микрофона
MICLEVEL            equ RB0

SENSOR_PORT         equ PORTB       ; шлейф сигнализации
SENSOR              equ RB8

CHARGE_PORT         equ PORTC       ; контроллер заряда
CHARGE              equ RC1

PWRKEY_PORT         equ PORTC       ; кнопка включения SIM900
PWRKEY              equ RC9         ; pin 1 of SIM900, pin 5 of PIC

STATUS_PORT         equ PORTB       ; SIM900 STATUS
STATUS              equ RB14

; PIC   - SIM900
; 2 RC6 - 10 RXD
; 3 RC7 - 9 TXD

DTR_PORT            equ PORTC       ; SIM900 DTR
DTR                 equ RC8         ; pin 3 of SIM900, pin 4 of PIC


#include "macro.asm"


.org 0


    goto main


; таблица векторов прерываний
#include "ivt.asm"


.org 0x200


main:
    mov #temp, w15

    ; стек
    mov #STKPTR, w0
    mov w0, SPLIM

    nop
    rcall setup1

    ; инициализированные переменные
    invoke2(intialize, #initialized_data, #0)

    ; ?
    mov #0, w0
    cp0 w0
    bra z, skip

    call 0

skip:
    call start
    sleep_
    reset


setup1:
    .scope

    ; Program Space Visibility in Data Space Enable bit:
    ; Program space is not visible in data space
    bclr CORCON, #PSV

    ; 8 trap vectors + interrupt vectors
    mov #(2 * (8 + 14) + ALTIVT_START), w0
    cp0 w0
    bra z, done

    mov #0, w0
    mov w0, PSVPAG

    ; Program space is visible in data space
    bset CORCON, #PSV

done:
    return
    .ends

#include "idata.asm"
#include "at.asm"


start:
    lnk #0x00

    ; A/D PORT CONFIGURATION REGISTER (199)
    setm w0
    mov w0, AD1PCFG

    ; Peripheral Output Function is Assigned to RP22 Output Pin bits (123)
    bset RPOR11, #0
    bset RPOR11, #1

    bclr RPOR11, #2
    bclr RPOR11, #3
    bclr RPOR11, #4

    ; Assign UART1 Receive (U1RX) to the Corresponding RPn Pin bits
    bset RPINR18, #0
    bset RPINR18, #1
    bset RPINR18, #2
    ; U1RXR3
    bclr RPINR18, #3
    bset RPINR18, #4

    ; TON: Timery On bit - 1 = Starts 16-bit Timery
    ; Timery Input Clock Prescale Select bits: 11 = 1:256
    mov #(TON | TCKPS1 | TCKPS0), w0
    mov w0, T4CON

    ; период таймера 4
    movlf(15200, PR4)

    mov #(TON | TCKPS1 | TCKPS0), w0
    mov w0, T3CON

    ; период таймера 3
    movlf(760, PR3)

    bclr IFS1, #T4IF
    ; T4IE: Timer4 Interrupt Enable bit
    bset IEC1, #T4IE

    movlf(STATE_RESET, state)

    delay(#1000)

    bclr TRISB, #TRISB2
    ; Change Notice 4 Pull-Up Enable
    bset CNPU1, #CN4PUE

loop:
    rcall SIM900_power
    rcall function_sim900_init
    rcall function_register
    bra loop


#include "atoi.asm"


interrupt_timer3:               ; 0x05f8
    .scope
    push.d w0
    push w2
    push PSVPAG
    mov #0, w0
    mov w0, PSVPAG
    lnk #0

    bclr IFS0, #T3IF            ; прерывание обработано

    IFNE(calling, TRUE, exit)   ; если соединение не активно, выход
    IBLINK
exit:

    ulnk
    pop PSVPAG
    pop w2
    pop.d w0
    retfie
    .ends


interrupt_timer4:
    .scope
    push.d w0
    push w2
    push PSVPAG
    mov #0, w0
    mov w0, PSVPAG
    lnk #0x00

    bclr IFS1, #T4IF            ; прерывание обработано

    ; увеличение счетчика времени в tm4h:initialized_data
    inc16(tm4h, initialized_data)

    IFNE(var_a, TRUE, exit)     ; мигаем, если var_a не ноль
    IBLINK
exit:

    ulnk
    pop PSVPAG
    pop w2
    pop.d w0
    retfie
    .ends


#include "utils.asm"
#include "uart.asm"


; кнопка включения модуля SIM900
SIM900_power:
    .scope
    lnk #0x00

    IFNE(state, STATE_RESET, exit)

    delay(#400)

    bclr TRISC, #TRISC9
    bclr PWRKEY_PORT, #PWRKEY

    delay(#2000)

    bset PWRKEY_PORT, #PWRKEY
    bset TRISB, #TRISB14

wait:                           ; ожидание SIM900 STATUS (66)
    mov.b HIREG(STATUS_PORT), wreg
    and.b #HIMASK(STATUS), w0
    CP0_W0
    bra z, wait

    rcall setup_uart

    movlf(STATE_GSMON, state)

exit:
    ulnk
    return
    .ends


function_sim900_init:
    .scope
    lnk #0x02

    IFNE(state, STATE_GSMON, exit)

    movlf(0, lbsent)

loop:
    ATCMD(at)                  ; отправляем "AT"

    blink                       ; мигаем
    blink

    unless_buffer_contains(ok + 1, retry)
    rcall clear_buffer
    bra autobauded              ; ответ содержит "OK"

retry:
    ATCMD(at)

    blink
    blink
    blink
    blink

    unless_buffer_contains(ok + 1, more)
    rcall clear_buffer
    bra autobauded

more:
    ATCMD(at)

    delay(#250)
    led_off
    delay(#250)
    blink
    blink
    blink
    blink
    blink
    blink
    blink

    unless_buffer_contains(ok + 1, loop)
    rcall clear_buffer

autobauded:                     ; получен "OK"
    clr w0
    mov w0, [w14]

repeat_pin:
    ATCMD(at_cpin)             ; нужен ли SIM PIN?

    blink
    blink

    if_buffer_contains(cpin_sim_pin + 1, pin_ready_or_sim_pin)
    unless_buffer_contains(cpin_ready, repeat_pin)

pin_ready_or_sim_pin:
    unless_buffer_contains(cpin_sim_pin + 1, enter_pin)

    movlo(TRUE, 0)              ; требуется ввод пин-кода

enter_pin:
    rcall clear_buffer

    mov #TRUE, w0               ; установлен пин-код?
    subr w0, [w14], [w15]
    bra z, repeat_enter_pin
    bra pin_accepted_or_no_pin

    ; если пин-код неправильный, зачем вводить его снова?
    ; SIM-карта будет заблокирована!
repeat_enter_pin:
    ATCMD0(at_cpin_2000 + 1)     ; вводим пин-код "2000"
    rcall clear_buffer
    delay(#1000)
    if_buffer_contains(ok + 1, pin_accepted_or_no_pin)
    bra repeat_enter_pin

pin_accepted_or_no_pin:         ; пин-код принят
    movlf(STATE_PIN_OK, state)

exit:
    ulnk
    return
    .ends


function_register:
    lnk #0x16

    IFNE(state, STATE_PIN_OK, wrong_state)

    ; ожидание регистрации в сети
registration_loop:
    ATCMD(at_creg)
    blink
    unless_buffer_contains(creg_01 + 1, registration_loop)

    delay(#500)

    ; чтение трех номеров из телефонной книги SIM карты

    rcall clear_buffer

    movlf(FALSE, active1)
    movlf(FALSE, active2)
    movlf(FALSE, active3)

    ATCMD0(at_cpbs_sm)
    delay(#2000)

    read_parse_phonebook(at_cpbr_1 + 1, phone1, active1)
    read_parse_phonebook(at_cpbr_2, phone2, active2)
    read_parse_phonebook(at_cpbr_3, phone3, active3)

    ATCMD0(at_cmgf_1)            ; переключаем SIM900 в текстовый режим

    led_on                      ; мигаем
    delay(#2000)
    led_off
    delay(#2000)

    ; пин MICLEVEL управляет выбором чувствительности микрофона
    mov.b MIC_PORT, wreg
    and.b w0, #MASK(MICLEVEL), w0
    CP0_W0
    bra nz, mic_sensitivity_high

    ATCMD0(at_cmic00)            ; установить низкую чувствительность микрофона

mic_sensitivity_high:
    mov.b MIC_PORT, wreg
    and.b w0, #MASK(MICLEVEL), w0
    CP0_W0
    bra z, mic_sensitivity_low

    ATCMD0(at_cmic015)           ; установить высокую чувствительность микрофона

mic_sensitivity_low:

    ; отправка SMS по каждому номеру
    ; успешность отправки SMS не проверяется!

    IFNE(active1, TRUE, skip_number1_sms_ready)
    delay(#1000)
    SEND_SMS(#phone1, Ready + 1, i)

skip_number1_sms_ready:
    IFNE(active2, TRUE, skip_number2_sms_ready)
    delay(#5000)
    SEND_SMS(#phone2, Ready + 1, i)

skip_number2_sms_ready:
    IFNE(active3, TRUE, skip_number3_sms_ready)
    delay(#5000)
    SEND_SMS(#phone3, Ready + 1, i)

skip_number3_sms_ready:
    delay(#1000)

    bclr IFS1, #CNIF            ; Change Notice Interrupt Flag

    mov #HIREG(IPC4), w1        ; INTERRUPT PRIORITY CONTROL REGISTER 4
    mov.b [w1], w1
    mov_w0(0x8f)                ; clear CNIP<2:0>
    and.b w1, w0, w0
    mov_w1(0x40)                ; set CNIP<2:0> to 100 - priority 4? (82)
    ior.b w0, w1, w0
    mov.b wreg, HIREG(IPC4)

    bset IEC1, #T1IE            ; Timer1 Interrupt Enable bit
    bset CNEN2, #CN22IE
    bset CNEN1, #CN8IE
    bset CNPU1, #CN8PUE

    movlf(FALSE, lowbat)
    movlf(2, pollcnt)

    delay(#1000)

    for (k, repeat, repeat)
    ; k++
    inco(w14, k)

    mov [w14+k], w0
    sub w0, #20, [w15]
    bra le, local11

    movlo(0, k)

    ; получить заряд батареи
    ATCMD(at_cbc + 1)
    delay(#500)

    ; проверяем наличие чего-то, похожего на ответ
    unless_buffer_contains(cbc, skip_sms_lowbat)

    ; позиция "ответа"
    invoke_strnpos(cbc)
    mov.b w0, [w14+cbcpos]

    ; преобразуем строку в число
    get_digit(14, w2)
    get_digit(13, w5)
    get_digit(12, w4)
    get_digit(11, w0)
    mov.b w2, w3
    mov.b w5, w2
    mov.b w4, w1
    rcall function_atoi

    ; напряжение 3.5В
    ; temp = 3500 - atoi(cbcpos + 11)
    mov w0, w1
    mov #3500, w0
    sub w1, w0, [w15]
    bra gtu, more3_5V

    movlf(TRUE, lowbat)

    mov lbsent, w0
    sub w0, #1, [w15]
    bra gtu, skip_sms_lowbat

    incf(lbsent)

    IFNE(active1, TRUE, skip_number1_sms_lowbat)
    SEND_SMS(#phone1, Low_battery, i)

skip_number1_sms_lowbat:
    IFNE(active2, TRUE, skip_number2_sms_lowbat)
    delay(#5000)
    SEND_SMS(#phone2, Low_battery, i)

skip_number2_sms_lowbat:
    IFNE(active3, TRUE, skip_number3_sms_lowbat)
    delay(#5000)
    SEND_SMS(#phone3, Low_battery, i)

skip_number3_sms_lowbat:
    delay(#1000)
    bra skip_sms_lowbat

more3_5V:
    movlf(FALSE, lowbat)

skip_sms_lowbat:
    rcall clear_buffer

local11:
    delay(#380)
    delay(#380)

    incf(pollcnt)

    delay(#380)
    delay(#380)
    delay(#380)

    led_on

    delay(#380)
    delay(#380)

    led_off

    ; входящий звонок?
    unless_buffer_contains(ring, local4)

    ; определить номер
    ATCMD(at_clcc + 1)

    delay(#380)
    delay(#380)
    delay(#380)
    delay(#380)
    delay(#380)

    for(m, local1, local2)
        erase(callee, m)
    endfor(m, PHONE_LEN, local1, local2)

    ; ошибка: даже если номер не определен,
    ; сравнение выполняется
    unless_buffer_contains(clcc + 1, check_phone)

    invoke_strnpos(clcc + 1)

    ; callee_offset = strnpos(clcc) + CLCC_PHONE_OFFSET
    ze w0, w0
    add w0, #CLCC_PHONE_OFFSET, w0
    mov w0, [w14+callee_offset]
    ; m = 0
    movlo(0, m)
    bra local3

local5:
    ; buffer[callee_offset + m] == '"'?
    mov [w14+callee_offset], w1
    mov [w14+m], w0
    add w1, w0, w1
    mov #buffer, w0
    add w1, w0, w0
    mov.b [w0], w1
    mov_w0('"')
    sub.b w1, w0, [w15]
    ; закрывающая кавычка
    bra z, check_phone

    ; копируем номер звонящего:
    ; callee[m] = buffer[callee_offset + m]
    mov [w14+m], w2
    mov [w14+callee_offset], w1
    mov [w14+m], w0
    add w1, w0, w1
    mov #buffer, w0
    add w1, w0, w0
    mov.b [w0], w1
    mov #callee, w0
    add w2, w0, w0
    mov.b w1, [w0]

    endfor(m, PHONE_LEN, local3, local5)

check_phone:
    ; звонит владелец?

    ; search_result = FALSE
    movlo(FALSE, search_result)

    ; 1. если сигнатура CLCC не обнаружена, в callee будет номер предыдущего звонка
    ; (почти всегда номер хозяина)
    ; 2. то же при пустом номере "" (запрет определения номера)

    check_number(active1, phone1)
    check_number(active2, phone2)
    check_number(active3, phone3)

    mov [w14+search_result], w0
    sub w0, #TRUE, [w15]
    bra nz, drop_call

    ; поднять трубку
    ATCMD(ata)

    mul.uu w0, #0, w0
    mov w0, initialized_data
    mov w1, tm4h
    bclr IFS0, #T3IF
    bset IEC0, #T3IE

    movlf(TRUE, calling)

    bra local4

drop_call:
    ; сбросить звонок
    ATCMD0(ath)

local4:
    ; tm4h:initialized_data < 0xf00?
    mov initialized_data, w2
    mov tm4h, w3
    mov #15, w0
    mov #0, w1
    sub w2, w0, [w15]
    subb w3, w1, [w15]
    bra leu, not_calling

    IFNE(calling, TRUE, not_calling)

    mov.b MIC_PORT, wreg
    and.b w0, #MASK(MICLEVEL), w0
    CP0_W0
    bra nz, not_calling

    ; повесить трубку
    ATCMD(ath)

    ; сбросить флаг активного звонка
    movlf(0, calling)

    bclr IFS0, #T3IF
    bclr IEC0, #T3IE

not_calling:
    ; нет связи?
    unless_buffer_contains(no_carrier, local7)
    rcall clear_buffer

    bclr IFS0, #T3IF
    bclr IEC0, #T3IE

    ; сбросить флаг активного звонка
    movlf(0, calling)

local7:
    unless_buffer_contains(ath2 + 1, check_alert)
    rcall clear_buffer

    bclr IFS0, #T3IF
    bclr IEC0, #T3IE

    ; сбросить флаг активного звонка
    movlf(0, calling)

check_alert:
    IFNE(alert, TRUE, dont_call)

    ; сбрасываем флаг тревоги
    movlf(0, alert)

    IFNE(active1, TRUE, number1_inactive)

    ; звоним по первому номеру
    CALL(phone1, 4)
    bra call_done

number1_inactive:
    IFNE(active2, TRUE, number2_inactive)

    delay(#5000)
    CALL(phone2, 2)
    bra call_done

number2_inactive:
    IFNE(active3, TRUE, call_done)

    delay(#5000)

    ; звоним по третьему номеру
    CALL(phone3, 0)

call_done:
    delay(#1000)

dont_call:
    IFNE(charged, TRUE, repeat)

    movlf(0, charged)

    IFNE(active1, TRUE, sms_number1_inactive)
    SEND_SMS(#phone1, High_battery + 1, j)

sms_number1_inactive:
    IFNE(active2, TRUE, sms_number2_inactive)
    delay(#5000)
    SEND_SMS(#phone2, High_battery + 1, j)

sms_number2_inactive:
    IFNE(active3, TRUE, sms_number3_inactive)
    delay(#5000)
    SEND_SMS(#phone3, High_battery + 1, j)

sms_number3_inactive:
    delay(#1000)
    bra repeat

wrong_state:
    ulnk
    return


#include "itoa_digit.asm"


; прерывание от охранного шлейфа
interrupt_sensor:
    .scope
    push RCOUNT
    push.d w0
    push.d w2
    push.d w4
    push.d w6
    push PSVPAG

    mov #0, w0
    mov w0, PSVPAG

    lnk #0x00

    ; Input Change Notification Interrupt Flag Status bit
    bclr IFS1, #CNIF

    ; контроль охранного шлейфа сигнализации
    mov.b HIREG(SENSOR_PORT), wreg
    and.b w0, #HIMASK(SENSOR), w0
    CP0_W0
    bra z, sensor_ok

    ; за это время можно и успеть восстановить шлейф (быстро войти и закрыть дверь)
    delay(#600)

    mov.b HIREG(SENSOR_PORT), wreg
    and.b w0, #HIMASK(SENSOR), w0
    CP0_W0
    bra z, sensor_ok

    ; тревога!
    movlf(TRUE, alert)

sensor_ok:

    ; аккумулятор полностью заряжен?
    mov.b CHARGE_PORT, wreg
    and.b w0, #CHARGE, w0
    CP0_W0
    bra nz, exit

    delay(#600)

    mov.b CHARGE_PORT, wreg
    and.b w0, #CHARGE, w0
    CP0_W0
    bra nz, exit

    ; аккумулятор заряжен
    movlf(TRUE, charged)

exit:
    ulnk
    pop PSVPAG
    pop.d w6
    pop.d w4
    pop.d w2
    pop.d w0
    pop RCOUNT
    retfie
    .ends

initialized_data:

; инициализированные данные
#include "idata2.asm"

; все неиспользуемые прерывания
interrupt_reset:
    reset

uart.asm

; антипаттерн работы с последовательным портом
interrupt_uart:                 ; оригинальный адрес 0x0748
    .scope

    push.d w0
    push PSVPAG
    mov #0, w0
    mov w0, PSVPAG
    lnk #0x02

    ; прерывание обработано
    ; бит нужно сбрасывать в конце обработчика, а не в начале
    ; https://www.joelw.id.au/PicMcuTips
    bclr IFS0, #U1RXIF

    ; этот цикл - источник зависаний
    ; не проверяются FERR, PERR, OERR
wait:
    mov.b U1STA, wreg
    ; Receive buffer has data; at least one more character can be read
    and.b w0, #MASK(URXDA), w0
    CP0_W0
    bra z, wait

    ; принятый байт (а в буфере USART могут быть еще)
    mov U1RXREG, w0

    ; временно сохраняем
    mov.b w0, [w14]

    ; buffer[bufptr++] = c
    mov bufptr, w1
    mov #buffer, w0
    add w1, w0, w0
    mov.b [w14], [w0]

    incf(bufptr)

    ; буфер не заполнен?
    IFNE2(bufptr, BUFSZ, end)

    ; первые байты перезаписываются новыми,
    ; что может привести к интересным уязвимостям
    movlf(0, bufptr)

end:
    ulnk
    pop PSVPAG
    pop.d w0
    retfie
    .ends

transmit_char:
    lnk #0x02
    mov.b w0, [w14]

    mov.b U1STA + 1, wreg
    and.b w0, #2, w0
    CP0_W0
    bra nz, $-6

    se [w14], w0
    mov w0, U1TXREG

    ulnk
    return

setup_uart:
    lnk #0x00

    movlf(103, U1BRG)           ; Baud Rate Generator Prescaler Register (160)
    movlf(0, U1MODE)
    bset U1MODE, #BRGH
    bset U1MODE, #UARTEN        ; UARTx Enable bit (162)

    ; URXISEL = 0
    ; Interrupt is set when any character is received and transferred from the
    ; RSR to the receive buffer;
    ; receive buffer has one or more characters (164)
    movlf(0, U1STA)

    ; Transmit is enabled, UxTX pin is controlled by UARTx (164)
    bset U1STA, #UTXEN
    bclr IFS0, #U1RXIF

    bset IEC0, #T4IE            ; разрешить прерывания от таймера 4

    ulnk
    return

transmit_at_command:
    .scope
    lnk #0x02
    mov w0, [w14]
    bra loop

next_char:
    mov.b U1STA + 1, wreg
    and.b w0, #2, w0
    CP0_W0
    bra nz, next_char
    mov [w14], w0
    mov.b [w0], w0
    se w0, w0
    mov w0, U1TXREG
    inc [w14], [w14]

loop:
    mov [w14], w0
    mov.b [w0], w0
    CP0_W0
    bra nz, next_char

    ulnk
    return
    .ends

macro.asm

;===============================================================================
; Макросы

#include "naken.asm"

.macro for(var, label_start, label_next)
    movlo(0, var)
    bra label_start
label_next:
.endm

.macro endfor(var, limit, label_start, label_next)
    inco(w14, var)
label_start:
    .if var == 0
        mov #limit, w0
        subr w0, [w14], [w15]
    .else
        mov [w14+var], w1
        mov #limit, w0
        sub w1, w0, [w15]
    .endif
    bra le, label_next
.endm

; for до конца строки
.macro endforc(buf, pos, label_start, label_next)
    inco(w14, pos)
label_start:
    getc(buf, pos)
    add.b w0, #1, [w15]
    bra nz, label_next
.endm

.macro endforc2(buf, pos, limit, label_start, label_next)
    inco(w14, pos)
label_start:
    getc(buf, pos)
    add.b w0, #1, [w15]
    bra z, break
    IFLEO(w14, pos, limit, label_next)
break:
.endm

.macro inc16(high, low)
    mov low, w0
    mov high, w1
    add w0, #1, w0
    addc w1, #0, w1
    mov w0, low
    mov w1, high
.endm

.macro erase(buf, var)
    movor(w14, var, w1)
    mov #buf, w0
    add w1, w0, w1
    ; 0xff
    setm.b w0
    mov.b w0, [w1]
.endm

; reg = base[pos]
.macro getc_(base, pos, reg)
    movor(w14, pos, w1)
    mov base, w0
    add w1, w0, w0
    mov.b [w0], reg
.endm

; w0 = base[pos]
.macro getc(base, pos)
    getc_(base, pos, w0)
.endm

.macro delay(n)
    mov n, w0
    rcall function_delay
.endm

; отправить AT-команду без предварительной очистки буфера ответа
.macro ATCMD0(cmd)
    mov LDM(cmd), w0
    rcall transmit_at_command
.endm

.macro ATCMD(cmd)
    rcall clear_buffer
    ATCMD0(cmd)
.endm

; отправить символ
.macro SEND(char)
    mov_w0(char)
    rcall transmit_char
.endm

.macro SEND_SMS(number, text, ii)
.scope
    ; AT+CMGS="recipient_number"
    ATCMD0(at_cmgs + 1)

    for(ii, start, next)
        getc(number, ii)
        rcall transmit_char
    endforc(number, ii, start, next)

    ATCMD0(Ready - 1)
    SEND('\r')
    SEND('\n')

    delay(#1000)

    ATCMD0(text)

    ; ^Z - символ конца сообщения
    SEND(0x1a)
.ends
.endm

.macro led_on
    bset LED_PORT, #LED
.endm

.macro led_off
    bclr LED_PORT, #LED
.endm

.macro blink
    led_on
    delay(#250)
    led_off
    delay(#250)
.endm

.macro IBLINK
    mov.b LED_PORT, wreg        ; мигаем
    ze w0, w0
    lsr w0, #LED, w0
    and.b w0, #1, w0
    com.b w0, w0
    and.b w0, #1, w0
    and.b w0, #1, w0            ; что за ерунда?
    sl w0, #LED, w2
    mov #LED_PORT, w1
    mov.b [w1], w1
    mov_w0(~MASK(LED))
    and.b w1, w0, w0
    ior.b w0, w2, w0
    mov.b wreg, LED_PORT
.endm

.macro CALL(number, tmp)
.scope
    ATCMD0(atd + 1)

    for(tmp, start, next)
        getc(#number, tmp)
        rcall transmit_char
    endforc2(#number, tmp, PHONE_LEN, start, next)

    ATCMD0(tail + 1)
.ends
.endm

.macro invoke2(func, arg1, arg2)
    mov arg1, w0
    mov arg2, w1
    rcall initialize
.endm

.macro invoke_strnpos(str)
    mov LDM(str), w1
    mov #BUFSZ, w2
    mov #buffer, w0
    rcall strnpos
.endm

.macro unless_buffer_contains(str, label)
    invoke_strnpos(str)
    CP0_W0
    bra lt, label
.endm

.macro if_buffer_contains(str, label)
    invoke_strnpos(str)
    CP0_W0
    bra ge, label
.endm

; читать номер из записной книги SIM карты
.macro read_parse_phonebook(cmd, number, flag_active)
    .scope
    ATCMD(cmd)
    delay(#1500)

    ; заполнить номер 0xff
    for(i, start1, next1)
        erase(number, i)
    endfor(i, PHONE_LEN, start1, next1)

    ; проверка корректности ответа
    unless_buffer_contains(cpbr + 1, error)

    invoke_strnpos(cpbr + 1)

    ze w0, w0
    add w0, #10, w0
    mov w0, [w14+pbk]

    for (i, start, next)
        mov [w14+pbk], w1
        mov [w14+i], w0
        add w1, w0, w1
        mov #buffer, w0
        add w1, w0, w0
        mov.b [w0], w1

        ; до закрывающей кавычки
        mov_w0('"')
        sub.b w1, w0, [w15]
        bra z, break

        ; сохраняем номер в number
        mov [w14+i], w2
        mov [w14+pbk], w1
        mov [w14+i], w0
        add w1, w0, w1
        mov #buffer, w0
        add w1, w0, w0
        mov.b [w0], w1
        mov #number, w0
        add w2, w0, w0
        mov.b w1, [w0]
    endfor(i, PHONE_LEN, start, next)

break:
    ; первый номер прочитан с SIM карты, пометить как активный
    movlf(TRUE, flag_active)
error:
    delay(#2000)
    .ends
.endm

.macro check_number(flag_active, number)
    .scope
    mov flag_active, w0
    sub w0, #TRUE, [w15]
    bra nz, exit

    for(m, start, next)
        ; number[m] == callee[m]?
        getc_(#number, m, w2)
        getc_(#callee, m, w0)
        sub.b w2, w0, [w15]
        bra nz, break
    endfor(m, PHONE_LEN, start, next)

break:
    ; number[m] != callee[m]
    mov [w14+m], w1
    mov #PHONE_LEN + 1, w0
    sub w1, w0, [w15]
    bra nz, exit

    movlo(TRUE, 6)

exit:
    .ends
.endm

.macro get_digit(ndigit, dest)
    mov.b [w14+cbcpos], w0
    ze w0, w0
    add w0, #ndigit, w1
    mov #buffer, w0
    add w1, w0, w0
    mov.b [w0], dest
.endm

utils.asm

clear_buffer:
    .scope
    lnk #0x02

    for(0, check_cond, repeat)
        erase(buffer, 0)
    endfor(0, BUFSZ, check_cond, repeat)

    movlf(0, bufptr)

    ulnk
    return
    .ends

function_delay:
    .scope
    lnk #0x02

    mov w0, [w14]

    movlf(0x8000, T2CON)
    bra start

loop:
    movlf(0, TMR2)
inner:
    mov TMR2, w1
    mov #3999, w0
    sub w1, w0, [w15]
    bra leu, inner
start:
    dec [w14], [w14]
    setm w0
    subr w0, [w14], [w15]
    bra nz, loop

    ulnk
    return
    .ends

; не используется
timer_delay:
    .scope
    lnk #0x02
    mov w0, [w14]

    movlf(0x8000, T2CON)

    bra start

repeat:
    movlf(0, TMR2)

wait:
    mov TMR2, w1
    mov #PHONE_LEN, w0
    sub w1, w0, [w15]
    bra leu, wait

start:
    dec [w14], [w14]
    setm w0
    subr w0, [w14], [w15]
    bra nz, repeat

    ulnk
    return
    .ends

strnpos:
.scope
    lnk #0x0c

    mov w0, [w14+4]             ; haystack
    mov w1, [w14+6]             ; needle
    mov w2, [w14+8]             ; максимальная длина (i + j)

    for(2, check, nextchar)  ; i (2) - позиция в haystack
    movlo(0, 0)                 ; j (0) - позиция в needle
    bra compare

match:
    inco(w14, 0)                ; j++

    ; needle[j] == '\0'?
    mov [w14], w1
    mov [w14+6], w0
    add w1, w0, w0
    mov.b [w0], w0
    CP0_W0
    bra nz, more

    ; нашли needle
    ; возвращаем i - позицию в haystack
    mov [w14+2], w0
    ze w0, w0
    mov w0, [w14+10]
    bra exit

more:
    ; еще не конец needle
    mov [w14+2], w0
    add w0, [w14], w1           ; i + j
    mov [w14+8], w0             ; максимальная длина
    sub w1, w0, [w15]
    bra ltu, compare

    ; подстрока не найдена
    ; достигнут предел суммы позиций в haystack и needle
    mov #0xff, w0
    mov w0, [w14+10]
    bra exit

compare:
    mov [w14+2], w0             ; i
    add w0, [w14], w0           ; j
    mov w0, w1

    mov [w14+4], w0             ; haystack
    add w1, w0, w0
    mov.b [w0], w2              ; w2 = haystack[i + j]

    mov [w14], w1               ; j
    mov [w14+6], w0             ; needle
    add w1, w0, w0
    mov.b [w0], w0              ; w0 = needle[j]

    sub.b w2, w0, [w15]
    bra z, match

    ; символы не совпали
    ; i++ (следующая позиция в haystack)
    inco(w14, 2)

check:
    ; достигли конца haystack?
    mov [w14+2], w1             ; i
    mov [w14+8], w0             ; предел haystack
    sub w1, w0, [w15]
    bra leu, nextchar

    ; конец haystack
    movlo(0xff, 10)             ; -1 - не найдено

exit:
    mov [w14+10], w0            ; результат
    ulnk
    return
.ends

at.asm

org 0x290
    ; AT команды и ответы SIM900

.align_bits 8
at:
    ; "AT\r\n"
    db "AT",0,0,"\r\n",0,0,0

ok:
    ; "OK"
    db 'O',0,0,'K',0,0,0

at_cpin:
    ; "AT+CPIN?\r\n"
    db "AT",0,0,"+C",0,0,"PI",0,0,"N?",0,0,"\r\n",0,0,0

cpin_sim_pin:
    ; "+CPIN: SIM PIN"
    db '+',0,0,"CP",0,0,"IN",0,0,": ",0,0,"SI",0,0,"M ",0,0,"PI",0,0,'N',0,0,0

cpin_ready:
    ; "+CPIN: READY"
    db "+C",0,0,"PI",0,0,"N:",0,0," R",0,0,"EA",0,0,"DY",0,0,0

at_cpin_2000:
    ; "AT+CPIN=2000\r\n"
    db 'A',0,0,"T+",0,0,"CP",0,0,"IN",0,0,"=2",0,0,"00",0,0,"0\r",0,0,'\n',0,0,0

at_creg:
    ; "AT+CREG?\r\n"
    db "AT",0,0,"+C",0,0,"RE",0,0,"G?",0,0,"\r\n",0,0,0

creg_01:
    ; "+CREG: 0,1"
    db '+',0,0,"CR",0,0,"EG",0,0,": ",0,0,"0,",0,0,'1',0,0,0

at_cpbs_sm:
    ; AT+CPBS="SM"\r\n
    db "AT",0,0,"+C",0,0,"PB",0,0,"S=",0,0,"\"S",0,0,"M\"",0,0,"\r\n",0,0,0

at_cpbr_1:
    ; "AT+CPBR=1\r\n"
    db 'A',0,0,"T+",0,0,"CP",0,0,"BR",0,0,"=1",0,0,"\r\n",0,0,0

cpbr:
    ; "+CPBR:"
    db '+',0,0,"CP",0,0,"BR",0,0,':',0,0,0

at_cpbr_2:
    ; "AT+CPBR=2\r\n"
    db "AT",0,0,"+C",0,0,"PB",0,0,"R=",0,0,"2\r",0,0,'\n',0,0,0

at_cpbr_3:
    ; "AT+CPBR=3\r\n"
    db "AT",0,0,"+C",0,0,"PB",0,0,"R=",0,0,"3\r",0,0,'\n',0,0,0

at_cmgf_1:
    ; AT+CMGF=1\r\n
    db "AT",0,0,"+C",0,0,"MG",0,0,"F=",0,0,"1\r",0,0,'\n',0,0,0

; AT+CMIC=<channel>,<gainlevel>
; https://www.espruino.com/datasheets/SIM900_AT.pdf
; <channel> 0 main audio handset channel
; 1 aux audio headset channel
; 2 main audio handfree channel
; 3 aux audio handfree channel
; <gainlevel> int: 0-15

; animals:
; на канале 1 чувствительность кажется получше, но качество звука идеальное, чистый без искажений звук
; на 2 канале отличий не увидел, всё также как и канале 0

at_cmic00:
    ; "AT+CMIC=0,0\r\n"
    db "AT",0,0,"+C",0,0,"MI",0,0,"C=",0,0,"0,",0,0,"0\r",0,0,'\n',0,0,0

at_cmic015:
    ; "AT+CMIC=0,15\r\n"
    db "AT",0,0,"+C",0,0,"MI",0,0,"C=",0,0,"0,",0,0,"15",0,0,"\r\n",0,0,0

at_cmgs:
    ; AT+CMGS="\x00"
    db 'A',0,0,"T+",0,0,"CM",0,0,"GS",0,0,"=\"",0,0,"\0\"",0,0,0

Ready:
    ; "Ready"
    db 'R',0,0,"ea",0,0,"dy",0,0,0

at_cbc:
    ; "AT+CBC\r\n"
    db 'A',0,0,"T+",0,0,"CB",0,0,"C\r",0,0,'\n',0,0,0

cbc:
    ; "+CBC:"
    db "+C",0,0,"BC",0,0,':',0,0,0

Low_battery:
    ; "Low battery"
    db "Lo",0,0,"w ",0,0,"ba",0,0,"tt",0,0,"er",0,0,'y',0,0,0

ring:
    ; "RING"
    db "RI",0,0,"NG",0,0,0

at_clcc:
    ; "AT+CLCC\r\n"
    db 'A',0,0,"T+",0,0,"CL",0,0,"CC",0,0,"\r\n",0,0,0

clcc:
    ; "+CLCC:"
    db '+',0,0,"CL",0,0,"CC",0,0,':',0,0,0

ata:
    db "AT",0,0,"A\r",0,0,'\n',0,0,0

ath:
    ; "ATH\r\n"
    db "AT",0,0,"H\r",0,0,'\n',0,0,0

no_carrier:
    ; "NO CARRIER"
    db "NO",0,0," C",0,0,"AR",0,0,"RI",0,0,"ER",0,0,0

; дубликат строки
ath2:
    ; "ATH"
    db 'A',0,0,"TH",0,0,0

atd:
    ; "ATD"
    db 'A',0,0,"TD",0,0,0

tail:
    ; ";\x00\r\n"
    db ";\0",0,"\r\n",0,0,0

High_battery:
    ; "High battery 100%"
    db 'H',0,0,"ig",0,0,"h ",0,0,"ba",0,0,"tt",0,0,"er",0,0,"y ",0,0,"10",0,0,"0%",0,0,0
    db 0,0,0

atoi.asm (преобразование строки в число)

.macro atoi_case(n, offset, label, nobra)
    movl(n, w0)
    movro(w0, w14, offset)
    .if nobra == 0
        bra label
    .endif
.endm

; avoid naken_asm 1024 chars macro limit
.macro atoi_subround(label, tmp1, tmp2, tmp3)
    ; select
    mov [w14+tmp1], w0
    mov [w14+tmp2], w1
    bra w0

    ; case
    bra $+20
    bra $+2*(10+1*2)
    bra $+2*(10+2*2)
    bra $+2*(10+3*2)
    bra $+2*(10+4*2)
    bra $+2*(10+5*2)
    bra $+2*(10+6*2)
    bra $+2*(10+7*2)
    bra $+2*(10+8*2)
    bra $+2*(10+9*2)

    atoi_case(0, tmp3, label, 0)
    atoi_case(1, tmp3, label, 0)
    atoi_case(2, tmp3, label, 0)
    atoi_case(3, tmp3, label, 0)
    atoi_case(4, tmp3, label, 0)
    atoi_case(5, tmp3, label, 0)
    atoi_case(6, tmp3, label, 0)
    atoi_case(7, tmp3, label, 0)
    atoi_case(8, tmp3, label, 0)
    atoi_case(9, tmp3, label, 1)
.endm

.macro atoi_round(tmp0, tmp1, tmp2, tmp3, label)
    mov.b [w14+tmp0], w0
    ze w0, w0
    mul.su w0, #1, w2
    mov #-'0', w0
    mov #-1, w1
    add w0, w2, w2
    addc w1, w3, w3
    mov w2, [w14+tmp1]
    mov w3, [w14+tmp2]
    mov #9, w0
    mov #0, w1
    mov [w14+tmp1], w2
    mov [w14+tmp2], w3
    sub w2, w0, [w15]
    subb w3, w1, [w15]
    bra gtu, label

    atoi_subround(label, tmp1, tmp2, tmp3)

label:
.endm

function_atoi:
    .scope
    lnk #0x1c
    mov.b w0, [w14+8]
    mov.b w1, [w14+9]
    mov.b w2, [w14+10]
    mov.b w3, [w14+11]

    atoi_round(8, 12, 14, 6, overflow1)
    atoi_round(9, 16, 18, 4, overflow2)
    atoi_round(10, 20, 22, 2, overflow3)
    atoi_round(11, 24, 26, 0, overflow4)

    mov [w14+6], w1
    mov #1000, w0
    mul.ss w1, w0, w0
    mov w0, w2
    mov [w14+4], w1
    mov #100, w0
    mul.ss w1, w0, w0
    add w2, w0, w2
    mov [w14+2], w0
    mul.su w0, #10, w0
    add w2, w0, w0
    add w0, [w14], w0

    ulnk
    return
    .ends

naken.asm

.macro sleep_
    ; unknown opcode
    db 0, 0x40, 0xda, 0
.endm

; константа - регистр
.macro movl(value, reg)
    .if (value) == 0
        clr reg
    .else
        mov #(value), reg
    .endif
.endm

; константа - файл
.macro movlf(value, file)
    movl(value, w0)
    mov w0, (file)
.endm

; [reg] and [reg+0] are not the same
.macro movro(src, dst, offset)
    .if (offset) == 0
        mov src, [dst]
    .else
        mov src, [dst+offset]
    .endif
.endm

.macro movor(src, offset, dst)
    .if (offset) == 0
        mov [src], dst
    .else
        mov [src+offset], dst
    .endif
.endm

.macro movlo(value1, var1)
    movl(value1, w0)
    movro(w0, w14, var1)
.endm

.macro inco(reg, offset)
    .if offset == 0
        inc [reg], [reg]
    .else
        mov [reg+offset], w0
        inc w0, w0
        mov w0, [reg+offset]
    .endif
.endm

; если [reg+offset] < n, перейти на label
.macro IFLEO(reg, offset, n, label)
    .if offset == 0
        mov #n, w0
        subr w0, [reg], [w15]
    .else
        mov [reg+offset], w1
        mov #n, w0
        sub w1, w0, [w15]
    .endif
    bra le, label
.endm

.macro incf(file)
    mov file, w0
    inc w0, w0
    mov w0, file
.endm

; naken_asm bugs fix

;cp0 w0
.macro CP0_W0
    db 0, 4, 0xe0, 0
.endm

; mov #b, w0
.macro mov_w0(b)
    db (b & 0x0f) << 4, 0xc0 | ((b >> 4) & 0x0F), 0xb3, 0
.endm

; mov #b, w1
.macro mov_w1(b)
    db ((b & 0x0f) << 4) | 1, 0xc0 | ((b >> 4) & 0x0F), 0xb3, 0
.endm

; если file не равно value, перейти на метку label
.macro IFNE(file, value, label)
    mov (file), w0
    sub w0, #(value), [w15]
    bra nz, (label)
.endm

.macro IFNE2(file, value, label)
    mov (file), w1
    mov #(value), w0
    sub w1, w0, [w15]
    bra nz, (label)
.endm

PIC24FJ64GA004.inc

;===============================================================================
; Самодельный PIC24FJ64GA004 для naken_asm
; см. include/dspic/convert.py для преобразования include-файлов
; из формата MPLAB в формат naken_asm (добавлено вручную - возможны ошибки!)

RPOR11  equ 0x06d6
RPINR18 equ 0x06a4
AD1PCFG equ 0x032c

PORTB   equ 0x02CA
RB14    equ 14          ; pin 14
RB8     equ 8           ; pin 44
RB2     equ 2           ; pin 23
RB0     equ 0           ; pin 21

TRISB   equ 0x02C8
TRISB14 equ 14
TRISB2  equ 2

PORTC   equ 0x02D2
RC9     equ 9           ; pin 5
RC1     equ 1           ; pin 26

TRISC   equ 0x02D0
TRISC9  equ 9

U1BRG   equ 0x0228
U1RXREG equ 0x0226
U1TXREG equ 0x0224

U1STA   equ 0x0222
UTXEN   equ 10
URXDA   equ 0

U1MODE  equ 0x0220
UARTEN  equ 15
BRGH    equ 3

T4CON   equ 0x011E
PR4     equ 0x011A
T3CON   equ 0x0112
T2CON   equ 0x0110
TMR2    equ 0x0106

TON     equ 0x8000
TCKPS0  equ 16
TCKPS1  equ 32

PR3     equ 0x010E

IPC4    equ 0x00AC

IEC1    equ 0x0096
T4IE    equ 11
T1IE    equ 3

IEC0    equ 0x0094
T3IE    equ 8

IFS1    equ 0x0086
T4IF    equ 11
CNIF    equ 3

IFS0    equ 0x0084
U1RXIF  equ 11
T3IF    equ 8

CNPU1   equ 0x0068
CN4PUE  equ 4
CN8PUE  equ 8

CNEN1   equ 0x0060
CN8IE   equ 8

CNEN2   equ 0x0062
CN22IE  equ 6

CORCON  equ 0x0044
PSV     equ 2

RCOUNT  equ 0x0036
PSVPAG  equ 0x0034
TBLPAG  equ 0x0032
SPLIM   equ 0x0020

idata.asm

; initialized data
initialize:
    .scope
    ; Table Memory Page Address Register
    mov w1, TBLPAG
    mov w0, w1
    clr w0
    bra local1

local2:
    add w1, #2, w1
    addc TBLPAG
    tblrdl [w1], w3
    add w1, #2, w1
    addc TBLPAG
    tblrdl [w1], w5
    add w1, #2, w1
    addc TBLPAG
    clr w4
    lsr w5, #7, w6
    and #0x7f, w5
    cp.b w5, #0
    bra nz, local3

loop:
    clr.b [w2++]
    dec w3, w3
    bra gtu, loop

    bra local1

local3:
    cp w5, #1
    bra z, skip
    setm w4

skip:
    rcall function_026c

local1:
    ; reading the lower word
    tblrdl [w1], w2
    cp0 w2
    bra nz, local2

    return
    .ends


function_026c:
    .scope
start:
    tblrdl.b [w1++], [w2++]
    dec w3, w3
    bra z, exit

    tblrdl.b [w1--], [w2++]
    dec w3, w3
    bra z, next

    cp0 w4
    bra nz, local1

local2:
    add w1, #2, w1
    addc TBLPAG
    bra start

local1:
    tblrdh.b [w1], [w2++]
    dec w3, w3
    bra nz, local2

next:
    inc w1, w1

exit:
    add w1, #1, w1
    addc TBLPAG
    return
    .ends

ivt.asm

    ; таблица векторов прерываний
    dd interrupt_reset, interrupt_reset, interrupt_reset, interrupt_reset
    dd interrupt_reset, interrupt_reset, interrupt_reset, interrupt_reset
    dd interrupt_reset, interrupt_reset, interrupt_reset, interrupt_reset
    dd interrupt_reset, interrupt_reset, interrupt_reset, interrupt_reset
    dd interrupt_timer3,interrupt_reset, interrupt_reset, interrupt_uart
    dd interrupt_reset, interrupt_reset, interrupt_reset, interrupt_reset
    dd interrupt_reset, interrupt_reset, interrupt_reset, interrupt_sensor
    dd interrupt_reset, interrupt_reset, interrupt_reset, interrupt_reset
    dd interrupt_reset, interrupt_reset, interrupt_reset, interrupt_timer4
    dd interrupt_reset, interrupt_reset, interrupt_reset, interrupt_reset
    dd interrupt_reset, interrupt_reset, interrupt_reset, interrupt_reset
    dd interrupt_reset, interrupt_reset, interrupt_reset, interrupt_reset
    dd interrupt_reset, interrupt_reset, interrupt_reset, interrupt_reset
    dd interrupt_reset, interrupt_reset, interrupt_reset, interrupt_reset
    dd interrupt_reset, interrupt_reset, interrupt_reset, interrupt_reset
    dd interrupt_reset, interrupt_reset, interrupt_reset, interrupt_reset
    dd interrupt_reset, interrupt_reset, interrupt_reset, interrupt_reset
    dd interrupt_reset, interrupt_reset, interrupt_reset, interrupt_reset
    dd interrupt_reset, interrupt_reset, interrupt_reset, interrupt_reset
    dd interrupt_reset, interrupt_reset, interrupt_reset, interrupt_reset
    dd interrupt_reset, interrupt_reset, interrupt_reset, interrupt_reset
    dd interrupt_reset, interrupt_reset, interrupt_reset, interrupt_reset
    dd interrupt_reset, interrupt_reset, interrupt_reset, interrupt_reset
    dd interrupt_reset, interrupt_reset, interrupt_reset, interrupt_reset
    dd interrupt_reset, interrupt_reset, interrupt_reset, interrupt_reset
    dd interrupt_reset, interrupt_reset, interrupt_reset, interrupt_reset
    dd interrupt_reset, interrupt_reset, interrupt_reset, interrupt_reset
    dd interrupt_reset, interrupt_reset, interrupt_reset, interrupt_reset
    dd interrupt_reset, interrupt_reset, interrupt_reset, interrupt_reset
    dd interrupt_reset, interrupt_reset, interrupt_reset, interrupt_reset
    dd interrupt_reset, interrupt_reset, interrupt_reset, interrupt_reset
    dd interrupt_reset, interrupt_reset, 0,                 0

    dd interrupt_reset, interrupt_reset, interrupt_reset, interrupt_reset
    dd interrupt_reset, interrupt_reset, interrupt_reset, interrupt_reset
    dd interrupt_reset, interrupt_reset, interrupt_reset, interrupt_reset
    dd interrupt_reset, interrupt_reset, interrupt_reset, interrupt_reset
    dd interrupt_timer3,interrupt_reset, interrupt_reset, interrupt_uart
    dd interrupt_reset, interrupt_reset, interrupt_reset, interrupt_reset
    dd interrupt_reset, interrupt_reset, interrupt_reset, interrupt_sensor
    dd interrupt_reset, interrupt_reset, interrupt_reset, interrupt_reset
    dd interrupt_reset, interrupt_reset, interrupt_reset, interrupt_timer4
    dd interrupt_reset, interrupt_reset, interrupt_reset, interrupt_reset
    dd interrupt_reset, interrupt_reset, interrupt_reset, interrupt_reset
    dd interrupt_reset, interrupt_reset, interrupt_reset, interrupt_reset
    dd interrupt_reset, interrupt_reset, interrupt_reset, interrupt_reset
    dd interrupt_reset, interrupt_reset, interrupt_reset, interrupt_reset
    dd interrupt_reset, interrupt_reset, interrupt_reset, interrupt_reset
    dd interrupt_reset, interrupt_reset, interrupt_reset, interrupt_reset
    dd interrupt_reset, interrupt_reset, interrupt_reset, interrupt_reset
    dd interrupt_reset, interrupt_reset, interrupt_reset, interrupt_reset
    dd interrupt_reset, interrupt_reset, interrupt_reset, interrupt_reset
    dd interrupt_reset, interrupt_reset, interrupt_reset, interrupt_reset
    dd interrupt_reset, interrupt_reset, interrupt_reset, interrupt_reset
    dd interrupt_reset, interrupt_reset, interrupt_reset, interrupt_reset
    dd interrupt_reset, interrupt_reset, interrupt_reset, interrupt_reset
    dd interrupt_reset, interrupt_reset, interrupt_reset, interrupt_reset
    dd interrupt_reset, interrupt_reset, interrupt_reset, interrupt_reset
    dd interrupt_reset, interrupt_reset, interrupt_reset, interrupt_reset
    dd interrupt_reset, interrupt_reset, interrupt_reset, interrupt_reset
    dd interrupt_reset, interrupt_reset, interrupt_reset, interrupt_reset
    dd interrupt_reset, interrupt_reset, interrupt_reset, interrupt_reset
    dd interrupt_reset, interrupt_reset, interrupt_reset, interrupt_reset
    dd interrupt_reset, interrupt_reset, interrupt_reset, interrupt_reset
    dd interrupt_reset, interrupt_reset, interrupt_reset, interrupt_reset

idata2.asm (инициализированные данные)

    dd unknown, 0x40,   2,      0
    dd 0,       0,      0,      0
    dd 0,       0,      0,      0
    dd 0,       0,      0,      0
    dd 0,       1,      0,      0
    dd 0,       1,      0,      0
    dd 0,       0x800,  0x9FE,  0
    dd 0

Подпишитесь на мой блог, не дайте фуфлыжникам шанса!

Все статьи →

© Copyright 2025 Badradio. All rights reserved.
Все права защищены. Копирование материалов сайта запрещено.
При цитировании ставить кликабельную ссылку на первоисточник.