Jump to content

SUBIECTE NOI
« 1 / 5 »
RSS
Presbiopia - la 43 ani ?

Termen transcriere autovehicul

Cazare Timisoara pe 4-5 zile

Primele zile ale internetului per...
 Ditra 25

Casti USB-C ptr A-54

Aplicatie medicala / asistent med...

De ce vor atația politicieni...
 ERR_ADDRESS_UNREACHABLE

Legea 18/1968 Se mai aplica?

Digi conectare 2 routere prin fir

Succesiune notar versus instanta ...
 Montaj aer conditionat in balcon ...

Cont curent mulți valuta far...

Sugestii plan casa

Experiente cu firme care cumpara ...
 

Arduino - automatizare DIY

* * * * * 2 votes
  • Please log in to reply
1573 replies to this topic

#1495
Iocan

Iocan

    Tefelist, fan Kövesi

  • Grup: Senior Members
  • Posts: 5,782
  • Înscris: 02.06.2006

View Postgmartau, on 06 mai 2019 - 10:03, said:

Procesorul nu se blocheaza niciodata, el executa instructiuni din program chiar daca aparent pare sa fie blocat, de exemplu, executa o bucla infinita. Daca sunt paraziti in sursa de alimentare procesorul ori ramane neafectat ori se reseteaza.
Dacă e așa cum spui tu, de ce majoritatea cipurilor Atmel au implementat mecanismul de hardware watchdog wdt_enable()?

#1496
gmartau

gmartau

    Member

  • Grup: Members
  • Posts: 639
  • Înscris: 30.04.2008

View PostIocan, on 06 mai 2019 - 11:23, said:

Dacã e așa cum spui tu, de ce majoritatea cipurilor Atmel au implementat mecanismul de hardware watchdog wdt_enable()?
In primul rand trebuie sa ai o sursa de alimentare stabila. Nu are sens sa discutam de surse de tensiune cu probleme. Daca nu mai ai blocaje atunci cauza problemei e rezolvata.

Mecanismul watchdog se poate folosi intr-un mod mai inteligent decat simpla resetare a microcontrolerului. In tutorialul de mai jos, cu ajutorul unei biblioteci care foloseste mecanismul watchdog, dupa fiecare blocaj se salveaza in memoria eeprom locatia unde se executa codul in momentul respectiv. Te ajuta sa descoperi exact unde e problema. Pot confirma ca libraria este foarte utila, am descoperit un bug altfel aproape imposibil de descoperit prin alte metode.
https://www.megunoli...duino-watchdog/

#1497
dexterash

dexterash

    --something---

  • Grup: Senior Members
  • Posts: 22,912
  • Înscris: 19.08.2004

View PostIocan, on 06 mai 2019 - 11:23, said:

Dacã e așa cum spui tu, de ce majoritatea cipurilor Atmel au implementat mecanismul de hardware watchdog wdt_enable()?
Știi la ce se referã si cum se foloseste, in general, un watchdog? Așteaptã o "comanda"(registru schimbat) de la software. Așa cã... tot la erori de software ajungem.

#1498
Iocan

Iocan

    Tefelist, fan Kövesi

  • Grup: Senior Members
  • Posts: 5,782
  • Înscris: 02.06.2006

View Postgmartau, on 06 mai 2019 - 16:22, said:

In primul rand trebuie sa ai o sursa de alimentare stabila.
Stabilă e una și imună la paraziți și impulsuri e alta.

View Postdexterash, on 06 mai 2019 - 17:28, said:

Știi la ce se referă si cum se foloseste, in general, un watchdog? Așteaptă o "comanda"(registru schimbat) de la software. Așa că... tot la erori de software ajungem.
Iar dacă controlerul este lăsat într-o stare impredictibilă după un impuls parazit, aia o fi tot eroare software?

Bun, acum văd că vrem doar să arătăm că sîntem mai competenți ca ălălalt. Mersi, nu mă interesează acest concurs.

Edited by Iocan, 06 May 2019 - 20:04.


#1499
10nutz_413x

10nutz_413x

    Member

  • Grup: Members
  • Posts: 460
  • Înscris: 01.02.2007
Salutare,
Sunt novice in ale Arduino, am achizitionat un kit de aprox 150lei cu destul de multe componente si am incercat sa fac cate ceva..
Primul lucru ce l am facut a fost sa testez cateva din componente cu un programel simplu de "Hello World" pe un lcd display. A functionat.
Mai jos schema dupa care m am luat, de pe net, si poza cu standul:
Attached File  11.png   208.75K   29 downloads
Attached File  1.jpeg   253.78K   39 downloads

Am vrut sa merg mai departe si sa incerc un senzor de culoare TCS3200 (cumparat separat, nu a venit in kit)
Aici intampin probleme, un motiv e ca schema gasita pe internet nu e chiar asa de usor de citit ca prima Posted Image (nu mai e mura-n gura) si desi consider ca am citit o corect nu merge 100%.
Displayul este foarte slab, deabia porneste, si nici din potentiometru nu poti da decat oprit de tot sau foarte foarte slab...de nici nu se poate citi.
Eu am considerat ca acolo este tot potentiometru in schema(unde am pus sageata rosie pe schema, dar nu sunt sigur daca am inteles bine schema).
Aveti vreo idee de ce nu primeste displayul suficienta energie si cum as putea remedia?
Attached File  22.jpg   192.35K   41 downloads
Attached File  2.jpeg   219.98K   35 downloads

Multumesc mult si scuze daca e banala postarea, nu am gasit ceva mai potrivit ptr incepatori.

#1500
gmartau

gmartau

    Member

  • Grup: Members
  • Posts: 639
  • Înscris: 30.04.2008
Din poza se pare ca ai conectat Vss (minus/gnd) de la ecran la linia rosie (5V) si Vdd(plus) la linia albastra. Daca e asa, corect e invers. Pune firul negru la linia albastra si cel rosu la linia rosie.
Edit: Acum vad ca ai alimentat si breadboardul gresit de aia ti-a mers. Deci refa conexiunile si la placa si respecta si indicatiile de pe breadboard ca altfel te incurci. Observ ca deja folosesti firele cu culori cat de cat in mod intuitiv.
Revii cu o poza de sus, nu lateral.

Edited by gmartau, 17 May 2019 - 08:40.


#1501
radurus

radurus

    Senior Member

  • Grup: Senior Members
  • Posts: 8,023
  • Înscris: 16.06.2006

View Post10nutz_413x, on 16 mai 2019 - 19:19, said:

