SPI_Ethernet code weirdness

General discussion on mikroBasic PRO for PIC.
Post Reply
Author
Message
bcdaus
Posts: 59
Joined: 17 Aug 2007 11:26

SPI_Ethernet code weirdness

#1 Post by bcdaus » 16 Apr 2009 13:04

I have a Olimex Pic-Web-Mini that I used to test the SPI_Ethernet demo from the examples folder. I changed the config to suit the pcb and the processor clock etc.

I made some changes to add some custom header text etc, but when I try to add some style information the page will no longer render - the browser just hangs waiting for the page to load. The processor has not crashed as I can still get to the other pages to see the text values etc, but when I add the following line to the start of the indexPage2 string def:

Code: Select all

const indexPage2 as string[861] =
                    "</HEAD><BODY> style=" + chr(34) + "font-family: verdana; " + chr(34) + ">" + ' why does this not work?
I tried to replace the colon and semi-colon with the CHR statements with the correct value for the ascii code.

Note there are some commented out lines in this code where I tried a few different things. I triedd to call the css style sheet as part of an inline define in the HTML code. I could browse to the file using the /c path in the URL, but when the main page loaded it just hung again. I've used ethereal to try to get a trace and it seems the unit does not respond to the html request.

I am going to try adding as serial lcd to the unit to see if I can track what is going on, but just wondered if anyone has seen this ? I am really stumped. Is it possible I am hitting the limit of the TCP frame as we don't have fragmentation. that might things very interesting to try to fit a nice page in a 1470 byte packet !!

Code: Select all

' *
' * Project Name:
'     enc28j60Demo (Ethernet Library demo for ENC28J60 mcu)
' * Target Platform:
'     PIC
' * Copyright:
'     (c) mikroElektronika, 2006.
' * Revision History:
'     20060810:
'       - Initial release. Author: Bruno Gavand.
' *
' * V1.0 : first release
' * V1.1 : bad MIME type for / path request, changed to HTML instead of SCRIPT (thanks Srdjan !)

' v2 Bill Coghill
' - modified for use with PIC-MINI-WEB PCB
' - redefined hardware with button on RB0 and LED on RC2
' - also changed the piouts for the ENC chip to suit PCB -
' ENC ETH_INT - RB2, ETH_CS - RB3, ETH_RST - RB5, EEPROM_CS - RB4
' changing the pins will toggle RA1-5 pins
' 15.04.09 Modified to try to include a CSS for the HTML file

' *
' * description  :
' *      this code shows how to use the Spi_Ethernet mini library :
' *              the board will reply to ARP & ICMP echo requests
' *              the board will reply to UDP requests on any port :
' *                      returns the request in upper char with a header made of remote host IP & port number
' *              the board will reply to HTTP requests on port 80, GET method with pathnames :
' *                      /               will return the HTML main page
' *                      /s              will return board status as text string
' *                      /t0 ... /t7     will toggle RD0 to RD7 bit and return HTML main page
' *                      all other requests return also HTML main page
' *
' * target devices :
' *      any PIC with integrated SPI and more than 4 Kb ROM memory
' *      32 to 40 MHz clock is recommended to get from 8 to 10 Mhz SPI clock,
' *      otherwise PIC should be clocked by ENC clock output due to ENC silicon bug in SPI hardware
' *      if you try lower PIC clock speed, don't be surprised if the board hang or miss some requests !
' *
' * EP settings :
' *      RA2 & RA3 pots jumper : closed
' *      PORTA : pull-down  (place jumper J1 to lower position)  (board specific)
' *      PORTB : pull-down  (place jumper J2 to lower position)  (board specific)
' *      PORTC : pull-down  (place jumper J3 to lower position)  (board specific)
' *      BUTTONS : pull-up  (place jumper J17 to upper position)  (board specific)
' *
' *      mE Serial Ethernet board on PORTC
' *      RC0 : !RESET    to ENC reset input pin
' *      RC1 : !CS       to ENC chip select input pin
' *      the ENC28J60 SPI bus CLK, SO, SI must be connected to the corresponding SPI pins of the PIC
' *      the INT and WOL signals from the ENC are not used *
' * Test configuration:
'     MCU:             PIC18F4520
'     Dev.Board:       EasyPIC5
'     Oscillator:      HS-PLL4, 08.000MHz
'     Ext. Modules:    mE Serial Ethernet board
'     SW:              mikroBasic PRO for PIC
' * NOTES:
'     - Since the ENC28J60 doesn't support auto-negotiation, full-duplex mode is
'       not compatible with most switches/routers.  If a dedicated network is used
'       where the duplex of the remote node can be manually configured, you may
'       change this configuration.  Otherwise, half duplex should always be used.
'     - External power supply should be used due to Serial Ethernet Board power consumption.
' *

