Whats the best way to write drivers that can use both SPI1 and SPI2?

victor_pv
Tue Jun 16, 2015 4:03 pm
We have at least 2 SPI ports in the STM32F1 mcus (and same goes for i2c), but most libraries are written for just SPI.

What would the best method to make libraries compatible with both ports, and make it easy to select which one to use?

The obvious easy way is to replace SPI.transfer etc for something like SPI_display.transfer, pretty much what everyone does with Serial.print, debugSerial.print, etc, and have a #define on the top of the header file, but is definitely not the most elegant. something such as:
#define SPI_display SPI
or
#define SPI_display SPI2

What other easy, mostly compatible ways we have, to make libraries compatible with both SPI ports, while at the same time not modifying them so much that existing sketches or examples need to be changed much. Anyone?


martinayotte
Tue Jun 16, 2015 5:19 pm
Hi Victor,

That is a big question …
The other thread viewtopic.php?f=13&t=53&start=30, which discussed about global instantiation and #ifdef bring the same question.
In my case, on my Netduino2Plus, I have 3 SPIs, one for SDCard, on for ENC28J60 and the third one is free to use on header pins.
I suggested to Roger to start another thread about this potential “can of worms”.
In the mean time, I think we don’t much choice to tweak core files according to our needs.


mrburnette
Tue Jun 16, 2015 5:27 pm
martinayotte wrote:
I suggested to Roger to start another thread about this potential “can of worms”.
In the mean time, I think we don’t much choice to tweak core files according to our needs.

martinayotte
Tue Jun 16, 2015 5:40 pm
Hi Ray,

