Seeking code for low-voltage programming of enhanced midrange PIC

Thread Starter

John P

Joined Oct 14, 2008
2,025
I'm trying to write code for a PIC16F690 to program a PIC16F1619--that's an "enhanced midrange" processor which has a low-voltage programming mode, with no need for anything above 5V. The way you get it to work is to pull Reset low, then send a 32-bit code which supposedly puts it into programming mode. So far I've just tried it for the first time, and as far as I can tell, my target processor is staying inert. I have a scope, and I know the PIC16F690 is doing what I've told it to. Before I put more time into this, I thought maybe someone has already got it working and might give me some code. I'm working in Mikro C, but I'm sure any language would make it clear what's supposed to happen.
 

andre_teprom

Joined Jan 17, 2016
31
Have you already checked how the programmer it self could interfere on the status of the core ? Some of them allow you to determine by roper configuration if the reset will be released or not after upload the code to uC.
 

Thread Starter

John P

Joined Oct 14, 2008
2,025
You're not understanding my setup. I'm using one processor to program another, without a commercially produced programmer of any kind. And so far, I'm keeping the target processor in reset all the time: what I want to see is a response to a "read" operation, which would confirm that I've got control of the processor.
 

ErnieM

Joined Apr 24, 2011
8,377
All the code I have ever needed is inside the executables of whatever programmer I use. I have PICkit II and III, plus an ICD 3 somewhere.

If you are attempting to program your device without a programmer I suggest you stop and get a proper programmer. You will get the added benefit of doing debugging inside your hardware if you can leave it connected.

Otherwise yes it is barely possible to program these devices without a programmer as they come with low voltage programming enabled. The spec is only 38 pages long: http://ww1.microchip.com/downloads/en/DeviceDoc/40001720C.pdf
 

MMcLaren

Joined Feb 14, 2010
861
Hi John P,

I tried this a couple years ago but couldn't quite get it to work... Last week however I spotted a project on Hackaday.io by Jaromir Sukuba that uses an Arduino to program PIC16F1xxx 'enhanced mid-range' devices using this newer LVP process. Anyway, since this Gentleman got it working I downloaded his 'sketch' and I plan to study the heck out of it while comparing it to the Microchip programming documents.

I'll wish you good luck on your project if you'll do the same for me, Sir.

Cheerful regards, Mike
 

andre_teprom

Joined Jan 17, 2016
31
@john, I believe you are already aware of that, but it's worth to point that. Had you already decreased the clock rate to check if could be happening a load coupling problem among I/Os of both devices ?
 

Thread Starter

John P

Joined Oct 14, 2008
2,025
Mike, thanks for the link to the Hackaday project. I've downloaded the code and I'm working through it, and in fact it's C code, which is even better than an Arduino sketch. I'll let you know how things go.

Ernie, I think you're being a bit too conservative. It can be handy to be able to program a processor with minimal equipment--ideally, just another processor, and the low-voltage mode is very tempting. I can live without debugging, but you'll get my oscilloscope when you pry my cold dead fingers off the trigger select knob.

What I've done since posting the first message is to accept that I need to delay the low-voltage programming and just get something working. (Question of keeping up the morale of the troops. I'm sure everyone understands.) So I put together a charge pump running off the PWM output of the PIC16F690, and with that creating a Vpp of about 8V I've been able to establish communication between the processors. So my failure must be with the 32-bit code that enables low-voltage programming mode. I'll try tinkering with it some more, with the Hackaday project to copy.
 

MMcLaren

Joined Feb 14, 2010
861
John, I dug up my code (from 2010, oh my). At the time I was concerned whether or not I was sending the 32 bit "MCHP" pattern (plus one extra clock) correctly to enter programming_mode. Based on Mr. Sukuba's code, it looks like I was... Anyway, I'm going to dig out the prototyping stuff and try this again...

Mike

LVP unlock sequence.png
 

dannyf

Joined Sep 13, 2015
2,197
The best place for such information is the datasheet / programming specification.

The quickest is to repurpose the pickit2 source code.
 

Thread Starter

John P

Joined Oct 14, 2008
2,025
I was most concerned about how to get the target processor into programming mode using the low-voltage method, because my version of that didn't work, and the instructions in the programming manual aren't entirely clear about the order of data to be sent. So naturally that's the first thing I looked at in Jaromir Sukuba's C program; thanks for that pointer, Mike! What I saw is that evidently he had his doubts too, because he wrote the code out 4 different ways with variations, and he must have been ready to comment blocks out and try another one in turn. I wonder how many attempts it took him. Or maybe each time he failed, he'd comment out the code and rewrite it, and he got it right the 4th time.

