Vzorové riešenie MIPS 2026: Rozdiel medzi revíziami
Zo stránky SensorWiki
dBez shrnutí editace |
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).
