Operácie

UART Kalkulačka

Zo stránky SensorWiki

Záverečný projekt predmetu MIPS / LS2026 - Broňa Dzivjaková


Zadanie

Úlohou bolo implementovať kalkulačku komunikujúcu cez sériové rozhranie UART (minimálne použiť: +, -, *, /). Používateľ zadáva matematický príklad (napr. 10+5 alebo 3.14*2) priamo cez sériový terminál a Arduino UNO výsledok vypočíta a odošle späť. Po úspešnom výpočte sa rozsvieti vstavaná LED dióda na pine 13. Program taktiež uchováva históriu posledných desiatich príkladov v pamäti EEPROM, ktorá zostane zachovaná aj po vypnutí zariadenia. Históriu je možné zobraziť príkazom 'h' a vymazať príkazom 'c'.

Vývojová doska Arduino UNO.

Literatúra:


Analýza a opis riešenia

Projekt je realizovaný na vývojovej doske Arduino UNO s mikrokontrolérom ATmega328P. Komunikácia prebieha cez UART rozhranie na rýchlosti 9600 baudov. Na vstup ani výstup nie sú potrebné žiadne externé súčiastky, nakoľko som využila vstavanú LED diódu na pine 13 (PB5) a sériový port cez USB kábel. Nakoľko matematické operácie sú na 8-bitových mikrokontroléroch nepresné, tak príklady sú riešené pomocou fixnej desatinnej čiarky. Všetky zadané hodnoty sú spracované ako stotiny (násobené *100) s použitím vlastnej funkcie. Týmto spôsobom vie kalkulačka dosiahnuť presnosť na 2 desatinné miesta.

Celkový pohľad na zariadenie.

Nezabudnite doplniť schému zapojenia! V texte by ste mali opísať základné veci zo zapojenia, samotná schéma nie je dostačujúci opis.

Schéma zapojenia.


Algoritmus a program

Program je rozdelený do niekoľkých funkcií. Po spustení sa inicializuje UART (funkcia uart_init) a LED pin ako výstup. Hlavná nekonečná slučka čaká na vstup od používateľa. Funkcia 'read_line' číta znaky zo sériového portu jeden po druhom a ukladá ich do vyrovnávacej pamäte (buffer), kým nepríde 'Enter'.

Hlavné funkcie programu:

• Funkcia 'na_stotiny', ktorá nám spracuje načítaný vstup z pamäte. Preskakuje medzery, spracuje nám záporné čísla a prenásobí ich *100.

• Funkcia 'process' je jadro kalkulačky. Kontroluje nám validitu vstupu a obsahuje iba čísla. Následne vyhľadáva operátor, podľa ktorého vykonáva matematickú operáciu. Ošetruje taktiež aj delenie nulou, výsledky so zápornými znamienkami a korektné formátovanie.

• Funkcie 'hist_save', 'hist_print' a 'hist_clear' zabezpečujú cyklický posun a ukladanie reťazcov do EEPROM pamäte. Pri zápise nového príkladu sa staré záznamy posunú o index vyššie. Pamäť ukladá 10 operácií. Pamäť sa maže zapísaním nulových bitov.

• Funkcia 'led_blink' nám rieši nastavenie pinu PB na vstavanej LED do logickej jednotky po dobu 1000 ms.


#define F_CPU 16000000UL

#include <avr/io.h>
#include <avr/eeprom.h>
#include <util/delay.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include "uart.h"


#define LED_DDR   DDRB
#define LED_PORT  PORTB
#define LED_PIN   PB5 //vstavaná LED

#define HIST_COUNT  10  // História definovaná - uloží mi posledných 10 operácií
#define HIST_LEN    32 
char EEMEM ee_hist[HIST_COUNT][HIST_LEN];

static FILE uart_stream = FDEV_SETUP_STREAM(uart_putc, uart_getc, _FDEV_SETUP_RW);

 // používané funkcie
void    led_init(void);
void    led_blink(void);
uint8_t read_line(char *buf, uint8_t max);
void    process(const char *line);
void    hist_save(const char *line);
void    hist_print(void);
void    hist_clear(void);
int     na_stotiny(const char *str);


