Lysstyring med Arduino 4

I dette fjerde afsnit om lysstyring med Arduino vil jeg se nærmere på TLC5940 kredsløbet og det tilhørende Arduino bibliotek. Jeg vil først omtale TLCen nærmere, og herefter se på biblioteket; i det sidste tilfælde både på det konkrete bibliotek til TLCen, men også Arduino biblioteker i mere almindelighed.

Texas Instruments

TLC kredsløbet er udviklet af Texas Instruments til brug i fladskærms TV og lignende. Firmaet kaldes normalt TI blandt elektronikfolk, så det vil jeg også tillade mig at gøre her; i mine unge dage var det kendt af enhver, da det var en af de største producenter af avancerede lommeregnere - enhver gymnasieelev med respekt for sig selv havde en TI 57 på den tid. I dag lever TI en mere tilbagetrukket tilværelse som producent af integrerede kredsløb - men er faktisk nummer tre i verden på området, kun overgået af Intel og Samsung.

Firmaets historie er dog endnu mere imponerende: Jack Kilby fra TI opfandt det integrerede kredsløb - IC - i 1958, hvad han sidenhen fik en Nobel-pris for. I 60erne opfandt og udviklede TI den såkaldte 7400 serie af TTL (Transistor to Transistor Logic) kredsløb, der er helt basale byggeklodser i enhver computer. I dag har man samlet en masse funktionalitet i nogle få, højt specialiserede ICer, men tidligere brugte man næsten altid TTL kredsløb som de basale komponenter i enhver computer. En simpel printerport i de første IBM PCer var et helt indstikskort, der bestod af et sted mellem 50 og 100 forskellige TTL kredsløb fra 7400 serien.

7400 serien var også af afgørende betydning for USAs rumfartsprogram, der kulminerede med månelandingen i 1969: Man siger normalt, at det var rumfartsprogrammet - og dermed den amerikanske stat - der betalte for udviklingen af netop 7400 serien. Den viden, man fik her, var siden af afgørende betydning for, at USA vandt våbenkapløbet mod Sovjetunionen. 

Det er altså ikke et hvilket som helst firma, vi har med at gøre.

Som et kuriosum kan bemærkes, at mange af de samme komponenter, der findes som hardware i 7400 serien, i dag kan genfindes som virtuelle komponenter i det meget populære computerspil Minecraft, hvor de går under navnet Redstone Circuits.

TLC 5940

Vil man vide mere om et kredsløb som TLC 5940, skal man have fat i et datablad: I de gode, gamle dage var det noget, man betalte i dyre domme for, men i dag er det heldigvis blevet noget, man kan hente gratis som PDF-fil på Internettet. I dette tilfælde skal man finde databladet hos TI, da det er dem, der har lavet kredsløbet. Jeg vil anbefale enhver, der har lyst til at lege med her, at se på databladet; men man skal ikke løbe skrigende væk, hvis man ikke forstår det hele - det gør kun de færreste. Datablade har gennem årene udviklet deres egen stil, og det kan godt tage noget tid at vænne sig til den; men vil man lege med elektronik af denne slags, er det en god idé at bruge lidt tid på at lære stilen. Selv som nybegynder kan man ofte finde små guldkorn i databladene.

I vores sammenhæng er databladet først og fremmest vigtigt af en bestemt grund: Indtil nu har jeg gentagne gange skrevet, at man kan regulere strømmen gennem lysdioderne fra nul til maksimum med en opløsning på 12 bit: Det betyder, at en værdi på 0 er det samme som ingen strøm, mens en værdi på 4095 er det samme som fuld styrke. Men hvad er fuld styrke?

Ved man noget om lysdioder, ved man også, at lysstyrken udelukkende bestemmes af den strøm, der går gennem dioden - spændingen er ligegyldig. De fleste lysdioder kan give fornuftigt lys ved en strøm på 10 mA, men det er ligegyldigt, om man opnår denne strøm med en spænding på 5 V eller 500 V. TLC kredsløbet har derfor det, man kalder en konstantstrømsgenerator - den sørger for, at der hele tiden går den ønskede strøm gennem dioden - uanset spændingen. Men jeg sniger mig stadig udenom det afgørende spørgsmål: Hvad er fuld styrke - eller mere præcist, hvad er den maksimale strøm, der går gennem dioden, når man sætter udgangen til 4095?

Det finder man ud af ved at læse databladet: Den relevante information finder man nederst på side 14 i PDF-filen. Her kan man se, at man skal forbinde en modstand mellem IREF benet på TLCen og stel. Størrelsen af denne modstand bestemmer, hvor stor den maksimale strøm vil være. Laver man sit eget kredsløb, bestemmer man også selv størrelsen af modstanden - men bruger man det breakout board fra Sparkfun Electronics, jeg tidligere har omtalt, er modstanden allerede forbundet. Ser man på produktsiden for breakout boardet, vil man se, at Sparkfun har sat en modstand med værdien 2,2 kΩ på, og det svarer, som de skriver, til en maksimum strøm på 17,8 mA. Her er svaret altså: Sætter man fuld knald på udgangen med værdien 4095, vil der gå 17,8 mA gennem dioden.

