Tag Archives: easylight

EasyLight: code source disponible et gratuit

easylight

Après de nombreux commentaires pour obtenir le code source et les exécutables de mon système EasyLight, j’ai décidé de publier les sources DotNet et Arduino sur mon GitHub.

Le projet fonctionne parfaitement et est dans un état stable, il ne reste plus qu’à améliorer les performances d’analyse de l’image.

Le code arduino permet de faire le lien entre easylight et vos leds.

Je vous invite donc à aller sur ma page consacrée à EasyLight et j’espère que ce système Ambilight Clone vous satisfera.

Comprendre le multiplexeur PWM TLC5940

TLC5940 pins

A quoi sert le composant TLC5940 ?

Le composant TLC5940 est un multiplexeur PWM (Pulse Width Modulation) qui peut gérer jusqu’à 16 canaux PWM. Ainsi avec seulement 3 pins de votre Arduino vous allez pouvoir utiliser jusqu’à 16 canaux PWM.

Tout comme le composant Shift Register 74HC595, le circuit intégré TLC5940 peut être connecté en cascade, ce qui permet d’étendre considérablement le nombre de canaux PWM utilisables.

Cela va donc être parfaitement adapté à mon projet EasyLight pour multiplexer des leds RGB.

Comment utiliser le composant TLC5940 avec Arduino ?

Il existe une bibliothèque nommée TLC5940Arduino qui permet de contrôler simplement un ou plusieurs composants TLC5940 directement avec Arduino.

La librairie TLC5940Arduino peut être téléchargée sur ce site.

 Comment fonctionne le composant TLC5940 ?

Le plus simple pour comprendre le fonctionnement du composant TLC5940 est de lire la fiche technique TLC5940.

Voici le schéma du composant TLC5940:

TLC5940 pins

 

Comme on peut le voir, on a bien nos 16 sorties PWM (de OUT0 à OUT15). Voici le détail des autres pins:

  • XERR: open collector, wire or-ed output that lets you know a TLC5940 is over heated or has a burnt out LED. We can ignore this as it will always be on unless you have current using elements on all of the outputs.
  • SOUT: serial data out from the TLC5940. Unless you wish to try to read the error bits you do not need this to come to the Arduino. If you have more than one TLC5940 this is the line you daisy chain to the SIN of the next package.
  • DCPRG: this selects the source of the current limiter register, you could just tie it high.
  • XLAT: you will need this to latch data after shifting.
  • SCLK: you will need this to shift data.
  • SIN: serial in to TLC5940, this is the output from the Arduino.
  • VPRG: you need this to select either the current limit registers or the duty cycle registers for writing.
  • GSCLK: this is the clock for the PWM. We will reprogram TIMER2 in the Arduino to make this signal. That will cost us the native PWM on that timer, digital 11 on a mega8, 11 and 3 on a mega168.
  • BLANK: this marks the end of a PWM cycle in addition to blanking the output. We will reprogram TIMER1 to generate this signal. That will cost us the native PWMs on digital 9 and digital 10. (Tie a real, physical pull-up resistor on this line to keep things blanked while your Arduino boots. Depending on your hardware, it is possible that the TLC5940 would come up in a configuration that would dissipate too much power.)

 La broche 20 du TLC5940: IREF

Sur la broche numérotée 20 du composant TLC5940, il faut brancher une résistance. Pour trouver la valeur de la résistance à utiliser, il suffit d’appliquer la formule suivante:

valeur de la résistance = 39.06 / intensité de la LED

Ainsi dans le cas où l’intensité de vos leds est de 30mA, vous devrez utiliser une résistance de 1302 ohms. On utilisera donc une résistance de 2K ohms.

Si vous utilisez des leds avec des intensités différentes, prenez l’intensité la plus faible comme base.

Comment brancher le composant TLC5940?

Voici le schéma électronique de branchement du composant TLC5940:

tlc5940 schema electronique

Et le schéma de connection e ntre un Arduino et un composant TLC5940:

tlc5940 schema arduino

Comment brancher le composant TLC5940 en cascade ?

