External Links

Creative Science Centre


Rookie 3

** AS from 10 August, (BvSerail 17) these files have been broken into smaller files and can be included into a file, this page will be updated soon ***

  • rookie 1: This is for the PIC32MX150 or PIC32MX340 running firmware 2.0n
  • rookie 2: This is for the PIC32MX170 ro PIC32MX370 running firmware 2.3n it is backwards compatible with rookie 1 and so should be used for the tutorials which were originally designed for rookie 1
  • rookie 3: This should be used for new projects it is much smaller and leaner than rookie 2 but it will not run software designed for rookie 1

This version takes full advantage of the ability to pass variables by reference including arrays. It makes it much easier to define hardware using constants. There is a version for the MX170 and MX370 processors. For the MX150 and MX340 then user either rookie 1 or rookie 2

<< back to Rookie 2

Quick link: GPIO, PPS, Tables, ADC, Timers, I2C, SPI, Interrupts, UART

This text is a description of the rookie functions, the rookie firmware is in the download section


The GPIO for the PIC has mainly three elements, ADC, I/O and Interrupts. The ports are designated A through G but not all processors have all of the ports:

  • MX170(DIL) uses PORTA through PORTB
  • MX170(SMD) uses PORTA through PORTC
  • MX370(SMD) used PORTB through PORTG

As an example of how to use the GPIO constants, suppose we have a reset on PORTB pin 6 and a chip enable of PORTC pin 2

constant RST {PORTB(PORT),6}
constant CE   {PORTC(PORT),2}

or alternatively:

constant RST {RB6(PPORT),RB6(PVAL)}
constant RST {RC2(PPORT),RB6(PVAL)}

To set the pins:


it is possible to refer to the pins directly, e.g:


GPIO Functions

The following can be used for:

inorout - either IN, OUT (must be upper case)

pull - WOFF of no resistor, WPU for weak pull up resistor, WPD for wak pull down resistor. These refer to input only but WOFF is still needed in specificing an output as it keep the functions for in and out consistant.

value - In general this is 0 for low, 1 for high and 2 for toggle

  • io_pinMode(*port(),pin,ionorout,pull) Removed - use rookie2
  • io_pinRole(*pin(),ionorout,pull) Prefered use over pinMode
  • io_pinSet(*pin(),value) Sets pin to high, low or toggle [1]
  • io_pinGet(*pin()) Returns value of a pin, this will be either 0 or 1 [2]
  • io_read(*port(),pin) Removed - use rookie2
  • io_write(*port(),pin,value) Removed - use rookie2
  • io_getPort(*port()) Returns the value of a whole port, port is *PORTB(),*PORTC() etc. [3]
  • io_setPort(*port(),theBits) Sets the value of a whole port [4]
  • io_setPortH(*port(),theBits) Sets the value of only those bits specified in theBits [5]
  • io_setPortL(*port(),theBits) Clears the bits of only those bits specified in theBits [6]
  • io_ir(*ir(), pri, func$[40], pin) Sets the interrupt on change for a port pin [7]
Sets pin on port [1]

io_pinSet(*port(), value)

NOTE: To use this the pin must have been set to an output first

io_pinSet(*RB5(),0) Sets Pin 5 on PORT B low
io_pinSet(*RB5(),1) Sets Pin 5 on PORT B high
io_pinSet(*RB5(),2) Toggle Pin 5 on PORT B

Get pin on port [2]


NOTE: To use this the pin must have been set to an input first

io_pinGet(*RB5()) Sets Pin 5 on PORT B low

Gets the whole port as a 32 bit number [3]

x = io_getPort(port)

Example: x = io_gePort(*PORTB()) // gets all pins on PORTB

Sets the whole port [4]

io_setPort(port, value)

Example io_setPort(*PORTB(), 3) // sets pins 0,1 to high, the rest low

This will set only those pins on the port that have the high bit set on the value [5]


In the following example only bits 0 and 1 are set, to 1 the other bits on the port are left alone

Example io_setPortH(*PORTB(), 3)

This will clear only those pins on the port that have the high bit set on the value [6]


