Digitálny potenciometer: Rozdiel medzi revíziami
Zo stránky SensorWiki
| (28 medziľahlých úprav od rovnakého používateľa nie je zobrazených.) | |||
| Riadok 11: | Riadok 11: | ||
'''Literatúra:''' | '''Literatúra:''' | ||
* [https://docs.arduino.cc/hardware/uno-rev3/ Dokumentácia k arduino UNO] | |||
| Riadok 22: | Riadok 22: | ||
* ATmega328P (Arduino Uno) | * ATmega328P (Arduino Uno) | ||
* Rotačný enkóder KY-040: Inkrementálny enkóder generujúci dva signály (CLK a DT). | * Rotačný enkóder KY-040: Inkrementálny enkóder generujúci dva signály (CLK a DT). | ||
* [https://www.rcscomponents.kiev.ua/datasheets/ky-040-datasheet.pdf?srsltid=AfmBOooZffelqefbJSubrHjM7WgmpMoAZWkroVXYnVCgRyxkQOjlL_Fk/ Datasheet KY-040] | |||
* Na potlačenie mechanických zákmitov boli použité dva externé keramické kondenzátory s kapacitou 47 nF pripojené medzi signálne piny (CLK, DT) a zem. | * Na potlačenie mechanických zákmitov boli použité dva externé keramické kondenzátory s kapacitou 47 nF pripojené medzi signálne piny (CLK, DT) a zem. | ||
[[Súbor: | [[Súbor:KY-040.jpg|400px|thumb|center|KY-040.]] | ||
Schéma zapojenia: Enkodér je napájaný cez 5V a GND z Arduina. Signály CLK, DT a SW pripájam k digitálnym vstupom D2, D3 a D4. Na potlačenie šumu používam 47nF kondenzátory zapojené medzi CLK/DT a zemou. | |||
[[Súbor:Schem zap12312.jpg|400px|thumb|center|Schéma zapojenia.]] | [[Súbor:Schem zap12312.jpg|400px|thumb|center|Schéma zapojenia.]] | ||
| Riadok 34: | Riadok 34: | ||
=== Algoritmus a program === | === Algoritmus a program === | ||
Program je rozdelený na obsluhu prerušení a hlavnú slučku (spracovanie výstupu). | |||
*Obsluha prerušení (ISR): | |||
Používame externé prerušenia INT0 (PD2) a INT1 (PD3). Obe funkcie volajú spoločnú funkciu handle_encoder(). static inline void handle_encoder(): Táto funkcia je optimalizovaná pre rýchlosť. Číta celý port PIND naraz, čím zabezpečuje synchronizáciu dát CLK a DT. | |||
* Atómový prístup (Atómia dát): | |||
Keďže int32_t je 32-bitová premenná na 8-bitovom procesore, jej čítanie v hlavnej slučke prebieha v kritickej sekcii medzi cli() a sei(). | |||
*Funkcia init_all(): | |||
Konfiguruje piny ako vstupné, zapína interné pull-up rezistory a inicializuje počiatočný stav last_state | |||
* Tlačidlo a debouncing: | |||
10 ms delay: odstraňuje mechanické zákmity (debouncing) pred potvrdením stlačenia. Atómický reset umožňuje bezpečné vynulovanie encoder_pos pod ochranou inštrukcií cli() a sei(). | |||
Kontrola uvoľnenia pomocou slučky while bráni opakovanému resetovaniu, kým je tlačidlo stlačené. | |||
Výpis kódu je nižšie... | Výpis kódu je nižšie... | ||
<tabs> | <tabs> | ||
<tab name=" | <tab name="main.c"><syntaxhighlight lang="c++" style="background: LightYellow;"> | ||
#ifndef F_CPU | |||
#define F_CPU 16000000UL | |||
#endif | |||
#include <avr/io.h> | #include <avr/io.h> | ||
#include <avr/interrupt.h> | |||
#include <util/delay.h> | |||
#include <stdio.h> | |||
#include "uart.h" | |||
// Makrá pre prácu s bitmi | |||
#define set_bit(ADDRESS,BIT) (ADDRESS |= (1<<BIT)) | |||
#define clear_bit(ADDRESS,BIT) (ADDRESS &= ~(1<<BIT)) | |||
// Piny pre KY-040 (PD2=CLK, PD3=DT, PD4=SW) | |||
#define CLK_PIN PD2 | |||
#define DT_PIN PD3 | |||
#define SW_PIN PD4 | |||
// premenné | |||
volatile int32_t encoder_pos = 0; | |||
volatile uint8_t last_state = 0; | |||
// Stavová tabuľka pre spracovanie Grayovho kódu | |||
const int8_t state_table[] = {0,1,-1,0,-1,0,0,1,1,0,0,-1,0,-1,1,0}; | |||
// Nastavenie výstupu cez UART | |||
FILE mystdout = FDEV_SETUP_STREAM(uart_putc, NULL, _FDEV_SETUP_WRITE); | |||
// Logika spracovania enkodéra | |||
// static inline umožňuje kompilátoru vložiť kód priamo do ISR | |||
/ | static inline void handle_encoder(void) { | ||
uint8_t pins = PIND; // Prečítame celý port jedenkrát | |||
uint8_t current_state = 0; | |||
// Formovanie 2-bitového čísla: bit1 = CLK, bit0 = DT | |||
if (pins & (1 << CLK_PIN)) current_state |= 2; | |||
if (pins & (1 << DT_PIN)) current_state |= 1; | |||
// Výpočet indexu: (predošlý_stav << 2) | aktuálny_stav | |||
uint8_t index = (last_state << 2) | current_state; | |||
// Aktualizujeme pozíciu podľa tabuľky | |||
encoder_pos += state_table[index & 0x0F]; | |||
// Uloženie stavu pre nasledujúci krok | |||
last_state = current_state; | |||
} | } | ||
// Prerušenia na oboch kanáloch pre maximálnu presnosť | |||
ISR(INT0_vect) { | |||
handle_encoder(); | |||
} | |||
ISR(INT1_vect) { | |||
handle_encoder(); | |||
} | |||
void init_all() { | |||
uart_init(); | |||
stdout = &mystdout; | |||
// Nastavenie pinov na vstup | |||
clear_bit(DDRD, CLK_PIN); | |||
clear_bit(DDRD, DT_PIN); | |||
clear_bit(DDRD, SW_PIN); | |||
// Zapnutie vnútorných prťahovacích odporov | |||
set_bit(PORTD, CLK_PIN); | |||
set_bit(PORTD, DT_PIN); | |||
set_bit(PORTD, SW_PIN); | |||
// Inicializácia predvoleného stavu PRED povolením prerušení | |||
last_state = (PIND & (1 << CLK_PIN) ? 2 : 0) | (PIND & (1 << DT_PIN) ? 1 : 0); | |||
// Nastavenie externých prerušení na akúkoľvek logickú zmenu | |||
EICRA |= (1 << ISC00) | (1 << ISC10); | |||
EIMSK |= (1 << INT0) | (1 << INT1); | |||
sei(); | |||
} | |||
int main(void) { | |||
init_all(); | |||
int32_t last_printed = 0; | |||
printf("\n--- Digital Potentiometer Ready ---\n"); | |||
while(1) { | |||
// Atomické čítanie pozície (ochrana pred prerušením počas čítania 4 bajtov) | |||
int32_t safe_pos; | |||
cli(); | |||
safe_pos = encoder_pos; | |||
sei(); | |||
// Spracovanie stlačenia tlačidla (reset to zero) | |||
if (!(PIND & (1 << SW_PIN))) { | |||
_delay_ms(10); // Softvérový debounce pre tlačidlo | |||
if (!(PIND & (1 << SW_PIN))) { | |||
cli(); | |||
encoder_pos = 0; | |||
safe_pos = 0; | |||
sei(); | |||
printf("[ RESET TO ZERO ]\n"); | |||
while(!(PIND & (1 << SW_PIN))); // Čakáme na uvoľnenie | |||
} | |||
} | |||
// Výpis do konzoly iba pri zmene | |||
if (safe_pos != last_printed) { | |||
printf("Steps: %ld \n", safe_pos); | |||
last_printed = safe_pos; | |||
} | |||
} | |||
} | |||
</syntaxhighlight ></tab> | |||
<tab name="uart.c"><syntaxhighlight lang="c++" style="background: LightYellow;"> | |||
#define F_CPU 16000000UL | |||
#define BAUD 9600 | |||
#include <util/setbaud.h> | |||
#include "uart.h" | |||
void uart_init(void) { | |||
UBRR0H = UBRRH_VALUE; | |||
UBRR0L = UBRRL_VALUE; | |||
#if USE_2X | |||
UCSR0A |= (1 << U2X0); | |||
#else | |||
UCSR0A &= ~(1 << U2X0); | |||
#endif | |||
UCSR0C = (1 << UCSZ01) | (1 << UCSZ00); | |||
UCSR0B = (1 << RXEN0) | (1 << TXEN0); | |||
} | |||
int uart_putc(char c, FILE *stream) { | |||
if (c == '\n') uart_putc('\r', stream); | |||
loop_until_bit_is_set(UCSR0A, UDRE0); | |||
UDR0 = c; | |||
return 0; | |||
} | |||
int uart_getc(FILE *stream) { | |||
loop_until_bit_is_set(UCSR0A, RXC0); | |||
return UDR0; | |||
} | |||
void uart_puts(const char *s) { | |||
while (*s) { | |||
uart_putc(*s++, NULL); | |||
} | |||
} | |||
</syntaxhighlight ></tab> | </syntaxhighlight ></tab> | ||
<tab name=" | <tab name="uart.h"><syntaxhighlight lang="c++" style="background: LightYellow;"> | ||
#ifndef UART_H_ | |||
#define UART_H_ | |||
#include <avr/io.h> | #include <avr/io.h> | ||
#include <stdio.h> | |||
void | void uart_init(void); | ||
int uart_putc(char c, FILE *stream); | |||
int uart_getc(FILE *stream); | |||
void uart_puts(const char *s); | |||
#endif | |||
</syntaxhighlight ></tab> | </syntaxhighlight ></tab> | ||
</tabs> | </tabs> | ||
Pridajte sem aj zbalený kompletný projekt, napríklad takto (použite jednoznačné pomenovanie, nemôžeme mať na serveri 10x ''zdrojaky.zip'': | 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: [[Médiá: | Zdrojový kód: [[Médiá:ProjectYaroslavBuryk.zip|zdrojaky.zip]] | ||
=== Overenie === | === Overenie === | ||
Na overenie funkčnosti digitálneho potenciometra som použil posielanie dát cez sériový terminál pomocou knižnice UART. | |||
* Pri otáčaní v smere hodinových ručičiek hodnoty v termináli narastali a pri opačnom pohybe klesali bez akýchkoľvek náhodných skokov. | |||
* Testovanie presnosti: Vykonal som test rýchleho otáčania hriadeľa v oboch smeroch. Systém sa pri návrate do východiskovej polohy vždy vrátil k 0(ak začínalo z 0). | |||
[[Súbor: | * Overenie tlačidla: Krátkym stlačením hriadeľa (tlačidlo SW) som overil okamžité vynulovanie počítadla. | ||
[[Súbor:Photo 2026-05-15 18-01-32.jpg|400px|thumb|center|Zapojenie]] | |||
'''Video:''' | '''Video:''' | ||
<center><youtube> | <center><youtube>kjprDCVZCww</youtube></center> | ||
== Čo by som urobil inak == | == Čo by som urobil inak == | ||
Aktuálna revízia z 18:50, 15. máj 2026
Záverečný projekt predmetu MIPS / LS2026 - Yaroslav Buryk
Zadanie
Cieľom bolo navrhnúť a realizovať program pre mikrokontrolér (ATmega328P), ktorý slúži ako digitálny potenciometer.Program musí presne načítavať impulzy bez straty krokov aj pri rýchlej zmene smeru otáčania a realizovať ošetrenie voči mechanickým zákmitom kontaktov (debouncing).

https://docs.arduino.cc/static/cb2219c4f549dd9a7fe040362b668567/a6d36/Pinout-UNOrev3_latest.png
Literatúra:
Analýza a opis riešenia
Pre riešenie úlohy bol zvolený prístup využívajúci hardvérové prerušenia a stavový automat (FSM). Na detekciu smeru otáčania sa využíva stavová tabuľka (state table), ktorá vyhodnocuje prechody v Grayovom kóde. Každá zmena na pinoch vyvolá prerušenie, kde sa porovnáva predchádzajúci stav s aktuálnym. Použité komponenty:
- ATmega328P (Arduino Uno)
- Rotačný enkóder KY-040: Inkrementálny enkóder generujúci dva signály (CLK a DT).
- Datasheet KY-040
- Na potlačenie mechanických zákmitov boli použité dva externé keramické kondenzátory s kapacitou 47 nF pripojené medzi signálne piny (CLK, DT) a zem.

Schéma zapojenia: Enkodér je napájaný cez 5V a GND z Arduina. Signály CLK, DT a SW pripájam k digitálnym vstupom D2, D3 a D4. Na potlačenie šumu používam 47nF kondenzátory zapojené medzi CLK/DT a zemou.

Algoritmus a program
Program je rozdelený na obsluhu prerušení a hlavnú slučku (spracovanie výstupu).
- Obsluha prerušení (ISR):
Používame externé prerušenia INT0 (PD2) a INT1 (PD3). Obe funkcie volajú spoločnú funkciu handle_encoder(). static inline void handle_encoder(): Táto funkcia je optimalizovaná pre rýchlosť. Číta celý port PIND naraz, čím zabezpečuje synchronizáciu dát CLK a DT.
- Atómový prístup (Atómia dát):
Keďže int32_t je 32-bitová premenná na 8-bitovom procesore, jej čítanie v hlavnej slučke prebieha v kritickej sekcii medzi cli() a sei().
- Funkcia init_all():
Konfiguruje piny ako vstupné, zapína interné pull-up rezistory a inicializuje počiatočný stav last_state
- Tlačidlo a debouncing:
10 ms delay: odstraňuje mechanické zákmity (debouncing) pred potvrdením stlačenia. Atómický reset umožňuje bezpečné vynulovanie encoder_pos pod ochranou inštrukcií cli() a sei(). Kontrola uvoľnenia pomocou slučky while bráni opakovanému resetovaniu, kým je tlačidlo stlačené. Výpis kódu je nižšie...
#ifndef F_CPU
#define F_CPU 16000000UL
#endif
#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>
#include <stdio.h>
#include "uart.h"
// Makrá pre prácu s bitmi
#define set_bit(ADDRESS,BIT) (ADDRESS |= (1<<BIT))
#define clear_bit(ADDRESS,BIT) (ADDRESS &= ~(1<<BIT))
// Piny pre KY-040 (PD2=CLK, PD3=DT, PD4=SW)
#define CLK_PIN PD2
#define DT_PIN PD3
#define SW_PIN PD4
// premenné
volatile int32_t encoder_pos = 0;
volatile uint8_t last_state = 0;
// Stavová tabuľka pre spracovanie Grayovho kódu
const int8_t state_table[] = {0,1,-1,0,-1,0,0,1,1,0,0,-1,0,-1,1,0};
// Nastavenie výstupu cez UART
FILE mystdout = FDEV_SETUP_STREAM(uart_putc, NULL, _FDEV_SETUP_WRITE);
// Logika spracovania enkodéra
// static inline umožňuje kompilátoru vložiť kód priamo do ISR
static inline void handle_encoder(void) {
uint8_t pins = PIND; // Prečítame celý port jedenkrát
uint8_t current_state = 0;
// Formovanie 2-bitového čísla: bit1 = CLK, bit0 = DT
if (pins & (1 << CLK_PIN)) current_state |= 2;
if (pins & (1 << DT_PIN)) current_state |= 1;
// Výpočet indexu: (predošlý_stav << 2) | aktuálny_stav
uint8_t index = (last_state << 2) | current_state;
// Aktualizujeme pozíciu podľa tabuľky
encoder_pos += state_table[index & 0x0F];
// Uloženie stavu pre nasledujúci krok
last_state = current_state;
}
// Prerušenia na oboch kanáloch pre maximálnu presnosť
ISR(INT0_vect) {
handle_encoder();
}
ISR(INT1_vect) {
handle_encoder();
}
void init_all() {
uart_init();
stdout = &mystdout;
// Nastavenie pinov na vstup
clear_bit(DDRD, CLK_PIN);
clear_bit(DDRD, DT_PIN);
clear_bit(DDRD, SW_PIN);
// Zapnutie vnútorných prťahovacích odporov
set_bit(PORTD, CLK_PIN);
set_bit(PORTD, DT_PIN);
set_bit(PORTD, SW_PIN);
// Inicializácia predvoleného stavu PRED povolením prerušení
last_state = (PIND & (1 << CLK_PIN) ? 2 : 0) | (PIND & (1 << DT_PIN) ? 1 : 0);
// Nastavenie externých prerušení na akúkoľvek logickú zmenu
EICRA |= (1 << ISC00) | (1 << ISC10);
EIMSK |= (1 << INT0) | (1 << INT1);
sei();
}
int main(void) {
init_all();
int32_t last_printed = 0;
printf("\n--- Digital Potentiometer Ready ---\n");
while(1) {
// Atomické čítanie pozície (ochrana pred prerušením počas čítania 4 bajtov)
int32_t safe_pos;
cli();
safe_pos = encoder_pos;
sei();
// Spracovanie stlačenia tlačidla (reset to zero)
if (!(PIND & (1 << SW_PIN))) {
_delay_ms(10); // Softvérový debounce pre tlačidlo
if (!(PIND & (1 << SW_PIN))) {
cli();
encoder_pos = 0;
safe_pos = 0;
sei();
printf("[ RESET TO ZERO ]\n");
while(!(PIND & (1 << SW_PIN))); // Čakáme na uvoľnenie
}
}
// Výpis do konzoly iba pri zmene
if (safe_pos != last_printed) {
printf("Steps: %ld \n", safe_pos);
last_printed = safe_pos;
}
}
}
#define F_CPU 16000000UL
#define BAUD 9600
#include <util/setbaud.h>
#include "uart.h"
void uart_init(void) {
UBRR0H = UBRRH_VALUE;
UBRR0L = UBRRL_VALUE;
#if USE_2X
UCSR0A |= (1 << U2X0);
#else
UCSR0A &= ~(1 << U2X0);
#endif
UCSR0C = (1 << UCSZ01) | (1 << UCSZ00);
UCSR0B = (1 << RXEN0) | (1 << TXEN0);
}
int uart_putc(char c, FILE *stream) {
if (c == '\n') uart_putc('\r', stream);
loop_until_bit_is_set(UCSR0A, UDRE0);
UDR0 = c;
return 0;
}
int uart_getc(FILE *stream) {
loop_until_bit_is_set(UCSR0A, RXC0);
return UDR0;
}
void uart_puts(const char *s) {
while (*s) {
uart_putc(*s++, NULL);
}
}
#ifndef UART_H_
#define UART_H_
#include <avr/io.h>
#include <stdio.h>
void uart_init(void);
int uart_putc(char c, FILE *stream);
int uart_getc(FILE *stream);
void uart_puts(const char *s);
#endif
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
Na overenie funkčnosti digitálneho potenciometra som použil posielanie dát cez sériový terminál pomocou knižnice UART.
- Pri otáčaní v smere hodinových ručičiek hodnoty v termináli narastali a pri opačnom pohybe klesali bez akýchkoľvek náhodných skokov.
- Testovanie presnosti: Vykonal som test rýchleho otáčania hriadeľa v oboch smeroch. Systém sa pri návrate do východiskovej polohy vždy vrátil k 0(ak začínalo z 0).
- Overenie tlačidla: Krátkym stlačením hriadeľa (tlačidlo SW) som overil okamžité vynulovanie počítadla.

Video:
Čo by som urobil inak
Zamyslite sa spätne nad problémom, ktorý ste riešili a napíšte, čo sa vám nepodarilo a nabudúce by ste spravili inak.
Kľúčové slová 'Category', ktoré sú na konci stránky nemeňte.