• Welcome to our site! Electro Tech is an online community (with over 170,000 members) who enjoy talking about and building electronic circuits, projects and gadgets. To participate you need to register. Registration is free. Click here to register now.

Building a MIDI keytar

granddad

Active Member
Mmmmm lots to digest there ... as posted my velocity was static ( preset) . did not do transpose as MiDi sound was enhancing the organ sounds the sound box (XG100) fed back into the organ amps ..
 
Thread starter #143
Here's the first pass of the schematic. I still have to add the AC244 and its' associated connector.
I'll do that over the weekend. It'll give me a chance to redo the AC238 outputs (I'm not too keen on the way they look.)

Next week, I'll pick up a display unit and set up the SPI interface to the display.

The final schematic will be done in Kicad. Then, I can generate the necessary files for making a PC board.

And now, on to the software.

Stay tuned. :)


KeytarSchematic.jpg
 
Last edited:

wkrug

Active Member
Wow - Your development speed is abnormal!

For "normal" usage of this display You need 7 lines to the Controller, but You can switch it to serial mode via solder point.
But I think You've known this.
And I guess it's quite expensive.

Don't forget the power supply, the contrast pot and backlight for the display in your schematic - I've done this a time ago.

Such displays are availible es OLED version too, that will increase the viewing angle.
E.g. https://www.reichelt.de/display-oled-2x20-116x37mm-gelb-ea-w202-xlg-p113323.html?&trstct=pos_15
20x2 chars. SPI ? - I've not explored.

My favorite would be a grafic display, You know.
But with an 4 Wire SPI Bus You can change it when want any time.
You only have to use an other library.
 

Pommie

Well-Known Member
Most Helpful Member
Out of curiosity, how fast does the count have to be? For example how long has a key taken to travel from top to bottom if the velocity is 25?

BTW, Those 20x4 displays are available with spi interface on ebay for around US$4 if you can wait a month.

Mike.
 

granddad

Active Member
MJ .. You must be eating your meals at the work bench :) you seem to have MiDi well under control.. Hope the PIC's 32k Flash is enough... as a side issue...If you have a spare IO , for a prototype I put a Led on a pin that flashes at startup so I know the 'beast' is running... LCD, writing to this display may eat up into your scan uSecs.
 
Last edited:
Thread starter #148
Wow - Your development speed is abnormal!

For "normal" usage of this display You need 7 lines to the Controller, but You can switch it to serial mode via solder point.
But I think You've known this.
And I guess it's quite expensive.

Don't forget the power supply, the contrast pot and backlight for the display in your schematic - I've done this a time ago.

Such displays are availible es OLED version too, that will increase the viewing angle.
E.g. https://www.reichelt.de/display-oled-2x20-116x37mm-gelb-ea-w202-xlg-p113323.html?&trstct=pos_15
20x2 chars. SPI ? - I've not explored.

My favorite would be a grafic display, You know.
But with an 4 Wire SPI Bus You can change it when want any time.
You only have to use an other library.
@wkrug,granddad: Thanks for the compliment. I'm a New Yorker. We're always pushed into getting everything done "yesterday". :)

wkrug: I was looking at displays and the one I picked from Mouser seemed to be the best compromise.
The cheapest and the most readily available.

Pommie: As far as what scan time will produce a specific velocity, I'm not sure.

Typically, the keybed scan rate is around 2000Hz. That seems to be what everybody else is doing. (Roland, et.al.)
I'm guessing that the geometry of the actual key switch seems to be a standard that was established by Fatar.

Once I get the system up and running, I'll hook a Logic Analyzer up to the pins and see. I'll post pics here. :)

Something just dawned on me. If I add one more AC573 and one more OE strobe, I can make this board handle keybeds of up to 88 keys.

Going to investigate that. :)
 

wkrug

Active Member
Typically, the keybed scan rate is around 2000Hz. That seems to be what everybody else is doing. (Roland, et.al.)
You can put an DSO at the 2 sample lines ( or a logic analyzer ), when pressing a key.
The diode lines can be set to a fix potetial.
Then press a key once hard as You playing and once super soft.
So You get 2 reaction times, between the 2 contacts that will give You the span of useable velocity.

When start the keyscan via timer in CTC Mode You can change the scan time very easy, only by changing one value ( Comparematch Value ). Terminology is from AVR - In PIC the functions are possibly named different.
 