I totally agree, and it is not only about the core files, but libraries too.
For example, how can we get the SD lib and the Ethernet lib running on separate SPIs at the same time if both are using the same global instance SPI ?
The link you provide simply shows that we can change pins, but nothing is mentionned about multiples instances.
This is really a “can of worms” that even Arduino community is facing, but no real answers to the question. :(
It sure that we can copy/paste libs to workaround that, but it is really ugly.
I wish to have all controls within the sketch itself, but I don’t know if we can achieve that.


victor_pv
Tue Jun 16, 2015 5:52 pm
mrburnette wrote:martinayotte wrote:
I suggested to Roger to start another thread about this potential “can of worms”.
In the mean time, I think we don’t much choice to tweak core files according to our needs.

Rick Kimball
Tue Jun 16, 2015 6:36 pm
We might look to the Energia port for inspiration. They have had to address a lot of these issues from day one. It started with a different architecture from the start and then moved to ARM cortex stuff later on.

In Energia, they have added a SPI.setModule() method for the arm cortex-m4f boards. This method lets you switch between an array of SPI devices. They have a table that maps the board pin numbers to the different SPI pins. So SPI.setModule(0); picks SPI1, SPI.setModule(1); picks SPI2. I haven’t looked at the details or actually used it.

-rick


martinayotte
Tue Jun 16, 2015 6:51 pm
Hi Rick,

Do you think it can be doable in the context of having everything done in the sketch and avoiding touching libs at all, including existing ones from Arduino ?


Rick Kimball
Tue Jun 16, 2015 6:56 pm
I think the intent is that you call SPI.setModule(x) before you make a call to something that uses the SPI calls. Whatever SPI module is set will be used by whatever is making SPI method calls.

martinayotte
Tue Jun 16, 2015 7:16 pm
I understand that in the context of doing call in the setup() or loop().
But how in the context of global constructor like Sd2Card for example ?
(Although it can work if everything is done in the begin() call by setup(), if nothing is done in the constructor itself, right ? …)

Rick Kimball
Tue Jun 16, 2015 7:21 pm
martinayotte wrote:I understand that in the context of doing call in the setup() or loop().
But how in the context of global constructor like Sd2Card for example ?
(Although it can work if everything is done in the begin() call by setup(), if nothing is done in the constructor itself, right ? …)

martinayotte
Tue Jun 16, 2015 7:39 pm
:lol:
But the existing libs are guilty :o

mrburnette
Tue Jun 16, 2015 8:36 pm
martinayotte wrote::lol:
But the existing libs are guilty :o

victor_pv
Tue Jun 16, 2015 8:54 pm
Rick Kimball wrote:I think the intent is that you call SPI.setModule(x) before you make a call to something that uses the SPI calls. Whatever SPI module is set will be used by whatever is making SPI method calls.

Rick Kimball
Tue Jun 16, 2015 8:57 pm
Yes, I think that is how it supposed to work. I was very active with Energia when it was on the msp430 and early arm cortex-m4 days. After that I mostly ignored it and I haven’t tried actually tried this SPI feature. I just know this was an issue the minute the TI boards had multiple SPI devices and that is how they addressed it.

0rick


RogerClark
Tue Jun 16, 2015 10:46 pm
Guys,

I just noticed this thread. ( I find it hard to keep track of which threads to spend my time reading, so if there is an important thread you think I should read, please feel free to PM me)

Global instantiation has been discussed a few times before, and there is no good solution.

Adding the CS pin stuff (due extended functionality), is a double edged sword. I didn’t think that the existing SPI libs on AVR controlled the CS pin at all.
Can someone confirm this ?

We can’t use the hardware NSS pin, as there is a bug / feature in the STM32 hardware that means its generally not usable.

We could put in extra code in SPI::transfer to set and reset a designated pin, but as transfer is a byte at a time, this would considerably slow down transfers, and just having an if statement to check if we should control the pin would also slow things down noticeably .

re: SPI::setModule()

This would be kind of slow.

We’d need to re instantiate SPI each time and possibly run all the hardware setup.

Well, I guess we could keep separate the lower and upper level of the current SPI class, so that the SPI class stores the data for multiple hardware SPI devices, e.g in an array, and switches which device it uses.

This would probably still retain the speed in SPI::transfer, but I’d need to look at the code to make sure

Or we ditch the global instantiation. But I can hear the screams from the newbies already, just by mentioning this.


martinayotte
Wed Jun 17, 2015 12:33 am
RogerClark wrote:I just noticed this thread. ( I find it hard to keep track of which threads to spend my time reading, so if there is an important thread you think I should read, please feel free to PM me)

RogerClark
Wed Jun 17, 2015 12:50 am
Martin

Re: Slowness

Actually, I think I should have gone back and edited my post. It was more a stream of consciousness

I agree.

if we implement

SPIClass::setModule(int spi_num)
{
switch (spi_num) {
#if BOARD_NR_SPI >= 1
case 1:
this->spi_d = SPI1;
break;
#endif
#if BOARD_NR_SPI >= 2
case 2:
this->spi_d = SPI2;
break;
#endif
#if BOARD_NR_SPI >= 3
case 3:
this->spi_d = SPI3;
break;
#endif
default:
ASSERT(0);
}

}


Rick Kimball
Wed Jun 17, 2015 1:14 am
I think you are over thinking it …

Here is what they do:

https://github.com/energia/Energia/blob … PI.cpp#L26


martinayotte
Wed Jun 17, 2015 1:21 am
Hi Roger,

Re: Pins and SPISettings,
those are already members of the SPI class, so it is not an issue, although it also need to become an array (depending of the implementation, because the current SPI class can be become an SPIport class and the SPI class can become parent with an array of SPIport)
For RAM consumption, there is maybe ways to not allocate them until the actual SPI.begin() been called for specific port number, which also leave the opportunity for backward compatibilty if SPI.setModule() is never called, it will always use the SPIdev[0].

It is sure that a lot of thinking still need to be done before starting such refactoring, but this can be a big milestone. :o

I can’t understand such brainstorming never became fruitful for boards such Mega or Due, the issue is there since years. :?

As Victor said in the first post, pretty much what everyone does with Serial.print, debugSerial.print, how many times we faced that.
Recently, personally, to do the debugging of SD lib under Netduino2Plus, I had to replace all Serial.printf to SerialUSB.printf (but not commit them) …
So, if our recipe work for SPI, we can even apply the same to Serial … but that’s another thougth … ;)


martinayotte
Wed Jun 17, 2015 1:24 am
Hi Rick,

Rick Kimball wrote:I think you are over thinking it …

Here is what they do:

https://github.com/energia/Energia/blob … PI.cpp#L26


RogerClark
Wed Jun 17, 2015 1:31 am
They use an array of structs / setings

static const unsigned long g_ulSSIPort[] = {
#ifdef TARGET_IS_BLIZZARD_RB1
GPIO_PORTA_BASE, GPIO_PORTF_BASE, GPIO_PORTB_BASE, GPIO_PORTD_BASE
#else
#ifdef __TM4C129XNCZAD__
GPIO_PORTA_BASE, GPIO_PORTB_BASE, GPIO_PORTD_BASE, GPIO_PORTF_BASE, GPIO_PORTG_BASE, GPIO_PORTQ_BASE
#endif
#ifdef __TM4C1294NCPDT__
GPIO_PORTA_BASE, GPIO_PORTB_BASE, GPIO_PORTD_BASE, GPIO_PORTF_BASE, GPIO_PORTQ_BASE
#endif
#endif
};


