User avatar
Laurens-wuyts
Posts: 716
Joined: Wed Aug 21, 2013 7:35 pm
Location: Belgium
Contact: Website

SPI wrong data out

Wed Jan 04, 2017 9:07 pm

Hey everyone,

I have a little problem using the SPI in bare metal C.
I have it working in assembler, but I can't seem to find the fault in my C code.

The problem is that when i send data over SPI, on the data line, there are only 'spikes' and not the actual data.
Image

This is the code that I'm using:

Code: Select all

volatile uint32_t *SPI0 = (uint32_t*)SPI0_BASE;

void SPI0_Init( void ) {
	SetGpioFunction(8, 4);
	SetGpioFunction(9, 4);
	SetGpioFunction(10, 4);
	SetGpioFunction(11, 4);

	SPI0[CS] = 0x00000030; 
	SPI0[CLK] = 1024;
}

void SPI0_Begin( void ) {
	SPI0[CS] = 0x004000B0;
}

uint8_t SPI0_Send( uint8_t data_out ) {
	uint8_t data_in;

	while(!(SPI0[CS] & (1 << TXD))) {}
	SPI0[FIFO] = data_out;
	while(!(SPI0[CS] & (1 << DONE))) {}
	while(!(SPI0[CS] & (1 << RXD))) {}
	data_in = SPI0[FIFO];
	
	return data_in;
}

void SPI0_End( void ) {
	SPI0[CS] = 0x00400000;
}

void SPI0_Clear( void ) {
	SetGpioFunction(8, 0);
	SetGpioFunction(9, 0);
	SetGpioFunction(10, 0);
	SetGpioFunction(11, 0);
}
In main:

Code: Select all

SPI0_Init();
	
SPI0_Begin();
SPI0_Send(0x23);
SPI0_End();
	
SPI0_Begin();
SPI0_Send(0x11);
SPI0_End();
	
SPI0_Clear();
I checked in the examples of Dwelch and I couldn't see any main difference.
If any of you could have a look, that would be very helpful.

Thanks in advance.
Laurens

RareHare
Posts: 88
Joined: Thu Jun 20, 2013 7:17 pm

Re: SPI wrong data out

Tue Jan 31, 2017 8:51 am

I guess the easiest thing to do would be to enable assembly output on GCC. I see your use of volatile in your code to tell compiler not to optimize-away the check on TXD, but some compilers will interpret the uint_32 at the specific base address that you indicated as being volatile, and not the entire array of uint_32's that extend from that base address.

timanu90
Posts: 65
Joined: Sat Dec 24, 2016 11:54 am

Re: SPI wrong data out

Tue Jan 31, 2017 10:09 am

Hello

I believe the parameter you pass to the function SetGPIOFunction is wrong. From the datasheet SPI0 gpio need to be configured in ALT0 function. ALT0 function value is 0b100 not 0. Page 102 of BCM2835 ARM Peripherals.

Cheers
Tiago

baantonia
Posts: 63
Joined: Fri Feb 06, 2015 2:19 pm

Re: SPI wrong data out

Tue Jan 31, 2017 12:50 pm

ALT0, value 4, appears correct for the GPIO pins as you set this then later change them to input, value 0, but I do find that the read enable bit, bit 12 in the CS needs to be set. I too was scratching my head for some time why my bare metal SPI code wasn't working, but was working with a module wrote for RISCOS. This could be an issue?
As an aside, your chip CSPOL1 (bit 22) is being set, although you are using chip select 0 so therefore would have no effect.

LdB
Posts: 1239
Joined: Wed Dec 07, 2016 2:29 pm

Re: SPI wrong data out

Tue Jan 31, 2017 12:56 pm

He has set the GPIO port to mode 0x04 it's part of SPI0_Init.

RareHare has got it correct ... he has declared volatile both incorrectly and to a single register pointer SPI0_BASE

You compile that with -O2 and half the code will disappear (optimized away). You will probably also need to put some memory barriers in place to get the thing to execute in correct order if you have the cache turned on.

David's code I quickly looked at for the SPI uses GET32 and PUT32 which are assembler stubs and can't and won't be optimized away.

