Spark Logic

New SdFat library (with and without DMA)

victor_pv Mon Apr 27, 2015 7:11 pm

***Latest Update 04/10/2017***
The code in the official repo from Bill Greyman has now the support for the libmaple based core (stm32duino) integrated in it. So no need to download it from my repo any more.

Official SD-Fat library repository:
https://github.com/greiman/SdFat

===============================================================

UPDATE TO THIS WARNING. SHOULD BE SOLVED IN THE LATEST REPO. If you have problems using pin 7 for CS, make sure you are in sync with Roger’s master STM32Duino repo.
WARNING!!
I was just testing an SD card with one of the sketches, and noticed that you CAN NOT use pin 7 in the maple mini (the stm32f103 native SS pin for spi1) for CS. I have not looked at how the library manages CS and how that pin is setup in out SPI library, but I guess there is a conflict in the pin mode. So the work around at the moment is to use a different pin for CS.
– Updated 06/04/2015-

– New update on 07/10/2015 – Small code changes to the library and specially to the SPI library, which should go to Roger’s master repo soon.

So as I posted before in the forum, I modified the latest SdFat library from Greiman.
I had submitted him changes for a previous version, but had issues. Now all the issues seem to be corrected in my latest version.
It is available here:
https://github.com/victorpv/SdFat

It seems to work fine for me for the few examples I have tested.
If you want the best performance for large transfers, can use DMA mode.

DMA would be advantageous for large transfers, but doesn’t help at all, and may even have a penalty, if you are just reading a few bytes to a log and things like that. Now if you transfer images or mp3 files from the sdcard, DMA can improve performance greatly, anything involving big blocks at once.

DMA can be enabled or disabled editing SdSpiSTM32F1.cpp, there is one #define line:
/** Use STM32 DMAC if nonzero */
#define USE_STM32F1_DMAC 1

Comment that line or change the 1 for 0, and DMA transfers will not be used.

RECOMMENDATIONS FOR SPEED:
Use DMA mode.
Set a bigger buffer, ideally 4KB

Read speeds without DMA, 4KB buffer:
read speed and latency
speed,max,min,avg
KB/Sec,usec,usec,usec
676.29,8189,5927,6056
676.38,7467,5927,6054
676.38,7468,5927,6054
676.38,7469,5926,6054
676.38,7472,5926,6054

Replies

victor_pv Wed Oct 21, 2015 12:10 am

I don’t remember all the details, but SD cards use some tricks in the initialization with the control signals, that may explain CS activating before clock, may be totally intentional. You could check the library code and see what it does. I really haven’t look at anything except the SPI functions to send data, which is the part I adapted to the mini, but I have not clue about the whole sdcard protocol except little snipets from reading here and there.

stevestrong Wed Oct 21, 2015 8:58 am

OK, the pulse train generated while CS is inactive (“1”) looks similar to a well known technique applied on I2C-bus to avoid hang-up of any slave device.
But what about the 27MHz frequency pulses on SCK? Are they also deliberately generated? For this I cannot find any meaningful explanation.
For me it looks like the SW tries to access the SPI bus before the correct frequency was set up.
And how about the funny behavior of the SCK before the 27MHz pulses come? The signal goes to “0” and after a while slowly back to “1”, seems like push-pull is not activated. What is the reasoning behind?
I have to take a deeper look into the SPI driver part used by the SdFat lib.

RogerClark Wed Oct 21, 2015 9:40 am

Which pin are you using for SS / Chip Select ?

If you are using the one on the Maple Mini, labelled as SS, (I think its Pin 7) try using another one.

There is a known issue with the SS pin not behaving. Its a fault / feature of the STM32 its self, and there wasnt a lot we could do about it.

I recall we tried to put in a work-around for this pin missbehaving, but I can’t remember precisely what the issue was or what the work-around was.

I’m sure someone one the forum will remember the details ;-)

mrburnette Wed Oct 21, 2015 12:01 pm

RogerClark wrote:<…>
I’m sure someone one the forum will remember the details ;-)

stevestrong Wed Oct 21, 2015 2:22 pm

As stated above, I am using the NSS port of SPI 2, pin 31, or PB12.
Does this pin behave the same or have same bug as PA4?

mrburnette Wed Oct 21, 2015 3:21 pm

stevestrong wrote:As stated above, I am using the NSS port of SPI 2, pin 31, or PB12.
Does this pin behave the same or have same bug as PA4?

