Learning to program the PIC16LF1823

Thread Starter

cmartinez

Joined Jan 17, 2007
8,765
This is why your receive interrupt routine should simply receive a character into a queue and promptly return to the main program for later processing, which your tl;dr solution seems to be doing.

This is (or should be) standard practice.
Yeah, that was easy to figure out. The tricky part was finding a way of reading the sensors and logging their data (which takes quite a few hundreds of instructions) and then returning to the main thread where work was left off. And I did that by using the interrupt vector as a node from which the logic would branch back and forth.
 

joeyd999

Joined Jun 6, 2011
6,300
Yeah, that was easy to figure out. The tricky part was finding a way of reading the sensors and logging their data (which takes quite a few hundreds of instructions) and then returning to the main thread where work was left off. And I did that by using the interrupt vector as a node from which the logic would branch back and forth.
Sorry, I thought you said you have a full second to collect and process the sensor data.

That is an eternity in CPU time.
 

Thread Starter

cmartinez

Joined Jan 17, 2007
8,765
Sorry, I thought you said you have a full second to collect and process the sensor data.

That is an eternity in CPU time.
It's an eternity at high frequency. But my project works at 32.768 kHz, so reading the sensors and logging the data can take up to 16 seconds, depending on other factors. And that needs to be done every 30 seconds, so MCU idle time is less than 50%
 

joeyd999

Joined Jun 6, 2011
6,300
It's an eternity at high frequency. But my project works at 32.768 kHz, so reading the sensors and logging the data can take up to 16 seconds, depending on other factors. And that needs to be done every 30 seconds, so MCU idle time is less than 50%
No wonder you are having such a hard time.

The CPU has a fast internal oscillator (up to 32MHz).

Use a 32kHz crystal for accurate timing (if necessary), but use the high-speed internal oscillator for instruction execution. Then, sleep whenever there is nothing to be done. Average power consumption will be about the same (or less), and you'll have plenty of cycles available to do everything you want effectively simultaneously, without needing to jump through an awful lot of hoops.
 

Thread Starter

cmartinez

Joined Jan 17, 2007
8,765
No wonder you are having such a hard time.

The CPU has a fast internal oscillator (up to 32MHz).

Use a 32kHz crystal for accurate timing (if necessary), but use the high-speed internal oscillator for instruction execution. Then, sleep whenever there is nothing to be done. Average power consumption will be about the same (or less), and you'll have plenty of cycles available to do everything you want effectively simultaneously, without needing to jump through an awful lot of hoops.
Yeap ... things are already working exactly the way you've described.
 

Thread Starter

cmartinez

Joined Jan 17, 2007
8,765
What's the deal with PCLATH? ... I mean, I've been real careful not to cross page boundaries in my code (which has required frequent reshuffling of routines in order for them to always fit within a single code page) and I've never had to worry about dealing with that register because I use the Fcall macro whenever I call a routine located in a different page:
Code:
;Select appropriate program memory page before and after calling a routine
Fcall macro Routine                 ;(5 instructions total)
         pagesel Routine            ;(2 instructions) select Routine's program memory page before calling
         call Routine               ;(1 instruction)  execute the routine
         pagesel $                  ;(2 instructions) restore the current program memory page
       endm
