Learning to program the PIC16LF1823

atferrari

Joined Jan 6, 2004
5,012
Hola César,

Used the attached code until moving to the 18F family.

Start by looking at how it is used, in file 014. The whole involves: EQUs, macros, code and text tables. All oriented to an LCD display 2x24 in my case.

Few weeks ago I reworked the whole thing to also work for serial comms 18F family and pic-as 2.20 (no more MPASM).
 

Attachments

joeyd999

Joined Jun 6, 2011
6,300
FYI @cmartinez: There are lots of methods for pushing characters out to LCD or serial. Just remember: most of them are blocking routines -- the CPU can do nothing else (save processing interrupts) until the complete line is output.

Often, this is OK. For me, never.

I always write custom non-blocking drivers that don't require waiting on hardware do its thing. This way, my code is only limited by available CPU cycles and not bottlenecked by the various and sundry attached peripherals.

Here's the difference: If I run out of CPU cycles, I can just increase the clock speed. This doesn't help if your CPU idles anyway due to blocking code.

For my user output devices, I also make my drivers I18L -- internationalized. Usually, a single global register holds the language (encoded as a byte value). The routines select the appropriate string to be output based upon the desired message and the current language. Adding languages is as easy as adding translated messages for each new language, and the drivers take care of the rest. Changing the language only requires updating the language register.
 

Thread Starter

cmartinez

Joined Jan 17, 2007
8,765
There you go. I finally made it work.

Here's my code:

Code:
Transmit_Id:
      movlw low device_id  ;load the File Select Register 1 with 
      movwf FSR1L          ;the code address of device_id
      movlw high device_id
      movwf FSR1H
      
      call Transmit_String ;transmit the device_id through the UART
     return

;*****************************************************************************************
;               Transmit a string stored in code memory through the UART
; FSR1 already points to the location of said string. Transmission will stop when a byte
;with a value of 0 is found
;*****************************************************************************************
Transmit_String:
       moviw FSR1++             ;load W with a byte pointed at by FSR1 and make FSR1 point 
       btfsc STATUS, Z          ;to the next byte
      goto Exit_Transmit_String ;exit if the loaded byte was a 0

       call Transmit_W          ;transmit the byte through the UART
      goto Transmit_String      ;keep transmitting until a 0 byte is found
 
Exit_Transmit_String:       
     return

device_id:     DT "Pressure And Power Control Module",13,0
I tried using the directive DA at first, and all I got was garbage. According to the manual, DT "Generates a series of RETLW instructions, one instruction for each expr" ... Fine, I understand that... but I haven't yet grasped how DA encodes the information.

The manual says:
4.14.4 Simple Examples
• da "abcdef"
will put 30E2 31E4 32E6 into program memory
• da "12345678" ,0
will put 18B2 19B4 1AB6 1BB8 0000 into program memory
• da 0xFFFF
will put 0x3FFF into program memory
What gives? ... I still don't get it.
 

joeyd999

Joined Jun 6, 2011
6,300
Is that the sort of blocking routine that you were referring to, Joey?
Yes. Transmit_string is implemented as a blocking routine. It will not return to the main program until all characters are transmitted. Depending on baud, this can consume (channeling Carl Sagen) billions and billions of instruction cycles.

Normally, one implements a UART driver as interrupt driven non-blocking code.

Edit: Actually, I cannot tell how "Transmit_W" is implemented. It may queue up characters for later transmission via interrupts.
 

Thread Starter

cmartinez

Joined Jan 17, 2007
8,765
The Transmit_W routine is extremely blocking too:

Code:
;*****************************************************************************************
;                          Transmit W through the serial port
; Will restore the previously active bank before leaving. Will also restore W to its 
;original value
; 11 JUL 2020, 7:11:05 PM: Routine has been changed so that the character is transmitted
;first, and then the routine waits until it has been sent before continuing
;*****************************************************************************************
Transmit_W:

     movwf TEMP00                 ;store data to be transmitted in TEMP00
     movf BSR, w                  ;store the active memory bank in PREVBANK
     movwf PREVBANK

     movf TEMP00, w               ;restore data back into w

     banksel TXSTA                ;TXSTA lies at bank 3
     movwf TXREG                  ;transmit W through the UART
Tx_Loop:     
      btfss TXSTA, TRMT           ;loop until TXSTA is set, that is, until the transmit 
     goto Tx_Loop                 ;buffer is empty
     
     movf PREVBANK, w             ;restore the previous memory bank before leaving
     movwf BSR
     
     movf TEMP00, w               ;also restore the original value of w
     
    return
You've mentioned in the past that one of the reasons (if not the main reason) you do all your programming in assembly is because of how fast and compact it is. In my case I also use it for those reasons, but the main one is that with assembly one can predict with a high degree of precision the number of cycles that each routine or instruction set takes to perform a task. This is critical for me because most of my applications have to do with engines and motors that need to be monitored in real time, and the precise measurement of the time during which an event takes place is essential. That's why most of my programs don't use interrupts, and work through polling instead.
 