program enc_ethernet

' ***********************************
' * RAM variables
' *

' mE ehternet NIC pinout
dim
  SPI_Ethernet_Rst as sbit at RB5_bit
  SPI_Ethernet_CS  as sbit at RB3_bit
  SPI_Ethernet_Rst_Direction as sbit at TRISB5_bit
  SPI_Ethernet_CS_Direction  as sbit at TRISB3_bit
' end ethernet NIC definitions

dim myMacAddr   as byte[6]   ' my MAC address
    myIpAddr    as byte[4]   ' my IP address
    gwIpAddr    as byte[4]   ' gateway (router) IP address
    ipMask      as byte[4]   ' network mask (for example : 255.255.255.0)
    dnsIpAddr   as byte[4]   ' DNS server IP address

' ************************************************************
' * ROM constant strings
' *
const httpHeader as string[31]         = "HTTP/1.1 200 OK"+chr(10)+"Content-type: "  ' HTTP header
const httpMimeTypeHTML as string[13]   = "text/html"+chr(10)+chr(10)                 ' HTML MIME type
const httpMimeTypeScript as string[14] = "text/plain"+chr(10)+chr(10)                ' TEXT MIME type
const httpMethod as string[5]          = "GET /"
' *
' * web page, splited into 2 parts :
' * when coming short of ROM, fragmented data is handled more efficiently by linker
' *
' * this HTML page calls the boards to get its status, and builds itself with javascript
' *

const cssPage as string[64]=
                             '"<style>
                 '       "PING!"
                             "body {font-family: Tahoma, Verdana, sans-serif; font-size: 10pt}" '</style>"
                       ' "<style>BODY, TABLE, SELECT " + chr(123) + "font-family" + chr(58) +" Tahoma, Verdana, sans-serif" + chr(59) + " font-size" + chr(58) +" 10pt" + chr(59) +  chr(125) +
                     ' "<style>BODY, TABLE, SELECT {font-family: Tahoma, Verdana, sans-serif; font-size: 10pt }" +
                      '"BODY { margin-top: 0px " + chr(59) + " background: #f0f0f0" + chr(59) + " color: black" + chr(59) + " } H1 { font-size: 14pt" + chr(59) + " margin: 0px" + chr(59) + " } " +
                      '"H2 { font-size: 13pt" + chr(59) + " margin: 0px" + chr(59) + " }  TABLE.Menu { border-top: 1px solid #000080" + chr(59) + " border-bottom: 1px solid #000080" + chr(59) + " } " +
                      '"TD.MenuItem { background: #ffffff" + chr(59) + " color: #000080" + chr(59) + " text-align: center" + chr(59) + " font-weight: bold" + chr(59) + " width: 16%" + chr(59) + " cursor: pointer" + chr(59) + " } " +
                      '"TD.MenuCurrent { background: #000080" + chr(59) + " color: #ffffff" + chr(59) + " font-weight: bold" + chr(59) + " text-align: center; width: 20%" + chr(59) + " } " +
                      '"A.MenuCurrent { background: #000080" + chr(59) + " color: #ffffff" + chr(59) + " text-decoration: none" + chr(59) + " } " +
                      '"A.MenuCurrent:hover { color: #ffff7f" + chr(59) + " background: inherit" + chr(59) + " } A.MenuItem { background: #ffffff" + chr(59) + " color: #000080" + chr(59) + " text-decoration: none" + chr(59) + " } " +
                      '"A.MenuItem:hover { color: #4040ff" + chr(59) + " background: inherit" + chr(59) + " } " +
                      '"TABLE.Sensors { border: 1px solid #c0c0c0" + chr(59) + " margin-top: 1ex" + chr(59) + " margin-bottom: 1ex" + chr(59) + " background: #f0f0f0" + chr(59) + " } " +
                      '"p.bad { background: #FF3333" + chr(59) + " color: #FFFFFF" + chr(59) + " } p.warn { background: #FFAA33" + chr(59) + " color: #000000" + chr(59) + " } " +
                      '"p { margin: 0px" + chr(59) + " } .cc { font-size: 8pt" + chr(59) + " color: #000000" + chr(59) + " } .cfg { font-size: 12pt" + chr(59) + " font-weight: bold" + chr(59) + " color: #000000" + chr(59) + " } " +
                      '"table.alarms { border-collapse: collapse" + chr(59) + " margin: 0px" + chr(59) + " padding: 0px" + chr(59) + " width: 260px" + chr(59) + " } table.alarms tr { padding: 0px" + chr(59) + " margin: 0px" + chr(59) + " } " +
                      '"table.alarms tbody { padding: 0px" + chr(59) + " margin: 0px" + chr(59) + " } table.alarms td { padding: 5px 0 0 0" + chr(59) + " text-align: center" + chr(59) + " width: 80px" + chr(59) + " } " +
                      '"table.alarms td.ch { padding-right: 5px" + chr(59) + " text-align: right" + chr(59) + " white-space: nowrap" + chr(59) + " width: 120px" + chr(59) + " } " +
                      '"table.alarms input { width: 70px" + chr(59) + " } table.alarms input.save { width: 100px" + chr(59) + " text-align: center" + chr(59) + " }
                      '"</style>"