In the following example only bits 0 and 1 are cleared, to 0 the other bits on the port are left alone

Example io_setPortL(*PORTB(), 3)

Interrupt on Change [7]

io_ir(*port(), pri, func$[40], pin)

This is the function that will associate a pin change with a user defined function:

*port() is PORTn
pri is the priority 0 to 7 but 3 should be used
function_name$ is the name of a user defined function, see the example
pin is the pin that will change to cause the interrupt

Example: Make hello world print out every time pin RB6 changes

1) create a function to be called

function hello()
  io_pinGet(*RB6()) // reading port clears interrupt
  print "\nHello world"

2) Set the pin to be an input and use a weak pull up so that it will only respond when we connect it to ground


3) create an entry into the interrupt table

ir_io(*PORTB(),3,"hello",6) Note PORT and pin are specified separately

Each time pin RB6 is shorted to ground an interrupt will occur that will output Hello World

When using ports and pins it is better to describe them for the system concerned. As an example suppose we had a switch connected to port B1 and an LED connected to C3, rather than using the port names, use the actula names as follows:

constant   SWITCH  {PORTB(PORT),1}
constant   LED        {PORTC(PORT),3}


Peripheral Pin Select (PPS) Applies to MX170 & MX370

This map refers to the MX170 devices. The SMD version of the MX170 also has port C shown in grey

  Peripheral Pin
Inputs INT4,T2CK,IC4,SS1, PRA0  PRB3  PRB4  RB15  RB7  RRC7  RRC0  RRC5 
Outputs U1TXs,U2RTSs,SS1s,OC1s,C2OUTs
Outputs SDO1s,SDO2s,OC2s
Outputs SDO1s,SDO2s,OC4s,OC5s,REFCLKO
Outputs U1RTSs,U2TXs,SS2s,OC3s,C1OUTs

This map refers to the MX370 devices. ** NOTE RPxx, above table uses PRxx

  Peripheral Pin          
Outputs U3TXs,U4RTSs,SD02s,OC3s,C2OUTs
Outputs SD02s,U2TXs,U1TXs,U5RTSs,SD01s,OC4s
Outputs SDO1s,U3RTSs,U4TXs,REFCLK0s,U5TXs,SS1s,OC5s,C1OUTs
Outputs SD01s,U5TXs,U2RTSs,U1RTSs,SS2s,OC2s,OC1s

Special NOCONNECTs (see pps_out)

NOTES: The above are constants and should be prefixed with '*' when used in the pps functions, notice also that the output peripheral constants have a 's' postfix.

This function associates an input peripheral to a pin

pps_in(*peripheral(), *pin())

This function associates an output pin to a peripheral

pps_out(*pin(), *peripheral())

There is a special peripheral called NOCONNECTs. This is to disconnect an output peripheral as one output peripheral can be directed to more than one port. This is particularly useful for redirecting UART2.

How to Use

Example 1: Set up external interrupt 2, which is an input to be on RA4

pps_in(*INT2(), *PRA4()) // that's it, you will still need to set up the interrupt

Example 2: On all the boards UART1 is left for the user to use as UART is used for the main communication with the IC. So on the MX1 device we can set up on a choice of pins:

pps_out(*PRA0(), *U1TXs())
pps_in(*U1RX(), *PRA2())

The above will have U1TX coming out of pin RA0 and the input to the UART will be on pin RA2

To use meaning full names when using the PPS a similar syntax is used as for the ports above. As an example, using the MX370 table setting up UART1 on port D2 and D3 could be:


A better way is to define the port and pins as a constant, then it is easy to change them if required.

constant   RX   {RPD2(0),RPD2(1)}
constant   TX   {RPD3(0),RPD3(1)}



Note: Unlike rookie 1 and 2, this does not use automatic conversion and so the adc_get is different, see below.

MX170 Devices
PORT    PIC    Channel
RA0      AN0      0
RA1      AN1      1
RB0      AN2      2
RB1      AN3      3
RB2      AN4      4
RB3      AN5      5
RC0      AN6      6   ** SMD Device 44 pin only
RC1      AN7      7   **
RC2      AN8      8   **
RB12    AN12     12
RB13    AN11     11
RB14    AN10     10
RB15    AN9      9

