Shop OBEX P1 Docs P2 Docs Learn Events
qZ80 - the third shot - Page 7 — Parallax Forums

qZ80 - the third shot

1234579

Comments

  • Dr_AculaDr_Acula Posts: 5,484
    edited 2010-06-12 08:18
    Hi pullmoll.

    I'm awfully sorry, but I don't understand that at all
    PUB Start : okay
    '' Call start to start cog
    '' Start serial driver - starts a cog
    '' returns false if no cog available
    ''
      rxbuff_head_ptr0 := rxbuff_ptr0 := @rx_buffer0
      rxbuff_head_ptr1 := rxbuff_ptr1 := @rx_buffer1
      txbuff_tail_ptr0 := txbuff_ptr0 := @tx_buffer0
      txbuff_tail_ptr1 := txbuff_ptr1 := @tx_buffer1
      rx_head_ptr0 := @rx_head0
      rx_head_ptr1 := @rx_head1
      rx_tail_ptr0 := @rx_tail0
      rx_tail_ptr1 := @rx_tail1
      tx_head_ptr0 := @tx_head0
      tx_head_ptr1 := @tx_head1
      tx_tail_ptr0 := @tx_tail0
      tx_tail_ptr1 := @tx_tail1
      bit_ticks_ptr0 := @new_bit_ticks0
      bit_ticks_ptr1 := @new_bit_ticks1
      new_bit_ticks0 := long[noparse][[/noparse]@bit_ticks0][noparse][[/noparse]0]
      new_bit_ticks1 := long[noparse][[/noparse]@bit_ticks0]
      okay := cog := cognew(@entry, @rx_head0) + 1
    
    



    This is in the pcfullduplexserial object. Ok, there are two new variables.

    and at the end of the code, in pasm

            FIT             $1F0
    new_bit_ticks0          long      0-0
    new_bit_ticks1          long      0-0
    
    



    My understanding of spin and pasm is somewhat limited, but are they not local variables to this object? They are defined in the object and all variables are local to the object and you only posted one object so presumably no other object uses these variables. So how does another object pass variables through to this object?

    BTW - yes that code compiles and works but I'm not sure how to change this variable.

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    www.smarthome.viviti.com/propeller

    Post Edited (Dr_Acula) : 6/12/2010 8:24:07 AM GMT
  • pullmollpullmoll Posts: 817
    edited 2010-06-12 12:53
    v0.9.32: Add ports $28 (40) and $29 (41) to set the baud rate of SIO port #0 and #1. The value written is multiplied by 1200 and clkfreq/value/1200 is then written to the bit_ticks values of the SIO object, which in turn reads the (new) values before receiving any character.

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    Pullmoll's Propeller Projects

    Post Edited (pullmoll) : 6/12/2010 1:00:35 PM GMT
  • Dr_AculaDr_Acula Posts: 5,484
    edited 2010-06-12 13:18
    Now that is one sweet piece of coding! Thanks pullmoll

    To use:
    MBASIC
    then
    OUT (41),1

    and the second serial port is now 1200 baud.

    Now I need to study how the code works. Sehr kluger, vielen dank.

    addit: found the code. So neat.
    '**********************************************************************************************
    '
    ' Set baud rate of SIO #0 to data * 1200 baud
    '
    output_baud0
            mov    lmm_pc, #lmm_baud0
            jmp    #lmm_command0
    
    '**********************************************************************************************
    '
    ' Set baud rate of SIO #1 to data * 1200 baud
    '
    output_baud1
            mov    lmm_pc, #lmm_baud1
            jmp    #lmm_command0
    
    

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    www.smarthome.viviti.com/propeller

    Post Edited (Dr_Acula) : 6/12/2010 1:38:51 PM GMT
  • kuronekokuroneko Posts: 3,623
    edited 2010-06-12 13:33
    Dr_Acula said...
    Sehr kluger, vielen dank.
    Shouldn't that read "Sehr schlau ..."? No, don't answer that, I'm just teasing ... [noparse]:)[/noparse]
  • Dr_AculaDr_Acula Posts: 5,484
    edited 2010-06-12 13:41
    You may be right. Of course in 'strine, we might say; Stone the crows, you beauty, you little ripper, she's bonzer, mate.

    run that through babelfish and see what mangled translation comes out *grin*.

    I'm going to celebrate by playing Mampf ManII on the prop.

    Addit: just got an email from China saying the new boards are on the way - RS485, sound, ECB bus, RCA socket for TV. Hopefully I can get one soldered up in the next week or so and get some audio on these games.

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    www.smarthome.viviti.com/propeller

    Post Edited (Dr_Acula) : 6/12/2010 1:47:15 PM GMT
  • pullmollpullmoll Posts: 817
    edited 2010-06-12 14:42
    Dr_Acula said...
    You may be right. Of course in 'strine, we might say; Stone the crows, you beauty, you little ripper, she's bonzer, mate.

    run that through babelfish and see what mangled translation comes out *grin*.

    I'm going to celebrate by playing Mampf ManII on the prop.

    Addit: just got an email from China saying the new boards are on the way - RS485, sound, ECB bus, RCA socket for TV. Hopefully I can get one soldered up in the next week or so and get some audio on these games.

    G'day bloke! Let's have some cans of bloody ice cool Foster's VBs on that matter.

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    Pullmoll's Propeller Projects

    Post Edited (pullmoll) : 6/12/2010 2:47:36 PM GMT
  • bazibazi Posts: 29
    edited 2010-06-12 19:08
    A quote from my other thread:
    pullmoll said...
    To run CP/M you can download the latest code from this thread. There's also a hard disk image and a boot.dsk that needs to copied to an SD card. Extract the zip and add the ./lib path to BST's compiler preferences (library search path), then compile cpm.spin to RAM (F10) or EEPROM (F11).

    Now i ran into problems... I've downloaded qz80-0.9.32 and tried to compile it...
    my computer said...
    bstc.linux -Orx -ls -b -e -p0 -d/dev/ttyUSB0 -L../lib -o cpm cpm.spin
    Brads Spin Tool Compiler v0.15.3 - Copyright 2008,2009 All rights reserved
    Compiled for i386 Linux at 08:17:46 on 2009/07/20
    Loading Object cpm
    Loading Object io
    Loading Object fatfs
    Loading Object spi_warp
    Loading Object pcFullDuplexSerial2FC
    Loading Object Keyboard
    Loading Object tv80

    io(138,24) Error : Unable to locate object
    vt100 : "vt100"
    _______________________^

    I've tried to change it to vt100_colour and vt100_mono and the TV shows a lot of garbage, what happened here?

    Heres the top of my cpm.spin http://uc.pastebin.com/KzGPBPQ2
    And yes, the SD-config is right, i've changed it. SD-Card works with propdos.
    my other thread said...

    Instead of VGA & TV i have only a TV out on Pins 16,17 and 18. Pin 19 has a jumper + resistor for the fourth TV-pin (aural subcarrier i think, it isn't needed).
    I've tested the TV-Out and Keyboard with propdos => works fine.

    I've added a 16bit SPI Port Portexpander (MC23S17) for GPIO and two Atari Joysticks [noparse]:)[/noparse]
    If i try to run the Parallax Graphics Demo, nothing shows up on TV and 4 LEDs, connected to the Expander are turned on. The expander is located at Pins 20,21,22,23. The Source is modified like this:

    // edit:
    serial console of prop said...
    SIO initialized, 5 cogs free.
    KBD initialized, 4 cogs free.
    TV80 initialized, 3 cogs free.
    qZ80 I/O starting...
    Volume serial #E875-2686, label
    BOOT.DSK, sector 0101A1, size 256, 2010-06-12 20:37:06
    A.DSK, sector 000191, size 32.0MB, 2010-06-12 20:37:06 contiguous - okay.
    B.DSK failed: -1
    B.DSK failed: -1
    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    some say the devil is dead... ...tish army!

    Post Edited (bazi) : 6/12/2010 7:26:21 PM GMT
  • Dr_AculaDr_Acula Posts: 5,484
    edited 2010-06-13 02:48
    Did you get the vt100 part working? It could be either;
    1) The vt100 file is in a sub folder. pullmoll keeps some files in sub folders so go through them all and see if it is there
    2) it could be that the #define for vga is not catching all the changes when you go from vga to tv. I'm not using tv so I haven't tested this code but I think pullmoll has a tv board.

    Re the errors on booting, you are getting very close. I suspect the files b.dsk and c.dsk are not on the sd card. Have these been copied over?

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    www.smarthome.viviti.com/propeller
  • bazibazi Posts: 29
    edited 2010-06-13 08:17
    dracula said...
    Did you get the vt100 part working?
    I don't know...
    dracula said...
    1) The vt100 file is in a sub folder. pullmoll keeps some files in sub folders so go through them all and see if it is there
    In the source "vt100.spin" is specified, but i can only find vt100_mono and vt100_colour. If i change the source to on of these, the above described error occurs.
    dracula said...
    2) it could be that the #define for vga is not catching all the changes when you go from vga to tv. I'm not using tv so I haven't tested this code but I think pullmoll has a tv board.
    It would be very very nice if he or any other with a working tv-qz80 could have a look at this http://uc.pastebin.com/KzGPBPQ2

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    some say the devil is dead... ...tish army!
  • pullmollpullmoll Posts: 817
    edited 2010-06-13 10:38
    @bazi: I hadn't tested the TV80 driver for some time. The vt100_mono.spin object is the one that should be included with TV. The error you see is some missing hard drive images. You can just copy a.dsk to b.dsk and c.dsk and later "era *.*" on b: and c:

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    Pullmoll's Propeller Projects
  • Dr_AculaDr_Acula Posts: 5,484
    edited 2010-06-13 11:05
    @pulmoll "G'day bloke! Let's have some cans of bloody ice cool Foster's VBs on that matter."

    ROTFLOL!

    Mind you, all bets might be off soon - Australia is playing Germany very soon in the World Cup.

    @bazi
    you should have:
    vt100.spin
    vt100_colour.spin
    vt100_mono.spin

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    www.smarthome.viviti.com/propeller
  • pullmollpullmoll Posts: 817
    edited 2010-06-13 20:43
    Dr_Acula said...
    @pulmoll "G'day bloke! Let's have some cans of bloody ice cool Foster's VBs on that matter."

    ROTFLOL!

    Mind you, all bets might be off soon - Australia is playing Germany very soon in the World Cup.

    I've seen it. I'm not usually a soccer fan, but that game was great. I think we will be champions after the games wink.gif
    Dr_Acula said...

    @bazi
    you should have:
    vt100.spin
    vt100_colour.spin
    vt100_mono.spin

    Not really. The vt100.spin was the code for 1 byte per character which is the same as vt100_mono.spin.

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    Pullmoll's Propeller Projects
  • Dr_AculaDr_Acula Posts: 5,484
    edited 2010-06-14 04:26
    I've seen it. I'm not usually a soccer fan, but that game was great. I think we will be champions after the games

    Aaaargh. 4-Nil. On behalf of all Australians, I think I'll go hide under a rock for a while...

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    www.smarthome.viviti.com/propeller
  • BradCBradC Posts: 2,601
    edited 2010-06-14 04:41
    Dr_Acula said...
    I think I'll go hide under a rock for a while...

    Oi! find your own rock, this one's taken!

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    "I mean, if I went around sayin' I was an emperor just because some moistened bint had lobbed a scimitar at me they'd put me away!"
  • Dr_AculaDr_Acula Posts: 5,484
    edited 2010-06-18 08:44
    Hi Pullmoll,

    I've been doing some experiments with batch files on KyeDOS and one of the things needed for batch files is the ability to write them. I think there are one (or two) text editors around for the propeller but not sure if they are fat32 compatible yet.

    Anyway, there are many text editors in CP/M, and now that it is so easy to change from CP/M to a spin DOS and back again, I figured it might be easier to move files in and out of the CP/M disk image.

    In other words, replicate the SIMH commands of R.COM and W.COM

    But it is not so simple!

    The main problem is you can't have two files open at once. So you might open the disk image file for reading, but how do you then open the file in the FAT KyeDOS for writing?

    (As an aside, how does PIP do that when moving files between disk images? Does it open and close each file in turn?)

    There might be several ways to do this. I think you have some raw FAT32 driver code already in the qZ80?

    One solution might be to move a file to high external ram, then reboot the propeller and write a program in spin to read that high ram back. (the ram should stay static, even after a reboot). To do that, one might think about a small CP/M program in C or Basic that takes a file and moves it to high ram. And to do that, one might need new OUT and IN commands to poke and peek to high ram.

    I wrote some for the zicog - code below

    ' this all works but takes up spin space and might end up using the space for other things
    PRI Peek_Ram | a ' read the value in a ram location
      ' pass address in DMA 80H=LSB, 81H=middle byte, 82H=MSB
      PeekPokeSetup
      a:=buff<<8 | buff[noparse][[/noparse]0] ' 0 to 65535 combine two bytes
      RamLatches.DoCmd("R", @buff, a, 1) ' value to buff[noparse][[/noparse]0] ' need to use buffcog with no @
      io_data:=buff[noparse][[/noparse]0]
      HighLatchZero
    
    PRI Poke_Ram | a ' read the value in a ram location
      ' pass address in DMA 80H=LSB, 81H=middle byte, 82H=MSB
      PeekPokeSetup
      a:=buff<<8 | buff[noparse][[/noparse]0] ' 0 to 65535 combine two bytes
      buff[noparse][[/noparse]0]:=io_data ' put io_data in buff array ready to output
      RamLatches.DoCmd("W", @buff, a, 1) ' write io_data which is in buff[noparse][[/noparse]0] ' need to use buffcog with no @
      HighLatchZero
    
    PRI PeekPokeSetup
      RamLatches.DoCmd("R", @buff, $80, 3) ' read 3 bytes from DMA area to buff array ' need to use buffcog with no @
      HighLatchByte :=buff << 16 ' multiply by 65536 to get high byte
      RamLatches.DoCmd("H", 0, HighLatchByte, 0)   ' setup high byte
    ' ------------------------------------------------------------------
    PUB HighLatchZero ' led off and bank 0
      HighLatchByte :=%00000000_00000000_00000000_00000000 ' xxxxxxxx_nnnnnnnn_xxxxxxxx_xxxxxxxx where n is A16 to A23
      RamLatches.DoCmd("H", 0, HighLatchByte, 0)   ' restore for CP/M
    
    
    



    and the complete RamLatches object:

    ''Dracblade driver for talking to a ram chip via three latches
    '' Modified code from Cluso's triblade
    ' DoCmd(command_, hub_address, ram_address, block_length)
    ' R - read bytes at address n up (n to n+block_length) where n =0 to 65535 (ie lower 64k of the sram chip)
    ' W - write bytes at address n up
    ' I - initialise
    ' N - Led on
    ' F - Led off
    ' H - set high latch to value in ramaddress A16 to A23 (will include the led)
    
    VAR
    
    ' communication params(5) between cog driver code - only "command" and "errx" are modified by the driver
       long  command, hubaddrs, ramaddrs, blocklen, errx, cog ' rendezvous between spin and assembly (can be used cog to cog)
    '        command  = R, W, N, F H =0 when operation completed by cog
    '        hubaddrs = hub address for data buffer
    '        ramaddrs = ram address for data ($0000 to $FFFF)
    '        blocklen = ram buffer length for data transfer
    '        errx     = returns =0 (false=good), else <>0 (true & error code)
    '        cog      = cog no of driver (set by spin start routine)
       
    PUB start : err_
    ' Initialise the Drac Ram driver. No actual changes to ram as the read/write routines handle this
      command := "I"
      cog := 1 + cognew(@tbp2_start, @command)
      if cog == 0
        err_ := $FF                 ' error = no cog
      else
        repeat while command        ' driver cog sets =0 when done
        err_ := errx                ' driver cog sets =0 if no error, else xx = error code
    
    PUB stop
       if cog
          cogstop(cog~ - 1)      
    
    PUB DoCmd(command_, hub_address, ram_address, block_length) : err_
    ' Do the command: R, W, N, F, H
      hubaddrs := hub_address       ' hub address start
      ramaddrs := ram_address       ' ram address start
      blocklen := block_length      ' block length
      command  := command_          ' must be last !!
    ' Wait for command to complete and get status
      repeat while command          ' driver cog sets =0 when done
      err_ := errx                  ' driver cog sets =0 if no error, else xx = error code
    
    
    DAT
    '' +--------------------------------------------------------------------------+
    '' | Dracblade Ram Driver (with grateful acknowlegements to Cluso)            |
    '' +--------------------------------------------------------------------------+
                            org     0
    tbp2_start    ' setup the pointers to the hub command interface (saves execution time later
                                          '  +-- These instructions are overwritten as variables after start
    comptr                  mov     comptr, par     ' -|  hub pointer to command                
    hubptr                  mov     hubptr, par     '  |  hub pointer to hub address            
    ramptr                  add     hubptr, #4      '  |  hub pointer to ram address            
    lenptr                  mov     ramptr, par     '  |  hub pointer to length                 
    errptr                  add     ramptr, #8      '  |  hub pointer to error status           
    cmd                     mov     lenptr, par     '  |  command  I/R/W/G/P/Q                  
    hubaddr                 add     lenptr, #12     '  |  hub address                           
    ramaddr                 mov     errptr, par     '  |  ram address                           
    len                     add     errptr, #16     '  |  length                                
    err                     nop                     ' -+  error status returned (=0=false=good) 
    
    
    ' Initialise hardware (unlike the triblade, just tristates everything and read/write set the pins)
    init                    mov     err, #0                  ' reset err=false=good
                            mov     dira,zero                ' tristate the pins
    
    done                    wrlong  err, errptr             ' status  =0=false=good, else error x
                            wrlong  zero, comptr            ' command =0 (done)
    ' wait for a command (pause short time to reduce power)
    pause                   mov     ctr, delay      wz      ' if =0 no pause
                  if_nz     add     ctr, cnt
                  if_nz     waitcnt ctr, #0                 ' wait for a short time (reduces power)
                            rdlong  cmd, comptr     wz      ' command ?
                  if_z      jmp     #pause                  ' not yet
    ' decode command
                            cmp     cmd, #"R"       wz      ' R = read block
                  if_z      jmp     #rdblock
                            cmp     cmd, #"W"       wz      ' W = write block
                  if_z      jmp     #wrblock
                            cmp     cmd, #"N"       wz      ' N= led on
                  if_z      jmp     #led_turn_on
                            cmp     cmd, #"F"       wz      ' F = led off
                  if_z      jmp     #led_turn_off
                            cmp     cmd, #"H"       wz      ' H sets the high latch
                  if_z      jmp     #sethighlatch
                            mov     err, cmd                ' error = cmd (unknown command)
                            jmp     #done
    
    
    tristate                mov     dira,zero                ' all inputs to zero
                            jmp     #done
    
    ' turn led on
    led_turn_on             or      HighLatch,ledpin        ' set the led pin high
                            jmp     #OutputHighLatch         ' send this out
    
    led_turn_off            andn    HighLatch,ledpin        ' set the led pin low
                            jmp     #OutputHighLatch         ' send this out
    
    ' set high address bytes with command H, pass value in third variable of the DoCmd
    ' 4 bytes - masks off all but bits 16 to 23
    
    sethighlatch            call #ram_open                  ' gets address value in 'address'
                            shr  address,#16                ' shift right by 16 places
                            and  address,#$FF               ' ensure rest of bits zero
                            mov  HighLatch,address          ' put value into HighLatch
                            jmp  #OutputHighLatch           ' and output it
    
    '---------------------------------------------------------------------------------------------------------
    'Memory Access Functions
    
    rdblock                 call    #ram_open               ' get variables from hub variables
    rdloop                  call    #read_memory_byte       ' read byte from address into data_8
                            wrbyte  data_8,hubaddr          ' write data_8 to hubaddr ie copy byte to hub
                            add     hubaddr,#1              ' add 1 to hub address
                            add     address,#1              ' add 1 to ram address
                            djnz    len,#rdloop             ' loop until done
                            jmp     #init                   ' reinitialise
    
    wrblock                 call    #ram_open                        
    wrloop                  rdbyte  data_8, hubaddr         ' copy byte from hub
                            call    #write_memory_byte      ' write byte from data_8 to address
                            add     hubaddr,#1              ' add 1 to hub address
                            add     address,#1              ' add 1 to ram address
                            djnz    len,#wrloop             ' loop until done
                            jmp     #init                   ' reinitialise
    
    ram_open                rdlong  hubaddr, hubptr         ' get hub address
                            rdlong  ramaddr, ramptr         ' get ram address
                            rdlong  len, lenptr             ' get length
                            mov     err, #5                 ' err=5
                            mov     address,ramaddr         ' cluso's variable 'ramaddr' to dracblade variable 'address'
    ram_open_ret            ret
      
    read_memory_byte        call #RamAddress                ' sets up the latches with the correct ram address
                            mov dira,LatchDirection2        ' for reads so P0-P7 tristate till do read
                            mov outa,GateHigh               ' actually ReadEnable but they are the same
                            andn outa,GateHigh              ' set gate low
                            nop                             ' short delay to stabilise
                            nop
                            mov data_8, ina                 ' read SRAM
                            and data_8, #$FF                ' extract 8 bits
                            or  outa,GateHigh               ' set the gate high again
    read_memory_byte_ret    ret
    
    write_memory_byte       call #RamAddress                ' sets up the latches with the correct ram address
                            mov outx,data_8                 ' get the byte to output
                            and outx, #$FF                  ' ensure upper bytes=0
                            or outx,WriteEnable             ' or with correct 138 address
                            mov outa,outx                   ' send it out
                            andn outa,GateHigh              ' set gate low
                            nop                             ' no nop doesn't work, one does, so put in two to be sure
                            nop                             ' another NOP
                            or outa,GateHigh                ' set it high again
    write_memory_byte_ret   ret
    
    RamAddress ' sets up the ram latches. Assumes high latch A16-A18 low so only accesses 64k of ram
                            mov dira,LatchDirection         ' set up the pins for programming latch chips
                            mov outx,address                ' get the address into a temp variable
                            and outx,#$FF                   ' mask the low byte
                            or  outx,LowAddress             ' or with 138 low address
                            mov outa,outx                   ' send it out
                            andn outa,GateHigh              ' set gate low
                                                            ' ?? a NOP
                            or outa,GateHigh                ' set it high again  
                                                            ' now repeat for the middle byte     
                            mov outx,address                ' get the address into a temp variable
                            shr outx,#8                     ' shift right by 8 places
                            and outx,#$FF                   ' mask the low byte
                            or  outx,MiddleAddress          ' or with 138 middle address
                            mov outa,outx                   ' send it out
                            andn outa,GateHigh              ' set gate low
                            or outa,GateHigh                ' set it high again 
    RamAddress_ret          ret
    
    OutputHighLatch ' sends out HighLatch to the 374 that does A16-19, led and the 4 spare outputs
                            mov     dira,latchdirection     ' setup active pins 138 and bus
                            mov     outa,HighLatch          ' send out HighLatch
                            or      outa,HighAddress        ' or with the high address
                            andn    outa,GateHigh           ' set gate low
                            or      outa,GateHigh           ' set the gate high again
    OutputHighLatch_ret     jmp     #tristate               ' set pins tristate
    
    
    
    
    
    delay                   long    80                                    ' waitcnt delay to reduce power (#80 = 1uS approx)
    ctr                     long    0                                     ' used to pause execution (lower power use) & byte counter
    GateHigh                long    %00000000_00000000_00000001_00000000  ' HC138 gate high, all others must be low
    Outx                    long    0                                     ' for temp use, same as n in the spin code
    LatchDirection          long    %00000000_00000000_00001111_11111111 ' 138 active, gate active and 8 data lines active
    LatchDirection2         long    %00000000_00000000_00001111_00000000 ' for reads so data lines are tristate till the read
    LowAddress              long    %00000000_00000000_00000101_00000000 ' low address latch = xxxx010x and gate high xxxxxxx1
    MiddleAddress           long    %00000000_00000000_00000111_00000000 ' middle address latch = xxxx011x and gate high xxxxxxx1
    HighAddress             long    %00000000_00000000_00001001_00000000 ' high address latch = xxxx100x and gate high xxxxxxx1
    'ReadEnable long    %00000000_00000000_00000001_00000000 ' /RD = xxxx000x and gate high xxxxxxx1
                                                            ' commented out as the same as GateHigh
    WriteEnable             long    %00000000_00000000_00000011_00000000 ' /WE = xxxx001x and gate high xxxxxxx1
    Zero                    long    %00000000_00000000_00000000_00000000 ' for tristating all pins
    data_8                  long    %00000000_00000000_00000000_00000000 ' so code compatability with zicog driver
    address                 long    %00000000_00000000_00000000_00000000 ' address for ram chip
    ledpin                  long    %00000000_00000000_00000000_00001000 ' to turn on led
    HighLatch               long    %00000000_00000000_00000000_00000000 ' static value for the 374 latch that does the led, hA16-A19 and the other 4 outputs
    
    



    So first question, is there room in the qZ80 for a similar piece of code (pasm or spin?)?

    But the second question - is there a smarter way to move files in and out of CP/M disk images?

    eg just brainstorming, you have code that dumps data to a print spooler. Maybe you could capture that somehow?

    Or maybe not do it through the ram? Maybe open and close files many times (Kye's driver has an "append" function).

    Thinking big picture, now it is possible with KyeDOS to open and close spin files so easily (mix and match PropBasic, Catalina, CP/M), and with your ctrl-alt-del shutdown for CP/M, it ought to be possible to use some of the programs in CP/M to do some on-board propeller development, eg writing batch files.

    Your thoughts would be most appreciated.

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    www.smarthome.viviti.com/propeller
  • heaterheater Posts: 3,370
    edited 2010-06-18 09:02
    DR_A: You don't need to have two file open at the same time to move a file from FAT to CP/M or vice versa.

    There is no need for any FAT files to be open when the Z80 emu is accessing it's CP/M files.
    That's because when Z80 emulation accesses CP/M files it ends up going through the I/O simulation layer, in Spin,
    that ends up doing low level block read and write on the SD card. The actual FAT file being accessed need not
    be open at this time. In fact it should NOT be open at this time, you can't allow FAT and CP/M access to the same
    file at the same time that would be a disaster.

    So when CP/M writes out a CP/M file through the SIMH style host port, as done by W.COM it is only necessary
    for the I/O emulation to detect those port accesses, open the appropriate file in FAT and write it out.
    Same for reads from the FAT host.

    Not sure how qZ80 handles CP/M file access but that is the case with ZiCog. If qZ80 keeps the CP/M disk files open
    all the time and uses seek to read/write the CP/M blocks then you would have a problem.

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    For me, the past is not over yet.
  • heaterheater Posts: 3,370
    edited 2010-06-18 09:05
    Dr_A: Oh yeah, being able to use WordStar or such editor on the Prop without ever really being aware that
    you have started CP/M to do so would be great.

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    For me, the past is not over yet.
  • pullmollpullmoll Posts: 817
    edited 2010-06-19 11:50
    Dr_Acula said...

    There might be several ways to do this. I think you have some raw FAT32 driver code already in the qZ80?

    I use my own fatfs.spin code in conjunction with hairmnstr's spi_warp.spin to read from FAT16 and FAT32 SD cards.
    Dr_Acula said...
    So first question, is there room in the qZ80 for a similar piece of code (pasm or spin?)?

    There are ports to access the unused (in MP/M) portions of the RAM. It's documented in the history at the first page. I guess you want to access all RAM except the first 64K in one sequence?
    Dr_Acula said...
    But the second question - is there a smarter way to move files in and out of CP/M disk images?

    Not unless there is code to write to the SD either in my fatfs.spin or in e.g. Kye's driver. Switching to that one means a lot of work in all emulations, though, which is why I would not want to do that lightheartedly... also Kye's code is certainly going to overflow the hub RAM in some of the emulations - the ones which already use up almost all of the hub RAM.
    Dr_Acula said...
    Your thoughts would be most appreciated.

    Well, if writing to the SD isn't too complicated, I can try to do that. Writing a file involves some steps, though:
    1) find an existing file by the given name and unlink (erase) it.
    2) find an empty directory slot ($e5) or append a new slot. For fat16 that may fail when writing the root directory. Otherwise it may involve allocating a new cluster to the current directory.
    3) create a new directory entry and find a first free cluster to enter into the structure.
    4) write to the sectors belonging to the cluster and when another cluster is required, create the cluster chain (in both copies of the FAT).
    5) on closing the file write the end mark to the current cluster in the FAT (== max. cluster number for the fat type) and modify the directory entry to have the correct size, perhaps update date/time fields.

    These steps involve a little bit of work and I'm not sure if/when I find time to try to add them.
    If writing was possible, I would suggest to add a fake port that receives a filename, a nul byte, a "W" for write, the size of the file (4 bytes long value) and then the file data. Reading should be possible through the same port by just specifying "R" for read. After that you could read the 4 byte file size from the port followed by the file data.

    It may be possible to squeeze yet another port handler call into io.spin, though it's very tight in there already. I have to find some code duplication to eliminate.

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    Pullmoll's Propeller Projects

    Post Edited (pullmoll) : 6/19/2010 11:58:50 AM GMT
  • pullmollpullmoll Posts: 817
    edited 2010-06-19 11:59
    Dr_Acula said...

    addit: found the code. So neat.
    '**********************************************************************************************
    '
    ' Set baud rate of SIO #0 to data * 1200 baud
    '
    output_baud0
            mov    lmm_pc, #lmm_baud0
            jmp    #lmm_command0
    
    '**********************************************************************************************
    '
    ' Set baud rate of SIO #1 to data * 1200 baud
    '
    output_baud1
            mov    lmm_pc, #lmm_baud1
            jmp    #lmm_command0
    
    


    Well, that's not all of the code.. it's just the code that dispatches to the LMM code baud0 or baud1. I can only add more LMM code, because the cog is full. In order to add yet another port handler, I need to free 2 cog longs for the dispatcher and even that will be impossible rather sooner than later.

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    Pullmoll's Propeller Projects
  • Dr_AculaDr_Acula Posts: 5,484
    edited 2010-06-20 03:12
    Yes, maybe moving files with a "W.COM" and "R.COM" might be too complicated.

    If you are really desperate for space, you could remove references to the N8VEM modem port (addit - no, still need this. but there are other solutions)
        n8vem_data_port        =    $68        ' modem data port
        n8vem_stat_port        =    $6d        ' modem status port
    
    



    and I could rewrite xmodemp.asm so it used these ports instead:
        constat_port        =    $10        ' console 0 status port (on SIO #0 or Keyboard / VGA)
        condata_port        =    $11        ' console 0 data port (on SIO #0 or Keyboard / VGA)
        punstat_port        =    $12        ' punch 1 status port
        pundata_port        =    $13        ' punch/reader 1 data port
        con1stat_port        =    $14        ' console 1 status port (on Keyboard / VGA or SIO #0)
        con1data_port        =    $15        ' console 1 data port (on Keyboard / VGA or SIO #0)
        con2stat_port        =    $16        ' console 2 status port (on SIO #1)
        con2data_port        =    $17        ' console 2 data port  (on SIO #1)
    
    



    There may be some advantages to this anyway, as currently the data comes in on user0, so if you do an xmodem to user2, I'm currently sending instructions to first change to user0, then xmodem, then PIP it to user2, then change back to user2. I haven't tested it yet, but maybe three versions of xmodem, xm0, xm1 and xm2 for each user?

    Addit: No don't delete the N8VEM ports - I still haven't got a replacement to work. Files still seem to end up in user 0. Need to edit xmodem code. Hmm - still need the N8VEM ports anyway for the CP/M version.

    It may just be a matter of changing variables MODCTLP, MODDATP and MODDATO in this xmodem code
    ;If you are using an external modem (not S-100 plug-in)
    ;change these equates for your modem port requirements
    ;
        .IF EXTMOD
    MODCTLP:.EQU 06DH        ; PUT YOUR MODEM STATUS PORT HERE
    MODSNDB:.EQU 20H        ; YOUR BIT TO TEST FOR SEND
    MODSNDR:.EQU 20H        ; YOUR VALUE WHEN READY
    MODRCVB:.EQU 01H        ; YOUR BIT TO TEST FOR RECEIVE
    MODRCVR:.EQU 01H        ; YOUR VALUE WHEN READY
    MODDCDB:.EQU 02H        ; CARRIER DETECT BIT                      *** DSR change status bit 
    MODDCDA:.EQU 00H        ; VALUE WHEN ACTIVE                    *** 0 = no change since 
    MODDATP:.EQU 068H        ; YOUR MODEM DATA IN PORT
    MODDATO:.EQU 068H        ; YOUR MODEM DATA OUT PORT
    MODCTL2:.EQU 0C1H        ; SECOND CONTROL/STATUS PORT.
        .ENDIF            ; END OF EXTERNAL MODEM .EQUATES
    
    



    BUT - maybe there is another way. You could xmodem the files between the PC and MP/M. Then you could reboot back into KyeDOS and xmodem the file between the PC and KyeDOS. Ok, that is two steps, but it would work with code already written.

    The reason I'm not doing this is that MP/M keeps crashing. And, I think that might be related to the interrupt timing.

    So I have gone through the code looking for variables that might affect the interrupt. First I tried changing variable timer_delta from 20x80000. At 20x80000 the probability of booting MP/M to the 0A> command prompt is 10/12. With timer_delta changed to 2*80000 the probability is 8/12 and at 200*80000 it is 8/12. Conclusion - timer_delta is not associated with this error.

    But I did have more luck adjusting the variable i1000 which appears in this code:
    output_simh_set_timer_delta
            rdlong    x, #0                    ' get clkfreq
            mov    y, i1000                ' divide by 1000
            mov    lmm_pc, #lmm_div32
            call    #lmm_func
            rdword    x, simh_params                ' get delta in milliseconds
            mov    lmm_pc, #lmm_mul32
            call    #lmm_func
            mov    timer_delta, x                ' delta in cycles
    
    



    The default is 1000 at which the probability of booting MP/M is 10/12. Change to 100 and it is 12/12. Change to 10000 and it is 0/12.

    Next is to work out is the threshold is a sudden change or more gradual. So I changed this variable to several different values and did lots of reboots.

    Then I plotted this on an x,y scatter plot using Excel. I think from this I can say that 100 is safe.

    My theory is that some interrupts are taking longer than others, and that another interrupt is arriving before the first one is finished. That is just a theory though.

    Also, changing the variable i1000 is a crude fix. Much better to change the value in the interrupt timer - but I think that is hidden in the modified source code for MP/M (I think pullmoll did change that code because part of the bootup for MP/M says "MP/M XIOS (qZ80 propeller V1.11 3HDs, Banked 15 Apr 10)"

    So what I am doing now is testing with i1000 set at 100, and seeing if xmodem transfers are more reliable.

    If they are.... then there is less of a need to try to write W.COM and R.COM - because you can always use xmodem and the PC and KyeDOS.

    Addit: got xmodem working on a different user. There is a variable RECU hidden away in the code. See attached. So now it can download to a particular user without having to PIP it over. Am still using the N8VEM port numbers - I need to test if it will work using the proper SIO port numbers.

    As an aside, changing i1000 to 100 has also fixed the unreliable PIP problem - all PIPs seem to go through reliably now.

    I tried changing the ports to the SIO for port1 but it does not work. So definitely need the N8VEM ports 6D and 68.

    But direct downloads to user area 1 and 2 is much quicker now for code development eg C and SBasic where the compilation is being done on the PC SIMH

    I'm now slowly working through the xmodem source code rewriting it for the different serial ports. It is complicated because the bits to test for input are different to a 16550 uart, and reading bytes works differently eg if you read a data byte from a uart it clears that byte, but if you read it from the software port it does not clear the byte. So it will need a few new lines of code. But when this is working, it will make downloads easier.

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    www.smarthome.viviti.com/propeller

    Post Edited (Dr_Acula) : 6/20/2010 11:00:33 PM GMT
    869 x 452 - 75K
  • pullmollpullmoll Posts: 817
    edited 2010-06-21 03:24
    @Dr_Acula: So you are effectively reducing the interrupt rate to 1/10th of the specified rate. The interrupt rate was set to 50 Hz, so it will now be running at 5 Hz. Not sure if and why MP/M works (better, at all) with this setting. The cleaner solution to change the rate would be to modify MPMXIOS.MAC and set the ticks to 5 there.

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    Pullmoll's Propeller Projects
  • Dr_AculaDr_Acula Posts: 5,484
    edited 2010-06-21 04:11
    Yes, the interrupt rate is now 5 per second. And MP/M is now *much* more reliable - no mysterious hangs, crashes etc.

    How do you recompile MP/M - is it with the standard version on the SIMH or do you have a modified version of MPMXIOS? If so, is that modified version on the A disk?

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    www.smarthome.viviti.com/propeller
  • pullmollpullmoll Posts: 817
    edited 2010-06-21 09:46
    Yes, there's the modified version on the A.DSK. You can edit it in Wordstar non-document mode and change the ticks value to 5. Then there's a submit file; I think it was mpm.sub, so do mpm should do the work.

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    Pullmoll's Propeller Projects
  • Dr_AculaDr_Acula Posts: 5,484
    edited 2010-06-21 14:55
    Yes that works. ticks=5. command is DO SYSMPM

    Initial tests = 100% bootup over 12 tries.

    How cool is this - recompiling a propeller operating system on the propeller itself?!

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    www.smarthome.viviti.com/propeller
  • pullmollpullmoll Posts: 817
    edited 2010-06-21 15:07
    Dr_Acula said...
    How cool is this - recompiling a propeller operating system on the propeller itself?!

    This is how it is supposed to be: boot strapping. You can be happy that you don't have to enter the initial loader into a hex console smile.gif

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    Pullmoll's Propeller Projects
  • Dr_AculaDr_Acula Posts: 5,484
    edited 2010-06-21 23:54
    Hi Pullmoll,

    Would you be able to re-release the A.DSK with MPM recompiled with the ticks set to 5?

    (Or I can create a file if you like).

    It has greatly increased the reliability of MPM.

    Attached is the latest version of xmodem for user 1. This has been tested with my vb.net program, and Teraterm and Hyperterminal. Baud rate is 38400. I have had some success at 57600 but occ errors creep in on user 2 at 57600. Maybe the cable is a bit long? Anyway, there really is not much point going over 38400 as the write speed of the sd card is the limiting factor.

    Changing the ticks from 50 to 5 has also fixed the strange bug where the LCD display would not work if the baud rate of the serial port 1 was 38400 (but would work at every other rate).

    Lots of things working nicely now.

    Of course, if you can xmodem files to and from user 1, you can then send them to user 0 or user 2 with
    PIP MYFILE.TXT[noparse][[/noparse]G2]=PIP MYFILE.TXT[noparse][[/noparse]G1]

    To make things easier, one could think about including XM1.COM on the A.DSK in the User 1 area.

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    www.smarthome.viviti.com/propeller

    Post Edited (Dr_Acula) : 6/22/2010 12:38:05 AM GMT
    xm1.zip 72.2K
  • Dr_AculaDr_Acula Posts: 5,484
    edited 2010-06-24 03:28
    Almost cracked it. Attached is xmodem for user 2. Works perfectly with Hypterterminal at 38k. Can send using Teraterm from the board to the PC. But can't receive via Teraterm - it hangs at packet 16 when it goes off to save data on the sd card.

    Teraterm has some other odd things too - it collects up the NAKs at startup, then if there are 5 NAKs that have accumulated, it sends the first packet 5 times. The proper protocol is supposed to clear those NAKs as they come in.

    The secret was to change the bit flags
    MODCTLP:.EQU 016H        ; PUT YOUR MODEM STATUS PORT HERE 
    MODSNDB:.EQU 02H        ; YOUR BIT TO TEST FOR SEND xxxxxx1x
    MODSNDR:.EQU 02H        ; YOUR VALUE WHEN READY 1 when ready 00000010 when ready
    MODRCVB:.EQU 01H        ; YOUR BIT TO TEST FOR RECEIVE xxxxxxx1
    MODRCVR:.EQU 01H        ; YOUR VALUE WHEN READY 00000001
    MODDCDB:.EQU 02H        ; CARRIER DETECT BIT    xxxxxx1x use bit 2, always assumes carrier on       
    MODDCDA:.EQU 02H        ; VALUE WHEN ACTIVE                    00000010
    MODDATP:.EQU 017H        ; YOUR MODEM DATA IN PORT
    MODDATO:.EQU 017H        ; YOUR MODEM DATA OUT PORT
    MODCTL2:.EQU 017H        ; SECOND CONTROL/STATUS PORT.
    
    



    and to rewrite the Gobble code
    ;Gobble up garbage chars from the line
    ;prior to receive or send
    ;
    ;    IN    A,(MODDATP)
    ;    IN    A,(MODDATP)
    
        CALL     GOBBLETWO
    
    



    and to remove gobble code in RECV
    ;---->    RECV: Receive a character
    ;
    ;Timeout time is in B, in seconds.  Entry via
    ;"RECVDG" deletes garbage characters on the
    ;line.    For example, having just sent a sector,
    ;calling RECVDG will delete any line-noise-induced
    ;characters "long" before the ACK/NAK would
    ;be received.
    ;
    RECVDG:    .EQU $            ; RECEIVE W/GARBAGE DELETE
    ;    IN    A,(MODDATP)    ; GET A CHAR
    ;    IN    A,(MODDATP)    ; ..TOTALLY PURGE UART
    ;    CALL     GOBBLETWO    ; code from MP/M instead *** no this causes problems with Send so comment it out **
    
    



    and to specifically only gobble two bytes, not indefinitely
    GOBBLETWO:        ; only gobble twice - same as the original xmodem
                ; this is quite time critical, and some terminals like Teraterm don't like delays
        IN A,(MODCTLP)    ; status byte for port2
        AND 01H        ; and with xxxxxxx1
        CP 01H        ; test bit 1, 0 if no bit, 1 if there is a bit
        RET NZ        ; return if nothing
        IN A,(MODDATP)    ; get the byte and discard
                ; do again
        IN A,(MODCTLP)    ; status byte for port2
        AND 01H        ; and with xxxxxxx1
        CP 01H        ; test bit 1, 0 if no bit, 1 if there is a bit
        RET NZ        ; return if nothing
        IN A,(MODDATP)    ; get the byte and discard
        RET
    
    



    The practical outcome is we now have XM1.COM for talking to serial port 1, and XM2.COM for talking to serial port 2.

    I've also attached the version of hyperterminal that I've been using as I think some versions of Windows do not have this.

    Addit: ShamCom seems to work too. Though they call upload download and vice versa.

    Another addit: The last screen shot is extremly nerdy. It shows MBASIC running on user 0. And at the same time, MBASIC is being downloaded to user 1 from Teraterm, and MBASIC is also being downloaded to user 2 using Hyperterminal. I have no idea how MP/M sorts out where those packets need to go but they are all getting there. It is nifty watching the download leds flashing. Hmm - a parallel chip running a serial operating system emulating parallel operating systems. Yummy!


    Addit: A question for Pullmoll.

    I'm working on a program running in User2 that can take packets from a serial port and pass them through to User0. I've got some things working, and I've sent a ^P to user0 from user2 and that is working fine with a DIR. All the bytes are coming back via this little bit of code
    Procedure ReadPrintBuffer
            var a,b=integer
            a=inp(12H)              rem is there a character ready?
            If a = 3 Then
                    begin
                            b=inp(13H)    rem read the data from the print buffer
                            OutPort2 b          rem send out the second serial port
                    End
    End
    
    



    The only problem is not everything seems to come back. If I run MBASIC, nothing comes back from within MBASIC. But when I go back to CP/M, all the CP/M bytes do go out (eg DIR and other CP/M programs)

    It seems that ^P is not capturing all the bytes that are going to the local vga screen. Specifically, it won't capture bytes from within other programs.

    I'm wondering if there is another way to capture bytes, perhaps capturing them from the code going to the vga monitor, rather than from within CP/M?

    Would it cost much in code to send all bytes to the vga screen, also to a circular buffer somewhere? Maybe just a head and tail pointer, and put the buffer in ram. Maybe not even switched on with a ^P - just always send them to this buffer too - I don't think it would slow down the vga screen much.

    Your thoughts would be most appreciated.

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    www.smarthome.viviti.com/propeller

    Post Edited (Dr_Acula) : 6/25/2010 8:34:49 AM GMT
    300 x 135 - 10K
  • pullmollpullmoll Posts: 817
    edited 2010-06-25 08:59
    Dr_Acula said...

    The only problem is not everything seems to come back. If I run MBASIC, nothing comes back from within MBASIC. But when I go back to CP/M, all the CP/M bytes do go out (eg DIR and other CP/M programs)

    It seems that ^P is not capturing all the bytes that are going to the local vga screen. Specifically, it won't capture bytes from within other programs.

    I'm wondering if there is another way to capture bytes, perhaps capturing them from the code going to the vga monitor, rather than from within CP/M?

    Would it cost much in code to send all bytes to the vga screen, also to a circular buffer somewhere? Maybe just a head and tail pointer, and put the buffer in ram. Maybe not even switched on with a ^P - just always send them to this buffer too - I don't think it would slow down the vga screen much.

    Your thoughts would be most appreciated.

    I think the other programs may use another BDOS function to emit characters, one which can not be redirected to another device.
    If the BIOS honours the IO byte (I think it was at $0004), then it should still be possible to redirect everything that usually goes to conout to e.g. printer - and thus our ring buffer, but I think the BIOS just ignores this byte. It would mean to add some code to MPMXIOS to switch outputs depending on the bits in the io byte.

    I think I could add code to store bytes that go to the vt100 terminal in another ring buffer. This would work in either setup, keyboard/VGA on user 0 or user 1 (or unused in CP/M). I'll see when I find time to add something like this.

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    Pullmoll's Propeller Projects
  • Dr_AculaDr_Acula Posts: 5,484
    edited 2010-06-25 13:56
    Cool, that would be great if you could put the VT100 display into another ring buffer (that can then be read in the same way as the existing ones)

    But only when you have time!

    What projects are you working on at the moment?

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    www.smarthome.viviti.com/propeller
  • pullmollpullmoll Posts: 817
    edited 2010-06-25 18:25
    Dr_Acula said...
    What projects are you working on at the moment?

    Windowze driver for the Canon EOS 550D to be accessed by an existing scanner software. Paid work pays...

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    Pullmoll's Propeller Projects
Sign In or Register to comment.