Code:
unsigned char enter_progmode (void)
{
ISP_MCLR_0
_delay_us(300);
/*
isp_send(0b10110010,8);
isp_send(0b11000010,8);
isp_send(0b00010010,8);
isp_send(0b00001010,8);
*/
/*
isp_send(0b01001101,8);
isp_send(0b01000011,8);
isp_send(0b01001000,8);
isp_send(0b01010000,8);
*/
/*
isp_send(0b00001010,8);
isp_send(0b00010010,8);
isp_send(0b11000010,8);
isp_send(0b10110010,8);
*/

isp_send(0b01010000,8);
isp_send(0b01001000,8);
isp_send(0b01000011,8);
isp_send(0b01001101,8);

isp_send(0,1);

}
 

NorthGuy

Joined Jun 28, 2014
611
It takes a while because you don't know if the entry was successful or not until you successfully read something. To make it a little bit easier, program your target with PICKit3 so that you know the first word programmed into the program memory. Then you can clock in the sequence (don't forget the extra 33-rd clock) and then do a read command. As a result of this read, you should read the first word of the program memory, which is known to you, so you will know right away if you're reading correctly. Once you get this right, you know your sequence is correct and you can continue on.
 

ErnieM

Joined Apr 24, 2011
8,377
Look, I've actually made a programmer as part of a test fixture for a PIC based timer. It first programs the device then performs a full acceptance test on it. So I know making a programmer is possible.

I also know I had debuggers and programmers to keep track of what was going on... not only to verify what the target was getting for a program but to watch what the programmer (another PIC) was up to.

While I have the source to look at it is proprietary for a job I did a few years back, thus I cannot release it.

This is not a beginners or even intermediate level task. Collecting bottles for nickles will get you a programmer faster than writing your own. But I can give a few hints as I see them...

Before you program some known data and try to verify it try this: do the Load Configuration command of section 4.3.1 to point to the configuration area. The 6th thing in there is always the device ID, something you should be checking for before programming anyway.
 

MMcLaren

Joined Feb 14, 2010
861
Look, I've actually made a programmer as part of a test fixture for a PIC based timer. It first programs the device then performs a full acceptance test on it. So I know making a programmer is possible.
Fun stuff, isn't it? My first PIC HV programmer design used a 16F88. I remember finding a bug in the 18F2620/18F4620 Programming Spec' which was reported to and corrected by Microchip and then I helped MELabs with a work-around for their Serial Programmer. Here's a picture of the prototype...

Cheerful regards, Mike

PPP 16F88.png
 
Last edited:

Thread Starter

John P

Joined Oct 14, 2008
2,025
I'd have included a picture of the programmer I made years ago, but I think I dumped it.

Anyway, the progress I've made is that after using high-voltage programming mode just once, I got the low-voltage mode working, with some inspiration from Jaromir Sukuba's code. To convince myself that it works, I used pretty much the method that Ernie suggested, go into the config area and advance the address 6 times, so the active address is the chip ID, and then read that. Now if I'm serious about this, I have to write code to do the actual programming, and that's going to take some time, with work to be done on the PIC16F690 and on the computer, where I have to write something to open a file and send it via a pseudo-serial port on a USB link. I have a UART converter based on the FTDI chip to connect to the PIC.

My program for the PIC16F690 is below, though I'm not sure how useful it would be to anyone. Points to note are that I send data both ways in packets, with a start character 0xFF, an opcode, a receiver/sender address, the number of bytes, the data, and a checksum. So far I'm not using data at all, just looking for particular opcodes. And I like circular buffers, one each for data sent and received. I often make up my own pointers for these and use the FSR register directly.

Code:
#define indirect  indf

#define set_high_bank  status.f7 = 1
#define set_low_bank  status.f7 = 0

#define MY_ADDR  0
#define GOOD_PACKET  100

#define TARGET_POWER_ON  1  // a
#define TARGET_POWER_OFF  2  // b
#define TARGET_RESET_LOW  3  // c
#define TARGET_RESET_HIGH  4  // d
#define ENTER_PROG_MODE  5  // e
#define PWM_ON  6  // f
#define PWM_OFF  7  // g
#define READ_SAME  8  // h
#define PROGRAM_DATA  9  // i Data from packet gets loaded to memory at current address
#define ADDR_ZERO  10  // j Issue RESET_ADDR command
#define ADDR_CONFIG  11  // k Issue LOAD_CONFIG command
#define GOTO_ADDRESS  12  // Issue enough INC_ADDR commands to reach this address (error if new address is < existing)

