Firstly, A video of the display in action:
I bought a 32×32 RGB Matrix display from China. Well more specifically it was from a helpful guy named James who works at www.wanzhouled.net
I have searched the internet for the cheapest prices on Matrix displays and James is by far the cheapest. A 32×32 RGB Matrix was only $22 (with postage being approx $10) Send an email to them via their site to get a price list.
And on to the project…
I had previously seen on youtube a simple bouncing balls animation and thought it looked really cool.
I decided to try my own version with my new display – on top of that, I wanted to finally get around to connecting up a PS2 mouse to a microcontroller. This proved to be somewhat difficult but I got there in the end.
The PICnDuino was used at the heart of this project (this is a dual microcontroller development platform that I made back in 2012, you can see more info on that HERE). More specifically, I am using the PIC18F25K20 microcontroller contained on the PICnDuino. Any Microcontroller should work fine as long as you have enough PORT pins.
Here is the PICnDuino Connected to the 32×32 RGB Matrix:
Here is how to connect the PICnDuino to the 32×32 RGB Matrix and PS2 Mouse. (It shouldn’t be to difficult to use this diagram for connecting to other microcontrollers).
I included the resistors on the mouse lines because one of them (RC6) is also used to communicate with the FTDI chip on the PICnDuino board. I put two in for good measure.
Here is a picture of the display in action showing the balls and a HELLO message:
Here is how I connected the mouse to the PICnDuino:
How the screen works:
It is quite interesting how these matrix displays actually do their thing I.E. draw pixels on the screen. I will do my best to explain and will use my Swordfish Basic code to help out:
First of all, I have given nicknames to all the port pins:
// Port Setup Dim RedData0 As PORTB.0 Dim RedData1 As PORTB.1 Dim GreenData0 As PORTB.2 Dim GreenData1 As PORTB.3 Dim BlueData0 As PORTB.4 Dim BlueData1 As PORTB.5 Dim Latch As PORTB.6 Dim OutputEnable As PORTB.7 Dim RowA As PORTC.0 Dim RowB As PORTC.1 Dim RowC As PORTC.2 Dim RowD As PORTC.3 Dim CLK As PORTC.4
Then I have a routine that deals with sending all the data to the 32×32 Pixel Display:
Sub DrawGraphics() For Y = 0 To 15 TempData0 = OutputDataRed(Y) TempData1 = OutputDataRed(Y + 16) TempData2 = OutputDataGreen(Y) TempData3 = OutputDataGreen(Y + 16) TempData4 = OutputDataBlue(Y) TempData5 = OutputDataBlue(Y + 16) For X = 0 To 31 RedData0 = TempData0.bits(31 - X) RedData1 = TempData1.bits(31 - X) GreenData0 = TempData2.bits(31 - X) GreenData1 = TempData3.bits(31 - X) BlueData0 = TempData4.bits(31 - X) BlueData1 = TempData5.bits(31 - X) CLK = 1 CLK = 0 Next RowA = Y.bits(0) RowB = Y.bits(1) RowC = Y.bits(2) RowD = Y.bits(3) Latch = 1 Latch = 0 OutputEnable = 0 DelayUS(150) OutputEnable = 1 Next End Sub
The screen has 32 rows, each containing 32 pixels. To draw a complete image, you need to send in 32 bits of data (serially) to each of the 6 Color lines, these being:
- Red 0
- Red 1
- Green 0
- Green 1
- Blue 0
- Blue 1
You then need to decide which rows these 32bits will show up on. That’s where the ROW A, B, C and D lines come into play. Since there are four row select lines, you are able to have 16 different combinations to select the rows. So here’s all the combinations and the resulting Rows your Red, Green and Blue data will end up:
DCBA 0000 - (R0, G0 AND B0 WILL BE ON ROW 0) (R1, G1 AND B1 WILL BE ON ROW 16) 0001 - (R0, G0 AND B0 WILL BE ON ROW 1) (R1, G1 AND B1 WILL BE ON ROW 17) 0010 - (R0, G0 AND B0 WILL BE ON ROW 2) (R1, G1 AND B1 WILL BE ON ROW 18) 0011 - (RO, G0 AND B0 WILL BE ON ROW 3) (R1, G1 AND B1 WILL BE ON ROW 19) 0100 - (R0, G0 AND B0 WILL BE ON ROW 4) (R1, G1 AND B1 WILL BE ON ROW 20) 0101 - (R0, G0 AND B0 WILL BE ON ROW 5) (R1, G1 AND B1 WILL BE ON ROW 21) 0110 - (R0, G0 AND B0 WILL BE ON ROW 6) (R1, G1 AND B1 WILL BE ON ROW 22) 0111 - (R0, G0 AND B0 WILL BE ON ROW 7) (R1, G1 AND B1 WILL BE ON ROW 23) 1000 - (R0, G0 AND B0 WILL BE ON ROW 8) (R1, G1 AND B1 WILL BE ON ROW 24) 1001 - (R0, G0 AND B0 WILL BE ON ROW 9) (R1, G1 AND B1 WILL BE ON ROW 25) 1010 - (R0, G0 AND B0 WILL BE ON ROW 10) (R1, G1 AND B1 WILL BE ON ROW 26) 1011 - (R0, G0 AND B0 WILL BE ON ROW 11) (R1, G1 AND B1 WILL BE ON ROW 27) 1100 - (R0, G0 AND B0 WILL BE ON ROW 12) (R1, G1 AND B1 WILL BE ON ROW 28) 1101 - (R0, G0 AND B0 WILL BE ON ROW 13) (R1, G1 AND B1 WILL BE ON ROW 29) 1110 - (R0, G0 AND B0 WILL BE ON ROW 14) (R1, G1 AND B1 WILL BE ON ROW 30) 1111 - (R0, G0 AND B0 WILL BE ON ROW 15) (R1, G1 AND B1 WILL BE ON ROW 31)
Hopefully you can see that when we draw the screen, we are drawing two rows at the same time, and these two rows are 16 rows away from each other. The first rows to be drawn will be the top row and middle(ish) row. Then we keep moving down by one row but always keeping a 16 row separation between the two drawn rows.
So back to my code, first up I am loading some data that was stored in an array, into a temp location so I can use it in my routine to draw pixels on the screen.
Note – Each TempData location holds 32bits (which is perfect for a 32 pixel wide display)
For Y = 0 To 15 TempData0 = OutputDataRed(Y) TempData1 = OutputDataRed(Y + 16) TempData2 = OutputDataGreen(Y) TempData3 = OutputDataGreen(Y + 16) TempData4 = OutputDataBlue(Y) TempData5 = OutputDataBlue(Y + 16)
Notice how I am using a for loop? Also notice that the for loop will run this code 16 times (Starting from 0 and then incrementing by 1 each time until we reach 15). This lines up with the number of rows we will cycle through on the screen. Remember we draw two rows at a time since the screen is made of 32 rows, we only need to run through the code 16 times.
Can you also see that I am loading into TempData1, the OutputDataRed component that is 16 steps away from what got loaded into TempData0? And then the same again for green then blue.
This is because we are drawing two rows at a time and the second row we’re drawing is always 16 rows apart from the first row.
Once this data is loaded into the easy to access TempData locations, we need to serially send the six lots of 32 bits into the display:
For X = 0 To 31 RedData0 = TempData0.bits(31 - X) RedData1 = TempData1.bits(31 - X) GreenData0 = TempData2.bits(31 - X) GreenData1 = TempData3.bits(31 - X) BlueData0 = TempData4.bits(31 - X) BlueData1 = TempData5.bits(31 - X) CLK = 1 CLK = 0 Next
To do this, we need another for loop. This time the for loop will run the code 32 times. Each time it will send through one bit from the TempData variables. Basically I tell the port pins which bits to be present on them, then I send a clock pulse which is connected to all the shift registers within the 32×32 matrix. I do this 32 times and then all the 32 bits of Red, Green and Blue for the first row will be loaded in aswell as the 32 bits of Red, Green and Blue for the second row.
Note – the reason I have written in
(31 - X)
in the code is because if I just had
(X)
the image would be drawn backwards. (Essentially I need to send the last bit first, and the first bit last)
Now that we have sent in all six lots of 32 bits (2 lines for Red, 2 lines for Green and 2 lines for Blue) We then need to tell the matrix which rows to show this data up on. That’s where the ROW A,B,C and D lines come in (remember, there are 16 combinations of 1’s and 0’s on four lines)
So here is the code that tells the data which lines to show up on:
RowA = Y.bits(0) RowB = Y.bits(1) RowC = Y.bits(2) RowD = Y.bits(3) Latch = 1 Latch = 0 OutputEnable = 0 DelayUS(150) OutputEnable = 1 Next End Sub
First it will put a four bit number on the Row A, B, C and D lines. It get’s this info from the Y variable. Let’s assume this is the first time that we have run this code (I.E. Y = 0) this means that the four bit binary representation of 0 is 0000. So all Row lines will have a zero on them.
Looking at the Row table above, you can see that with 0000 on the row lines, it will place the newly loaded RowData0 on the very top row and then RowData1, 16 rows down from that. Let’s say that we had been running through this loop a number of times now and Y was 10. that means that the binary number would be 1010. These digits would then be sent to Row A, B, C and D – this would then send the loaded RowData0 to line 10 and then RowData1 16 rows away at line 26.
So we need to latch it in to do this we have this piece of code:
Latch = 1 Latch = 0
Now were are nearly there! The data is now in place. All that’s left to do is enable the tri-state buffers connected to all these LED’s – to do this you just need this line of code:
OutputEnable = 0
The LED’s will now turn on with their data. We hold it there for just a short while and then turn them off again, with this code:
DelayUS(150) OutputEnable = 1
Then we need to loop back and do it again 15 more times!
Next End Sub
That’s how to draw to the display, so in order to display what you want, you just need to fill up your Red, Green and Blue arrays with a whole bunch of 1’s and 0’s. This is how I declare them:
Dim OutputDataRed(32) As LongWord Dim OutputDataGreen(32) As LongWord Dim OutputDataBlue(32) As LongWord
I then make sure all of the data contained in this array is cleared when I first start. Here’s how we do that:
Sub ClearScreen() For X = 0 To 31 OutputDataRed(X) = 0 OutputDataGreen(X) = 0 OutputDataBlue(X) = 0 Next End Sub
If you wanted to simply show a single red dot in the top right of the screen, all you would need to do is this:
OutputDataRed(0) = %00000000000000000000000000000001
By the way, OutputDataRed(0) is the top row of the screen and OutputDataRed(31) is the bottom row. (Same goes for green and blue). So the code above has now given us a single red pixel on the screen.
If you wanted a blue box that outlines the screen you would do this:
OutputDataBlue(00) = %11111111111111111111111111111111 OutputDataBlue(01) = %10000000000000000000000000000001 OutputDataBlue(02) = %10000000000000000000000000000001 OutputDataBlue(03) = %10000000000000000000000000000001 OutputDataBlue(04) = %10000000000000000000000000000001 OutputDataBlue(05) = %10000000000000000000000000000001 OutputDataBlue(06) = %10000000000000000000000000000001 OutputDataBlue(07) = %10000000000000000000000000000001 OutputDataBlue(08) = %10000000000000000000000000000001 OutputDataBlue(09) = %10000000000000000000000000000001 OutputDataBlue(10) = %10000000000000000000000000000001 OutputDataBlue(11) = %10000000000000000000000000000001 OutputDataBlue(12) = %10000000000000000000000000000001 OutputDataBlue(13) = %10000000000000000000000000000001 OutputDataBlue(14) = %10000000000000000000000000000001 OutputDataBlue(15) = %10000000000000000000000000000001 OutputDataBlue(16) = %10000000000000000000000000000001 OutputDataBlue(17) = %10000000000000000000000000000001 OutputDataBlue(18) = %10000000000000000000000000000001 OutputDataBlue(19) = %10000000000000000000000000000001 OutputDataBlue(20) = %10000000000000000000000000000001 OutputDataBlue(21) = %10000000000000000000000000000001 OutputDataBlue(22) = %10000000000000000000000000000001 OutputDataBlue(23) = %10000000000000000000000000000001 OutputDataBlue(24) = %10000000000000000000000000000001 OutputDataBlue(25) = %10000000000000000000000000000001 OutputDataBlue(26) = %10000000000000000000000000000001 OutputDataBlue(27) = %10000000000000000000000000000001 OutputDataBlue(28) = %10000000000000000000000000000001 OutputDataBlue(29) = %10000000000000000000000000000001 OutputDataBlue(30) = %10000000000000000000000000000001 OutputDataBlue(31) = %11111111111111111111111111111111
Note – I purposely wrote the code out the long way above just to help you visualize.
If you wanted a yellow face in the top left corner, you would need to write the same data for green and red:
OutputDataRed(00) = %0111111000000000000000000000000 OutputDataRed(01) = %1000000100000000000000000000000 OutputDataRed(02) = %1010010100000000000000000000000 OutputDataRed(03) = %1000000100000000000000000000000 OutputDataRed(04) = %1010010100000000000000000000000 OutputDataRed(05) = %1001100100000000000000000000000 OutputDataRed(06) = %1000000100000000000000000000000 OutputDataRed(07) = %0111111000000000000000000000000 OutputDataGreen(00) = %0111111000000000000000000000000 OutputDataGreen(01) = %1000000100000000000000000000000 OutputDataGreen(02) = %1010010100000000000000000000000 OutputDataGreen(03) = %1000000100000000000000000000000 OutputDataGreen(04) = %1010010100000000000000000000000 OutputDataGreen(05) = %1001100100000000000000000000000 OutputDataGreen(06) = %1000000100000000000000000000000 OutputDataGreen(07) = %0111111000000000000000000000000
Hopefully these examples give you an idea of how it all works.
And now onto how the PS2 Mouse works.
The PS2 mouse was quite hard to get to work. I found all sorts of web sites on the topic although they all seemed to make it all to confusing. Eventually I figured it out with the help of some Arduino code I found HERE. Thankyou to the person who wrote that!
So how does it work?
That’s a good question. Have you ever tried to connect a PS2 mouse to a computer when the computer is already turned on. It doesn’t actually work – the reason is because the computer first needs to send some commands to the mouse to essentially tell the mouse to start transmitting data. Without these commands, the mouse won’t do anything.
I found some good info HERE that details a bit about the commands that you can send to the PS2 mouse.
Here is how I got my microcontroller to get the mouse to send data:
- Tell the mouse to reset itself by sending the command ‘FF’
- Tell the mouse to operate in read mode by sending the command ‘F0’
- Tell the mouse that we want to change the sample rate by sending the command ‘F3’
- Set sample rate to decimal 200 by sending the hex equivalent value of ‘C8’
- Tell the mouse that we want to change the resolution by sending the command ‘E8’
- Set the resolution to the lowest setting (1 count per mm) by sending the hex value ’00’
If you don’t mind the default sample rate and resolution, you can get rid of steps 3, 4, 5 and 6.
You may say – ‘well that sounds simple enough!’ well unfortunately it wasn’t. I needed to work out how to talk to the mouse and how to listen to the mouse.
The PS2 mouse has a bi-directional serial data bus and a bi-directional clock. The microcontroller needs to keep changing it’s port pins between being inputs and outputs (depending on if we are sending data to the mouse or receiving from the mouse.)
First up, here are the PORT and TRIS nicknames for the mouse (we need the TRIS because sometimes we need to make the PORT pins inputs and sometimes outputs.)
Dim MouseClockTris As TRISC.5 Dim MouseDataTris As TRISC.6 Dim MouseClock As PORTC.5 Dim MouseData As PORTC.6
Here’s the code I used to send a command to the mouse:
Sub InitMouse2(InstructionToSend As Word) MouseClock = 1 // make sure we start with a logic 1 MouseData = 1 // make sure we start with a logic 1 MouseClockTris = 0 // allow the microcontroller to control the clock line MouseDataTris = 0 // allow the microcontroller to control the data line DelayUS(100) // need to hold it for 100uS MouseClock = 0 // then set the clock line low to tell the mouse we want to talk to it DelayUS(100) // hold it for 100uS MouseData = 0 // and then we set the data line low (part of the communication process) DelayUS(5) // hold it for 5uS MouseClock = 1 // set the clock line back to a 1 (we will then make it an input and wait for the mouse to make it low again MouseClockTris = 1 // set the clock line to an input to allow the mouse to take control DelayUS(5) // we need a slight delay here just to give the mouse some time to take control of the clock pin While MouseClock <> 0 // wait for the mouse to make it a 0 // do nothing Wend // now the mouse has caused the clock to go low, we can start sending data. For X = 0 To 9 // we send 10 bits, 1 byte of data + odd parity bit + logic 1 stop bit MouseData = instructiontosend.bits(X) While MouseClock <> 1 // wait for clock to go high Wend While MouseClock <> 0 // and now wait for it to go low again... Wend Next While MouseClock <> 1 // wait until the mouse makes the clock line high Wend End Sub
Firstly, the microcontroller needs to set the clock and data lines high and these two port pins as outputs. We then hold the two lines high for 100uS:
MouseClock = 1 MouseData = 1 MouseClockTris = 0 MouseDataTris = 0 DelayUS(100)
Then pull the MouseClock line low and hold it for 100uS, this indicates to the mouse that we want to talk to it:
MouseClock = 0 DelayUS(100)
Then pull the MouseData line low and hold it for 5uS (this is still part of the mouses communication protocol):
MouseData = 0 DelayUS(5)
The mouse should now understand that we want to talk to it. The last thing we do before giving control to the mouse is set the MouseClock back to a logic 1. (the mouse will make it low when it is ready to acknowledge that it now has control of the communication process):
MouseClock = 1
Then we set the microcontroller MouseClock pin to an input to allow the mouse to send us a reply. We then pause for 5uS to give the mouse time to get all set up:
MouseClockTris = 1 DelayUS(5)
And now we wait in a constant loop until the mouse pulls the MouseClock line low. Basically this code keeps checking the MouseClock pin until the mouse makes it a logic 0. I.E. if it does not equal 0, it will stay in the loop. When it does equal zero, it will move on:
While MouseClock <> 0 Wend
Now that the mouse has acknowledged that we want to send it data, we can actually start to send that data. We need to send it an eight bit command, followed by an ODD parity bit and then finally a stop bit which is always a logic 1. So if we wanted to send the reset command, we would have called the InitMouse2 routine like this:
InitMouse2(%1111111111)
This will pass the binary number 1111111111 into the sub routine InitMouse2. The sub routine InitMouse2 will then refer to this binary number as InstructionToSend and is declared as a word (which can hold 16bits). Just for clarification, here is the heading for the subroutine InitMouse2:
Sub InitMouse2(InstructionToSend As Word)
If we break down all those logic 1’s. We are sending the command ‘FF’ which in binary is 11111111. Then we need to add to the left of that an ODD parity bit. Since there is an even number of 1’s, we need to make the parity bit a 1 to make it an odd number of 1’s. Then finally we always need to add a stop bit of 1 to the left of the parity bit. This now gives us ten binary digits: 1111111111 and this is now contained within the variable InstructionToSend:
For X = 0 To 9 MouseData = instructiontosend.bits(X) While MouseClock <> 1 // wait for clock to go high Wend While MouseClock <> 0 // and now wait for it to go low again... Wend Next
The above for loop, sends out each of these ten bits one after the other along the serial data bus (MouseData). We send the least significant bit first. We need to stay in the loop ten times (because there are ten bits to send I.E. bits 0 to 9)
For X = 0 To 9 MouseData = instructiontosend.bits(X)
Now that the first bit is present on the MouseData line, we need to wait for the mouse clock to synchronise the data. Once the mouse causes the clock line to go high and then low again, we can go to the next bit I.E. bit 1, then again we will wait for the mouse clock to go high and then low and then loop around again for bit 2 etc…
While MouseClock <> 1 Wend While MouseClock <> 0 Wend Next
Once all bits have been sent, we will exit the for loop. Â Then we wait for the mouse to make the clock line low again:
While MouseClock <> 1 Wend
And that is how we send a command to the mouse!
Here is the code to send all the commands to the mouse that I used in conjunction with the 32×32 RGB LED Matrix:
sub InitMouse() InitMouse2(%1111111111) // reset mouse by sending $FF (then an odd parity bit of 1 and then stop bit of 1) InitMouse2(%1111110000) // tell the mouse to operate in read mode by sending $F0 (odd parity bit of 1 and stop bit of 1) InitMouse2(%1111110011) // tell mouse we want to set sample rate by sending $F3 (odd parity bit of 1 and stop bit of 1) InitMouse2(%1011001000) // then set sample rate to decimal 200 by sending $c8 (odd parity bit of 1 and stop bit of 1) InitMouse2(%1111101000) // tell mouse we want to set resolution by sending $E8 (odd parity bit of 1 and stop bit of 1) InitMouse2(%1100000000) // then set resolution to the lowest (1 count per 1mm) (odd parity bit of 1 and stop bit of 1) end sub
Remember you only need the first two commands, to get the mouse to run in receive mode:
sub InitMouse() InitMouse2(%1111111111) // reset mouse by sending $FF (then an odd parity bit of 1 and then stop bit of 1) InitMouse2(%1111110000) // tell the mouse to operate in read mode by sending $F0 (odd parity bit of 1 and stop bit of 1) end sub
Now that we have told the mouse to operate in receive mode, we then request data from it whenever we want to by sending the command ‘EB’. Once the mouse recognises this command, it will send us four bytes of data. The first byte is an acknowledgment byte which should always be ‘FA’. It’s the next three bytes that we want to store:
- The second byte contains the x overflow flag, y overflow flag, y sign bit, x sign bit, always 1, middle button status, right button status and left button status. The ones we would be most concerned with here is probably just bit 0 and bit 1 which are our left and right mouse buttons.
- The third byte gives us the x direction of the mouse in 2’s compliment form If the mouse is still, this byte will be 00000000. If we move it left or right, we will get a number proportional to how fast we are moving it. I.E. the faster you move it, the higher the number.
- The fourth byte gives us the y direction of the mouse in 2’s compliment form. It works the same as the third byte.
Here is the code to receive and store these three bytes of data:
Sub SaveMouseData() InitMouse2(%1111101011) // request data from mouse ReceiveMouseData // this first one should always give back %11111010 ($FA) because this is the mouses first response which means acknowledge ReceiveMouseData // this second one is like this (Y overflow, X overflow, Y sign bit, X sign bit, always 1, middle button, right button, left button) MouseByte(0) = MouseDataIn ReceiveMouseData // this third one is the x direction of the mouse (2s compliment form) MouseByte(1) = MouseDataIn ReceiveMouseData // this fourth on is the y direction of the mouse (2s compliment form) MouseByte(2) = MouseDataIn End Sub
We first send the command ‘EB’ to the mouse to tell it to send us data:
InitMouse2(%1111101011) // request data from mouse
Then we have another sub routine that handles receiving data from the mouse. I.E. as soon as we have sent the command to the mouse to send us data, we need to straight away get ready to receive the reply:
ReceiveMouseData
The first time we call this, we don’t need to save it because the mouse always responds with an acknowledgement reply (which will always be ‘FA’ if everything went to plan)
We then call receive data again and this time we want to save the data:
ReceiveMouseData MouseByte(0) = MouseDataIn
We then want to receive the next byte and save it:
ReceiveMouseData MouseByte(1) = MouseDataIn
And finally, the last byte:
ReceiveMouseData MouseByte(2) = MouseDataIn
Here is the code that handles receiving data from the mouse:
Sub ReceiveMouseData() MouseClock = 1 MouseData = 1 MouseClockTris = 0 MouseDataTris = 0 DelayUS(5) MouseClockTris = 1 // set it back as an input and then wait for the mouse to take control DelayUS(5) While MouseClock <> 0 // keep waiting until the mouse sends the clock line low Wend DelayUS(5) While MouseClock <> 1 // wait for the mouse to send the clock high Wend MouseDataTris = 1 // to receive data, we need to make the data line an input // now that the clock has gone high, we need to start reading the incoming data For X = 0 To 7 While MouseClock <> 0 // wait for it to go to a 0 Wend MouseDataIn.bits(X) = MouseData While MouseClock <> 1 // wait for it to go to a 1 Wend Next // grab the parity bit (and ignore it) While MouseClock <> 0 // wait for it to go to a 0 Wend While MouseClock <> 1 // wait for it to go to a 1 Wend // grab the stop bit (and ignore it) While MouseClock <> 0 // wait for it to go to a 0 Wend While MouseClock <> 1 // wait for it to go to a 1 Wend MouseClockTris = 0 MouseClock = 0 End Sub
It is quite similar to the previous code except that this time we allow the mouse to send us data on the MouseData line, we then receive the data bit by bit and then save it to a global variable MouseDataIn.
And that’s how I got the screen and mouse working!
Here is the complete Swordfish Basic code for my Bouncing Balls with mouse painting program (download also provided at the bottom of this page). Please note that this program also uses the RandGen2.bas file which you can get HERE
Code Designed to run straight on the PICnDuino or PIC18F25K20 with a 16Mhz external Oscillator Device = 18F25K20 //Automatically brings in device file 18F25K22.bas Clock = 64 //64MHz (top speed) Config FOSC = HSPLL //tells PIC to use external high speed (crystal) oscillator, medium power Include "utils.bas" // we are including an extra file here which allows us to use shortcuts Include "RandGen2.bas" Structure Ball Color As Byte X As Byte Y As Byte XDelay As Byte XDelay2 As Byte YDelay As Byte YDelay2 As Byte GoingUp As Boolean GoingLeft As Boolean MaxJumpHeight As Byte CurrentJumpHeight As Byte End Structure // Variables Dim Multiball(20) As Ball Dim X As Byte Dim Y As Byte Dim MouseDelay As Byte Dim TempData0 As LongWord Dim TempData1 As LongWord Dim TempData2 As LongWord Dim TempData3 As LongWord Dim TempData4 As LongWord Dim TempData5 As LongWord Dim OutputDataRed(32) As LongWord Dim OutputDataGreen(32) As LongWord Dim OutputDataBlue(32) As LongWord Dim MouseDataRed(32) As LongWord Dim MouseDataGreen(32) As LongWord Dim MouseDataBlue(32) As LongWord Dim MouseDataBarrier(32) As LongWord Dim MouseByte(3) As Byte Dim MouseDataIn As Byte Dim PaintColor As Byte // Port Setup Dim RedData0 As PORTB.0 Dim RedData1 As PORTB.1 Dim GreenData0 As PORTB.2 Dim GreenData1 As PORTB.3 Dim BlueData0 As PORTB.4 Dim BlueData1 As PORTB.5 Dim Latch As PORTB.6 Dim OutputEnable As PORTB.7 Dim RowA As PORTC.0 Dim RowB As PORTC.1 Dim RowC As PORTC.2 Dim RowD As PORTC.3 Dim CLK As PORTC.4 Dim MouseClockTris As TRISC.5 Dim MouseDataTris As TRISC.6 Dim MouseClock As PORTC.5 Dim MouseData As PORTC.6 Dim MouseCursorX As Byte Dim MouseCursorY As Byte // Sub Routines Sub BounceTheBall() Dim TempBall As Ball For X = 0 To Bound(Multiball) TempBall = Multiball(X) If TempBall.XDelay <> 0 Then TempBall.XDelay = TempBall.XDelay - 1 Else TempBall.XDelay = TempBall.XDelay2 If TempBall.GoingLeft = false Then TempBall.X = TempBall.X + 1 If MouseDataBarrier(tempball.X + 1).bits(tempball.Y) = 1 Then // check to see if there is a wall to the right of us TempBall.GoingLeft = true EndIf If tempball.X > 31 Then // if we have been moving right and we go out of the right hand side of the screen, then make it appear at the left side. tempball.X = 0 EndIf Else TempBall.X = TempBall.X - 1 If MouseDataBarrier(tempball.X - 1).bits(tempball.Y) = 1 Then // check to see if there is a wall to the left of us TempBall.GoingLeft = false EndIf If tempball.X > 128 Then // if we have been moving left and we go out of the left hand side of the screen, then make it appear at the right side. (going left will eventually go below 0 to 255, so I just check if its greater than 128 but I could have probably said if it equals 255 etc.. tempball.X = 31 EndIf EndIf EndIf If TempBall.YDelay <> 0 Then TempBall.YDelay = TempBall.YDelay - 1 Else If TempBall.GoingUp = false Then // if the ball is going down If TempBall.YDelay2 > 0 Then // only take one away if we are still greater than 0 (0 is the fastest we can get because its the lowest delay) TempBall.YDelay2 = TempBall.YDelay2 - 1 EndIf TempBall.YDelay = TempBall.YDelay2 TempBall.Y = TempBall.Y + 1 Else // if the ball is not going down, then it must be going up - so we need to slow the ball down TempBall.YDelay2 = TempBall.YDelay2 + 1 TempBall.YDelay = TempBall.YDelay2 TempBall.Y = TempBall.Y - 1 TempBall.CurrentJumpHeight = TempBall.CurrentJumpHeight + 1 If MouseDataBarrier(tempball.X).bits(tempball.Y - 1) = 1 Then TempBall.GoingUp = false EndIf EndIf If MouseDataBarrier(tempball.X).bits(tempball.Y + 1) = 1 And tempball.GoingUp = false Then // Check the pixel just below us to see if we can bounce off of it TempBall.YDelay = 0 TempBall.YDelay2 = 0 TempBall.GoingUp = true TempBall.CurrentJumpHeight = 0 End If If tempball.Y > 31 Then // if its gone below the bottom of the screen, then send it back to the top! tempball.Y = 0 EndIf If TempBall.GoingUp = true And TempBall.CurrentJumpHeight >= TempBall.MaxJumpHeight Then TempBall.GoingUp = false EndIf EndIf Multiball(X) = TempBall Next End Sub Sub InitMouse2(InstructionToSend As Word) MouseClock = 1 // make sure we start with a logic 1 MouseData = 1 // make sure we start with a logic 1 MouseClockTris = 0 // allow the microcontroller to control the clock line MouseDataTris = 0 // allow the microcontroller to control the data line DelayUS(100) // need to hold it for 300uS MouseClock = 0 // then set the clock line low to tell the mouse we want to talk to it DelayUS(100) // hold it for 300uS MouseData = 0 // and then we set the data line low (part of the communication process) DelayUS(5) // hold it for 10uS MouseClock = 1 // set the clock line back to a 1 (we will then make it an input and wait for the mouse to make it low again MouseClockTris = 1 // set the clock line to an input to allow the mouse to take control DelayUS(5) // we need a slight delay here just to give the mouse some time to take control of the clock pin and to allow the mouse to be the one who makes the clock line 0. (without a delay, the microcontroller will think that the mouse has taken control and it will move onto the next piece of code - before the mouse is ready for the data) While MouseClock <> 0 // wait for the mouse to make it a 0 //do nothing Wend // now the mouse has caused the clock to go low, we can start sending data. For X = 0 To 9 // we send 10 bits, 1 byte of data + odd parity bit + logic 1 stop bit MouseData = instructiontosend.bits(X) While MouseClock <> 1 // wait for clock to go high Wend While MouseClock <> 0 // and now wait for it to go low again... Wend Next While MouseClock <> 1 // wait for the mouse to make this line high again Wend End Sub Sub ReceiveMouseData() MouseClock = 1 MouseData = 1 MouseClockTris = 0 MouseDataTris = 0 DelayUS(5) MouseClockTris = 1 //set it back as an input and then wait for the mouse to take control DelayUS(5) While MouseClock <> 0 // keep waiting until the mouse sends the clock line low Wend DelayUS(5) While MouseClock <> 1 // wait for the mouse to send the clock high Wend MouseDataTris = 1 // to receive data, we need to make the data line an input // now that the clock has gone high, we need to start reading the incoming data For X = 0 To 7 While MouseClock <> 0 // wait for it to go to a 0 Wend MouseDataIn.bits(X) = MouseData While MouseClock <> 1 // wait for it to go to a 1 Wend Next // grab the parity bit (and ignore it) While MouseClock <> 0 // wait for it to go to a 0 Wend While MouseClock <> 1 // wait for it to go to a 1 Wend // grab the stop bit (and ignore it) While MouseClock <> 0 // wait for it to go to a 0 Wend While MouseClock <> 1 // wait for it to go to a 1 Wend MouseClockTris = 0 MouseClock = 0 End Sub Sub SaveMouseData() InitMouse2(%1111101011) // request data from mouse ReceiveMouseData // this first one should always give back %11111010 ($FA) because this is the mouses first response which means acknowledge ReceiveMouseData // this second one is like this (Y overflow, X overflow, Y sign bit, X sign bit, always 1, middle button, right button, left button) MouseByte(0) = MouseDataIn ReceiveMouseData // this third one is the x direction of the mouse (2s compliment form) MouseByte(1) = MouseDataIn ReceiveMouseData // this fourth on is the y direction of the mouse (2s compliment form) MouseByte(2) = MouseDataIn End Sub // All we need to do is store 32 long words in our output data registers, and this routine will take care of displaying them on the screen! Sub DrawGraphics() For Y = 0 To 15 TempData0 = OutputDataRed(Y) TempData1 = OutputDataRed(Y + 16) TempData2 = OutputDataGreen(Y) TempData3 = OutputDataGreen(Y + 16) TempData4 = OutputDataBlue(Y) TempData5 = OutputDataBlue(Y + 16) For X = 0 To 31 RedData0 = TempData0.bits(31 - X) RedData1 = TempData1.bits(31 - X) GreenData0 = TempData2.bits(31 - X) GreenData1 = TempData3.bits(31 - X) BlueData0 = TempData4.bits(31 - X) BlueData1 = TempData5.bits(31 - X) CLK = 1 CLK = 0 Next RowA = Y.bits(0) RowB = Y.bits(1) RowC = Y.bits(2) RowD = Y.bits(3) Latch = 1 Latch = 0 OutputEnable = 0 DelayUS(150) OutputEnable = 1 Next End Sub Sub ClearScreen() For X = 0 To 31 OutputDataRed(X) = 0 OutputDataGreen(X) = 0 OutputDataBlue(X) = 0 Next End Sub Sub MoveMouseCursor() MouseCursorX = MouseCursorX + MouseByte(1) If MouseCursorX > 31 And MouseCursorX < 128 Then // if the cursor goes past the screen max (31 pixels) we want to hold it at 31 pixels (we need to check that its also less than 128 because if we go past screen min (pixel 0) the mousecursor will go to 255. MouseCursorX = 31 ElseIf MouseCursorX > 128 Then MouseCursorX = 0 EndIf MouseCursorY = MouseCursorY - MouseByte(2) If MouseCursorY > 31 And MouseCursorY < 128 Then MouseCursorY = 31 ElseIf MouseCursorY > 128 Then MouseCursorY = 0 EndIf If MouseByte(0).bits(0) = 1 Then // draw if only left button pressed MouseDataBarrier(MouseCursorX).bits(MouseCursorY) = 1 // any color, we want to be a barrier\ MouseDataRed(MouseCursorX).bits(MouseCursorY) = 1 EndIf If MouseByte(0).bits(1) = 1 Then MouseDataBarrier(MouseCursorX).bits(MouseCursorY) = 0 // any color, we want to be a barrier\ MouseDataRed(MouseCursorX).bits(MouseCursorY) = 0 EndIf If MouseByte(0).bits(0) = 1 And MouseByte(0).bits(1) = 1 Then // clear the screen if both buttons pressed For X = 0 To 31 MouseDataRed(X) = 0 MouseDataGreen(X) = 0 MouseDataBlue(X) = 0 MouseDataBarrier(X) = 0 Next EndIf End Sub Sub SaveGraphicData() ClearScreen OutputDataRed(MouseCursorX).bits(MouseCursorY) = 1 OutputDataGreen(MouseCursorX).bits(MouseCursorY) = 1 OutputDataBlue(MouseCursorX).bits(MouseCursorY) = 1 For X = 0 To 31 OutputDataRed(X) = OutputDataRed(X) Or MouseDataRed(X) OutputDataGreen(X) = OutputDataGreen(X) Or MouseDataGreen(X) OutputDataBlue(X) = OutputDataBlue(X) Or MouseDataBlue(X) For Y = 0 To Bound(Multiball) If Multiball(Y).X = X Then OutputDataRed(X).bits(Multiball(Y).Y) = Multiball(Y).Color.bits(0) OutputDataGreen(X).bits(Multiball(Y).Y) = Multiball(Y).Color.bits(1) OutputDataBlue(X).bits(Multiball(Y).Y) = Multiball(Y).Color.bits(2) EndIf Next Next End Sub Sub InitBalls() For X = 0 To Bound(Multiball) Multiball(X).X = rand() Multiball(X).Y = rand() / 1 Multiball(X).YDelay = (rand() / 4 + 1) Multiball(X).YDelay2 = Multiball(X).YDelay Multiball(X).MaxJumpHeight = 5 Multiball(X).GoingLeft = false Multiball(X).GoingUp = false Multiball(X).XDelay = (rand() / 4 + 1) Multiball(X).XDelay2 = Multiball(X).XDelay Multiball(X).Color = X If Multiball(X).Color = 0 Then Multiball(X).Color = 7 EndIf Next End Sub Sub ClearMouseData() For X = 0 To 31 MouseDataRed(X) = 0 MouseDataGreen(X) = 0 MouseDataBlue(X) = 0 MouseDataBarrier(X) = 0 Next End Sub Sub InitMouse() InitMouse2(%1111111111) // reset mouse by sending $FF (then an odd parity bit of 1 and then stop bit of 1) InitMouse2(%1111110000) // tell the mouse to operate in read mode by sending $F0 (odd parity bit of 1 and stop bit of 1) InitMouse2(%1111110011) // tell mouse we want to set sample rate by sending $F3 (odd parity bit of 1 and stop bit of 1) InitMouse2(%1011001000) // then set sample rate to decimal 200 by sending $c8 (odd parity bit of 1 and stop bit of 1) InitMouse2(%1111101000) // tell mouse we want to set resolution by sending $E8 (odd parity bit of 1 and stop bit of 1) InitMouse2(%1100000000) // then set resolution to the lowest (1 count per 1mm) (odd parity bit of 1 and stop bit of 1) End Sub // Start Of Program... SetAllDigital TRISB = %00000000 TRISA = %00000000 TRISC = %00000000 SetRndMax(31) InitBalls ClearScreen ClearMouseData DelayMS(250) InitMouse MouseCursorX = 15 MouseCursorY = 12 // Main Loop While True() BounceTheBall SaveMouseData MoveMouseCursor SaveGraphicData DrawGraphics Wend
I hope you found this page interesting and informative!
Downloads
32x32LEDMatrixBouncyBallsWithPS2Mouse
Hi Ben,
Thanks for letting me know. It seems my download monitor plugin was corrupting all downloads on my site. I have removed it and all should be working now 🙂
Let me know if you have any hassles.
Awesome project and just got my matrix board from James (they were great and made things easy).
But the zip file for the code download appears to be broken. It downloads as a zip file, but is just a message that the page/file can’t be found. Can you double check this and update the link?
Hi,
first off all great project and work with the display.
I have a project in mind that would make use of this kind of displays.
Do you have any idea or expirience on chaining these together and displaying content? (unanimated pictures would be enough)
I was thinking on chaining 4 Pieces of 16X16 P20 displays together for an dynamic PixelArt-WallArt. This way I’d get a decent sized one for my living room.
Thanks in advance,
Jakamoto
Hi Jakamoto, are you looking perhaps at something like this:
http://ledpixelart.com/
Hi and thanks for your fast response! You’re right, thats basically what I’m trying to achieve. I know the Ledpixelart, but I find the size quite small (the image area is only 19.7×19.7 cm) and I’d like to put a “waffle grid” for seperating the LED-Pixels (no light bleed-through) and have a diffusor plate in front. The size I’m aiming for is minimum around 40×40 cm, with the 4 16×16 P20 Displays it would turn out just nice. The only thing I’m missing is the kowledge if chaining and driving it with a µC as you did would be possible.… Read more »
You sure can chain them together using just one microcontroller however there are limits to this due to the speed that the screens need to refresh at to avoid flicker VS the speed that you can get all the bits sent to the screens ready for displaying the data. Another thing to think about is the number of colors you would like the screen to be able to display. I have been able to achieve 512 colors from a 16×32 RGB matrix but then when I tried this with a 32×32 pixel RGB matrix, there was too much flicker (due… Read more »
Hello,
Very nice project! Can you please provide the link to the actual module that you’re using? I’m not sure if I’ve located the correct one on their page.
Thanks,
Bogda
Hi Bogda, I didn’t get it directly from their site but just got in touch with them by email. The one that I got was the P5 32×32 RGB Matrix. It is 160mm x 160mm and has an LED spacing of 5mm. send an email to: salesb#wanzhouled,net (replace the # with @CHRISTOPHER SPOONER and the , with .) address it to James, and say that you got the email from Brad @CHRISTOPHER SPOONER bradsprojects.com and that you would like to buy a P5 32×32 RGB LED Matrix including shipping to where ever you live. I asked for the cheapest shipping… Read more »
Thanks a lot, that definitely helps!
Best,
Bogdan
(and yes, I’ve spelt my own name wrong the first time 🙂 )
I think we’ve all spelt our name wrong at some stage in our lives…
Regards,
Admi
[…] [Brad] just acquired a 32×32 RGB LED matrix and he jumped right into the deep end with his first project. To try out his skills on the device he used an Arduino to drive a slew of pixels with bouncing-ball physics. […]
[…] [Brad] just acquired a 32×32 RGB LED matrix and he jumped right into the deep end with his first project. To try out his skills on the device he used an Arduino to drive a slew of pixels with bouncing-ball physics. […]
[…] [Brad] just acquired a 32×32 RGB LED matrix and he jumped right into the deep end with his first project. To try out his skills on the device he used an Arduino to drive a slew of pixels with bouncing-ball physics. […]
[…] [Brad] just acquired a 32×32 RGB LED matrix and he jumped right into the deep end with his first project. To try out his skills on the device he used an Arduino to drive a slew of pixels with bouncing-ball physics. […]