Un des intérêts à utiliser le multiplexeur TLC5940 est qu’on peut chainer plusieurs composants ensemble afin d’augmenter de nombre de canaux PWM. On dit alors qu’on met les composants TLC5940 en cascade.

Voici le schéma électronique:

tlc5940 schema electronique cascade

Et voici le schéma de connection entre plusieurs multiplexeur TLC5940 en cascade et un Arduino:

tlc5940 schema arduino cascade

Lorsque l’on utilise les multiplexeurs TLC5940 en cascade, il faut penser à modifier le fichier tlc_config.h de la librairie TLC5940Arduino:

Changez la ligne #define NUM_TLCS 1 pour #define NUM_TLCS n (ou n est le nombre de TLC5940 que vous avez chainé).

Conclusion sur le multiplexeur TLC5940

Les multiplexeurs TLC5940 sont des composants qui ne coûtent pas cher et qui permettent d’augmenter le nombre de canaux PWM de votre Arduino.

Ils sont relativement pratique si vous devez utiliser des leds RGB par exemple. Le schéma suivant illustre parfaitement leur utilisation:

tlc5940 schema led rgb

Attention toutefois lorsque vous utilisez des leds RGB avec le multiplexeur TLC5940 à verifier que vos leds soient à anode commune:

led rgb common anode

Intéraction entre Dotnet et Arduino

arduino with dotnet

Pour mon projet EasyLight, je vais devoir faire interagir un programme développé en DotNet avec un Arduino Uno.

Après avoir parcouru plusieurs forums, j’ai noté qu’il existait plusieurs possibilités:

  • utiliser FirmData pour faire communiquer Arduino et votre programme DotNet
  • faire du code pour connecter votre logiciel DotNet au port COM de votre Arduino

J’ai donc décidé de partir sur cette seconde option en développant 2 parties de code:

  • une classe DotNet
  • un gabarit pour Arduino

Le code s’inspire de pas mal de samples trouvés ici et là sur le net. Je l’ai commenté et il est relativement simple à comprendre.

Voici la classe DotNet Arduino:

Public Class Arduino
 
#Region "Properties"
    Private WithEvents CurrentSerialPort As System.IO.Ports.SerialPort
    Private Property ComPort As String = "COM8"
    Private Property BaudRate As Integer = 9600
    Private WithEvents WatchDogTimer As New System.Timers.Timer(5000) With {.Enabled = True}
 
    Private Receiving As Boolean
    Private BufferPointer As Integer
    Private CBuffer(30) As Byte
#End Region
 
#Region "Event"
    Public Event WatchdogReceived() 'Fires when a watchdog message (heartbeat) is received
    Public Event ConnectionLost() 'Fires when there has not been a watchdog message for 5 seconds
    Public Event LogMessageReceived(ByVal Message As String) 'Gives a system message from the Arduino
#End Region
 
#Region "Constructeur"
    Public Sub New(ByVal _comport As String, ByVal _baudrate As Integer)
        Me.ComPort = Trim(_comport)
        Me.BaudRate = _baudrate
    End Sub
#End Region
 
#Region "Connection / Start / Stop"
 
    'Fonction qui démarre la connection
    Public Sub Start()
        Call StartCommunication()
    End Sub
 
    'Fonction qui stoppe la connection
    Public Sub [Stop]()
        CurrentSerialPort.Close()
        WatchDogTimer.Stop()
    End Sub
 
    'Fonction de connection au arduino
    Private Function StartCommunication() As Boolean
        Dim ret As Boolean = False
        Try
            Dim components As System.ComponentModel.IContainer = New System.ComponentModel.Container()
            CurrentSerialPort = New System.IO.Ports.SerialPort(components)
            CurrentSerialPort.PortName = _ComPort
            CurrentSerialPort.BaudRate = _BaudRate
            CurrentSerialPort.ReceivedBytesThreshold = 1
            CurrentSerialPort.Open()
 
            If Not CurrentSerialPort.IsOpen Then
                WriteLog("[ERROR]" & vbTab & "[" & System.Reflection.MethodBase.GetCurrentMethod.Name & "]" & vbTab & "Unable to open port com...")
                Return ret
            Else
                CurrentSerialPort.DtrEnable = True
                WriteLog("[INFO]" & vbTab & "[" & System.Reflection.MethodBase.GetCurrentMethod.Name & "]" & vbTab & "Serial port is open")
                System.Threading.Thread.Sleep(1000)
 
                'on envoie une commande pour nettoyer le buffer du arduino
                Dim Command As Byte() = {40, 0, 0, 0, 41, 0}
                Me.SendCommand(Command)
                Me.SendCommand(Command)
                WatchDogTimer.Start()
                ret = True
            End If
 
            'On ajoute un handler pour récupérer les infos envoyées par le arduino
            AddHandler CurrentSerialPort.DataReceived, AddressOf OnReceived
        Catch ex As Exception
            WriteLog("[ERROR]" & vbTab & "[" & System.Reflection.MethodBase.GetCurrentMethod.Name & "]" & vbTab & "Error opening port com...")
            ret = False
        End Try
        Return ret
    End Function
 
