Operácie

Ovládanie vyklápacích svetiel

Zo stránky SensorWiki

Záverečný projekt predmetu MIPS / LS2025 - Martin Lenarth


Zadanie

Cieľom bolo navrhnúť mikropočítačový systém, ktorý reaguje na stlačenie tlačidla a umožňuje riadiť pohyb vyklápacích svetiel na aute. Svetlá sa ovládajú pomocou motorčekov (dva smery pohybu), a to buď:

  • pomocou jednoduchého alebo dvojitého zatlačenia tlačidla, alebo
  • pomocou príkazov cez UART (sériová linka) z počítača
Arduino NANO

Literatúra:


Analýza a opis riešenia

Hlavnou úlohou systému je riadiť pohyb výklopných svetiel nezávisle od seba. Každé svetlo je ovládané dvoma výstupmi – jeden pre pohyb hore a druhý pre pohyb dole. Týmto spôsobom je možné svetlá zdvíhať, spúšťať a vykonávať rôzne blikacie sekvencie. Svetlá je možné ovládať dvoma spôsobmi – mechanickým tlačidlom alebo príkazmi cez UART.

Použite komponenty:

  • Tlačidlo v aute
  • Motorčeky na vyklápanie svetiel
  • 4-Kanálová relé doska s optočlenom
Relé doska

Na detekciu klikov a časovanie pohybov sa využíva časovač TIMER0 v režime CTC, ktorý generuje prerušovanie každú 1 ms. Tým vzniká softvérový časovač pomocou premennej milliseconds.

Program rozlišuje:

  • Jedno kliknutie – slúži na zapnutie alebo vypnutie svetiel (pohyb hore alebo dole).
  • Dvojklik – spúšťa animovanú sekvenciu bliknutia svetiel.
  • UART príkazy – umožňujú spustiť rôzne preddefinované sekvencie

Funkcie pre ovládanie svetiel sú implementované v súbore sequences.c. Tieto funkcie priamo nastavujú piny výstupov podľa požadovaného smeru pohybu a obsahujú časové oneskorenia, ktoré simulujú fyzický pohyb svetiel.

Schéma zapojenia:

Schéma zapojenia.


Algoritmus a program

Program začína inicializáciou periférií:

  • Nastavenie vstupného pinu pre tlačidlo s pull-up rezistorom.
  • Nastavenie výstupných pinov pre ovládanie svetiel.
  • Spustenie UART a časovača TIMER0.
  • Povolenie globálnych prerušení.

Hlavná slučka vykonáva:

  1. Sleduje stav tlačidla, používa debounce a detekciu zmeny stavu.
  2. Rozlišuje jedno a dvojité kliknutie podľa časového odstupu medzi klikmi.
  3. Ovláda svetlá – pri jednom kliknutí sa svetlá zapnú/vypnú, pri dvojkliku sa spustí blikacia sekvencia.
  4. Spracováva znaky z UART – ak bol prijatý znak, vykoná sa príslušná funkcia

Projekt taktiež využíva aplikáciu Processing na vytvorenie dialógového okna a spomínanú sériovú komunikáciu cez UART

#define F_CPU 16000000UL
#define BAUD 9600

#include <avr/io.h>
#include "uart.h"
#include "sequences.h"
#include <util/delay.h>
#include <avr/interrupt.h>
#include <stdio.h>

// Pin Definitions
#define SWITCH  PD2
#define L_UP    PB3
#define R_UP    PD5
#define L_DOWN  PB4
#define R_DOWN  PD6

// Timing Constants
#define BUTTON_TIME  500   // button wait time for double click
#define DEBOUNCE_DELAY 50  // debounce duration

// Headlight State
HeadlightStatus headlights = {0, 0};

// Toggle Pattern Tracking
int lastSwitchState = 1;
int firstToggleState = 1;
volatile uint32_t firstToggleTime = 0;
volatile uint8_t toggleSequenceActive = 0;
volatile uint8_t handledSequence = 0;

// Millisecond Counter
volatile uint32_t milliseconds = 0;

// Timer Setup
void setup_timer0() {
	TCCR0A |= (1 << WGM01);            // CTC
	OCR0A = 249;                       // 1ms
	TIMSK0 |= (1 << OCIE0A);          // Enable interrupt
	TCCR0B |= (1 << CS01) | (1 << CS00); // Prescaler 64
}

ISR(TIMER0_COMPA_vect) {
	milliseconds++;
}

unsigned long millis() {
	unsigned long ms;
	uint8_t oldSREG = SREG;
	cli();
	ms = milliseconds;
	SREG = oldSREG;
	return ms;
}

