This text explain how to support a new device in the built-in programming engine
used by pikdev and pkp.

1) Concepts

Support of device (or devices set) is based on a plug-in framework. 
Each plug-in provides two types of informations: 
a) a list of  device names supported by this plugin,
b) basic algorithms for erasing, programming, reading and verifying a chip.
Optionnal algorithm can also be provided for handling special features
but are generally not needed. 

Please note that a plugin does not provide any information about memory sizes, availability
of specific features or structure of configurations words. These informations are automatically
retreived by pikdev from devices name.

The following of this document will explain how pikdev gets informations about devices, then 
how to write a plugin.

2) Devices "data base"

Informations about each supported devices are stored in a static array of structures of type deviceRecord 
named devDB. This array is included in pikdev/pkp executable via the header file "device_data.hh".
When a device type is selected, pikdev automatically make the informations about this device available for the plugin.
 
To add a new device, you must add a new element to this array.
Here is the beginning of this array. Each structure element is autodocumented, so is is not difficult to 
manually write such a code, excepted for config words description, which can by very cryptic.

// ---------------
deviceRecord devDB[] = { 
{
"p10f200"
, -1U // non autodetectable 
// config bits descriptor
,
// CONFIG (0xFFF)
"1(11-5)"
"{Unused\n}"
"MCLRE[4]"
"{Master Clear Enable\n"
"1=Functions as Master Clear\n0=Functions as GP3\n""}"
"CP[3]"
"{Code Protect\n"
"1=Off\n0=On\n""}"
"WDT[2]"
"{Watchdog Timer\n"
"1=On\n0=Off\n""}"
"1(1-0)"
"{Unused\n}"

, 0x00, 0xFF // program memory 
, 0x100, 0x103 // user ID data
, -1, -1 // EEPROM data
, 0xFFF, 0xFFF // CONFIG data
, 0xFF, 0xFF // calibration data words
, 1 // write buffer size 

, 12 // bits per word 
}
,
// ----------------

Fortunatly, this job can be done automatically by the pikdb application (available on pikdev site).
pikdb uses ".dev" files provided by Microchip which describe each device. Please use the documentation 
provided with pikdb for details.

Note that many unsupported devices are already in the device_data.hh file. 
In this case, updating this file is of course not required.

3) Howto write a device specific plug-in

A plug-in is an instance of a class inheriting (directly or not) from the class pic. 
As this class generally supports a set of devices, I recommand to use the names
genericXXX.hh for header, and genericXXX.cc for implementation. 
Please DO NOT use .cpp or .h for extension.

3-1) Getting and using the programming specifications

Programming specifications (PS) are available from Microchip website. You absolutely need this document, for each device you plan to program.
A PS generally covers more than one device: this point allows to support all the devices covered with only one plugin. 

The most   important points are 

a) programming diagrams (which explain programming algorithms, step by step)

b) commands mapping  list. 
A command may used alone, or followed by data. 
Data can be provided by the programmer itself (in this case it is a parameter of the command),
or by the device (in this case, it is a response to the command).
Some commands (such as "Load Configuration") need a parameter, but this one is not used by the device and can be dummy.
Commands are generally described as a bit flow, from Msb to Lsb. Be careful, some PS uses a Lsb to Msb order !!
A command contains 6 bits (or 4 bits), and thus can be view as a decimal number. Some bits are sometimes "dont't care". I recommend to set dont care bits to 0.

For example, the "Load Data from Program Memory" command can be specified by a PS as:

X X 0 0 1 0 and must be viewed as "command 2" from the plugin point of view.


Read the PS carefully because some devices (especially old ones) can be very tricky. 



3-2) Writing a plugin class

The basic method is to inherit from the pic class. This class is a mix of ordinary and pure virtual methods.
Ordinary methods can be viewed as a tool collection. 
Pure virtual methods corresponds to action which are specific to each device type, and cannot have any default implementation. You absolutely need to implement these methods.

The most important of them are:

a) virtual const char *names()  ; 