const indexPage as string[92] =
                    "<meta http-equiv=" + Chr(34) + "refresh"  + Chr(34) + " content="  + Chr(34) + "url=http://192.168.100.9"  + Chr(34) + ">" +
                    "<HEAD>" +
                 '  "<style type=text/css> body {font-family: Tahoma, Verdana, sans-serif; font-size: 10pt; }</style>" +
                    "<script src=/s></script>" '" 'LINK rel=" + chr(34) + "stylesheet" + chr(34) + "type=" + CHR(34) + "text/css href=" + Chr(34) + "c.css" + Chr(34) + ">"

const indexPage2 as string[861] =
                    "</HEAD><BODY>" + ' style=" + chr(34) + "font-family: verdana; " + chr(34) + ">" +[/b] ' why does this not work? ' ;font-family: verdana ;" + chr(34) + ">" +
                    "<center><table width=" + chr(34) + "100%"+ chr(34) + "><tr><td align=left><h1>Test Power Distro</h1><h2>bcd v0.1</h2>IP Address:&nbsp;<b>192.168.100.9</b></td><td align=right>" +
                    "<h1>bluecog PIC Mini Web Server</h1>" +
                    "<a href=/>Reload</a></td></tr></table><HR>" +
                    "<table><tr><td valign=top><table border=1 style="+chr(34)+"font-size:20px ;font-family: terminal ;"+chr(34)+"> " +
                    "<tr><th colspan=2>ADC</th></tr>" +
                    "<tr><td>AN2</td><td><script>document.write(AN2)</script></td></tr>" +
                    "<tr><td>AN3</td><td><script>document.write(AN3)</script></td></tr>" +
                    "</table></td><td><table border=1 style="+chr(34)+"font-size:20px ;font-family: terminal ;"+chr(34)+"> " +
                    "<tr><th colspan=2>PORTB</th></tr>" +
                    "<script>" +
                    "var str,i;" +
                    "str="+chr(34)+chr(34)+"; " +
                    "for(i=0;i<8;i++)" +
                    "{str+="+chr(34)+"<tr><td bgcolor=pink>BUTTON #"+chr(34)+"+i+"+chr(34)+"</td>"+chr(34)+"; " +
                    "if(PORTB&(1<<i)){str+="+chr(34)+"<td bgcolor=red>ON"+chr(34)+";}" +
                    "else {str+="+chr(34)+"<td bgcolor=#cccccc>OFF"+chr(34)+";}" +
                    "str+="+chr(34)+"</td></tr>"+chr(34)+";}" +
                    "document.write(str) ;" +
                    "</script>"

