greiman / SdFat-beta

Beta SdFat for test of new features
MIT License
166 stars 62 forks source link

SPI access to external SD Card other than normal SPI Pins (not soft spi) #65

Open ccdzapper opened 3 years ago

ccdzapper commented 3 years ago

Hi Bill, I ran the SDInfo application successfully on an external SD Card connected to the 'standard' SPI pins 10,11,12,13 on a Teensy 4.1 & edited SdFatConfig.h with: #define HAS_SDIO_CLASS 0 This worked just fine. However, I wanted to try the same with SPI1 which would use MOSI1,SCK1,MISO1 & CS1 - pins 26,27,39 & 38. This does not work even though I did specify #define SDCARD_SS_PIN 38 at the top of the file, and further added in setup(): SPI1.setMISO(39); SPI1.setMOSI(26); SPI1.setSCK(27); I get the error right on initialization. SD errorCode: SD_CARD_ERROR_CMD0 = 0x1 SD errorData = 0x0. I looked in SdFatConfig.h, but could find any way to pass the information to the driver. I tried to drill through the hierarchy to find out where the alternate SPIs could be selected, but after some hours got lost in the abstraction!

Is this even possible as it stands?

Thank You.

greiman commented 3 years ago

The way you pass info to the driver is in SdSpiTeensy3.cpp. This should work for Teensy 4.1 since is is used if: #if defined(SD_USE_CUSTOM_SPI) && defined(__arm__) && defined(CORE_TEENSY)

Check SdFatConfig.h to make sure SPI_DRIVER_SELECT is zero.

 * If the symbol SPI_DRIVER_SELECT is:
 *
 * 0 - An optimized custom SPI driver is used if it exists
 *     else the standard library driver is used.

here is the code:

void SdSpiArduinoDriver::begin(SdSpiConfig spiConfig) {
  if (spiConfig.spiPort) {
    m_spi = spiConfig.spiPort;
#if defined(SDCARD_SPI) && defined(SDCARD_SS_PIN)
  } else if (spiConfig.csPin == SDCARD_SS_PIN) {
    m_spi = &SDCARD_SPI;
    m_spi->setMISO(SDCARD_MISO_PIN);
    m_spi->setMOSI(SDCARD_MOSI_PIN);
    m_spi->setSCK(SDCARD_SCK_PIN);
#endif  // defined(SDCARD_SPI) && defined(SDCARD_SS_PIN)
  } else {
    m_spi = &SPI;
  }
  m_spi->begin();
}

So define the following:

const uint8_t SD_CS_PIN = 38;  // or what CS is
#define SPI_CLOCK SD_SCK_MHZ(50)
#define SD_CONFIG SdSpiConfig(SD_CS_PIN, DEDICATED_SPI, SPI_CLOCK, &SPI1)

Try this:

  if (!sd.begin(SD_CONFIG)) {
    // Handle failure to init.
  }
ccdzapper commented 3 years ago

Hi Bill, Thank you for responding so quickly! The suggestions did work, with the following changes:

In setup(), I added: SPI1.setMISO(39); //SPI1.setMOSI(26); //SPI1.setSCK(27);

(For some strange reason, setMOSI and setSCK were not needed)

Additionally I reduced the frequency to 16 MHz by changing the definition:

define SPI_CLOCK SD_SCK_MHZ(16)

The program (SDInfo) ran through to the end where it prints all the relevant FAT information.

At 50 Mhz., it fails thus: init time: 0x11 ms readInfo failed SD errorCode: SD_CARD_ERROR_READ_TOKEN = 0x18 SD errorData = 0x1

And without the 'SPI1.setMISO(39)'

SD initialization failed. ..... ..... SD errorCode: SD_CARD_ERROR_CMD0 = 0x1 SD errorData = 0xff

I hope this information is helpful to others who may run into the same problem. For me, the key is in understanding that the 'port' parameter in the SdSpiConfig constructor is 'SPIx'

Thanks Ram