int main(void)
{
    char buf[HIST_LEN];

    uart_init();				//basic uart inicializácia
    stdin  = &uart_stream;
    stdout = &uart_stream;
    led_init();

	printf("=================================\r\n\r");
	printf("======= MIPS 2026 ZADANIE: ======\r\n");
    printf("======== UART Kalkulačka ========\r\n");
    printf("   Zadaj príklad (napr.: 3.14*2)\r\n");
	printf("   Dostupné operandy: +, -, *, / \r\n");
    printf("     Pre pozretie histórie: h \r\n");
    printf("    Pre vymazanie histórie: c \r\n");
    printf("=================================\r\n\r\n");

    while (1)
    {
        printf("> ");
        if (read_line(buf, HIST_LEN))
        {
            if (strcmp(buf, "h") == 0) {		//čítanie zadaného vstupu 
                hist_print();					//kontrola, či nie je 'h' alebo 'c'
            }
            else if (strcmp(buf, "c") == 0) { 
                hist_clear();
            }
            else {
                process(buf);
            }
        }
    }
    return 0;
}


void led_init(void)
{
    LED_DDR  |=  (1 << LED_PIN);
    LED_PORT &= ~(1 << LED_PIN);
}
									//basic LED blikanie
void led_blink(void)
{
    LED_PORT |=  (1 << LED_PIN);
    _delay_ms(1000);
    LED_PORT &= ~(1 << LED_PIN);
}


uint8_t read_line(char *buf, uint8_t max)	//čítanie vstupu a 
{											// ukladanie do bufferu, kde to potom spracuvávam
    uint8_t i = 0;
    char c;
    while (i < max - 1)
    {
        c = uart_getc(stdin);
        if (c == '\r' || c == '\n') { uart_puts("\r\n"); break; }
        if (c == '\b' && i > 0)    { i--; uart_puts("\b \b"); continue; } //možný backspace
        uart_putc(c, stdout);
        buf[i++] = c;
    }
    buf[i] = '\0';
    return (i > 0);
}



int na_stotiny(const char *str)  //keďže nefunguje float dobre a ani iné operácie s desatinami
{								//musím to manuálne spracovať - zvolila som si prácu s max 
    int cela = 0;				// 2 desatinnými číslami 
    int desatina = 0;
    int znamienko = 1;
    const char *p = str;

    while (*p == ' ') p++;

    if (*p == '-') { znamienko = -1; p++; }
    else if (*p == '+') p++;

    //hlavným cieľom je nájsť bodku/čiarku - tu čítam čísla pred
    while (*p >= '0' && *p <= '9') {
        cela = cela * 10 + (*p - '0');
        p++;
    }

    // ak prídem na . akebo , 
    if (*p == '.' || *p == ',') {
        p++;
        if (*p >= '0' && *p <= '9') {
            desatina += (*p - '0') * 10; // desatina
            p++;
        }
        if (*p >= '0' && *p <= '9') {
            desatina += (*p - '0');      // stotina
            p++;
        }
    }

    return (cela * 100 + desatina) * znamienko;
}

void process(const char *line)
{
    // Kontrola na nepovolené znaky (písmená a iné)
    const char *check = line;
    while (*check) {
        if (!isdigit((unsigned char)*check) && *check != '.' && 
            *check != '+' && *check != '-' && *check != '*' && *check != '/') {
            printf("Chyba: neplatný znak v príklade!\r\n\r\n");
            return;
        }
        check++;
    }

    const char ops[] = "*/+-";
    const char *op_pos = NULL;
    uint8_t i;
    
    // aký operátor
    for (i = 0; i < 4; i++) {
        op_pos = strchr(line + 1, ops[i]);
        if (op_pos) break;
    }

    if (!op_pos) {
        printf("Chyba: nebol zadaný operátor!\r\n\r\n");
        return;
    }

    char op = *op_pos;

    // a-pred čiarkou, b-za čiarkou
    long a = na_stotiny(line);
    long b = na_stotiny(op_pos + 1);
    long r = 0;

    if (op == '/' && b == 0) {		//kontrola delenia s 0
        printf("Chyba: delenie nulou!\r\n\r\n");
        return;
    }

    //operácie poriešené cez * a / 100 
    if (op == '+') r = a + b;
    else if (op == '-') r = a - b;
    else if (op == '*') r = (a * b) / 100; 
    else if (op == '/') r = (a * 100) / b;

    long cela_cast = r / 100;
    long des_cast = r % 100;
    if (des_cast < 0) des_cast = -des_cast;


    // rozlišovanie, či výsledok má desatinné číslo
     printf("Vysledok: ");
    if (r < 0 && cela_cast == 0) {
        printf("-");
    }

    printf("%ld", cela_cast);

    // ak zistím, ... vypíšem bodku
    if (des_cast != 0) {
        printf(".");
        if (des_cast < 10) {
            printf("0"); // ak mám 0.05 napr.
        }
        printf("%ld", des_cast);
    }
    printf("\r\n\r\n");

    char hist_buffer[HIST_LEN];
    if (des_cast != 0) {
        if (r < 0 && cela_cast == 0) {
            sprintf(hist_buffer, "%s=-%ld.%02ld", line, cela_cast, des_cast);
        } else {
            sprintf(hist_buffer, "%s=%ld.%02ld", line, cela_cast, des_cast);
        }
    } else {
        sprintf(hist_buffer, "%s=%ld", line, cela_cast);
    }

    hist_save(hist_buffer); // uložím do histórie
    led_blink();
}