martinayotte
Wed Jun 17, 2015 1:51 am
Maybe I’m falling to sleep, I only see an array of base address here … :(

What is good to know is that you seems to day that is easy to do. :)


RogerClark
Wed Jun 17, 2015 1:57 am
umm

looks like multiple arrays

static const unsigned long g_ulSSIPins[] = {
#ifdef TARGET_IS_BLIZZARD_RB1
GPIO_PIN_2 | GPIO_PIN_3 | GPIO_PIN_4 | GPIO_PIN_5,
GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3,
GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3
#else
#ifdef __TM4C129XNCZAD__
GPIO_PIN_2 | GPIO_PIN_3 | GPIO_PIN_4 | GPIO_PIN_5,
GPIO_PIN_5 | GPIO_PIN_4 | GPIO_PIN_4 | GPIO_PIN_5,
GPIO_PIN_3 | GPIO_PIN_2 | GPIO_PIN_1 | GPIO_PIN_0,
GPIO_PIN_3 | GPIO_PIN_2 | GPIO_PIN_1 | GPIO_PIN_0,
GPIO_PIN_7 | GPIO_PIN_6 | GPIO_PIN_5 | GPIO_PIN_4,
GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3
#endif
#ifdef __TM4C1294NCPDT__
GPIO_PIN_2 | GPIO_PIN_3 | GPIO_PIN_4 | GPIO_PIN_5,
GPIO_PIN_5 | GPIO_PIN_4 | GPIO_PIN_4 | GPIO_PIN_5,
GPIO_PIN_3 | GPIO_PIN_2 | GPIO_PIN_1 | GPIO_PIN_0,
GPIO_PIN_3 | GPIO_PIN_2 | GPIO_PIN_1 | GPIO_PIN_0,
GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3
#endif
#endif
};


martinayotte
Wed Jun 17, 2015 2:33 am
I’m not sure our problem lie in those pins definitions since it is already handled.
I think the conclusion until now (but it is not concluded) is that we need SPI class becomes an array of SPIdev, right ?

RogerClark
Wed Jun 17, 2015 3:11 am
The SPI class has a variable which is a pointer to the hardware device struct.

It it uses this for all the low level access to the hardware

If that pointer was to a different hardware device, then the SPI class would interact with that SPI channel.

But the hardware device doesn’t store the bitOrder or the SPI mode or the clock divider, they are separate vars (properties) of the SPI class.

When you call begin, it has to set the bit Order and divider etc as they are part of the control register for the hardware.

It may be possible to read back the bit order and clock divider, so that when we set these values we only update the values we actually want to change, but I’d need to look at the code in detail.

because basically SPIClass:begin() is what does the initialization on the hardware.

I think at least to start with, its easier just to have use the SPISettings class / struct to store the data (or a struct that contains a pointer to the spi device struct and ints for bitOrder, clock divider and spi mode)

We could initially duplicate SPI and call it SPI_STM.h and then we can do independent testing.
(Note I could put this in a branch of the repo, but I find most people don’t use git on their machines, and just download the zip, so adding a tempoarary new SPI class folder is easier for people to test with)


victor_pv
Wed Jun 17, 2015 1:46 pm
RogerClark wrote:=

But the hardware device doesn’t store the bitOrder or the SPI mode or the clock divider, they are separate vars (properties) of the SPI class.

When you call begin, it has to set the bit Order and divider etc as they are part of the control register for the hardware.


Rick Kimball
Wed Jun 17, 2015 4:23 pm
I guess I’m totally off how the Energia setModule thing works .. I just spent some time looking at the code more deeply. It does invoke the begin() each time and that seems to be overkill and would be a performance killer.

However, I think we could implement something like it without doing the begin() each time. Just as @victor_pc explained.The SPI class just needs to have a concept of current SPI peripheral in use. The setModule(x) would just change the currently active SPI device. … I’ll have to play with this some to see how well this might work. Heh .. and yeah we need to tie in transactions support too, for those people using mulitple SPI devices on one SPI peripheral.

-rick


RogerClark
Thu Jun 18, 2015 1:04 am
Victor

Sorry. I think I didn’t explain my concerns very well.

Yes. The bit order etc are stored in the SPI control register.

Normally what happens is that there is one write to the control register to set up a whole bunch of things rather than doing multiple Read – Write cycles with appropriate masking to set individual bits of the register that set the various parameters e.g. bitOrder

I think the thing I was trying to work out the best way to do, was the initial setup.