...
De preferat, daca ai gasit un setup/schema pentru display care functioneaza, incerci sa refolosesti partea aceea cu cod cu tot.
Asta inseamna sa poti citi scheciul si sa identifici partile din cod si care e scopul lor.

Stiu ca varianta replicarii schemei si a incarcarii scheciului facut de altcineva (tutorial, instructables) e cea mai simpla si e buna pentru inceput.
Trebuie insa sa intelegi cum functioneaza si cum poti dezvolta schema si codul acela.

Eu am inceput progresiv cu display pe "paralel" ca si tine. Ei bine, am inceput cu blink. :)

Am citit, am experimentat diferite exemple si mi-am dat seama cum se citeste un port analogic si cum se poate afisa pe ecran la cordonatele dorite valoarea.

Mi-am dat seama ca ecranul pe paralel imi tinea pinii digitali ocupati si dupa cautari si incercari am mutat afisajul pe comunicatie i2c astfel ca am pastrat bucata de cod si schema pentru afisare pe display cu i2c.

Apoi am vrut sa citesc temperatura pe un senzor digital (onewire) si sa o pun pe ecran.

Am vrut sa stiu cum se citeste un pin digital (buton) si cum se modifica iesirea unui pin digital (releu, led).

Apoi am adaugat mai multi senzori de temperatura (10), inca 3 relee.

Important este sa intelegi bazele (pentru ce e folosita o comanda/variabila, cum se foloseste).
Mai tarziu vine si optimizarea codului. (Se poate obtine acelasi rezultat sau mai bun/rapid cu mai putine linii de cod si comenzi).

Apoi doar imaginatia te mai opreste sa faci ceea ce ti-ai propus.

Inca un sfat.
Cand cumperi senzori, te uiti mai intai daca a facut cineva un tutorial pentru el, exista "librarii" pentru senzor, sunt destule persoane care au confirmat ca functioneaza.

Edited by radurus, 17 May 2019 - 10:29.


#1502
10nutz_413x

10nutz_413x

    Member

  • Grup: Members
  • Posts: 460
  • Înscris: 01.02.2007
Salut, am revenit cu ceva poze.

Am modificat firele, inteleg la ce te ai referit gmartau.
radurus asa este, poate ai dreptate, poate m am aruncat cu capul inainte e adevarat..oricum voi incerca si alte lucruri mai simple si mai ales sa incerc eu sa programez, fara sa iau copy-paste, dar tentatia e mare cu atata material pe internet Posted Image

Am umblat la potentiometru si am reusit sa fac sa se vada, din anumite unghiuri, foarte foarte slab, si ce este pe ecran.
Trebuie umblat la cod, asta e clar, ca da rateuri la corelarea valorilor RGB cu culorile proprizise (cum se vede din poze, dar aia e ok, e modificabil)
Am auzit o varianta ca poate nu e destul de la usb ce vine si ar trebui sa incerc cu o baterie de 9V sa alimentez circuitul. (nu am incercat inca)
Deasemenea am inteles ca poate e din cod setata luminozitatea la un maxim, nu am reusit sa descifrez asa ceva din cod. (totusi nu cred varianta asta)

Am pus si poza de sus, poate e mai clar asa.
Mersi de pareri si sfaturi Posted Image

Attached File  333.jpeg   238.73K   19 downloadsAttached File  111.jpeg   212.42K   21 downloadsAttached File  222.jpeg   238.26K   25 downloads

Later Edit:
Am rezolvat problema. Chiar dupa ce am scris postul initial m am gandit ca nu are rost sa nu mearga, ceva lipseste, era ca si cum nu era alimentat displayul corespunzator.
Am analizat schita precenta, de la Hello World, ce avea in plus fata de cea de fata si am adaugat ultimele 2 fire, de la Ground la pinul K si cu un rezistor de la 5V la pinul A al displayului (schema din poza mai jos, zona incercuita cu rosu) si functioneaza perfect.
Imi mai ramane problema depistarii corecte a culorii, problema de software.
Attached File  555.png   202.18K   39 downloads
Attached File  444.jpeg   172.26K   42 downloads

Edited by 10nutz_413x, 18 May 2019 - 17:30.


#1503
radurus

radurus

    Senior Member

  • Grup: Senior Members
  • Posts: 8,023
  • Înscris: 16.06.2006
Offtopic.

Am aruncat o privire pe discutiile de pe thread.
...era candva vorba despre automatizari centrale termice cu arduino.

Cand am ajuns pe ramura asta (2015), pentru partea asta am ajuns.
Mi-am facut automatizarea si am tot perfectionat la ea si am invatat multe.

Anul acesta am montat si o CT in condensatie.
In versiunea actuala a trebuit sa integrez sistemele (lemne si gaz).
Nu sunt inca dispus sa renunt de tot la incalzirea cu lemne.

Deoarece experienta/confortul e mai bun la utilizarea cu boiler ACM versus ACM instant, am gandit o solutie prin care sa fac ACM cu CT dar sa o folosesc din boiler.

Asa ca automatizarea s-a extins de la 4 relee (4 pompe) la 6 relee (o pompa si un circuit de trimitere contact TA catre noua centrala.

Pompa adaugata trage din baza boilerului si impinge in CT pe apa rece, apoi apa la 60 ajunge sus in boiler (adica am facut un boiler cu acumulare fara a folosi serpentina)
Am adaugat contoare de timp pentru astea 2 optiuni, astfel ca pot vedea cat timp a functionat CT pe incalzire si pe ACM.
Am inplementat o optiune mod vacanta dente ACM a.i. sa pot dezactiva functia de incarcare a boilerului.


#1504
ramadoss

ramadoss

    Member

  • Grup: Members
  • Posts: 599
  • Înscris: 24.07.2012
Poti dezvolta un pic partea cu contoarele de timp ? Ce contor, de unde iei semnalul, etc...

#1505
radurus

radurus

    Senior Member

  • Grup: Senior Members
  • Posts: 8,023
  • Înscris: 16.06.2006
Sorry ca incep mai din spate...
Codul e structurat pe "module".
In loop sunt timere cu millis pentru rutine care ruleaza la 25, 100, 1000, 4000, 30000 etc. milisecunde. (citire temperaturi, diferite calcule,...)

Pentru fiecare releu am cate o variabila uint8t care ia valori 0 sau 1.
(Stiu ca se poate face la nivel de bit si cu bitmasking si e f. utila varianta cand trimiti pe i2c spre un expander i/o, dar asa cum facut e mai usor de urmarit variabila in cod decat sa urmaresti un bit dintr-un byte)

Pentru a atribui valori la variabilele astea, am facut o clasa decisionsPumpState()
La fiecare 100ms e apelata clasa asta.
In ea sunt o groaza de if-uri facute dupa scheme logice.
La terminarea rularii clasei, toate valiabilele in legatura cu releele plus altele (histerezisuri, limite de temperatura, etc.) au valori care sunt folosite in urmatoarea clasa setOutputRelays() apelata.

Atata timp cat un releu e activ, se incrementeaza o variabila cu diferenta de timp de la rularea anterioara.
Cand releul e inactiv, variabila millis anterioara e doar actualizata cu millis.

In alta clasa contoareDeTimp(), variabila asta care se tot incrementeaza, cand trece de 60000, se scad 60000 si se incrementeaza o variabila 8bit care reprezinta minutele... etc. la fel pentru variabila orelor 16bit.
La fiecare incrementare a orelor/ minutelor este scrisa variabila in Eeprom.

La pornirea automatizarii, se citesc variabilele din Eeprom. Fractiunile de minut se pierd dar minutele si orele se incarca si se reia contorul de umde a ramas

O sa pun un fragment de cod mai incolo ca e mai usor de inteles.

Edited by radurus, 10 October 2019 - 18:45.


#1506
radurus

radurus

    Senior Member

  • Grup: Senior Members
  • Posts: 8,023
  • Înscris: 16.06.2006
Revin cu fragmentul de cod promis.
Am sters ce nu e relevant si am inlocuit cu [....] si comemtarii.
Codul ar trebui sa fie functional (binenteles cu bibliotecile corespunzatoare).

Stripped_code.ino

/*-----( Import needed libraries )-----*/
#include <EEPROM.h>
#include <OneWire.h>			 // Get 1-wire Library here: http://www.pjrc.com/teensy/td_libs_OneWire.html
#include <DallasTemperature.h>   //Get DallasTemperature Library here:  http://milesburton.com/Main_Page?title=Dallas_Temperature_Control_Library
#include <Wire.h>				// Wire (I2C) Library
#include <LCD.h>				 // LCD Library
#include <LiquidCrystal_I2C.h>   // F Malpartida's NewLiquidCrystal library
//Download: https://bitbucket.org/fmalpartida/new-liquidcrystal/downloads
char timp01_hmm[12]; //
#define ONE_WIRE_BUS 4		   // onewire data e conectat la pin 4
#define I2C_ADDR	0x27		 // adresa modulului I2CtoLCD PCF8574T este 0x27.
//asignare pini/semnale pentru PCF8574T
#define BACKLIGHT_PIN  3
#define En_pin  2
#define Rw_pin  1
#define Rs_pin  0
#define D4_pin  4
#define D5_pin  5
#define D6_pin  6
#define D7_pin  7
#define  LED_OFF  0
#define  LED_ON  1
// initializare pini iesire/intrare
[....]
const int termostat_ambianta = 3; // initializare pin 3 intrare termostat ambianta
const int pompa_CT_boiler = 7; // pinul 7 va comanda pompa de recirculare boiler-CT condensatie pentru reumplere cu ACM, Releu_2.2 (Rel5 in schema fritzing)
[....]
uint8_t staretermostatambianta = 0;
uint8_t mod_ACM_vacanta = 0;			 // variabila folosita pentru dezactiva pompa de recirculare ACM prin CT in condensatie modificabila in 0/1 intr-um meniu cu ajutorul butoanelor
// declarare variabile pentru contoare de timp
[....]
unsigned long start_counter00 = 0; // contorul 00 e folosit pentru bucla de citire temperaturi, inputuri 100ms ciclu
unsigned long stop_counter00;
unsigned long start_counter04 = 0; // contorul 04 e folosit la actualizarea informatiilor dinamice pe LCD o data la 1000ms, nivel pufer, energie, procent, autonomie...
unsigned long stop_counter04;
//initializare variabile temperaturi
[....]
float temp_bacm = 0;
float temp_bacmjos = 0; // adaugate pentru comanda pompei de recirculare boiler-CT
// initializare limite temperaturi pentru bucla de decizii
[....]
uint8_t temp_startpompaboilerCT = 42; // adaugat pentru rutina pompei de incarce boiler de la CT, senzor mijloc
uint8_t temp_stopompaboilerCT = 52;  // adaugat pentru rutina pompei de incarce boiler de la CT, senzor jos
// initializare variabile contoare pompe
[....]
//---------------------------- pompa de recirculare boiler prin CT
unsigned int contor_pompa_boilerCT;
unsigned long millis_p_boCT_acum = 0; // contorul dintre pornire si oprire
unsigned long millis_p_boCT_anterior = 0; // contorul dintre pornire si oprire
unsigned int tmp_contor_pompa_boilerCT = 0; // contor de 60000ms
uint8_t minute_p_boilerCT = 0;
unsigned int adresa_eepprom_start = 512; // adresa de inceput pentru parametri/contoare de unde incepe maparea
/*-----( Declare objects )-----*/
OneWire oneWire(ONE_WIRE_BUS);
// Pass address of our oneWire instance to Dallas Temperature.
DallasTemperature sensors(&oneWire);
// Start the LCD display library
LiquidCrystal_I2C  lcd(I2C_ADDR, En_pin, Rw_pin, Rs_pin, D4_pin, D5_pin, D6_pin, D7_pin);
// initializare adrese senzori DS18B20
// pentru citire adrese si afisare pe serial, vezi exemplul DS18x20_Temperature.pde
[....]
DeviceAddress T_acm = { 0x28, 0x38, 0x47, 0xD4, 0x07, 0x00, 0x00, 0x9F }; // senzor 13
DeviceAddress T_acmsus = { 0x28, 0x38, 0x47, 0xD4, 0x07, 0x00, 0x00, 0x90 }; // senzor ?   adrese fictive
DeviceAddress T_acmjos = { 0x28, 0x38, 0x47, 0xD4, 0x07, 0x00, 0x00, 0x91 }; // senzor ?   adrese fictive
int a = 0; // ADC value for button

void setup()
{
  Wire.begin();
	//------- Initialize the Temperature measurement library--------------
  sensors.begin();
  // set the resolution to 9 bit -->0.5grad 10bit --> 0.25 grad (Can be 9 to 12 bits .. lower is faster)
[....] // alti senzori
  sensors.setResolution(T_acm, 10);
  sensors.setResolution(T_acmjos, 10);// neinstalat inca
  
	 //---------------- Initializare lcd ------------------
  lcd.begin (20, 4); // 20 caractere, 4 linii
  // pornire iluminare
  lcd.setBacklightPin(BACKLIGHT_PIN, POSITIVE);
  lcd.setBacklight(LED_ON);
  lcd.clear();
 
  // initializare pin 14,15,16,17,6,7 ca OUTPUT si activare ca HIGH)
  // pentru releele cu trigger pe LOW
  // !!! in cazul releelor care ce activeaza cu HIGH toate starile trebuie inversate !!!
[.....]
  pinMode(pompa_CT_boiler, OUTPUT);
 
  /// test iesiri relee
[.....]
  digitalWrite(pompa_CT_boiler, LOW);

  pinMode(termostat_ambianta, INPUT_PULLUP); //termostatul va conecta pinul respectiv la GND prin intermediul unui releu sau optocuplor
  pinMode(A7, INPUT); //butoanele sunt conectate la A7
 
  /// citire/actualizare valori contoare ore EEPROM/ram
  EEPROM.get((adresa_eepprom_start + 17), contor_pompa_boilerCT); //contor ore p_boCT
  EEPROM.get((adresa_eepprom_start + 19), minute_p_boilerCT);
}

void loop()   /****** LOOP: ruleaza continuu ******/
{
/// citeste intrarea A7 la fiecare 25ms si evalueaza ce buton e apasat
/// citeste intrarea D
/// alte operatii nerelevante pentru bucata de cod prezentata
/// bucla pentru operatii repetitive la 100ms (citire temperaturi, calculare temperaturi medii puffer, citire inputuri, citire ora)
  stop_counter00 = millis();
  if ((stop_counter00 - start_counter00) >= 100)
  {
	start_counter00 = stop_counter00;  // resetare contor
	[....]   /// alte operatii si instructiuni
	sensors.requestTemperatures(); // trimitere comanda pentru achizitie temperarturi
	staretermostatambianta = digitalRead(termostat_ambianta); // citire stare termostat ambianta
		temp_bacm = sensors.getTempC(T_acm);
		temp_bacmjos = sensors.getTempC(T_acmjos);
  [....]
   
  }
  /// sfarsit operatii la 100ms
  [....] // o multime de linii de cod :)
 
  [timer configurabil 100-250ms similar cu cel de 100ms, pentru actualizarea informatiilor de pe ecran, deoarece nu are rost sa aglomeram comunicatia i2c inutil]
  [mai multe meniuri de afisat pe LCD, unele meniuri se actualizeaza doar la 1000ms, ca infoscreen03, dar altele au elemente care se actualizeaza dupa acest timer]
  infoscreen03 (); // timpi functionare CT
  /// sfarsit timer
 
	if (start_counter00 == stop_counter00)	// adica o data la 100ms
  {
	decisionsPumpState(); /// --- rutina decizii stari pompe in functie de temperaturi --- ///
	setOutputrelays(); // functie de setare iesiri pentru relee in LOW/HIGH, pentru pinii 4-7
	contoarepompe(); // contorizarea timpilor de functionare ai pompelor
   }
  delay(5); // o mica pauza inainte de a relua void loop
}
void decisionsPumpState()
{
[....]
  if (mod_ACM_vacanta == 0)  // ocolire logica de start daca e setat ACM mod vacanta
  {
	if ((temp_bacm < temp_startpompaboilerCT) && (temp_bacm > 0) && (starepompaboilerCT == 0)) // conditii de start...momentan 42  + conditie de ocolire a startului daca senzorul da rateuri sau daca e deja pornita
	{
	  starepompaboilerCT = 1;
	}
	if (starepompaboilerCT == 1)  // conditie de ocolire a rutinei daca pomopa e deja oprita  
	{
	  if ((temp_bacmjos > 0) && (temp_bacmjos < 120))  // conditie de folosire a sensorului daca semnalul e valid, varianta 2 senzori prezenti
	  {
		if (temp_bacmjos >= temp_stopompaboilerCT) // conditie de stop cu senzor boiler jos... momentan 52
		{
		  starepompaboilerCT = 0;
		}	  
	  }
	  else
	  {
		if ((temp_bacm >= temp_stopompaboilerCT) || (temp_bacm <= 0)) // conditii de stop cu senzor boiler mijloc daca semnalul senzor jos nu e valid sau daca da rateuri temp_bacm , varianta un senzor prezent
		{
		  starepompaboilerCT = 0;
		}
	  }
	  if ((staretermostatambianta == LOW) && (temp_teu_tur_puffer < temp_startpomparadiatoare) && (temp_puffersus < temp_startpomparadiatoare) && (temp_bacm >= ((temp_stopompaboilerCT+temp_startpompaboilerCT)/2))) // conditie de scurtare a timpului de recirculare a ACM daca este activat TA (CT comuta de pe ACM e incalzire)
	  {
		starepompaboilerCT = 0;
	  }
	}
  }
  else
  {
   starepompaboilerCT = 0;
  }
[....]
}

void setOutputrelays()
{
[.....]
if (starepompaboilerCT == 1)
  {
	digitalWrite(pompa_CT_boiler, LOW); // activeaza releu 6
	millis_p_boCT_acum = millis();
	tmp_contor_pompa_boilerCT = tmp_contor_pompa_boilerCT + (millis_p_boCT_acum - millis_p_boCT_anterior);
	millis_p_boCT_anterior = millis_p_boCT_acum;
   
  }
  else
  {
	digitalWrite(pompa_CT_boiler, HIGH);  // dezactiveaza releu 6
	millis_p_boCT_anterior = millis();
  }

[.....]
}
void contoarepompe()  // contoare incrementale pentru pompe [ore si minute]
{
[....]
	if (tmp_contor_pompa_boilerCT >= 60000) // pompa de recirculare boiler prin CT
  {
	tmp_contor_pompa_boilerCT -= 60000; // scadere un minut din milisecunde
	minute_p_boilerCT ++; // incrementare contor minute
	if (minute_p_boilerCT > 59)
	{
	  minute_p_boilerCT = 0;
	  contor_pompa_boilerCT ++;  // incrementare contor de ore
	  EEPROM.put((adresa_eepprom_start + 17), contor_pompa_boilerCT); //contor ore p_boCT
	}
	EEPROM.put((adresa_eepprom_start + 19), minute_p_boilerCT);
  }
 
[....]
}
void infoscreen03 () // afisare timpi functionare CT condensatie (ACM + incalzire)
{
  stop_counter04 = millis();
  if ((stop_counter04 - start_counter04) >= 1000)
  {
	// urmeaza comenzi care sunt executate la fiecare 1000ms, informatii pe ecran
	start_counter04 += 1000; // actualizare contor
	lcd.clear();   
	[....]
	sprintf(timp01_hmm, "%4u:%02u:%02u", contor_pompa_boilerCT, minute_p_boilerCT, (tmp_contor_pompa_boilerCT / 1000)); // timp01_hmm e o variabila string de minim 11 caractere (10 necesare + 1 non printable), sprintf construieste un string de  din variabilele din argumente  %4u - 4 digiti, unsigned, no leading zero pentru ore; %02u - 2 digiti, leading zero pentru minute; la fel pentru secunde
	lcd.setCursor(0, 2); lcd.print(F("Apa calda ")); lcd.print(timp01_hmm);
  }
}
void readButtonsDoAction () // citire ADC butoane, decizii actiuni in functie de buton activ, meniu activ, selectie, etc.
{
/// nenecesar deoarece in fragmentul acesta nu folosim navigarea prim meniuri fiid doar unul (infoscreen03)
}



#1507
radurus

radurus

    Senior Member

  • Grup: Senior Members
  • Posts: 8,023
  • Înscris: 16.06.2006
A facut careva pe aici un meniu cu scroll?

Pe langa ce am vreau sa mai introduc o pagina cu un meniu unde sa vad diferiti parametri (as avea vreo 15, m-as putea la 5-7, dar ap putea sa mai adaug peste 15), valorile lor, sa defilez cu up/down.
Cu ok sa pot intra un nivel mai jos pe parametru (nume, valoare actuala, valoare setabila up/down/ok)
Cu left (back) sar inapoi un nivel mai sus pe lista de unde am plecat.

Idei?

#1508
radurus

radurus

    Senior Member

  • Grup: Senior Members
  • Posts: 8,023
  • Înscris: 16.06.2006
Am facut meniul (primul nivel).
Am definit un "char array" cu 21 parametri
Afisez pe ecran 3 dintre ei.
Il am setat pe cel curent, cel anterior si urmatorul sunt calculati cu +/- 1 sau conditii daca trebuie sarit la capatul opus al listei.

Cu up/down defilez prin lista schimband parametrul curent. Ceilalti doi (deasupra/dedesubt) de afiseaza automat.
La capete, lista face rollover.

Eu consideram numerotarea si "call"-ul din array incepand cu 1 dar se incepe cu 0.

Urmeaza sa fac nivelul urmator.


#1509
radurus

radurus

    Senior Member

  • Grup: Senior Members
  • Posts: 8,023
  • Înscris: 16.06.2006
...deci
Nivelul urmator e facut.

Pe baza unui switch/case aduc valoarea parametrului curent (selectat) impreuna cu limitele upper/lower in noul "ecran" (unde am si numele parametrului curent)

Aici pot face + sau - la valoare dar am in dreapta si valoarea nemodificata (sunt de fapt 2 variabile distincte temporare)

La actiunea "ok" valoarea din dreapta este actualizata cu noua valoare.

Butonul "back" (stanga) ma aduce inaloi in lista parametrilor.
Am renuntat la ideea de a afisa si valorile parametrilor anterior/urmator. Necesita mai mult cod.

Ramane sa vad cum salvez inapoi valoarea modificata in parametrul original...

Am incercat sa pun valorile parametrilor intr-un "array" de uint8_t dar nu a mers. Poate n-am facut eu ceva bine si trebuia sa declar int.
Daca functioneaza asa pot plasa parametrii si limitele in 3 constructii "array", ii citesc de acolo unde pot sa si actualizez.
Ar fi mai simplu sa actualizez punctual valorile din "array".

Se pare ca in testele mele mi-am "busit" Nano-ul cu care faceam testele. Merge dar nu mai pot incarca pe usb nimic.

#1510
radurus

radurus

    Senior Member

  • Grup: Senior Members
  • Posts: 8,023
  • Înscris: 16.06.2006
Mi-am meniul cu parametri pe care il doream.
Este loc de extins numarul de parametri/ optiuni si de optimizat.

Nano-ul nu e bushit, am reusit sa incarc soft in el dar necesita mai multe incercari si ledul 13 se aprinde ciudat.

O sa fac un filmulet scurt ca e mai usor de inteles dar cine este interesat poate citi mai jos logica de functionare:

- am declarat 5 arrays (vectori?) Un "char*" si 4 ca uint8_t pentru consum mai redus de resurse  (numele parametrilor , variabilele parametrilor, valorile "default", valorile upperlimit, valorile lowerlimit)
- de asemenea a trebuit sa declar si variabilele parametrilor
- principiul de lucru este cel prezentat anterior, meniu se afla intr-un void separat (eu le spun "screen" sau pagina)
- pe LCD linia 0 am titlul meniului
- la linia 2 este afisat parametrul curent si valoarea lui (extrase din vectorii respectivi)
- pe linia 1 si 3 sunt numele parametrilor n-1 si n+1 fara valori (extrase din vectorul nu numele lor)
- defilarea de face cu up/down cu -- sau ++ la indicele obiectului din vector si rollover la primul/ultimul parametru
- activarea butonului ok conduce in al screen (e o alta rutina void) in care sunt preluate 4 valori din 4 vectori (nume parametru curent, valoare parametru curent, limita sus, limita jos) si o variabila temporara egala cu valoarea parametrului curent pentru incrementare/decrementare cu ++ (up) sau -- (down)
- limitele opresc incrementarea/ decrementarea la valorile max/min
- activarea butonului "ok" in ecranul acesta conduce la copierea valorii temporare in vector la indicele parametrului curent, si scrierea in epprom la o adresa (am adresa de start + indicele parametrului curent din vector).
- butonul "stanga" e considerat "back" si face  saritura la inapoi la lista parametrilor la pozitia ramasa.
- butonul "dreapta" nu l-am implementat dar ar putea reseta parametrul curent la valoarea "default".
- actiunile butoanelor sunt verificate in subrutina paginii respective.

In lista parametrilor am pus si o optiune "reset parametri la valori initiale". Asta ia doar valori 0/1. Daca o fac 1 si dau ok, programul sare (in fundal, nu se vede pe display) intr-o alta subrutina (void) unde este un "for" in care pe rand se citeste fiecare parametru din vectorul cu valori default, se copiaza in vectorul cu valori curente si se scrie in eeprom la adresa curenta (ca in pagina cu salvare in eeprom), apoi intorcandu-se cu executia in pagina unde este optiunea "initializare parametri".

#1511
radurus

radurus

    Senior Member

  • Grup: Senior Members
  • Posts: 8,023
  • Înscris: 16.06.2006
Iata si videclipul promis.

[ https://www.youtube-nocookie.com/embed/WnlLuo0EXBU?feature=oembed - Pentru incarcare in pagina (embed) Click aici ]


#1512
radurus

radurus

    Senior Member

  • Grup: Senior Members
  • Posts: 8,023
  • Înscris: 16.06.2006
Salut.
Unul dintre colegii de pe aici mi-a sugerat sa postez codul pentru meniul pe care l-am facut.
Nu este un meniu universal dar cred ca poate fi folosit/adaptat usor daca e cineva care ar dori sa isi implementeze ceva similar.
Imi pare rau pentru explicatiile din preambulul lung dar cine stie poate sari direct la cod.
Trebuie explicat deoarece meniul acesta mai are si tentacule prin restul codului automatizarii, iar cele 2 rutine ar fi incomplete fara inca altele.
Codul adaugat pentru meniul acesta impreuna cu alte optimizari facute ocupa suplimentar de ceea ce aveam ~900byte in flash si ~430byte in SRAM.

Variabila uint8_t [buton] este actualizata la fiecare 20ms (desi cred ca e mai mult in realitate) pe baza citirii unui port analogic.
0 - niciun buton
1 - sus
2 - dreapta
3 - jos
4 - stanga
5 - ok

Altcineva ar putea implementa butoane pe port digital si ar putea face conexiunea usor cu meniul acesta.
In meniul acesta se afla doar comenzile pentru butoanele sus/ok/jos.
Comenzile pentru stanga/dreapta se afla altundeva in rutina de evaluare a ADC-ului (void readButtonsDoActions() care nu apare in codul de mai jos) deoarece ele incrementeaza/decrementeaza variabila uint8_t [meniu] (0....6) adica maxim 7 pagini sau cate vrei si fac rollover la capete.(daca puneam comenzile stanga/dreapta in fiecare pagina de meniu, se repetau inutil ocupand spatiu de memorie).

Eu am incercat sa optimizez folosirea variabilelor ca sa ocupe cat mai putina memorie, e valabil si pentru EEPROM:
- uint8_t - pentru orice numar care e doar pozitiv pana la 255 (1 byte), de mai foloseste si "byte". Am folosit si pentru variabila true/false  1/0 tot uint8_t ca nu se poate mai mic de 8 biti.
- unsigned int - pentru orice numar pozitiv pana la 65535 (2 byte) (de exemplu 60000ms este 1 minut, pentru contor de minut nu are rost sa folosesc o variabila de tip mai "mare", sau in EEPROM pentru contoare de ore de functionare deoarece 255 era prea mic)
Adresa de pornire pentru operatii EEPROM este 562 deoarece de la 512 am alte chestii rezervate si mi s-a parut mie interesant sa nu incep de la 0.
Coordonatele pentru lcd.setcursor(x-coloana,y-rand) sunt aranjate pentru display 20(x) x 4 (y) caractere, incepand cu 0,0 din stanga-sus.

Sper sa va fie de ajutor.
Va doresc "Happy programing"!
Lozinca de programatori: "we brake keyboards not hearts"

// declarare variabile, constante

// lista parametrilor, array, pentru meniul de setari, alegerea dimensiunii textului trebuie facuta a.i. la inceput sa aiba loc indicele parametrului (daca vrei) iar 3 caractere la final pentru valoarea parametrului
const byte parametriFolositi = 25; // numarul maxim de parametri din lista, e pus intr-o "variabila" ca sa poata fi extinsa mai usor lista. Atentie ca pointerul dintr-un vector ia valori de la 0 la 24 (in cazul meu)
const char * const listaParametriMeniu[] PROGMEM =
{
  "TTurStartPPu",
  "TTurStartHist", 
  "TMinRetur",
  "TMinReturHist",
  "TTurStartPBo",
  "TTurDifBoiler",
  "TBoilerStopPBo",
  "TBoilerStopHist",
  "TBoilerStrtReCT",
  "TBoilerStopReCT",
  "TPufMinIncalz.",
  "TPufMinHist",
  "En%MaxStartPRa",
  "ModDetectFoc",
  "TempStartFoc",
  "TempStopFoc",
  "ACMVacanta",
  "AutoscrollTime",// counter05// contorul 05 e folosit la defilare automata intre ecrane la 3s configurabil 1-6 dintr-
  "DoarPufer",
  "DoarBoiler",
  "InitParametri",
  "ResetTimpPBy",
  "ResetTimpPuf",
  "ResetTimpPBo",
  "ResetTimpPRad",
};
uint8_t j_param = 0; // variabila folosita pentru indicele array din lista parametri char* si nu numai
uint8_t j_param_prev, j_param_next;// variabile folosita pentru indicele array anterior/urmator din lista parametri char*
uint8_t tmp_param_value, i; // tmp_param_value e "volatila", i e folosit in 2 constructii de tip "for"

// declarare tip variabile din vectorul urmator
uint8_t temp_min_start_pompapuffer, histerezis_temp_start_pompapuffer, temp_min_retur, histerezis_retur, temp_min_start_pompaboiler, temp_diff_start_boiler, temp_max_boiler, histerezis_temp_stop_boiler, temp_startpompaboilerCT, temp_stopompaboilerCT, temp_min_start_radiatoare, histerezis_temp_start_radiatoare, energy_percent_max_avarie, mod_detectie_foc_activ, temp_startfoc, temp_stopfoc, mod_ACM_vacanta, counter05, mod_doar_puffer, mod_vara;

// declarare vector
uint8_t parameterValues[parametriFolositi] // valoarea actuala parametriFolositi= 25, variabilele trebuie sa corespunda  la ordine cu numele prametrilor din lista
{
  temp_min_start_pompapuffer, // acestia sunt parametrii mei
  histerezis_temp_start_pompapuffer,
  temp_min_retur,
  histerezis_retur,
  temp_min_start_pompaboiler,
  temp_diff_start_boiler,
  temp_max_boiler,
  histerezis_temp_stop_boiler,
  temp_startpompaboilerCT, // aceasta este o alta pompa, nu cea de la  cazan pentru serpentina
  temp_stopompaboilerCT, //
  temp_min_start_radiatoare,
  histerezis_temp_start_radiatoare,
  energy_percent_max_avarie, // daca puferul trece de o limita (%) atunci porneste pompa radiatoarelor
  mod_detectie_foc_activ, // 0/1 pot detecta focul cu termostat de cos sau cu temperatura cazanului
  temp_startfoc,
  temp_stopfoc,
  mod_ACM_vacanta, ///
  counter05, // contorul 05 e folosit la defilare automata intre ecrane la 3s configurabil 1-6 dintr-un meniu
  mod_doar_puffer,  // dezactivare pompa boiler
  mod_vara, ///  doar boiler, dezactivare pompa pufer
  0, // acestia sunt "parametri" folositi pentru a face reset la diferite chestii
  0,
  0,
  0,
  0
};
// valori "default" pentru parametri modificabili din optiuni
const uint8_t default_parameters[20] PROGMEM ={81,1,65,10,70,6,80,2,42,52,43,2,110,1,70,65,0,3,0,0};

// limitele de modificare ale parametrilor de mai sus - ultimii 5 sunt doar comenzi de reset.
const uint8_t parameters_upperlimit[parametriFolositi] PROGMEM ={85,5,70,12,75,8,80,3,50,55,50,4,115,1,75,65,1,6,1,1,1,1,1,1,1};
const uint8_t parameters_lowerlimit[parametriFolositi] PROGMEM ={75,1,50,6,65,4,65,1,35,40,40,1,100,0,60,50,0,1,0,0,0,0,0,0,0};

void setup()
{...
  for (i=0 ;i <20; i++) // 20 numarul parametrilor, array este [25] dar ultimii 5 sunt doar comenzi de reset.
  {
	EEPROM.get((562 + i), parameterValues[i]); // citire din EEPROM a parametrilor de la adresele corespunzatoare si actualizarea valorilor in vector pe rand la fiecare rulare cate unul
	copy_arrayParamToFunctionalVariable(); // a trebuit sa construiesc rutina asta pentru a actualiza valorile realtime din RAM cu valorile din vector
  }
.....
}

// urmeaza void loop()
{..o groaza de cod..  unde e si switch/case pentru afisat paginile pe ecran in functoe de [meniu] etc.}

// iata si rutina de actualizare a valorilor parametrilor, nu mi-a venit alta idee, cred e buna ca sa o pot refolosi
void copy_arrayParamToFunctionalVariable()
{
  switch (i)
  {
   case 0:
	temp_min_start_pompapuffer = parameterValues[i];
	break;
   case 1:
	histerezis_temp_start_pompapuffer = parameterValues[i];
	break;
   case 2:
	temp_min_retur = parameterValues[i];
	break;
   case 3:
	histerezis_retur = parameterValues[i];
	break;
   case 4:
	temp_min_start_pompaboiler = parameterValues[i];
	break;
   case 5:
	temp_diff_start_boiler = parameterValues[i];
	break;
   case 6:
	temp_max_boiler = parameterValues[i];
	break;
   case 7:
	histerezis_temp_stop_boiler = parameterValues[i];
	break;
   case 8:
	temp_startpompaboilerCT = parameterValues[i];
	break;
   case 9:
	temp_stopompaboilerCT = parameterValues[i];
	break;
   case 10:
	temp_min_start_radiatoare = parameterValues[i];
	break;
   case 11:
	histerezis_temp_start_radiatoare = parameterValues[i];
	break;
   case 12:
	energy_percent_max_avarie = parameterValues[i];
	break;
   case 13:
	mod_detectie_foc_activ = parameterValues[i];
	break;
   case 14:
	temp_startfoc = parameterValues[i];
	break;
   case 15:
	temp_stopfoc = parameterValues[i];
	break;
   case 16:
	mod_ACM_vacanta = parameterValues[i];
	break;
   case 17:
	counter05 = parameterValues[i];
	break;
   case 18:
	mod_doar_puffer = parameterValues[i];
	break;
   case 19:
	mod_vara = parameterValues[i];
	break;
  }
}


// meniu vizualizare parametri - meniu functional scroll up/down/rollover
// la mine lista asta e accesata cand meniu=4
void meniuParameterList()
{
  // actiuni butoane
  if (buton == 1)  // buton sus
  {
  // conditiile aste se pot scrie prescurat , am incerca dar nu stiu de ce nu mi-a mers
  if (j_param == 0)
	{
	j_param = parametriFolositi-1;
	}
	else
	{
	j_param -= 1;
	}
  }
  if (buton == 3)  // buton jos
  {
	if (j_param == parametriFolositi-1) // conditii pentru rollover in jos
	{
	  j_param = 0;
	}
	else
	{
	  j_param += 1; // incrementare
	}
  // j_param = (j_param == 1) ? parametriFolositi : (j_param+1);
  }
  j_param_prev = (j_param == 0) ? (parametriFolositi-1) : (j_param-1);// calculam aici pointerele anterior si urmator si punem conditii ca sa faca rollover si sa nu incurce treaba pe aici
  j_param_next = (j_param == (parametriFolositi-1)) ? 0 : (j_param+1);// daca j_param=24, atunci j_param_next=0, daca nu, ia valoare +1.
  if (buton == 5) // buton ok
  {
  // sari in ecran singleParamDisplay unde se preia numele si valoarea din vector cu pointer j_param de doua ori (una fixa si una modificabila)
	tmp_param_value = parameterValues [j_param];
	meniu_anterior = meniu; // ca sa ma pot intoarce de une am plecat
	meniu = 49; // sare la singleParamDisplay(); // paginile intregii interfete sunt accesate pe baza valorii variabilei meniu intr-o constructie switch/case
  }
// sfarsit actiuni butoane

// urmeaza afisarea pe ecran
  lcd.clear();
  lcd.setCursor(0, 0); lcd.print(F("Parametri"));
  lcd.setCursor(16, 0); lcd.print((char)13); lcd.print(F("OK")); lcd.print((char)14); // char 13 si 14 sunt definite/declarate inainte de setup ca uparrow si downarrow
	lcd.setCursor(0, 1);
	lcd.print (j_param_prev); // acesta e pointerul pentru randul de deasupra sa fie afisat la inceputul randului
	lcd.print(listaParametriMeniu[j_param_prev]); // acesta e numele parametrului de la pointerul anterior
	lcd.setCursor(0, 2);
	lcd.print (j_param);
	lcd.print(listaParametriMeniu[j_param]);
	lcd.setCursor(17, 2); sprintf(timp01_hmm, "%3u", parameterValues [j_param]); lcd.print(timp01_hmm);// aliniere la dreapta cu sprinf 3 caractere fara 0 in fata a valorii parametrului actual, pentru cei nepretentiosi se poate printa direct variabila asa cum e
// timp01_hmm e o variabila char de cel putin 3+1 caractere (in cazul asta) dar in codul meu ea este de 11 deoarece o refolosesc pentru diferite contoare de timp de tipul hhhh:mm:ss

	lcd.setCursor(0, 3);
	lcd.print (j_param_next);
	lcd.print(listaParametriMeniu[j_param_next]); // n-am mai pus si valorile parametrilor anterior si urmator ca imi ocupa memorie
}
// cam asta e lista parametrilor, treaba importanta e in declararea vectorilor

// rutina urmatoare este pentru afisarea unui singur parametru cu scopul de a-l modifica
// am sarit aici cand am zis meniu=49 dar poate fi alt numar insa trebuie sa fie acelasi cu  cel din switch/casevoid
// meniu= 49 este trecut ca exceptie la actiunile comune pentru butoanele 4 si 2 (stanga/dreapta) situate altundeva in cod cum am explicat undeva mai sus
// tmp_param_value are deja valoarea parametrului curent cu pointerul [j_param]
singleParamDisplay()
{
// actiuni butoane , limitele sunt incarcare din array
  //sus tmp_param_value ++
  if (buton == 1)
  {
	tmp_param_value ++; // incrementam valoarea
	if (tmp_param_value > parameters_upperlimit[j_param])
	{
	  tmp_param_value = parameters_upperlimit[j_param]; // dar o limitam la valoarea limita superioara
	  // tmp_param_value = parameters_lowerlimit[j_param];// sau daca preferam rollover activam linia asta si o comentam pe anterioara
	 }
  }
  //jos tmp_param_value --
  if (buton == 3)
  {
	tmp_param_value --; // decrementam valoarea
	if (tmp_param_value < parameters_lowerlimit[j_param])
	{
	  tmp_param_value = parameters_lowerlimit[j_param];// dar nu mai jos decat ne permite limita inferioara definita in vectorul respectiv
	  // tmp_param_value = parameters_upperlimit[j_param]; //pentru rollover
	}
  }
  // buton ok confirmare, aici treaba se complica putin
  if (buton == 5)
  {
	parameterValues[j_param] = tmp_param_value; // atribuim valoarea temporara variabilei din vector la pointerul curent [j_param] care are aceeasi valoare in toata rutina asta
	if (j_param < 20)  // daca e vorba de parametri, scriem in eeprom altfel ignoram ca de la 21 la 25 sunt niste comenzi de reset.
	{
	  EEPROM.put((562+ j_param), parameterValues[j_param]); //scriem in EEPROM noua valoare pe care am actualizat-o anterior in vector
	  i = j_param; // ii dam lui [i] valioarea lui [j_param] deoarece trebuie folosit in rutina urmatoare pentru switch/case la actualizare variabila reala din RAM
	  copy_arrayParamToFunctionalVariable();  // sarim acolo, facem greaba si ne introarcem inapoi sa continuam  
	}
	if ((j_param == 20) && (tmp_param_value==1)) // parametrul 20 este pentru a initializa toti parametri la valorile din "fabrica". Valorile lor sunt luate din vectorul default_parameters[20]
	{
	  resetParamToDefault(); // vezi mai jos codul
	  tmp_param_value = 0;
	  parameterValues[j_param] = 0;// actualizeaza valorile pe ecran dupa ce ai s-a terminat citirea din eeprom
	}
  }
  // stanga sari inapoi in meniu
  if (buton == 4)
  {
	 meniu = meniu_anterior;// adica va fi parcursa rutina meniuParameterList();
  }
  if (buton == 2) // buton dreapta
  {
	 // adaugi aici comenzile pentru butonul [dreapta]
  }
  // sfarsit actiuni butoane

// ce apare pe ecran?
lcd.clear();
lcd.setCursor(0, 0); lcd.print(listaParametriMeniu[j_param]); // numele parametrului ca titlu
// afisam pe ecran si indicii despre ce butoane/functii sunt disponibile aici
lcd.setCursor(1, 1);
lcd.print((char)13); //up arrow
lcd.setCursor(13, 1); lcd.print((char)127); // left arrow
lcd.print(F(" back"));
lcd.setCursor(0, 2); lcd.print(F("OK [   ]")); lcd.setCursor(4, 2); lcd.print(tmp_param_value);  lcd.setCursor(15, 2);
// lcd.print(current_param_value);
lcd.print (parameterValues [j_param]);
lcd.setCursor(1, 3); lcd.print((char)14); // down arrow
lcd.setCursor(13, 3); lcd.print(F("reset ")); lcd.print((char)126);// right arrow... dar butonul 2 (dreapta) nu are nicio comanda implementata
}

void resetParamToDefault()
{
   for (i=0 ; i <20; i++)
   {
	 uint8_t tempval  = default_parameters[i]; // folosim o variabila intermediara volatila careia ii atribuim pe rand valorile parametrilor, nu a mers direct vector cu vector
	 parameterValues[i] = tempval; // actualizam in vector valoarea parametrului
	 EEPROM.put((562 + i), tempval); // citire din array a parametrilor si actualizarea valorilor in EEPROM
	 copy_arrayParamToFunctionalVariable(); // il avem pe [i], refolosim rutina de actualizare (switch/case) a variabilelor din RAM
	}
}



Anunturi

Chirurgia spinală minim invazivă Chirurgia spinală minim invazivă

Chirurgia spinală minim invazivă oferă pacienților oportunitatea unui tratament eficient, permițându-le o recuperare ultra rapidă și nu în ultimul rând minimizând leziunile induse chirurgical.

Echipa noastră utilizează un spectru larg de tehnici minim invazive, din care enumerăm câteva: endoscopia cu variantele ei (transnazală, transtoracică, transmusculară, etc), microscopul operator, abordurile trans tubulare și nu în ultimul rând infiltrațiile la toate nivelurile coloanei vertebrale.

www.neurohope.ro

0 user(s) are reading this topic

0 members, 0 guests, 0 anonymous users

Forumul Softpedia foloseste "cookies" pentru a imbunatati experienta utilizatorilor Accept
Pentru detalii si optiuni legate de cookies si datele personale, consultati Politica de utilizare cookies si Politica de confidentialitate