Sparkfun skriver også, at de har efterladt plads på breakout boardet, så man kan forbinde sin egen modstand i parallel med den, der allerede sidder der: Når man parallelforbinder to modstande, bliver den samlede modstand mindre. Ser man i databladet, vil man se, at det betyder, at den maksimale strøm bliver større: Man kan altså sætte en modstand på, der betyder, at den maksimale strøm bliver forøget - men man kan ikke gøre den mindre end 17,8 mA uden at lodde den eksisterende modstand af og erstatte den med en større. Det betyder ikke noget i vores sammenhæng - 17,8 mA er et fint maksimum for lysdioder - men jeg har brugt det som eksempel for at vise, hvordan man finder de nødvendige informationer. Og for at vise, at det noget ubestemmelige fuld styrke, jeg tidligere har skrevet, ikke er en eller anden kryptisk værdi grebet ud af den blå luft, men derimod en meget reel værdi, man kan finde frem til.

Ser man på side 12 i databladet, ser man noget andet, der bør være ret skræmmende: Et indviklet diagram, der viser, hvordan Arduinoen skal kommunikere med TLC kredsen. Det vigtigste her timingen - der er en helt masse impulser, der skal synkroniseres og sendes på præcist de rigtige tidspunkter, hvis systemet skal virke. Det kan godt være svært at få puslet på plads - det er ofte et spørgsmål om mange små detaljer, der skal gå op i en højere enhed, hvis man ikke skal få indført en uheldig forsinkelse et eller andet sted.

Man kan klare sig med lidt - men skal man for alvor arbejde med denne slags opgaver, er det bedst at have en såkaldt protokol analysator, så man kan se, hvad man sender ud af Arduinoen og ind i TLCen. Jeg fik et fint tip fra Mikael i denne tråd, og har derfor anskaffet en Logic16. Et pragtfuldt legetøj - men ikke helt gratis - og heller ikke for begyndere. Væsentligt, hvis man skal skrive programmer, der kan kommunikere med f.eks. en TLC - men heldigvis behøver man ikke skrive den slags programmer selv, det er der som regel andre, der har gjort for en på forhånd. Disse "andre" har så nok haft adgang til en protokol analysator, men når først de har fået deres program færdigt, kan de dele programmet med resten af verden, der så slipper for at skulle bruge og forstå den lidt mere avancerede teknik. Det er et af de steder, hvor Arduinoen er rigtig stærk, for der findes en masse færdige programstumper til den - og jo mere populær, den bliver, jo flere programmer kommer der.

Når man skriver kode til at kommunikere med f.eks. et TLC kredsløb, vil man normalt gemme denne kode i et bibliotek - eller library på engelsk - så man kan genbruge koden i andre programmer. Det er også tilfældet her, så det næste, vi skal se på, er biblioteket.

Biblioteket

Det kan være nemt at arbejde med en Arduino - det håber jeg, jeg har fået demonstreret tydeligt nok i de tidligere indlæg.

Men det er ikke alt, der er nemt: At lave den nødvendige kode til en kreds som TLC 5940 hører til i den mere krævende del, da der er en masse timing, der skal være på plads. Jeg kommer tilbage til det senere, men en processor som ATMega, der sidder på Arduinoen, har så få ressourcer, at det godt kan være et større puslespil at få det hele til at gå op i en højere enhed. Det vil derfor være en stor fordel, hvis man kan lave det krævende arbejde een gang for alle, og så gemme det til genbrug senere hen.

Det kan man også med Arduinoen: Mere præcist ved at skrive et bibliotek. Arduinoen har sit eget programmeringssprog, der minder om programmeringssproget C, men i virkeligheden er skabt ud fra sproget Processing. Sproget er simpelt og let at lære, og man kan lave rigtigt mange gode programmer med det; men enkelhed har sin pris, så hvis man skal lave noget avanceret, er sproget ikke det bedste. I Arduino verdenen har man klaret dette ved at beslutte, at biblioteker skal skrives i C eller C++ i stedet for Arduinos eget sprog.

Et bibliotek tjener derfor to formål: Det giver mulighed for at genbruge kode, og det "oversætter" kompliceret kode fra C/C++ til Arduinos eget, simplere sprog. Er man absolut begynder, skal man nok ikke kaste sig ud i at skrive biblioteker, men har man blot lidt programmeringserfaring, er det nu ikke så svært. Der findes en udmærket begyndervejledning på Arduinos hjemmeside.

I det aktuelle tilfælde skal vi bruge et bibliotek til vores TLC 5940: Der findes flere forskellige, men et af dem har "officiel" support fra Arduino projektet, så det er det, vi bruger her. Man kan finde frem til det via Arduinos hjemmeside, men vi kan også gå direkte til det, da det ligger på Googles sider for softwareudvikling. Her kan man downloade den nyeste version