const indexPage3 as string[469] =
                    "</table></td><td>"+
                    "<table border=1 style="+chr(34)+"font-size:20px ;font-family: terminal ;"+chr(34)+"> "+
                    "<tr><th colspan=3>PORTD</th></tr>"+
                    "<script>"+
                    "var str,i;"+
                    "str="+chr(34)+chr(34)+"; "+
                    "for(i=0;i<8;i++)"+
                    "{str+="+chr(34)+"<tr><td bgcolor=yellow>LED #"+chr(34)+"+i+"+chr(34)+"</td>"+chr(34)+"; "+
                    "if(PORTD&(1<<i)){str+="+chr(34)+"<td bgcolor=red>ON"+chr(34)+";}"+
                    "else {str+="+chr(34)+"<td bgcolor=#cccccc>OFF"+chr(34)+";}"+
                    "str+="+chr(34)+"</td><td><a href=/t"+chr(34)+"+i+"+chr(34)+">Toggle</a></td></tr>"+chr(34)+";}"+
                    "document.write(str) ;"+
                    "</script>"+
                    "</table></td></tr></table>"+
                    "This is HTTP request #<script>document.write(REQ)</script></BODY></HTML>"

dim    getRequest  as byte[15]   ' HTTP request buffer
       dyna        as byte[30]   ' buffer for dynamic response
       httpCounter as word       ' counter of HTTP requests
       txt         as string[11]

' *******************************************
' * user defined functions
' *

' *
' * this function is called by the library
' * the user accesses to the HTTP request by successive calls to Spi_Ethernet_getByte()
' * the user puts data in the transmit buffer by successive calls to Spi_Ethernet_putByte()
' * the function must return the length in bytes of the HTTP reply, or 0 if nothing to transmit
' *
' * if you don't need to reply to HTTP requests,
' * just define this function with a return(0) as single statement
' *
' *
sub function Spi_Ethernet_UserTCP(dim byref remoteHost as byte[4],
    dim remotePort, localPort, reqLength as word) as word
  dim  i as word        ' my reply length
       bitMask as byte  ' for bit mask
       txt         as string[11]
    result = 0
    if(localPort <> 80) then          ' I listen only to web request on port 80
      result = 0
      exit
    end if

    ' get 10 first bytes only of the request, the rest does not matter here
    for i = 0 to 10
      getRequest[i] = Spi_Ethernet_getByte()
    next i

    getRequest[i] = 0

    ' copy httpMethod to ram for use in memcmp routine
    for i = 0 to 4
      txt[i] = httpMethod[i]
    next i

    if(memcmp(@getRequest, @txt, 5) <> 0) then  ' only GET method is supported here
      result = 0
      exit
    end if

    Inc(httpCounter)                           ' one more request done

    if(getRequest[5] = "s") then               ' if request path name starts with s, store dynamic data in transmit buffer
      ' the text string replied by this request can be interpreted as javascript statements
      ' by browsers
      
      result = SPI_Ethernet_putConstString(@httpHeader)                    ' HTTP header
      result = result + SPI_Ethernet_putConstString(@httpMimeTypeScript)   ' with text MIME type

      ' add AN2 value to reply
      WordToStr(ADC_Read(2), dyna)
      txt  = "var AN2="
      result = result + Spi_Ethernet_putString(@txt)
      result = result + Spi_Ethernet_putString(@dyna)
      txt  = ";"
      result = result + Spi_Ethernet_putString(@txt)

      ' add AN3 value to reply
      WordToStr(ADC_Read(3), dyna)
      txt  = "var AN3="
      result = result + Spi_Ethernet_putString(@txt)
      result = result + Spi_Ethernet_putString(@dyna)
      txt  = ";"
      result = result + Spi_Ethernet_putString(@txt)

      ' add PORTB value (buttons) to reply
      txt  = "var PORTB="
      result = result + Spi_Ethernet_putString(@txt)
      WordToStr(PORTB, dyna)
      result = result + Spi_Ethernet_putString(@dyna)
      txt  = ";"
      result = result + Spi_Ethernet_putString(@txt)

      ' add PORTD value (LEDs) to reply
      txt  = "var PORTD="
      result = result + Spi_Ethernet_putString(@txt)
      WordToStr(PORTD, dyna)
      result = result + Spi_Ethernet_putString(@dyna)
      txt  = ";"
      result = result + Spi_Ethernet_putString(@txt)

      ' add HTTP requests counter to reply
      WordToStr(httpCounter, dyna)
      txt  = "var REQ="
      result = result + Spi_Ethernet_putString(@txt)
      result = result + Spi_Ethernet_putString(@dyna)
      txt  = ";"
      result = result + Spi_Ethernet_putString(@txt)
    else
    if(getRequest[5] = "t") then                         ' if request path name starts with t, toggle PORTD (LED) bit number that comes after
        bitMask = 0
        if(isdigit(getRequest[6]) <> 0) then               ' if 0 <= bit number <= 9, bits 8 & 9 does not exist but does not matter
          bitMask = getRequest[6] - "0"                    ' convert ASCII to integer
          bitMask = 1 << bitMask                           ' create bit mask
          PORTA   = PORTA xor bitMask                      ' toggle PORTD with xor operator
          if bitmask.1 = 1 then portc.2 = 1 else portc.2 = 0 end if
        end if
      'end if
    else
    if(getRequest[5] = "c") then                         ' if request path name starts with c, then send CSS sheet
      portc.2 = 1
      result = SPI_Ethernet_putConstString(@httpHeader)                    ' HTTP header
      result = result + SPI_Ethernet_putConstString(@httpMimeTypeScript)   ' with text MIME type
      result = result + SPI_Ethernet_putConstString(@cssPage)                ' HTML page CSS part
      end if
    end if
   end if
    if(result = 0) then ' what do to by default
      result = SPI_Ethernet_putConstString(@httpHeader)                  ' HTTP header
      result = result + SPI_Ethernet_putConstString(@httpMimeTypeHTML)   ' with HTML MIME type
      result = result + SPI_Ethernet_putConstString(@indexPage)          ' HTML page first part
    '  result = result + SPI_Ethernet_putConstString(@cssPage)                ' HTML page CSS part
      result = result + SPI_Ethernet_putConstString(@indexPage2)         ' HTML page second part
      result = result + SPI_Ethernet_putConstString(@indexPage3)         ' HTML page second part
    end if
    
    ' return to the library with the number of bytes to transmit