atferrari

Joined Jan 6, 2004
5,012
FYI @cmartinez: There are lots of methods for pushing characters out to LCD or serial. Just remember: most of them are blocking routines -- the CPU can do nothing else (save processing interrupts) until the complete line is output.

Often, this is OK. For me, never.

I always write custom non-blocking drivers that don't require waiting on hardware do its thing. This way, my code is only limited by available CPU cycles and not bottlenecked by the various and sundry attached peripherals.

Here's the difference: If I run out of CPU cycles, I can just increase the clock speed. This doesn't help if your CPU idles anyway due to blocking code.

For my user output devices, I also make my drivers I18L -- internationalized. Usually, a single global register holds the language (encoded as a byte value). The routines select the appropriate string to be output based upon the desired message and the current language. Adding languages is as easy as adding translated messages for each new language, and the drivers take care of the rest. Changing the language only requires updating the language register.
To be non-blocking, is it other way than interrupts, Joey?
 

joeyd999

Joined Jun 6, 2011
6,300
The Transmit_W routine is extremely blocking too:

Code:
;*****************************************************************************************
;                          Transmit W through the serial port
; Will restore the previously active bank before leaving. Will also restore W to its
;original value
; 11 JUL 2020, 7:11:05 PM: Routine has been changed so that the character is transmitted
;first, and then the routine waits until it has been sent before continuing
;*****************************************************************************************
Transmit_W:

     movwf TEMP00                 ;store data to be transmitted in TEMP00
     movf BSR, w                  ;store the active memory bank in PREVBANK
     movwf PREVBANK

     movf TEMP00, w               ;restore data back into w

     banksel TXSTA                ;TXSTA lies at bank 3
     movwf TXREG                  ;transmit W through the UART
Tx_Loop:    
      btfss TXSTA, TRMT           ;loop until TXSTA is set, that is, until the transmit
     goto Tx_Loop                 ;buffer is empty
    
     movf PREVBANK, w             ;restore the previous memory bank before leaving
     movwf BSR
    
     movf TEMP00, w               ;also restore the original value of w
    
    return
You've mentioned in the past that one of the reasons (if not the main reason) you do all your programming in assembly is because of how fast and compact it is. In my case I also use it for those reasons, but the main one is that with assembly one can predict with a high degree of precision the number of cycles that each routine or instruction set takes to perform a task. This is critical for me because most of my applications have to do with engines and motors that need to be monitored in real time, and the precise measurement of the time during which an event takes place is essential. That's why most of my programs don't use interrupts, and work through polling instead.
You got it backassward! When used correctly, interrupts improve your ability to create -- and precisely respond to -- asynchronous events.
 

joeyd999

Joined Jun 6, 2011
6,300
I'm all ears...
Then you look funny...but hear really, really well.

Are you asking for a lesson on how to create and respond to asynchronous events via interrupts? I don't have time for that.

Ask specific questions, and I'll give you specific answers. Otherwise, maybe some of the retired folk will chime in here.
 

joeyd999

Joined Jun 6, 2011
6,300
I will try that next time. Equivalent to polling my keyboard or my time base routine. Thanks.
Note: this only works for transmission. It will not work reliably for reception.

If you intend to use the polling method for reception, you pretty much have to block until the entire expected message is received. Otherwise, you risk overrun errors in the receiver.

Edit: And you still need to trigger your reception code somehow within 2 character times, otherwise, you will fail to capture a message (and have to deal with the resulting overrun error and spurious characters).
 

jpanhalt

Joined Jan 18, 2008
11,087
@joeyd999
Promises, promises. Show us something that actually works. That is, does what it is supposed to do and doesn't block the CPU for even one cycle as you claim.

@cmartinez
What gives? ... I still don't get it.
The DA directive packs the ascii characters. That's why I used 14-bit memory. There are two common sources for 14-bits in the enhanced mid-range: program memory and EEPROM (you can use the EEPROM registers without saving). You can address program memory with the FSRn's. In theory, set bit 7 of FSRnH to indicate program memory. NB: The assembler will often do that for you when you movwf FSRnH, but not always.

Because 2 ascii characters can be saved in a single 14-bit byte, it saves space, but they have to be unpacked. That unpacking is why my code was a bit more complicated. If you don't unppack, you will get garbage.

Suggestion: Try my code. Then do your code with the DT directive with FSRn. It is one character per 8-bit byte. Then try your code with DA, FSRn, and unpacking.

Edit: Meant EEPROM registers. Fixed.
 
Last edited:

Thread Starter

cmartinez