Når man har hentet biblioteket, skal det installeres. Det kan gøres på to måder: Man kan blande det sammen med Arduino grundkoden, men det har den ulempe, at man så skal huske at reinstallere det, hver gang man opdaterer Arduino koden til en nyere version. Der findes derfor en anden måde, der er "den rigtige": Man bør installere biblioteket i den Arduino struktur, programmet har oprettet i ens hjemmemappe. Placeringen varierer fra computer til computer, men på nyere Windows versioner vil man finde en Arduino mappe i mappen Dokumenter - for en dansk Windows svarer det typisk til stien C:\Brugere\<brugernavn>\Dokumenter\Arduino. I Arduino mappen finder man en undermappe med navnet libraries, og her skal man pakke zip-filen ud, man downloadede ovenfor.

Har man gjort det rigtigt, vil man kunne finde det inde i Arduino udviklingsmiljøet:

Billede

Herefter vil man kunne bruge det i sine Arduino programmer ved at importere det med linien:

#include "Tlc5940.h"

Og så er det egentligt bare om at programmere løs.

Problemer

Men træerne vokser jo sjældent ind i himlen, og det er heller ikke tilfældet her: Selvom ovenstående fungerer fantastisk og er meget nemt at have med at gøre, er det dog ikke helt problemfrit. Først og fremmest bør man naturligvis læse dokumentationen for biblioteket: Den findes somme tider i løs form, men som regel er man nødt til at læse i selve koden. Vi er trods alt i Open Source verdenen, og dokumentation er sjældent det, udviklerne bruger mest tid på. Er koden lavet af en ordentlig programmør, vil den til gengæld indeholder en masse kommentarer, der forklarer, hvordan man skal bruge koden. Det er heldigvis også tilfældet her.

Er koden og kommentarerne lavet på den rigtige måde, kan man lave en ret fin dokumentation fuldautomatisk - f.eks. ved brug af et program som Doxygen. Og det er præcist, hvad man har gjort for dette bibliotek: Doxygen dokumentationen kan findes her.

Når man har læst dokumentationen, ved man også, at man skal konfigurere biblioteket ved at rette i filen tlc_config.h, som man finder i mappen med bibliotekets kode. Der er mange ting, der kan konfigureres, men specielt een parameter, der er vigtig: NUM_TLCS, der fortæller, hvor mange TLC kredse, man har koblet sammen. Dokumentationen er dog forældet, for den gør opmærksom på, at man skal huske at slette Tlc5940.o filen, hver gang man retter i tlc_config.h - men det behøves ikke længere: Nyere versioner af Arduino miljøet gør det automatisk, når det er nødvendigt.

/* ------------------------ START EDITING HERE ----------------------------- */
 
/** Number of TLCs daisy-chained.  To daisy-chain, attach the SOUT (TLC pin 17)
    of the first TLC to the SIN (TLC pin 26) of the next.  The rest of the pins
    are attached normally.
    \note Each TLC needs it's own IREF resistor */
#define NUM_TLCS    2
 
/** Determines how data should be transfered to the TLCs.  Bit-banging can use
    any two i/o pins, but the hardware SPI is faster.
    - Bit-Bang = TLC_BITBANG
    - Hardware SPI = TLC_SPI (default) */
#define DATA_TRANSFER_MODE    TLC_SPI
 
/* This include is down here because the files it includes needs the data
   transfer mode */
#include "pinouts/chip_includes.h"

Denne løsning kan virke ganske tilforladelig, men er i virkeligheden et problem. Opdelingen, vi lavede ovenfor, var ganske fornuftig: Vi har en mappe til Arduino grundkoden, der bliver opdateret med få måneders mellemrum og derfor bør opdateres tit. Og vi har en anden mappe i vores hjemmemappe, hvor vi kan placere ekstra biblioteker, som vi enten har fundet ude på nettet eller selv skrevet. På den måde kan vi blot downloade en ny version af Arduino grundkoden og slette den gamle - uden at miste al den ekstra kode, vi måtte have installeret ved siden af. Det er god stil for programmører.

Men det er ikke god stil, at vi skal rette i selve bibliotekets kode for lave almindelig, dagligdags konfiguration. Denne konfiguration bør gemmes sammen med det program, der bruger biblioteket - ikke sammen med selve biblioteket. Hvad nu, hvis vi skriver fire forskellige programmer, der alle bruger TLC biblioteket, men har brug for fire forskellige konfigurationer? Jeg kunne f.eks. sagtens forestille mig, at jeg vil have et stærkt varierende antal TLC kredse på mine forskellige Arduinoer. Som biblioteket er skabt nu, vil det ikke være tilstrækkeligt at åbne programmet og compile det - jeg vil hver gang skulle huske at rette tlc_config.h også, så den passer til det aktuelle program. Det er noget rigtig snavs, men desværre noget af det, man somme tider støder på, når man bruger andre folks software. Nej, det er bestemt ikke kun noget, man finder i Open Source programmer - her kan man jo relativt nemt gøre noget ved det, det plejer faktisk at være værre i kommerciel software.