end sub

' *
' * this function is called by the library
' * the user accesses to the UDP request by successive calls to Spi_Ethernet_getByte()
' * the user puts data in the transmit buffer by successive calls to Spi_Ethernet_putByte()
' * the function must return the length in bytes of the UDP reply, or 0 if nothing to transmit
' *
' * if you don't need to reply to UDP requests,
' * just define this function with a return(0) as single statement
' *
' *
sub function Spi_Ethernet_UserUDP(dim byref remoteHost as byte[4],
                              dim remotePort, destPort, reqLength as word) as word
  dim txt as string[5]
    result = 0
    ' reply is made of the remote host IP address in human readable format
    byteToStr(remoteHost[0], dyna)            ' first IP address byte
    dyna[3] = "."
    byteToStr(remoteHost[1], txt)             ' second
    dyna[4] = txt[0]
    dyna[5] = txt[1]
    dyna[6] = txt[2]
    dyna[7] = "."
    byteToStr(remoteHost[2], txt)             ' second
    dyna[8]  = txt[0]
    dyna[9]  = txt[1]
    dyna[10] = txt[2]

    dyna[11] = "."
    byteToStr(remoteHost[3], txt)             ' second
    dyna[12] = txt[0]
    dyna[13] = txt[1]
    dyna[14] = txt[2]

    dyna[15] = ":"                            ' add separator

    ' then remote host port number
    WordToStr(remotePort, txt)
    dyna[16] = txt[0]
    dyna[17] = txt[1]
    dyna[18] = txt[2]
    dyna[19] = txt[3]
    dyna[20] = txt[4]
    dyna[21] = "["
    WordToStr(destPort, txt)
    dyna[22] = txt[0]
    dyna[23] = txt[1]
    dyna[24] = txt[2]
    dyna[25] = txt[3]
    dyna[26] = txt[4]
    dyna[27] = "]"
    dyna[28] = 0

    ' the total length of the request is the length of the dynamic string plus the text of the request
    result = 28 + reqLength

    ' puts the dynamic string into the transmit buffer
    Spi_Ethernet_putBytes(@dyna, 28)

    ' then puts the request string converted into upper char into the transmit buffer
    while(reqLength <> 0)
      Spi_Ethernet_putByte(Spi_Ethernet_getByte())
      reqLength = reqLength - 1
    wend
    
    ' back to the library with the length of the UDP reply
end sub

