Building a MIDI keytar

Active Member
Character LCD usually power up, before being initialised with ( 4x20 ) 2 rows of blank 'blocks' if you get that then nothing, ? then the initialisation is failing .. what bytes are you sending ? and what delays between bytes ? or are you reading busy ready ?
CS is low to select the slave , if the backlight is on then leave whatever you have .

Edit...(Easy )

INTERFACE WITH MPU IN SERIAL MODE When IM port input is "Low", serial interface mode is started. At this time, all three ports, SCLK (synchronizing transfer clock), SID (serial input data), and SOD (serial output data), are used. If you want to use SSD1803 with other chips, chip select port (CS) can be used. By setting CS to "Low", SSD1803 can receive SCLK input. If CS is set to "High", SSD1803 reset the internal transfer counter.

Before transfer real data, start byte has to be transferred. It is composed of succeeding 5 "High" bits, read write control bit (R/W), register selection bit (RS) and end bit that indicates the end of start byte. Whenever succeeding 5 "High" bits are detected by SSD1803, it makes serial transfer counter reset and ready to receive next information.

The next input data are register selection bit that determine which register will be used, and read write control bit that determine the direction of data. Then end bit is transferred, which must have "Low" value to show the end of start byte. (Refer to Figure 7-11 and Figure 7-12)

Write Operation (R/W = 0)

After start byte is transferred from MPU to SSD1803, 8-bit data is transferred which is divided into 2 bytes, each byte has 4 bit's real data and 4 bit's partition token data. For example, if real data is "10110001" (D0 - D7), then serially transferred data becomes "1011 0000 0001 0000" where 2nd and 4th 4 bits must be "0000" for safe transfer. To transfer several bytes continuously without changing RS bit and RW bit, start byte transfer is needed only at first starting time. Namely, after first start byte is transferred, real data can be transferred succeeding.

After start byte is transferred to SSD1803, MPU can receive 8-bit data through the SOD port at a time from the LSB. Wait time is needed to insert between start byte and data reading, because internal reading from RAM requires some delay. Continuous data reading is possible like serial write operation. It also needs only one start byte, only if you insert some delay between reading operations of each byte. During the reading operation, SSD1803 observes succeeding 5 "High" from MPU. If it is detected, SSD1803 restarts serial operation at once and ready to receive RS bit. So in continuous reading operation, SID port must be "Low".

Last edited:

MichaelaJoy

Active Member
I put a connector on the board so that I can use the logic analyzer as an SPI monitor. From that, I can see that I have something
wrong with the polarity and phase of the SPI clock.

The other thing is the data transfer. I'm sending high nybble first, then low nybble.

I'm going to do some tests this morning (Morning here...coffee time. )

Will let everybody know.

rjenkinsgb

Active Member
I can't swear to it, but I have a feeling that you must actively use the /CS input to start and end each bus transaction...

MichaelaJoy

Active Member
Here's my transmitter code.

Code:
            .equ    SPICommand,        #0xf8
.equ    SPIData,        #0xfa
.equ    SPIRcvData,        #0xfc
;
; SPI2Send:
;           W0: Data byte to send
;           W1: Command or Data
;
_SPI2Send:
mov.w    W1,W2
swap    W0                ;
clr.b    W0                ; Clear the upper byte of the command word
swap    W0                ;
mov.w    W0,W1            ; Save a copy

mov        W2,SPI2BUF        ; Write the command preamble to SPI2
_SPI2Send1:
mov        SPI2STAT,W0        ; Wait for transmit done
btst.z    W0,#1
bra        nz,_SPI2Send1

mov        W1,W0
and.b    W0,#0x0f,W0
swap.b    W0
mov        W0,SPI2BUF
_SPI2Send2:
mov        SPI2STAT,W0
btst.z    W0,#1
bra        nz,_SPI2Send2

mov        W1,W0
swap.b    W0
and.b    W0,#0x0f,W0
swap.b    W0

mov        W0,SPI2BUF
_SPI2Send3:
mov        SPI2STAT,W0
btst.z    W0,#1
bra        nz,_SPI2Send3

repeat    #32
nop
return
Usage:
Code:
            mov.w    #0x0034,W0
mov        #SPICommand,W1
call    _SPI2Send
rjenkinsgb: You may be correct. It doesn't seem to make any difference though.

Active Member
MJ .. Display solved ? From the chart above , the SPI interface would appear to be taking 3 bytes then 'addressing ' the LCD driver as a 4 bit device, I presume that is how your sending controls and data ..?