MX370 devices as follows
Chan, pin, Port
AN0  16  RB0
AN1  15  RB1
AN2  14  RB2
AN4  13  RB3
AN5  12  RB4
AN6  11  RB5
AN7  17  RB6
AN8  18  RB7
AN9  21  RB8
AN10 22  RB9
AN11 23  RB10
AN12 24  RB11
AN13 27  RB12
AN14 28  RB13
AN15 29  RB14
AN16 30  RB15
AN17 4   RG6
AN18 5   RG7
AN19 6   RG8
AN20 8   RG9
AN21 62  RE1
AN22 64  RE4
AN23 1   RE5
AN24 49  RD1
AN25 50  RD2
AN26 51  RD3
AN27 3   RE7

Initialise the ADC system for the particular channel, the channel number is given in the table as a range as above


Example adc_init(6) // this will set RB12 to be an adc input

This only needs to be called once.

Gets the results of the adc channel
x = adc_get(channel,sample,timeout)

The above will get the results of an ADC conversion. The conversion begins when the function is called and will start with the sampling time. This is an arbitrary value, 1,000,000 will give about 1 second. After the sample period a conversion will take place. This is internally times but if the channel has not been initialised it can get stuck and so a timeout is specified. If it does get stuck the value returned will be 0 after the timeout period. The timeout of course must give the internal timer chance to do the conversion, 1000 should be adequate, of course the function returns as soon as the conversion is complete so a larger value will only take effect on a 'stuck' conversion.

x=adc_get(1,100,1000) Example for getting channel 1



TIMER1 ** This timer from 2.08/76 on is used for scheduling (tasks)

TIMER23 - This is the timer 2 and 3 combination - up to 214 seconds
TIMER45 - This is the timer 4 and 5 combination - up to 214 seconds

A timer has three main parameters, the configuration that is set by timer_init(), the value of the current timer which when enabled is counting up, refered to in the data sheet as TMR register. This is controlled by tmr_clr() and tmr_get() and also the count limit, refered to in the data sheet as PR.

  • tmr_init(*tmr(),prescale,limit) Initialises and starts timer [1]
  • tmr_stop(*tmr())
  • tmr_start(*tmr())
  • tmr_get(*tmr()) Gets the count value of the timer (register TMRn)
  • tmr_clr(*tmr())  Clears TMR - restes the timer back to 0
  • tmr_limit(*tmr(),limit) Sets register PRn [2]

tmr_init(*timer(),prescale,limit) [1]

A timer can be specified by these two values. When the limit is reached the TMR register resets and starts counting up again from zero. Also a flag is set that can be either read or used by the interrupt system.

Calculate period for pre-scale assuming 40MHz perif clock
prescale             counts / ms      1 second   *32 bits needed  Timer 1 pre
256 (7) =  6.4 us    156.25         156250                               (3)
64   (6) =  1.6 us    625             625000   *                           (2)
32   (5) =  0.8 us    1250           1250000  *
16   (4) =  0.4 us    2500           2500000  *
8    (3) =  0.2 us    5000            5000000  *                          (1)
4    (2) =  0.1 us    10000          10000000 *
2    (1) =  0.05 us   20000         20000000 *
1    (0) =  0.025us   40000        40000000 *                          (0)

The table gives the pre-scale value (in brackets) that can be used for the given counts per mS, timer 1 has different pre-scale values. NOTE: any value over 0xffff (65,635) requires a 32 bit combination timer.

tmr_limit(*tmr(),limit) [2]

This will simply set the PRn resister to some other value from that which was set using the tmr_init() function.

Timer Interrupts

ir_set(*interrupt(), pri,function_name$)

This is the function that will associate a timer interrupt with a user defined function:

*interrupt() is TIMERn
pri is the priority 0 to 7 but 3 should be used

Example: Make hello world print out every 10 seconds

1) create a function to be called

function hello()
  print "\nHello world"

2) Set the timer to interrupt every 10 seconds, this will need a 32 bit timer


3) create an entry into the interrupt table