Thread starter #152
granddad: I'm using it as this very moment. :D

It was a royal PITA at first, but I figured out how to use the assembler.

Here's what I have so far.
EDIT: See post #154 for the latest code

Please take a look and let me know if anything looks wrong.
 
Last edited:
Thread starter #154
This is the latest firmware for the keytar project. It was built using MPLAB-X version 5.1 and the XC16 Assembler.

It is designed to go with the schematic diagram in post #143
This post will be updated as I update source code.

There's still a lot to do. I need to set up and test the A/D convertors, as well as the UART settings.
Then, the SPI display needs to be made working. For that, I have some time because I haven't ordered the display yet.

If you see something that doesn't look right or have some suggestions, please post a reply here. :)

I'm releasing this source to the public domain, with the proviso that you agree to indemnify me and hold me harmless
for any problems or issues that you may have using this source code.

Use it at your own risk and peril. ;)

And please! Be sure to mention this forum if you're asked where it came from.

The folks here have been very kind and helpful. They give freely and ask for nothing in return.

Thanks everyone for all your help and input. :)

Enjoy...

File:
KeytarMain.s
Code:
            .equ __24F32KA304, 1
            .include "p24F32KA304.inc"

            .section __FBS.sec, code
            .global __FBS
__FBS:        .pword BWRP_OFF & BSS_OFF

            .section __FGS.sec, code
            .global __FGS
__FGS:        .pword GWRP_OFF

            .section __FOSCSEL.sec, code
            .global __FOSCSEL
__FOSCSEL:    .pword FNOSC_PRI & OSCIOFNC_ON & LPRCSEL_HP & IESO_ON & POSCFREQ_HS

;            .section __REFOCON.sec, code
;            .global __REFOCON
;__REFOCON:    .pword 



            .section __FPOR.sec, code
            .global __FPOR
__FPOR:        .pword BOREN_BOR0 & MCLRE_ON

            .section __FWDT.sec, code
            .global __FWDT
__FWDT:        .pword FWDTEN_OFF

            .section __FICD.sec, code
            .global __FICD
__FICD:        .pword ICS_PGx1

            .section __FDS.sec, code
            .global __FDS
__FDS:        .pword DSWDTEN_OFF

            .section __FOSC.sec, code
            .global __FOSC
__FOSC:        .pword FNOSC_PRI & OSCIOFNC_ON & LPRCSEL_HP & IESO_ON & POSCFREQ_HS

            .data
            .equ    SPICommand,        #0x1f
            .equ    SPIData,        #0x5f
            .equ    SPIRcvData,        #0x3f

ADCControl:            .word    0
LastPitchBend:        .word    0
PitchBend:            .word    0

LastModulation:        .word    0
Modulation:            .word    0

LastVolume:            .word    0
Volume:                .word    0

Transpose:            .space    2,0
Octave:                .space    2,0
RowAddress:            .word    0
VelocityAddress:    .word    0

SentNoteOn:            .space    12,0
SentNoteOff:        .space    12,0
Fifo:                .space    98,0
FifoIndex:            .word    2,0xffff
DisplayBuffer:        .space    80,0

            .equ    SPIDelay,    8192

            .text
            .global        __reset
;            .global        __U1RXInterrupt
__reset:
            mov        #__SP_init,w15
            mov        #__SPLIM_init,w0
            mov        w0, SPLIM
            nop
            clr        w0
            mov        w0,RowAddress
            mov        w0,VelocityAddress
            mov        w0,w14
            repeat  #12
            mov        w0,[++w14]
            clr        w14

            call    _InitExtClock
            call    _InitInterrupts
            call    _InitChangeNotification
            call    _InitI2C
            call    _InitSPI
            call    _InitRTC
            call    _InitPORTA
            call    _InitPORTB
            call    _InitPORTC
            call    _InitCTMU
            call    _InitHLVD
            call    _InitOC
            call    _InitADC
            call    _InitUARTS
            call    _InitTimer1

            call    _LCDHardReset
            bset    T1CON,#TON
InitDone:
            goto    InitDone

;__U1RXInterrupt:
;            retfie

    .end
File: Init.s