void hist_save(const char *line)
{
    char tmp[HIST_LEN];
    int8_t i;
    
    for (i = HIST_COUNT - 1; i > 0; i--) {
        eeprom_read_block(tmp, ee_hist[i - 1], HIST_LEN);
        eeprom_write_block(tmp, ee_hist[i], HIST_LEN);
    }
    eeprom_write_block(line, ee_hist[0], HIST_LEN);
}

void hist_print(void)   
{
    char tmp[HIST_LEN];
    uint8_t found = 0;
    uint8_t i;
   
    printf("============ História ===========\r\n", HIST_COUNT);
    for (i = 0; i < HIST_COUNT; i++) {
        eeprom_read_block(tmp, ee_hist[i], HIST_LEN);
        tmp[HIST_LEN - 1] = '\0';
        
        if ((unsigned char)tmp[0] == 0xFF || tmp[0] == '\0') continue;
        
        printf("  %d: %s\r\n", i + 1, tmp);
        found = 1;
    }
    if (!found) printf("       História je prázdna.\r\n");
    printf("=================================\r\n\r");
}

void hist_clear(void)  //vymazanie histórie
{
    char prazdne[HIST_LEN];
    uint8_t i;
    memset(prazdne, 0, HIST_LEN);
    for (i = 0; i < HIST_COUNT; i++) {
        eeprom_write_block(prazdne, ee_hist[i], HIST_LEN);
    }
    printf("       História je vymazaná.\r\n\r\n");
}
#ifndef UART_H_
#define UART_H_

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

void uart_init(void);
//void uart_putc(char c);
 
 
//char uart_getc(void);
int  uart_putc(char c, FILE *stream);
int  uart_getc(FILE *stream);

void uart_puts(const char *s);

#endif /* UART_H_ */
#define F_CPU 16000000UL
#define BAUD  9600
#define MYUBRR (F_CPU / 16 / BAUD - 1)

#include <avr/io.h>
#include <stdio.h>
#include "uart.h"

void uart_init(void)
{
    unsigned int ubrr = MYUBRR;

    /* nastavenie baud rate */
    UBRR0H = (unsigned char)(ubrr >> 8);
    UBRR0L = (unsigned char)(ubrr);

    /* zapni RX a TX */
    UCSR0B = (1 << RXEN0) | (1 << TXEN0);

    /* 8 datových bitov, 1 stop bit, bez parity */
    UCSR0C = (1 << UCSZ01) | (1 << UCSZ00);
}

int uart_putc(char c, FILE *stream)
{
    if (c == '\n')
        uart_putc('\r', stream);

    while (!(UCSR0A & (1 << UDRE0)));   /* čakaj kým je buffer prázdny */
    UDR0 = c;

    return 0;
}

int uart_getc(FILE *stream)
{
    while (!(UCSR0A & (1 << RXC0)));    /* čakaj na prijatý znak */
    return UDR0;
}

void uart_puts(const char *s)
{
    while (*s)
        uart_putc(*s++, NULL);
}

Pridajte sem aj zbalený kompletný projekt, napríklad takto (použite jednoznačné pomenovanie, nemôžeme mať na serveri 10x zdrojaky.zip:

Zdrojový kód: zdrojaky.zip

Overenie

Funkčnosť som overila pomocou sériového terminálu s knižnicou UART. Testovala som každú z ponúknutých operácií, operáciu s desatinnými a zápornými číslami, a aj chybové stavy.

• Základné operácie: 5+8 = 13, 8*3 = 24, -9-4 = -13, 10/4 = 2.5 89/0 = Delenie nulou

• Desatinné čísla: 3.14*7 = 21.98

• Chybové stavy: delenie nulou, zadanie písmena namiesto čísla

• História: príkaz 'h' zobrazí posledné 3 príklady, 'c' ich vymaže

• LED: po každom správnom výpočte blikne vstavaná LED na pine 13


Aplikácia.

Video:

Čo by som urobil inak

Projekt splnil požiadavky zadania, no v budúcnosti by som ho rozšírila o LCD displej, kde by sa príklady a výsledky zobrazovali priamo na zariadení bez potreby počítača a sériového terminálu. Taktiež by bolo zaujímavé pridať podporu pre viac operácií v jednom výraze s dodržaním matematickej priority (násobenie pred sčítaním).


Kľúčové slová 'Category', ktoré sú na konci stránky nemeňte.