mov.w #0x0034,W0
Would suggest an 8 bit data entry ?

My raw LCD init stream starts with h'33 ( 8 bit mode ) h'28 (4 bit mood {0x33,0x32,0x28,0x08,0x0C,0x06,0x01};

Last edited:

MichaelaJoy

Active Member
granddad: It's worse than that. I did some snooping yesterday.

Their example is -very- wierd. It looks like they're doing a software SPI implementation. (???)

But it tells me a lot about the display unit, and what's needed to get it working.

Take a look at this code.

Code:
//-----------------------------------------------------------------------------------------
// Portkonstanten

#define DMRESPORT    PORTA
#define DMRES        0
#define DMCSPORT    PORTA
#define DMCS        1
#define DMCLKPORT    PORTA
#define DMCLK        2
#define DMSIDPORT    PORTA
#define DMSID        3
#define DMSODPIN    PINA
#define DMSOD        4

//-----------------------------------------------------------------------------------------
// Funktions Makros

#define TstBit(adr, bnr)    ( ((adr) &   (1 << (bnr))) > 0 )

//-----------------------------------------------------------------------------------------

void SPIout(char out)
{
char i = 8;

while(i-- > 0)
{ ClrBit(DMCLKPORT, DMCLK);
if(out & 1)
SetBit(DMSIDPORT, DMSID);
else
ClrBit(DMSIDPORT, DMSID);
out >>= 1;
_NOP();
_NOP();
SetBit(DMCLKPORT, DMCLK);
}
}

char SPIin(void)
{
char i = 8, in = 0;

while(i-- > 0)
{
ClrBit(DMCLKPORT, DMCLK);
_NOP();
_NOP();
_NOP();
_NOP();
_NOP();
SetBit(DMCLKPORT, DMCLK);
in >>= 1;
if(TstBit(DMSODPIN, DMSOD))
in |= 0x80;
}

return in;
}

//-----------------------------------------------------------------------------------------

void lcdbefout(char out)
{
ClrBit(DMCSPORT, DMCS);
SPIout(0x1F);
SPIout(out & 0x0F);
SPIout(out >> 4);
SetBit(DMCSPORT, DMCS);

if(out > 0  &&  out < 4)
waitms(3);
else
waitus(50);
}

void lcddatout(char out)
{
ClrBit(DMCSPORT, DMCS);
SPIout(0x5F);
SPIout(out & 0x0F);
SPIout(out >> 4);
SetBit(DMCSPORT, DMCS);

waitus(50);
}

//-----------------------------------------------------------------------------------------

GLOBAL void LcdClr(void)
{
lcdbefout(0x01);        // Display l”schen
}

GLOBAL void LcdInit(void)
{
ClrBit(DMRESPORT, DMRES);
waitms(2);
SetBit(DMRESPORT, DMRES);
waitms(50);

lcdbefout(0x34);        // Function Set: 8-Bit, Bit RE=1
lcdbefout(0x09);        // ext. Function Set: 4 Zeilen Modus
lcdbefout(0x30);        // Function Set: 8-Bit, Bit RE=0
lcdbefout(0x0C);        // Display On/Off: Display ein, Cursor aus
lcdbefout(0x06);        // Entry Mode Set: Cursor Auto-Increment

LcdClr();
}
I had to redo the display connector to add provisions for a reset line. I should have that finished today.

Please correct me if I'm wrong. With a crystal of 32 Mhz, Fcy should be 16 Mhz. That means that each nop should be about 62.5 ns.

That means 16 nops should give me a delay of 1 us.

If I'm right, that's how I'll build my delay routines.

Active Member
Please correct me if I'm wrong. With a crystal of 32 Mhz, Fcy should be 16 Mhz. That means that each nop should be about 62.5 ns. That means 16 nops should give me a delay of 1 us.
Looks good . pic24s have 2 clocks per cycle. the code init for 8 bit LCD data length ? but the chart suggest 4 bit . you can read busy ready if delays get too Yuck ... some commands need loads of time ( h'01 clear LCD )

Last edited:

MichaelaJoy

Active Member
TBH I'm not sure at this point. There's another question that gets raised;

To read status, you'd send five '1's, followed by the R/W bit and the RS bit. You would think that the proper byte to write
would be #0xf8 for status.

They're writing it in reverse-order. (0x3f)

That's something that needs to be looked into.

rjenkinsgb may be right. The CS may be needed to start and stop transactions.

Once I nail all of this stuff down, I'm going to create a post with all of the pertinent info.

Then, everybody will be able to benefit from the research.

MichaelaJoy

Active Member
I'm so frustrated with this display. No matter what I try, nothing seems to work.
At this point, I'm not sure if the display is blown up.

I sent an email off to the company with details of what I'm doing.
Hopefully, they'll spot something and let me know.

MichaelaJoy

Active Member
I think I might have made some headway. This subroutine seems to get data from the display.

Code:
_SPI2GetStatus:
mov        #0x8000,W0
mov        W0,SPI2STAT
mov        #0x003f,W0        ; This is the command word.
mov        W0,SPI2BUF
_SPI2GetStatus1:
mov        SPI2STAT,W0
btst.z    W0,#1
bra        nz,_SPI2GetStatus1

clr.w    W0
mov        W0,SPI2BUF
_SPI2GetStatus2:
mov        SPI2STAT,W0
btst.z    W0,#1
bra        nz,_SPI2GetStatus2
_SPI2GetStatus3:
mov        SPI2STAT,W0
btst.z    W0,#0
bra        z,_SPI2GetStatus3
mov        SPI2BUF,W0
return
In my reset code, I set the OE to low and left it there. So it only needs to be low to address the display unit.

There are a few things that need to be pointed out:

1 ) The command word is a mirror image of what is implied in the data sheet. That may be due to how SPI works.
I really don't know that much about it. Looking at the timing on the logic analyzer is confusing because it does not match the
documentation. But it appears to work (?)

2 ) After sending the command word, you -need- to send a zero. Then and only then will the display send the status.