Code:
            .equ __24F32KA304, 1
            .include "p24F32KA304.inc"

            .text
            .global    _InitExtClock
            .global    _InitInterrupts
            .global    _InitChangeNotification
            .global    _InitI2C
            .global    _InitSPI
            .global    _InitRTC
            .global    _InitPORTA
            .global    _InitPORTB
            .global    _InitPORTC
            .global    _InitCTMU
            .global    _InitHLVD
            .global    _InitOC
            .global    _InitADC
            .global    _InitUARTS
            .global    _InitTimer1

_InitExtClock:
            mov        #0xb600,w1
            mov        w1,REFOCON
            return

_InitInterrupts:
            mov        #0x8000,w1
            mov        w1,INTCON1

            mov        #0x4000,w1
            mov        w1,INTCON2

            mov        #0x0008,w1
            mov        w1,IEC0

            clr        w1
            mov        w1,IEC1
            mov        w1,IEC2
            mov        w1,IEC3
            mov        w1,IEC4

            mov        #0x7000,w1
            mov        w1,IPC0

            clr        w1
            mov        w1,IPC1
            mov        w1,IPC2
            mov        w1,IPC3
            mov        w1,IPC4
            mov        w1,IPC5
            mov        w1,IPC6
            mov        w1,IPC7
            mov        w1,IPC8
            mov        w1,IPC9
            mov        w1,IPC12
            mov        w1,IPC15
            mov        w1,IPC16
            mov        w1,IPC18
            mov        w1,IPC19
            mov        w1,IPC20
            return

_InitChangeNotification:
            clr        w0
            mov        W0,CNEN1
            mov        W0,CNEN2
            mov        W0,CNPU1
            mov        W0,CNPU2
            mov        W0,CNPD1
            mov        W0,CNPD2
            mov        w0,CM1CON
            mov        w0,CM2CON
            mov        w0,CM3CON
            return

_InitI2C:
            clr        w1
            mov        W1,I2C1CON        ; Disable I2C...
            return

_InitSPI:
            mov.w    #0x1800,w0
            mov.w    w0,SPI1CON1

            clr.w    w0
            mov.w    w0,SPI1CON2

            mov        #0x8000,W3
            mov        W3,SPI1STAT

            mov.w    #0x1800,w0
            mov.w    W0,SPI2CON1

            clr.w    W0
            mov.w    W0,SPI2CON2

            mov        #0x8000,W3
            mov        W3,SPI2STAT
            return

_InitRTC:
            clr        w0
            mov        W0,RCFGCAL        ; Disable RTCC
            return

_InitPORTA:
            mov.w    #0,W0
            mov.w    W0,TRISA

            clr        W0
            mov.w    W0,ODCA
            mov.w    W0,ANSA

            mov.w    #0x0500,W0        ; Reset and OE High...
            mov        W0,LATA

            return

_InitPORTB:
            mov        #0x03FC,W0
            mov        W0,TRISB

            clr        W0
            mov        W0,ODCB
            mov        W0,ANSB

            mov        #0x5100,w0
            mov        w0,LATB
            return

_InitPORTC:
            mov.w    #0x000f,W0
            mov        W0,TRISC

            clr        W0
            mov        W0,ODCC

            mov        #7,W0
            mov        W0,ANSC

            mov.w    #0x001c,w0
            mov        w0,LATC
            bclr    LATC,#4        ; Set SOUT low...
            bset    LATC,#3        ; Set SCLK high...
            bset    LATC,#8
            return

_InitCTMU:
            clr        w0
            mov        w0,CTMUCON1
            mov        w0,CTMUCON2

            mov        #0x0100,w0
            mov        w0,CTMUICON
            return

_InitHLVD:
            clr        w0
            mov        w0,HLVDCON
            return

_InitOC:
            clr        w0
            mov        w0,OC1CON1
            mov        w0,OC2CON1
            mov        w0,OC3CON1
            return

_InitADC:
            mov        #0x8402,w0
            mov        w0,AD1CON1

            mov        #0x080A,w0
            mov        w0,AD1CON2

            clr        w0
            mov        w0,AD1CON3

            mov        #0x1f3f,w0
            mov        w0,AD1CON5

            mov        #0x1d1d,w0
            mov        w0,AD1CHS
            return

_InitUARTS:
;            mov        0x8810,w1
;            mov        w1,U1MODE
;            mov        #31,w1
;            mov        w1,U1BRG
            return