#define LOAD_CONFIG  0
#define LOAD_DATA  2
#define READ_DATA  4
#define INC_ADDR  6
#define RESET_ADDR  0x16
#define INT_PROG  8
#define EXT_PROG  0x18
#define END_EXT  0xA
#define BULK_ERASE  9
#define ROW_ERASE  0x11

typedef unsigned char byte;

/* High memory usage
Low bank (0xA0 - 0xEF):
  0xA0 - 0xAF
  0xB0 - 0xBF
  0xC0 - 0xDF  Outgoing serial buffer (incoming_push, incoming_pop)
  0xE0 - 0xEF

High bank (0x20 - 0x6F):
  0x20 - 0x6F  Incoming serial buffer
*/

byte incoming_push, incoming_pop, incoming_opcode, incoming_nbytes, incoming_address, checksum, tbyte;
byte outgoing_push, outgoing_pop;
byte ser_state=0;
byte portc_shadow;

byte outgoing_buffer[32] absolute 0xC0;  // Dummy array to make sure compiler doesn't use this area

byte serial_in(void)
{
  byte i;

  set_high_bank;
  fsr = incoming_pop;
  i = indirect;
  incoming_pop++;
  if (incoming_pop >= 0x70)
  incoming_pop = 0x20;
  set_low_bank;

  switch(ser_state)
  {
  case 1:
  if (i == 0xFF)
  ser_state = 0;  // Fail, assume the 0xFF was starting a new packet
  else
  incoming_opcode = i;  // Opcode
  break;
  case 2:
  if (i == 0xFF)
  ser_state = 0;  // Fail, assume the 0xFF was starting a new packet
  else
  incoming_address = i;  // Addressee
  break;
  case 3:
  if (i == 0xFF)
  ser_state = 0;  // Fail, assume the 0xFF was starting a new packet
  else
  {
  incoming_nbytes = i;  // Number of bytes
  checksum = incoming_opcode + incoming_nbytes + incoming_address;
  tbyte = 0;
  }
  break;
  case 4:
  if (tbyte == incoming_nbytes)  // Seen final data byte
  {
  if ((checksum == i) && (incoming_address == MY_ADDR))
  ser_state = (GOOD_PACKET - 1);
  else  // Checksum passes
  ser_state = 0xFF;  // Failed packet
  break;
  }
  else
  {
  if (tbyte < 9)
  {
  // incoming[tbyte] = i;
  tbyte++;  // We can't accept data that overfills the array
  }
  }
  checksum += i;
  if (i != 0xFF)
  ser_state--;  // Normal condition, value of 4 in ss is repeated}
  break;
  case 5:  // Last input was 0xFF
  if (i == 0xFF)  // 0xFF again, OK
  ser_state = 3;  // Next ss is 4, expect regular data
  else
  {
  ser_state = 1;  // Fail, assume the 0xFF was starting a new packet
  incoming_opcode = i;  // and this was the opcode, so next ss is 2
  }
  break;
 
  default:  // Uaually serial_state is 0 here
  ser_state = 0;
  if (i != 0xFF)
  ser_state--;  // Fail, set ss so it gets reset to 0
  break;
  }  // End switch-case
  ser_state++;  // Return 0 for trash chars between packets
  return(ser_state);  // GOOD_PACKET for complete packet
}  // Any other value for packet in progress

void interrupt( void)
{
  txreg = 0x55;
  //bit_clear(pir1, TMR2IF);
}

void send_prog_instr(byte command, unsigned dataa)
{
  int i;

  if ((command == READ_DATA) || (command == INC_ADDR) || (command == RESET_ADDR) ||
  (command == INT_PROG) || (command == EXT_PROG) || (command == END_EXT))
  command.f7 = 1;  // Flag says no data, just a 6-bit command

  for (i = 0; i < 6; i++)
  {
  if (command.f0 == 1)
  portc_shadow.f7 = 1;
  else
  portc_shadow.f7 = 0;
  portc_shadow.f6 = 1;
  portc = portc_shadow;
  portc_shadow.f6 = 0;
  portc = portc_shadow;

  command >>= 1;
  }
  if (command.f1 == 1)  // Test flag, return if no data required
  return;

  dataa <<= 1;
  for (i = 0; i < 16; i++)
  {
  if (dataa & 1)
  portc_shadow.f7 = 1;
  else
  portc_shadow.f7 = 0;
  portc_shadow.f6 = 1;
  portc = portc_shadow;
  portc_shadow.f6 = 0;
  portc = portc_shadow;

  dataa >>= 1;
  }
}

