1 | #define F_CPU 9600000UL
|
2 | #include <avr/io.h>
|
3 | #include <avr/interrupt.h>
|
4 | #include <util/delay.h>
|
5 |
|
6 | /*
|
7 | * Pin 1 - Reset
|
8 | * Pin 2 - [PB3] Taster
|
9 | * Pin 3 - [ADC2] TMP36 Sensor
|
10 | * Pin 4 - GND
|
11 | * Pin 5 - [PB0] LED
|
12 | * Pin 6 - [OC0B] PWM Lüfter
|
13 | * Pin 7 - [PCINT2] Tacho
|
14 | * Pin 8 - VCC
|
15 | */
|
16 |
|
17 | const uint8_t PWM_BOTTOM = 0;
|
18 | const uint8_t PWM_TOP = 48; // 48 inkl. 0 (?)
|
19 | const uint8_t VREF = 5;
|
20 |
|
21 | uint16_t volatile to_counter; // Zähler für Timer-Overflows
|
22 | uint16_t volatile fan_tacho_signals; // Zähler für halbe Umdrehungen
|
23 | int8_t volatile temperature = 20; // Temperature in °C
|
24 | uint16_t fan_rpm; // Lüfter RPM
|
25 |
|
26 | // Timer Overflow Interrupt ISR
|
27 | // Aufruf der ISR setzt das TOV0 Flag im TIFR Register zurück.
|
28 | ISR(TIM0_OVF_vect) {
|
29 | to_counter++;
|
30 | }
|
31 |
|
32 | // ADC Conversion Complete Interrupt ISR
|
33 | // Aufruf der ISR setzt das ADIF Flag im ADCSRA Register zurück.
|
34 | ISR(ADC_vect) {
|
35 | uint16_t voltage = ((uint32_t) (ADC)) * VREF * 1000 / 1024; // Auf ref. Spannung beziehen
|
36 | voltage -= 500; // Offset abziehen
|
37 | temperature = (voltage + 10) / 10; // +10 um korrekt zu runden
|
38 | // Nächste Messung startet automatisch (Free Running Mode)
|
39 | }
|
40 |
|
41 | // Pin Change Interrupt ISR
|
42 | // Aufruf der ISR setzt das PCIF Flag im GIFR Register zurück.
|
43 | ISR(PCINT0_vect) {
|
44 | if (PINB & (1 << PB2)) {
|
45 | // Steigende Flanke
|
46 | } else {
|
47 | // Fallende Flanke
|
48 | fan_tacho_signals++;
|
49 | }
|
50 | }
|
51 |
|
52 | void setupPWM(void) {
|
53 | // Register TCCR0A/B initialisieren
|
54 | // Fast-PWM Modus wählen (TOP = OCR0A)
|
55 | // OCR0B entspricht Duty-Cycle
|
56 | TCCR0A |= (1 << COM0B1) | (0 << COM0B0) | (1 << WGM01) | (1 << WGM00);
|
57 |
|
58 | // Counter initialisieren bzw. festlegen
|
59 | OCR0A = PWM_TOP;
|
60 | OCR0B = PWM_BOTTOM; // Duty-Cycle
|
61 |
|
62 | // Prescaler auf 8 setzen
|
63 | // Formel für Fast-PWM-Frequenz: F_CPU / (Prescaler * 256)
|
64 | // 9,6 MHz: 9.600.000 / (8 * 48) = 25.000 Hz --> 25 kHz
|
65 | TCCR0B |= (1 << WGM02) | (0 << CS02) | (1 << CS01) | (0 << CS00);
|
66 |
|
67 | // Timer Overflow Interrupt einschalten
|
68 | // Erhöht Zählervariable; zur Auslösung einer ADC-Messung
|
69 | TIMSK0 |= (1 << TOIE0);
|
70 | to_counter = 0;
|
71 | }
|
72 |
|
73 | void setupADC(void) {
|
74 | // ADC Multiplexer Selection Register
|
75 | // REFS0: VCC als Referenzspannung (Standard).
|
76 | // ADLAR: Left adjust ausschalten (siehe unten!)
|
77 | // MUX[1:0]: ADC2 (PB4, Pin 3) als analogen Eingang festlegen.
|
78 | ADMUX |= (0 << REFS0) | (0 << ADLAR) | (1 << MUX1) | (0 << MUX0);
|
79 |
|
80 | // Digitalen Input für analogen Pin abschalten
|
81 | DIDR0 |= (1 << ADC2D);
|
82 |
|
83 | // ADC Control and Status Register A
|
84 | // ADEN: ADC einschalten
|
85 | // ADSC: Einzelne Messung starten
|
86 | // ADIE: ADC Conversion Complete Interrupt einschalten
|
87 | // ADPS[2:0]: ADC Prescaler auf 64 (Ziel: zwischen 50 und 200 kHz)
|
88 | // 9,6 MHz: 9.600.000 Hz / 64 = 150.000 Hz --> 150 kHz
|
89 | ADCSRA |= (1 << ADEN) | (1 << ADSC) | (1 << ADIE) | (1 << ADPS2) | (1 << ADPS1) | (0 << ADPS0);
|
90 |
|
91 | // Ergebnis wird in ADCL und ADCH (ADC Low Byte und ADC High Byte)
|
92 | // gespeichert, da 10 Bit Wert. ADC enthält vollständigen Wert.
|
93 | while (ADCSRA & (1 << ADSC)); // Ergebnis abwarten...
|
94 | (void) (ADC); // Erste Messung verwerfen (lt. Datenblatt empfohlen)
|
95 | }
|
96 |
|
97 | void setupTachoRead(void) {
|
98 | // Pin 7 für Tachosignal konfiguriern
|
99 | // Open-Collector, zieht 2x pro Umdrehung auf GND
|
100 | PORTB |= (1 << PORTB2); // Pull-up-Widerstand einschalten
|
101 | GIMSK |= (1 << PCIE); // Pin-Change-Interrupt einschalten
|
102 | PCMSK |= (1 << PCINT2); // PCI (nur!) für Pin 7 einschalten
|
103 | }
|
104 |
|
105 | void blinkLED(uint8_t times) {
|
106 | PORTB &= ~(1 << PORTB0); // LED aus
|
107 | for (uint8_t i = 0; i < times; i++) {
|
108 | PORTB |= (1 << PORTB0);
|
109 | _delay_ms(200);
|
110 | PORTB &= ~(1 << PORTB0);
|
111 | _delay_ms(200);
|
112 | }
|
113 | }
|
114 |
|
115 | void blinkMyInt(uint16_t someInt) {
|
116 | if (someInt == 0) return;
|
117 | uint8_t zt = someInt / 10000;
|
118 | blinkLED(zt + 1);
|
119 | _delay_ms(500);
|
120 | uint8_t t = (someInt - (zt * 10000)) / 1000;
|
121 | blinkLED(t + 1);
|
122 | _delay_ms(500);
|
123 | uint8_t h = (someInt - (zt * 10000) - (t * 1000)) / 100; // z.B. 123 / 100 = 1,23 ==> 1
|
124 | blinkLED(h + 1);
|
125 | _delay_ms(500);
|
126 | uint8_t z = (someInt - (zt * 10000) - (t * 1000) - (h * 100)) / 10; // z.B. (123 - 100) / 10 = 2,3 ==> 2
|
127 | blinkLED(z + 1);
|
128 | _delay_ms(500);
|
129 | uint8_t e = (someInt - (zt * 10000) - (t * 1000) - (h * 100) - (z * 10)); // z.B. 123 - ... = 3 ==> 3
|
130 | blinkLED(e + 1);
|
131 | _delay_ms(5000);
|
132 | }
|
133 |
|
134 | int main(void) {
|
135 | DDRB |= (1 << DDB1) | (1 << DDB0); // Pin 3 u. 6 als Ausgang festlegen (Lüfter/LED).
|
136 | PORTB |= (1 << PORTB3); // Taster Pull-up ein
|
137 | blinkLED(1);
|
138 |
|
139 | setupADC();
|
140 |
|
141 | setupPWM();
|
142 |
|
143 | setupTachoRead();
|
144 |
|
145 | // ADC in Free Running Mode versetzen und erste Messung starten
|
146 | ADCSRA |= (1 << ADSC) | (1 << ADATE);
|
147 |
|
148 | sei(); // Interrupts einschalten
|
149 |
|
150 | while(1) {
|
151 | if (to_counter > (25 * 1000)) { // ca. einmal pro Sekunde
|
152 | cli();
|
153 | to_counter = 0;
|
154 |
|
155 | fan_rpm = (fan_tacho_signals / 2) * 60;
|
156 | fan_tacho_signals = 0;
|
157 |
|
158 | if (temperature <= 20) {
|
159 | OCR0B = PWM_BOTTOM;
|
160 | } else if (temperature > 20 && temperature < 25) {
|
161 | OCR0B = PWM_TOP / 2;
|
162 | } else {
|
163 | OCR0B = PWM_TOP;
|
164 | }
|
165 | sei();
|
166 | }
|
167 |
|
168 | if (!(PINB & (1 << PINB3))) {
|
169 | cli();
|
170 | blinkMyInt(fan_rpm);
|
171 | sei();
|
172 | }
|
173 | }
|
174 | }
|