Looking at the description of what SPI::Begin() does (according to Arduino.cc’s page)

Initializes the SPI bus by setting SCK, MOSI, and SS to outputs, pulling SCK and MOSI low, and SS high

What this omits to say is that it will also set up the default clock divider, and default mode and default bit order.

So we’d need to have those values stored in the SPI class so that they can be used in begin()

Well, that is, unless we can set those values in the SPI constructor, without turning the SPI on . (which we probably can)

But at the moment we can’t do stuff in the constructor because of the PIN MAP in RAM issue (which I still have not had time to get working)

If we fix PIN_MAP into Flash, we can setup defaults for divider, bit order and mode in the constructor, without actually enabling the SPI device (or activating the pins as SPI rather then GPIO)

Then we don’t need to store those vars, as we can modify the setBitOrder() etc func’s to just update the bits of the CR that need to be updated, rather than updating the whole CR in begin()

It will mean that what we do in begin() will need to change, so that it doesn’t change the bit order etc .

In regards to the use of setModule(). I think we are saying that we’d need to call setModule if you were using separate libraries that each used SPI and were hard coded to use SPI with no way to set another device.

I think this could still be relatively problematic, if any of these libs calls setBitOrder etc in their constructor. But at the moment libs that do that won’t work anyway. And no matter which way we do things, this would be an issue, because the order in which the constructors are called is not defined, and we’d not know the state of bitOrder that SPI1 has been left with.

Anyway, that aside.

I think that using setModule() is worth a go.


martinayotte
Thu Jun 18, 2015 1:19 am
Hi Roger,

So we’d need to have those values stored in the SPI class so that they can be used in begin()

I think it is already done using SPISettings member. Althougth I’m not sure it is done early, and it should have “defaults” values in case EndUsers never set them.

I think that using setModule() is worth a go.

Yes ! :)

We could initially duplicate SPI and call it SPI_STM.h and then we can do independent testing.

I agree on that too ! … and as soon we discover that new lib is backward compatible, we can simply do the switch over.


RogerClark
Thu Jun 18, 2015 12:37 pm
i took a quick look at modifying the SPI class and it doesnt seem to hard to do, but i did some overzealous search and replaces and i think I’ll need to start over (tomorrow)

But the basic principal seems to be have an array of SPISettings and also a pointer to the current setting. add spi_d to SPISettings, and change the code that uses spi_d etc to use
_currentSetting->spi_d
etc

then add a few method of setModule(int num)
which does

_currentSetting = &_settings[num];

i may have to play around with the dereferencing e.g pointers stuff, but i think the basic principal should be ok.

i will keep you posted.


victor_pv
Thu Jun 18, 2015 2:11 pm
Roger, if you are creating a new struct, is there any chance we can have the TX and RX dma channels for each port in that struct?
That way the dmaSend functions can get the TX and RX channel from there and be able to do DMA for both ports, rather than having SPI1 channels hardcoded as it is right now.

RogerClark
Thu Jun 18, 2015 9:18 pm
victor

i was going to use the SPISettings class, which is already defined in SPI.h,

And… Yes, i can add vars for these. are they board ints ?


RogerClark
Thu Jun 18, 2015 10:39 pm
Victor and @all

I’ve started the work to add setModule() to SPI, and it kind of works.

Well. I changed all the code over to use the settings in the SPISettings class (i.e struct), and the SD card is working fine.

However… strangely, on my test rig with had a VS1053 MP3 (SPI) board attached to SPI channel 2 (where I’m passing the pointer to SPI 2 it already).
It crashes completely as soon as I call begin on the VS1053 class.

I tried changing moving the VS1053 to use SPI1 (albeit the SD card is also on that channel) and it still crashes (well perhaps it gets stuck in a loop in the begin() in the VS1003 class

But, I’ll need to leave it until this evening, as I need to get on with the day job


RogerClark
Sun Jul 12, 2015 6:37 am
I have posted to the Announcements section that SPI setModule is ready for testing

See viewtopic.php?f=16&t=423

i.e it does work with the SD lib, but I think we also are getting more issues due to PIN MAP not being initialised early enough, so this needs to be the next thing I need to get into the repo


victor_pv
Sun Jul 12, 2015 3:00 pm
RogerClark wrote:I have posted to the Announcements section that SPI setModule is ready for testing

See viewtopic.php?f=16&t=423

i.e it does work with the SD lib, but I think we also are getting more issues due to PIN MAP not being initialised early enough, so this needs to be the next thing I need to get into the repo


Leave a Reply

Your email address will not be published. Required fields are marked *