ccdzapper commented 3 years ago

A small modification to my previous message: It does work up to 45 Mhz, - it is possible that the full 50 Mhz. may work if this was a clean soldered up PCB. As such I am running these tests on a jumper wired breadboard, and with no added pull ups.

greiman commented 3 years ago

I suspect the 50 MHz is a wiring problem. Arduino SPI libraries should set the rate to the highest rate supported by the SPI controller that is less than or equal to the rate in SpiSetting(). So on an Uno the rate will actually be 8 MHz.

Paul is great with hardware libraries like SPI.h so I bet he has this correct.

I use Teensy 4.1 a lot and was sad that the built-in SD could not run in both SDIO mode and SPI mode Like Teensy 3.6. I may use the second SPI port in some projects.

ccdzapper commented 3 years ago

I tried exactly that (SPI on the built-in socket)with no luck . I tried your SdInfo code with the following initialization:

define SD_CONFIG SdSpiConfig(44, DEDICATED_SPI,45, &SPI2)

I selected the CS pin based on KurtE's useful spread sheet (https://github.com/KurtE/TeensyDocuments/blob/master/Teensy4%20Pins.xlsx) I also tried adding the SPI2.setMISO(42); line in setup() but still got the initialization error. Lastly, I 'scoped the tiny MicroSD card socket solder pads at the edge of the card while looping the initialization and found that both CS and SCK lines were pulsing, showing that the SPI code is accessing the card. I was hoping to build a more compact setup by using the built-in socket, but it seems I have to settle for an external SD socket wired to the SPI1 pins. I would have gladly used SDIO with its greater speed, but the blocking write would cause dropped sensor data.

greiman commented 3 years ago

You can map SPI to be on the 4.1 built-in SD socket but the signals are on the wrong SD pins.

greiman commented 3 years ago

The problem with write busy for SDIO is that Paul didn't put pull-up resistors on the SD signal lines. The SD specification requires the CMD and DAT lines to have a 10-100 kΩ pull-up. Busy is signaled by the state of DAT0.

On SPI I send a 0XFF byte and the card drives DAT0. on SDIO I can't clock the card. Maybe it would be possible to enable an internal pull-up. I will check DAT0 on Teensy 4.1, I think there is a eFuse for a pull-up also.

Edit: I do have pull-up enabled on the data lines. The problem is that I wait on busy after a write to get status. Avoiding write busy will require a major change to the driver.

greiman commented 3 years ago

I have made major changes to the Teensy SDIO driver. isBusy() now works for FIFO SDIO and write allows overlap of program execution with bus transfer of data to the SD.

See the TeensySdioLogger example for a 25 ksps ADC logger.

ccdzapper commented 3 years ago

Wow! Biil, Thank you so much! I will test it first thing tomorrow morning. At the risk of showing my age I must say I wondered for a long time as to why no one seems to complain about I/O writes holding up the processor - I worked on various systems since the 60's and even back then on the IBM 1401 series we had the beginng of process overlap where you could initiate a card feed command and move on to crunch on the numbers you just read from the previous card, returning just in time to perform the card read! In my current project I am trying to read MPU9250 data at 4K frames (64 bytes each) monotonically, i.e., without breaks in the data rate. Based on your earlier suggestions, I did get it to work on the Teensy 4.1 SPI1.
This new SDIO enhancement should enable me to use the built-in SD card. Thank You! Ram

greiman commented 3 years ago

You might want to look at the TeensyDmaAdcLogger for use of the RingBuf class for queuing data from the sensor if you are logging binary data. It uses an ISR to queue DMA blocks from the Teensy ADC at up to 6 MB/sec but you can use it in a loop.

Sounds like the MPU9250 would work well with a RTOS. A high priority thread reading the MPU9250 to a ring buffer and a lower priority thread writing to the SD.

My next project is to update the version of ChibiOS that runs on Teensy. It can do a context switch on Teensy 4.1 in 1/3 of a microsecond.