6502.org Forum  Projects  Code  Documents  Tools  Forum
It is currently Thu May 09, 2024 4:58 pm

All times are UTC




Post new topic Reply to topic  [ 10 posts ] 
Author Message
PostPosted: Sat Jan 07, 2023 3:04 am 
Offline

Joined: Sat Oct 09, 2021 11:21 am
Posts: 704
Location: Texas
Hello everyone, I need advice. On my latest board, I have an expansion area which includes the SPI pins: CS, CLK, MOSI, and MISO. All of the lines are shared besides CS of course. MISO has a 10K pull-up resistor. Standard stuff.

In my System Monitor program, I want to have a way to interface an SPI device through these expansion pins. Right now I have a 25LC640A EEPROM and an "SPI MicroSD Card Adapter" (here https://duckduckgo.com/?t=ffab&q=spi+mi ... ter&ia=web) connected using SPI protocols, but they share a common base code of enable/disable/read-byte/write-byte.

What should I do through my System Monitor to be able to interface general SPI devices? Some ideas I've tried:

1) Enable/Disable function, Write Byte function, Read Byte function, with the exact byte to be written or location of read byte as an argument. Pros: Basic, easy, probably universal. Cons: Lots of overhead, lots of wasted time. Example from my monitor (note, mine is not exactly like this, but you get the idea):
Code:
 
FF>SPI ; to write byte FF
0000<SPI ; to read byte into address 0000


2) Read and/or Write from list of bytes/addresses, having the values in memory instead of hand-typed by user. Pros: You can get a lot more done quickly. Cons: Sometimes reading and writing switch out every other byte, so this isn't as helpful. Example:
Code:
0000.007F>SPI ; to write list of bytes from 0000 to 007F
0080.00FF<SPI ; to read list of bytes from 0080 to 00FF


3) Command + Byte structure. This would end up being like a psuedo-code that can be manipulated for advanced instructions. Pros: You can do a lot more when pre-programmed. Cons: Super complicated to the user. Example:
Code:
0000.001F SPI ; send commands + bytes

Memory would look like:
0000: 01 02 AA 02 55 03 80 00 00
01 = enable
02 AA = write byte AA
02 55 = write byte 55
03 80 00 = read byte into location 0080
00 = disable


I just don't know of a good way to interface general SPI devices. I haven't used anything really outside of what I have already mentioned. I want this to be general enough that I could make it work with most SPI devices without actually needing special assembly code. I want it to be easy to use but also useful.

Thoughts? I don't care about notation and all that, I want to know what you think the functionality should be like. Write a byte at a time? Write a string of bytes? Write commands + bytes? Or something else, please! Any suggestions and advice is greatly appreciated. Thank you all!

Chad


Top
 Profile  
Reply with quote  
PostPosted: Sat Jan 07, 2023 6:28 am 
Offline
User avatar

Joined: Fri Aug 30, 2002 1:09 am
Posts: 8432
Location: Southern California
sburrow wrote:
Write a byte at a time? Write a string of bytes? Write commands + bytes? Or something else, please!