The most simple: just returns a string containing the namse of all devices this plugin is able to handle
example: return "p12f629 p12f675 p16f630 p16f676" ;
Names must be blank separated and  lowercase. They are gpasm compatible, ie: similar to names returned by the 
"gpasm -l" command. 
The MPASM style names like PIC12F629 are NOT allowed. 

b) virtual int erase_pic()  ; 

Totally erase the device. There is no "parial erase" function in the pikdev framework. However I strongly recommend to be careful about calibration informations. A calibration word is used in some devices to tune
oscillators frequency. Calibration data is factory initialized and should not be erased.
Depending on target device, the solutions are:
-use an erase function which preserve calibration data or
-read calibration data before erasing, then restore them.

c) virtual int program_pic(ostream& out) ; 

This method erases the device (flash devices only), then totally reprogram it. 
Data to be programmed are located in the "mem" member inherited from "pic" class. This member
is an (associative) array in which data are located at various indexes position.

Programmed data fall into one of four categories 
-program data (PIC code, from index memaddr1 to memadd2)
-configuration data (containing config bits, also known as fuse bits, from cfaddr1 to cfaddr2)
-eeprom data (data than can be read or altered by the PIC program, from eeaddr1 to eeaddr2)
-id data (a few words of data  identifyting application, from idaddr1 to idaddr2)

This method must perform a verification of written informations. 
This step can be done after the writing pass via the "verify_pic()" function described below, or can be interlaced with programming pass. However, the former solution generally leads to simpler code.

In case of programming errors, error messages can be written on the "out" stream. I recommend to use the "warning" method of the "pic" class to format error messages in a consistant way.


d) virtual void read_pic() ; 

Reads the device and store data in the "mem" array. Any previous contents of the "mem" array is
discarded.
 
d) virtual int verify_pic(ostream& out) ;

Verify the device content and compare it against the "mem" array. Any difference must be reported
as a message written on the "out" stream, and an appropriate error code return (see below for error code values).

The easier way to implement this function is to
-save the "mem" array in a temporary, by an STL swap call.
-read the device with "read_pic()"
-compare "mem" against the temporary.
-restore "mem"

If you do not directly inherit from "pic" class, but from a more specialized one, this
 method is likely to be already (correctly) implemented.


Errors codes returned by "verify_pic()", "read_pic()" or "erase_pic()" are formed by ORing the following values:

0 == No error detected
1 == program memory error
2 == EEPROM data error
4 == ID error
8 == config fuses error
64 == calibration word error

This error code allows pikdev GUI to display the proper diagnostic.

3-3) Communication API

All plugin methods communicate with the programmer hardware thru a single method:

int pulseEngine(const char *cmd, unsigned int *word=0) ;

This method is a simple interpreter receiving a command string "cmd" and an optional int parameter "word".
Technically being a pointer, "word" can be used as input or output parameter.

The "cmd" string is a sequence of letters acting as instructions for the hardware. Some of them take a parameter, passed by the "word" parameter. 

Instructions can be categorized as follow:

a) low level instructions

C  = clock Hi
c  = clock low
D  = data Hi
d  = data low
P  = power (5V) on
p  = power off
B  = Vpp (13V) on
b  = Vpp low
D  = entering debug mode : each command is echoed on stdout.
d  = exiting debug mode

b) general  instructions

wnnnnn = wait for nnnnn microseconds (nnnnn is decimal, ie: w10000 = 10mS)
, = a shorthand for w1 

S  = send data word (data is pointer to by word)
R  = read data word (data is pointer to by word)
s  = send byte (data is pointer to by word)
r  = read byte (data is pointer to by word)

F  = fake word read (read from pic, but don't send back via *word,
          keep the value for subsequent Verify, see V command )
f  = dummy byte read (read from pic, but don't send back via *word,
          keep the value for subsequent verify, see v command)

knn  = send command nn to PIC (nn is decimal, ex: k6 for increment address)