The interrupt beigins imediately and will cause "hello World" to be output every 10 seconds. Note just the "hello" is used to specify the function.



Currently defined interrupts are for the ports (interrupt on change) and the timers. The necessary attributes to make these work are either in the PORTx contants or TIMERnn contants.

The following will clear all of the interrupts and remove then from the interrupt table.


An interrupt will call a user function and so setting up an interrupt involves selecting why an interrupt occurs (timer, external etc.) and then pointing this to a user function that will be called when the interrupt happens.


Globally enables interrupts, this is only needed after the use of ir_di() as the interrupts are normally enabled.


There is another type of interrupt 'set' that applies to interrupt on change. The interrupt in this case is different for MX1 and MX3 devices.


When working with interrupts, particularly those that work with a timer. the interrupt will be active all of the time or at least after the user program has run. This will effect the download process and so the interrupt should be stopped for that, one way to do this is to reset the device before downloading but it is easy to forget to do so. The simplest way is to put ir_clear() in the send string ot the tload dialog box like this:

(dialog box from the .tl command of BvSerial)


This is mainly implemented for the MX170 as currently the MX370 has I2C functions as part of the ByPic but this may change. ** Only channel 1 is implemented

SCL1 is RB8
SDA1 is RB9
SDA2 is RB2 **
SCL2 is RB3 **


// SCL1 is RG2
// SDA1 is RG3

I2c must be opened before use, this opens it at the requested frequency, normally 100000 or 400000

This will send a start condition followed by a byte that is the address of the slave I2C device you want to communicate with. The address is the 8 bit address and for writing should be an even number but for reading should be an off number.

Puts a byte on to the I2C bus, the device must have been addressed first using i2c_start() with an even address.

This closes the I2C bus for that data exchange

This is the restart command, stop and start can be used instead.
i2c_rstart(address) // Not available on MX340

This will get a byte from the I2C bus, this device actually does the clocking and so the slave needs some additional information. Last is set to 0 if there are more bytes to get, set to 1 if this is the last byte required from the slave. Prior to using theis the start function must be used with an odd address.
x = i2c_getc(last)

A utility that will return the first address (between start and end) of any device that may happen to be on the bus.
i2c_find(start,end) ** This does not work on the MX370 devices

Example: print i2c_find(0x30,0xf0) // searches for addresses between 0x30 and 0xf0


Serial Peripheral Interface.

BV500 Hardware

// Data out SDO1 is RB13
// Data in  SDI1 is RB8
// Clock SCK1 is RB14

BV513,BV514,BV523 Hardware (MX3) Channel 2 implemented

On this hardware SPI channel 2 is already initialised and so spi_init() will simply set the speed, the default initialised speed is 10 MHz.

// Data out SDO2 is RG8 (MOSI)
// Data in  SDI2 is RG7 (MISO)
// Clock SCK2 is RG6

// Initialises the SPI bus, speed is in HZ, for 10MHz the speed value will be 10000000
spi_init(speed)  // for 8 bits serial

spi_init16(speed) // for 16 bits serial

spi_init32(speed) // for 32 bits serial

Transfers data, with the SPI interface data is transferred in and out at the same time, by convention id just receiving is required the output byte is normally set to 0xff

Example: x = spi_data(0xff) // gets a byte from the SPI bus 
                     spi_data(0x33) // send 0x33 to the SPI bus



UART2 is always initialised as this is part of the operating system and is used for general communication. The port pins used are.

MX170 TX=RB10,  RX=RB11
MX370 TX=RF5, RX=RF4

Because of the many options the UART is implemented as the ByPic documentation e.g. keywords. It is important also the PPS is used, to give an example set pins RF2 as RX and RF3 as TX

comopen(1,115200,250) // open com 1, 115200 Baud and a 250 byte buffer
// rx,tx
pps_in(*U1RX(),*PRF2()) // RX pin set to U1RX
pps_out(*PRF3(),*U1TXs()) // TX pin set to U1TX (NOTE the U1TXs)

// the following may not be necessary
io_pinRole(*RF2(),IN,WOFF) // Set RF2 as an input
io_pinRole(*RF3(),OUT,WOFF) // Set RF3 as an output