Part Number: CC2538DK
Hi all,
I've spent a good few hours on this, and am at a loss. We have a product that is using the OpenThread framework and I am attempting to get the watchdog timer working on it so that a device may recover itself should it fail in the field.
As it happens, that's exactly what the watchdog timer is for, and reading through the documentation it seems simple enough. Trouble is… maybe the dog isn't barking loud enough to reset the MCU.
For reference, I have committed my changes here. Toolchain is GNU ARM GCC (GNU Tools for ARM Embedded Processors 6-2017-q2-update) running on Linux/AMD64.
The following is the output from objdump, showing the initialisation sequence in C and assembly… to my eye, this assembly code looks correct. The initialisation code is based on what I've seen in the foundation firmware, and also what I understand of the documentation. This is supposed to:
1. Turn off the watchdog by clearing the EN bit
2. Read the current configuration into temporary storage
3. Set the timer interval to 32768 ticks (~1 second)
4. Write that to the configuration register
5. Re-read the configuration
6. Clear the (undocumented!) mode bit
7. Set the enable bit
8. Write the temporary value to the configuration register
/* Initialise watchdog */ { uint32_t tmp; HWREG(SMWDTHROSC_WDCTL) &= ~(1 << 3); /* Turn off */ 21184a: 4b0d ldr r3, [pc, #52] ; (211880 <PlatformInit+0xa8>) 21184c: 681a ldr r2, [r3, #0] 21184e: f022 0208 bic.w r2, r2, #8 211852: 601a str r2, [r3, #0] tmp = HWREG(SMWDTHROSC_WDCTL); 211854: 681a ldr r2, [r3, #0] tmp &= ~0x03; 211856: f022 0203 bic.w r2, r2, #3 tmp |= (0 << 0); HWREG(SMWDTHROSC_WDCTL) = tmp; 21185a: 601a str r2, [r3, #0] tmp = HWREG(SMWDTHROSC_WDCTL); 21185c: 681a ldr r2, [r3, #0] tmp &= ~(1 << 2); 21185e: f022 0204 bic.w r2, r2, #4 tmp |= (1 << 3); 211862: f042 0208 orr.w r2, r2, #8 HWREG(SMWDTHROSC_WDCTL) = tmp; 211866: 601a str r2, [r3, #0] 211868: bd08 pop {r3, pc} HWREG(GPIO_C_BASE | (0x0f << 2)) ^= 0xff; 21186a: 6813 ldr r3, [r2, #0] 21186c: f083 03ff eor.w r3, r3, #255 ; 0xff 211870: 6013 str r3, [r2, #0] 211872: e7e8 b.n 211846 <PlatformInit+0x6e> 211874: 400d9400 .word 0x400d9400 211878: 400db420 .word 0x400db420 21187c: 400d40d0 .word 0x400d40d0 211880: 400d5000 .word 0x400d5000
In the main loop, the following code is executed (once again, showing the disassembly with source from objdump):
void PlatformProcessDrivers(otInstance *aInstance) { 211884: b538 push {r3, r4, r5, lr} static uint16_t leds_delay = 0; static uint8_t leds_dir = 0; static uint16_t leds_speed = 10000; sInstance = aInstance; if (!leds_delay) { 211886: 4a25 ldr r2, [pc, #148] ; (21191c <PlatformProcessDrivers+0x98>) sInstance = aInstance; 211888: 4b25 ldr r3, [pc, #148] ; (211920 <PlatformProcessDrivers+0x9c>) { 21188a: 4604 mov r4, r0 sInstance = aInstance; 21188c: 6018 str r0, [r3, #0] if (!leds_delay) { 21188e: 8813 ldrh r3, [r2, #0] 211890: 4615 mov r5, r2 211892: b9f3 cbnz r3, 2118d2 <PlatformProcessDrivers+0x4e> if (leds_dir) 211894: 4923 ldr r1, [pc, #140] ; (211924 <PlatformProcessDrivers+0xa0>) 211896: 4b24 ldr r3, [pc, #144] ; (211928 <PlatformProcessDrivers+0xa4>) 211898: 780a ldrb r2, [r1, #0] leds >>= 1; 21189a: 7818 ldrb r0, [r3, #0] if (leds_dir) 21189c: b1aa cbz r2, 2118ca <PlatformProcessDrivers+0x46> leds >>= 1; 21189e: 0840 lsrs r0, r0, #1 else leds <<= 1; 2118a0: 7018 strb r0, [r3, #0] if (!(leds & 0x0f)) { 2118a2: 7818 ldrb r0, [r3, #0] 2118a4: 0700 lsls r0, r0, #28 2118a6: d106 bne.n 2118b6 <PlatformProcessDrivers+0x32> if (leds_dir) 2118a8: b18a cbz r2, 2118ce <PlatformProcessDrivers+0x4a> leds = 2; 2118aa: 2002 movs r0, #2 else leds = 4; leds_dir = !leds_dir; 2118ac: fab2 f282 clz r2, r2 2118b0: 0952 lsrs r2, r2, #5 leds = 4; 2118b2: 7018 strb r0, [r3, #0] leds_dir = !leds_dir; 2118b4: 700a strb r2, [r1, #0] } HWREG(GPIO_C_BASE | (0x0f << 2)) = leds; 2118b6: 781a ldrb r2, [r3, #0] 2118b8: 4b1c ldr r3, [pc, #112] ; (21192c <PlatformProcessDrivers+0xa8>) 2118ba: 601a str r2, [r3, #0] leds_delay = leds_speed; 2118bc: 4b1c ldr r3, [pc, #112] ; (211930 <PlatformProcessDrivers+0xac>) 2118be: 881b ldrh r3, [r3, #0] 2118c0: 802b strh r3, [r5, #0] } else { leds_delay--; } /* If button is held, pause */ if (!HWREG(GPIO_C_BASE | ((1 << 4) << 2))) { 2118c2: 4b1c ldr r3, [pc, #112] ; (211934 <PlatformProcessDrivers+0xb0>) 2118c4: 681b ldr r3, [r3, #0] 2118c6: b93b cbnz r3, 2118d8 <PlatformProcessDrivers+0x54> 2118c8: e7fe b.n 2118c8 <PlatformProcessDrivers+0x44> leds <<= 1; 2118ca: 0040 lsls r0, r0, #1 2118cc: e7e8 b.n 2118a0 <PlatformProcessDrivers+0x1c> leds = 4; 2118ce: 2004 movs r0, #4 2118d0: e7ec b.n 2118ac <PlatformProcessDrivers+0x28> leds_delay--; 2118d2: 3b01 subs r3, #1 2118d4: 8013 strh r3, [r2, #0] 2118d6: e7f4 b.n 2118c2 <PlatformProcessDrivers+0x3e> while (1); } if (!HWREG(GPIO_C_BASE | ((1 << 6) << 2))) 2118d8: 4b17 ldr r3, [pc, #92] ; (211938 <PlatformProcessDrivers+0xb4>) 2118da: 681b ldr r3, [r3, #0] 2118dc: b91b cbnz r3, 2118e6 <PlatformProcessDrivers+0x62> leds_speed = 2500; 2118de: f640 12c4 movw r2, #2500 ; 0x9c4 2118e2: 4b13 ldr r3, [pc, #76] ; (211930 <PlatformProcessDrivers+0xac>) 2118e4: 801a strh r2, [r3, #0] if (!HWREG(GPIO_C_BASE | ((1 << 7) << 2))) 2118e6: 4b15 ldr r3, [pc, #84] ; (21193c <PlatformProcessDrivers+0xb8>) 2118e8: 681b ldr r3, [r3, #0] 2118ea: b91b cbnz r3, 2118f4 <PlatformProcessDrivers+0x70> leds_speed = 40000; 2118ec: f649 4240 movw r2, #40000 ; 0x9c40 2118f0: 4b0f ldr r3, [pc, #60] ; (211930 <PlatformProcessDrivers+0xac>) 2118f2: 801a strh r2, [r3, #0] /* Feed the watchdog */ { uint32_t tmp = HWREG(SMWDTHROSC_WDCTL) & 0x0f; 2118f4: 4a12 ldr r2, [pc, #72] ; (211940 <PlatformProcessDrivers+0xbc>) 2118f6: 6813 ldr r3, [r2, #0] 2118f8: f003 030f and.w r3, r3, #15 HWREG(SMWDTHROSC_WDCTL) = 0x50 | tmp; 2118fc: f043 0150 orr.w r1, r3, #80 ; 0x50 HWREG(SMWDTHROSC_WDCTL) = 0xa0 | tmp; 211900: f043 03a0 orr.w r3, r3, #160 ; 0xa0 HWREG(SMWDTHROSC_WDCTL) = 0x50 | tmp; 211904: 6011 str r1, [r2, #0] HWREG(SMWDTHROSC_WDCTL) = 0xa0 | tmp; 211906: 6013 str r3, [r2, #0] } // should sleep and wait for interrupts here cc2538UartProcess(); 211908: f000 fc48 bl 21219c <cc2538UartProcess> cc2538RadioProcess(aInstance); 21190c: 4620 mov r0, r4 21190e: f000 f9cf bl 211cb0 <cc2538RadioProcess> cc2538AlarmProcess(aInstance); 211912: 4620 mov r0, r4 } 211914: e8bd 4038 ldmia.w sp!, {r3, r4, r5, lr} cc2538AlarmProcess(aInstance); 211918: f7ff bf1e b.w 211758 <cc2538AlarmProcess> 21191c: 20002aba .word 0x20002aba 211920: 20002980 .word 0x20002980 211924: 20002abc .word 0x20002abc 211928: 20002ab9 .word 0x20002ab9 21192c: 400db03c .word 0x400db03c 211930: 20000008 .word 0x20000008 211934: 400db040 .word 0x400db040 211938: 400db100 .word 0x400db100 21193c: 400db200 .word 0x400db200 211940: 400d5000 .word 0x400d5000
Now there's some re-ordering of instructions going on there, but the gist of what was said is still present. Notably, the while(1) loop when the LEFT button is pressed. This is simulating the CPU getting stuck and needing a reset from the watchdog. The watchdog though never triggers. If I hit that button, then break into the code with the debugger, I see the following state:
(gdb) printf "ctl=%08x stat=%08x wdt=%08x\n", (*(volatile uint32_t*)(0x400d2000)), (*(volatile uint32_t*)(0x400d2004)), (*(volatile uint32_t)(0x400d5000)) ctl=00000000 stat=004c0000 wdt=00000008
That there, is the state of the clock control and status registers, and of the watchdog register. As can be seen, both crystal oscillators are turned on and running, I/O and system clock is running at 32MHz, and the watchdog is turned on. The documentation states that with those settings, it should reset after a second (32768 ticks at 32.768kHz = 1 second).
Clearly I need to do something more to enable the watch dog, but neither the user guide for the CC2538, nor the examples in the foundation firmware are shedding any light on how this is supposed to work.