forked from novag/SlimLoRa
-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathSlimLoRaTimers.cpp
More file actions
executable file
·260 lines (222 loc) · 9.35 KB
/
SlimLoRaTimers.cpp
File metadata and controls
executable file
·260 lines (222 loc) · 9.35 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
#if NON_BLOCKING
// SlimLoRaTimers.cpp
//
// This file provides non-blocking timer functionality for LoRaWAN RX windows
// using either Timer1 (for ATmega328P) or Timer3 (for ATmega1284P, ATmega2560).
// It supports both a 1-second periodic interrupt and a precise one-shot microsecond timer.
#include <Arduino.h>
#include <avr/io.h>
#include <avr/interrupt.h> // Required for ISR() macro
#include "SlimLoRaTimers.h" // Include its own header for declarations
#include "SlimLoRa.h"
extern SlimLoRa lora;
// --- Conditional Timer Selection Macros ---
#if defined(__AVR_ATmega1284P__) || defined(__AVR_ATmega2560__)
#define LORAWAN_TIMER_NAME "Timer3"
#define LORAWAN_TIMER_ISR_VECT TIMER3_COMPA_vect
#define LORAWAN_TCCRA TCCR3A
#define LORAWAN_TCCRB TCCR3B
#define LORAWAN_TCNT TCNT3
#define LORAWAN_OCRA OCR3A
#define LORAWAN_TIMSK TIMSK3
#define LORAWAN_OCIEA OCIE3A
#define LORAWAN_PRESCALER_DIV_1 (1 << CS30)
#define LORAWAN_PRESCALER_DIV_8 (1 << CS31)
#define LORAWAN_PRESCALER_DIV_64 ((1 << CS31) | (1 << CS30))
#define LORAWAN_PRESCALER_DIV_256 (1 << CS32)
#define LORAWAN_PRESCALER_DIV_1024 ((1 << CS32) | (1 << CS30))
#else // Default to Timer1 for MCUs without Timer3 (e.g., ATmega328P)
#define LORAWAN_TIMER_NAME "Timer1"
#define LORAWAN_TIMER_ISR_VECT TIMER1_COMPA_vect
#define LORAWAN_TCCRA TCCR1A
#define LORAWAN_TCCRB TCCR1B
#define LORAWAN_TCNT TCNT1
#define LORAWAN_OCRA OCR1A
#define LORAWAN_TIMSK TIMSK1
#define LORAWAN_OCIEA OCIE1A
#define LORAWAN_PRESCALER_DIV_1 (1 << CS10)
#define LORAWAN_PRESCALER_DIV_8 (1 << CS11)
#define LORAWAN_PRESCALER_DIV_64 ((1 << CS11) | (1 << CS10))
#define LORAWAN_PRESCALER_DIV_256 (1 << CS12)
#define LORAWAN_PRESCALER_DIV_1024 ((1 << CS12) | (1 << CS10))
#endif
// Global volatile counter for the 1-second ticks generated by the timer ISR.
volatile byte loraWanRxTimerCounter = 0;
// Internal flag to distinguish between 1-second periodic and one-shot modes
static bool _isOneShotModeActive = false;
/**
* @brief Configures the selected hardware timer for 1-second periodic interrupts.
* This is typically used for general application timing.
*/
void initializeLoRaWAN_Timer() {
cli(); // Disable global interrupts during timer setup
// Clear control registers and stop the timer
LORAWAN_TCCRA = 0;
LORAWAN_TCCRB = 0;
LORAWAN_TCNT = 0; // Reset the timer/counter register to 0
uint16_t ocr_value;
uint8_t prescaler_bits;
// Determine OCR value and prescaler based on the MCU's F_CPU
if (F_CPU == 8000000UL) {
ocr_value = 31249;
prescaler_bits = LORAWAN_PRESCALER_DIV_256;
Serial.println("\nSlimLoRaTimer: Initializing for 8MHz F_CPU (1-sec periodic).");
} else if (F_CPU == 1000000UL) {
ocr_value = 15624;
prescaler_bits = LORAWAN_PRESCALER_DIV_64;
Serial.println("\nSlimLoRaTimer: Initializing for 1MHz F_CPU (1-sec periodic).");
} else {
Serial.print("\nSlimLoRaTimer ERROR: Unsupported F_CPU (");
Serial.print(F_CPU / 1000000UL);
Serial.println("MHz) for 1-sec periodic timer config!");
// Timer remains off (ocr_value=0, prescaler_bits=0)
return;
}
LORAWAN_OCRA = ocr_value; // Set the Output Compare Register A for 1-second period
// Configure CTC (Clear Timer on Compare Match) mode
LORAWAN_TCCRB |= (1 << WGM12);
// Set the calculated prescaler bits
LORAWAN_TCCRB |= prescaler_bits;
// Enable Compare Match A interrupt
LORAWAN_TIMSK |= (1 << LORAWAN_OCIEA);
_isOneShotModeActive = false; // Ensure one-shot mode is off
sei(); // Re-enable global interrupts
Serial.print("\nSlimLoRaTimer: ");
Serial.print(LORAWAN_TIMER_NAME);
Serial.println(" initialized for 1-second periodic interrupts.");
}
/**
* @brief Sets up the selected hardware timer for a precise one-shot interrupt.
* The interrupt will fire after 'duration_micros' has elapsed.
* This function disables any previously configured periodic mode.
*
* @param duration_micros The desired delay in microseconds before the interrupt fires.
* Maximum duration is limited by F_CPU and timer's 16-bit capacity.
* A duration of 0 or negative will trigger immediately (or as soon as possible).
*/
void setupRxOneShotTimer(unsigned long duration_micros) {
cli(); // Disable global interrupts
// Clear control registers and stop the timer
LORAWAN_TCCRA = 0;
LORAWAN_TCCRB = 0;
LORAWAN_TCNT = 0; // Reset counter
// Clear interrupt flag just in case
// Specific register TIFR1 for Timer1, TIFR3 for Timer3
#if defined(__AVR_ATmega1284P__) || defined(__AVR_ATmega2560__)
bitSet(TIFR3, OCF3A);
#else
bitSet(TIFR1, OCF1A);
#endif
unsigned long target_ticks = 0;
uint8_t prescaler_bits = 0;
float current_prescaler_val = 0; // Actual prescaler value (e.g., 1, 8, 64)
// Adjust F_CPU if CATCH_DIVIDER is active
unsigned long effective_f_cpu = F_CPU;
#if defined CATCH_DIVIDER && defined (__AVR__)
// clockShift is 0 for no division, 1 for /2, 2 for /4, etc.
// So we need to divide F_CPU by 2^clockShift.
if (clockShift > 0) {
// EVAL: I think it's better with F_CPU >> clockShift
effective_f_cpu = F_CPU >> clockShift;
// original
// effective_f_cpu = F_CPU / (1UL << clockShift);
}
#endif
// Determine the best prescaler for the given duration
// We want to avoid overflow of OCRxA (max 65535) and have good precision.
// target_ticks = (duration_micros * effective_f_cpu) / (1,000,000 * prescaler_value)
// Rearranged: prescaler_value = (duration_micros * effective_f_cpu) / (1,000,000 * target_ticks)
// Try prescaler 1
current_prescaler_val = 1;
target_ticks = (unsigned long)((float)duration_micros * (effective_f_cpu / 1000000.0) / current_prescaler_val);
if (target_ticks <= 65535UL && target_ticks > 0) {
prescaler_bits = LORAWAN_PRESCALER_DIV_1;
} else {
// Try prescaler 8
current_prescaler_val = 8;
target_ticks = (unsigned long)((float)duration_micros * (effective_f_cpu / 1000000.0) / current_prescaler_val);
if (target_ticks <= 65535UL && target_ticks > 0) {
prescaler_bits = LORAWAN_PRESCALER_DIV_8;
} else {
// Try prescaler 64
current_prescaler_val = 64;
target_ticks = (unsigned long)((float)duration_micros * (effective_f_cpu / 1000000.0) / current_prescaler_val);
if (target_ticks <= 65535UL && target_ticks > 0) {
prescaler_bits = LORAWAN_PRESCALER_DIV_64;
} else {
// Try prescaler 256
current_prescaler_val = 256;
target_ticks = (unsigned long)((float)duration_micros * (effective_f_cpu / 1000000.0) / current_prescaler_val);
if (target_ticks <= 65535UL && target_ticks > 0) {
prescaler_bits = LORAWAN_PRESCALER_DIV_256;
} else {
// Fallback to prescaler 1024 (largest, least precise for short delays)
current_prescaler_val = 1024;
target_ticks = (unsigned long)((float)duration_micros * (effective_f_cpu / 1000000.0) / current_prescaler_val);
if (target_ticks <= 65535UL && target_ticks > 0) {
prescaler_bits = LORAWAN_PRESCALER_DIV_1024;
} else {
// Duration is too long even for 1024 prescaler or other error
// Stop timer and report error, or return
Serial.print(F("SlimLoRaTimer ERROR: Duration ")); Serial.print(duration_micros); Serial.println(F("us too long or invalid for one-shot timer!"));
sei(); // Re-enable interrupts
return;
}
}
}
}
}
if (target_ticks == 0) target_ticks = 1; // Ensure at least 1 tick for very short delays
// Set OCRxA for one-shot delay
LORAWAN_OCRA = (uint16_t)(target_ticks - 1); // -1 because timer counts from 0 to OCRxA
// Configure CTC (Clear Timer on Compare Match) mode
// The WGM bits for CTC are generally consistent for 16-bit timers
LORAWAN_TCCRB |= (1 << WGM12);
// Set the chosen prescaler bits
LORAWAN_TCCRB |= prescaler_bits;
// Enable Compare Match A interrupt
LORAWAN_TIMSK |= (1 << LORAWAN_OCIEA);
rxTimerTriggered = false; // Clear flag before starting timer
_isOneShotModeActive = true; // Activate one-shot mode
sei(); // Re-enable global interrupts
#if DEBUG_SLIM >= 1
Serial.print("\nSlimLoRaTimer: One-shot "); Serial.print(LORAWAN_TIMER_NAME);
Serial.print(" set for "); Serial.print(duration_micros); Serial.print("us. Ticks: "); Serial.println(target_ticks);
#endif
}
/**
* @brief Stops the one-shot RX timer and clears its triggered flag.
* This should be called after the RX window has been processed or cancelled,
* or after the ISR has fired.
*/
void stopRxOneShotTimer() {
cli(); // Disable global interrupts
// Stop the timer by clearing prescaler bits (No clock source)
LORAWAN_TCCRB &= ~((1 << CS12) | (1 << CS11) | (1 << CS10)); // Clear prescaler bits
// Also clear WGM bits to fully reset the timer mode (important if switching modes)
LORAWAN_TCCRB &= ~((1 << WGM13) | (1 << WGM12));
LORAWAN_TCCRA = 0; // Clear TCCRA
// Disable Compare Match A interrupt
LORAWAN_TIMSK &= ~(1 << LORAWAN_OCIEA);
rxTimerTriggered = false;
_isOneShotModeActive = false; // Deactivate one-shot mode
sei(); // Re-enable global interrupts
#if DEBUG_SLIM >= 1
Serial.println("\nSlimLoRaTimer: RX One-shot timer stopped.");
#endif
}
/**
* @brief Interrupt Service Routine for the selected timer (Timer1 or Timer3).
* Handles both 1-second periodic increments and one-shot RX events.
*/
ISR(LORAWAN_TIMER_ISR_VECT) {
if (_isOneShotModeActive) {
lora.RFstatus++; // Move to next status
rxTimerTriggered = true; // Signal that the one-shot timer has elapsed
stopRxOneShotTimer();
} else {
// Only increment the 1-second counter if not in one-shot mode
loraWanRxTimerCounter++;
}
}
#endif