void xmit_byte(byte input)
{
  fsr = outgoing_push;
  indirect = input;
  outgoing_push++;
  outgoing_push.f5 = 0;
}

void read_data (void)
{
  int i;
  union
  {
  unsigned int in_data16;
  byte in_data8[2];
  } ind;

  send_prog_instr(READ_DATA, 0);
  ind.in_data16 = 0;
  trisc.f7 = 1;
 
  for (i = 0; i < 16; i++)
  {
  if (portc.f7 == 1)
  ind.in_data16 |= 0x4000;
  portc_shadow.f6 = 1;
  portc = portc_shadow;
  portc_shadow.f6 = 0;
  portc = portc_shadow;

  ind.in_data16 >>= 1;
  }
  trisc.f7 = 0;

  xmit_byte(0xFF);
  xmit_byte(0);
  xmit_byte(MY_ADDR);
  xmit_byte(2);
  xmit_byte(ind.in_data8[1]);
  xmit_byte(ind.in_data8[0]);
  xmit_byte(MY_ADDR + 2 + (ind.in_data8[0]) + (ind.in_data8[1]));
}

void enter_prog (void)
{
  unsigned long mchp32 = ('M' << 24) + ('C' << 16) + ('H' << 8) + 'P';
  byte i;

  for (i = 0; i < 33; i++)
  {
  portc_shadow.f7 = 0;
  if (mchp32 & 1)
  portc_shadow.f7 = 1;
  portc_shadow.f6 = 1;
  portc = portc_shadow;
  portc_shadow.f6 = 0;
  portc = portc_shadow;
 
  mchp32 >>= 1;
  }
 
  send_prog_instr(LOAD_CONFIG, 0);  //Load config, addr now 0x8000
  for (i = 0; i < 6; i++)
  send_prog_instr(INC_ADDR, 0);  // Increment addr to 0x8006 (chip ID)
  read_data();
}
 
void main() {
  byte i;

  trisa = 0b00000000;  // 0-5 available
  trisb = 0b00100011;  // 4-7 available, bit 5 is RX, bit 7 is TX
  trisc = 0b00000010;  // 0-7 available
  portc_shadow = 0;
  portc = portc_shadow;  // Target reset high

  ansel = 0b00000000;
  anselh = 0;  // No analogs

  option_reg = 0b00000111;  // Global enable for weak pullups, prescaler to T0, 256:1 (overflow at 30/sec)
  osccon = 0b01110001;  // Internal oscillator, 8MHz
  ccp1con = 0b00001100;  // PWM on portc.5
  pr2 = 100;
  ccpr1l = 50;  // 50% duty cycle at 10KHz
  t2con = 0b00000000;  // TMR2 off, pre and postscale are 1:1

//  intcon.GIE = 1;
  intcon.PEIE = 1;
  pie1.TMR2IE = 1;

  spbrg = 16;
  spbrgh = 0;
  rcsta.SPEN = 1;  // Set up serial port for 115.2KB
  rcsta.CREN = 1;
  txsta.SYNC = 0;
  txsta.TXEN = 1;
  txsta.BRGH = 1;
  baudctl.BRG16 = 1;
  incoming_push = 0x20;
  incoming_pop = 0x20;
  outgoing_push = 0xC0;
  outgoing_pop = 0xC0;
 
  while (1)
  {
  portb.f6 = 0;
  if (pir1. RCIF)  // New character on serial port
  {
  set_high_bank;
  fsr = incoming_push;
  indirect = rcreg;
  incoming_push++;
  if (incoming_push >= 0x70)
  incoming_push = 0x20;
  set_low_bank;
  }

  if ((outgoing_push != outgoing_pop) && (txsta.TRMT != 0))
  {  // Data to send, and serial port can accept it
  fsr = outgoing_pop;  // Start at 0xC0, 0b11000000
  txreg = indirect;  // Out it goes
  outgoing_pop++;  // Cause wrap from 0xE0 to 0xC0
  outgoing_pop.f5 = 0;
  }

  if (incoming_push != incoming_pop)  // New chars in buffer
  {
  i = serial_in();
  if (i == GOOD_PACKET)
  {
  portb.f6 = 1;
  if (incoming_opcode == TARGET_POWER_ON)
  portc_shadow.f0 = 1;
  else if (incoming_opcode == TARGET_POWER_OFF)
  portc_shadow.f0 = 0;
  else if (incoming_opcode == TARGET_RESET_LOW)
  trisc.f1 = 0;
  else if (incoming_opcode == TARGET_RESET_HIGH)
  trisc.f1 = 1;
  else if (incoming_opcode == ENTER_PROG_MODE)
  enter_prog();
  else if (incoming_opcode == PWM_ON)
  t2con.f2 = 1;
  else if (incoming_opcode == PWM_OFF)
  t2con.f2 = 0;
  else if (incoming_opcode == READ_SAME)
  read_data();
  portc = portc_shadow;
  }
  }
  }
}
 