I imagine you've already been through http://wilsonminesco.com/6502primer/SPI.ASM .  (Even if so, the mention may be helpful to another reader.)  Certain low-level stuff goes down first, and each level becomes a set of building blocks for subsequent levels.  Where simplicity and speed may diverge is in the fact that SPI can send and receive at the same time but most devices don't need to, and the fact that there are the four modes.  It'd be easier if you only ever had to deal with one mode (never having to check the mode), and if you never had to send and receive at the same time.  Further, once you get into specific devices, you'll have to write device-specific code for them, on top of the lower-level stuff used by all SPI devices.  My general SPI stuff is in the file: initializing the hardware port, doing the clock edges, setting or clearing MOSI, selecting a device, sending a byte, receiving a byte, doing both at once, and managing the modes.  From there, you could add sending or receiving arrays, including strings.  (Below, I called this a "range" of memory addresses, with a starting address and a count.)  Anything further will be device-specific, not particularly part of SPI; for example, for the 25VF032, I had:
Code:
WAIT_TIL_NOT_BUSY
INST_FACTOR        ; instruction factor, used in the single-byte instructions below
EN_WR_STAT
WR_ENABL
WR_DISABL
ENABL_SO_AS_RDY
DISABL_SO_AS_RDY
ERASE_FLASH
WR_STAT
BEG_RD_STAT
CONT_RD_STAT
END_RD_STAT
RD_STAT            ; for if you want just a single read; uses BEG_RD_STAT and END_RD_STAT
REMOVE_ALL_WR_PROTECT
ERASE_FACTOR       ; factor used by the next few commands
32K_ERASE
64K_ERASE
SECTOR_ERASE
CHIP_ERASE
BYT_PGM
BEG_AAI_PGM        ; begin auto-addr-incr programming
AAI_RDY_CK         ; to see if pgm cycle is done so you can continue
CONT_AAI_PGM       ; continuation of AAI programming above; no need to keep sending addr
END_AAI_PGM
BEG_RD_FLASH
CONT_RD_FLASH
END_RD_FLASH
RD_ID              ;read flash ID; should result in 4A BF for the 25VF032
TEST_ID
TEST_AAI_PGM       ; this was for my tiny modules
FLASH_PGM_RANGE    ; Program a range.  Input params are RAM addr, count, and flash addr.
READ_FLASH_RANGE   ; Input params are flash addr, RAM addr, & count.


_________________
http://WilsonMinesCo.com/ lots of 6502 resources
The "second front page" is http://wilsonminesco.com/links.html .
What's an additional VIA among friends, anyhow?


Top
 Profile  
Reply with quote  
PostPosted: Sat Jan 07, 2023 11:54 am 
Offline

Joined: Sat Oct 09, 2021 11:21 am
Posts: 704
Location: Texas
Thank you Garth. That list of commands is incredibly extensive. And that is only for the 25VF'?

GARTHWILSON wrote:
Further, once you get into specific devices, you'll have to write device-specific code for them, on top of the lower-level stuff used by all SPI devices.


That was what I was hoping to avoid. I have my own SPI functions and they work great. My DIP-8 EEPROM and the MicroSD Card Adapter work beautifully, and use most of the same functionality. That's why I was hoping to use that same functionality on a third non-specific device. I know not all devices are going to follow the same patterns, but I was hoping to get something that would *probably* work on the whole.

GARTHWILSON wrote:
From there, you could add sending or receiving arrays, including strings.


The way I interface my 25LC' is by reading or writing one single byte at a time. It's 'slow' but because the thing is only 8K in size, it doesn't really matter. How I read the SDcard is by one 'block' at a time, thus 512 bytes in sequence. The problem is that I was hoping for a "different" device. Like, say, a thermometer, or a light sensor, etc. I don't think I'll need a ton of strings for something like that. But a string of commands might be important! I just don't know.

I don't have a specific target device. That's why I'm confused about how approach this problem. I want something that is generally useful and universal. I don't require that it works on EVERY device, just hopefully some or most. And, as a reminder, I'm not looking for specific code or notations here. Just advice and ideas as to what approach might be more handy.

Thank you Garth.

Chad


Top
 Profile  
Reply with quote  
PostPosted: Sun Jan 08, 2023 9:26 pm 
Online
User avatar

Joined: Wed Feb 14, 2018 2:33 pm
Posts: 1407
Location: Scotland
One thing to remember is that SPI is full duplex and some devices require you to clock out the command and read the data at the same time. ADCs spring to mind, so you end up selecting the device, sending it e.g. 16 clock pulses with the first few bits being data like select channel, select accuracy, then you continue to clock but need to read the input to get the result back - all in the same 'transaction' - which would be bounded by the CS signal for that device.

So having a generic command to handling it might be tricky.

-Gordon