Der er også et andet problem, der dog ikke er specielt for dette bibliotek: Jeg har sagt det mange gange før, og jeg kommer helt sikkert til at gentage det mange gange endnu - Arduinoen har ikke så mange ressourcer, så man skal passe godt på dem. Hvis man bruger TLC biblioteket uden videre dikkedarer - og det burde man kunne - skal man forbinde TLC kredsen til bestemte ben på Arduinoen - i anden del viste jeg hvordan. Det kan virke ganske tilforladeligt, men kan hurtigt blive et problem: Hvad nu, hvis vi også gerne vil bruge et bibliotek til at lade vores Arduino kommunikere med resten af modelbanen via DCC? Der findes et færdigt bibliotek også til dette formål - men hvad nu, hvis dette bibliotek gerne vil bruge nogle af de samme ben, som dem, vi allerede har brugt til TLC biblioteket? Forhåbentligt kan man så flytte nogle af funktionerne til andre ben - og det er bl.a. netop det, man kan i tlc_config.h.

Det er desværre bare ikke spor let: Jeg har tidligere henvist til databladet for ATMega processoren, og de, der har set på det, har nok konstateret, at det er - ja, omfattende. Der er meget at lære, og det kan være svært at bevare overblikket. Uden at gå i detaljer vil jeg kort prøve at forklare nogle af problemerne: Når man skal arbejde med følsom timing som her ved TLC kredsløbet, er man nødt til at sikre sig, at processoren ikke bliver forstyrret midt i en kommunikation, der i så fald vil falde sammen, fordi timingen bliver forkert. Det kan man gøre ved at bruge timere og interrupts, men der er ikke mange af dem, så man får hurtigt problemer, hvis man bruger to biblioteker, der begge forventer at kunne "eje" en timer eller et interrupt.

ATMegaen har ikke mange af hver, og hvad værre er, de er ofte bundet til bestemte porte på processoren. Det betyder, at selvom man kan anvende alle 14 digital porte som simple ind- eller udgange, kan man ikke bare flytte en funktion, der bruger en timer, fra en port til en anden. Timeren fungerer som regel kun på en eller to af de 14 porte. Der er mange af den slags fælder, når man bevæger sig dybere ind i teknologien. Jeg vil dog gerne understrege, at det på ingen måde har noget med ATMega processoren eller Arduino at gøre - det er begrænsninger, der gælder for alle denne slags små processorer. Arduinoen er stadig genial, og ATMega er nok verdens bedste processor af sin art.

Konklusion

Jo mere, jeg roder med disse ting, jo mere bliver jeg overbevist om, at vejen frem for mig ikke er at bruge en masse af de standard biblioteker, man kan finde rundt om på nettet. De er fantastiske til små projekter og til at komme i gang, men de løsninger, jeg gerne vil lave, kommer hurtigt til kort, når man tager ovenstående problemer i betragtning. Jeg har derfor besluttet, at jeg vil lave mit eget bibliotek, der er specielt rettet mod mine behov. Det betyder ikke, at jeg ikke vil bruge standard biblioteker, når jeg kan slippe afsted med det - men jeg forventer mange problemer på længere sigt, når jeg vil kombinere mine behov i en samlet løsning, så vejen frem for mig vil nok være et nyt bibliotek.

Heldigvis behøver jeg ikke starte forfra: Det er jo Open Source software, vi arbejder med, så jeg kan uhæmmet hugge velfungerende kode fra andre biblioteker og putte den ind i mit eget. Da jeg samtidigt ikke har de store problemer med at skrive C++ programmer, er den for mig lige til. Med den interesse, andre har vist, håber jeg, vi kan gøre det til et fælles projekt at lave et bibliotek til dansk modeltog? Det kunne indeholde kode for styring af alle gængse, danske signaler, servostyring til sporskifter, kode, der hjælper med at lave kontrolpulte og sætte sporveje - og så naturligvis al det nødvendige interface som DCC og S88. Ved at samle det hele i eet bibliotek, vil vi kunne sikre, at det for begynderen vil være let at bruge - og at man slipper for de konflikter, man ellers næsten uvægerligt vil møde, når man begynder at kombinere biblioteker.

Vi kommer nok til at prøve nogle grænser af undervejs - men det er jo netop en del af charmen, er det ikke?

Praktisk kode

Helt mod alle regler vil jeg her - efter konklusionen - fortsætte med nogle eksempler på praktisk kode, som jeg lovede i forrige del: Det skal her først og fremmest dreje sig om, hvordan man styrer mere end een lysdiode - det var jo der, jeg slap sidst.

Hvis man har hængt på så langt som hertil, er man nok ikke helt uvant med programmering, så jeg vil derfor skære lidt ned på begynderinformationen; går jeg for hurtigt frem, er man naturligvis velkommen til at stille spørgsmål.

