Seeking code for low-voltage programming of enhanced midrange PIC

Discussion in 'Embedded Systems and Microcontrollers' started by John P, Jan 30, 2016.

  1. John P

    Thread Starter AAC Fanatic!

    Oct 14, 2008
    1,632
    224
    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.
     
  2. andre_teprom

    Member

    Jan 17, 2016
    31
    9
    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.
     
  3. John P

    Thread Starter AAC Fanatic!

    Oct 14, 2008
    1,632
    224
    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.
     
  4. ErnieM

    AAC Fanatic!

    Apr 24, 2011
    7,386
    1,605
    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
     
  5. MMcLaren

    Well-Known Member

    Feb 14, 2010
    759
    116
    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
     
  6. andre_teprom

    Member

    Jan 17, 2016
    31
    9
    @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 ?
     
  7. John P

    Thread Starter AAC Fanatic!

    Oct 14, 2008
    1,632
    224
    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.
     
  8. MMcLaren

    Well-Known Member

    Feb 14, 2010
    759
    116
    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
     
  9. dannyf

    Well-Known Member

    Sep 13, 2015
    1,779
    360
    The best place for such information is the datasheet / programming specification.

    The quickest is to repurpose the pickit2 source code.
     
  10. John P

    Thread Starter AAC Fanatic!

    Oct 14, 2008
    1,632
    224
    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 (Text):
    1.  
    2. unsigned char enter_progmode (void)
    3. {
    4. ISP_MCLR_0
    5. _delay_us(300);
    6. /*
    7. isp_send(0b10110010,8);
    8. isp_send(0b11000010,8);
    9. isp_send(0b00010010,8);
    10. isp_send(0b00001010,8);
    11. */
    12. /*
    13. isp_send(0b01001101,8);
    14. isp_send(0b01000011,8);
    15. isp_send(0b01001000,8);
    16. isp_send(0b01010000,8);
    17. */
    18. /*
    19. isp_send(0b00001010,8);
    20. isp_send(0b00010010,8);
    21. isp_send(0b11000010,8);
    22. isp_send(0b10110010,8);
    23. */
    24.  
    25. isp_send(0b01010000,8);
    26. isp_send(0b01001000,8);
    27. isp_send(0b01000011,8);
    28. isp_send(0b01001101,8);
    29.  
    30. isp_send(0,1);
    31.  
    32. }
    33.  
     
  11. NorthGuy

    Active Member

    Jun 28, 2014
    603
    121
    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.
     
    atferrari likes this.
  12. ErnieM

    AAC Fanatic!

    Apr 24, 2011
    7,386
    1,605
    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.
     
    JohnInTX likes this.
  13. MMcLaren

    Well-Known Member

    Feb 14, 2010
    759
    116
    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: Feb 1, 2016
  14. John P

    Thread Starter AAC Fanatic!

    Oct 14, 2008
    1,632
    224
    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 (Text):
    1.  
    2. #define indirect  indf
    3.  
    4. #define set_high_bank  status.f7 = 1
    5. #define set_low_bank  status.f7 = 0
    6.  
    7. #define MY_ADDR  0
    8. #define GOOD_PACKET  100
    9.  
    10. #define TARGET_POWER_ON  1  // a
    11. #define TARGET_POWER_OFF  2  // b
    12. #define TARGET_RESET_LOW  3  // c
    13. #define TARGET_RESET_HIGH  4  // d
    14. #define ENTER_PROG_MODE  5  // e
    15. #define PWM_ON  6  // f
    16. #define PWM_OFF  7  // g
    17. #define READ_SAME  8  // h
    18. #define PROGRAM_DATA  9  // i Data from packet gets loaded to memory at current address
    19. #define ADDR_ZERO  10  // j Issue RESET_ADDR command
    20. #define ADDR_CONFIG  11  // k Issue LOAD_CONFIG command
    21. #define GOTO_ADDRESS  12  // Issue enough INC_ADDR commands to reach this address (error if new address is < existing)
    22.  
    23. #define LOAD_CONFIG  0
    24. #define LOAD_DATA  2
    25. #define READ_DATA  4
    26. #define INC_ADDR  6
    27. #define RESET_ADDR  0x16
    28. #define INT_PROG  8
    29. #define EXT_PROG  0x18
    30. #define END_EXT  0xA
    31. #define BULK_ERASE  9
    32. #define ROW_ERASE  0x11
    33.  
    34. typedef unsigned char byte;
    35.  
    36. /* High memory usage
    37. Low bank (0xA0 - 0xEF):
    38.   0xA0 - 0xAF
    39.   0xB0 - 0xBF
    40.   0xC0 - 0xDF  Outgoing serial buffer (incoming_push, incoming_pop)
    41.   0xE0 - 0xEF
    42.  
    43. High bank (0x20 - 0x6F):
    44.   0x20 - 0x6F  Incoming serial buffer
    45. */
    46.  
    47. byte incoming_push, incoming_pop, incoming_opcode, incoming_nbytes, incoming_address, checksum, tbyte;
    48. byte outgoing_push, outgoing_pop;
    49. byte ser_state=0;
    50. byte portc_shadow;
    51.  
    52. byte outgoing_buffer[32] absolute 0xC0;  // Dummy array to make sure compiler doesn't use this area
    53.  
    54. byte serial_in(void)
    55. {
    56.   byte i;
    57.  
    58.   set_high_bank;
    59.   fsr = incoming_pop;
    60.   i = indirect;
    61.   incoming_pop++;
    62.   if (incoming_pop >= 0x70)
    63.   incoming_pop = 0x20;
    64.   set_low_bank;
    65.  
    66.   switch(ser_state)
    67.   {
    68.   case 1:
    69.   if (i == 0xFF)
    70.   ser_state = 0;  // Fail, assume the 0xFF was starting a new packet
    71.   else
    72.   incoming_opcode = i;  // Opcode
    73.   break;
    74.   case 2:
    75.   if (i == 0xFF)
    76.   ser_state = 0;  // Fail, assume the 0xFF was starting a new packet
    77.   else
    78.   incoming_address = i;  // Addressee
    79.   break;
    80.   case 3:
    81.   if (i == 0xFF)
    82.   ser_state = 0;  // Fail, assume the 0xFF was starting a new packet
    83.   else
    84.   {
    85.   incoming_nbytes = i;  // Number of bytes
    86.   checksum = incoming_opcode + incoming_nbytes + incoming_address;
    87.   tbyte = 0;
    88.   }
    89.   break;
    90.   case 4:
    91.   if (tbyte == incoming_nbytes)  // Seen final data byte
    92.   {
    93.   if ((checksum == i) && (incoming_address == MY_ADDR))
    94.   ser_state = (GOOD_PACKET - 1);
    95.   else  // Checksum passes
    96.   ser_state = 0xFF;  // Failed packet
    97.   break;
    98.   }
    99.   else
    100.   {
    101.   if (tbyte < 9)
    102.   {
    103.   // incoming[tbyte] = i;
    104.   tbyte++;  // We can't accept data that overfills the array
    105.   }
    106.   }
    107.   checksum += i;
    108.   if (i != 0xFF)
    109.   ser_state--;  // Normal condition, value of 4 in ss is repeated}
    110.   break;
    111.   case 5:  // Last input was 0xFF
    112.   if (i == 0xFF)  // 0xFF again, OK
    113.   ser_state = 3;  // Next ss is 4, expect regular data
    114.   else
    115.   {
    116.   ser_state = 1;  // Fail, assume the 0xFF was starting a new packet
    117.   incoming_opcode = i;  // and this was the opcode, so next ss is 2
    118.   }
    119.   break;
    120.  
    121.   default:  // Uaually serial_state is 0 here
    122.   ser_state = 0;
    123.   if (i != 0xFF)
    124.   ser_state--;  // Fail, set ss so it gets reset to 0
    125.   break;
    126.   }  // End switch-case
    127.   ser_state++;  // Return 0 for trash chars between packets
    128.   return(ser_state);  // GOOD_PACKET for complete packet
    129. }  // Any other value for packet in progress
    130.  
    131. void interrupt( void)
    132. {
    133.   txreg = 0x55;
    134.   //bit_clear(pir1, TMR2IF);
    135. }
    136.  
    137. void send_prog_instr(byte command, unsigned dataa)
    138. {
    139.   int i;
    140.  
    141.   if ((command == READ_DATA) || (command == INC_ADDR) || (command == RESET_ADDR) ||
    142.   (command == INT_PROG) || (command == EXT_PROG) || (command == END_EXT))
    143.   command.f7 = 1;  // Flag says no data, just a 6-bit command
    144.  
    145.   for (i = 0; i < 6; i++)
    146.   {
    147.   if (command.f0 == 1)
    148.   portc_shadow.f7 = 1;
    149.   else
    150.   portc_shadow.f7 = 0;
    151.   portc_shadow.f6 = 1;
    152.   portc = portc_shadow;
    153.   portc_shadow.f6 = 0;
    154.   portc = portc_shadow;
    155.  
    156.   command >>= 1;
    157.   }
    158.   if (command.f1 == 1)  // Test flag, return if no data required
    159.   return;
    160.  
    161.   dataa <<= 1;
    162.   for (i = 0; i < 16; i++)
    163.   {
    164.   if (dataa & 1)
    165.   portc_shadow.f7 = 1;
    166.   else
    167.   portc_shadow.f7 = 0;
    168.   portc_shadow.f6 = 1;
    169.   portc = portc_shadow;
    170.   portc_shadow.f6 = 0;
    171.   portc = portc_shadow;
    172.  
    173.   dataa >>= 1;
    174.   }
    175. }
    176.  
    177. void xmit_byte(byte input)
    178. {
    179.   fsr = outgoing_push;
    180.   indirect = input;
    181.   outgoing_push++;
    182.   outgoing_push.f5 = 0;
    183. }
    184.  
    185. void read_data (void)
    186. {
    187.   int i;
    188.   union
    189.   {
    190.   unsigned int in_data16;
    191.   byte in_data8[2];
    192.   } ind;
    193.  
    194.   send_prog_instr(READ_DATA, 0);
    195.   ind.in_data16 = 0;
    196.   trisc.f7 = 1;
    197.  
    198.   for (i = 0; i < 16; i++)
    199.   {
    200.   if (portc.f7 == 1)
    201.   ind.in_data16 |= 0x4000;
    202.   portc_shadow.f6 = 1;
    203.   portc = portc_shadow;
    204.   portc_shadow.f6 = 0;
    205.   portc = portc_shadow;
    206.  
    207.   ind.in_data16 >>= 1;
    208.   }
    209.   trisc.f7 = 0;
    210.  
    211.   xmit_byte(0xFF);
    212.   xmit_byte(0);
    213.   xmit_byte(MY_ADDR);
    214.   xmit_byte(2);
    215.   xmit_byte(ind.in_data8[1]);
    216.   xmit_byte(ind.in_data8[0]);
    217.   xmit_byte(MY_ADDR + 2 + (ind.in_data8[0]) + (ind.in_data8[1]));
    218. }
    219.  
    220. void enter_prog (void)
    221. {
    222.   unsigned long mchp32 = ('M' << 24) + ('C' << 16) + ('H' << 8) + 'P';
    223.   byte i;
    224.  
    225.   for (i = 0; i < 33; i++)
    226.   {
    227.   portc_shadow.f7 = 0;
    228.   if (mchp32 & 1)
    229.   portc_shadow.f7 = 1;
    230.   portc_shadow.f6 = 1;
    231.   portc = portc_shadow;
    232.   portc_shadow.f6 = 0;
    233.   portc = portc_shadow;
    234.  
    235.   mchp32 >>= 1;
    236.   }
    237.  
    238.   send_prog_instr(LOAD_CONFIG, 0);  //Load config, addr now 0x8000
    239.   for (i = 0; i < 6; i++)
    240.   send_prog_instr(INC_ADDR, 0);  // Increment addr to 0x8006 (chip ID)
    241.   read_data();
    242. }
    243.  
    244. void main() {
    245.   byte i;
    246.  
    247.   trisa = 0b00000000;  // 0-5 available
    248.   trisb = 0b00100011;  // 4-7 available, bit 5 is RX, bit 7 is TX
    249.   trisc = 0b00000010;  // 0-7 available
    250.   portc_shadow = 0;
    251.   portc = portc_shadow;  // Target reset high
    252.  
    253.   ansel = 0b00000000;
    254.   anselh = 0;  // No analogs
    255.  
    256.   option_reg = 0b00000111;  // Global enable for weak pullups, prescaler to T0, 256:1 (overflow at 30/sec)
    257.   osccon = 0b01110001;  // Internal oscillator, 8MHz
    258.   ccp1con = 0b00001100;  // PWM on portc.5
    259.   pr2 = 100;
    260.   ccpr1l = 50;  // 50% duty cycle at 10KHz
    261.   t2con = 0b00000000;  // TMR2 off, pre and postscale are 1:1
    262.  
    263. //  intcon.GIE = 1;
    264.   intcon.PEIE = 1;
    265.   pie1.TMR2IE = 1;
    266.  
    267.   spbrg = 16;
    268.   spbrgh = 0;
    269.   rcsta.SPEN = 1;  // Set up serial port for 115.2KB
    270.   rcsta.CREN = 1;
    271.   txsta.SYNC = 0;
    272.   txsta.TXEN = 1;
    273.   txsta.BRGH = 1;
    274.   baudctl.BRG16 = 1;
    275.   incoming_push = 0x20;
    276.   incoming_pop = 0x20;
    277.   outgoing_push = 0xC0;
    278.   outgoing_pop = 0xC0;
    279.  
    280.   while (1)
    281.   {
    282.   portb.f6 = 0;
    283.   if (pir1. RCIF)  // New character on serial port
    284.   {
    285.   set_high_bank;
    286.   fsr = incoming_push;
    287.   indirect = rcreg;
    288.   incoming_push++;
    289.   if (incoming_push >= 0x70)
    290.   incoming_push = 0x20;
    291.   set_low_bank;
    292.   }
    293.  
    294.   if ((outgoing_push != outgoing_pop) && (txsta.TRMT != 0))
    295.   {  // Data to send, and serial port can accept it
    296.   fsr = outgoing_pop;  // Start at 0xC0, 0b11000000
    297.   txreg = indirect;  // Out it goes
    298.   outgoing_pop++;  // Cause wrap from 0xE0 to 0xC0
    299.   outgoing_pop.f5 = 0;
    300.   }
    301.  
    302.   if (incoming_push != incoming_pop)  // New chars in buffer
    303.   {
    304.   i = serial_in();
    305.   if (i == GOOD_PACKET)
    306.   {
    307.   portb.f6 = 1;
    308.   if (incoming_opcode == TARGET_POWER_ON)
    309.   portc_shadow.f0 = 1;
    310.   else if (incoming_opcode == TARGET_POWER_OFF)
    311.   portc_shadow.f0 = 0;
    312.   else if (incoming_opcode == TARGET_RESET_LOW)
    313.   trisc.f1 = 0;
    314.   else if (incoming_opcode == TARGET_RESET_HIGH)
    315.   trisc.f1 = 1;
    316.   else if (incoming_opcode == ENTER_PROG_MODE)
    317.   enter_prog();
    318.   else if (incoming_opcode == PWM_ON)
    319.   t2con.f2 = 1;
    320.   else if (incoming_opcode == PWM_OFF)
    321.   t2con.f2 = 0;
    322.   else if (incoming_opcode == READ_SAME)
    323.   read_data();
    324.   portc = portc_shadow;
    325.   }
    326.   }
    327.   }
    328. }
    329.  
     
    andre_teprom likes this.
  15. MMcLaren

    Well-Known Member

    Feb 14, 2010
    759
    116
    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
     
  16. John P

    Thread Starter AAC Fanatic!

    Oct 14, 2008
    1,632
    224
    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.
     
  17. ErnieM

    AAC Fanatic!

    Apr 24, 2011
    7,386
    1,605
    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.
     
  18. NorthGuy

    Active Member

    Jun 28, 2014
    603
    121
    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.
     
  19. John P

    Thread Starter AAC Fanatic!

    Oct 14, 2008
    1,632
    224
    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?
     
  20. MMcLaren

    Well-Known Member

    Feb 14, 2010
    759
    116
    Hey, North'. Sounds neat. Are there really over 1000 different PICs?
     
Loading...