_________________
--
Gordon Henderson.
See my Ruby 6502 and 65816 SBC projects here: https://projects.drogon.net/ruby/


Top
 Profile  
Reply with quote  
PostPosted: Mon Jan 09, 2023 11:30 am 
Offline

Joined: Sat Oct 09, 2021 11:21 am
Posts: 704
Location: Texas
drogon wrote:
So having a generic command to handling it might be tricky.


Thank you Gordon. This post seems to discourage me (not in a bad way!) from doing something 'universal', because there isn't anything universal. Good point. I'll be pondering on this as well. Thank you again.

Chad


Top
 Profile  
Reply with quote  
PostPosted: Mon Jan 09, 2023 1:32 pm 
Offline

Joined: Wed Jun 23, 2021 8:02 am
Posts: 165
I've been doing some work with SPI over the last few days and I've found that just using memory read and write commands was generally enough to do basic stuff. For example I have a serial flash connected to one of the SPI ports and I can test that like this:

:d0800 00 (write $00 to $D0800 - this selects SPI channel 0 which is the serial flash, sets clock frequency to maximum 12.5MHz, slave select deasserted)
:d0800 01 (assert slave select to flash)
:d0400 03 ff fd 00 ff (send READ command, address 0xfffd00, extra FF to clock out first returned byte)
m d0400+100 (read $100 bytes from $d0400 onwards - each read starts another SPI transfer and each SPI controller register maps to a 1K block, so it displays 256 bytes of flash)
:d0800 00 (deassert slave select to end command)

OK, it's more complicated with other devices, but you can do quite a lot with just raw byte send/receive, certainly enough to determine if things are generally working.


Top
 Profile  
Reply with quote  
PostPosted: Mon Jan 09, 2023 1:43 pm 
Online
User avatar

Joined: Wed Feb 14, 2018 2:33 pm
Posts: 1407
Location: Scotland
sburrow wrote:
drogon wrote:
So having a generic command to handling it might be tricky.


Thank you Gordon. This post seems to discourage me (not in a bad way!) from doing something 'universal', because there isn't anything universal. Good point. I'll be pondering on this as well. Thank you again.

Chad


I think you can do something general purpose as an aid to developing specific code for a new device - that's what I've done in the past....

The Raspberry Pi does an in-memory exchange as each bit of data is clocked out, the sampled data in is then written in-place, so I used this when I wrote my dumbed-down access for as part of my wiringPi library - if a device needed 2 bytes of data with (say) the first few bits being a command, then I'd build up a 16-bit memory buffer with the command and then get the hardware to exchange 16-bits and pick out the data from the same memory buffer.

So... If you had a command to simultaneously read/write a number of bytes at an address and good commands to edit RAM then this might help. Write the RAM, call the SPI data exchange, read the RAM.

I have a little test program that does this on the Pi (and Arduino which is similar) which I use when developing a 'driver' for a new device or testing some existing or weird device.


So after testing a device "by hand", what I often do is write dedicated handlers for devices I might commonly use, so my wiringPi library has a basic low-level read/write command which takes a data buffer and number of bytes and each device I cater for calls this after setting up it's command/parameter block and picks the result out.

Here is an example of code to read the MCP3002 ADC - this is a 10-bit device with 2 input channels:

Code:
static int myAnalogRead (struct wiringPiNodeStruct *node, int pin)
{
  unsigned char spiData [2] ;
  unsigned char chanBits ;
  int chan = pin - node->pinBase ;

  if (chan == 0)
    chanBits = 0b11010000 ;
  else
    chanBits = 0b11110000 ;

  spiData [0] = chanBits ;
  spiData [1] = 0 ;

  wiringPiSPIDataRW (node->fd, spiData, 2) ;

  return ((spiData [0] << 8) | (spiData [1] >> 1)) & 0x3FF ;
}


The low-level command is the wiringPiSPIDataRW () function call that does the heavy-lifting of the operation to select channel and clock a number of bytes of data out and in.

