;
;    Mecrimus-B - A firmware-only USB implementation for MSP430 microcontrollers
;    Copyright (C) 2012  Matthias Koch
;
;    This program is free software: you can redistribute it and/or modify
;    it under the terms of the GNU General Public License as published by
;    the Free Software Foundation, either version 3 of the License, or
;    (at your option) any later version.
;
;    This program is distributed in the hope that it will be useful,
;    but WITHOUT ANY WARRANTY; without even the implied warranty of
;    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;    GNU General Public License for more details.
;
;    You should have received a copy of the GNU General Public License
;    along with this program.  If not, see <http://www.gnu.org/licenses/>.
;  
  
  
  cpu msp430

  include "mspregister.asm"  ; Register definitions
  include "datastack.asm"    ; Defines a second stack in r4 used here only for printing messages
  
  org 0F000h                 ; Start somewhere... Target is msp430g2452 at the moment.


debug_messages equ 1 ; Soll eine LED Meldungen auf RS232-Art ausgeben ?
                     ; Define this if you want RS232 debug messages on P1.0

;------------------------------------------------------------------------------
; Leitungen und Elektrisches
; Wires and connections
;------------------------------------------------------------------------------

; Connections to Pins on Port 1:
; Simply the Anode of a LED on P1.0 for RS232 like transmissions.

; Connections to Pins on Port 2:

;syncled  equ 1 ; P2.0  Anode of LED that shows syncinc including 100 Ohms
;               ; P2.1  Cathode of this LED, soldered in this way with brighness measurements and Ledcomm in mind...
;               ;       Always driven low here.

toggle_syncled macro ; Preserve clock cycles if you want to remove this !
  xor.b #1, &P2OUT   ; Toggles Sync LED, good for triggering an oscilloscope and visual feedback
  endm

usbout equ P2OUT
usbdir equ P2DIR
usbin  equ P2IN

usbminus equ 4  ; P2.2 D- over 50 Ohms with 1.5 kOhm pullup, driven to simulate connect and disconnent by P2.5
usbplus  equ 8  ; P2.3 D+ over 50 Ohms

usbpullup equ 32 ; P2.5 


;------------------------------------------------------------------------------
; Konstanten, Pakete und Beschreibungen
; Constants, Packets and Descriptions
;------------------------------------------------------------------------------

; USB PID values
USB_PID_SETUP   equ 02dh
USB_PID_OUT     equ 0e1h
USB_PID_IN      equ 069h
USB_PID_DATA0   equ 0c3h
USB_PID_DATA1   equ 04bh
USB_PID_ACK     equ 0d2h
USB_PID_NAK     equ 05ah
USB_PID_STALL   equ 01eh

usb_pid_data0_xor_data1 equ 088h ; Zum Wechseln zwischen Data0/Data1

; Be careful to feed in an even number of bytes to ".byte" if you want to continue in next line, else the assembler will add a padding-zero.

usb_paket_ack: .byte 1, usb_pid_ack
usb_paket_nak: .byte 1, usb_pid_nak

usb_paket_data1_leer_status: .byte 3, 04Bh, 000h, 000h
usb_paket_get_status:        .byte 5, 04Bh, 0, 0, 0FEh, 04Fh

; This time it's a mouse.

Device_Descriptor:  .byte 18,   012h, 001h, 010h, 001h, 000h, 000h, 000h, 008h, 012h, 034h, 056h, 078h, 042h, 043h, 001h, 002h, 000h, 001h

Configuration_Descriptor_first9:   .byte  9,    9, 2,   34, 0,    1,    1,    0, 080h,   50
Configuration_Descriptor_complete: .byte 34,    9, 2,   34, 0,    1,    1,    0, 080h,   50
                                   .byte        9, 4, 0, 0, 1, 3, 1, 2, 0,        9
                                   .byte                                             021h,  010h, 001h,  0, 1, 34, 52, 0
                                   .byte        7, 5, 081h, 3, 004h, 000h, 00Ah

