Python Code
1 #include <xc.h>
2
3 // Configuration bits for PIC12F508 (XC8 compiler)
4 #pragma config WDTE = ON // Watchdog Timer enabled
5 #pragma config FOSC = INTRC // Internal RC oscillator (~4 MHz)
6 #pragma config MCLRE = OFF // MCLR disabled (GP3 as input)
7
8 // Pin definitions
9 #define PIN_R (1 << 1) // GP1 - Phase R
10 #define PIN_Y (1 << 4) // GP4 - Phase Y
11 #define PIN_B (1 << 5) // GP5 - Phase B
12 #define PIN_RELAY (1 << 0) // GP0 - Relay control (high = active)
13 #define PIN_LED (1 << 2) // GP2 - Status LED (high = on)
14
15 #define CLRWDT() asm("clrwdt")
16
17 // Global variables (PIC12F508 has very limited RAM - these fit comfortably)
18 unsigned char time_no_toggle[3]; // 0=R, 1=Y, 2=B (ms since last toggle)
19 unsigned char same_time_ry = 0;
20 unsigned char same_time_rb = 0;
21 unsigned char same_time_yb = 0;
22 unsigned int healthy_time = 0; // ms of continuous healthy state
23 unsigned char blink_state = 0;
24 unsigned int blink_timer = 0;
25 unsigned char last_phases = 0;
26 unsigned char current_phases = 0;
27 unsigned char last_tmr0 = 0;
28 unsigned int us_accumulator = 0; // Accumulates microseconds for 1 ms
ticks
29
30 void main(void)
31 {
32 // Calibrate internal oscillator using factory value
33 OSCCAL = __osccal_val();
34
35 // Configure TMR0: no prescaler on TMR0 (increments every instruction cy
cle ~1 µs),
36 // prescaler assigned to WDT with 1:128 ratio for longer WDT timeout
37 OPTION = 0b00001111; // PSA=1 (prescaler to WDT), PS2:0=111 (1:128), TO
CS=0 (internal clock)
38
39 // Configure GPIO directions (1 = input, 0 = output)
40 // GP5(in), GP4(in), GP3(in/don't care), GP2(out), GP1(in), GP0(out)
41 TRISGPIO = 0b00111010;
42
43 // Initialise outputs off
44 GPIO &= ~(PIN_RELAY | PIN_LED);
45
46 // Initialise TMR0 reading
47 last_tmr0 = TMR0;
48
49 // Read initial phase states to avoid spurious "toggles" on startup
50 current_phases = ((GPIO & PIN_R) ? 1 : 0) |
51 ((GPIO & PIN_Y) ? 2 : 0) |
52 ((GPIO & PIN_B) ? 4 : 0);
53 last_phases = current_phases;
54
55 // Main infinite loop
56 while (1)
57 {
58 CLRWDT();
59
60 // ----- Accurate microsecond timing using TMR0 -----
61 unsigned char curr_tmr = TMR0;
62 char delta = (char)(curr_tmr - last_tmr0);
63 if (delta < 0) delta += 256;
64 us_accumulator += (unsigned char)delta;
65 last_tmr0 = curr_tmr;
66
67 // ----- Update current phase logic levels -----
68 current_phases = ((GPIO & PIN_R) ? 1 : 0) |
69 ((GPIO & PIN_Y) ? 2 : 0) |
70 ((GPIO & PIN_B) ? 4 : 0);
71
72 // ----- Detect toggles and reset no-toggle timers -----
73 unsigned char changed = current_phases ^ last_phases;
74 if (changed & 1) time_no_toggle[0] = 0; // R toggled
75 if (changed & 2) time_no_toggle[1] = 0; // Y toggled
76 if (changed & 4) time_no_toggle[2] = 0; // B toggled
77 last_phases = current_phases;
78
79 // ----- Process 1 ms ticks -----
80 while (us_accumulator >= 1000)
81 {
82 us_accumulator -= 1000;
83
84 // Increment no-toggle timers (cap at 255)
85 if (time_no_toggle[0] < 255) time_no_toggle[0]++;
86 if (time_no_toggle[1] < 255) time_no_toggle[1]++;
87 if (time_no_toggle[2] < 255) time_no_toggle[2]++;
88
89 // Extract current logic levels
90 unsigned char r = current_phases & 1;
91 unsigned char y = (current_phases >> 1) & 1;
92 unsigned char b = (current_phases >> 2) & 1;
93
94 // Update same-phase timers
95 if (r == y) {
96 if (same_time_ry < 255) same_time_ry++;
97 } else {
98 same_time_ry = 0;
99 }
100 if (r == b) {
101 if (same_time_rb < 255) same_time_rb++;
102 } else {
103 same_time_rb = 0;
104 }
105 if (y == b) {
106 if (same_time_yb < 255) same_time_yb++;
107 } else {
108 same_time_yb = 0;
109 }
110
111 // Detect faults
112 bit missing_phase = (time_no_toggle[0] > 25) ||
113 (time_no_toggle[1] > 25) ||
114 (time_no_toggle[2] > 25);
115 bit same_phase_fault = (same_time_ry > 15) ||
116 (same_time_rb > 15) ||
117 (same_time_yb > 15);
118 bit fault = missing_phase | same_phase_fault;
119
120 // Healthy duration counter (reset on any fault)
121 if (fault) {
122 healthy_time = 0;
123 } else {
124 if (healthy_time < 2000) healthy_time++; // Cap to prevent
overflow
125 }
126
127 // 2 Hz blink timer (~250 ms period, 50% duty)
128 blink_timer++;
129 if (blink_timer >= 250) {
130 blink_timer = 0;
131 blink_state ^= 1;
132 }
133 }
134
135 // ----- Set outputs -----
136 bit relay_on = (!fault && healthy_time >= 1000);
137
138 if (relay_on) {
139 GPIO |= PIN_RELAY; // Relay active
140 } else {
141 GPIO &= ~PIN_RELAY; // Relay off
142 }
143
144 bit led_on = relay_on ? 1 : blink_state; // Solid when healthy, bli
nk when fault
145
146 if (led_on) {
147 GPIO |= PIN_LED;
148 } else {
149 GPIO &= ~PIN_LED;
150 }
151 }
152 }