Operácie

Vzorové riešenie MIPS 2026: Rozdiel medzi revíziami

Zo stránky SensorWiki

Balogh (diskusia | príspevky)
dBez shrnutí editace
Balogh (diskusia | príspevky)
dBez shrnutí editace
 
Riadok 108: Riadok 108:




== Analýza problémov a najčastejšie chyby
== Analýza problémov a najčastejšie chyby ==





Aktuálna revízia z 08:25, 9. jún 2026

Praktická časť skúšky z predmetu MIPS / LS2026 - Richard Balogh


Zadanie

Dostali ste neznámu elektronickú súčiastku spolu s jej datasheet-om. Vašou úlohou je z datasheetu zistiť, o aký snímač ide, identifikovať jeho vývody a navrhnúť zapojenie k mikroprocesoru ATmega328P. Analógový výstup snímača pripojte na vstup ADC0 (pin PC0). Na LCD displeji EA-DOGM163 zobrazte aktuálnu teplotu podľa predpísaného formátu.

+----------------+
| Snimac teploty |
| T = 25.3 °C    |
| Meno Priezvisk |
+----------------+

K dispozícii máte:

  • Rozširujúci modul s displejom EA-DOGM163
  • Knižnice lcd.h / lcd.c a uart.h / uart.c z cvičení
  • Datasheet obvodu LM35


Analýza a opis riešenia

Z datasheetu zistíme, že súčiastka LM35DZ je analógový snímač teploty v puzdre TO-92 s lineárnym výstupom 10 mV/°C. Má tri vývody — pri pohľade spredu (plochá strana k nám, nožičky smerujú dole) sú zľava: +Vs (napájanie), Vout (analógový výstup) a GND (zem). Napájací rozsah je 4–30 V, takže 5 V z dosky Acrob vyhovuje.

Zapojenie je jednoduché — napájanie na 5V, zem na GND a výstup Vout priamo na pin PC0 (ADC0). Nie je potrebný žiadny prídavný rezistor ani kondenzátor.

Schéma zapojenia LM35DZ k ATmega328P.

Ak sa zo schémy zapojenia neviete celkom zorientovať, možno vám pomôže fotografia zapojenia priamo na doštičke.

Zapojenie na doštičke.

Prepočet ADC na teplotu

ATmega328P má 10-bitový ADC prevodník (rozsah 0–1023). S referenčným napätím AVcc = 5 V je rozlíšenie jedného kroku:

5000 mV / 1024 = 4,88 mV/krok

Keďže LM35 dáva 10 mV/°C, rozlíšenie merania je približne 0,5 °C. Prepočet na teplotu v desatinách stupňa (celočíselná aritmetika):

t_x10 = adc_val × 5000 / 1024

Pozor na pretečenie — hodnota 1023 × 5000 = 5 115 000, čo sa nezmestí do 16-bitového typu (max 65 535). Preto je nutné pretypovanie na uint32_t:

uint16_t t_x10 = (uint32_t)adc_val * 5000 / 1024;
uint8_t  cele     = t_x10 / 10;    // celá časť
uint8_t  desatiny = t_x10 % 10;    // desatinná časť

Algoritmus a program

Program najprv inicializuje UART (pre ladenie cez printf), LCD displej a ADC prevodník. V hlavnej slučke periodicky číta hodnotu z ADC0, prepočíta ju na teplotu a zobrazí na displej.

#include <avr/io.h>
#include <util/delay.h>
#include <stdio.h>
#include "uart.h"
#include "adc.h"
#include "lcd.h"

FILE mystdout = FDEV_SETUP_STREAM(uart_putc, NULL, _FDEV_SETUP_WRITE);


int main(void)
{
    uart_init();
    stdout = &mystdout;

    lcd_init();
    lcd_bklt(1);
    lcd_command(0x0C);

    adc_init();

    char riadok[17];

    while (1)
    {
        uint16_t adc_val = adc_read(0);
        uint16_t t_x10   = (uint32_t)adc_val * 5000 / 1024;
        uint8_t  cele     = t_x10 / 10;
        uint8_t  desatiny = t_x10 % 10;

        printf("ADC=%4u  T=%2u.%1u C\r\n", adc_val, cele, desatiny);

        lcd_setCursor(1, 0);
        lcd_puts(" Snimac teploty ");

        sprintf(riadok, " T = %2u.%1u oC    ", cele, desatiny);
        lcd_setCursor(2, 0);
        lcd_puts(riadok);

        lcd_setCursor(3, 0);
        lcd_puts(" Meno Priezvisko ");

        _delay_ms(100);
    }
    return 0;
}


Analýza problémov a najčastejšie chyby

Prevažná väčšina dokázala správne identifikovať funkciu súčiastky a väčšinou aj správne identifikovať pinout z datasheetu (ak si odmyslím "pohľad zdola"). Väčšina študentov tiež ovláda jednotlivé stavebné bloky — napr. ADC inicializácia sa objavuje takmer všade a je väčšinou správna, sériová linka fungovala a často aj displej.

