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.
I have one of the typical cheapo four-module 7219 LED matrices (e.g. four panels of 8x8 each, with power, ground, clock and cs daisy-chained; each module after the first gets it's DIN from the previous DOUT). I downloaded the current Max72197221.cs from Netduino Helpers, and was able to send letters and patterns to the first module. So far ,so good.
However, according to the 7219 datasheet, I should be able to address each of the subsequent modules by sending a No-Op after each row of data (the four 7219 chips acting as a shift register) for each module I want to move to the right. But sending what I think is the right value to the matrix doesn't work.
For example, I changed the original Max72197221.cs code to define SpiBuffer as ushort[2], and modified the Write subroutine as follows:
protected void Write(byte register, byte value) {
SpiBuffer[0] = register;
SpiBuffer[0] <<= 8;
SpiBuffer[0] |= value;
SpiBuffer[1] = (byte)RegisterAddressMap.NoOp;
SpiBuffer[1] <<= 8;
SpiBuffer[1] |= 0;
Spi.Write(SpiBuffer);
}
But the display doesn't - well, display. It also doesn't work when defining SpiBuffer[1] simply as 0 (without all the bit-shifting nonsense). Can anyone help me spot the flaw in my logic?
Assuming (haven't read the d/s) the first matrix will swallow the nop only passing forward the next operation to the next matrix in chain, shouldn't you be sending the nop first when addressing the second matrix?
The built-in 16 bit shiftregister has the effect of data coming in though DIN being spit out through DOUT 16.5 clock cycles later. So there's a 16 bit delay before data reaches the 2nd chip. Data is latched in from the shiftreg on the high edge of CS. So you are correct, the nop should go last.
I agree, your code ought to work, after clocking through 32 bits, the nop should be sitting in the shiftregister of the 1st matrix and the actual op in the 2nd. As LOAD goes high (!CS goes low) ending the SPI write, the shiftregister data should be latched in to both chips respectively. At this point, the 1st would load the nop while the 2nd would load the op.
What happens if you send the same op twice in a row (eg without a nop in between) - does both chips act as expected or only the 1st, 2nd or none?
EDIT: I've had some bad experiences with 16 bit SPI, could be you have to make two transfers, 16 + 16 bits. Naturally, if you have a scope or analyzer, have a look at the signals to what really happens and check your wiring, SPI timing config with setup time, etc.
Same thing: when I tried sending live data twice, only the first module responded. I'm reletively certain it's not a hardware issue, as I was able to get all leds on all modules to light using my Bus Pirate.
The two transfers is a good idea; I'll give that a try.
Do you think the new enhanced SPI (which permits transferes other lengths than 8 or 16 bits) might help?
Thank you as always, my friend, for taking the time to help.
The thing to look for is probably that LOAD doesn't go high in between bytes but only in between 16 bit words otherwise the chips might get confused. I'm not sure, but I don't think the ushort array SPI methods actually use 16 bit word length so trying explocit variable word length SPI is definately worth a try.
A thing to try next where you are more in control of the signals, would be to bitbang the thing, i.e using gpio for CLK, DIN and LOAD/!CS. That way you could check to see if the chip really works the way you think it would. If it does, it's probably an SPI thing.
Here is the class thats work with a cascade display:
Imports SystemImports Microsoft.SPOTImports System.Collections''' <summary>''' I do not need to reinvent the wheel twice !''' thanks to embedded labs and Fabien Royen for there examples, http://embedded-lab.com/blog/?p=6644 ; ''' http://fabienroyer.wordpress.com/2011/03/13/using-a-max7219max7221-led-display-driver-with-a-netduino/''' ''' based on these examples and modified to fit in our project.''' </summary>''' <remarks></remarks>'Public Class Max7219 Protected m_Spi As MultiSPI4SevenSegment Private CharAddressMap As Hashtable Private _digits As Byte Private _Devices As Integer Public Enum RegisterAddressMap As Byte DecodeMode = &H9 Intensity = &HA ScanLimit = &HB Shutdown = &HC DisplayTest = &HF End Enum Public Enum DisplayRegisterFormat As Byte ShutdownMode = &H0 NormalOperation = &H1 End Enum Public Enum Intensity As Byte One Two Three Four Five Six Seven Eight Nine Ten Eleven Twelve Thirteen Fourteen Fifteen Sixteen Max = &H15 Min = &H0 Average = &H8 End Enum ''' <summary> ''' Netduino Pin: ''' D11 - SPI MOSI --> DIn of Board ''' D13 - SPI CLK --> CLK of Board ''' </summary> ''' <param name="chipSelect">Load pin of Board</param> ''' Public Sub New(ByVal chipSelect As Cpu.Pin, ByVal Devices As Integer, Optional ByVal Digits As Byte = 8) _digits = Digits _Devices = Devices Spi = New MultiSPI4SevenSegment(New SPI.Configuration(chipSelect, False, 0, 0, False, True, 1000, Hardware.SPI.SPI_module.SPI1)) Initialize() End Sub Public Sub Initialize() CharAddressMap = New Hashtable() CharAddressMap.Add("0", &H7E) CharAddressMap.Add("1", &H30) CharAddressMap.Add("2", &H6D) CharAddressMap.Add("3", &H79) CharAddressMap.Add("4", &H33) CharAddressMap.Add("5", &H5B) CharAddressMap.Add("6", &H5F) CharAddressMap.Add("7", &H70) CharAddressMap.Add("8", &H7F) CharAddressMap.Add("9", &H7B) CharAddressMap.Add("A", &H77) CharAddressMap.Add("B", &H1F) CharAddressMap.Add("C", &H4E) CharAddressMap.Add("D", &H3D) CharAddressMap.Add("E", &H4F) CharAddressMap.Add("F", &H47) CharAddressMap.Add("G", &H5E) CharAddressMap.Add("H", &H37) CharAddressMap.Add("I", &H30) CharAddressMap.Add("J", &H3C) CharAddressMap.Add("K", &H2F) CharAddressMap.Add("L", &HE) CharAddressMap.Add("M", &H55) CharAddressMap.Add("N", &H15) CharAddressMap.Add("O", &H1D) CharAddressMap.Add("P", &H67) CharAddressMap.Add("Q", &H73) CharAddressMap.Add("R", &H5) CharAddressMap.Add("S", &H5B) CharAddressMap.Add("T", &HF) CharAddressMap.Add("U", &H3E) CharAddressMap.Add("V", &H1C) CharAddressMap.Add("W", &H5C) CharAddressMap.Add("X", &H49) CharAddressMap.Add("Y", &H3B) CharAddressMap.Add("Z", &H6D) CharAddressMap.Add(" ", &H0) CharAddressMap.Add(".", &H80) CharAddressMap.Add("-", &H1) CharAddressMap.Add("_", &H8) EnableDigits(_digits) DisplayTest(False) ShutDown(False) Clear(_digits) End Sub Public Sub WriteTxt(value As String, decPointOnDigit As Integer, ByVal startDigit As Byte, Optional ByVal cascadeDevice As Integer = 0) Const cnstDecPoint As Byte = 128 Const cnstLeftToRight As Byte = 1 If value.Length > 8 Then value = value.Substring(0, 8) End If Dim texts As Char() = value.ToCharArray() Dim displayText As String For i As Integer = 0 To texts.Length - 1 displayText = texts(i).ToString().ToUpper() If CharAddressMap.Contains(displayText) Then If i = decPointOnDigit Then Write(startDigit, Convert.ToByte(CharAddressMap(displayText).ToString()) + cnstDecPoint, cascadeDevice) Else Write(startDigit, Convert.ToByte(CharAddressMap(displayText).ToString()), cascadeDevice) End If End If startDigit -= cnstLeftToRight Next End Sub Public Sub Write(register As Byte, value As Byte, Optional ByVal cascadeDevice As Integer = 0) SyncLock Spi Dim WriteRegister(cascadeDevice) As UShort WriteRegister(0) = CUShort(CUShort(register) * 2 ^ 8) Or value For t As Integer = 1 To cascadeDevice Step 1 WriteRegister(t) = &H0 Next Spi.Write(WriteRegister) Thread.Sleep(2) Spi.Write(New UShort() {&H0, &H0, &H0, &H0, &H0, &H0, &H0, &H0}) End SyncLock End Sub Public Sub Clear(Optional ByVal numberOfDigits As Byte = 8) For device As Integer = 0 To _Devices - 1 For i As Byte = 1 To numberOfDigits Write(i, &H0, device) Thread.Sleep(2) Next Next End Sub Public Sub SetIntensity(ByVal intensity As Intensity) For device As Integer = 0 To _Devices - 1 Write(CByte(RegisterAddressMap.Intensity), CByte(intensity), device) Next End Sub Public Sub DisplayTest(ByVal enable As Boolean) For device As Integer = 0 To _Devices - 1 If enable Then Write(CByte(RegisterAddressMap.DisplayTest), &H1, device) Else Write(CByte(RegisterAddressMap.DisplayTest), &H0, device) End If Next End Sub Public Sub ShutDown(ByVal shutDown__1 As Boolean) For device As Integer = 0 To _Devices - 1 If shutDown__1 Then Write(CByte(RegisterAddressMap.Shutdown), &H0, device) Else Write(CByte(RegisterAddressMap.Shutdown), &H1, device) End If Next End Sub Private Sub EnableDigits(ByVal numberOfDigits As Byte) For device As Integer = 0 To _Devices - 1 Write(CByte(RegisterAddressMap.ScanLimit), CByte(numberOfDigits - 1), device) Next End Sub Public Property Spi() As MultiSPI4SevenSegment Get Return m_Spi End Get Set(value As MultiSPI4SevenSegment) m_Spi = value End Set End Property Public Sub dispose() Try If Spi IsNot Nothing Then Spi.Dispose() End If Catch ex As Exception End Try Spi = Nothing Thread.Sleep(100) End SubEnd Class
''' <summary>''' extended class to manage multiple seven segment displays''' </summary>''' <remarks></remarks>Public Class MultiSPI4SevenSegment Implements IDisposable Protected _SPIDevice As SPI Protected _Configuration As SPI.Configuration Public ReadOnly Property Config() As SPI.Configuration Get Return Me._Configuration End Get End Property Public Sub Dispose() Implements IDisposable.Dispose If Me._SPIDevice IsNot Nothing Then Try Me._SPIDevice.Dispose() Catch ex As Exception End Try Me._SPIDevice = Nothing End If End Sub Public Sub New(ByVal config As SPI.Configuration) Me._Configuration = config If Me._SPIDevice Is Nothing Then Try Me._SPIDevice = New SPI(Me._Configuration) Catch ex As Exception End Try End If End Sub Public Sub Write(ByVal WriteBuffer As Byte()) Me._SPIDevice.Config = Me._Configuration Me._SPIDevice.Write(WriteBuffer) End Sub Public Sub Write(ByVal WriteBuffer As UShort()) Me._SPIDevice.Config = Me._Configuration Me._SPIDevice.Write(WriteBuffer) End SubEnd Class
First, I couldn't get JoopC's code to work any better than mine. So I immediately decided I had a hardware issue (despite my success with the Bus Pirate) and decided to do some research.
I found a post online that said thant when sending multiple bytes to the chained matrices, all bytes have to be sent within a single ChipSelect(false)/ChipSelect(true) pair. So I went back to my original code, set the cs pin to GPIO_PIN_NONE in the SPI constructor, set up an OutputPort for the chip select (per http://forums.netdui...l=gpio_pin_none - thanks CW2!), and send my bunch of bytes.
And it worked!
Sort of - the intensity isn't right, and I'm getting display on every other matrix (rather than adjacent ones). But it's a start.
If and when I ever get this fully working, I'll post my code.
Good you found it and that's exactly what I meant when I wrote this:
The thing to look for is probably that LOAD doesn't go high in between bytes but only in between 16 bit words otherwise the chips might get confused. I'm not sure, but I don't think the ushort array SPI methods actually use 16 bit word length...
Manual control; I ser SPI CS to PIN_NONE, and manually toggled an output port at the start and beginning of my multi-digit write.
My biggest problem was all the fonts I could find online were rotated 90 degrees on my cheap set of LED matrices. I found a freeware app that let me create my own fonts, which came with a sample that worked quite nicely as-is!
Right now, I have the font arrays stored in a hashtable, with the equivalent character of the alphabet as the key. Clunky, but it works. still, I'm going to just set up a simple array and use the ASCII value as the index, and see if that affects performance or memory usage.
I am also using a 7219 but since I don't have hardware yet (waiting for the official 4.3) I have worked with emulation by using the Extensible Emulator. Now I read this so to save me some suffering could you clarify this for me to make sure I got it right?
I understood from the 7219 that the NOOP was used to shift to the slave 7219 but since it was not explicitely mentioned I was sending the NoOP as the first 2 bytes: (a) NOOP(empty_data) and then ( COMMAND(value). However, reading this thread it seems I had it wrong, so you say IF I want to write to the slave 7219 I would send the COMMAND(value) I need and then follow it by NOOP(dummy) (two 16 byte transactions but with the NOOP in the 2nd transaction), right?
Commands are always 16 bits, address plus data, right?
Emulation is not the same as real hardware, according to what I read here, letting the MF SPI s/w do the Loading won't work in this setup right? so you recommend doing the SS (Chip Select/Load) manually making it active before the 2 bytes are sent and inactive AFTER the two bytes are sent.
A write to the slave 7219 would require only one LOAD activation/deactivation for the 32 bytes (Command[value] + NoOp[dummy]) OR do it for every 16-bit (2 byte) transaction?
Another problem I just noticed in my setup is that each 7219 drove a 4+2 digit setup (better than 6 individual digits) but unfortunately the 4-digit 7Segment uses 35mA and the 2-digit 7Segment uses 25mA which I guess wouldn't work well with a single Iset resistor (one set would look dimmer than the other). So I will have to probably drive the two 4-digit 7Segments by the main (same current limit) and the two 2-digit 7Segments by the slave 7219 (same current) with the complication that writing a value on either set would require two transactions (one for main to fill the first 4 digits and one for slave to fill the remaining decimal digits) :*(