// Debounce
uint8_t debounceSwitch(uint8_t pin) {
	uint8_t stableState = (PIND & (1 << pin)) ? 1 : 0;
	_delay_ms(DEBOUNCE_DELAY);
	uint8_t newState = (PIND & (1 << pin)) ? 1 : 0;
	return (stableState == newState) ? newState : stableState;
}

// Main Loop
int main(void) {
	// Switch input
	DDRD &= ~(1 << SWITCH);
	PORTD |= (1 << SWITCH); // Pull-up

	// Outputs
	DDRB |= (1 << L_UP) | (1 << L_DOWN);
	DDRD |= (1 << R_UP) | (1 << R_DOWN);

	uart_init();
	setup_timer0();
	sei();
	stop();

	while (1) {
		int current = debounceSwitch(SWITCH);
		unsigned long now = millis();

		if (current != lastSwitchState) {
			if (!toggleSequenceActive) {
				toggleSequenceActive = 1;
				handledSequence = 0;
				firstToggleState = lastSwitchState;
				firstToggleTime = now;
				} else if ((now - firstToggleTime) <= (BUTTON_TIME - DEBOUNCE_DELAY) && current == firstToggleState) {
				if (firstToggleState == 1) {
					AlternateWink();
					} else {
					Wink();
				}
				handledSequence = 1;
				toggleSequenceActive = 0;
			}
			lastSwitchState = current;
		}

		if (toggleSequenceActive && !handledSequence && (now - firstToggleTime) > (BUTTON_TIME - DEBOUNCE_DELAY)) {
			toggleSequenceActive = 0;
			if (lastSwitchState == 0 && !areHeadlightsUp()) {
				raiseHeadlights();
				uart_puts("ON\n");
				} else if (lastSwitchState == 1 && areHeadlightsUp()) {
				lowerHeadlights();
				uart_puts("OFF\n");
			}
		}

		if (!toggleSequenceActive && !handledSequence) {
			if (current == 0 && !areHeadlightsUp()) {
				raiseHeadlights();
				uart_puts("ON\n");
				} else if (current == 1 && areHeadlightsUp()) {
				lowerHeadlights();
				uart_puts("OFF\n");
			}
		}

		if (UCSR0A & (1 << RXC0)) {
			char command = uart_getc();

			switch (command) {
				case 'M':
				if (areHeadlightsUp()) MexicanWink();
				else AlternateMexicanWink();
				break;

				case 'W':
				if (areHeadlightsUp()) Wink();
				else AlternateWink();
				break;

				case 'L':
				WinkLeft();
				break;

				case 'R':
				WinkRight();
				break;
			}
		}
	}
}
#ifndef SEQUENCES_H
#define SEQUENCES_H

#include <stdint.h>

typedef struct {
	int leftUp  : 1;
	int rightUp : 1;
} HeadlightStatus;

void moveHeadlight(uint8_t side, uint8_t direction);
void raiseHeadlights(void);
void lowerHeadlights(void);
uint8_t areHeadlightsUp(void);

void Wink(void);
void AlternateWink(void);
void MexicanWink(void);
void AlternateMexicanWink(void);
void WinkLeft(void);
void WinkRight(void);

void stop(void);

#endif
#define F_CPU 16000000UL
#define BAUD 9600

#include <avr/io.h>
#include <util/delay.h>
#include "sequences.h"

#define UP 1
#define DOWN 0
#define LEFT 1
#define RIGHT 0

#define L_UP     PB3
#define R_UP     PD5
#define L_DOWN   PB4
#define R_DOWN   PD6

#define MOVE_TIME    750
#define SEQUENCE_TIME   150

extern HeadlightStatus headlights;

static void set_pin(volatile uint8_t *port, uint8_t pin, uint8_t high) {
	if (high)
	*port |= (1 << pin);
	else
	*port &= ~(1 << pin);
}

void delay(int delay)
{
	for (int i=1; i<=delay; i++)
	_delay_ms(1);
}

void stop() {
	set_pin(&PORTB, L_UP, 1);
	set_pin(&PORTD, R_UP, 1);
	set_pin(&PORTB, L_DOWN, 1);
	set_pin(&PORTD, R_DOWN, 1);
}

static void stopLeft() {
	set_pin(&PORTB, L_UP, 1);
	set_pin(&PORTB, L_DOWN, 1);
}

static void stopRight() {
	set_pin(&PORTD, R_UP, 1);
	set_pin(&PORTD, R_DOWN, 1);
}

void moveHeadlight(uint8_t side, uint8_t direction) {
	if (side == LEFT) {
		set_pin(&PORTB, L_UP, !direction);
		set_pin(&PORTB, L_DOWN, direction);
		headlights.leftUp = direction;
		} else {
		set_pin(&PORTD, R_UP, !direction);
		set_pin(&PORTD, R_DOWN, direction);
		headlights.rightUp = direction;
	}
}

void raiseHeadlights() {
	if (!headlights.leftUp || !headlights.rightUp) {
		if (!headlights.leftUp) moveHeadlight(LEFT, UP);
		if (!headlights.rightUp) moveHeadlight(RIGHT, UP);
		delay(MOVE_TIME);
		stop();
		headlights.leftUp = UP;
		headlights.rightUp = UP;
	}
}

void lowerHeadlights() {
	if (headlights.leftUp || headlights.rightUp) {
		if (headlights.leftUp) moveHeadlight(LEFT, DOWN);
		if (headlights.rightUp) moveHeadlight(RIGHT, DOWN);
		delay(MOVE_TIME);
		stop();
		headlights.leftUp = DOWN;
		headlights.rightUp = DOWN;
	}
}

uint8_t areHeadlightsUp() {
	return headlights.leftUp && headlights.rightUp;
}

void Wink() {
	moveHeadlight(LEFT, DOWN);
	delay(MOVE_TIME);
	stop();

	moveHeadlight(LEFT, UP);
	moveHeadlight(RIGHT, DOWN);
	delay(MOVE_TIME);
	stop();

	moveHeadlight(LEFT, DOWN);
	moveHeadlight(RIGHT, UP);
	delay(MOVE_TIME);
	stop();

	moveHeadlight(LEFT, UP);
	moveHeadlight(RIGHT, DOWN);
	delay(MOVE_TIME);
	stop();

	moveHeadlight(RIGHT, UP);
	delay(MOVE_TIME);
	stop();

	headlights.leftUp = UP;
	headlights.rightUp = UP;
}

void AlternateWink() {
	moveHeadlight(LEFT, UP);
	delay(MOVE_TIME);
	stop();

	moveHeadlight(LEFT, DOWN);
	moveHeadlight(RIGHT, UP);
	delay(MOVE_TIME);
	stop();

	moveHeadlight(LEFT, UP);
	moveHeadlight(RIGHT, DOWN);
	delay(MOVE_TIME);
	stop();

	moveHeadlight(LEFT, DOWN);
	moveHeadlight(RIGHT, UP);
	delay(MOVE_TIME);
	stop();

	moveHeadlight(RIGHT, DOWN);
	delay(MOVE_TIME);
	stop();

	headlights.leftUp = DOWN;
	headlights.rightUp = DOWN;
}

void MexicanWink() {
	for (int i = 0; i < 2; i++) {
		moveHeadlight(LEFT, DOWN);
		delay(SEQUENCE_TIME);
		moveHeadlight(RIGHT, DOWN);
		delay(MOVE_TIME - SEQUENCE_TIME);
		stopLeft();
		delay(SEQUENCE_TIME);
		stopRight();

		moveHeadlight(LEFT, UP);
		delay(SEQUENCE_TIME);
		moveHeadlight(RIGHT, UP);
		delay(MOVE_TIME - SEQUENCE_TIME);
		stopLeft();
		delay (SEQUENCE_TIME);
		stopRight();
	}

	headlights.leftUp = UP;
	headlights.rightUp = UP;
}

void AlternateMexicanWink() {
	for (int i = 0; i < 2; i++) {
		moveHeadlight(LEFT, UP);
		delay(SEQUENCE_TIME);
		moveHeadlight(RIGHT, UP);
		delay(MOVE_TIME - SEQUENCE_TIME);
		stopLeft();
		delay(SEQUENCE_TIME);
		stopRight();

		moveHeadlight(LEFT, DOWN);
		delay(SEQUENCE_TIME);
		moveHeadlight(RIGHT, DOWN);
		delay(MOVE_TIME - SEQUENCE_TIME);
		stopLeft();
		delay(SEQUENCE_TIME);
		stopRight();
	}

	headlights.leftUp = DOWN;
	headlights.rightUp = DOWN;
}

void WinkLeft() {
	if (headlights.leftUp) {
		moveHeadlight(LEFT, DOWN);
		delay(MOVE_TIME);
		stopLeft();
		moveHeadlight(LEFT, UP);
	} else {
		moveHeadlight(LEFT, UP);
		delay(MOVE_TIME);
		stopLeft();
		moveHeadlight(LEFT, DOWN);
	}
	delay(MOVE_TIME);
	stopLeft();
}

void WinkRight() {
	if (headlights.rightUp) {
		moveHeadlight(RIGHT, DOWN);
		delay(MOVE_TIME);
		stopRight();
		moveHeadlight(RIGHT, UP);
		} else {
		moveHeadlight(RIGHT, UP);
		delay(MOVE_TIME);
		stopRight();
		moveHeadlight(RIGHT, DOWN);
	}
	delay(MOVE_TIME);
	stopRight();
}
#ifndef UART_H_
#define UART_H_

void uart_init( void );

void uart_putc( char c );
void uart_puts( const char *s );

char uart_getc( void );

#endif /* UART_H_ */
#define F_CPU 16000000UL
#define BAUD 9600

#include <avr/io.h>
#include <util/setbaud.h>

void uart_init( void )
{
	UBRR0H = UBRRH_VALUE;
	UBRR0L = UBRRL_VALUE;

	#if USE_2X
	UCSR0A |= _BV(U2X0);
	#else
	UCSR0A &= ~(_BV(U2X0));
	#endif

	UCSR0C = _BV(UCSZ01) | _BV(UCSZ00); /* 8-bit data */
	UCSR0B = _BV(RXEN0) | _BV(TXEN0);   /* Enable RX and TX */
}


void uart_putc(char c)
{
	if (c == '\n')
	{
		uart_putc('\r');
	}
	loop_until_bit_is_set(UCSR0A, UDRE0); /* Wait until data register empty. */
	UDR0 = c;
}


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

char uart_getc(void) {
	loop_until_bit_is_set(UCSR0A, RXC0); /* Wait until data exists. */
	return UDR0;
}
import processing.serial.*;
import g4p_controls.*;

Serial myPort;

GLabel lblStatus;
GButton buttonMexican, buttonWink, buttonLeft, buttonRight;
boolean ledState = false;

void setup() {
  size(300, 250);
  createGUI();
  
  println(Serial.list());
  myPort = new Serial(this, Serial.list()[0], 9600);
  myPort.clear();
}

void draw() {
  background(230);
}

void serialEvent(Serial p) {
  String incoming = p.readStringUntil('\n');
  if (incoming != null) {
    incoming = trim(incoming);
    println(incoming);

    if (incoming.equals("ON")) {
      lblStatus.setText("Lights are up");
    } else if (incoming.equals("OFF")) {
      lblStatus.setText("Lights are down");
    }
  }
}

public void buttonMexican_click(GButton source, GEvent event) {
  if (myPort != null) {
    myPort.write('M');
  }
} 

public void buttonWink_click(GButton source, GEvent event) { 
  if (myPort != null)
    myPort.write('W');
} 

public void buttonLeft_click(GButton source, GEvent event) { 
  if (myPort != null)
    myPort.write('L');
} 

public void buttonRight_click(GButton source, GEvent event) { 
  if (myPort != null)
    myPort.write('R');
} 

public void createGUI() {
  G4P.messagesEnabled(false);
  G4P.setGlobalColorScheme(GCScheme.BLUE_SCHEME);
  G4P.setMouseOverEnabled(true);
  G4P.setDisplayFont("Arial Black", G4P.PLAIN, 14);
  surface.setTitle("Headlight Control");

  buttonMexican = new GButton(this, 20, 40, 120, 40);
  buttonMexican.setText("Mexican Wink");
  buttonMexican.setLocalColorScheme(GCScheme.BLUE_SCHEME);
  buttonMexican.addEventHandler(this, "buttonMexican_click");

  buttonWink = new GButton(this, 160, 40, 120, 40);
  buttonWink.setText("Wink");
  buttonWink.setLocalColorScheme(GCScheme.BLUE_SCHEME);
  buttonWink.addEventHandler(this, "buttonWink_click");

  buttonLeft = new GButton(this, 20, 120, 120, 40);
  buttonLeft.setText("Wink Left");
  buttonLeft.setLocalColorScheme(GCScheme.BLUE_SCHEME);
  buttonLeft.addEventHandler(this, "buttonLeft_click");
  
  buttonRight = new GButton(this, 160, 120, 120, 40);
  buttonRight.setText("Wink Right");
  buttonRight.setLocalColorScheme(GCScheme.BLUE_SCHEME);
  buttonRight.addEventHandler(this, "buttonRight_click");
  
  lblStatus = new GLabel(this, 0, 190, 300, 30);
  lblStatus.setText("Ready");
  lblStatus.setTextAlign(GAlign.CENTER, null);
}

Zdrojový kód: zdrojaky.zip

Overenie

Ako ste overili funkciu, napríklad... Na používanie našej aplikácie stačia dve tlačítka a postup používania je opísaný v sekcii popis riešenia. Na konci uvádzame fotku hotového zariadenia.

Aplikácia.

Video:



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