SPI0_BASE[CS] and SPIO_BASE[CLK] and any other single array entry is not SPI0_BASE and hence isn't covered by the volatile. The OP seems to be under the illusion volatile belongs to the whole array block just because it has the name SPIO_BASE at the start. It doesn't it is specific to the pointer it was used to describe, the individual array pointers are not volatile as described.

The OP needs definitions like this in his current scheme or throw the volatile in as a directive in front of each access

Code: Select all

volatile uint32_t * SPI0_CLK = (volatile uint32_t*) &SPI0_BASE[CLK];
volatile uint32_t * SPI0_CS =  (volatile uint32_t*) &SPI0_BASE[CS];
Even with that if you access one of the 4 bytes in singular form it isn't subject to the volatile. Volatile is not on the area it is on the pointer.

Take a look at a real Hardware driver, this sample is off the PI USB and look at the volatiles on each and every way the register can be accessed. You can't just put a single volatile of the HostGlobalRegs structure it doesn't work like that, hence you put it one each way the hardware can be accessed by a pointer.

Code: Select all

 extern volatile struct HostGlobalRegs {
	volatile struct {
		volatile enum {
			Clock30_60MHz,
			Clock48MHz,
			Clock6MHz
		} ClockRate : 2; // @0
		volatile bool FslsOnly : 1; // @2
		volatile unsigned _reserved3_6 : 4; // @3
		volatile unsigned en_32khz_susp:1; // @7
		volatile unsigned res_val_period:8; // @8
		volatile unsigned _reserved16_22 : 7; // @16
		volatile bool EnableDmaDescriptor : 1; // @23
		volatile unsigned FrameListEntries : 2; // @24
		volatile bool PeriodicScheduleEnable : 1; // @26
		volatile const bool PeriodicScheduleStatus : 1; // @27
		volatile unsigned reserved28_30 : 3; // @28
		volatile bool mode_chg_time:1; // @31
	} __attribute__ ((__packed__)) Config; // +0x400

User avatar
Laurens-wuyts
Posts: 716
Joined: Wed Aug 21, 2013 7:35 pm
Location: Belgium
Contact: Website

Re: SPI wrong data out

Tue Jan 31, 2017 3:27 pm

Thank you everyone for the very useful information. :)

I'll try to change the code to use volatile in the right way, when I have some time.
I'll see if I can get it working. ;)

Laurens

LdB
Posts: 1239
Joined: Wed Dec 07, 2016 2:29 pm

Re: SPI wrong data out

Tue Jan 31, 2017 4:40 pm

https://gcc.godbolt.org/
Your friendly online compiler makes life easier

Compile flags used
-O2 -mfpu=vfp -mfloat-abi=hard -march=armv6zk -mtune=arm1176jzf-s

I was expecting it to crunch it worse .. note I just fed in dummy constant values I don't really know them. Goto the image on fullscreen and you will see what I did. Doesn't seem to have optimized anything out and there is three wait loops so I am guessing you have the cache turned on and it's pre-fetching thru something.

I put where I think you will need memory barriers .. if you put them in the C code they will go to the right place anyhow :-)
I assume you have a memory barrier macro in your C code you probably needed it other places.

Code: Select all

SPI0_Send(unsigned char):
        ldr     r3, .L20
        ldr     r3, [r3]
        /* Might need a memory barrier  .. r3 must evaluate before 1st access to peripheral */ 
.L9:
        ldr     r2, [r3, #8]
        /* needs a memory barrier ... r2 must be read from IO can't pre-fetch*/
        tst     r2, #32
        beq     .L9
        str     r0, [r3, #12]
.L10:
        ldr     r2, [r3, #8]
        /* needs a memory barrier ... r2 must be read from IO can't be pre-fetched */
        tst     r2, #128
        beq     .L10
.L15:
        ldr     r2, [r3, #8]
        /* needs a memory barrier ... r2 must be read from IO can't be pre-fetched */
        tst     r2, #64
        beq     .L15
        ldr     r0, [r3, #12]
        uxtb    r0, r0
        bx      lr
.L20:
Image

Return to “Bare metal, Assembly language”