Netduino home hardware projects downloads community

Jump to content


The Netduino forums have been replaced by new forums at community.wildernesslabs.co. This site has been preserved for archival purposes only and the ability to make new accounts or posts has been turned off.
Photo

Mayhew Labs MuxShield and TI 74HC4067


  • Please log in to reply
22 replies to this topic

#1 Eric Falsken

Eric Falsken

    Member

  • Members
  • PipPip
  • 25 posts
  • LocationSan Francisco, CA, USA

Posted 21 November 2011 - 03:55 PM

As seen on the SparkFun and Mayhew Labs websites. (schematic) The board comes fully assembled with quality components. Very well built. I'm happy to announce that it is 100% Netduino compatible with no flaws, although there is a bit of confusion about what this board is and is not.

  • 3 TI 74HC4067 Analog/Digital Muxer chips allow you to work with analog or digital signals from 2-6V. (but it just passes them through to your Netduino, so more than 5V isn't safe)
  • 48 GPIO pins for the cost of 7 (D2-5, A0-2)
  • All pins are individually addressable (although not all at the same time, and without a "latch")
  • Very convenient 5v and Ground pins adjacent to every header.
  • In a compact, stackable shield.

First the code to drive the 74HC4067. The code below should also compatible with any other 74HC4067 usage. (like the SparkFun breakout)

public class Ic74HC4067 {
	private readonly OutputPort _s0 = null;
	private readonly OutputPort _s1 = null;
	private readonly OutputPort _s2 = null;
	private readonly OutputPort _s3 = null;
	private readonly Port _commonRead = null;
	private readonly OutputPort _commonWrite = null;
	private readonly AnalogInput _analogRead = null;
	private readonly OutputPort _enable = null;

	/// <summary>
	/// A list of all General Purpose Input/Output pins
	/// </summary>
	public enum Pins : byte {
		/// <summary>1st GPO pin</summary>
		GPIO_PIN_00,
		/// <summary>2nd GPO pin</summary>
		GPIO_PIN_01,
		/// <summary>3rd GPO pin</summary>
		GPIO_PIN_02,
		/// <summary>4th GPO pin</summary>
		GPIO_PIN_03,
		/// <summary>5th GPO pin</summary>
		GPIO_PIN_04,
		/// <summary>6th GPO pin</summary>
		GPIO_PIN_05,
		/// <summary>7th GPO pin</summary>
		GPIO_PIN_06,
		/// <summary>8th GPO pin</summary>
		GPIO_PIN_07,
		/// <summary>9th GPO pin</summary>
		GPIO_PIN_08,
		/// <summary>10th GPO pin</summary>
		GPIO_PIN_09,
		/// <summary>11th GPO pin</summary>
		GPIO_PIN_10,
		/// <summary>12th GPO pin</summary>
		GPIO_PIN_11,
		/// <summary>13th GPO pin</summary>
		GPIO_PIN_12,
		/// <summary>14th GPO pin</summary>
		GPIO_PIN_13,
		/// <summary>15th GPO pin</summary>
		GPIO_PIN_14,
		/// <summary>16th GPO pin</summary>
		GPIO_PIN_15,
		/// <summary>Common I/O disabled</summary>
		GPIO_PIN_OFF = 0x10
	}

	public Ic74HC4067(Cpu.Pin s0, Cpu.Pin s1, Cpu.Pin s2, Cpu.Pin s3, Cpu.Pin common, Cpu.Pin enable, bool analog) :
		this(new OutputPort(s0, false), new OutputPort(s1, false), new OutputPort(s2, false), new OutputPort(s3, false)) {
		if (enable != Cpu.Pin.GPIO_NONE)
			_enable = new OutputPort(enable, true);
		if (common != Cpu.Pin.GPIO_NONE) {
			if (analog)
				_analogRead = new AnalogInput(common);
			else {
				_commonWrite = new OutputPort(common, false);
				_commonRead = _commonWrite;
			}
		}
	}

	public Ic74HC4067(OutputPort s0, OutputPort s1, OutputPort s2, OutputPort s3) : this(s0, s1, s2, s3, null, null) { }

	public Ic74HC4067(OutputPort s0, OutputPort s1, OutputPort s2, OutputPort s3, Port common) : this(s0, s1, s2, s3, common, null) { }

	public Ic74HC4067(OutputPort s0, OutputPort s1, OutputPort s2, OutputPort s3, Port common, OutputPort enable) {
		if (s0 == null) throw new ArgumentNullException("s0", "s0 is required");
		if (s1 == null) throw new ArgumentNullException("s1", "s1 is required");
		if (s2 == null) throw new ArgumentNullException("s2", "s2 is required");
		if (s3 == null) throw new ArgumentNullException("s3", "s3 is required");
		_s0 = s0;
		_s1 = s1;
		_s2 = s2;
		_s3 = s3;
		_commonRead = common;
		if(common != null)
			_commonWrite = common as OutputPort;
		_enable = enable;
	}

	public Ic74HC4067(OutputPort s0, OutputPort s1, OutputPort s2, OutputPort s3, AnalogInput common){
		_s0 = s0;
		_s1 = s1;
		_s2 = s2;
		_s3 = s3;
		_analogRead = common;
	}

	/// <summary>
	/// Sets the currently operating GPIO pin.
	/// </summary>
	public Pins CurrentPin {
		get {
			if (_enable != null && _enable.Read())
				return Pins.GPIO_PIN_OFF;

			byte ret = 0;
			if (_s3.Read())
				ret |= 0x08;
			if (_s2.Read())
				ret |= 0x04;
			if (_s1.Read())
				ret |= 0x02;
			if (_s0.Read())
				ret |= 0x01;
			return (Pins)ret; 
		}
		set {
			if (value == Pins.GPIO_PIN_OFF)
				Enabled = false;

			_s3.Write(((byte)value & 0x08) == 0x08);
			_s2.Write(((byte)value & 0x04) == 0x04);
			_s1.Write(((byte)value & 0x02) == 0x02);
			_s0.Write(((byte)value & 0x01) == 0x01);
		}
	}

	/// <summary>
	/// When true, disables all GPIO output pins. (true=low, false=high)
	/// </summary>
	public bool Enabled {
		get {
			if (_enable == null) throw new InvalidOperationException("Enable pin is not specified.");
			return !_enable.Read(); }
		set {
			if (_enable == null) throw new InvalidOperationException("Enable pin is not specified.");
			_enable.Write(!value); 
		}
	}

	public bool ReadBit() {
		if (_commonRead == null) throw new InvalidOperationException("CommonIO pin is not specified or not in digital mode.");
		return _commonRead.Read();
	}

	public void WriteBit(bool value) {
		if (_commonWrite == null) throw new InvalidOperationException("CommonIO pin is not specified or is not writeable.");
		_commonWrite.Write(value);
	}

	public int ReadValue() {
		if (_analogRead == null) throw new InvalidOperationException("CommonIO pin is not specified or not in analog mode.");
		return _analogRead.Read();
	}

	public override string ToString() {
		if (_commonWrite != null)
			return "Ic74HC4067 out=" + (_commonWrite.Read() ? "1" : "0");
		if (_commonRead != null)
			return "Ic74HC4067 dIn=" + (_commonRead.Read() ? "1" : "0");
		if (_analogRead != null)
			return "Ic74HC4067 aIn=" + _analogRead.Read();
		return base.ToString();
	}
}

And now the MuxShield driver class:

public class MuxShield {
	public readonly Ic74HC4067[] muxes = new Ic74HC4067[3];
	public readonly OutputPort S0 = new OutputPort(Pins.GPIO_PIN_D2, false);
	public readonly OutputPort S1 = new OutputPort(Pins.GPIO_PIN_D3, false);
	public readonly OutputPort S2 = new OutputPort(Pins.GPIO_PIN_D4, false);
	public readonly OutputPort S3 = new OutputPort(Pins.GPIO_PIN_D5, false);
	public readonly OutputPort D13LED = new OutputPort(Pins.GPIO_PIN_D13, false);
	const Cpu.Pin GPIO_MUX0 = Pins.GPIO_PIN_A0;
	const Cpu.Pin GPIO_MUX1 = Pins.GPIO_PIN_A1;
	const Cpu.Pin GPIO_MUX2 = Pins.GPIO_PIN_A2;

	public enum MuxMode{
		AnalogInput,
		DigitalInput,
		DigitalOutput
	}

	public MuxShield(MuxMode mode0, MuxMode mode1, MuxMode mode2) {
		muxes[0] = CreateMux(mode0, GPIO_MUX0);
		muxes[1] = CreateMux(mode1, GPIO_MUX1);
		muxes[2] = CreateMux(mode2, GPIO_MUX2);
	}

	protected Ic74HC4067 CreateMux(MuxMode mode, Cpu.Pin iopin) {
		switch (mode) {
			case MuxMode.AnalogInput:
				return new Ic74HC4067(S0, S1, S2, S3, new AnalogInput(iopin));
			case MuxMode.DigitalInput:
				return new Ic74HC4067(S0, S1, S2, S3, new InputPort(iopin, false, Port.ResistorMode.PullDown));
			case MuxMode.DigitalOutput:
				return new Ic74HC4067(S0, S1, S2, S3, new OutputPort(iopin, false));
			default:
				throw new InvalidOperationException();
		}
	}

	/// <summary>
	/// Sets the currently operating pins
	/// </summary>
	public Ic74HC4067.Pins CurrentPin {
		get {
			//On the MuxShield, all Ic74HC4067 chips share the same s0-s3 pins, so setting one of them is good enough.
			return MUX0.CurrentPin; }
		set { MUX0.CurrentPin = value; }
	}

	public Ic74HC4067 this[int index]{
		get{
			return muxes[index];
		}
	}

	public Ic74HC4067 MUX0 { get{return this[0];} }
	public Ic74HC4067 MUX1 { get{return this[1];} }
	public Ic74HC4067 MUX2 { get{return this[2];} }

	public override string ToString() {
		string ret = "MuxShield ";
		ret += (S3.Read() ? "1" : "0");
		ret += (S2.Read() ? "1" : "0");
		ret += (S1.Read() ? "1" : "0");
		ret += (S0.Read() ? "1" : "0");
		ret += " ";
		ret += MUX0.ToString();
		ret += " ";
		ret += MUX1.ToString();
		ret += " ";
		ret += MUX2.ToString();
		return ret;
	}
}

And the sample program to make it blink a handful of LEDs in sequence:

public static void Main() {
	MuxShield shield = new MuxShield(MuxShield.MuxMode.DigitalOutput, MuxShield.MuxMode.DigitalOutput, MuxShield.MuxMode.DigitalOutput);
	//Set the pins high so that as we move through the pins, LEDs will light up
	shield.MUX0.WriteBit(true);
	shield.MUX1.WriteBit(true);
	shield.MUX2.WriteBit(true);

	while (true) {
		for (byte x = 0; x <= 15; x+=2) {
			//itterate over the pins
			shield.CurrentPin = (Ic74HC4067.Pins)x;
			//blink the onboard led
			shield.D13LED.Write(!shield.D13LED.Read());
			Debug.Print(shield.ToString());
			Thread.Sleep(100);
		}
	}
}


#2 michel capua

michel capua

    Member

  • Members
  • PipPip
  • 26 posts

Posted 18 May 2013 - 06:50 PM

Is it possible use this code with netduino plus 2 ?



#3 hanzibal

hanzibal

    Advanced Member

  • Members
  • PipPipPip
  • 1287 posts
  • LocationSweden

Posted 18 May 2013 - 07:56 PM

Sure, just change the pin names. Eric, how fast can you cycle through pins with this thing?

#4 michel capua

michel capua

    Member

  • Members
  • PipPip
  • 26 posts

Posted 18 May 2013 - 09:03 PM

with netduino plus run.

 

with netduino plus 2 what are pin names that I have to change? 



#5 michel capua

michel capua

    Member

  • Members
  • PipPip
  • 26 posts

Posted 18 May 2013 - 10:45 PM

Eric,

 

with netduino plus and without shield I use only six analog input and the code to read all analog input is:

 

while (true)
{
value0 = voltagePort0.Read();
value1 = voltagePort1.Read();
value2 = voltagePort2.Read();
value3 = voltagePort3.Read();
value4 = voltagePort4.Read();
value5 = voltagePort5.Read();
Debug.Print("A0--> " + value0.ToString("f") ....
Thread.Sleep(1000);
}
 
what is the code to read all 40 analog input using the shield?
 
Can You help me?


#6 hanzibal

hanzibal

    Advanced Member

  • Members
  • PipPipPip
  • 1287 posts
  • LocationSweden

Posted 19 May 2013 - 04:05 AM

Basically, you need to set CurrentPin between reads from each different pin. About the pin names, it could be you don't have to change anything.

#7 michel capua

michel capua

    Member

  • Members
  • PipPip
  • 26 posts

Posted 19 May 2013 - 08:12 AM

Ok many thanks.

 

Now the problem I netduino plus 2.

 

I have 3 errors:

 

 

Error 1 The expression being assigned to 'NetduinoPlusApplication5.MuxShield.GPIO_MUX0' must be constant
C:ExNetduinoPlusApplication5NetduinoPlusApplication5MuxShield.cs 18 35 NetduinoPlusApplication5
 
Error 2 The expression being assigned to 'NetduinoPlusApplication5.MuxShield.GPIO_MUX1' must be constant
C:ExNetduinoPlusApplication5NetduinoPlusApplication5MuxShield.cs 19 35 NetduinoPlusApplication5
 
Error 3 The expression being assigned to 'NetduinoPlusApplication5.MuxShield.GPIO_MUX2' must be constant
C:ExNetduinoPlusApplication5NetduinoPlusApplication5MuxShield.cs 20 35 NetduinoPlusApplication5
 
 
and  the rows of MuxShields.cs are:
 
18    const Cpu.Pin GPIO_MUX0 = Pins.GPIO_PIN_A0;
19    const Cpu.Pin GPIO_MUX1 = Pins.GPIO_PIN_A1;
20    const Cpu.Pin GPIO_MUX2 = Pins.GPIO_PIN_A2;
 
 
where is the problem? what do it change using netduino plus 2?

 

I remember that with netduino plus 2 is valid:
 
AnalogInput voltagePort = new AnalogInput(AnalogChannels.ANALOG_PIN_A0);
 
and not
 
AnalogInput voltagePort = new AnalogInput(Pins.GPIO_PIN_A0);

 

 

and another particular the board netduino plus 2 with muxshield 40 pin gets hot

 

 

 

 

 



#8 hanzibal

hanzibal

    Advanced Member

  • Members
  • PipPipPip
  • 1287 posts
  • LocationSweden

Posted 19 May 2013 - 11:19 AM

I don't have an NP2 but those errors suggest Pins.GPIO_PIN_Ax are not const but variables on the NP2 which seems a little odd. Anyway, try removing the leading "const" in each of the three lines. The board should not get _hot_ so check your wiring and make sure you're not using more current than the board(s) can handle.

#9 michel capua

michel capua

    Member

  • Members
  • PipPip
  • 26 posts

Posted 20 May 2013 - 12:29 PM

Eric,

 

can you send me an example to use the digital input?



#10 hanzibal

hanzibal

    Advanced Member

  • Members
  • PipPipPip
  • 1287 posts
  • LocationSweden

Posted 20 May 2013 - 01:59 PM

How did it go, does it work after removing the "const" statement?

 

can you send me an example to use the digital input?

 

I'm obviously not Eric, but early in this post he gave an example with three multiplexed digital outputs:

public static void Main() {	MuxShield shield = new MuxShield(MuxShield.MuxMode.DigitalOutput, MuxShield.MuxMode.DigitalOutput, MuxShield.MuxMode.DigitalOutput);		//Set the pins high so that as we move through the pins, LEDs will light up	shield.MUX0.WriteBit(true);	shield.MUX1.WriteBit(true);	shield.MUX2.WriteBit(true);	while (true) 	{		for (byte x = 0; x <= 15; x+=2) 		{			//itterate over the pins			shield.CurrentPin = (Ic74HC4067.Pins)x;						//blink the onboard led			shield.D13LED.Write(!shield.D13LED.Read());			Debug.Print(shield.ToString());			Thread.Sleep(100);		}	}}

If you need digital input(s), simply modify the three parameters in the call to the constructor according to your needs. You would then use the readBit method to read the pin after having set CurrentPin according to which of the multiplexed inputs you wish to read.

 

For example, this should give you one multiplexed input and two outputs.

// digital in, out, outMuxShield shield = new MuxShield(MuxShield.MuxMode.DigitalInput, MuxShield.MuxMode.DigitalOutput, MuxShield.MuxMode.DigitalOutput);


#11 michel capua

michel capua

    Member

  • Members
  • PipPip
  • 26 posts

Posted 20 May 2013 - 03:52 PM

Hanzibal,

 

 

do this istruction shield[color=rgb(102,102,0);].[/color]MUX0[color=rgb(102,102,0);].[/color][color=rgb(102,0,102);]WriteBit[/color][color=rgb(102,102,0);]([/color][color=rgb(0,0,136);]true[/color][color=rgb(102,102,0);]) put high all pins of the mux0?[/color]

 

another question:

 

I use:

 

[color=rgb(0,0,136);]public[/color] [color=rgb(0,0,136);]static[/color] [color=rgb(0,0,136);]void[/color] [color=rgb(102,0,102);]Main[/color][color=rgb(102,102,0);]()[/color]
[color=rgb(102,102,0);]{[/color]
[color=rgb(102,0,102);]MuxShield[/color] shield [color=rgb(102,102,0);]=[/color] [color=rgb(0,0,136);]new[/color] [color=rgb(102,0,102);]MuxShield[/color][color=rgb(102,102,0);]([/color][color=rgb(102,0,102);]MuxShield[/color][color=rgb(102,102,0);].[/color][color=rgb(102,0,102);]MuxMode[/color][color=rgb(102,102,0);].[/color][color=rgb(102,102,0);]DigitalInput,[/color] MuxShield.MuxMode.DigitalOutput, MuxShield.MuxMode.DigitalOutput);       
  while (true) {

    for (byte x = 0; x <= 7; x+=1) {
  shield.CurrentPin = (Ic74HC4067.Pins)x;
    Debug.Print(shield.MUX0.ReadBit().ToString());
     Thread.Sleep(1000);
     }
     }

[color=rgb(102,102,0);]}[/color]

 

but receive this error:

An unhandled exception of type 'System.ArgumentException' occurred in Microsoft.SPOT.Hardware.dll. Break Or Continue.

 

 

and a green arrow in this istruction of MuxShield.cs

 

--> 43 return new Ic74HC4067(S0, S1, S2, S3, new InputPort(iopin, false, Port.ResistorMode.PullDown));



#12 hanzibal

hanzibal

    Advanced Member

  • Members
  • PipPipPip
  • 1287 posts
  • LocationSweden

Posted 20 May 2013 - 05:54 PM

No, it should only output a logic high on the currently selected pin but if you cycle through all pins writing a high level to one pin after another, they will all be high eventually since outputs are latched. The latch makes the output "remember" its level after you have selected another pin.

You can think of a multiplexer like one of those rotating platforms that they used to have in front of train garages in the old days. Pins s0...s3 form a 4 bit number 0...15 that is used to select which of the 16 tracks to use:
http://www.yeeeeee.c...eneralele-1.jpg

I'll have a closer look later but the error could be pull-down mode is not supported on the input pin. Certain combinations are not allowed but I can never remember which. I'll get back shortly.

#13 hanzibal

hanzibal

    Advanced Member

  • Members
  • PipPipPip
  • 1287 posts
  • LocationSweden

Posted 20 May 2013 - 10:43 PM

Hmm...after removing the "const" statements, I basically get 3 errors when trying to compile as an NP2 project.

 

These two errors:

 

[font="'courier new', courier, monospace;"]Error 1 The best overloaded method match for 'Microsoft.SPOT.Hardware.AnalogInput.AnalogInput(Microsoft.SPOT.Hardware.Cpu.AnalogChannel)'[/font]

 

[font="'courier new', courier, monospace;"]Error 2 Argument 1: cannot convert from 'Microsoft.SPOT.Hardware.Cpu.Pin' to 'Microsoft.SPOT.Hardware.Cpu.AnalogChannel'[/font]
 
...refers to this code
public Ic74HC4067(Cpu.Pin s0, Cpu.Pin s1, Cpu.Pin s2, Cpu.Pin s3, Cpu.Pin common, Cpu.Pin enable, bool analog) :    this(new OutputPort(s0, false), new OutputPort(s1, false), new OutputPort(s2, false), new OutputPort(s3, false)){    if (enable != Cpu.Pin.GPIO_NONE)        _enable = new OutputPort(enable, true);    if (common != Cpu.Pin.GPIO_NONE)    {        if (analog)            _analogRead = new AnalogInput(common);        else        {            _commonWrite = new OutputPort(common, false);            _commonRead = _commonWrite;        }    }}
 
While this one:
 
[font="'courier new', courier, monospace;"]Error 3 Cannot implicitly convert type 'double' to 'int'. An explicit conversion exists (are you missing a cast?)[/font]

 

...refers to this code:

public int ReadValue(){    if (_analogRead == null)          throw new InvalidOperationException("CommonIO pin is not specified or not in analog mode.");    return _analogRead.Read();}

Michel, are you seeing these too?



#14 michel capua

michel capua

    Member

  • Members
  • PipPip
  • 26 posts

Posted 20 May 2013 - 11:17 PM

yes



#15 michel capua

michel capua

    Member

  • Members
  • PipPip
  • 26 posts

Posted 20 May 2013 - 11:32 PM

I am using NP.

 

with this simply code:

 

 

public static void Main()
        {
MuxShield shield = new MuxShield(MuxShield.MuxMode.DigitalInput, MuxShield.MuxMode.DigitalInput, MuxShield.MuxMode.DigitalOutput);
 while (true) 
{
for (byte x = 0; x <= 15; x += 1)
{
shield.CurrentPin = (Ic74HC4067.Pins)x;                 
Debug.Print(shield.ToString());
Thread.Sleep(300);
}
}
}//main
 
the error is the same:
 
An unhandled exception of type 'System.ArgumentException' occurred in Microsoft.SPOT.Hardware.dll

 

and go to the method:

 

 

 

protected Ic74HC4067 CreateMux(MuxMode mode, Cpu.Pin iopin)
        {
            switch (mode)
            {
                case MuxMode.AnalogInput:
                    return new Ic74HC4067(S0, S1, S2, S3, new AnalogInput(iopin));
                case MuxMode.DigitalInput:
                    return new Ic74HC4067(S0, S1, S2, S3, new InputPort(iopin, false, Port.ResistorMode.PullDown)); //HERE
                case MuxMode.DigitalOutput:
                    return new Ic74HC4067(S0, S1, S2, S3, new OutputPort(iopin, false));
                default:
                    throw new InvalidOperationException();
            }
        }

 

 



#16 michel capua

michel capua

    Member

  • Members
  • PipPip
  • 26 posts

Posted 21 May 2013 - 01:39 AM

[color=rgb(40,40,40);font-family:helvetica, arial, sans-serif;]MuxShield.MuxMode.DigitalInput[/color]

 i think that this mode have an error.



#17 hanzibal

hanzibal

    Advanced Member

  • Members
  • PipPipPip
  • 1287 posts
  • LocationSweden

Posted 21 May 2013 - 02:47 PM

I'll see if I got one of those multiplexers in my junk box, if so I'll try the driver with an NP I got in there too.

 

Eric said for the shield to be "100% Netduino compatible with no flaws" but unfortunately, this does not seem to go for the driver class unless only tested for something else than a regular N or NP.



#18 michel capua

michel capua

    Member

  • Members
  • PipPip
  • 26 posts

Posted 22 May 2013 - 07:22 AM

Summary of my experience:

 

with NP:

 

MuxShield.MuxMode.DigitalInput I think that this mode have an error.

[color=rgb(40,40,40);font-family:helvetica, arial, sans-serif;]MuxShield.MuxMode.AnalogInput run.[/color]

 

with NP2:

the driver class have problems.

 

and now the new version of netduino plus is netduino plus 2 .



#19 michel capua

michel capua

    Member

  • Members
  • PipPip
  • 26 posts

Posted 22 May 2013 - 11:43 PM

can buy [color=rgb(40,40,40);font-family:helvetica, arial, sans-serif;font-size:14px;]MuxShield for NP2?[/color]



#20 hanzibal

hanzibal

    Advanced Member

  • Members
  • PipPipPip
  • 1287 posts
  • LocationSweden

Posted 23 May 2013 - 10:42 AM

I could, but I don't own a NP2 (only an NP) and would probably only use it in trying to help you out so I thought I'll use the bare chip instead.

 

Didn't find one in my junk box so I ordered a couple of SO24s but by the time I get them, perhaps you'll already solved the problem your self.

 

We'll see.






0 user(s) are reading this topic

0 members, 0 guests, 0 anonymous users

home    hardware    projects    downloads    community    where to buy    contact Copyright © 2016 Wilderness Labs Inc.  |  Legal   |   CC BY-SA
This webpage is licensed under a Creative Commons Attribution-ShareAlike License.