victor_pv Thu Oct 22, 2015 4:47 am

This is what I remember, without looking for the thread:
1.- We found the part of the core in which the NSS pin is set to AF (to be controller by the SPI peripheral itself)
2.- We removed that line, so NSS remains as GPIO.
3.- From that point on the core allows to keep using the NSS pin as GPIO after an SPI port has been started. It is not under direct control of the SPI peripheral when you start the SPI port.

The above applies to any SPI port, 1 to 3 (in devices with 3 ports). So any code using controlling the CS pin as any normal GPIO pin can use the NSS pin for CS, or anything else for that matter. It is not NSS anymore in our core unless Roger reverted that change at some point, which I don’t remember reading.

EDIT:
Regarding the sck pulses when CS remains low, I am almost certain that is part of the normal way of communicating with the SD card. Do a search in google for SD card in spi mode, and you should find all the details, but I remember reading about a bunch of tricks that are done will all the control lines in the SD card to set them up. I just don’t remember the exact things that were done, but google is your friend ;)
There it is all explained about those pulses:
http://elm-chan.org/docs/mmc/mmc_e.html

stevestrong Thu Oct 22, 2015 8:26 am

Thanks you all for your answers.

I have also found the place where the 74 or more SCK pulses (needed for SD card init) are generated while SS is high (inactive). However, these pulses are generated with a 27MHz clock (line 132 of SdSpiCard.cpp, using SPI_SCK_INIT_DIVISOR). Shouldn’t here already the input parameter “sckDivisor” be used? At least in the case where the SD card does not support 27MHz.

Another issue is the 3 slow and wide SCK pulses before the normal data transfer pulses. Did I understand correctly: this is a bug in the silicon and will be generated by the HW when the SPI CR register is written (e.g. data mode, bit order and clock divider)?
Can anyone confirm this?

A third topic (low prio) would be that it seems that spi_init() is superfluously called in line 133 of SdSpiCard.cpp. The init routine will be called anyway in chipSelectLow() (line 207), at the beginning of each card command. Btw, isn’t any faster option to set/clear the SS pin other than digitalWrite() – lines 296 and 279?

[Off topic: how can be generated a function call graph in the easiest way?
Maybe automatically generated and checked in once after each code change?]

RogerClark Thu Oct 22, 2015 9:00 am

I’d try writing some basic code calling SPI transfer etc and see if you get the same results.

The problem with the SD card lib, is that from what I’ve seen, its code that has evolved over a number of years and I suspect there are lots of legacy calls which end up doing the same setup more than once.

In our SPI lib, it takes a cautious approach, so its quite possible that it re-inits more than it needs to, but this generally means that we don’t have that much trouble with it.

Re: faster methods than digitalWrite

Yes, plenty of faster ways.

There are internal gpio calls in libmaple that could be called directly

Or the hardware registers can be manipulated directly, and you could cache the CS port and mask details.

However on the whole, this isnt a big issue either, unless the code is setting and resetting CS every time a byte is transferred.

But really thats a issue with the higher level code, ie the library that is using SPI

stevestrong Thu Oct 22, 2015 9:46 am

Roger, I agree with you, robustness may be more important than code optimization.
Anyway, as I told, is not a big issue, and this place seems really not to be very critical since is one time action for transferring several bytes.

Still, the other issue with those 3 wide pulses on SCK line before the normal data transfer pulses really bothers me, they should not be there. And I am not sure whether they cause any problems when accessing the SD card, specially when they occur while SS is active low.
I think it is important to find out why are they there. I need to dig deeper…

RogerClark Thu Oct 22, 2015 9:58 am

Thanks Steve

I agree, we should not have extra pulses.

I think several people have looked at the SPI, with logic analysers and scopes, over the last 6 months or more, and I don’t remember anyone seeing any extra pulses, so this problem may be specific to the SD lib

Anyway. Thanks for investigating this strange phenomenon

mrburnette Thu Oct 22, 2015 12:11 pm

stevestrong wrote:<…>
I think it is important to find out why are they there. I need to dig deeper…

stevestrong Thu Oct 22, 2015 9:26 pm

So, here you have a plot taken with SPI_CLOCK_DIV4. This is what I call a clean signal :).

Image

The SPI speed has not much influence on the run-time performance, DIV4 or DIV8 gives pretty much the same results, so I would suggest use DIV8 to keep on the safe side. DIV2 (full speed) haven´t tried yet.