v  = verify byte data (compare previously readed byte  against *word & 0xFF)
V  = verify word data (compare previously readed word  against *word)
?  = if a V or v command report a mismatch between *word value and chip value, this command
          returns the chip value. Useful to format error messages. 

;  = NOP (does nothing, can be used as separator for readability - useful in debug mode)

c) PIC 18Fxxx  specific instructions

xhh  = send a 8 bits constant (hh must be a 2 digits hex constant, ex: xA0)      
Xhhhh  =  send a 16 bits constant (hhhh must be a 4 digits hex constant, ex: XF055)  


The value returned by "pulse_engine()" is the number of errors found during a ? command.
The error count is reset to 0 at each "pulseEngine()" call.

3-4) All right, but I an a little bit lost. I need a step by step example.

Well, the better way to write a new programming plugin is to read other plugins.
More precisely, prefer to read the code of a plugin concerning a device which looks like the device you
plan to support. 

As a rule of thumb, begin to select devices of the same family (base line, midrange, etc..), then 
compare the command table of the target device against the already supported ones. 
If most of the command numbers are the same, is is likely you will not have to rewrite all the code.
Once you have identified a good candidate, use this class either as a  base class, or a template class.
ATTENTION: The term template does not refer here to C++ template, but just to the fact I will cut and paste
code, then customize it to my needs.

Here is an example: the target device being a 16F610.

The most recent programming specification (at time of writing) for this chip 
is referenced DS41284C (dated 2007). Please refer to the document, which also covers 12F609, 12F615, 16F616, 12HV609, 12HV615, 16HV610 and 16HV616. The objective is to supports all these devices.

After a look at the collection of PS I have got, an acceptable similar device is 12F629. Despite its name, this is a midrange device. As a consequence  I will use the generic12F629_675 class as a (kind of) template. 
Just copy generic12F629_675.cc and generic12F629_675.hh
files to  generic12F609_615.cc  and generic12F609_615.hh respectively.

The next step is to implement methods, one at a time. This point is very important: do not try to implement all the methods, it will never works at the first try and you will get a lot of frustration.

a) Fix identifiers
Replace each occurence of generic12F629_675 by generic12F609_615, everywhere in the code.
Do not forget to modify the preprocessor guard variable to  fit the file name.

#ifndef generic12F609_615_HH
#define generic12F609_615_HH
...

b)  "names()" method: this is easy

const char * generic12F609_615::names()
{
  return "p12f609 p12f615 p16f616 p16f610 p12hv609 p12hv615 p6hv610 p16hv616" ;
}

c) Inject the new plugin into pikdev/pkp

Edit the PicFactory.cc file, and add
    
		#include "generic12F609_615.hh"

in the header section
and 
		registerPic(new generic12F609_615) ;

in the code. Your plugin will be taken into account by pikdev.

Do not forget to add these two files to your project. Then try to compile. Once successfully compiled/linked
pikdev will be able to recognize your device (click "detect" button in the programming window: the device name and revision number should be displayed).

d) "erase_pic()" implementation.


Fortunatly, the erase algorithms described in PS, page 8 shows it
is not necessary to read and store  the calibration word (located at addr 0x2008)
to preserve it. The code is quite simple. Here is a detailled listing, with comments.

int generic12F609_615::erase_pic()
{
	unsigned int dummy = 0x3FFF ;

	// this is for pikdev progress bar not mandatory, but useful. 
	// It means that this operation contains two phases.
	progress_status = 2 ;
	// classic power up sequence :
	// set clock and datalines low,
	// Power up the programmer, wait 1 us
	// set VPP hi, wait 1 us
	pulseEngine ( "cdP,B," ) ;
	// step finished
	--progress_status ;
	// enter load configuration mode (cmd 0), wait 1us, 
	// send dummy argument, wait
	// send "Bulk erase program memory" (command 9) and wait 6ms
	pulseEngine ( "k0,S,k9w6000", &dummy ) ;
	// shutdown:
	// VCC low, VPP low
	// data and clock low
	pulseEngine ( "bpcd" ) ;

	// step finished
	--progress_status ;

	return 0 ;
}

