/* ------------------------------------------------------------ mpx_2digit_uart.c 2 stellige gemultiplexte 7-Segmentanzeige mit gemeinsamer Anode MCU : attiny84 F_CPU : 8000000 07.02.2022 by R. Seelig ------------------------------------------------------------ */ #include #include #include #include #include #include "mpx_2conc.h" /* ATtiny 24 / 44 / 84 Anschlusspins IC +-----------+ Vcc | 1 A 14 | GND PB0 - PCINT8 - XTAL1 - SCI - CLKI | 2 T 13 | PA0 - ADC0 - AREF - PCINT0 PB1 - PCINT9 - XTAL2 | 3 t 12 | PA1 - ADC1 - AIN0 - PCINT1 PB3 - PCINT11 - /reset | 4 i 11 | PA2 - ADC2 - AIN1 - PCINT2 PB2 - PCINT10 - INT0 - OC0A / CKOUT | 5 n 10 | PA3 - ADC3 - T0 - PCINT3 PA7 - PCINT7 - ICP - OC0B - ADC7 | 6 y 9 | PA4 - ADC4 - USCK - SCL - T1 - SDO- PCINT4 PA6 - OC1A - SDA - SDI -MOSI - DI - ADC6 | 7 8 | PA5 - ADC5 - DO - MISO - OC1B - SII -PCINT5 +-----------+ */ // Zuordnung der Anschluesse der 7-Segmentanzeige zu den Portpins // des Controllers #define kseg1 A3 #define kseg0 A5 #define seg_a A1 #define seg_b A4 #define seg_c A7 #define seg_d B1 #define seg_e B0 #define seg_f A2 #define seg_g A0 #define seg_dp B2 volatile uint32_t millis; /* -------------------------------------------------------- Framebuffer, dieses Bitmuster wird auf der 7-Segmentanzeige angezeigt. Zentrales Element, da lediglich auf diese beiden Bytes geschrieben werden muss, um Segmente auf der Anzeige zum Leuchten zu bringen -------------------------------------------------------- */ volatile uint8_t seg7_fb[2]; /* -------------------------------------------------------- Bitmuster fuer alphanumerische 7-Segmentanzeige 0..9, A..F seg7_bmp[16]= alle aus -------------------------------------------------------- */ uint8_t seg7_bmp[17] = { ~0x3F, ~0x06, ~0x5B, ~0x4F, ~0x66, ~0x6D, ~0x7D, ~0x07, ~0x7F, ~0x6F, ~0x77, ~0x7C, ~0x39, ~0x5E, ~0x79, ~0x71, ~0x00 }; uint8_t seg7_dpmask = 0x00; // Maske fuer anzuzeigende Dezimalpunkte // 0xff = alle DPs aus, 0x01 = rechter DP an, // 0x02 = linker DP an // 0x03 = beide DP an /* -------------------------------------------------------- seg7_allclr schaltet alle Segmente einer Anzeige aus, funktioniert schneller als seg7_setbmp(0) -------------------------------------------------------- */ void seg7_allclr(void) { a_segset(); b_segset(); c_segset(); d_segset(); e_segset(); f_segset(); g_segset(); dp_segset(); } /* -------------------------------------------------------- seg7_setbmp schaltet einzelne Segmente einer Anzeige an oder aus, je nachdem, welches Bitmuster uebergeben wurde; -------------------------------------------------------- */ void seg7_setbmp(uint8_t bmp) { if (bmp & 0x01) a_segset(); else a_segclr(); if (bmp & 0x02) b_segset(); else b_segclr(); if (bmp & 0x04) c_segset(); else c_segclr(); if (bmp & 0x08) d_segset(); else d_segclr(); if (bmp & 0x10) e_segset(); else e_segclr(); if (bmp & 0x20) f_segset(); else f_segclr(); if (bmp & 0x40) g_segset(); else g_segclr(); if (bmp & 0x80) dp_segset(); else dp_segclr(); } /* -------------------------------------------------------- seg7_initall initialisiert alle benoetigten GPIO Pins -------------------------------------------------------- */ void seg7_initall(void) { kseg0_init(); kseg1_init(); a_seginit(); b_seginit(); c_seginit(); d_seginit(); e_seginit(); f_seginit(); g_seginit(); dp_seginit(); } /* -------------------------------------------------------- fb_clear loescht den Framebuffer (alle LEDs aus) -------------------------------------------------------- */ void fb_clear(void) { seg7_fb[0]= 0; seg7_fb[1]= 0; } /* -------------------------------------------------------- fb_sethex beschreibt den Framebuffer mit einem 2 stelligen Hexadezimalwert -------------------------------------------------------- */ void fb_sethex(uint8_t val) { seg7_fb[0] = seg7_bmp[val & 0x0f]; seg7_fb[1] = seg7_bmp[(val & 0xf0) >> 4]; } /* -------------------------------------------------------- fb_setdez beschreibt den Framebuffer mit einem 2 stelligen Dezimalwert -------------------------------------------------------- */ void fb_setdez(uint8_t val) { seg7_fb[0] = seg7_bmp[val % 10]; seg7_fb[1] = seg7_bmp[val / 10]; } /* -------------------------------------------------------- millis_delay wartet die Anzahl Millisekunden, die in dtime ge- geben ist. Die Delay-Funktion wertet die Variable millis aus, die vom Timer1 Interrupt aktuallisiert wird. -------------------------------------------------------- */ void millis_delay(uint32_t dtime) { volatile uint32_t now; now= millis; while(now + dtime > millis); } /* -------------------------------------------------------- Interruptvector Timer 1 Interruptroutine, multiplext die 7-Segmentanzeige -------------------------------------------------------- */ ISR (TIM1_COMPA_vect) { static uint8_t gk_cnt = 0; volatile uint8_t bmp_tmp; millis++; seg7_allclr(); switch (gk_cnt) { case 0 : kseg0_clr(); break; case 1 : kseg1_clr(); break; default : break; } gk_cnt++; gk_cnt = gk_cnt % 2; switch (gk_cnt) { case 0 : kseg0_set(); break; case 1 : kseg1_set(); break; default : break; } // Bitmapmuster des 7-Segmentdigits in Abhaengigkeit des // aktuell aktiven Segments laden bmp_tmp = seg7_fb[gk_cnt]; bmp_tmp |= 0x80; if (seg7_dpmask & (1 << gk_cnt)) bmp_tmp &= ~0x80; // Dezimalpunktanzeige seg7_setbmp(bmp_tmp); } /* -------------------------------------------------------- mxp_init initialisiert den Timerinterrupt und die Portpins, an den die 7-Segmentanzeige angeschlossen ist -------------------------------------------------------- */ void mpx_init(void) { TCCR1B = 1< 255) #define DIVISOR 8 #define CLOCKSELECT 2 #else #define DIVISOR 1 #define CLOCKSELECT 1 #endif #define FULL_BIT_TICKS ( (CYCLES_PER_BIT) / (DIVISOR) ) #define HALF_BIT_TICKS ( FULL_BIT_TICKS / 2) // Anzahl der Takte nach Eingang eines Pin-Changes bis zum Start des USI-Timers #define START_DELAY (99) // Anzahl der Takte die vergehen, nachdem Interrupt aktiviert ist #define COMPA_DELAY 42 #define TIMER_MIN ( COMPA_DELAY / DIVISOR ) #define TIMER_START_DELAY ( START_DELAY / DIVISOR ) #if (HALF_BIT_TICKS - TIMER_START_DELAY)>0 #define TIMER_TICKS ( HALF_BIT_TICKS - TIMER_START_DELAY ) #if (TIMER_TICKS < TIMER_MIN) #warning TIMER_TICKS zu langsam, niedrigere Baudrate waehlen #endif #else #error "TIMER_TICKS unzulaessige Werte: F_CPU, BAUDRATE and START_DELAY muessen unterschiedliche Werte besitzen" #define TIMER_TICKS 1 #endif /* ------------------------------------------------------------------------ Variable ------------------------------------------------------------------------ */ volatile bool usiserial_readfinished= true; volatile bool serialdataready = false; // zeigt an, ob ein Datum eingegangen ist volatile uint8_t serialinput; // letztes eingegangenes Datum /* ------------------------------------------------------------------ reverse_byte Bit MSB nach LSB und umgekehrt Bsp: aus 0x03 wird 0xC0 ------------------------------------------------------------------ */ static uint8_t reverse_byte (uint8_t x) { x = ((x >> 1) & 0x55) | ((x << 1) & 0xaa); x = ((x >> 2) & 0x33) | ((x << 2) & 0xcc); x = ((x >> 4) & 0x0f) | ((x << 4) & 0xf0); return x; } /* ------------------------------------------------------------------ serialreceived setzt Flag, dass ein Datum eingegangen ist und speichert dieses Datum ------------------------------------------------------------------ */ void serialreceived(uint8_t data) { serialdataready = true; serialinput = data; } /* ------------------------------------------------------------------ on_serial_pinchange Timer nach erstem Pinchange auf Baudrate konfigurieren, starten und weitere Pinchanges bis zum Ende des Frames verhindern. ------------------------------------------------------------------ */ void on_serial_pinchange() { GIMSK &= ~(1 << PCIE0); // Disable pin change interrupt TCCR0A = 2 << WGM00; // CTC mode TCCR0B = CLOCKSELECT; // Taktvorteiler GTCCR |= 1 << PSR10; // Prescaler reset OCR0A = TIMER_TICKS; // Die Zeit, an der der RxD Pin gelesen wird auf die (zeitliche) Mitte des Bits setzen TCNT0 = 0; TIFR0 = 1 << OCF0A; // Output Compareflag loeschen TIMSK0 |= 1 << OCIE0A; // und Timerinterrupts zulassen } /* ------------------------------------------------------------------ Pinchange Interruptvektor reakiert auf alle "Veraenderungen" an den Anschluessen des PortA und detektiert somit das Startbit. Mit dem Aufruf von on_serial_pinchange wird dieser Interrupt fuer den Rest des seriellen Datenframes gesperrt ------------------------------------------------------------------ */ ISR (PCINT0_vect) { uint8_t pinbVal; usiserial_readfinished= false; pinbVal = USI_PIN; if (!(pinbVal & 1 << USI_DI)) // wird nur eingelesen, wenn DI == 0 { on_serial_pinchange(); } } /* ------------------------------------------------------------------ Timer 0 Compare Match Interruptvektor startet in der zeitlichen Mitte von Bit 0 ------------------------------------------------------------------ */ ISR (TIM0_COMPA_vect) { TIMSK0 &= ~(1 << OCIE0A); // COMPA sperren TCNT0 = 0; // Zaehler auf 0 OCR0A = FULL_BIT_TICKS; // einzelne zeitliche Bitbreite // USI-Overflow zulassen, Timer 0 ist Taktquelle fuer USI USICR = 1 << USIOIE | 0 << USIWM0 | 1 << USICS0; // Resetr Start Kondition Interrupt Flag, USI OVF flag, Counter auf 8 setzen USISR = 1 << USIOIF | 8; } /* ------------------------------------------------------------------ USI Overflow Interruptvektor USI overflow interrupt fuer senden UND empfangen ------------------------------------------------------------------ */ ISR (USI_OVF_vect) { uint8_t temp = USIBR; cli(); USICR = 0; // Disable USI serialreceived(reverse_byte(temp)); GIFR = 1 << PCIF0; // Pinchange Interrupt Flag quittieren GIMSK |= 1 << PCIE0; // und Pinchange Interrupts wieder zulassen usiserial_readfinished= true; USISR |= 1 << USIOIF; // Interrupt quittieren sei(); } /* ------------------------------------------------------------------ uart_init initialisiert das USI Interface als serielle Schnittstelle RxD - USI_DI (Hinweis: nicht von den Anschluessen des SPI zum Flashen des AVR's verwirren lassen !!!) Aktivieren des Pinchange-Interrupts ------------------------------------------------------------------ */ void uart_init() { USI_PORT |= ( 1 << USI_DI ); // Pull-Up Widerstand fuer Rx-Data USI_DIR &= ~( 1 << USI_DI ); USICR = 0; // Disable USI. GIFR = 1 << PCIF0; // Clear pin change interrupt flag (PCINT0--PCINT7) GIMSK |= 1 << PCIE0; // Enable pin change interrupts (PCINT0--PCINT7) PCMSK0 |= 1 << USI_PCINT; // Enable pin change on pin PA6 sei(); } /* ------------------------------------------------------------------ uart_getchar liest ein Zeichen auf der USI-Schnittstelle ein, die als UART konfiguriert ist. RxD ist Anschluss DI (das entspricht wirklich MOSI - Anschluss der SPI Schnittstelle) ------------------------------------------------------------------ */ uint8_t uart_getchar(void) { uint8_t ch; while(!(serialdataready)) while(!(usiserial_readfinished)); serialdataready= false; ch= serialinput; _delay_us((100000 / (BAUDRATE / 100))); // warten bis 10 Bits (Startbit, Stopbit, 8 Datenbits) eingelesen sind return ch; } /* ------------------------------------------------------------------ uart_ischar testet, ob auf der USI-Schnittstelle die als UART konfiguriert ist, ein Zeichen eingetroffen ist. Hierbei wird dieses Zeichen jedoch NICHT gelesen. ------------------------------------------------------------------ */ uint8_t uart_ischar(void) { if (serialdataready) return 1; else return 0; } /* ------------------------------------------------------------------ uart_setfbval rudimentaere Funktion zum Einlesen der Daten, die auf dem Display angezeigt werden soll. uart_getfbval erwartet 3 Zeichen auf der Schnittstelle: - 1. Zeichen: 0 => kein dp wird angezeigt 1 => dp wird angezeigt - 2. Zeichen: 10er Stelle der Anzeige - 3. Zeichen: 1er Stelle der Anzeige ------------------------------------------------------------------ */ void uart_setfbval(void) { uint8_t ch; uint8_t i; ch= uart_getchar(); // Zeichen einlesen fuer DP if (ch== '1') seg7_dpmask= 0x02; else seg7_dpmask= 0x00; for (i= 0; i< 2; i++) // 2 Ziffern einlesen { ch= uart_getchar(); if (ch== ' ') { ch= 16; // Digit ausschalten } else { if ((ch >= '0') && (ch <= '9')) { ch -= '0'; } else { ch= toupper(ch); if ((ch >= 'A') && (ch <= 'F')) { ch = (ch - 'A') + 10; } } } seg7_fb[1-i]= seg7_bmp[ch]; } } /* --------------------------------------------------------------------------- M A I N --------------------------------------------------------------------------- */ int main(void) { mpx_init(); uart_init(); // beim Start alle Segmente aus seg7_dpmask= 0x00; seg7_fb[1]= seg7_bmp[16]; seg7_fb[0]= seg7_bmp[16]; while(1) { uart_setfbval(); } }