Recent

Author Topic: Reset AVR from FPC code, possible ?  (Read 422 times)

pascalbythree

  • Full Member
  • ***
  • Posts: 130
Reset AVR from FPC code, possible ?
« on: November 21, 2022, 11:58:57 am »
Does anybody know weither there is a freepascal command on AVR to reset it? instead of reset button? So it starts at the begin again? Greets, Wouter

d.ioannidis

  • Full Member
  • ***
  • Posts: 202
    • Nephelae
Re: Reset AVR from FPC code, possible ?
« Reply #1 on: November 21, 2022, 12:12:19 pm »
Hi,

  I don't know if a reset command exists but you can achieve what you want using the watchdog functionality.

PS: In atmega4809 you can reset the MCU using a single CCP write and SWRR write after you disable interrupts .

EDIT: Added Microchip(Atmel) knowledge article reference

regards,
« Last Edit: November 21, 2022, 12:23:17 pm by d.ioannidis »

ccrause

  • Hero Member
  • *****
  • Posts: 688
Re: Reset AVR from FPC code, possible ?
« Reply #2 on: November 22, 2022, 06:49:29 pm »
  I don't know if a reset command exists but you can achieve what you want using the watchdog functionality.

PS: In atmega4809 you can reset the MCU using a single CCP write and SWRR write after you disable interrupts .

EDIT: Added Microchip(Atmel) knowledge article reference
The above description is the correct way to reset an AVR (including re-initializing hardware registers).

However, here is a shortcut way to restart code execution (without re-initializing hardware registers, so e.g. pins will retain their current values):
Code: Pascal  [Select][+][-]
  1. var
  2.   _start: record end; external name '_START';
  3.  
  4. begin
  5.   asm
  6.     // clear status register
  7.     out 0x3f, r1
  8.     {$if defined(FPC_FLASHSIZE) and (FPC_FLASHSIZE>8192)}
  9.     jmp _start
  10.     {$else}
  11.     rjmp _start
  12.     {$endif}
  13.   end;
  14. end.

d.ioannidis

  • Full Member
  • ***
  • Posts: 202
    • Nephelae
Re: Reset AVR from FPC code, possible ?
« Reply #3 on: November 24, 2022, 05:38:01 pm »
Hi,

  I had some free time so I wrote a watchdog unit, which works like Microchip's AVR watchdog timer ( actually I ported only the atmega328p part ), specific for atmega328p ( it was fun ;) ) :

Code: Pascal  [Select][+][-]
  1. unit watchdog;
  2.  
  3. {$mode ObjFPC}
  4.  
  5. interface
  6.  
  7. const
  8.   WDTO_15MS  = 0;
  9.   WDTO_30MS  = 1;
  10.   WDTO_60MS  = 2;
  11.   WDTO_120MS = 3;
  12.   WDTO_250MS = 4;
  13.   WDTO_500MS = 5;
  14.   WDTO_1S    = 6;
  15.   WDTO_2S    = 7;
  16.   //WDTO_4S    = 8;
  17.   //WDTO_8S    = 9;
  18.  
  19. procedure wdt_enable(const Timeout: byte);
  20. procedure wdt_disable;
  21. procedure wdt_reset;
  22.  
  23. implementation
  24.  
  25. var
  26.   bWdtEnable: byte = (1 << WDCE) or (1 << WDE);
  27.  
  28. procedure wdt_enable_internal(const Timeout: byte); assembler;
  29. asm
  30.          LDS     r25, bWdtEnable;
  31.          IN      r0, 0x3f;
  32.          CLI;
  33.          WDR;
  34.          STS     WDTCSR, r25;
  35.          OUT     0x3f, r0;
  36.          STS     WDTCSR, r24;
  37. end;
  38.  
  39. procedure wdt_enable(const Timeout: byte);
  40. var
  41.   tmp: byte;
  42. begin
  43.   if (Timeout and $08) <> 0 then
  44.     tmp := (1 << WDP+5) or (1 << WDE) or (Timeout and $07)
  45.   else
  46.     tmp := $00 or (1 << WDE) or (Timeout and $07);
  47.   wdt_enable_internal(tmp);
  48. end;
  49.  
  50. procedure wdt_disable; assembler; nostackframe;
  51. asm
  52.          LDS     r25, bWdtEnable;
  53.          IN      r0, 0x3f;
  54.          CLI;
  55.          WDR;
  56.          LDS     r24, WDTCSR;
  57.          OR      r24, r25;
  58.          STS     WDTCSR, r24;
  59.          STS     WDTCSR, r1;
  60.          OUT     0x3f, r0;
  61. end;
  62.  
  63. procedure wdt_reset; assembler; nostackframe;
  64. asm
  65.          WDR;
  66. end;
  67.  
  68. end.