HID_Descriptor: .byte 52, 005h, 001h, 009h, 002h, 0A1h, 001h, 009h, 001h, 0A1h
                .byte     000h, 005h, 009h, 019h, 001h, 029h, 005h, 015h, 000h, 025h
                .byte     001h, 095h, 005h, 075h, 001h, 081h, 002h, 095h, 001h, 075h
                .byte     003h, 081h, 001h, 005h, 001h, 009h, 030h, 009h, 031h, 009h
                .byte     038h, 015h, 081h, 025h, 07Fh, 075h, 008h, 095h, 003h, 081h
                .byte     006h, 0C0h, 0C0h


String_Descriptor_0: .byte 4,   04, 03, 09, 04 ; English-US als Standardsprache

; 4 Bytes
; 3 = String Descriptor Type
; Lang-ID Code 0 in two Bytes... 

String_Descriptor_1: .byte 28,   28, 3,   77, 0,  97, 0,  116, 0,  116, 0,  104, 0,  105, 0,  97, 0,  115, 0,  32 ; Achte auf eine gerade Anzahl von Bytes ! Sonst 0-padding !
                     .byte                                                                                         0,  75, 0,  111, 0,  99, 0,  104, 0  ; Matthias Koch
String_Descriptor_2: .byte 22,   22, 3,   77, 0,  101, 0,  99, 0,  114, 0,  105, 0,  109, 0,  117, 0,  115, 0, 45 ; Achte auf eine gerade Anzahl von Bytes ! Sonst 0-padding !
                     .byte                                                                                         0,  66, 0 ; Mecrimus-B


; Unicode-16-Encoded Strings

;------------------------------------------------------------------------------
; Speicherkarte
;------------------------------------------------------------------------------

; Memory map:

; 200 - 20F Data Stack   Only used for Forth message printing routines
; 210 - 23F Return Stack
; 240 - 25F Space for Variables
; 260 - 27F Buffers. At the end of memory to not currupt stack and variables in case of buffer overflow.

Receivebufferpointer     equ 242h
ReadyforTransmit         equ 244h
Current_Address          equ 246h
DatatoTransmit           equ 248h
DatatoTransmitOffset     equ 24Ah
Data_PID_toggle          equ 24Ch
New_Address              equ 24Eh

ReadyforTransmit_Irq_In  equ 250h
Data_PID_Toggle_Irq_In   equ 252h


Irq_In_Sendepaketpuffer  equ 02B0h ; Für den Interrupt-In-Endpoint der Maus      For interrupt-In-Endpoint of Mouse
Sendepaketpuffer         equ 02C0h ; Läuft nie über, denn ich fülle ihn selbst ! I fill this one myself, so it doesn't overflow


; Empfangs-Puffer ganz ans Ende des Speichers legen, denn sie neigen dazu, manchmal überzulaufen.
; Place receive buffers at the end of memory, they overflow sometimes.
Datapacketbuffer      equ 02D0h ; Weniger vom Überlauf betroffen  Less likey to overflow
Tokenpacketbuffer equ 02E0h ; Ganz ans Ende ! Mehr vom Überlauf betroffen... Tokenpacketbuffer More likely to overflow

  ifdef debug_messages
    include "rs232-over-led.asm"
    include "messages.asm"
  endif
 
 include "Receiver-18MHz.asm"
 include "Transmitter-18MHz.asm"
 include "PacketGenerator.asm"

  ifdef debug_messages
PrintBuffer:
  mov #16, r6       ; Number of bytes to print
  
- mov.b @r5+, r7
  writebyte r7
  write " "
  dec r6
  jnz -

  write " | "
  ret
  endif

Reset:
  mov #WDTPW+WDTHOLD, &WDTCTL ; Stop watchdog  
  mov #0240h, sp              ; Init Stackpointer
  mov #0210h, r4              ; Init Datastackpointer, only used for messages...

  ; Prepare Port 1
  clr.b &P1OUT
  bis.b #255, &P1DIR

  ; Prepare Port 2
  clr.b &P2OUT
  mov.b #1+2, &P2DIR  ; Prepare Pins for the Sync-LED, Anode and Cathode driven low. USB lines inputs.

  mov.b &CALBC1_1MHZ, &BCSCTL1   ; Set DCO
  mov.b &CALDCO_1MHZ, &DCOCTL    ;   to 1 MHz. To see if external clock is properly used, as between 1 MHz and 15 MHz is a big difference.


  
  ; Select LFXT1 (HF external clock mode) for MCLK
  bic #OSCOFF, sr              ; Turn on osc.
  bis.b #XTS, &BCSCTL1         ; HF mode
  mov.b #LFXT1S_3, &BCSCTL3     ; External clock source
  