main:
  ADCON1 = 0x0B           ' ADC convertors will be used with AN2 and AN3
  CMCON  = 0x07           ' turn off comparators

  PORTA  = 0
  TRISA  = 0 'xFF           ' set PORTA as input for ADC

  PORTB  = 0
  TRISB  = 0xFF           ' set PORTB as input for buttons

 '' PORTD  = 0
  'TRISD  = 0              ' set PORTD as output

  httpCounter = 0

  ' set mac address
  myMacAddr[0] = 0x00
  myMacAddr[1] = 0x14
  myMacAddr[2] = 0xA5
  myMacAddr[3] = 0x76
  myMacAddr[4] = 0x19
  myMacAddr[5] = 0x3F

  ' set IP address
  myIpAddr[0] = 192
  myIpAddr[1] = 168
  myIpAddr[2] = 100
  myIpAddr[3] = 9
  
  ' set gateway address
  gwIpAddr[0]  = 192
  gwIpAddr[1]  = 168
  gwIpAddr[2]  = 100
  gwIpAddr[3]  = 1

  ' set dns address
  dnsIpAddr[0] = 192
  dnsIpAddr[1] = 168
  dnsIpAddr[2] = 100
  dnsIpAddr[3] = 1

  ' set subnet mask
  ipMask[0]    = 255
  ipMask[1]    = 255
  ipMask[2]    = 255
  ipMask[3]    = 0

'   *
'   * starts ENC28J60 with :
'   * reset bit on PORTC.B0
'   * CS bit on PORTC.B1
'   * my MAC & IP address
'   * full duplex
'   *

  SPI1_Init()  ' init spi module
  SPI_Ethernet_Init(myMacAddr, myIpAddr, _SPI_Ethernet_FULLDUPLEX)           ' init ethernet module
  SPI_Ethernet_setUserHandlers(@SPI_Ethernet_UserTCP, @SPI_Ethernet_UserUDP) ' set user handlers
 ' is this what we can do here ?
 ' SPI_Ethernet_doDHCP
 ' if SPI_Ethernet_DHCPReceive = 0 then

  ' dhcp will not be used here, so use preconfigured addresses
  SPI_Ethernet_confNetwork(ipMask, gwIpAddr, dnsIpAddr)

  while TRUE                      ' do forever
    SPI_Ethernet_doPacket()       ' process incoming Ethernet packets

'     *
'     * add your stuff here if needed
'     * SPI_Ethernet_doPacket() must be called as often as possible
'     * otherwise packets could be lost
'     *
  wend
end.

]

Thanks for your help !

bill.

User avatar
zristic
mikroElektronika team
Posts: 6608
Joined: 03 Aug 2004 12:59
Contact:

Re: SPI_Ethernet code weirdness

#2 Post by zristic » 16 Apr 2009 13:44

The only solution for you is to use the ehternet sniffer program to see what is PIC sending to web browser. In that way you may see what is wrong with the generated html page.

bcdaus
Posts: 59
Joined: 17 Aug 2007 11:26

#3 Post by bcdaus » 17 Apr 2009 06:45

Using ethereal it looks like we are getting fragmentation issues - so the reply must be longer than the tcp/ip frame limit.

If I reduce the size of the html code code I can get it to work. Is there going to be support for packet fragmentation added to this library, or should I look to trying something else ?

User avatar
srdjan
mikroElektronika team
Posts: 1552
Joined: 28 Dec 2005 12:47
Location: Serbia

#4 Post by srdjan » 21 Apr 2009 08:33

Hi,
bcdaus wrote:Using ethereal it looks like we are getting fragmentation issues - so the reply must be longer than the tcp/ip frame limit.

If I reduce the size of the html code code I can get it to work. Is there going to be support for packet fragmentation added to this library, or should I look to trying something else ?
Fragmented packets are not supported at the moment. There will be a full tcp/ip stack implementations, but not deadlines at the moment.

bcdaus
Posts: 59
Joined: 17 Aug 2007 11:26

#5 Post by bcdaus » 21 Apr 2009 08:49

Good to know, but please work on the dsPic basic update first !!
I can get around the limit by adding things like links to style sheets and includes files.

Right now I am also working on a Telent interface - are you going to add Telnet as an option as I believe Florin (YO2LIO) has a telnet library for the ENC28J60, but I am not sure his library it is compatible with the new PRO version.

bill

Post Reply

Return to “mikroBasic PRO for PIC General”