#End Region
 
#Region "Reception d'une commande"
    'Fonction de traitements des données reçues (données émises par le arduino
    Private Sub OnReceived(ByVal sender As Object, ByVal e As System.IO.Ports.SerialDataReceivedEventArgs)
        If Receiving = False Then
            Receiving = True
            Try
                Dim BytesRead As Integer
                While CurrentSerialPort.BytesToRead > 0
                    BytesRead = CurrentSerialPort.Read(CBuffer, BufferPointer, CurrentSerialPort.BytesToRead)
                    BufferPointer += BytesRead
                    While BufferPointer > 0
                        'on recherche le début de commande (
                        Dim CommandStart As Integer = -1
                        For i As Integer = 0 To BufferPointer
                            If CBuffer(i) = CByte(40) Then
                                CommandStart = i
                                Exit For
                            End If
                        Next
 
                        'si aucun debut de commande n'a été trouvé, on nettoie le buffer car la commande est invalide
                        If CommandStart = -1 Then
                            ClearCBuffer()
                        End If
 
                        'si la commande ne commence pas au premier byte alors on supprime les bytes avant la commande
                        If CommandStart > 0 Then
                            LeftShiftCBuffer(CommandStart)
                        End If
 
                        'a partir d'ici le buffer est clean
 
                        'on recherche la fin de commande )
                        Dim Commandend As Integer = 0
                        For i As Integer = 0 To BufferPointer
                            If CBuffer(i) = CByte(41) Then
                                Commandend = i
                                Exit For
                            End If
                        Next
 
                        'si la fin de commande a été trouvée alors on execute la commande
                        If Commandend > 0 Then
                            Dim CommandBytes(Commandend) As Byte
                            For i As Integer = 0 To Commandend
                                CommandBytes(i) = CBuffer(i)
                            Next
                            'on execute la commande
                            ProcessCommand(CommandBytes)
 
                            'on reset le buffer et le pointer
                            LeftShiftCBuffer(Commandend + 1)
                        Else
                            Exit While
                        End If
                    End While
                End While
            Catch ex As Exception
            End Try
            Receiving = False
        End If
    End Sub
 
    'Fonction qui nettoie le buffer de réception
    Private Sub ClearCBuffer()
        For i As Integer = 0 To CBuffer.Length - 1
            CBuffer(i) = 0
        Next
        BufferPointer = 0
    End Sub
 
    'Fonction qui shift le buffer vers la gauche
    Private Sub LeftShiftCBuffer(ByVal NrOfPlaces As Integer)
        For i As Integer = NrOfPlaces To CBuffer.Length - 1
            CBuffer(i - NrOfPlaces) = CBuffer(i)
        Next
        For i As Integer = CBuffer.Length - NrOfPlaces To CBuffer.Length - 1
            CBuffer(i) = 0
        Next
        BufferPointer -= NrOfPlaces
    End Sub
 
    'Fonction de traitement des commandes reçues
    Private Sub ProcessCommand(ByVal CommandBytes As Byte())
        Try
            If ((CommandBytes(0) = CByte(40)) And (CommandBytes(CommandBytes.Length - 1) = CByte(41))) Then 'toutes les commandes sont au format (cmd)
                Dim PType As Char = ChrW(CommandBytes(1))
                Select Case PType
                    Case CChar("S") 'Arduino send it started
                        RaiseEvent WatchdogReceived()
                        If Not IsNothing(WatchDogTimer) Then
                            WatchDogTimer.Stop()
                        End If
                        WatchDogTimer.Start()
 
                    Case CChar("W") 'Arduino send watchdog
                        RaiseEvent WatchdogReceived()
                        If Not IsNothing(WatchDogTimer) Then
                            WatchDogTimer.Stop()
                        End If
                        WatchDogTimer.Start()
 
                    Case Else
                        Dim CommandString As String = String.Empty
                        For i As Integer = 0 To CommandBytes.Length - 1
                            CommandString += CommandBytes(i).ToString + " "
                        Next
                        WriteLog("[MSG]" & vbTab & "[" & System.Reflection.MethodBase.GetCurrentMethod.Name & "]" & vbTab & CommandString)
                End Select
            Else
                Dim CommandString As String = String.Empty
                For i As Integer = 1 To CommandBytes.Length - 1
                    CommandString += CommandBytes(i).ToString
                Next
                WriteLog("[ERROR]" & vbTab & "[" & System.Reflection.MethodBase.GetCurrentMethod.Name & "]" & vbTab & "Bad command format received: " & CommandString)
            End If
        Catch ex As Exception
            WriteLog("[ERROR]" & vbTab & "[" & System.Reflection.MethodBase.GetCurrentMethod.Name & "]" & vbTab & "Bad command format received")
        End Try
    End Sub