The changes:
SdSpiCard:
– ::begin() – swapped call sequence between spiBegin() and spiInit(). I think it is normal that the initialization should be done before the interface is started (begin).
– chip_select_low() – remove the line with spiInit(), which has been already called in begin(), no need to call it all over again.
SPI.cpp
– remove the call of this.begin() from the functions setClockDivider(), setBitOrder() and SetDataMode(). Same reasoning, the SPI::begin already executed before.

After these changes the funny wide pulses are gone, no more pulses with 27MHz, the card behaves very robustly, no more scope probe problem at all!

The only small issue is a problem with the serial interface, some data sent with Serial.print() is sometimes not displayed, and other times is displayed doubled or tripled in the COM window. I am not sure whether my changes have influenced the DFU behavior or is just my PC which was not rebooted since a week…

Attached my local copy of these files.

The setup consists of only one SD card module on SPI 1, SS is maple mini pin 31, or PB12.
The same sketch as in my first post.

EDIT:
Here is the serial output:
SdFat version: ******* the version number for some reason will not be displayed ******
init time: 0 ms

Card type: SD2

Manufacturer ID: 2
OEM ID: TM
Product: SA02G
Version: 0.3
Serial number: A07C6B9C
Manufacturing date: 12/2008

cardSize: 1973.42 MB (MB = 1,000,000 bytes)
flashEraseSize: 128 blocks
eraseSingleBlock: true
OCR: 80FF8000

SD Partition Table
part,boot,type,start,length
1,0,6,135,3854201
2,0,0,0,0
3,0,0,0,0
4,0,0,0,0

Volume is FAT16
blocksPerCluster: 64
clusterCount: 60214
freeClusters: 60207
freeSpace: 1972.86 MB (MB = 1,000,000 bytes)
fatStartBlock: 136
fatCount: 2
blocksPerFat: 236
rootDirStart: 608
dataStartBlock: 640

Starting RawWrite...
removing RAW.TXT...
creating contiguous file...
getting contiguous range...
Start raw write of 51200 bytes at
51200 bytes per second
Please wait 1 seconds
RawWrite done
Elapsed time: 1.00 seconds
Max write time: 557 micros
Overruns: 0

type any character to start

RogerClark Thu Oct 22, 2015 10:36 pm

SPI.cpp
– remove the call of this.begin() from the functions setClockDivider(), setBitOrder() and SetDataMode().

I’m surprised this works.

if you look in setClockDivider, all it does is set a property called _currentSetting->clockDivider, it doesnt actually change the hardware

The reason it calls begin(), is because begin calls

spi_master_enable(_currentSetting->spi_d, (spi_baud_rate)_currentSetting->clockDivider, (spi_mode)_currentSetting->dataMode, flags);

Which actually sets up the hardware, and to do this it has to disable the SPI hardware and then make those changes and then re-enable the SPI hardware

It has to do this because some changes (I think bit order is one of them), can’t be changed if the SPI interface is active.

You could try writing a loop to call

spi_peripheral_disable(dev);
spi_peripheral_enable(dev);

stevestrong Thu Oct 22, 2015 10:54 pm

Roger, the function spi_master_enable() is actually called already by spiBegin() from the beginning of SdSpiCard::begin(), after spiInit() – in my version.
spiBegin() calls SPI.begin(), which calls spi_master_enable() after executing spi_init() and configure_gpios().
So I think the HW will be correctly initialised and started… that’s why is working… I guess…

RogerClark Thu Oct 22, 2015 11:06 pm

Steve

I expect the default SPI config for some things like byte order, is fine for the SD card, which is why its working.

But if you remove the calls to begin() from the setByteOrder etc, some other devices will not work.

Can you test if its the act of disabling and re-enbling the SPI hardware that’s causing the glitch

It could also be that the code is returning the SPI pins to GPIO mode (probably inputs), which cause the glitches

stevestrong Fri Oct 23, 2015 8:33 am

But if you remove the calls to begin() from the setByteOrder etc, some other devices will not work.
I see what you mean, I also had some thoughts about it.

I checked the whole STM32 repo, there are quite few places (only 7 files!) where these functions are called.
In those cases the SPI.begin() is always called before.
I think this is generally not quite ok. In my view, begin() is not init(), and init() should come first.
I assume, the setByteOrder() and similar functions belong to init part, right?
That means, the working parameters should be setup first, and only afterwards call begin().