Tested in an arduino and it works ( for me anyway ;) ) .

If you use it you'll need to reset the MCUSR register on start ( MCUSR := 0 ) .

In the following demo code, the "blink" (PB5) led starts off for 2 seconds, then we enter the loop and its starts blinking for ~8 seconds. After the first ~4 seconds, the watchdog is enabled with a 2 seconds timeout and for the last ~4 seconds we reset the timeout every blink cycle ( ~400ms < timeout) so no mcu reset. When it exits the loop the led is changed on and because we don't reset watchdog timeout after ~2 seconds, the watchdog resets the mcu.

Code: Pascal  [Select][+][-]
  1. program watchdog_demo;
  2.  
  3. uses
  4.   delay, watchdog;
  5.  
  6. const
  7.   PB5 = 1 shl 5;
  8.  
  9. var
  10.   pinport: byte absolute PORTB;
  11.   pindir: byte absolute DDRB;
  12.   i: byte = 0;
  13.  
  14. begin
  15.   MCUSR := 0;
  16.   wdt_disable;
  17.   pindir := pindir or PB5;
  18.  
  19.   pinport := pinport and not PB5;  // LED off
  20.   delay_ms(2000);    // delay 1 second
  21.  
  22.   repeat
  23.     pinport := pinport or PB5;  // LED on
  24.     delay_ms(200);    // delay 200 ms
  25.  
  26.     pinport := pinport and not PB5;  // LED off
  27.     delay_ms(200);    // delay 200 ms
  28.  
  29.     Inc(i);
  30.  
  31.     If i = 10 then
  32.       wdt_enable(WDTO_2S);
  33.  
  34.     if (i > 10) and (i <= 20) then
  35.       wdt_reset;
  36.  
  37.   until i > 20;
  38.  
  39.   pinport := pinport or PB5;  // LED on
  40.  
  41. end.

So for your specific case, all you need to do to reset the mcu from software, is something like this :

Code: Pascal  [Select][+][-]
  1.  
  2. procedure ResetMCU;
  3. begin
  4.   wdt_enable(WDTO_15MS or WDTO_1S or ....whatever)
  5. end;
  6.  
  7. ....... Somewhere else in the code ........
  8.  
  9. if blabla then
  10.   ResetMCU;
  11.  
  12. .......
  13.  


EDIT: The delay unit in the demo is from ccrauce's repo .
 
regards,

« Last Edit: November 24, 2022, 05:43:06 pm by d.ioannidis »

d.ioannidis

  • Full Member
  • ***
  • Posts: 202
    • Nephelae
Re: Reset AVR from FPC code, possible ?
« Reply #4 on: November 25, 2022, 01:41:58 am »
Hi,

  added the missing WDTO_4S and WDTO_8S options and some minor enhancements ....