#End Region
 
#Region "Emission d'une commande"
    'Fonction qui envoie une commande
    Public Sub SendCommand(ByVal Command As Byte())
        Try
            If Command.Length > 0 Then
                If CurrentSerialPort.IsOpen Then
                    CurrentSerialPort.Write(Command, 0, Command.Length - 1)
                End If
            End If
        Catch ex As Exception
        End Try
    End Sub
#End Region
 
#Region "Fonctions Diverses"
    'Fonction qui permet de savoir lorsqu'on n'a pas reçu de signal du arduino depuis 5 secondes
    Private Sub WatchdogTimerElaped(ByVal sender As Object, ByVal e As System.Timers.ElapsedEventArgs) Handles WatchDogTimer.Elapsed
        WriteLog("[ERROR]" & vbTab & "[" & System.Reflection.MethodBase.GetCurrentMethod.Name & "]" & vbTab & "Connection lost...")
        RaiseEvent ConnectionLost()
        If Not IsNothing(WatchDogTimer) Then
            WatchDogTimer.Stop()
        End If
    End Sub
 
    'Fonction de notification de message
    Private Sub WriteLog(ByVal Message As String)
        RaiseEvent LogMessageReceived(Message)
    End Sub
#End Region
 
End Class

Et voici le gabarit de sketch Arduino qu’il vous faudra compléter avec votre code :

// Interaction entre Arduino et DotNet
// (Ce code fonctionne avec la classe DotNet "Arduino" )
// (Ce code s'inspire de plusieurs projets libres)
// www.zem.fr
 
int pt = 0;  			        			//Pointeur courant
boolean cfound = false;		        		//Fin de commande trouvée
byte currentCmd[200];                     	//Buffer de commande
long previousMillis = 0;                	//Interval de verification du watchdog
long interval = 1000;                   	//Interval d'émission entre 2 watchdogs
 
#define SOC 40								//Byte de debut de commande
#define EOC 41								//Byte de fin de commande
 
void setup() {
	Serial.flush();
	Serial.begin(9600);
	delay(500);
	Serial.print("(S)");					//On envoie une commande pour notifier que l'arduino est démarré
}
 