And note also that sometimes devices take or return data in the order you might not expect... Or it can be changed on the fly... Read section 5 of the MCP3002 data sheet for the gory details of this device... But in essence, the first 4 bits sent to it are: "Wake up" (1), Single/Diff (1=Single), Channel (0 or 1), MSBF first (1 = yes, send most significant bit first) - then you keep on clocking it for another 12 cycles as it returns the data.

Also note that some devices like this use the SPI clock as their internal clock to do stuff like control the gates to the
SAR logic so sometimes the clock must be in a particular range...

-Gordon

_________________
--
Gordon Henderson.
See my Ruby 6502 and 65816 SBC projects here: https://projects.drogon.net/ruby/


Top
 Profile  
Reply with quote  
PostPosted: Mon Jan 09, 2023 2:44 pm 
Offline
User avatar

Joined: Fri Dec 11, 2009 3:50 pm
Posts: 3354
Location: Ontario, Canada
kernelthread wrote:
I've found that just using memory read and write commands was generally enough to do basic stuff.
In 2016 I wired up an SPI UART as a subassembly that can plug into other projects. As far as I can tell, there's no need at all for the SPI to run full duplex, because all you're doing is peeking and poking the registers of what is seemingly an industry-standard UART. And certainly the original (parallel) interface couldn't read and write at the same time!

the datasheet wrote:
The SC16IS740/750/760’s internal register set is backward-compatible with the widely used and widely popular 16C450. This allows the software to be easily written or ported from another platform.

-- Jeff

ps- the chip can speak I2C as well as SPI; there's a mode pin for this.


Attachments:
IMG_2564CrpCrv.JPG
IMG_2564CrpCrv.JPG [ 119.26 KiB | Viewed 862 times ]

_________________
In 1988 my 65C02 got six new registers and 44 new full-speed instructions!
https://laughtonelectronics.com/Arcana/ ... mmary.html
Top
 Profile  
Reply with quote  
PostPosted: Tue Jan 10, 2023 5:29 pm 
Offline

Joined: Mon Jan 19, 2004 12:49 pm
Posts: 683
Location: Potsdam, DE
As an aside, I'm currently looking at using a 2MB NOR flash as a source of data to fill a bank of fast RAM chips holding the microcode, such as it is, for a TTL processor (8080, not 6052!) and it's beginning to look as if only a small handful of chips - mostly HC393 counters and an HC165 SIPO - are needed to perform the whole process. Still looking at the timings for the chips, but it looks hopeful that it's only going to take a second or so.

I was considering how one might implement a similar system to load a 6502 with a 64k RAM from a 64k or smaller part (one command byte less initially) to load a monitor program or basic or whatever. In a perfect world, one would play games with fake NOPs and let the 6502 do the address counting, but the odd three bytes at the start of the sequence make it tricky. I think it'll be easier with actual counter chips.

Neil

(microcode vs not microcode: I can't come up with a decent definition, but I suspect that a processor contains microcode only if it executes loops within an instruction. I believe the 6502 uses what is referred to as a PAL, even though it's not programmable, so it's basically a ROM, no?)


Top
 Profile  
Reply with quote  
PostPosted: Tue Jan 10, 2023 6:41 pm 
Offline
User avatar

Joined: Thu Dec 11, 2008 1:28 pm
Posts: 10800
Location: England
(The 6502's instruction decode is just half a PLA really, not even a ROM - there's no address input. It's a structured way to make a lot of many-input NOR gates. The sequencing is nearby and also has inputs to the decode, but again it's an address. Being structured, it's relatively dense and would have been amenable to tweaks or fixes before masks are made, or from one mask revision to another. The so-called 'random logic' can be viewed as the other half of the PLA, the second NOR plane, although it's not nearly as simple as being a second layer of NOR gates.)


Top
 Profile  
Reply with quote  
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 10 posts ] 

All times are UTC


Who is online

Users browsing this forum: drogon and 8 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to: