Operácie

Generovanie kódu z prostredia Matlaba/Simulink

Zo stránky SensorWiki

Záverečný projekt predmetu MIPS / LS2025 - Meno Priezvisko


Zadanie

Na platforme Arduino Uno R3 (ATmega328P) implementovať diskrétny filter prvého rádu so spojitou prenosovou funkciou G(s)=K/(T*s+1), K=1[-], T=0,5 [s], so vzorkovacím časom Ts=0,01. Vstupom je jednorazový skok napätia z 0 V na 2,00 V (potenciometer na A0). Výstup má byť v celočíselnej aritmetike (integer), formátovaný ako text "x.yy\n" a odoslaný cez UART0 (115200 Bd). Grafické zobrazenie prechodovej charakteristiky sa vykoná v SerialPlot.

Vývojová doska ACROB.

Literatúra:


Analýza a opis riešenia

V tejto časti popíšeme, prečo a ako sme diskrétny filter navrhli, od spojitého modelu až po finálnu integer implementáciu.

1. Spojitý model

• Prenosová funkcia prvého rádu je G(s)=1/(0,5*s+1), čo znamená, že pri skoku vstupu sa výstup exponenciálne približuje k cieľovej hodnote s časovou konštantou T = 0,5 s.

2. Prečo digitálny filter

• Mikrokontrolér ATmega328P pracuje v diskrétnom čase a vyžaduje digitálne spracovanie signálu.

• ADC prevádza analógový signál na čísla, následný filter sa implementuje v kóde ako rozdielová rovnica.

3. Diskretizácia (ZOH)

• Pri vzorkovacom čase Ts = 0,01 [s]: a = exp(–Ts/T) = exp(–0,01/0,5) ≈ 0,98; b = 1 – a ≈ 0,02.

• Rovnica v diskrétnej podobe: y[n] = 0,98*y[n–1] + 0,02*u[n].

4. Integer aproximácia

• Pre rýchle a efektívne výpočty na AVR používame celočíselný zápis: K1 = 98, K2 = 2, deliteľ = 100.

• Riešenie: y[n] = (98*y[n–1] + 2*u[n]) / 100.

5. Mapovanie ADC signálu

• ADC hodnota 0–1023 zodpovedá 0–5 V.

• Pre zobrazenie v stotinách voltu: volt = (adc_val * 500) / 1023, výsledok 0–500.

6. Model v MATLAB/Simulink

Nižšie je zobrazená schéma, ktorú sme vytvorili v Simulinku na simuláciu diskrétneho filtra (vstup , výstup ):

Simulink zapojenie.

Vo vnútri bloku simulink sa nachádza MATLAB Function s implementáciou v integer aritmetike:

function y = simulink(u)

%#codegen

  persistent y_prev

  if isempty(y_prev)

    y_prev = int32(0);

  end

  % y[n] = (98*y_prev + 2*u) / 100

  y = (98*y_prev + 2*int32(u)) / 100;

  y_prev = y;

end

Tento model sme generátorom kódu premenili na C-knihovne simulink.c a simulink.h.

Algoritmus a program

1. Inicializácia periférií

• init_uart() (uart.c): Nastaví UART0 na 115200 Bd.

• init_adc() (p_f_1.c): ADC0, AVcc referencia, prescaler 128, povolené ADC prerušenie.

• init_timer2() (p_f_1.c): Timer2 CTC, prescaler 1024, OCR2A=156 → prerušenie každých 10 ms.

• sei() (main.c): povolenie globálnych prerušení.

• simulink_init() (simulink.c): vynulovanie vnútorného stavu filtra (y_prev=0)

2. Obsluha prerušení

• Timer2_COMPA_vect (p_f_1.c)

• ADC_vect (p_f_1.c)

3. Hlavná slučka (main.c)

Hlavná slučka beží v nekonečnej slučke while(1) a čaká na príznak new_sample, ktorý sa nastaví v ADC ISR


// simulink.c
#include "simulink.h"

static int32_t y_prev = 0;

// Reset vnútorného stavu filtra
void simulink_init(void) 
{
	y_prev = 0;
}

// Výpočet nového výstupu filtra
int32_t simulink_step(int32_t u) 
{
	y_prev = (98 * y_prev + 2 * u) / 100;
	return y_prev;
}
// simulink.h
#ifndef SIMULINK_H
#define SIMULINK_H

#include <stdint.h>

// Resetuje vnútorný stav filtra (y[n-1]=0)
void simulink_init(void);
// Vypočíta y[n] = (98·y[n-1] + 2·u) / 100
int32_t simulink_step(int32_t u);

#endif // SIMULINK_H
// p_f_1.c
#include "p_f_1.h"
#include <avr/io.h>
#include <avr/interrupt.h>

volatile uint16_t adc_val    = 0;
volatile uint8_t  new_sample = 0;

// Inicializácia ADC0 + povolenie ADC ISR
void init_adc(void) 
{
	ADMUX  = (1<<REFS0);                                // AVcc, ADC0
	ADCSRA = (1<<ADEN)|(1<<ADIE)|(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0);
	// EN, IE, prescaler=128 → ~125 kHz ADC clock
}

// Konfigurácia Timer2 CTC pre 10ms
void init_timer2(void) 
{
	TCCR2A = (1<<WGM21);                                // CTC režim
	TCCR2B = (1<<CS22)|(1<<CS21)|(1<<CS20);              // prescaler=1024
	OCR2A  = 156;                                       // 16 MHz/1024/156 ≈ 100 Hz
	TIMSK2 = (1<<OCIE2A);                               // Compare Match A interrupt
}

// ADC konverzia
ISR(TIMER2_COMPA_vect) 
{
	ADCSRA |= (1<<ADSC);                                // spustí ADC konverziu
}

//Uloženie adc_val a nastavenie new_sample
ISR(ADC_vect) 
{
	adc_val    = ADC;                                   // zachytí ADC výsledok
	new_sample = 1;                                     // príznak pre hlavný loop
}
// p_f_1.h
#ifndef P_F_1_H
#define P_F_1_H

#include <stdint.h>

// ISR sem uloží 10-bit ADC hodnotu (0..1023)
extern volatile uint16_t adc_val;
// ISR sem nastaví príznak novej vzorky každých ~10 ms
extern volatile uint8_t  new_sample;

// Inicializuje ADC0 (AVcc ref, prescaler=128, interrupt enable)
void init_adc(void);
// Inicializuje Timer2 CTC (prescaler=1024, OCR2A=156 → ~10 ms) + interrupt
void init_timer2(void);

#endif // P_F_1_H
// uart.c
#ifndef F_CPU
#define F_CPU 16000000UL
#endif
#ifndef BAUD
#define BAUD 115200UL
#endif

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

// Konfigurácia UART0
void init_uart(void) 
{
	// nakonfiguruje UBRR0 podľa makier a setbaud.h
	UBRR0H = UBRRH_VALUE;
	UBRR0L = UBRRL_VALUE;
	#if USE_2X
	UCSR0A |=  (1<<U2X0);
	#else
	UCSR0A &= ~(1<<U2X0);
	#endif

	// 8 dátových bitov, žiadna parita, 1 stop bit
	UCSR0C = (1<<UCSZ01)|(1<<UCSZ00);
	// povol TX
	UCSR0B = (1<<TXEN0);
}

// Posielanie C-reťazca cez UART0
void uart_puts(const char *s) 
{
	while (*s) {
		if (*s == '\n') {
			// vlož carriage return pred newline
			while (!(UCSR0A & (1<<UDRE0)));
			UDR0 = '\r';
		}
		while (!(UCSR0A & (1<<UDRE0)));
		UDR0 = *s++;
	}
}
// uart.h
#ifndef UART_H
#define UART_H

// Inicializuje UART0 na 8N1 s BAUD (máme ho definované v main.c)
void init_uart(void);
// Pošle C-string, vloží '\r' pred každé '\n'
void uart_puts(const char *s);

#endif // UART_H
// main.c

#ifndef F_CPU
#define F_CPU 16000000UL
#endif
#ifndef BAUD
#define BAUD 115200UL
#endif

#include <avr/io.h>
#include <avr/interrupt.h>
#include <stdio.h>            // pre snprintf

#include "p_f_1.h"            // init_adc, init_timer2, extern adc_val, new_sample
#include "uart.h"             // init_uart, uart_puts
#include "simulink.h"      // simulink_init, simulink_step

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

	// 1) Inicializácia periférií
	init_uart();    // UART0 na 115200
	init_adc();     // ADC0 s prerušeniami
	init_timer2();  // Timer2 CTC ~10 ms
	sei();          // povoli globálne prerušenia

	// 2) Reset stavu filtra
	simulink_init();

	// 3) Hlavná slučka
	while (1) 
	{
		if (new_sample) 
		{
			new_sample = 0; //Reset príznaku

			// Prevzorkovanie ADC 0..1023 → 0..500 (stotiny voltu)
			uint16_t volt = (adc_val * 500UL) / 1023;

			// Výpočet filtra 1. rádu
			int32_t filt = simulink_step((int32_t)volt);

			// Formátovanie výstupu na ASCII "x.yy\n" pre SerialPlot
			int whole = filt / 100;
			int frac  = filt % 100;
			snprintf(buf, sizeof(buf), "%d.%02d\n", whole, frac);

			uart_puts(buf); // Odoslanie cez UART
		}
	}
	return 0;
}


Zdrojový kód: zdrojaky.zip

Zapojenie

Pripojenie potenciometra (B10K) na Arduino UNO kvôli možnosti plynulej zmeny vstupného napätia v rozsahu 0-5V.

Schéma zapojenia.

Overenie

1. Arduino IDE Serial Monitor (115200 Bd): overené prijímanie hodnôt "0.00", "2.00" po skoku.

2. SerialPlot (ASCII, prázdny delimiter, 100 Hz): zobrazená exponenciálna prechodová charakteristika z 0 V na 2,00 V.

• Inicializácia: Načítali sme spúšťací skok do 2,00 V.

• Spustenie: Otáčaním potenciometra z 0 V na 2,00 V sme generovali jednorazový skok.

• Záznam dát: SerialPlot zaznamenával postupnosť prijímaných hodnôt, Arduino do PC posielalo reťazce „x.yy “ každých 10 ms.

• Opakované merania: Krátke kroky sme zopakovali viackrát, aby sme overili opakovateľnosť.


Overenie funkčnosti.

Video:



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