3 ) The return value is also reversed. That means bit #0 is the actual status bit.

This is what I've found so far. Next, I'll attempt to apply what I've learned to the command transmitter routine.

More to follow...

Pommie

Well-Known Member
SPI can be sent either MSB or LSB first. So it looks like you have two devices that don't use the same bitness(??). I know pics default to MSB first - does that help with the timing diagram?

Mike.

Active Member
MJ , Guess those LCD wont be flying off the shelf . There seems little advantage using the SPI ? There are some character LCD designs just using an 8 bit shift register . I have used an I2C port expander MCP23008 / 17 ( MC do an SPI port expander ) and tried a pack pack module , but found making my own back pack much better .

MichaelaJoy

Active Member
SPI can be sent either MSB or LSB first. So it looks like you have two devices that don't use the same bitness(??). I know pics default to MSB first - does that help with the timing diagram?

Mike.
Pommie: That explains why the command byte needs to be backwards.

Yesterday (what seems to be a few hours ago. ) I sent an email off to Electronic Assemblies.

Their turnaround is lightning fast!

Herr Demmel suggested that my SPI baud rate may be too fast. I'm going to look into that today.

(After my first cup of coffee. )

MJ , Guess those LCD wont be flying off the shelf . There seems little advantage using the SPI ? There are some character LCD designs just using an 8 bit shift register . I have used an I2C port expander MCP23008 / 17 ( MC do an SPI port expander ) and tried a pack pack module , but found making my own back pack much better .
They look like really nice display units. If I can't get this working, it would be a shame.

MichaelaJoy

Active Member
So I built a "bit-banger" serial interface.

Here's the source code, in case anyone wants to take a look

Code:
                    .equ __24F32KA304,1
.include "p24F32KA304.inc"

.text

.global _SPIInitialize
.global _SPISendCmd
.global _SPISendData
.global _SPIGetStatus
.global _Delay_US

.equ    OELine,#9
.equ    SDILine,#3
.equ    SDOLine,#4
.equ    SCKLine,#5