On the other side, the function SPI.begin() also calls the function spi_init(), which executes an RCC enable and reset. Hence, each time begin() is called, beside calling spi_master_enable(), the RCC is reset. I think the repeated RCC reset and master enable makes no sense and therefore should be avoided. Maybe this is the reason why what I got rid of glitches by applying my changes.

I know, one try to avoid changes which could affect other libs. But I think with these changes we could really improve the lib.
As an alternative to remove lines which call begin(), I would maybe introduce a semaphore variable to monitor whether begin() was called before, and don’t call it again if it is the case.

What is your opinion?

EDIT:
The reason it calls begin(), is because begin calls

spi_master_enable(_currentSetting->spi_d, (spi_baud_rate)_currentSetting->clockDivider, (spi_mode)_currentSetting->dataMode, flags);

Which actually sets up the hardware, and to do this it has to disable the SPI hardware and then make those changes and then re-enable the SPI hardware
Because of this, without changing the calling order of init() and begin(), the idea with semaphore variable won’t work.

RogerClark Fri Oct 23, 2015 10:03 am

Hi Steve,

The code for the SPI lib was inherited from the original Leaflabs libmaple libraries. We made some changes to it, but I don’t recall changing begin();

I don’t know why Leaflabs needed to call rcc_clk_enable or rcc_reset_dev, (unfortunately they have not responded to questions on github etc for well over a year, and they have not participated on the old leaflabs forum for several years (as far as I can tell).)

But I’d guess that you have to setup the rcc_clk and reset the SPI device before you can use it at all.

If some libs call begin multiple times, we could probably put flags into spi_init() so that it doesnt re-init if its already been inited, but I suspect that the reason that the is done on every call to begin() is in case someone decides to set the pinMode of on of the SPI pins, between calling one begin() on one transfer and another begin() on another transfer

Re: setBitOrder being called ??

I’m not sure what you mean. As far as I can see setBitOrder is only called in SPI.cpp from beginTransaction

If another lib or a sketch calls this, its out of our control

setBitOrder can be called at any time, before or after SPI.begin(), so I can’t see how the code can do anything but disable the spi hardware, change the setting, and re-enable the hardware

Albeit, it does this by calling spi.begin() but even if I copied the code from spi.begin() into setBitOrder, it would be the same code.

We can probably make some improvement by changing

void SPIClass::setBitOrder(BitOrder bitOrder)
{
#ifdef SPI_DEBUG
Serial.print("Bit order set to "); Serial.println(bitOrder);
#endif
_currentSetting->bitOrder = bitOrder;
this->begin();
}

stevestrong Fri Oct 23, 2015 11:27 am

I’m not sure what you mean. As far as I can see setBitOrder is only called in SPI.cpp from beginTransaction

If another lib or a sketch calls this, its out of our control


you can check it here: https://github.com/rogerclarkmelbourne/ … etBitOrder
Usually setBitOrder, setClockDivider and setDatMode are called sequentially after each other.
For example: https://github.com/rogerclarkmelbourne/ … _ports.ino.
void setup() {

// Setup SPI 1
SPI.begin(); //Initialize the SPI_1 port.
SPI.setBitOrder(MSBFIRST); // Set the SPI_1 bit order
SPI.setDataMode(SPI_MODE0); //Set the SPI_2 data mode 0
SPI.setClockDivider(SPI_CLOCK_DIV16); // Slow speed (72 / 16 = 4.5 MHz SPI_1 speed)
pinMode(SPI1_NSS_PIN, OUTPUT);

// Setup SPI 2
SPI_2.begin(); //Initialize the SPI_2 port.
SPI_2.setBitOrder(MSBFIRST); // Set the SPI_2 bit order
SPI_2.setDataMode(SPI_MODE0); //Set the SPI_2 data mode 0
SPI_2.setClockDivider(SPI_CLOCK_DIV16); // Use a different speed to SPI 1
pinMode(SPI2_NSS_PIN, OUTPUT);

}

mrburnette Fri Oct 23, 2015 12:13 pm

I think before too many cycles are expended in init() and begin() one should go back to the Arduino core and itemize how these functions are being utilized (or Teensy3 for that matter.) A state diagram of the called functions may shed light on this issue as I do not know if ‘initialize’ and ‘begin’ are actually being used as the name-definition suggests.

One needs to recognize, also, that the Leaflabs core was written prior to Arduino 1.0 which was a huge architectural and structural change for Arduino libraries.

Ray

Sign in to reply