├── .gitignore ├── CrashTracking ├── .gitignore ├── CrashTracking.ino ├── CrashTracking.vcxproj.filters ├── Program.cpp ├── ApplicationMonitor.h ├── CrashTracking.vcxproj └── ApplicationMonitor.cpp ├── README.md ├── CrashTracking.sln ├── LICENSE └── CrashTesting.mlx /.gitignore: -------------------------------------------------------------------------------- 1 | /bin/ 2 | /obj/ 3 | *.sdf 4 | -------------------------------------------------------------------------------- /CrashTracking/.gitignore: -------------------------------------------------------------------------------- 1 | /bin/ 2 | /obj/ 3 | .sdf 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ArduinoCrashMonitor 2 | =================== 3 | -------------------------------------------------------------------------------- /CrashTracking/CrashTracking.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Program is in Program.cpp 3 | */ 4 | -------------------------------------------------------------------------------- /CrashTracking.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 2012 4 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CrashTracking", "CrashTracking\CrashTracking.vcxproj", "{60C8E4F8-C1BA-44CD-B07B-ED536EB9C571}" 5 | EndProject 6 | Global 7 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 8 | Debug|Win32 = Debug|Win32 9 | Release|Win32 = Release|Win32 10 | EndGlobalSection 11 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 12 | {60C8E4F8-C1BA-44CD-B07B-ED536EB9C571}.Debug|Win32.ActiveCfg = Debug|Win32 13 | {60C8E4F8-C1BA-44CD-B07B-ED536EB9C571}.Debug|Win32.Build.0 = Debug|Win32 14 | {60C8E4F8-C1BA-44CD-B07B-ED536EB9C571}.Release|Win32.ActiveCfg = Release|Win32 15 | {60C8E4F8-C1BA-44CD-B07B-ED536EB9C571}.Release|Win32.Build.0 = Release|Win32 16 | EndGlobalSection 17 | GlobalSection(SolutionProperties) = preSolution 18 | HideSolutionNode = FALSE 19 | EndGlobalSection 20 | EndGlobal 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Megunolink 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /CrashTracking/CrashTracking.vcxproj.filters: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hpp;hxx;hm;inl;inc;xsd 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms 15 | 16 | 17 | 18 | 19 | Source Files 20 | 21 | 22 | Source Files 23 | 24 | 25 | 26 | 27 | Header Files 28 | 29 | 30 | -------------------------------------------------------------------------------- /CrashTracking/Program.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "ApplicationMonitor.h" 3 | 4 | Watchdog::CApplicationMonitor ApplicationMonitor; 5 | 6 | // An LED to flash while we can. 7 | const int gc_nLEDPin = 13; 8 | 9 | // countdown until the program locks up. 10 | int g_nEndOfTheWorld = 15; 11 | 12 | // number of iterations completed. 13 | int g_nIterations = 0; 14 | 15 | void setup() 16 | { 17 | Serial.begin(9600); 18 | pinMode(gc_nLEDPin, OUTPUT); 19 | Serial.println("Ready"); 20 | 21 | ApplicationMonitor.Dump(Serial); 22 | ApplicationMonitor.EnableWatchdog(Watchdog::CApplicationMonitor::Timeout_4s); 23 | //ApplicationMonitor.DisableWatchdog(); 24 | 25 | Serial.println("Hello World!"); 26 | } 27 | 28 | void loop() 29 | { 30 | ApplicationMonitor.IAmAlive(); 31 | ApplicationMonitor.SetData(g_nIterations++); 32 | 33 | Serial.println("The end is nigh!!!"); 34 | 35 | digitalWrite(gc_nLEDPin, HIGH); // turn the LED on (HIGH is the voltage level) 36 | delay(200); // wait for a second 37 | digitalWrite(gc_nLEDPin, LOW); // turn the LED off by making the voltage LOW 38 | delay(200); // wait for a second 39 | 40 | if (g_nEndOfTheWorld == 0) 41 | { 42 | Serial.println("The end is here. Goodbye cruel world."); 43 | while(1) 44 | ; // do nothing until the watchdog timer kicks in and resets the program. 45 | } 46 | 47 | --g_nEndOfTheWorld; 48 | } -------------------------------------------------------------------------------- /CrashTracking/ApplicationMonitor.h: -------------------------------------------------------------------------------- 1 | /* ************************************************************************************************ 2 | ** Class to log watchdog lockup location to eeprom. 3 | ** Configures the watchdog to fire an interrupt before resetting the micro. When the interrupt 4 | ** fires, the program counter and 4 bytes of user data are written to the eeprom. The data can 5 | ** be examined when the micro restarts. 6 | ** ************************************************************************************************ */ 7 | 8 | #pragma once 9 | #include 10 | #include 11 | 12 | namespace Watchdog 13 | { 14 | #if defined(__AVR_ATmega2560__) 15 | #define PROGRAM_COUNTER_SIZE 3 /* bytes*/ 16 | #else 17 | #define PROGRAM_COUNTER_SIZE 2 /* bytes*/ 18 | #endif 19 | 20 | struct CApplicationMonitorHeader 21 | { 22 | // The number of reports saved in the eeprom. 23 | uint8_t m_uSavedReports; 24 | 25 | // the location for the next report to be saved 26 | uint8_t m_uNextReport; 27 | 28 | } __attribute__((__packed__)); 29 | 30 | struct CCrashReport 31 | { 32 | // Address of code executing when watchdog interrupt fired. 33 | // On the 328 & 644 this is just a word pointer. For the mega, 34 | // we need 3 bytes. We can use an array to make it easy. 35 | uint8_t m_auAddress[PROGRAM_COUNTER_SIZE]; 36 | 37 | // User data. 38 | uint32_t m_uData; 39 | 40 | } __attribute__((__packed__)); 41 | 42 | class CApplicationMonitor 43 | { // The address in the eeprom where crash data is saved. The 44 | // first byte is the number of records saved, followed by 45 | // the location for the next report to be saved, followed by 46 | // the indivual CApplicationState records. 47 | const int c_nBaseAddress; 48 | 49 | // The maximum number of crash entries stored in the eeprom. 50 | const int c_nMaxEntries; 51 | enum EConstants { DEFAULT_ENTRIES = 10 }; 52 | 53 | CCrashReport m_CrashReport; 54 | public: 55 | CApplicationMonitor(int nBaseAddress = 500, int nMaxEntries = DEFAULT_ENTRIES); 56 | void Dump(Print &rDestination, bool bOnlyIfPresent = true) const; 57 | 58 | enum ETimeout 59 | { 60 | Timeout_15ms = WDTO_15MS, 61 | Timeout_30ms = WDTO_30MS, 62 | Timeout_60ms = WDTO_60MS, 63 | Timeout_120ms = WDTO_120MS, 64 | Timeout_250ms = WDTO_250MS, 65 | Timeout_500ms = WDTO_500MS, 66 | Timeout_1s = WDTO_1S, 67 | Timeout_2s = WDTO_2S, 68 | #if defined(WDTO_4S) 69 | Timeout_4s = WDTO_4S, 70 | #endif 71 | #if defined(WDTO_8S) 72 | Timeout_8s = WDTO_8S, 73 | #endif 74 | }; 75 | 76 | void EnableWatchdog(ETimeout Timeout); 77 | void DisableWatchdog(); 78 | 79 | void IAmAlive() const; 80 | void SetData(uint32_t uData) { m_CrashReport.m_uData = uData; } 81 | uint32_t GetData() const { return m_CrashReport.m_uData; } 82 | 83 | void WatchdogInterruptHandler(uint8_t *puProgramAddress); 84 | 85 | private: 86 | void SaveHeader(const CApplicationMonitorHeader &rReportHeader) const; 87 | void LoadHeader(CApplicationMonitorHeader &rReportHeader) const; 88 | 89 | void SaveCurrentReport(int nReportSlot) const; 90 | void LoadReport(int nReport, CCrashReport &rState) const; 91 | int GetAddressForReport(int nReport) const; 92 | 93 | void ReadBlock(int nBaseAddress, void *pData, uint8_t uSize) const; 94 | void WriteBlock(int nBaseAddress, const void *pData, uint8_t uSize) const; 95 | 96 | void PrintValue(Print &rDestination, const __FlashStringHelper *pLabel, 97 | uint32_t uValue, uint8_t uRadix, bool bNewLine) const; 98 | }; 99 | 100 | } 101 | 102 | extern Watchdog::CApplicationMonitor ApplicationMonitor; 103 | -------------------------------------------------------------------------------- /CrashTesting.mlx: -------------------------------------------------------------------------------- 1 | 2 | 3 | en-NZ 4 | 5 | 6 | 7 | true 8 | true 9 | true 10 | false 11 | false 12 | false 13 | 14 | 15 | 16 | true 17 | 18 | 19 | Crash tester 20 | CrashTracking\bin\Debug\CrashTracking.hex 21 | true 22 | Uno 23 | serial 24 | COM37 25 | 2013-09-16T21:33:28.1009248+12:00 26 | true 27 | false 28 | 29 | 30 | 31 | 32 | 33 | 34 | 0 35 | 36 | 37 | 0 38 | true 39 | RS232 0 40 | false 41 | 42 | Fixed 43 | COM37 44 | 45 | 9600 46 | 8 47 | None 48 | None 49 | One 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 0 61 | Connection Manager 62 | false 63 | 64 | 65 | true 66 | false 67 | false 68 | false 69 | 0 70 | Monitor 71 | false 72 | 73 | 74 | 0 75 | Program Devices 76 | false 77 | false 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | false 131 | 132 | 133 | 0 134 | d:\Users\Paul\Documents 135 | true 136 | Report {Seq} 137 | true 138 | PT1H 139 | 0001-01-01T00:00:00 140 | 141 | 142 | 143 | -------------------------------------------------------------------------------- /CrashTracking/CrashTracking.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | 14 | $(registry:HKEY_CURRENT_USER\Software\Blue Leaf Software\Arduino Build Tools@Tools) 15 | 16 | 17 | $(VCTargetsPath)Platforms\BLSArduino\ 18 | 19 | 20 | 21 | 22 | 23 | {60C8E4F8-C1BA-44CD-B07B-ED536EB9C571} 24 | CrashTracking 25 | 26 | 27 | 28 | Application 29 | true 30 | v110 31 | MultiByte 32 | 33 | 34 | Application 35 | false 36 | v110 37 | true 38 | MultiByte 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | $(ProjectDir)\bin\$(Configuration)\ 56 | .elf 57 | EEPROM;SPI;SD;Ethernet 58 | $(ArduinoCorePath);$(ArduinoLibPath);$(AVRRoot)\avr\include;$(AVRRoot)\lib\gcc\avr\4.3.2\include 59 | obj\$(Configuration)\ 60 | Mega2560 61 | 62 | 63 | $(ProjectDir)\bin\$(Configuration)\ 64 | .elf 65 | EEPROM;SPI;SD;Ethernet 66 | $(ArduinoCorePath);$(ArduinoLibPath);$(AVRRoot)\avr\include;$(AVRRoot)\lib\gcc\avr\4.3.2\include 67 | obj\$(Configuration)\ 68 | 69 | 70 | Uno 71 | 72 | 73 | 74 | Level3 75 | Disabled 76 | true 77 | 78 | 79 | true 80 | 81 | 82 | 83 | 84 | Level3 85 | MaxSpeed 86 | true 87 | true 88 | true 89 | 90 | 91 | true 92 | true 93 | true 94 | 95 | 96 | 97 | 98 | $(ArduinoVariantPath)\$(Variant);%(AdditionalIncludeDirectories) 99 | $(BuildToolsPath)ArduinoVisualStudioFixes.h 100 | 101 | 102 | 103 | 104 | $(ArduinoVariantPath)\$(Variant);%(AdditionalIncludeDirectories) 105 | $(BuildToolsPath)ArduinoVisualStudioFixes.h 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | -------------------------------------------------------------------------------- /CrashTracking/ApplicationMonitor.cpp: -------------------------------------------------------------------------------- 1 | #include "ApplicationMonitor.h" 2 | #include 3 | 4 | using namespace Watchdog; 5 | 6 | /* 7 | Function called when the watchdog interrupt fires. The function is naked so that 8 | we don't get program stated pushed onto the stack. Consequently the top two 9 | values on the stack will be the program counter when the interrupt fired. We're 10 | going to save that in the eeprom then let the second watchdog event reset the 11 | micro. We never return from this function. 12 | */ 13 | uint8_t *upStack; 14 | extern "C" { 15 | void appMon_asm_gate(void) __attribute__((used)); 16 | void appMon_asm_gate(void){ 17 | ApplicationMonitor.WatchdogInterruptHandler(upStack); 18 | } 19 | } 20 | 21 | ISR(WDT_vect, ISR_NAKED) 22 | { 23 | // Setup a pointer to the program counter. It goes in a register so we 24 | // don't mess up the stack. 25 | upStack = (uint8_t*)SP; 26 | 27 | // The stack pointer on the AVR micro points to the next available location 28 | // so we want to go back one location to get the first byte of the address 29 | // pushed onto the stack when the interrupt was triggered. There will be 30 | // PROGRAM_COUNTER_SIZE bytes there. 31 | ++upStack; 32 | 33 | // Newer versions of GCC don't like when naked functions call regular functions 34 | // so we use call an assembly gate function instead 35 | __asm__ __volatile__ ( 36 | "call appMon_asm_gate \n" 37 | ); 38 | } 39 | 40 | /* 41 | Initialize the application monitor. There should only be a single instance 42 | of the application monitor in the whole program. 43 | nBaseAddress: The address in the eeprom where crash data should be stored. 44 | nMaxEntries: The maximum number of crash entries that should be stored in the 45 | eeprom. Storage of eeprom data will take up sizeof(CApplicationMonitorHeader) + 46 | nMaxEntries * sizeof(CCrashReport) bytes in the eeprom. 47 | */ 48 | CApplicationMonitor::CApplicationMonitor(int nBaseAddress, int nMaxEntries) 49 | : c_nBaseAddress(nBaseAddress), c_nMaxEntries(nMaxEntries) 50 | { 51 | m_CrashReport.m_uData = 0; 52 | } 53 | 54 | /* 55 | Enable the watchdog timer & have it trigger an interrupt before 56 | resetting the micro. When the interrupt fires, we save the program counter 57 | to the eeprom. 58 | */ 59 | void CApplicationMonitor::EnableWatchdog(CApplicationMonitor::ETimeout Timeout) 60 | { 61 | wdt_enable(Timeout); 62 | WDTCSR |= _BV(WDIE); 63 | } 64 | 65 | void CApplicationMonitor::DisableWatchdog() 66 | { 67 | wdt_disable(); 68 | } 69 | 70 | /* 71 | Lets the watchdog timer know the program is still alive. Call this before 72 | the watchdog timeout ellapses to prevent program being aborted. 73 | */ 74 | void CApplicationMonitor::IAmAlive() const 75 | { 76 | wdt_reset(); 77 | } 78 | 79 | void CApplicationMonitor::Dump(Print &rDestination, bool bOnlyIfPresent) const 80 | { 81 | CApplicationMonitorHeader Header; 82 | CCrashReport Report; 83 | uint8_t uReport; 84 | uint32_t uAddress; 85 | 86 | LoadHeader(Header); 87 | if (!bOnlyIfPresent || Header.m_uSavedReports != 0) 88 | { 89 | rDestination.println(F("Application Monitor")); 90 | rDestination.println(F("-------------------")); 91 | PrintValue(rDestination, F("Saved reports: "), Header.m_uSavedReports, DEC, true); 92 | PrintValue(rDestination, F("Next report: "), Header.m_uNextReport, DEC, true); 93 | 94 | for (uReport = 0; uReport < Header.m_uSavedReports; ++uReport) 95 | { 96 | LoadReport(uReport, Report); 97 | 98 | rDestination.print(uReport); 99 | uAddress = 0; 100 | memcpy(&uAddress, Report.m_auAddress, PROGRAM_COUNTER_SIZE); 101 | PrintValue(rDestination, F(": word-address=0x"), uAddress, HEX, false); 102 | PrintValue(rDestination, F(": byte-address=0x"), uAddress * 2, HEX, false); 103 | PrintValue(rDestination, F(", data=0x"), Report.m_uData, HEX, true); 104 | } 105 | } 106 | } 107 | 108 | void CApplicationMonitor::PrintValue(Print &rDestination, const __FlashStringHelper *pLabel, 109 | uint32_t uValue, uint8_t uRadix, bool bNewLine) const 110 | { 111 | rDestination.print(pLabel); 112 | rDestination.print(uValue, uRadix); 113 | if (bNewLine) 114 | rDestination.println(); 115 | } 116 | 117 | void CApplicationMonitor::WatchdogInterruptHandler(uint8_t *puProgramAddress) 118 | { 119 | CApplicationMonitorHeader Header; 120 | 121 | LoadHeader(Header); 122 | memcpy(m_CrashReport.m_auAddress, puProgramAddress, PROGRAM_COUNTER_SIZE); 123 | SaveCurrentReport(Header.m_uNextReport); 124 | 125 | // Update header for next time. 126 | ++Header.m_uNextReport; 127 | if (Header.m_uNextReport >= c_nMaxEntries) 128 | Header.m_uNextReport = 0; 129 | else 130 | ++Header.m_uSavedReports; 131 | SaveHeader(Header); 132 | 133 | // Wait for next watchdog time out to reset system. 134 | // If the watch dog timeout is too short, it doesn't 135 | // give the program much time to reset it before the 136 | // next timeout. So we can be a bit generous here. 137 | wdt_enable(WDTO_120MS); 138 | while (true) 139 | ; 140 | } 141 | 142 | void CApplicationMonitor::LoadHeader(CApplicationMonitorHeader &rReportHeader) const 143 | { 144 | ReadBlock(c_nBaseAddress, &rReportHeader, sizeof(rReportHeader)); 145 | 146 | // Ensure the report structure is valid. 147 | if (rReportHeader.m_uSavedReports == 0xff) // eeprom is 0xff when uninitialized 148 | rReportHeader.m_uSavedReports = 0; 149 | else if (rReportHeader.m_uSavedReports > c_nMaxEntries) 150 | rReportHeader.m_uSavedReports = c_nMaxEntries; 151 | 152 | if (rReportHeader.m_uNextReport >= c_nMaxEntries) 153 | rReportHeader.m_uNextReport = 0; 154 | } 155 | 156 | void CApplicationMonitor::SaveHeader(const CApplicationMonitorHeader &rReportHeader) const 157 | { 158 | WriteBlock(c_nBaseAddress, &rReportHeader, sizeof(rReportHeader)); 159 | } 160 | 161 | void CApplicationMonitor::SaveCurrentReport(int nReportSlot) const 162 | { 163 | WriteBlock(GetAddressForReport(nReportSlot), &m_CrashReport, sizeof(m_CrashReport)); 164 | } 165 | 166 | void CApplicationMonitor::LoadReport(int nReport, CCrashReport &rState) const 167 | { 168 | ReadBlock(GetAddressForReport(nReport), &rState, sizeof(rState)); 169 | 170 | // The return address is reversed when we read it off the stack. Correct that. 171 | // by reversing the byte order. Assuming PROGRAM_COUNTER_SIZE is 2 or 3. 172 | uint8_t uTemp; 173 | uTemp = rState.m_auAddress[0]; 174 | rState.m_auAddress[0] = rState.m_auAddress[PROGRAM_COUNTER_SIZE - 1]; 175 | rState.m_auAddress[PROGRAM_COUNTER_SIZE - 1] = uTemp; 176 | } 177 | 178 | int CApplicationMonitor::GetAddressForReport(int nReport) const 179 | { 180 | int nAddress; 181 | 182 | nAddress = c_nBaseAddress + sizeof(CApplicationMonitorHeader); 183 | if (nReport < c_nMaxEntries) 184 | nAddress += nReport * sizeof(m_CrashReport); 185 | return nAddress; 186 | } 187 | 188 | void CApplicationMonitor::ReadBlock(int nBaseAddress, void *pData, uint8_t uSize) const 189 | { 190 | uint8_t *puData = (uint8_t *)pData; 191 | while (uSize --) 192 | *puData++ = eeprom_read_byte((const uint8_t *)nBaseAddress++); 193 | } 194 | 195 | void CApplicationMonitor::WriteBlock(int nBaseAddress, const void *pData, uint8_t uSize) const 196 | { 197 | const uint8_t *puData = (const uint8_t *)pData; 198 | while (uSize --) 199 | eeprom_write_byte((uint8_t *)nBaseAddress++, *puData++); 200 | } 201 | 202 | --------------------------------------------------------------------------------