Code: Pascal  [Select][+][-]
  1. unit watchdog;
  2.  
  3. {$mode ObjFPC}
  4. {$macro on}
  5.  
  6. interface
  7.  
  8. const
  9.   WDTO_15MS = 0;
  10.   WDTO_30MS = 1;
  11.   WDTO_60MS = 2;
  12.   WDTO_120MS = 3;
  13.   WDTO_250MS = 4;
  14.   WDTO_500MS = 5;
  15.   WDTO_1S = 6;
  16.   WDTO_2S = 7;
  17.   WDTO_4S = 8;
  18.   WDTO_8S = 9;
  19.  
  20. procedure wdt_enable(const Timeout: byte);
  21. procedure wdt_disable;
  22. procedure wdt_reset;
  23.  
  24. implementation
  25.  
  26. const
  27.   WDP3 = 5;
  28.  
  29. {$define _WD_PS3_MASK := (1 << WDP3)}
  30. {$define _WD_CONTROL_REG := WDTCSR}
  31. {$define _WD_CHANGE_BIT := WDCE}
  32.  
  33. {$define _WD_WDE := (1 << WDE)}
  34. {$define _WD_CHANGE_ENABLE := (1 << _WD_CHANGE_BIT) or (1 << WDE)}
  35.  
  36. var
  37.   WD_CONTROL_REG: byte absolute _WD_CONTROL_REG;
  38.   WD_PS3_MASK: byte absolute _WD_PS3_MASK;
  39.   WD_CHANGE_ENABLE: byte absolute _WD_CHANGE_ENABLE;
  40.   WD_WDE: byte absolute _WD_WDE;
  41.  
  42. procedure wdt_enable(const Timeout: byte); assembler;
  43. asm
  44.          LDI     r25, WD_CHANGE_ENABLE;
  45.          MOV     r1, r24;
  46.          ANDI    r24, 7;
  47.          ORI     r24, WD_WDE;
  48.          SBRC    r1, 3;
  49.          ORI     r24, WD_PS3_MASK;
  50.          EOR     r1, r1;
  51.          IN      r0, 0x3F;
  52.          CLI;
  53.          WDR;
  54.          STS     WD_CONTROL_REG, r25;
  55.          OUT     0x3F, r0;
  56.          STS     WD_CONTROL_REG, r24;
  57. end;
  58.  
  59. procedure wdt_disable; assembler; nostackframe;
  60. asm
  61.          IN      r0, 0x3F;
  62.          CLI;
  63.          WDR;
  64.          LDI     r24, WD_CONTROL_REG;
  65.          ORI     r24, WD_CHANGE_ENABLE;
  66.          STS     WD_CONTROL_REG, r24;
  67.          STS     WD_CONTROL_REG, r1;
  68.          OUT     0x3F, r0;
  69. end;
  70.  
  71. procedure wdt_reset; assembler; nostackframe;
  72. asm
  73.          WDR;
  74. end;
  75.  
  76. end.
 

Also forgot to mention ( quoting from avr sources ) :

Quote
    Note that for newer devices (ATmega88 and newer, effectively any
    AVR that has the option to also generate interrupts), the watchdog
    timer remains active even after a system reset (except a power-on
    condition), using the fastest prescaler value (approximately 15
    ms).  It is therefore required to turn off the watchdog early
    during program startup, the datasheet recommends a sequence like
    the following:

    \code
    #include <stdint.h>
    #include <avr/wdt.h>

    uint8_t mcusr_mirror __attribute__ ((section (".noinit")));

    void get_mcusr(void) \
      __attribute__((naked)) \
      __attribute__((section(".init3")));
    void get_mcusr(void)
    {
      mcusr_mirror = MCUSR;
      MCUSR = 0;
      wdt_disable();
    }
    \endcode

    Saving the value of MCUSR in \c mcusr_mirror is only needed if the
    application later wants to examine the reset source, but in particular,
    clearing the watchdog reset flag before disabling the
    watchdog is required, according to the datasheet.

Hope it helps !

EDIT: Some minor changes ...

regards,


« Last Edit: November 27, 2022, 03:43:32 pm by d.ioannidis »

 

TinyPortal © 2005-2018