.equ    SCLKDelay1,#64    ; 32 = 23.15uS
.equ    SCLKDelay2,(SCLKDelay1 + #13)

_Delay_US:
repeat    #14
nop
dec        w0,w0
cp        w0,#0
bra        gt,_Delay_US
return

_SPIInitialize:
mov.w    #0x1800,w0
mov.w    w0,SPI1CON1
nop

clr.w    w0
mov.w    w0,SPI1CON2
nop

mov        #0x8000,W3
mov        W3,SPI1STAT

mov.w    #0x1800,w0
mov.w    W0,SPI2CON1
nop

clr.w    W0
mov.w    W0,SPI2CON2
nop

mov        #0x8000,W3
mov        W3,SPI2STAT

bset    LATA,#11    ; Set Reset high...
call    _SPI2Reset
return

_SPI2Reset:
bset    LATA,#OELine        ; Set OE high...
bclr    LATC,#SDOLine        ; Set SOUT low...
bset    LATC,#SCKLine        ; Set SCLK high...
return

_SPISend:
clr.w        w2                ; Reset the loop counter...
mov            PORTC,W8        ; Grab a copy of PORT C
_SPISend1:
ior            #32,W8            ;
xor            #32,W8            ; SCK = 0
btst.z        W0,#0
bra            nz,_SPISend2
ior            #16,W8            ;
xor            #16,W8            ; Clear SOUT
goto        _SPISend3
_SPISend2:
ior            #16,W8            ; Set SOUT
_SPISend3:
mov            W8,LATC
repeat        #SCLKDelay2
nop
xor            #32,W8            ; Toggle SCK...
mov            W8,LATC
repeat        #SCLKDelay1
nop
rrnc        w0,w0
inc            w2,w2
cp            w2,#8
bra            lt,_SPISend1
bclr        LATC,#SDOLine
bset        LATC,#SCKLine
return

_SPIRcv:
clr.w        w0                ; Clear result...
clr.w        w2                ; Reset the loop counter...
mov            PORTC,W8        ; Grab a copy of PORT C
_SPIRcv1:
ior            #32,W8            ;
xor            #32,W8            ; SCK = 0
btst.z        W0,#0
bra            nz,_SPIRcv2
ior            #16,W8            ;
xor            #16,W8            ; Clear SOUT
goto        _SPIRcv3
_SPIRcv2:
ior            #16,W8            ; Set SOUT
_SPIRcv3:
mov            W8,LATC
repeat        #SCLKDelay2
nop
xor            #32,W8            ; Toggle SCK...
mov            W8,LATC
repeat        #SCLKDelay1
nop
rrnc        W0,W0
btst.z        W8,#SDILine
bra            z,_SPIRcv4
ior            #128,W0
_SPIRcv4:
inc            w2,w2
cp            w2,#8
bra            lt,_SPIRcv1
bclr        LATC,#SDOLine
bset        LATC,#SCKLine
return

_SPISendCmd:
bclr        LATA,#OELine
nop
nop
nop
nop
swap        W0
clr.b        W0
swap        W0
mov.w        W0,W1
mov            #0x001f,W0
call        _SPISend
mov            W1,W0
and.b        W0,#0x0f,W0
call        _SPISend
mov            W1,W0
swap.b        W0
and.b        W0,#0x0f,W0
call        _SPISend
bset        LATA,#OELine
return

_SPISendData:
bclr        LATA,#OELine
nop
nop
nop
nop
swap        W0
clr.b        W0
swap        W0
mov.w        W0,W1
mov            #0x005f,W0
call        _SPISend
mov            W1,W0
and.b        W0,#0x0f,W0
call        _SPISend
mov            W1,W0
swap.b        W0
and.b        W0,#0x0f,W0
call        _SPISend
bset        LATA,#OELine
return

_SPIGetStatus:
bclr        LATA,#OELine
nop
nop
nop
nop
mov            #0x003f,W0
call        _SPISend
call        _SPIRcv
bset        LATA,#OELine
return
.end
Please let me know if you spot anything silly.

I'll be testing this over the next few days...

MichaelaJoy

Active Member
EUREKA!

The above code seems to work.

Active Member
MJ Patience rewarded . and 4200 views . not bad for a newbie

MichaelaJoy

Active Member
granddad: Thanks. TBH, this is my first PIC project. I have worked on other processors before, (8085, 80186EB)
but never the PIC. And I think I chose one of the more difficult ones; one that was the least used.

There are so few code examples out there.

BTW: heartfelt thanks to all who have been involved in helping me to struggle through this.

I'm grateful.

Well...back to the keybed scan code. With a working display, I can see what the data looks like.
This will also be invaluable for testing the A/D converters.

The saga continues...

MichaelaJoy

Active Member
I have updated the schematic and the firmware.

Post #143 has the updated schematic and Post #154 has the latest firmware.

wkrug

Active Member
You have 4 unused logic gates of the 74HC14.
You can use it to implement a MiDi thru, when You need one for Your Keytar.

MichaelaJoy

Active Member
wkrug: I won't need it for the keytar, but it's needed for the next version of this board. .