MMcLaren

Joined Feb 14, 2010
861
Happy to hear you've gotten past the first hurdles, John. I should be in a position to test my code soon, too.

After getting this technique working and characterized, I'd like to tackle what we all were talking about five or six years ago. That is, developing a simple and inexpensive programmer that uses a USB-to-Serial adapter and a couple 74HC chips (no programmable parts required) that can be used to program all of the "enhanced mid-range" parts and other parts that use this same serial LVP technique.

Cheerful regards, Mike
 

Thread Starter

John P

Joined Oct 14, 2008
2,025
I don't recall that topic, but most likely I wasn't a member here then. However, I can't see any reason why anyone would want to do this. Small processors like the PIC exist exactly so people don't have to put together assemblies of logic chips! At less than $2.00 each, it seems hard to justify anything else. It looks pretty awkward, too--could it be that the plan was actually to use the controllable I/O pins on the FTDI interface chip, rather than the UART? Generating the clock from a UART in hardware is a tough project.

But a programmer that uses just a single cheap processor and no other hardware is an attractive idea! What I'd really like to see would be getting the USB interface on board the PIC also, which would be possible with the PIC16F1459, very similar to the PIC16F690 but with a built-in USB function. But although that chip has been available for a while now, it doesn't seem to be getting much use.
 

ErnieM

Joined Apr 24, 2011
8,377
But a programmer that uses just a single cheap processor and no other hardware is an attractive idea! What I'd really like to see would be getting the USB interface on board the PIC also, which would be possible with the PIC16F1459, very similar to the PIC16F690 but with a built-in USB function. But although that chip has been available for a while now, it doesn't seem to be getting much use.
I own several programmers that use but a single cheap processor, they all come in black or red plastic cases, they also can do in circuit debugging of code. They are called PICkit II or III.

They work very nicely too.

The time I built a programmer it was part of an acceptance test fixture for a small pic based timing board. The fixture did several tests besides time such as switch sat voltage, current draw, external start and external timing resistor. Results were displayed on a 4x20 display. The fixture was powered off a 30v supply and used a large PIC as the workhorse. There were plenty of spare pins and code space to add in a simple programming function so freshly built parts could be inserted a single time to be programmed and tested all in a single step.

The fun part was there were 4 major variants requiring their own code, though the code did need to vary depending on the nominal timer time which could vary from 0.1 seconds to 18 minutes. The programmer portion would pull the appropriate code base from ROM and change several instructions to set the timing constants. Then the delta code was inserted into the target, checked, and the code protect door was shut.

For just development work you can't beat a PICkit. Cheap, it works, and it can also debug for you.
 

NorthGuy

Joined Jun 28, 2014
611
But a programmer that uses just a single cheap processor and no other hardware is an attractive idea! What I'd really like to see would be getting the USB interface on board the PIC also, which would be possible with the PIC16F1459, very similar to the PIC16F690 but with a built-in USB function. But although that chip has been available for a while now, it doesn't seem to be getting much use.
I've just built a programmer like this, but using PIC16F1454 (which BTW is cheaper than the FTDI chip). Works well and supports programming/debugging of over 1000 different PICs. The chip even had enough capacities for adding a USB(HID)-to-UART converter in addition to programming/debugging function. We're now finishing testing.
 

Thread Starter

John P

Joined Oct 14, 2008
2,025
I'd be very interested in the design for this, or if "We're finishing testing" means that it's a commercial product, I'd be a likely customer. Can we assume that since it's based on such an inexpensive component, the product will be priced pretty low?
 
Top