_InitTimer1:
            mov        #0x0020,W0    ; FClk / 64 = 250khz
            mov        W0,T1CON

            clr        W0
            mov        W0,T2CON
            mov        W0,T3CON
            mov        W0,TMR1

            mov        #0x001e,W0
            mov        W0,PR1

            mov        #0x01,W0
            mov        IPC0,w1

            bset    IEC0,#T1IE
            bclr    IFS0,#T1IF
            return

.end
File: Timer1Tick.s
Code:
            .equ __24F32KA304, 1
            .include "p24F32KA304.inc"

            .data
VelCounters:        .space    50,0
LastKeybedScanData:    .space    24,0
KeybedScanData:        .space    24,0

            .text
            .global        __T1Interrupt

            .equ    LEPreDelay,            50
            .equ    LE_OE_PreDelay,        30
            .equ    LEPulseWidth,        80
            .equ    OEPulseWidth,        10
            .equ    OE1_OE2_Delay,        30

__T1Interrupt:
            push.s
            clr        w3                            ; W3 is RowAddress
MainLoop:
            mov.w    w3,w4
            mov.w    w3,w5
            sl.w    w4,#6,w4                    ; Shift address bits into range of PORT A
            rlnc.w    w5,w5                        ; Compute offset into Raw/LastRawScanData
            mov.w    KeybedScanData,w6
            mov.w    LastKeybedScanData,w7
            mov.w    [w5 + w6],[w5 + w7]            ; RawScanData[RowAddress * 2] -> LastRawScanData[RowAddress * 2]

            mov.w    w4,LATA
            bset    LATC,#8                        ; LE1 High...
            bset    LATB,#14                    ; OE1 High...
            bset    LATB,#12                    ; OE2 High...
            bset    LATC,#9                        ; OE3 High...
            repeat    #LEPreDelay                    ; Delay to allow keybed to settle...
            nop

            bclr    LATC,#8                        ; LE1 Low to latch the 'AC573s...
            repeat    #LEPulseWidth                ; Delay for 'AC573 setup and hold time...
            nop

            bset    LATC,#8                        ; LE1 High...
            repeat    #LE_OE_PreDelay                ; Delay for OE1 pre-delay...
            nop

            bclr    LATB,#14                    ; OE2 Low...
            repeat    #OEPulseWidth                ; Delay for buss settling...
            nop

            mov        PORTB,w8                    ; Read PORT B (Primary contacts)
            mov.b    w8,[w5 + w6]                ; Store the byte...
            inc        w5,w5                        ; Increment the byte pointer...
            bset    LATB,#14                    ; OE2 High...
            repeat    #OE1_OE2_Delay                ; Delay for bus settling...
            nop

            bclr    LATB,#12                    ; OE1 Low...
            repeat    #OEPulseWidth                ; Delay for setup and hold time...
            nop

            mov        PORTB,w8                    ; Read PORT B (Secondary contacts)
            mov.b    w8,[w5 + w6]                ; Store the byte in RawScanData...
            bset    LATB,#12
            nop

            inc        w3,w3
            cp        w3,#8
            bra        lt,MainLoop