void loop() {
 
	//STEP1: Lecture du Buffer
	int sa;
	byte bt;
	sa = Serial.available();	        	//On recupere les données du port serie
	if (sa > 0) {			        		//On ecrit le buffer dans une variable jusqu'à ce que l'on rencontre le caractère de fin de commande
		for (int i=0; i < sa; i++){
			bt = Serial.read();
			currentCmd[pt] = bt;
			pt++;
			if (bt == EOC) {
				cfound = true;
				break;						//Le caractere de fin a été trouvé, on pourra proceder à l'execution de la commande
			}
		} 
	}
 
	//STEP2: Execution d'une commande
	if (cfound) {
		if (int(stringIn[0]) == SOC) {		//On verifie que le premier caractère est le caractere de debut de commande
			RunCommand();
		}
		ResetCurrentCmd();
		cfound = false;
	}
 
	//STEP3: Divers traitements
	//Vous pouvez ajouter des traitements ici
 
	//STEP4: Déclenchement du WatchDog
	if (millis() - previousMillis > interval) {
		previousMillis = millis();   
		Serial.print("(W)");       
	}
}
 
//Fonction qui reset la commande courante
void ResetCurrentCmd(void) {
  for (int i=0; i<=200; i++) {
    currentCmd[i] = 0;
    pt = 0;
  }
}
 
//Fonction qui distribue les differentes commandes
void RunCommand(void) {
	char c = stringIn[1];                	//Type de commande
 
	switch (c) {
		case 'D':							//Mode Dynamic
			break;
 
		case 'F':							//Mode Static
			break;
 
		break;
	}
}

[.Net] Convertir une couleur hexadécimale en Couleur

dotnet color converter

Toujours pour mon projet EasyLight, j’ai développé une petite fonction qui convertit un code couleur hexadécimal en Media.Color.

Le code hexadécimal peut être au format #FFFFFF ou bien au format FFFFFF. Le code retourne un System.Windows.Media.Color, ce qui vous permettra de récupérer ses propriétés R, G ou B :-)

'Fonction qui convertit une couleur hexadecimale en couleur
Private Shared Function HexToColor(ByVal hex As String) As System.Windows.Media.Color
	hex = hex.Replace("#", "")
	Dim r As Byte = 0
	Dim g As Byte = 0
	Dim b As Byte = 0
 
	If hex.Length = 6 Then '#RRGGBB
		r = Byte.Parse(hex.Substring(0, 2), Globalization.NumberStyles.AllowHexSpecifier)
		g = Byte.Parse(hex.Substring(2, 2), Globalization.NumberStyles.AllowHexSpecifier)
		b = Byte.Parse(hex.Substring(4, 2), Globalization.NumberStyles.AllowHexSpecifier)
	ElseIf hex.Length = 3 Then '#RGB
		r = Byte.Parse(hex(0).ToString() + hex(0).ToString(), Globalization.NumberStyles.AllowHexSpecifier)
		g = Byte.Parse(hex(1).ToString() + hex(1).ToString(), Globalization.NumberStyles.AllowHexSpecifier)
		b = Byte.Parse(hex(2).ToString() + hex(2).ToString(), Globalization.NumberStyles.AllowHexSpecifier)
	End If
	Return System.Windows.Media.Color.FromRgb(r, g, b)
End Function

[.Net] Convertir un nombre dans un format lisible

vbnet

Pour mon projet EasyLight, j’ai dû faire une fonction qui convertit un nombre dans un format lisible et compréhensible pour le commun des mortels.

L’exemple classique est de convertir un nombre de bytes en GigaBytes, MégaBytes, etc…

Pour faire cette conversion de nombre, je vous propose la fonction suivante:

'Fonction qui convertit un nombre en format Human Readable
Private Shared Function ConvertToHumanReadable(ByVal value As Double, Optional ByVal diviser As Integer = 1024, Optional ByVal nbDecimal As Integer = 0) As String
	Dim _units() As String = {"", "K", "M", "G", "T", "P", "E", "Z", "Y"}
	Dim size As Double = value
	Dim i As Integer = 0
	While size >= diviser
		size /= diviser
		i += 1
	End While
	Return Math.Round(size, nbDecimal) & _units(i)
End Function

Cette fonction vous permettra de convertir un nombre dans ses différents multiple rapidement et facilement.