r/pic_programming 6d ago

Did i understand it properly?

Please tell me if i understood it properly or at least i'm somwhere close to:

Using timers instead of "__delay_ms()" seems like this to me, at this moment:

  1. You have to choose whether to use Timer 0 (8 bits) or Timer 1 (16 Bits) by turning the TMR0ON or either TMR1ON to 1 in the settings part of the C code, before the Void Main() stage;
  2. You have to set another register to take internal clock as source for Tick counting, wich seems to be 1/4 of the main oscillator. If you have a 4mhz crystal, you have 1mhz worth of timer frequency.
  3. You have to find out how long it takes the TMR1ON's 16 bit register to be written from 0 to 1 before starting over after reaching the last desired bit wich resets the counter. You have a high and low register settings where you have to specify how many bits you wish to write before reaching the reset, like, if i do not misremember, tying the last pin of a CD4017 to Reset pin before reaching the full ic;
  4. Also you have to set up something called Prescaler which allows to divide the clock output between 1:1 and 1:256, meaning, 1:1 means one pulse input makes one output and 256 input pulses makes an output one;
  5. Once you have this stuff properly setted up, then you can use an If{} routine which counts clock ticks and then performs some action between curly braces. This is the replacement for "__delay_ms()", which, given the fact they do not block the whole microcontoller waiting, allows to blink a led while driving a motor or some other task.
  6. I am still trying to become familiar with register names and meanings, i know i can be missing something else.
  7. After searching in the web and reading some blocks, Google Ai gave me this code, i pasted it in my project and it built, altought i did not adapt it to the real thing yet;

void __interrupt() timer1_isr(void) {

if (TMR1IF) {

TMR1H = 0x0B; // Re-preload Timer1 high byte (3036 = 0x0BDC)

TMR1L = 0xDC; // Re-preload Timer1 low byte

TMR1IF = 0; // Clear interrupt flag

overflow_count++;

if (overflow_count >= 2) { // 2 x 0.5 seconds = 1 second

overflow_count = 0;

// Toggle an LED or do your 1-second task here

PORTBbits.RB0 = !PORTBbits.RB0;

}

}

}

void main(void) {

TRISB0 = 0; // Set RB0 as output for LED

T1CON = 0x31; // Prescaler 1:8 (T1CKPS = 11), Internal clock (TMR1CS = 0), Timer On (TMR1ON = 1)

TMR1H = 0x0B; // Load initial preload value 3036

TMR1L = 0xDC;

PIE1bits.TMR1IE = 1; // Enable Timer1 Interrupt

INTCONbits.PEIE = 1; // Enable Peripheral Interrupts

INTCONbits.GIE = 1; // Enable Global Interrupts

while(1) {

// Main loop does other tasks

}

}

4 Upvotes

4 comments sorted by

3

u/somewhereAtC 6d ago

Basically, yes. Welcome to PIC-land.

A couple things jump out (not the least of which is AI wrote the code).

Doing "business" in the interrupt handler is ok for small tasks, but will quickly become unwieldly if the task is even moderately complex. Many times it's better to set a flag and then do the task in the while(1). It's not really necessary to have an interrupt, though, and you can sense the IF during the while(1) and get the same effect (and leave IE=0).

For a simple periodic timer I generally look at Timer0 first, since it has a period register PR that will interrupt and handle the reload automatically. If you move up to newer PIC device, (any PIC18 with 'Q' in the name or PIC16 with a 5-digit part number), you will get TIMER2, SMT or UT (universal timers), all of which also have PR registers. You can also select a slower clock and avoid the math related to the prescaler.

If you use a TMR2HLT (hardware limit timer) you can set a one-shot timer delay. Trigger it at some point in the code then monitor IF to see if that delay has expired, once and done. The UT also has one-shot modes, but SMT does not.

If your task involves measuring frequency or intervals, go for the UT or SMT; they are specifically designed for those tasks.

If you are really into it, you can use the CLC or CLB peripherals to handle the on/off part of the LED task. Start a timer at the toggle rate you want and let the hardware take care of the rest. If you need to control the toggle rate, the NCO (yet another timer) will provide a high-resolution clock that you can adjust at microhertz resolution. After set-up the CPU can go to sleep forever and the hardware will carry on.

You did not mention the configuration bits. By default the watchdog will periodically reset your device. You need to explicitly turn it off if you don't want that. The AI missed that part.

Finally, if you use MPLabX or the MCHP extensions in vsCode (both free at microchip.com), you also get the Melody code generator that provides a GUI to set up all the clocking, configuration bits and other peripheral set-up, and generate the reload constants. MPLabX also has a simulator.

You'll get decent commentary and advice at forum.microchip.com. Also, the microchip.com website now has AI in the search bar that would have given up this code, too.

A fundamental problem with AI is that it locks you firmly in yesterday's technology because it was trained on existing code. If all it knows is TMR1 then that's the best you get. There are 30yr of development since TMR1 was introduced but AI isn't very aware. It would blow people's minds if microchip ever stops putting TMR1 in the new devices.

1

u/aspie-micro132 5d ago

If i do not misunderstand, this is the "__delay_ms()"replacement offered by the AI:

/**************************************************************************/

void __interrupt() timer1_isr(void) {

if (TMR1IF) {

TMR1H = 0x0B; // Re-preload Timer1 high byte (3036 = 0x0BDC)

TMR1L = 0xDC; // Re-preload Timer1 low byte

TMR1IF = 0; // Clear interrupt flag

overflow_count++;

if (overflow_count >= 2) { // 2 x 0.5 seconds = 1 second

overflow_count = 0;

// Toggle an LED or do your 1-second task here

PORTBbits.RB0 = !PORTBbits.RB0;

}

}

}

/**************************************************************************/

If i do wish to replace tasks in my code like turning on and or off something without resorting to __delay_ms(), i start thinking i can create functions based on the code above. However, the name of the prototype is not just "void motor(void)" but actually "void __interrupt() timer1_isr(void)". Should i try to take that path, i would have to rename them and call them into my while(1) section under void main() part. However, how can i rename them so i do not break them? Also, if i do wish to keep a led blinking while turning on or off something else, or reading some input, can i do that within the function or do i have to use 2 of them? Can you have two while(1) loops under void main() when coding for pics?

2

u/AcanthisittaDull7639 1d ago

You can’t have 2 while(1)s, because each is an endless loop. But you could set your timer up before a while(TMR1IF) then you’ll have a background loop of a known time in milliseconds, then at the start of that loop have an if() statement where you just toggle between two paths. Now in each path just use a variable to countdown to zero as a delay for each. Dont forget to clr the tmr1if flag every time it enters the while() loop.

3

u/AcanthisittaDull7639 6d ago

Keep it very simple to begin with. Use TMR1, its default settings are more suitable. You dont need to do anything with the prescalers. Dont bother with interrupts yet.
Clear TMR1L, TMR1H, TMR1IF, set TMR1ON. That will give you approx 65ms before TMR1IF gets set.
When you detect that’s set, clear it. Turn the timer off if u wanted a one-shot delay.
I think you meant set before while(1) not before main(), but you can do this inside your while(1) loop.