201 lines
11 KiB
C++
201 lines
11 KiB
C++
/* main.cpp - Main loop for Arduino sketches
|
|
Part of megaTinyCore - github.com/SpenceKonde/megaTinyCore
|
|
Copyright (c) 2018~2021 Spence Konde, (c) 2005-2013 Arduino
|
|
Free Software - LGPL 2.1, please see LICENCE.md for details */
|
|
|
|
#include <Arduino.h>
|
|
|
|
/* Required by some libraries to compile successfully. Even though it's nonsense in Arduino. */
|
|
int atexit(void ( * /*func*/)()) { return 0; }
|
|
|
|
void initVariant() __attribute__((weak));
|
|
/* Weak empty variant initialization function. The purpose is unclear. It sounds like it was intended
|
|
* initialize the variant, and specific variants would have their own implementation. But in practice
|
|
* it seems to be instead used as an initialization callback that libraries can use to run code before
|
|
* setup, like FreeRTOS - it would have been nice if the official API included such a callback. */
|
|
void initVariant() { }
|
|
|
|
void __attribute__((weak)) onPreMain();
|
|
void __attribute__((weak)) onPreMain() {
|
|
/* Override with any user code that needs to run WAY early, in .init3 */
|
|
}
|
|
void __attribute__((weak)) onBeforeInit();
|
|
void __attribute__((weak)) onBeforeInit() {
|
|
/* Override with any user code that needs to run before init() */
|
|
}
|
|
uint8_t __attribute__((weak)) onAfterInit();
|
|
uint8_t __attribute__((weak)) onAfterInit() {
|
|
/* Override with any user code that needs to run before interrupts are
|
|
* enabled but after all other core initialization.
|
|
* return 1 to not enable interrupts) */
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
int main() __attribute__((weak));
|
|
/* The main function - call initialization functions (in wiring.c) then setup, and finally loop *
|
|
* repeatedly. If SerialEvent is enabled (which should be unusual, as it is no longer a menu *
|
|
* option even, that gets checked for after each call to loop). Note that _pre_main() is *
|
|
* called first in non-optiboot configurations (neither is needed on Optibooot configurations() *
|
|
* an extra bit of initialization code in .init3 to fix the vectors and still happen if user *
|
|
* overrides main. In the past there was a USB-related function here, that is removed, as work *
|
|
* will be needed in any event at the core level if VUSB-based "stuff" arrives, but really I'm *
|
|
* just waiting for the DU-series now */
|
|
int main() {
|
|
onBeforeInit(); // Emnpty callback called before init but after the .init stuff. First normal code executed
|
|
init(); // Interrupts are turned on just prior to init() returning.
|
|
initVariant();
|
|
if (!onAfterInit()) sei(); // enable interrupts.
|
|
setup();
|
|
for (;;) {
|
|
loop();
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
#if (!defined(USING_OPTIBOOT))
|
|
|
|
|
|
|
|
/*********************************** CHECK RESET FLAGS ******************************************/
|
|
/* If we are not using Optiboot, we need to check the reset flagss, and reset via software for *
|
|
* a clean start. Unfortunately, if we clear the registers here, we'll prevent user code from *
|
|
* seeing them, which isn't helpful. As documented in the reset guide, we suggest overriding *
|
|
* this function with your own version. One example is included below and others in that guide *
|
|
* init_reset_Flags() should be overridden with one of the ones from the reset guide in any *
|
|
* production code. *
|
|
* If using optiboot, this will never be called, because Optiboot does the same thing. *
|
|
* By the time app runs, the flags will have been cleared and moved to GPIOR0* (it needs to *
|
|
* clear flags to honor bootloader entry conditions, so I didn't have a choice about that. *
|
|
* This function is called before *anything* else, so the chip is a blank slate - or it's *
|
|
* state is unknown. You're probably running at 4 MHz unless it was a dirty reset, in which *
|
|
* case it could be anything. No timekeeping is possible, period. The only exception is the *
|
|
* WDT reset timer with is independent of the HF oscillators and is designed to reset you out *
|
|
* of hangs amd bad states fthat you end up with when a bug causes the code but not the *
|
|
* hardware to reset, *
|
|
* Interrupts are disabled, or in event of dirty reset *
|
|
* LVL0EX bit will block them. In the event of a clean reset, nothing is set up. There is no *
|
|
* PWM, no timekeeping of any millis/micros/delay see no time passing and and all delays, *
|
|
* delay_microseconds, _delay_ms() and _delay_us(), are the wrong length because they are *
|
|
* based on F_CPU. *
|
|
* If you end up here from a dirty reset, you know nothing about the configuration of the *
|
|
* peripherals. Check the flags, save them if you need them, and maybe turn on an LED while *
|
|
* waiting for the WDT to trigger. If you're debugging something really nasty, you can try to *
|
|
* gather data about the nature of the fault. For example, turn on an LED if\ LVL0EX is set *
|
|
* meaning you got here from a missing ISR. With one of those little boards with 6 LEDs on *
|
|
* (many are available reasonably cheaply on aliexpress et al.) end up being very useful *
|
|
* for this sort of thing.
|
|
*
|
|
* * The register in question is GPIOR0 on megaTinyCore, GPR.GPR0 on Dx-series, but both names *
|
|
* are aliases of each other per core_devices for compatibility
|
|
*/
|
|
|
|
/* Minimum: Reset if we wound up here through malfunction - this relies on user clearing the *
|
|
* register on startup, which is rarely done in Arduino land. */
|
|
void __attribute__((weak)) init_reset_flags() ;
|
|
void __attribute__((weak)) init_reset_flags() {
|
|
uint8_t flags = RSTCTRL.RSTFR;
|
|
RSTCTRL.RSTFR = flags;
|
|
if (flags == 0) {
|
|
_PROTECTED_WRITE(RSTCTRL.SWRR, 1);
|
|
}
|
|
GPIOR0 = flags;
|
|
}
|
|
|
|
#endif
|
|
|
|
/* If using SPM from app, but not actually using Optiboot, we need to point the vector tables in the right place.
|
|
* since the "application" is actually split across "boot" and "application" pages of flash... and it's vectors
|
|
* are all in the section defined as "boot" section, tell the interrupt controller that, otherwise nothing'll work!
|
|
* This could just as well be set in init() but for the fact that we support overriding main(). I don't know if
|
|
* anyone who is doing that wants to use my flashwrite library, but it seems plausible.
|
|
* And while we way you need to take full responsibility for setting up the part if you do, nobody is going
|
|
* to figure this out; that's not a reasonable expectation.
|
|
* We also at the same time make sure there's a reset flag. We can't clear it, even though that
|
|
* needs to be done becauwe then it wouldn't be there if user needed it. But we will document the
|
|
* need to clear it and suggest overriding init_reset_flags(), and give the examples.
|
|
*/
|
|
|
|
/* So we need to do 1 or 2 things - as long as we're not using Optiboot, we should force a *
|
|
* software reset if we don't see any reset flags on startup - init_reset_flags() does that, *
|
|
* Then if we're using SPM from app, we need to also flip the it that move s the interrupts *
|
|
* to the start of flash.
|
|
*/
|
|
|
|
/**************************************************************************************************
|
|
* INITIALIZATION FUNCTIONS LOCATED ANYWHERE SPECIAL GO HERE! *
|
|
* *
|
|
* They *MUST* be declared with both the ((naked)) ahd ((used)) attributes! Without the latter, *
|
|
* the optimizer will eliminate them. Without the former, the sketch will not start... *
|
|
* Wait what? Yeah, it was generating a and outputting a ret instruction, which caused the *
|
|
* sketch to return to nowhere under certain conditions and never reach main() at all. *
|
|
* I do not understand how the old vector fixer allowed the sketch to start ever... but *
|
|
* since it was only compiled in when flash write was enabled it could have been missed for a *
|
|
* long time. *
|
|
**************************************************************************************************/
|
|
|
|
#if (!defined(USING_OPTIBOOT))
|
|
void _initThreeStuff() __attribute__ ((naked)) __attribute__((used)) __attribute__ ((section (".init3")));
|
|
// this runs, as the name implies, before the main() function is called.
|
|
#if !defined(SPM_FROM_APP)
|
|
// If we're not doing the SPM stuff, we need only check the flags
|
|
void _initThreeStuff() {
|
|
init_reset_flags();
|
|
onPreMain();
|
|
}
|
|
#else /* Otherwise, SPM_FROM_APP is defined */
|
|
#if defined(__AVR_Dx__) /* So this better be a Dx!!! */
|
|
/*******************************************
|
|
* THIS FUNCTIONALITY IS ONLY EXPOSED ON *
|
|
* DX-SERIES PARTS SO THIS CODE CANT-HAPPEN *
|
|
* megaTinyCore. You must write to flash *
|
|
* using optiboot if required *
|
|
*******************************************/
|
|
// if we are, we also need to move the vectors. See longwinded deascription above.
|
|
void _initThreeStuff() {
|
|
init_reset_flags();
|
|
_PROTECTED_WRITE(CPUINT_CTRLA,CPUINT_IVSEL_bm);
|
|
onPreMain();
|
|
}
|
|
#if (SPM_FROM_APP == -1) /* Unlimited SPM from app and no bootloader, means we need to stick entry point on page 0 */
|
|
/* Declared as being located in .trampolines so it gets put way at the start of the binary. This guarantees that
|
|
* it will be in the first page of flash. Must be marked ((used)) or LinkTime Optimization (LTO) will see
|
|
* that nothing actually calls it and optimize it away. The trick of course is that it can be called if
|
|
* the user wants to - but it's designed to be called via hideous methods like
|
|
* __asm__ __volatile__ ("call EntryPointSPM" : "+z" (zaddress))
|
|
* see Flash.h */
|
|
/* It must be located *before everything* - including PROGMEM, which the compiler puts ahead of .init.
|
|
* .trampolines however comes before progmem. The function must be naked, it must be used, and you need to guard it
|
|
* with the rjmp that hops over the spm and ret instructions unless you jump directly to the entrypoint. */
|
|
void __spm_entrypoint (void) __attribute__ ((naked)) __attribute__((used)) __attribute__ ((section (".trampolines")));
|
|
void __spm_entrypoint (void)
|
|
{
|
|
__asm__ __volatile__(
|
|
"rjmp .+4" "\n\t" // Jump over this if we got here the wrong way
|
|
"EntryPointSPM:" "\n\t" // this is the label we call
|
|
"spm z+" "\n\t" // write r0, r1 to location pointed to by r30,r31 with posrincewmwr
|
|
"ret"::); // by 2, and then return.
|
|
}
|
|
#endif /* SPM from app unlimited */
|
|
#else
|
|
#error "The selected part has SPM_FROM_APP = unlimited, isn't using Optiboot, or it's not a Dx-series. You must change one of those things"
|
|
#endif /* Only AVR Dx supports this */
|
|
/* End if DXCore only spm from app stuff */
|
|
#endif
|
|
// Finally, none of these three things need to be done if running optiboot!
|
|
// We want the vectors in the alt location, it checks, clears, and stashes the reset flags (in GPR0)
|
|
// and it providews the entrypoint we call to write to flash.
|
|
#else
|
|
void _initThreeStuff() __attribute__ ((naked)) __attribute__((used)) __attribute__ ((section (".init3")));
|
|
void _initThreeStuff() {
|
|
onPreMain();
|
|
}
|
|
#endif
|