Joined Jan 17, 2007
8,765
After studying the PIC12LF1822 datasheet, I noticed that among its features is the ability to be run using a 32.768 kHz quartz crystal. This has the obvious advantage of being able to use the MCU as a more or less accurate time keeper.

I have several questions on this issue, though.

The datasheet says that the LP internal oscillator mode runs at 31 kHz... and it also mentions that this frequency has a tolerance of ±25% !!!

So I assume that using the internal oscillator for the task of keeping time is a terrible idea?

1596757082121.png

Another question is one regarding operating temperature. What if one were to use an external 32.768 kHz crystal in a device that is meant to be used outdoors? That would mean that it would be exposed to temperatures in the range of -20°C to 50°C throughout the seasons, more or less. How large a timing error can one expect from this thing in the lapse of one year? Would that error depend entirely on the quality of the crystal being used?

And finally, would the MCU draw more power when an external oscillator is used? Or is the power being drawn simply proportional to its working frequency?
 

OBW0549

Joined Mar 2, 2015
3,566
The datasheet says that the LP internal oscillator mode runs at 31 kHz... and it also mentions that this frequency has a tolerance of ±25% !!!

So I assume that using the internal oscillator for the task of keeping time is a terrible idea?
Uhhh... the LP oscillator mode uses a 32.768 kHz crystal, and is as accurate as the crystal.

LPmode.png

The LFINTOSC mode operates at 31 kHz and is the one that's uncalibrated. So using LFINTOSC for tiekeeping is definitely not a good idea.

LFINTOSC.png

What if one were to use an external 32.768 kHz crystal in a device that is meant to be used outdoors? That would mean that it would be exposed to temperatures in the range of -20°C to 50°C throughout the seasons, more or less. How large a timing error can one expect from this thing in the lapse of one year? Would that error depend entirely on the quality of the crystal being used?
Yes, it would depend entirely on the crystal.

And finally, would the MCU draw more power when an external oscillator is used? Or is the power being drawn simply proportional to its working frequency?
You'd have to consult the datasheet, but I think in general the power drawn is roughly proportional to frequency.
 

Thread Starter

cmartinez

Joined Jan 17, 2007
8,765
Uhhh... the LP oscillator mode uses a 32.768 kHz crystal, and is as accurate as the crystal.
right ... thanks for the clarification ...

I have two different 32.768 kHz crystals with me at this moment:

https://support.epson.biz/td/api/doc_check.php?dl=brief_FC-135R&lang=en
http://www.farnell.com/datasheets/1468416.pdf

They both claim a ±20 ppm frequency tolerance, and have almost the same values on other specs... except one has a 0.9 pF motional capacitance, and the other 3.4 pF ... whatever that means. I assume the less capacitance, the better? Also, is a lower series resistance better?
 

jpanhalt

Joined Jan 18, 2008
11,087
1) I believe you can use the LF oscillator for system clock. Realistically, I never considered doing that. I am not sure that LF oscillator can be used for TMR1 with a different system clock. I never looked into trying that as I use the internal HF for system clock, usually at 8 MHz with 4xPLL (available with 12F1822/3) to give 32 MHz or at 8 or 16 MHz if 4xPLL is not used. I use a crystal for 32.768 kHz for TMR1. That gives a nice 2-second pace. If you want 1 second, similar crystals are made for that. I have plenty of enhanced mid-range code for setting up that system, if you decide to go that way.

2) As for temperature, the variation is usually 0.03 to 0.04 ppm/°C (70°C = about 2.4 ppm). Corrected: See EDIT below. Aging is 3 ppm/year. A year is 31.5 million seconds. You do have a choice of initial accuracy. Really, nothing will come close unless you opt for a temperature corrected crystal, RTC module, or something based on GPS.
EDIT: Effect of temperature is different for different crystal types. Tuning fork type are common for 32.768 kHz. Their behavior is described here: https://abracon.com/Support/Tuning-Fork-Crystals-and-Oscillator.pdf The formula is 0.03 to 0.04 ppm/°C^2. Turning point is about 25°C. At -20°C error is about -80 ppm and at +40° error is -20 ppm. Both values from chart.

3) As for the power, Table 30.2 covers that. An external crystal for system clock consumes a tiny bit less power than the same frequency with the internal oscillator. I'd rather have the extra port pins available and use the internal oscillator.

John

Sorry for any duplication. It took me awhile to write this response.
 
Last edited:

OBW0549

Joined Mar 2, 2015
3,566
I have two different 32.768 kHz crystals with me at this moment:

https://support.epson.biz/td/api/doc_check.php?dl=brief_FC-135R&lang=en
http://www.farnell.com/datasheets/1468416.pdf

They both claim a ±20 ppm frequency tolerance, and have almost the same values on other specs... except one has a 0.9 pF motional capacitance, and the other 3.4 pF ... whatever that means. I assume the less capacitance, the better? Also, is a lower series resistance better?
Dunno, to both questions.
 
Top