/*
            mov        VelocityAddress,w0
            mov        w0,w9
            mov.w    RowAddress,w10
            mov.w    w10,w11
            sl.w    w11,#6,w11                    ; Shift address bits into range of PORT A
            rlnc.w    w10,w10                        ; Compute offset into Raw/LastRawScanData

            mov.w    RawScanData,w12
            mov.w    LastRawScanData,w13
            mov.w    [w10 + w12],[w10 + w13]        ; RawScanData[RowAddress * 2] -> LastRawScanData[RowAddress * 2]

            mov.w    w11,LATA
            bset    LATC,#8                        ; LE1 High...
            bset    LATB,#14                    ; OE1 High...
            bset    LATB,#12                    ; OE2 High...
            repeat    #LEPreDelay                    ; Delay to allow keybed to settle...
            nop

            bclr    LATC,#8                        ; LE1 Low to latch the 'AC573s...
            repeat    #LEPulseWidth                ; Delay for 'AC573 setup and hold time...
            nop

            bset    LATC,#8                        ; LE1 High...
            repeat    #LE_OE_PreDelay                ; Delay for OE1 pre-delay...
            nop

            bclr    LATB,#14                    ; OE2 Low...
            repeat    #OEPulseWidth                ; Delay for buss settling...
            nop

            mov        PORTB,w6                    ; Read PORT B (Primary contacts)
            mov        w12,w5                        ; Get address of RawScanData... 
            mov.b    w6,[w10 + w5]                ; Store the byte...
            inc        w5,w5                        ; Increment the byte pointer...

            bset    LATB,#14                    ; OE2 High...
            repeat    #OE1_OE2_Delay                ; Delay for buss settling...
            nop

            bclr    LATB,#12                    ; OE1 Low...
            repeat    #OEPulseWidth                ; Delay for setup and hold time...
            nop

            mov        PORTB,w6                    ; Read PORT B (Secondary contacts)
            mov.b    w6,[w10 + w5]                ; Store the byte in RawScanData...

            bset    LATB,#12
            nop

            mov.w    [w10 + w12],w6        ; Get RawScanData[w10]...
            mov.w    [w10 + w13],w7        ; Get LastRawScanData[w10]...

;
; ---------------------------------------------
; | Secondary Contacts   |   Primary Contacts |
; ---------------------------------------------
;
; Logic:
;        if (LastRawPrimary[B] == 0 && RawPrimary[B] == 0)
;            Do Nothing...
;        else if (LastRawPrimary[B] == 0 && RawPrimary[B] == 1)
;        begin
;            Leading edge of key down...
;            VelocityCounter[VelocityAddress] = 0;
;        end
;        else if (LastRawPrimary[B] == 1 && RawPrimary[B] == 1)
;        begin
;            if (LastRawSecondary[B] == 0 && RawSecondary[B] == 0)
;            begin
;                VelocityCounter[VelocityAddress]++;
;            end
;            else if (LastRawSecondary[B] == 0 && RawSecondary[B] == 1)
;            begin
;                Leading edge of secondary contact closure...
;                if (SentNoteOn[Address][B] == 0)
;                begin
;                    Transmit Note On...
;                    SentNoteOn[Address][B] = 1;
;                    SentNoteOff[Address][B] = 0;
;                end
;            end
;        end
;        else if (LastRawPrimary[B] == 1 && RawPrimary[B] == 0)
;        begin
;            Leading edge of key up...
;            if (SentNoteOn[Address][B] == 0)
;            begin
;                Transmit Note Off...
;                SentNoteOff[Address][B] = 1;
;            end
;        end
;

            clr        w3
            clr        w4
            mov        #0x0040,w5        ; The bit mask for the key scan lines...
MainLoop:
            btst.z    w5,w7            ; is LastRawPrimary[RowAddress][W5] on?
            bra        nz,LastRawOn

            btst.z    w5,w6            ; is RawPrimary[RowAddress][W5] on?
            bra        z, DoNothing
                                    ; Leading edge of key down
            clr.b    [w9]
            goto    DoNothing

LastRawOn:
            btst.z    w5,w6
            bra        nz, DoNothing    ; Leading edge of key up

DoNothing:
            inc        w3,w3
            rrnc    w5,w5
            cp        w3,#7
            bra        lt,MainLoop
            mov        #50,w0
            cp        w1,w0
            bra        ge,Done1
            clr        w1
Done1:

            mov        w1,VelocityAddress
            mov.w    RowAddress,w0
            inc.w    w0,w0
            and.w    w0,#7,w0
            mov.w    w0,RowAddress
*/
            pop.s
            bclr    IFS0,#T1IF
            retfie