- bic.b #OFIFG, &IFG1          ; Clear OFIFG
  mov #0FFh,R15                ; Delay
- dec R15
  jnz -
  bit.b #OFIFG, &IFG1          ; Re-test OFIFG
  jnz --                       ; Repeat test if needed
  bis.b #SELM_3, &BCSCTL2 ; Select LFXT1CLK
 

    
Debugloop:           ; Print contents of buffer (empty RAM in first run als welcome message), then connect and try to catch packets.

  ifdef debug_messages

    ; Print buffers with old content, RAM is preserved for some time even if power is disconnected briefly
    mov #Tokenpacketbuffer, r5  ; Address of Buffer
    call #Printbuffer
  
    mov #Datapacketbuffer, r5       ; Address of Buffer
    call #Printbuffer
  
    writeln ""

    ; Pufferspeicher löschen:  Clear receive buffers
    mov #Datapacketbuffer, r5
-   mov.b #00h, @r5 ; Ehemals AA
    inc r5
    cmp #Tokenpacketbuffer+16, r5
    jne -
  
  endif
  
  ; Los geht's ! Jetzt wird verbunden, jetzt wird es ernst.
  ; Lets start ! Time to connect !
    
  ; Initialisierung des Paketverteilers und dessen Variablen:
  ; Initialization of packet generator and its variables:

  mov #usb_pid_data0, &Data_PID_Toggle ; Erster Anfang festgelegt...
  clr &ReadyforTransmit      ; Kein Sendebereites Paket
  clr &DatatoTransmit        ; Keine Daten in der Warteschlange
  clr &DatatoTransmitOffset  ; Offset Null
     
  clr &ReadyforTransmit_Irq_In ; Keine Daten für den Interrupt-In-Endpoint der Maus
  mov #usb_pid_data1, &Data_PID_Toggle_Irq_In ; Reset des Data Togglings für den Interrupt Endpoint
     
  mov #Tokenpacketbuffer, &Receivebufferpointer ; Vor dem ersten Einlauf den Puffer korrekt setzen !
  clr.b &Datapacketbuffer ; Länge im Datapacketbuffer auf Null setzen - keine bereitliegenden Daten für die Protokollschleife.
  
  clr &Current_Address  ; Am Anfang hat das Ding Adresse Null                    At the beginning, the device has address zero
  clr &New_Address      ; Und die neue Adresse ist ebenso noch nicht gesetzt.    New address is zero, too
  
  bis.b #usbpullup, &P2OUT  ; "Connect to USB" via enabling the D- pullup resistor.
  bis.b #usbpullup, &P2DIR
 
  nop4 ; Some time for charging line over pullup...
  
  ;mov.b #usbminus, &P2IES ; Reagiere auf high-to-low-Flanke an D-
  ;mov.b #usbminus, &P2IE  ; Interrupt für die Leitung aktivieren
  ;clr.b &P2IFG            ; Keine wartenden Interrupts
  
  clr.b &P2IES            ; Reagiere auf low-to-high-Flanke an D+   Interrupt on rising edge on D+
  mov.b #usbplus, &P2IE   ; Interrupt für die Leitung aktivieren    Using D+ because there is keep-alive signaling in D- that would cause false syncs
  clr.b &P2IFG            ; Keine wartenden Interrupts              No interrupt pending
  
  eint                    ; Start !
        
Mainloop:
  ; SE0 in der Leitung, außerhalb eines Paketes ? Das bedeutet: Reset !
  ; SE0 for more than 2.5 us is a reset.
  ; Specification: 10ms Reset !
  
  mov #40, r10 ; Schleifendurchläufe  Loop runs to detect Bus Reset
  
- bit.b #usbplus+usbminus, &usbin ; Leitungen prüfen Check lines
  jc + ; Aussprung, wenn mindestens eine Leitung derweil high geht. Jump out if one of them is high
  dec r10
  jnz -
    ; Reset !
    clr &Current_Address  ; Am Anfang hat das Ding Adresse Null                    At the beginning, the device has address zero
    clr &New_Address      ; Und die neue Adresse ist ebenso noch nicht gesetzt.    New address is zero, too
    ;; write "R"      
