PICCommsLibrary

From RepRap
Revision as of 20:01, 5 May 2006 by SimonMcAuliffe (talk) (version migrated from twiki)
Jump to: navigation, search

PIC Comms Library

Introduction

In a RepRap, all of the electronics are comprised of fairly independent modules. In order for the modules to co-operate and communicate there is a network connecting each module and also connecting to the host PC. The PIC Comms library is some general purpose communications software that is installed into each of the modules to provide a simple high level interface to the network communications. The idea is that the interface is abstract enough so that if we change the underlying network architecture at some point in the future, the library can be swapped and the rest of the software in each module will remain unchanged. More specifically, it currently implements a ring topology packet based network and is used to send simple small packets between the devices on the network. There are plans in the future to possibly change this to a more efficient bus topology, but that's probably some time away.

Getting It

Subversion Location: /reprap/firmware

Status: Basics working

Work to be done:

  • Timeouts and re-sends are not quite there.
  • The API has changed a bit and this page is no longer correct, so this page should be updated.

Using the library

Synopsis:

Most of the functionality is encapsulated in serial_inc.c. Only a few lines of code are necessary to make use of the serial routines. A simple example is below (most of the code is just processor initialisation etc).

The general structure is:

  • Initialisation
  • Main loop
    • Is there a message?
      • Process message and possibly reply
    • Do other main loop work if needed

All messages are protocol-independent. The first byte is the command, and the following bytes are command dependent. All devices must implement command 0 (get version). There are no operands for the request and the response includes at least two bytes, the minor and major version numbers. For more details, see the device messages page.

The API is kept simple so that if the underlying implementation changes in the future (eg to i2c), code will not need to be changed.

Receiving a message:

Example if (processingLock) { printf("Received command %d", buffer[0]); releaseLock(); }

Receiving is typically performed in the main idle loop. The reception itself is completely automatic and happens in the background, driven by the RS-232 interrupts. When a valid packet for the device is received it is stored in a packet buffer and the buffer is locked. When this happens, the global boolean value processingLock will be set. The packet payload is available in the global byte array buffer. The buffer contains only the user message, not any protocol or packet information.

When any processing of the payload is complete, the buffer can be unlocked by calling the releaseLock() function. Another packet cannot be received until the buffer is released.

Sending messages:

Example sendReply(); sendDataByte(0); sendDataByte(1); endMessage();

A new message is started by calling one of two functions:

sendMessage(destAddress); or sendReply();

sendMessage(byte) requires a single parameter which is the destination address to send the packet to. sendReply() requires no parameters and starts a new packet destined for the sender of the most recently received packet.

Payload is transmitted by calling the sendDataByte(byte) function with each byte of content. The endMessage() function indicates that there is no more content and the packet will be transmitted.

Transmission occurs in the background, controlled by interrupts. This means the foreground application is not held up while the packet is delivered (and possibly NAKd, re-delivered, etc).

Other requirements:

In the main interrupt service routine, it is important to call the serialInterruptHandler() function, otherwise no reception or transmission will occur.


API details

Global variables

extern byte processingLock

Contains the value 0 when no packet data is avaiting processing or 1 when complete and valid packet data is available.

extern byte buffer[16];

Contains the actual user payload portion of the packet. This is only valid when processingLock is 1.

Functions (alphabetical order)

void awaitDelivery();

Not yet implemented

byte deliveryStatus();

Not yet implemented

void endMessage();

Indicates that all data queued for a message is now complete and the packet details can be finalised and transmitted.

void releaseLock();

Indicates that processing of a received packet is complete and a new packet can be received.

void sendDataByte(byte byteToSend);

Queues a single byte into the current message. Prior to calling this function, sendMessage or sendReply must have first been called.

void sendMessage(byte destinationAddress);

Starts a new message to the given node

void sendReply();

Starts a new message to the sender of the most recent message

void serialInterruptHandler();

Whenever an interrupt occurs, this should be called so the serial routines can do any necessary work.

Minimal application:

THE FOLLOWING IS NOT COMPLETE YET, DON'T TRY IT BECAUSE IT PROBABLY WON'T WORK. It will be cleaned up and finished soon. // Select device #define __16f627 #include <pic/pic16f627.h> #include "pic14.h" typedef unsigned int config; config at 0x2007 __CONFIG = _CP_OFF & _WDT_OFF & _BODEN_OFF & _PWRTE_ON & _INTRC_OSC_CLKOUT & _MCLRE_OFF & _LVP_OFF; // This is the address that will messages will be accepted for byte deviceAddress = 2; // Support routines for bank 1 #include "serial-inc.c" static void isr() interrupt 0 { serialInterruptHandler(); } void processCommand() { switch(buffer[0]) { case 0: // Command 0 is a standard "get version" message that all devices implement sendReply(); // Start a reply to the current packet sendDataByte(0); // Return the version number in bigendian format as major-minor sendDataByte(1); endMessage(); // Complete and send the message break; } } void main() { OPTION_REG = BIN(11011111); // Disable TMR0 on RA4, 1:128 WDT CMCON = 0xff; // Comparator module defaults TRISA = BIN(00110000); // Port A outputs (except 4/5) // RA4 is used for clock out (debugging) // RA5 can only be used as an input TRISB = BIN(00000110); // Port B outputs (except 1/2 for serial) PIE1 = BIN(00000000); // All peripheral interrupts initially disabled INTCON = BIN(00000000); // Interrupts disabled PIR1 = 0; // Clear peripheral interrupt flags SPBRG = 25; // 25 = 2400 baud @ 4MHz TXSTA = BIN(00000000); // 8 bit low speed RCSTA = BIN(10000000); // Enable port for 8 bit receive TXEN = 1; // Enable transmit RCIE = 1; // Enable receive interrupts CREN = 1; // Start reception PEIE = 1; // Peripheral interrupts on GIE = 1; // Now turn on interrupts PORTB = 0; PORTA = 0; T1CON = BIN(00000000); // Timer 1 in clock mode with 1:1 scale TMR1IE = 1; // Enable timer interrupt init(); // Clear up any boot noise from the TSR uartTransmit(0); for(;;) { // This is the main processing loop. // You would normally put your main application // in here. // In this case, there's nothing to do so we just // loop endlessly (this is not normally a cool thing // to do, but this is the world of microcontrollers and // it's okay, except for the fact we don't do any // power saving. [We could perhaps extend this to // wake up on serial interrupt?] // If there is a message waiting, we should process it if (processingLock) { // A message is waiting processCommand(); // Process command releaseLock(); // Release buffer } } }

In the examples, why are there two C files for each device?:

This is related to an sdcc restriction on register allocation. See the sdcc documentation for further explanation.