The thing is, that I had to go through a painful headache a while back when my interrupt routine grew a bit larger (and trust me, I've been keeping it as short and direct as I can). I found it convenient to make several (short) calls from within the interrupt, but there came a point in which my program stopped working, and I solved the problem by setting PCLATH to zero every time I made a call from the interrupt routine.

Why is it that I have to do that at the interrupt routine, but not on the rest of the program? ... I mean, the interrupt code never calls a function outside of page zero. And yet clearing PCLATH after a call is a must if I want the code to behave.
 

joeyd999

Joined Jun 6, 2011
6,300
What's the deal with PCLATH? ... I mean, I've been real careful not to cross page boundaries in my code (which has required frequent reshuffling of routines in order for them to always fit within a single code page) and I've never had to worry about dealing with that register because I use the Fcall macro whenever I call a routine located in a different page:
Code:
;Select appropriate program memory page before and after calling a routine
Fcall macro Routine                 ;(5 instructions total)
         pagesel Routine            ;(2 instructions) select Routine's program memory page before calling
         call Routine               ;(1 instruction)  execute the routine
         pagesel $                  ;(2 instructions) restore the current program memory page
       endm
The thing is, that I had to go through a painful headache a while back when my interrupt routine grew a bit larger (and trust me, I've been keeping it as short and direct as I can). I found it convenient to make several (short) calls from within the interrupt, but there came a point in which my program stopped working, and I solved the problem by setting PCLATH to zero every time I made a call from the interrupt routine.

Why is it that I have to do that at the interrupt routine, but not on the rest of the program? ... I mean, the interrupt code never calls a function outside of page zero. And yet clearing PCLATH after a call is a must if I want the code to behave.
To the best of my recollection (it's been a long time since I dealt with PIC16F), upon interrupt, PCLATH's contents are not modified. Therefore, if you attempt a call, the call will go the the page that was executing when the interrupt occurred.

You can confirm this by setting up some test code and stepping through the call.

IMHO, if you are calling subs from an interrupt routine, you are doing it wrong.
 

Thread Starter

cmartinez

Joined Jan 17, 2007
8,765
To the best of my recollection (it's been a long time since I dealt with PIC16F), upon interrupt, PCLATH's contents are not modified. Therefore, if you attempt a call, the call will go the the page that was executing when the interrupt occurred.

IMHO, if you are calling subs from an interrupt routine, you are doing it wrong.

You can confirm this by setting up some test code and stepping through the call.
Excellent advice, Joey. That's gonna save me a lot of pain. Many, many thanks.
 

Thread Starter

cmartinez

Joined Jan 17, 2007
8,765
Yeap ... you were right. It's necessary to clear PCLATH only once, and as the very first instruction in the interrupt routine, to let the chip know that we'll be working in page 0, and that all calls and gotos are referred to that page. I guess I could've also used the pagesel directive and things would've worked out quite as well.

The PCLATH is automatically fetched from the PCLATH_SHAD register and restored upon execution of the retfie instruction, thanks to the automatic context saving feature of the PIC16LF.

I was gonna rant one more time about how much I miss the good'ol 8051 architecture ... but this chip has kinda grown on me ... :)
 
Last edited:

Thread Starter

cmartinez

Joined Jan 17, 2007
8,765
IMHO, if you are calling subs from an interrupt routine, you are doing it wrong.
Maybe ... I have a lot of respect for your opinion, so I'll keep that in mind...

I'm making as few calls within the interrupt routine as I believe I possibly can:
  • 3 calls to assign different values to a set of registers depending on current conditions. Said routine takes about 20 instructions
  • 1 call to fetch characters arriving at the UART and placing them in a queue. That takes a bit longer.
  • 1 call to backup/restore the general purpose ram, the shadow registers and the top of the stack, so that the interrupt routine can return to a different address depending on certain conditions to perform a secondary task. And then said registers are restored on the next interrupt event (and after confirmation that said task is complete) so that the program can go back to the execution of the main thread.

That's the only way I could think of to prioritize UART reception (which takes a couple dozen of instructions) over Sensor Readings (which takes a few hundreds of instructions)
 

John P

Joined Oct 14, 2008
2,061
Maybe ... I have a lot of respect for your opinion, so I'll keep that in mind...

I'm making as few calls within the interrupt routine as I believe I possibly can:
  • 3 calls to assign different values to a set of registers depending on current conditions. Said routine takes about 20 instructions
  • 1 call to fetch characters arriving at the UART and placing them in a queue. That takes a bit longer.
  • 1 call to backup/restore the general purpose ram, the shadow registers and the top of the stack, so that the interrupt routine can return to a different address depending on certain conditions to perform a secondary task. And then said registers are restored on the next interrupt event (and after confirmation that said task is complete) so that the program can go back to the execution of the main thread.

That's the only way I could think of to prioritize UART reception (which takes a couple dozen of instructions) over Sensor Readings (which takes a few hundreds of instructions)
CMartinez, it sounds as if your plan should work, but it adds time for its own execution when saving time is important. Depending on how fast your UART is running, you know the maximum rate at which characters can arrive, so if you had a timer running at least that fast, you could poll the UART for anything new and not give it an interrupt. I've successfully written a lot of programs where there's a timer interrupt that does nothing except set a flag (I always call it "tick") which is checked inside an endless loop in the main() function. When it's found to be set, it's cleared and a further check is made for every task that might need service, where some of them might need to run every time and some only one time in 10 (or whatever, based on incrementing a counter). This only works if you can tolerate having tasks run at irregular times rather than exactly when a timer interrupt occurs. But you have the option of running a small fast task in the interrupt if you really need to, and let the others wait a while. Also, if it's really complex you have to be aware of the "jackpot situation" where everything wants service at once! Does this cause a problem?

I said the interrupt sets a flag, but actually that's a waste of time too. You could set the timer running and have the "flag" be the bit that's set when the interrupt wants service--usually that's in one of the PIR registers. That works just as well, and it's faster. You do have to remember to clear it!
 

Thread Starter

cmartinez

Joined Jan 17, 2007
8,765
CMartinez, it sounds as if your plan should work, but it adds time for its own execution when saving time is important. Depending on how fast your UART is running, you know the maximum rate at which characters can arrive, so if you had a timer running at least that fast, you could poll the UART for anything new and not give it an interrupt. I've successfully written a lot of programs where there's a timer interrupt that does nothing except set a flag (I always call it "tick") which is checked inside an endless loop in the main() function. When it's found to be set, it's cleared and a further check is made for every task that might need service, where some of them might need to run every time and some only one time in 10 (or whatever, based on incrementing a counter). This only works if you can tolerate having tasks run at irregular times rather than exactly when a timer interrupt occurs. But you have the option of running a small fast task in the interrupt if you really need to, and let the others wait a while. Also, if it's really complex you have to be aware of the "jackpot situation" where everything wants service at once! Does this cause a problem?

I said the interrupt sets a flag, but actually that's a waste of time too. You could set the timer running and have the "flag" be the bit that's set when the interrupt wants service--usually that's in one of the PIR registers. That works just as well, and it's faster. You do have to remember to clear it!
Thanks for chiming in, John. All help (and informed opinions) is always appreciated.

I already made things work!! ... and almost exactly as I described. As you've said, I tried and kept the interrupt service as short and direct as I could, the main reason being the flow of characters arriving at the UART. My chip is working at 115,200 bauds (14,400 bytes/sec). That's a period of 69 µs per character. And since the chip works at 32 MHz, it executes one instruction every 0.125 µs, That allows the execution of 552 instructions between byte reception at the UART. Plus the UART has an internal buffer of two bytes, which gives it a bit more leeway.

Anyway, my interrupt routine DOES NOT come close to having 552 instructions in it, even in its worst case scenario. So things worked out just fine.

As for my almost comment, I mentioned that I planned on using a routine to back up the 16 bytes of common RAM (which worked just fine), and another one to back up the Shadow Registers and the value of Top of Stack (TOSL and TOSH) ... Stupid me ... I lost more than an hour wondering why things were not working before I realized that I COULD NOT use a routine for that purpose. The reason being that if a routine is called, then the Stack Pointer is incremented, the Top of Stack changes, and a new set of Shadow Registers is created. So the original info pointing to where the interrupt originated is lost.

The simple solution was to use a "goto to" and a "goto back" on a code segment (plus a flag) to accomplish what I wanted.

Code:
;*****************************************************************************************
;  Backup or Restore the contents of the Shadow Core retgisters and the Top of Stac in the
; zone assigned in Bank 6. This segment of the program MUST BE ACCESSED THROUGH A GOTO
; ONLY. Otherwise the shadow registers and TOS would change their values if we were to
; make a call instead
; The back up routine must be called AFTER first backing up the GP RAM, and the restore
; routine must be called BEFORE restoring the GP RAM
;
;                                    No memory bank change
;                            Destroyed registers: FSR0, FSR1, CNT00
;*****************************************************************************************
Backup_Shad_And_TOSX:          ;clear bit 0 of FLAGS1 before getting here through a goto
       LoadFSRs STATUS_SHAD, Backup_STATUS_SHAD
     goto Shad_And_TOSX_Reg_Transfer

Restore_Shad_And_TOSX:         ;set bit 0 of FLAGS1 before getting here through a goto
      LoadFSRs Backup_STATUS_SHAD, STATUS_SHAD
    ;goto Shad_And_TOSX_Reg_Transfer

Shad_And_TOSX_Reg_Transfer:
      MovLf d'8', CNT00        ;8 registers to be transferred
SaT_RT_Loop:
       moviw FSR0++            ;back up STATUS_SHAD, WREG_SHAD, BSR_SHAD, PCLATH_SHAD,
       movwi FSR1++            ;FSR0L_SHAD, FSR0H_SHAD, FSR1L_SHAD, FSR1H_SHAD
      decfsz CNT00
     goto SaT_RT_Loop

     addfsr FSR0, d'2'         ;skip an empty register, and STKPTR
     addfsr FSR1, d'2'

     moviw FSR0++              ;TOSL
     movwi FSR1++

     moviw 0[FSR0]             ;TOSH
     movwi 0[FSR1]

     btfss FLAGS1, d'0'
      goto Ret_From_Backup
      goto Ret_From_Restore
 
Last edited:

Thread Starter

cmartinez

Joined Jan 17, 2007
8,765
Your advice has proven invaluable, Joey. I didn't know how PCLATH behaved during interrupts until you pointed it out. I found quite a few routines in my program that had coincidentally been working only because the very first instruction in them was an Fcall macro (shown in post #606) which first executes the pagesel directive ... so, there were several bugs lurking out there ready to explode on my face from routines that I thought I had thoroughly tested already. But those routines began to behave weirdly when I put them through the PCLATH scenario you warned me about.

I've gone through the entire code looking for similar bugs, line by line, thrice already. And I think I've squished them all.

Thanks again, And have yourself and your close ones a very Merry Christmas. :)
 

Thread Starter

cmartinez

Joined Jan 17, 2007
8,765
Quick question:

I am now beginning production of a small device and I'm looking over the process of programming the units being produced. I have assigned a unique serial number to each device and said number is a set of 10 characters that is included in the code (not the EEPROM). Same thing applies for the device's password and other fields whose value is permanent and will never change.

Is there a way to automate this sort of process and update (say, increment the serial number) a field within the code each time a device is programmed? I am currently using MPLAB X IDE (and IPE) v5.35 for this purpose. Maybe a plugin already exists for this purpose?
 

John P

Joined Oct 14, 2008
2,061
Maybe it is possible to do this, if you have control over the device which does the programming. You'd have a script of some kind running on your computer, and it would open the hex file that you're about to send to the programming device, and change the data that's in it to add an updated serial number, obtained from another file which you would re-write and then save for the next unit. The updated file would then be used to program your processor. But you'd have to write a program to handle the files and control the programming device. And if it can't be fully automated, how many manual steps would you be willing to go through before you say that updating and re-compiling the code would actually be easier?
 

Thread Starter

cmartinez

Joined Jan 17, 2007
8,765
Maybe it is possible to do this, if you have control over the device which does the programming. You'd have a script of some kind running on your computer, and it would open the hex file that you're about to send to the programming device, and change the data that's in it to add an updated serial number, obtained from another file which you would re-write and then save for the next unit. The updated file would then be used to program your processor. But you'd have to write a program to handle the files and control the programming device. And if it can't be fully automated, how many manual steps would you be willing to go through before you say that updating and re-compiling the code would actually be easier?
My line of thought exactly. But what kind of language or script would be used for this task?

I'd like to go through "zero" manual steps to attain what I want, and leave everything to the script or program in question.
 

John P

Joined Oct 14, 2008
2,061
If I had to do it, it would be written in C for Windows, because that's what I know how to do.

You haven't said what you use to program PIC processors, but I'll assume it's a PICKIT device. It seems as if it would be easiest to run it from a command line, and a search comes back saying you can do that using ipecmd. exe.
[For some reason this website wouldn't let me post the name of the program unless I put a space before exe.]

The C command that runs a program based on a command line is CreateProcess(). Using that, I think you could create an application that would open the hex file that the compiler produced, also open the file where you store the latest serial number, read the number and increment it, insert it into the file, run the programming device, and store the serial number back into a new version of its file.

Of course, every software project is easy until you actually try it!
 

Thread Starter

cmartinez

Joined Jan 17, 2007
8,765
The command line! ... why hadn't I thought of that? ... I can write a small program that would serialize the code each time it's run, and run it from a batch that will in turn run the PICKIT device software (MPLAB) ...

I'll take it from here. Many, many thanks for your assistance, John. I'll be back to report my results later on.
 

Thread Starter

cmartinez

Joined Jan 17, 2007
8,765
Just thought I'd document this here.

When setting up the Fixed Voltage Reference (FVR) of the PIC16LF15323 the following code DOES NOT work:
Code:
   banksel FVRCON       ;bank 18
   bsf FVRCON, FVREN    ;enable internal Fixed Voltage Reference
   bcf FVRCON, ADFVR1
   bsf FVRCON, ADFVR0   ;these two bits define ADC FVR Buffer Gain as 1x, (1.024V)

This is the code that works:
Code:
   banksel FVRCON       ;bank 18
   bcf FVRCON, ADFVR1
   bsf FVRCON, ADFVR0   ;these two bits define ADC FVR Buffer Gain as 1x, (1.024V)
   bsf FVRCON, FVREN    ;enable internal Fixed Voltage Reference
It seems that the FVR's gain must be set prior to activating it, and once set, it cannot be changed unless it's deactivated and then set up again ... I hate this sort of quirks ... took a few hours of my life. I hope this post helps to save hours of other people in the future.
 
Top