.end
File: LCD_ssd1803.s
Code:
            .equ __24F32KA304, 1
            .include "p24F32KA304.inc"

            .equ    SCLKDelay1,#64    ; 32 = 23.15uS
            .equ    SCLKDelay2,(SCLKDelay1 + #13)

            .text
            .global    _LCDHardReset
            .global _SPISendCmd
            .global _SPISendData

_Delay_US:
                    repeat    #14
                    nop
                    dec        w0,w0
                    cp        w0,#0
                    bra        gt,_Delay_US
                    return

_LCDHardReset:
                    bclr    LATA,#11

                    mov        #0x0300,W0
                    call    _Delay_US

                    bset    LATA,#11
                    mov        #0x2000,W0
                    call    _Delay_US

                    mov.w    #0x0034,W0
                    call    _SPISendCmd

                    mov.w    #0x0009,W0
                    call    _SPISendCmd

                    mov.w    #0x0030,W0
                    call    _SPISendCmd

                    mov.w    #0x000c,W0
                    call    _SPISendCmd

                    mov.w    #0x0006,W0
                    call    _SPISendCmd

                    mov.w    #0x0001,W0
                    call    _SPISendCmd
                    return

_SPISend:
                    clr.w        w2                ; Reset the loop counter...
                    mov            PORTC,W8        ; Grab a copy of PORT C
_SPISend1:
                    ior            #32,W8            ;
                    xor            #32,W8            ; SCK = 0
                    btst.z        W0,#0
                    bra            nz,_SPISend2
                    ior            #16,W8            ;
                    xor            #16,W8            ; Clear SOUT
                    goto        _SPISend3
_SPISend2:
                    ior            #16,W8            ; Set SOUT
_SPISend3:
                    mov            W8,LATC
                    repeat        #SCLKDelay2
                    nop
                    xor            #32,W8            ; Toggle SCK...
                    mov            W8,LATC
                    repeat        #SCLKDelay1
                    nop
                    rrnc        w0,w0
                    inc            w2,w2
                    cp            w2,#8
                    bra            lt,_SPISend1
                    bclr        LATC,#4
                    bset        LATC,#5
                    return

_SPIRcv:
                    clr.w        w0                ; Clear result...
                    clr.w        w2                ; Reset the loop counter...
                    mov            PORTC,W8        ; Grab a copy of PORT C
_SPIRcv1:
                    ior            #32,W8            ;
                    xor            #32,W8            ; SCK = 0
                    btst.z        W0,#0
                    bra            nz,_SPIRcv2
                    ior            #16,W8            ;
                    xor            #16,W8            ; Clear SOUT
                    goto        _SPIRcv3
_SPIRcv2:
                    ior            #16,W8            ; Set SOUT
_SPIRcv3:
                    mov            W8,LATC
                    repeat        #SCLKDelay2
                    nop
                    xor            #32,W8            ; Toggle SCK...
                    mov            W8,LATC
                    repeat        #SCLKDelay1
                    nop
                    rrnc        W0,W0
                    btst.z        W8,#3
                    bra            z,_SPIRcv4
                    ior            #128,W0
_SPIRcv4:
                    inc            w2,w2
                    cp            w2,#8
                    bra            lt,_SPIRcv1
                    bclr        LATC,#4
                    bset        LATC,#5
                    return

_SPISendCmd:
                    bclr        LATA,#9
                    nop
                    nop
                    nop
                    nop
                    swap        W0
                    clr.b        W0
                    swap        W0
                    mov.w        W0,W1
                    mov            #0x001f,W0
                    call        _SPISend
                    mov            W1,W0
                    and.b        W0,#0x0f,W0
                    call        _SPISend
                    mov            W1,W0
                    swap.b        W0
                    and.b        W0,#0x0f,W0
                    call        _SPISend
                    bset        LATA,#9
                    return


_SPISendData:
                    bclr        LATA,#9
                    nop
                    nop
                    nop
                    nop
                    swap        W0
                    clr.b        W0
                    swap        W0
                    mov.w        W0,W1
                    mov            #0x005f,W0
                    call        _SPISend
                    mov            W1,W0
                    and.b        W0,#0x0f,W0
                    call        _SPISend
                    mov            W1,W0
                    swap.b        W0
                    and.b        W0,#0x0f,W0
                    call        _SPISend
                    bset        LATA,#9
                    return


_SPIGetStatus:
                    bclr        LATA,#9
                    nop
                    nop
                    nop
                    nop
                    mov            #0x007f,W0
                    call        _SPISend
                    call        _SPIRcv
                    bset        LATA,#9
                    return

.end
 
Last edited:
Thread starter #157
granddad: Thanks for the heads up. I just made changes to the Change Notification Initialization routine.
I think that should completely shut off pullups / pulldowns on the ICN pins.

Post #154 should be the latest source. :)
 
Thread starter #158
Ok. The source code has been updated. All of the signals are toggling correctly, albeit a little slow.

Take a look at post #154 for the latest source.

Time for a dinner break. Then I'll do some timing tests and show some pics with the logic analyzer.
 
Last edited:

Latest threads

EE World Online Articles

Loading

 
Top