1 |
#include <avr/io.h>
|
2 |
#include <util/delay.h>
|
3 |
#include <avr/interrupt.h>
|
4 |
#include <avr/sleep.h> // Required for sleep modes
|
5 |
#include <stdint.h> // For standard integer types, e.g., uint8_t
|
6 |
|
7 |
// --- Pin Definitions ---
|
8 |
#define TX_PIN PIN6_bm // PA6 for transmission
|
9 |
#define CLK_PIN PIN7_bm // PA7 for rotary encoder clock (interrupt)
|
10 |
#define DT_PIN PIN1_bm // PA1 for rotary encoder data
|
11 |
#define SW_PIN PIN2_bm // PA2 for rotary encoder switch (interrupt)
|
12 |
|
13 |
// --- Task Queue Configuration ---
|
14 |
#define BUFFER_SIZE 8 // Size of the circular buffer for tasks
|
15 |
|
16 |
// --- Event Types (messages to be stored in the queue) ---
|
17 |
typedef enum {
|
18 |
NO_EVENT = 0,
|
19 |
ENCODER_RIGHT = 0x01, // Example value for right turn
|
20 |
ENCODER_LEFT = 0x02, // Example value for left turn
|
21 |
BUTTON_PRESS = 0x04 // Example value for button press
|
22 |
} EventType;
|
23 |
|
24 |
// --- Task Queue Variables ---
|
25 |
volatile EventType taskQueue[BUFFER_SIZE]; // Array for the task queue
|
26 |
volatile uint8_t queueHead = 0; // Index for adding new tasks
|
27 |
volatile uint8_t queueTail = 0; // Index for removing tasks
|
28 |
|
29 |
// --- Function to add a task to the queue ---
|
30 |
void enqueue_task(EventType event) {
|
31 |
uint8_t nextHead = (queueHead + 1) % BUFFER_SIZE;
|
32 |
// Check if queue is not full before enqueuing
|
33 |
if (nextHead != queueTail) {
|
34 |
taskQueue[queueHead] = event;
|
35 |
queueHead = nextHead;
|
36 |
}
|
37 |
}
|
38 |
|
39 |
// --- Function to remove a task from the queue ---
|
40 |
EventType dequeue_task() {
|
41 |
// Check if queue is empty
|
42 |
if (queueHead == queueTail) {
|
43 |
return NO_EVENT;
|
44 |
}
|
45 |
EventType event = taskQueue[queueTail];
|
46 |
queueTail = (queueTail + 1) % BUFFER_SIZE;
|
47 |
return event;
|
48 |
}
|
49 |
|
50 |
// --- Transmission Functions (approx. 5kbps) ---
|
51 |
// For 5kbps, each bit duration is 1 / 5000 Hz = 200 us.
|
52 |
// Adjust BIT_DURATION_US empirically for accurate timing if needed.
|
53 |
#define BIT_DURATION_US 193 // Adjusted for potential instruction overhead
|
54 |
|
55 |
void send_bit(uint8_t bit) {
|
56 |
if (bit) {
|
57 |
PORTA.OUTSET = TX_PIN; // High (Transmitter ON)
|
58 |
} else {
|
59 |
PORTA.OUTCLR = TX_PIN; // Low (Transmitter OFF)
|
60 |
}
|
61 |
_delay_us(BIT_DURATION_US);
|
62 |
}
|
63 |
|
64 |
void send_byte(uint8_t data) {
|
65 |
// Start bit (active low, so 0)
|
66 |
send_bit(0);
|
67 |
|
68 |
// Data bits (LSB first)
|
69 |
for (uint8_t i = 0; i < 8; i++) {
|
70 |
send_bit(data & 0x01);
|
71 |
data >>= 1;
|
72 |
}
|
73 |
|
74 |
// Stop bit (active high, so 1)
|
75 |
send_bit(1);
|
76 |
}
|
77 |
|
78 |
// --- Interrupt Service Routine for PORTA Pin Change Interrupts ---
|
79 |
ISR(PORTA_PORT_vect) {
|
80 |
// Clear the interrupt flags for the pins that caused the interrupt.
|
81 |
// This is crucial to prevent re-triggering immediately.
|
82 |
if (PORTA.INTFLAGS & CLK_PIN) {
|
83 |
PORTA.INTFLAGS = CLK_PIN; // Clear CLK_PIN's interrupt flag
|
84 |
// Read DT_PIN to determine direction
|
85 |
if (PORTA.IN & DT_PIN) {
|
86 |
// DT_PIN is HIGH when CLK_PIN goes HIGH -> Counter-clockwise (Left)
|
87 |
enqueue_task(ENCODER_LEFT);
|
88 |
} else {
|
89 |
// DT_PIN is LOW when CLK_PIN goes HIGH -> Clockwise (Right)
|
90 |
enqueue_task(ENCODER_RIGHT);
|
91 |
}
|
92 |
}
|
93 |
|
94 |
if (PORTA.INTFLAGS & SW_PIN) {
|
95 |
PORTA.INTFLAGS = SW_PIN; // Clear SW_PIN's interrupt flag
|
96 |
// Enqueue button press. Consider debouncing for real-world buttons.
|
97 |
enqueue_task(BUTTON_PRESS);
|
98 |
}
|
99 |
}
|
100 |
|
101 |
// --- Setup Function ---
|
102 |
void setup() {
|
103 |
// Configure TX_PIN (PA6) as output
|
104 |
PORTA.DIRSET = TX_PIN; // Define PA6 as output
|
105 |
PORTA.OUTCLR = TX_PIN; // Set initial value of PA6 to 0 (OFF)
|
106 |
|
107 |
// Configure CLK_PIN (PA7), DT_PIN (PA1), SW_PIN (PA2) as inputs
|
108 |
PORTA.DIRCLR = CLK_PIN;
|
109 |
PORTA.DIRCLR = DT_PIN;
|
110 |
PORTA.DIRCLR = SW_PIN;
|
111 |
|
112 |
// Configure interrupts for CLK_PIN (PA7) and SW_PIN (PA2) on rising edge
|
113 |
// Make sure these are the correct bits for your specific AVR family (e.g., tinyAVR 0/1/2-series, megaAVR)
|
114 |
PORTA.PIN7CTRL |= PORT_ISC_RISING_gc; // CLK_PIN interrupt on rising edge
|
115 |
PORTA.PIN2CTRL |= PORT_ISC_RISING_gc; // SW_PIN interrupt on rising edge
|
116 |
|
117 |
// Set the sleep mode to POWER_DOWN for maximum power saving.
|
118 |
// The device will wake up on any enabled interrupt.
|
119 |
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
|
120 |
|
121 |
// Enable global interrupts
|
122 |
sei();
|
123 |
}
|
124 |
|
125 |
// --- Main Loop ---
|
126 |
void loop() {
|
127 |
// First, check if there are any events to process before going to sleep.
|
128 |
// This prevents the system from sleeping if an event arrived just before `sleep_mode()`.
|
129 |
cli(); // Disable interrupts while checking/modifying the queue
|
130 |
if (queueHead != queueTail) { // If the queue is not empty
|
131 |
EventType currentEvent = dequeue_task();
|
132 |
sei(); // Re-enable interrupts
|
133 |
// Transmit the event. Keep interrupts disabled during transmission
|
134 |
// to ensure accurate bit timing.
|
135 |
cli();
|
136 |
send_byte((uint8_t)currentEvent);
|
137 |
sei();
|
138 |
} else {
|
139 |
// If the queue is empty, enable interrupts, and go to sleep.
|
140 |
// The MCU will wake up on any enabled interrupt (CLK_PIN or SW_PIN).
|
141 |
// Interrupts must be enabled *before* calling sleep_mode().
|
142 |
sei();
|
143 |
sleep_mode(); // Go to selected sleep mode
|
144 |
// Execution resumes here after an interrupt wakes the MCU
|
145 |
}
|
146 |
// After waking up or processing an event, the loop continues to check the queue again.
|
147 |
// If there were multiple events queued, it will process them one by one.
|
148 |
}
|