+ ; Reset detection finished
  
  
;;  call #Protokollhelferlein ; Protocol handler can be called either here or at the end of the transmitter.
  call #Answer_for_Irq_In ; Prepare HID reports

  jmp Mainloop
  
  
Breakout: ; Hier kommen unbekannte Setup-Pakete und andere Seltsame Fälle an.
          ; Place to catch unknown Setup-Packets and other strange cases.

  ifdef debug_messages
    write " Ad: "
    writebyte &Current_Address
    write " . "
  endif
  
  jmp Debugloop

  include "Protocolhandler.asm"
    

;------------------------------------------------------------------------------    
Answer_for_Irq_In:
;------------------------------------------------------------------------------    
  tst &ReadyforTransmit_Irq_In
  jz +  ; Do nothing while there is still a processed data packet ready for transmission.
    ret ; Solange noch Daten zum Senden bereitliegen, nichts tun.

+ ; Ok. Der Sendepuffer für den Interrupt-In-Endpunkt ist leer. Kann neues bereiten !
  ; Data toggling:
  xor #usb_pid_data0_xor_data1, &Data_PID_toggle_Irq_In ; Implementiere das Data0/Data1-Wechseln. usb_pid_data0_xor_data1
  mov.b &Data_PID_toggle_Irq_In, &Irq_In_Sendepaketpuffer+1 ; PID des Paketes setzen.      
  
  ; Konstruiere die Antwort der Maus:  Build the voice of the mouse
  mov.b #0, &Irq_In_Sendepaketpuffer+2 ; Buttons pressed
  mov.b #1, &Irq_In_Sendepaketpuffer+3 ; X movement
  mov.b #0, &Irq_In_Sendepaketpuffer+4 ; Y movement
  mov.b #0, &Irq_In_Sendepaketpuffer+5 ; Wheel movement

  mov.b #5, &Irq_In_Sendepaketpuffer ; Length of paket
  
  ; Add CRC
  push r10
  push r11
  push r12
  push r13
  
  mov #Irq_In_Sendepaketpuffer, r10
  call #pluscrc
  
  ; Put it in place for endpoint-transmission
  mov #Irq_In_Sendepaketpuffer, &ReadyforTransmit_Irq_In
  
  pop r13
  pop r12
  pop r11
  pop r10
  ret
    
    
;------------------------------------------------------------------------------
null_handler: ; Fängt unbenutzte Interrupts auf  Catches unused interrupts
;------------------------------------------------------------------------------
  reti


 org 0FFE0h ; Interrupttabelle mit eigenen Routinen

  .word   null_handler         ; 01: 0FFE0  Unbelegt
  .word   null_handler         ; 02: 0FFE2  Unbelegt
  .word   null_handler         ; 03: 0FFE4  Port 1
  .word   usb_sync             ; 04: 0FFE6  Port 2
  .word   null_handler         ; 05: 0FFE8  Unbelegt
  .word   null_handler         ; 06: 0FFEA  Für das Sammeln von Werten aus dem AD-Wandler: Temperatur und Vcc !
  .word   null_handler         ; 07: 0FFEC  USCI Transmit  - Hierrüber läuft das Terminal.
  .word   null_handler         ; 08: 0FFEE  USCI Receive   - Werde sie also nicht benutzen.
  .word   null_handler         ; 09: 0FFF0  Für den Überlauf-Interrupt in der Zeitnehmer-Routine.
  .word   null_handler         ; 10: 0FFF2  Für das Wecken nach einer Pause und den Taktlauscher
  .word   null_handler         ; 11: 0FFF4  Für die Zeitzählung
  .word   null_handler         ; 12: 0FFF6  Komparantor
  .word   null_handler         ; 13: 0FFF8  Überlauf-Interrupt
  .word   null_handler         ; 14: 0FFFA  Normales Wecken
  .word   null_handler         ; 15: 0FFFC  NMI. Vorsichtshalber erstmal nicht rausgeführt.
  .word   Reset                ; 16: 0FFFE  Für den Start
  