this post was submitted on 03 Aug 2023
9 points (90.9% liked)
Embedded
444 readers
1 users here now
This sub is dedicated to discussion and questions about embedded systems: "a controller programmed and controlled by a real-time operating system (RTOS) with a dedicated function within a larger mechanical or electrical system, often with real-time computing constraints."
founded 1 year ago
MODERATORS
you are viewing a single comment's thread
view the rest of the comments
view the rest of the comments
So it took me a little while to figure out between reading the datasheet for the Max7219 and looking at the source code. Basically it's taking advantage of a feature of the Max7219 that allows daisy chaining multiple chips off the same SPI connection. In order to take advantage of this feature you would take N Max7219 chips and wire all their CS and CLK pins together with your controller, and then run the connection from the controller to the first chips DIN port, and then the DOUT port from the first chip to the DIN port of the next chip. Keep chaining DOUT to DIN to daisy chain all the chips together.
In the datasheet for the Max7219 there's this section:
Essentially what that all boils down to, is that each Max7219 maintains a 16 bit internal shift register, so as each bit is received on DIN it's pushed onto the register, and the highest bit of the register gets pushed out to DOUT. When you daisy chain multiple chips together it's effectively like concatenating all their shift registers together. So if you have 4 chips, that's 64 bits of register. If you write 64 bits out to MOSI the first 16 bits will end up on the farthest out chip, the next 16 in the next closest, etc. Switching the CS pin from low to high is the trigger for the Max7219 to actually lock in and read the contents of those shift registers. The way the driver crates code is structured that's the purpose of the buffer field in the various Connector structs. So if you have say 4 chips, you need 4 x u16 storage, and each write cycle you write all 4 u16 values out, one to each daisy chained device. Technically the driver is less efficient than it could be, in that it takes advantage of the fact that writing 0 to a chip is a no-op, so in practice while it does write to every device each time, when you call write_raw it actually 0s the buffer for all but the selected chip.
If you think about a sequence of chips, lets say once again 4 of them labeled A to D. They would be connected like so:
Then you write to all four chips like so:
Thank you.
Ive actually read the section you quoted a few times and my brain just couldn't parse it. But i finally understand how the max7219 makes this. I've thought about it completely wrong. It just shifting through all the bits from chip to chip so obvious now.
I think i will go a step back and not use spi for a while and just do the bit-banging -thingy first to get more familiar first.
I've read somewhere that Is faster and I guess it's cheaper for the cpu to use as the cpu doesn't have to set the pin outs high or low with each cycle. Instead (i guess) the cpu can simply call a spi-out-funtctio one time and the spi does its thing for a while while the cpu can do other things.
But right now I don't do much yet on the rest of the CPU, so i can afford to do it manually.
Just one other question regarding multiple displays: as e.g. 4 displays requires 4x16bits does this mean that there would have to be a Write-trait implemented somewhere (or Write<[u16;4]>)?
could it be that the max7219-crate is incomplete here? The write-funtion you corrected seems like it was copied 1to1 from the cpp-lib (LedControl).
Nope. The Write trait is indicating the size of the "packet" that's written on the SPI bus, it's the equivalent of the DS generic off the Spi struct. The way SPI works is, when you toggle CS low, the device is notified that it needs to start listening on MOSI, at which point you're free to start sending it packets. There's no requirement that you only send a single packet, you can send as many as you want, however many devices will have special rules about processing with respect to the state of the CS pin. E.G. just like with the Max7219 it's common for devices to buffer commands and not actually process them until CS is sent high.
The only reason why the Write and the Spi generic are important is because it defines the minimum number of bits that will be written to the bus (or more concretely it's the stride size the SPI controller uses when reading and writing from its buffers). That's why using u8/8 as the parameter mostly works except for occasionally demonstrating strange behavior. Using u16 guarantees that it always writes a number of bits that's a multiple of 16, while using u8 can allow for essentially a half packet to be written.
As for bit banging vs. SPI controller, it's essentially the same thing as DMA if you're familiar with that concept. Using bit banging the CPU is spending time toggling the various pins off and on, which although fast, is still relatively slow by communication standards and puts an upper limit on the speed data is transmitted on the SPI bus that's directly tied to the frequency of the CPU and the number of cycles it takes to toggle a pin (minimum two pin toggles, maybe one for MOSI, two for CLK). Using the SPI controller on the other hand, the CPU writes bytes into memory and then passes essentially a couple of pointers to the SPI controller then flips some bits in a register. The CPU does need to pause occasionally to refill the buffers, but that's a relatively fast operation and is mostly decoupled from the actual bus speed of SPI.
Manually implementing SPI with bit banging is probably a good learning exercise, but understanding how to properly use the SPI controller is also good to know. For an extra challenge you can usually also setup the SPI buffer to be managed using DMA for the most optimal way to handle things. I would suggest configuring a u16 buffer sized based on the number of devices and then using DMA to write its contents out using the SPI buffer would be a very educational exercise.