Al den kode, jeg har vist indtil nu, har været koncentreret om et formål: At få en lysdiode til at blinke. Med understregning af een. Det er naturligvis ikke nok, så denne gang vil jeg vise, hvordan man kan håndtere flere lysdioder - gerne op til de 224 dioder, TLC siger, den kan håndtere. Den opmærksomme læser vil her straks falde over en ændret sprogbrug: Ifølge databladet for TLC 5940 kan man maksimalt koble 14 TLCer sammen, og det giver et maksimum på 224 dioder. Men jeg har i mellemtiden læst om andre på nettet, der har koblet endnu flere sammen, så 14 lader til at være sat som sikkerhedsmargen, hvor TI mere eller mindre garanterer, det virker - i praksis kan man godt trække mere.

Kommer man op i den størrelse, skal man dog nok ikke koble TLCerne sammen med en stump fladkabel, som jeg tidligere har vist: Så mange dioder vil trække alt for megen strøm, så det kan fladkablet ikke klare. I stedet skal man nøjes med at daisy chaine styresignalerne, og så koble 0V og 5V direkte til strømforsyningen.

Tilbage til koden: Kender man lidt til programmering, ved man også, at når man skal klare mange stort set ens opgaver, vil man normalt lave en loop. Det kan gøres på mange måder, men den klassiske low-level måde er en for loop, hvor man bruger en variabel til at tælle, hvor mange gange man har udført en given opgave. Det gør vi også her: Vi vil naturligvis gerne lave en loop over antallet af udgange, og det er som bekendt 16 pr. TLC kreds. Ovenfor fandt vi ud af, at vi skulle sætte værdien af NUM_TLCS til antallet af kredse: Så antallet af udgange må nødvendigvis ende med at blive 16 * NUM_TLCS.

for (int i = 0; i < NUM_TLCS * 16; i++) {
  // Kode der skal gentages for alle porte
}

Det var vores loop. Men inde i loop'en skal der naturligvis ske noget, og det noget handler om at styre vores dioder. Går man tilbage til de eksempler, jeg tidligere har vist, har vi efterhånden mange muligheder for at justere på dioderne - min. lysstyrke, max. lysstyrke, blink ja/nej, blink frekvens, blødt eller hårdt blink osv. osv. Det vil derfor være smart, hvis vi kunne lave en container af en eller anden form, der kunne indeholde al den information om hver diode, vi ønsker at bruge.

Det kan vi heldigvis godt - i Arduino sproget hedder det en struct: Den er sjovt nok ikke dokumenteret i Arduinos beskrivelse af programmeringssproget, men jeg har set mange andre bruge den, og den virker fint. I praksis er den kopieret direkte fra C. I andre programmeringssprog kaldes den en record, under alle omstændigheder er der netop tale om en container, der kan indeholde en masse variable:

struct Led {
  int lowValue;
  int highValue;
  boolean state;
  int currentValue;
  boolean blink;
  int type;
};

Nu mangler vi så et sted at gemme en masse structs - og det kan passende være et array:

Led leds[NUM_TLCS * 16];

Men hov, vi har efterhånden lavet regnestykket 16 * NUM_TLCS mange gange: Det er ikke særlig effektivt, så lad os i stedet definere en konstant, der indeholder resultatet - så slipper vi for at skulle lave beregningen igen og igen:

const int numPorts = 16 * NUM_TLCS;

Den samlede, nye kode kan nu se således ud:

const int numPorts = 16 * NUM_TLCS;
const int frequency = 700;
 
const int BULB = 0;
const int NEON = 1;
const int LED = 2;
 
unsigned long lastSwitch = 0;
 
struct Led {
  int lowValue;
  int highValue;
  boolean state;
  int currentValue;
  boolean blink;
  int type;
};
 
Led leds[numPorts];

Vi skal nu give alle vore mange udgang - dvs. lysdioder - nogle standardværdier: Computere tænker jo ikke selv, så de har ingen chance for at vide, hvilke værdier, vi forventer at de forskellige variable har, når vi starter programmet. En oplagt måde kunne være at sætte værdierne i setup() funktionen:

void setup() {
  Tlc.init();
 
  for (int i = 0; i < numPorts; i++) {
    leds[i].lowValue = 0;
    leds[i].highValue = 4095;
    leds[i].state = LOW;
    leds[i].currentValue = 0;
    leds[i].blink = false;
    leds[i].type = BULB;
  }
 
  leds[5].blink = true;
  leds[21].blink = true;
  leds[14].type = LED;
  leds[14].highValue = 256;
}

Her looper vi over samtlige structs i vores array, og giver alle variable samme værdi. Sandsynligheden taler for, at det ikke er helt galt: Min. lysstyrke sættes til 0, max. til max. osv. Når alle porte så er sat til en fornuftig standardværdi, kan vi efterfølgende sætte specialværdier for nogle af dioderne:

  leds[5].blink = true;
  leds[21].blink = true;
  leds[14].type = LED;
  leds[14].highValue = 256;

Det er her, vi endeligt når til det punkt, hvor vi styrer alle dioderne individuelt. Der kan blive mange linier, hvis der er mange dioder - men det er nok så langt, vi kan trække det grundlæggende Arduino sprog. Lavede vi i stedet et bibliotek til formålet, ville vi kunne bruge alle de mange ekstra funktioner i C++, og det ville kunne spare en del kode - mere om det en anden gang. Med lige netop TLC kredsen har man også en anden mulighed: Man kan så og sige brænde max. lysstyrke ind i den enkelte TLC kreds ved at bruge det, de kalder gray scale correction - så behøver man ikke bruge Arduino kode på det, men kan blot gå efter fuld styrke på alle udgange altid, og så i stedet gemme de individuelle værdier i TLCen. Det er også lidt for langhåret til, at jeg vil komme nærmere ind på det her.

Vi er ved at have alle rammerne klar - så nu skal der blot laves lidt kode, der udnytter dem:

#include "Tlc5940.h"
 
const int numPorts = 16 * NUM_TLCS;
const int frequency = 700;
 
const int BULB = 0;
const int NEON = 1;
const int LED = 2;
 
unsigned long lastSwitch = 0;
 
struct Led {
  int lowValue;
  int highValue;
  boolean state;
  int currentValue;
  boolean blink;
  int type;
};
 
Led leds[numPorts];
 
 
void setup() {
  Tlc.init();
 
  for (int i = 0; i < numPorts; i++) {
    leds[i].lowValue = 0;
    leds[i].highValue = 4095;
    leds[i].state = LOW;
    leds[i].currentValue = 0;
    leds[i].blink = false;
    leds[i].type = BULB;
  }
 
  leds[5].blink = true;
  leds[21].blink = true;
  leds[14].type = LED;
  leds[14].highValue = 256;
}
 
 
 
void loop() {
  if (millis() > lastSwitch + frequency) {
    lastSwitch = millis();
    for (int i = 0; i < numPorts; i++) {
      if (leds[i].blink) {
        leds[i].state = !leds[i].state;
      }
    }
  }
 
  for (int i = 0; i < numPorts; i++) {
    if (leds[i].state == LOW && leds[i].currentValue > leds[i].lowValue) {
      if (leds[i].type == BULB) {
        leds[i].currentValue = leds[i].currentValue >> 1;
      } else {
        leds[i].currentValue = leds[i].lowValue;
      }
    }
 
    if (leds[i].state == HIGH && leds[i].currentValue < leds[i].highValue) {
      if (leds[i].type == BULB) {
        leds[i].currentValue = (leds[i].currentValue << 2) + 3;
      } else {
        leds[i].currentValue = leds[i].highValue;
      }
    }
 
    Tlc.set(i, leds[i].currentValue);
  }
 
  Tlc.update();
 
  delay(50);
}

Så enkelt er det: Denne kode kan styre lige så mange dioder, man har lyst til at slutte til. Der mangler kun een ting: Vi kan stadig ikke ændre status på dioderne - vi kan ikke tænde og slukke dem - vi mangler kort sagt input. Det vil være emnet for næste - og nok sidste - del.

__________________

MVH
Lars

Like 1 kan lide
Top

Picture

Cas

It

Indlæg: 2

PB

Hej Lars

Er du nogensinde kommet i mål med din lysstyring via Arduino?

Jeg har arbejdet lidt med din kode og gjort den mere simpel og har fået noget til at virke. Det har været svært at tidsstyre så man f.eks. har en døgnrytme på 15 min.

Jeg forsøgte at lave noget lys som lignede noget flimrende tv lysudstråling. Men det gik helt galt i forhold til at styre tidsrytmen.

Mit største problem er dog at jeg nu har koblet nogle Viessmann gadelygter med lysdioder til. Men det virker slet ikke og det hele står og blinker ukontrolleret.

Har du nogle erfaringer med dette? Jeg forstår ikke hvorfor det ikke virker. Så snart jeg tager gadelygterne fra så virker det hele igen for de alm. Lysdioder.

mvh Cas

Like 0 kan lide
Top

Picture

Lars Skjærlund

Rødovre

Webmaster

Indlæg: 3.465

PB  Blog  Hjemmeside

Cas skrev:
Er du nogensinde kommet i mål med din lysstyring via Arduino?

Det kommer an på, hvad du mener med "i mål": Jeg har desværre ikke noget fast anlæg at montere det på i øjeblikket, men jeg har monteret dioder i en række huse, og systemet virker fint i en testopstilling.

Citat:
Jeg har arbejdet lidt med din kode og gjort den mere simpel og har fået noget til at virke. Det har været svært at tidsstyre så man f.eks. har en døgnrytme på 15 min.

Hvorfor? Hvad har drillet dig?

Citat:
Jeg forsøgte at lave noget lys som lignede noget flimrende tv lysudstråling. Men det gik helt galt i forhold til at styre tidsrytmen.

Det tror jeg gerne på - det er lidt af en udfordring; jeg har selv tænkt mig at lege med det på et tidspunkt, men har ikke fået det gjort endnu.

Citat:
Mit største problem er dog at jeg nu har koblet nogle Viessmann gadelygter med lysdioder til. Men det virker slet ikke og det hele står og blinker ukontrolleret.

Jeg kender ikke Viessmanns produkter - og deres hjemmeside er ikke videre informativ, bortset fra at de siger, man skal bruge deres egen strømforsyning. Du tror ikke, der er formodstande i lygterne? 5940 styrer jo selv strømmen, så det vil være en alvorlig fejl at sætte en formodstand på - jeg kunne forestille mig, det ville give resultater som dem, du snakker om.

__________________

MVH
Lars

Like 0 kan lide
Top

Picture

Cas

It

Indlæg: 2

PB

Hej Lars

Jeg tror det som drillede mig mest var at det var meget svært at styre tiden specielt fordi koden bruger delay kommandoen. Dermed blev der pauser i gennemløbene som gjorde det svært at styre tiden, så alle cycluser havde samme længde.

Det var også de problemer jeg løb ind i da jeg forsøgte med at simulere TV lys. Det påvirkede hurtigt de øvrige LEDs tidsmæssigt.

Der hvor jeg har gjort koden simpel er at jeg har droppet de rimelig avancerede tænd og sluk rutiner. Jeg har valgt at der tændes eller slukkes relativ kontant. Jeg synes ikke det gør noget, da det meste af lyset er inde i husene, så man ser alligevel ikke de fine detaljer. Tænd og Sluk har været godt nok.

I forhold til Viessmann, så tror jeg desværre du har ret. Det må være det som er galt. Dog er de gadelamper meget meget små, så det så ikke ud til at der var andet end lysdioden, men det kan da godt være at der har været indbygget en meget lille modstand sammen med dioden. Der sidder ikke nogen normal modstand på ledningen. Men jeg leger videre med det, indtil at jeg finder ud af hvorfor.

Du får lige min kode, så du kan se hvad jeg har lavet om. Det kunne jo også være at andre kunne bruge det.

#include "Tlc5940.h"

// Antal porte i alt på TLC
const int numPorts = 16 * NUM_TLCS;

// Størrelse på døgncyclus
const int frequency = 900000; //15 min
//const int frequency = 10000; //10 sek
//const int frequency = 20000; //20 sek

unsigned long lastSwitch = 0;
boolean night = true;
 
 // Datastruktur for en LED
struct Led {
  int lowValue; // Lavste lysstyrke
  int highValue; // Højeste lysstyrke
  int currentValue; // Aktuel lysstyrke
  int LightTurnedOn; //0=Never, 1=Always, 2=Night, 3=Day
  int StartDelay; // Forsinket start fra overgang mellem nat/dag
  int EndDelay; // Forsinket slut fra overgang mellem nat/dag
  unsigned long lastSwitch; // Hvornår blev der sidst skiftet
  int TVCount; // Forsøg på TV flimmer - ikke i brug
};

// Byg et array for LEDs
Led leds[numPorts];

// Døgncyclus skifte
void daynightswitch() {
  if (millis() > lastSwitch + frequency) { // er det tid til at skifte til mellem dag og nat eller modsat?
    lastSwitch = millis(); // Der skiftes mellem nat og dag eller modsat
    night = !night; // Hvis nat så skift til dag og omvendt
  }
}

// Skift tilstand for en LED
void LEDswitch() {
  for (int i = 0; i < numPorts; i++) { // Gå alle LEDs igennem
  // LigthTurnedOn  har værdi for hvornår en LED skal være tændt. Værdier: 0=Never, 1=Always, 2=Night, 3=Day, 4=TV
    if (leds[i].LightTurnedOn == 2) { // Night
      if (night == true) { // Er det nat?
        if (millis() >= lastSwitch + leds[i].StartDelay) { // Er det tid til at skifte LED tilstand? 
          leds[i].currentValue = leds[i].highValue; // Tænd LED
        }
      }
    }
    if (leds[i].LightTurnedOn == 3) { // Day
      if (night == false) { // Er det dag?
        if (millis() >= lastSwitch + leds[i].StartDelay) { // Er det tid til at skifte LED tilstand?
          leds[i].currentValue = leds[i].highValue; // Tænd LED
        }
      }
    }
    if ((leds[i].LightTurnedOn == 2) || (leds[i].LightTurnedOn == 3)) { // Night eller Day
      if (millis() >= lastSwitch + frequency + leds[i].EndDelay){ // Er det tid til at skifte LED tilstand?
        leds[i].currentValue = leds[i].lowValue; // Sluk LED
      }
    }

//    if (leds[i].LightTurnedOn == 4) { // TV
//      if (leds[i].currentValue == leds[i].lowValue) {
//        if (leds[i].TVCount == 4) {
//          leds[i].currentValue = rand() % leds[i].highValue; // leds[i].highValue; // rand() % leds[i].highValue;
//          leds[i].TVCount = 0;
//        }
//        else {
//          leds[i].TVCount = leds[i].TVCount + 1;
//        }
//      }
//      else {
//        leds[i].currentValue = leds[i].lowValue;
//      }
//    } 

    Tlc.set(i, leds[i].currentValue); // Sæt TLC værdi til LED værdi
  }
  Tlc.update(); // Opdater TLC
}

// Procedure til at indsætte data i en LED datastruktur 
void setLED(int LEDnr, int setLowValue, int setHighValue, int setLightTurnedOn, int setStartDelay, int setEndDelay) {
  leds[LEDnr].lowValue = setLowValue; // Laveste lysstyrke
  leds[LEDnr].highValue = setHighValue; // Højeste lysstyrke
  leds[LEDnr].LightTurnedOn = setLightTurnedOn; // Hvornår skal LED lyse
  leds[LEDnr].StartDelay = setStartDelay; // Er der ved cyclusskifte forsinkelse på skiftet når LED tændes?
  leds[LEDnr].EndDelay = setEndDelay; // Er der ved cyclusskifte forskinkelse på skiftet når LED slukkes?
 // leds[LEDnr].TVCount = 0;
  //0=Never, 1=Always, 2=Night, 3=Day, 4=TV
  if (setLightTurnedOn == Innocent { // Hvis lyset aldrig skal tændes
    leds[LEDnr].currentValue = setLowValue; // Sæt til laveste lysstyrke
  }
  if (setLightTurnedOn == 1) { // Hvis lyset skal være tændt hele tiden
    leds[LEDnr].currentValue = setHighValue; // Sæt til højeste lysstyrke
  }
}

// Lav det initielle setup (Køres kun en gang ved opstart) 
void setup() {
  Tlc.init(); // Initielliser TLC
 
 // Nulstil Datastruktur for LEDs
  for (int i = 0; i < numPorts; i++) {
    leds[i].lowValue = 0;
    leds[i].highValue = 4095;
    leds[i].LightTurnedOn = 0;
    leds[i].currentValue = 0;
    leds[i].StartDelay = 0;
    leds[i].EndDelay = 0;
    leds[i].TVCount = 0;
  }
 
 // Sæt initielle værdier for LEDS her.
 // setLED(int LEDnr, int setLowValue, int setHighValue, setLightTurnedOn, setStartDelay, setEndDelay)
 //4. værdi == 0=Never, 1=Always, 2=Night, 3=Day, 4=TV
 // 5.+6. værdi == Husk at 1 sek = 1000, 10 sek = 10000

// Gruppe 1 - Nat

  setLED(3,0,4095,2,0,0); // 3. LED
  setLED(4,0,4095,2,0,0); // 4. LED
  setLED(5,0,4095,2,0,0); // 5. LED

// Gruppe 2 - Nat
 
// Gruppe 3 - Nat

// Dagtid

// Altid

  setLED(0,0,4095,1,0,0); // 1. LED
  setLED(1,0,4095,1,0,0); // 2. LED
  setLED(2,0,4095,1,0,0); // 3. LED

// TV

 
}

// Uendelig lykke som køres så længe programmet kører.
void loop() {
  daynightswitch(); // Skift mellem nat/dag
  LEDswitch(); // Skift LEDs
}

mvh Cas

Like 0 kan lide
Top

Picture

futlgb

Elektriker

Indlæg: 84

PB  Hjemmeside

  Hej Lars

Fed serie om Arduino, du har skruet sammen :)  Jeg prøver at lære mere....

Det du skriver her:

"Med den interesse, andre har vist, håber jeg, vi kan gøre det til et fælles projekt at lave et bibliotek til dansk modeltog? Det kunne indeholde kode for styring af alle gængse, danske signaler, servostyring til sporskifter, kode, der hjælper med at lave kontrolpulte og sætte sporveje - og så naturligvis al det nødvendige interface som DCC og S88. Ved at samle det hele i eet bibliotek, vil vi kunne sikre, at det for begynderen vil være let at bruge - og at man slipper for de konflikter, man ellers næsten uvægerligt vil møde, når man begynder at kombinere biblioteker."

Glæder jeg mig meget til, jeg håber, at du kan få plads til at få RocRail osv... H)

Med venlig fut hilsen

Futlgb

__________________

Med venlig
Fut fut
Futlgb
Til store tog uden lup! www.dgmf.dk
1:22,5 + Lenz 3,6 + WinDigiPet 9 + Lenz trådløse telefoner + Massoth Navigatorer + Mobiltelefonerne
Og for børnene: Roco`s gamle mus

Like 0 kan lide
Top

Kommentarvisning

Vælg din foretrukne kommentarvisning og klik på "Gem indstillinger" for at aktivere dit valg.