Hlavný problém nebol v žiadnej jednotlivej časti — každú z nich (ADC, LCD, UART) sme robili na cvičeniach samostatne. Problém je však celková integrácia. Na skúške ste mali spojiť všetky tri naraz, navyše s neznámou súčiastkou a pochopiť datasheet v angličtine.

Vaše programy boli obvykle mozaikou kúskov z rôznych cvičení (PWM, tlačidlá, ADC, UART, LCD), poskladaných vedľa seba bez toho, aby tvorili súvislý celok. To naznačuje, že ste kód z cvičení skôr kopírovali než pochopili, a keď ho bolo potrebné poskladať inak, nevedeli ste ako.

Najčastejšie chyby

  • Príliš ambiciózny prístup
    • písanie celého kódu na jeden krát bez priebežnej kompilácie a overovania, alebo
    • pokus o čítanie ADC cez prerušenie (na skúšku zbytočne zložité a nedá sa stihnúť)
  • Zmätok a neprehľadnosť v kóde
    • chýba slučka while(1), takže celý program zbehne len raz,
    • funkcia ktorá sa ani neskompiluje, pretože chýba jej deklarácia,
    • mŕtvy kód - displej je za nekonečnou slučkou, takže sa nikdy nevykoná
    • kompiluje sa celkom iný program z iného cvičenia ako ten čo je otvorený v editore
    • Copy-paste bez rozmyslu - často sa v kóde objavovali funkcie na prácu s časovačmi a PWM výstupom.

Na čo sa sústrediť pri príprave na opravný termín

  • Pochopiť, nielen kopírovať príkazy
  • Snažiť sa natrénovať celý reťazec od nuly, nie jednotlivé bloky.
  • Kompletný program: inicializuj ADC → prečítaj → prepočítaj → zobraz na LCD → opakuj.
  • Naučiť sa vzor sprintf → lcd_puts. Toto je mechanický vzor, ktorý sa opakuje stále rovnako:
  cchar buf[17];
  sprintf(buf, "T = %2d.%d oC", cele, desatiny);
  lcd_setCursor(2, 0);
  lcd_puts(buf);

Kto si toto zapamätá ako jeden blok, ušetrí na skúške cenné minúty.

  • Vedieť rýchlo preskenovať a prečítať datasheet: Stačí nájsť tri veci: čo to je, aký je pinout, aká je charakteristika.


Rozšírená verzia kódu

Na záver ukážka programu, ktorý rieši aj niektoré menšie problémy. Je tu zadefinovaný vlastný znak pre stupeň celzia aj pre priezvisko s diakritikou. Aby hodnota teploty nekolísala, spravíme 16 meraní a vypočítame z nich priemernú hodnotu, teda spravíme akýsi jednoduchý filter.

Najzložitejšie je pochopiť, že v tomto kóde sme zmenili referenčné napätie z 5,0V na interných 1,1 V. Predpokladáme, že teplota nikdy neprekročí 100 stupňov a teda aj napätie bude vždy menšie ako 1,0V. Preto zmenou referenčného napätia rapídne zlepšíme presnosť merania.


/*
 * Vzorové riešenie — rozšírená verzia (bonus)
 * =============================================
 * LM35DZ na ADC0 (PC0), displej EA-DOGM163
 *
 * Vylepšenia oproti základu:
 *   1. Interná referencia 1,1V  (rozlíšenie ~0,1 °C)
 *   2. Priemerovanie 16 vzoriek (potlačenie šumu)
 *   3. Vlastné znaky: °C (stupeň), ž, č  →  "Jožko Mrkvička"
 *
 * F_CPU=16000000UL, BAUDRATE=9600
 */

#define F_CPU 16000000UL

#include <avr/io.h>
#include <util/delay.h>
#include <stdio.h>

#include "uart.h"
#include "lcd.h"

FILE mystdout = FDEV_SETUP_STREAM(uart_putc, NULL, _FDEV_SETUP_WRITE);


/* =====================================================================
 *  Vlastné znaky v CGRAM  (5×8 pixelov)
 *
 *  CGRAM má 8 pozícií (0–7). Znak na pozícii N sa vloží ako '\x0N'.
 * ===================================================================== */

/*  Pozícia 1: stupeň °
 *
 *   .XX..     0x06
 *   X..X.     0x09
 *   X..X.     0x09
 *   .XX..     0x06
 *   .....     0x00
 *   .....     0x00
 *   .....     0x00
 *   .....     0x00
 */
unsigned char znak_stupen[8] = {
    0x06, 0x09, 0x09, 0x06, 0x00, 0x00, 0x00, 0x00
};

/*  Pozícia 2: ž  (z s mäkčeňom)
 *
 *   .X.X.     0x0A
 *   ..X..     0x04
 *   XXXX.     0x1E
 *   ...X.     0x02
 *   ..X..     0x04
 *   .X...     0x08
 *   XXXX.     0x1E
 *   .....     0x00
 */
unsigned char znak_z_hacik[8] = {
    0x0A, 0x04, 0x1E, 0x02, 0x04, 0x08, 0x1E, 0x00
};

/*  Pozícia 3: č  (c s mäkčeňom)
 *
 *   .X.X.     0x0A
 *   ..X..     0x04
 *   .XXX.     0x0E
 *   X....     0x10
 *   X....     0x10
 *   X....     0x10
 *   .XXX.     0x0E
 *   .....     0x00
 */
unsigned char znak_c_hacik[8] = {
    0x0A, 0x04, 0x0E, 0x10, 0x10, 0x10, 0x0E, 0x00
};


/* =====================================================================
 *  ADC — interná referencia 1,1V + priemerovanie
 * ===================================================================== */

void adc_init_1(void)
{
    /*  REFS1=1, REFS0=1  →  interná referencia 1,1V
     *
     *  Prečo 1,1V a nie 5V?
     *    5V:   rozlíšenie = 5000/1024 = 4,88 mV/krok → ~0,49 °C/krok
     *    1,1V: rozlíšenie = 1100/1024 = 1,07 mV/krok → ~0,11 °C/krok
     *
     *  Maximum s 1,1V referenciou je ~110 °C, čo LM35DZ (0–100 °C) pokryje.
     */
    ADMUX  = (1 << REFS1) | (1 << REFS0);

    ADCSRA = (1 << ADEN)
           | (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0);  /* /128 → 125 kHz */
}


/*  Priemer z N meraní — potlačenie šumu
 *
 *  Prečo priemerovať?
 *    Jeden ADC prevod kolíše ±1–2 LSB kvôli šumu.
 *    Priemer z 16 vzoriek to vyfiltruje.
 *    16 je mocnina 2, takže delenie je rýchly bitový posun.
 */
uint16_t adc_read_avg(uint8_t channel, uint8_t n)
{
    uint32_t sum = 0;
    for (uint8_t i = 0; i < n; i++)
    {
        sum += adc_read(channel);
        _delay_ms(2);
    }
    return (uint16_t)(sum / n);
}


/* =====================================================================
 *  MAIN
 * ===================================================================== */

int main(void)
{
    /* --- Inicializácia --- */
    uart_init();
    stdout = &mystdout;
    printf("\r\n=== LM35DZ Teplomer (bonus) ===\r\n");
    printf("Ref: 1.1V interna, priemer 16 vzoriek\r\n\r\n");

    lcd_init();
    lcd_bklt(1);
    lcd_command(0x0C);           /* display ON, cursor OFF */

    /* Nahraj vlastné znaky do CGRAM */
    def_znak(znak_stupen,  1);   /* ° na pozícii 1 → '\x01' */
    def_znak(znak_z_hacik, 2);   /* ž na pozícii 2 → '\x02' */
    def_znak(znak_c_hacik, 3);   /* č na pozícii 3 → '\x03' */

    /* Inicializuj ADC s internou 1,1V referenciou */
    adc_init_1();
    (void)adc_read(0);           /* zahoď prvý prevod po zmene Vref */
    _delay_ms(50);               /* nechaj referenciu ustáliť sa */

    char riadok[17];

    /* --- Hlavná slučka --- */
    while (1)
    {
        uint16_t adc_val = adc_read_avg(0, 16);

        /*  Prepočet s 1,1V referenciou:
         *
         *    U [mV] = adc_val × 1100 / 1024
         *    T [°C] = U / 10            (LM35: 10 mV/°C)
         *    t_x10  = adc_val × 1100 / 1024   (teplota × 10)
         *
         *    Príklad: adc_val = 227
         *      t_x10 = 227 × 1100 / 1024 = 243  →  24,3 °C
         */
        uint16_t t_x10 = (uint32_t)adc_val * 1100 / 1024;

        uint8_t cele     = t_x10 / 10;
        uint8_t desatiny = t_x10 % 10;

        /* Debug na UART */
        printf("ADC=%4u  T=%2u.%1u C\r\n", adc_val, cele, desatiny);

        /* Riadok 1: nadpis */
        lcd_setCursor(1, 0);
        lcd_puts(" Senzor LM35 ");

        /* Riadok 2: teplota so znakom ° z CGRAM pozície 1 */
        sprintf(riadok, " T = %2u.%1u \x01""C    ", cele, desatiny);
        lcd_setCursor(2, 0);
        lcd_puts(riadok);

        /* Riadok 3: "Jožko Mrkvička" s vlastnými ž a č */
        lcd_setCursor(3, 0);
        lcd_puts(" Jo\x02ko Mrkvi\x03ka ");

        _delay_ms(500);
    }

    return 0;
}


Overenie

Program funguje ako vidno na videu - je na ňom vidieť aktuálnu teplotu na displeji, ktorá reaguje na dotyk prsta (zahriatie).