Problem utilizing spi module of beaglebone black - c

I have the following weird problem.
I have setup the BBB to activate the spi1 module. The the module is connected to an F-RAM chip (FM25CL64B). I have done all the necessary configuration. The /dev/spidev1.0 is present and I wrote a small program to write to, and read from the chip, by opening the /dev/spidev1.0 and using ioctl with the SPI_IOC_MESSAGE macro command.
Using that program I managed to successfully write 32 bytes of text in to the F-RAM chip. Reading also seemed to be successful... How do I know they both were successful? I used a logic analyzer with an SPI decoder activated to actually see what's going on over all four of the SPI lines. Monitoring all SPI lines I could see that the write and read operations generate the correct signals with the correct timings, and all signals are in sync. The CS enables the chip during the transaction, CLK clocks every byte in 8-bit words (as configured), the data lines show the correct values, which I could see thanks to the SPI decoder which shows the byte value right above each 8-bit signal sequence of both MOSI and MISO lines.
The problem is that even though I can see that correct information is sent over the MISO line during read operation, the buffer I provide to the ioctl(iSPIR, SPI_IOC_MESSAGE(2), xfer) is filled with zeros.
I intentionally initialized that buffer with other values, so I can see if the ioctl even writes into it. And it does. Zeros.
Now, the fact that I could see all the bytes sent over the MISO line during the read operation, proves that the writing operation not only looked right in the analyzer but actually wrote the intended data during the previous write operation.
I checked several times whether the MISO line is configured correctly, in the dts file (which I can rebuild and reinstall on demand). I checked if it's the correct pin, and if it's configured as input. Everything seemed to be correctly configured.
I ran the program as root in case there are permission issues - no difference.
I also implemented the spi communication in GPIO mode. I.e. the spi module is disabled, all lines configured as GPIO. CE, CLK, MOSI configured as otuputs and the MISO configured as input. This way I could implement the entire communication in software so I could have full control over the lines. Doing that, this time, I was able to successfully fill a buffer with the correct data from the F-RAM chip. I.e. the sequential read operation went fine from the F-RAM chip all the way to my user space buffer. I was able to print the data into the console. However, that worked way too slow. Also I find it inefficient to use purely software implementation of SPI com when there is a module available for use.
In order to write my sample program I used the spi_test.c open source example available online.
I also built and ran spi_test.c itself with no modification, same result.
Here is listing of my program (Relevant snippets):
// SPI config ...
int InitSPIReadMode(const char* pstrDeviceF)
{
int file;
__u8 wr_mode = SPI_MODE_0, rd_mode = SPI_MODE_0, lsb = 0, bits = 8;
__u32 speed = CLOCK_FREQ_HZ; // 500kHz
if((file = open(pstrDeviceF, O_RDWR)) < 0)
{
printf("Failed to open the bus.");
/* ERROR HANDLING; you can check errno to see what went wrong */
exit(1);
}
if(ioctl(file, SPI_IOC_RD_MODE, &rd_mode) < 0)
{
printf("SPI rd_mode\n");
return -1;
}
if(ioctl(file, SPI_IOC_RD_LSB_FIRST, &lsb) < 0)
{
printf("SPI rd_lsb_fist\n");
return -1;
}
if(ioctl(file, SPI_IOC_RD_BITS_PER_WORD, &bits) < 0)
{
printf("SPI bits_per_word\n");
return -1;
}
if(ioctl(file, SPI_IOC_RD_MAX_SPEED_HZ, &speed) < 0)
{
printf("SPI max_speed_hz\n");
return -1;
}
printf("%s: spi wr-mode=%d, spi rd-mode=%d, %d bits per word, %s, %d Hz max\n", pstrDeviceF, wr_mode, rd_mode, bits, lsb ? "(lsb first) " : "(msb first)", speed);
xfer[0].cs_change = 0; /* Keep CS activated */
xfer[0].delay_usecs = 0; //delay in us
xfer[0].speed_hz = CLOCK_FREQ_HZ; //speed
xfer[0].bits_per_word = 8; // bites per word 8
xfer[1].cs_change = 0; /* Keep CS activated */
xfer[1].delay_usecs = 0;
xfer[1].speed_hz = CLOCK_FREQ_HZ;
xfer[1].bits_per_word = 8;
return file;
}
In the main function: (as the logic analyzer shows this code correctly sends the command, address and clocks the 32 bytes of data afterwards)
int iSPIR = InitSPIReadMode("/dev/spidev1.0"); //open("/dev/spidev1.0", O_RDWR | O_SYNC);
char arrInstruct[3] = { OPCO_READ, 0x00, 0x00 };
char arrFRamData[512];
for(int pos = 0; pos < 512; pos++) arrFRamData[pos] = pos;
xfer[0].tx_buf = (unsigned long)arrInstruct;
xfer[0].len = 3;
xfer[1].rx_buf = (unsigned long)arrFRamData;
xfer[1].len = 32;
if(ioctl(iSPIR, SPI_IOC_MESSAGE(2), xfer) < 0) printf("ioctl write error %s.\n", strerror(errno));
// hex dumping of the arrFRamData buffer.
xfer is a global variable defined as:
struct spi_ioc_transfer xfer[2];
Thanks a lot in advance! :)

I found what the problem was with my setup. But even though the whole thing works now, the solution I applied raises more questions than answers.
So I found this solution in an article (https://elinux.org/BeagleBone_Black_Enable_SPIDEV) on the beaglebone wiki.
There I noticed that, in the device-tree overlay they set the CLK as an input. Reading the entire article turned out nothing as to why the CLOCK on the BBBs side has to be an input. Even though it's the master... The article only explains how to build and install the new DTBO in order to activate the SPI1 module.
So, even though it didn't make any sense to me, I though it wouldn't hurt to try changing the CLK line in my DTBO file form output to input to see what happens... And it worked! :O
Now, why is it so strange that the setup works like this. The BBB is supposed to be the SPI master, so its clock line should be an Output in order to drive this synchronous communication. That means the FM25CL64B chip must act as SPI slave. I just checked the datasheet and yes, the CLK on the chips side, is an input. So the CLK on the BBB must be an output. That's how I've configured the CLK pin on the BBB. As an output. And it didn't work.
This is why I'm so puzzled. So it works even though both ends of the CLK line are inputs?! Looking at the logic analyzers output, I can clearly see that the CLK line is being driven correctly. But if both the master an slave have inputs on that line there shouldn't be anything to generate those impulses?! There is nothing else connected to that line....

Related

Mixed voltage reading from different AD7606 channels

Please help! I am using FSMC to connect a STM32F407 MCU with AD7606 to sample voltage value. MCU would send sampled values to PC using USB HS port after 1024 conversions. But when I inspect the values from PC, I found that readings from channel 0 occasionally contains data from other channels. For example, if connect channel 0 to 5v, connect channel 8 to 3.3v, connect other channels to ground. Then the printed value from channel 0 would contain 5v, 0v, 3.3v. The basic setup is as follows:
A 200KHZ PWM single is generated by TIM10 to act as CONVST signal for AD7606.
7606 will then issue a BUSY signal which I used as an external interrupt source.
In the Interrupt handler, An DMA request would be issued to read 8 16bit data
from FSMC address space to memory space. TIM10 PWM would be stopped if 1024
conversions has been done.
In the DMA XFER_CPLT call back, if 1024 conversions has been done, the converted
data would be sent out by USB HS port, and TIM10 PWM would be enabled again.
Some code blocks:
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
if(GPIO_Pin == GPIO_PIN_7)
{
// DMA data from FSMC to memory
HAL_DMA_Start_IT(&hdma_memtomem_dma2_stream0, 0x6C000000, (uint32_t)(adc_data + adc_data_idx) , 8);
adc_data_idx += 8;
if (adc_data_idx >= ADC_DATA_SIZE)
HAL_TIM_PWM_Stop(&htim10, TIM_CHANNEL_1);
}
}
void dma_done(DMA_HandleTypeDef *_hdma)
{
int i;
int ret;
// adc_data[adc_data_idx] would always contain data from
// channel 1, led1 wouldn't light if every thing is fine.
if (adc_data[adc_data_idx] < 0x7f00 )
HAL_GPIO_WritePin(led1_GPIO_Port, led1_Pin, GPIO_PIN_SET);
if (adc_data_idx >= ADC_DATA_SIZE)
{
if(hUsbDeviceHS.dev_state == USBD_STATE_CONFIGURED)
{
// if I don't call CDC_Transmit_HS, everything is fine.
ret = CDC_Transmit_HS((uint8_t *)(adc_data), ADC_DATA_SIZE * 2 );
if (ret != USBD_OK)
{
HAL_GPIO_WritePin(led1_GPIO_Port, led2_Pin, GPIO_PIN_SET);
}
}
adc_data_idx = 0;
HAL_TIM_PWM_Start(&htim10, TIM_CHANNEL_1);
}
}
It seems that a single USB transaction would take longer than 5us(one conversion time), so I stopped PWM signal to stop conversion...
If I only send the second half of the data buffer, there is no data mixture. It's very strange.
According to your description, I think the processing is correct, and the problem is at the CDC_Transmit_HS(); I have met the problem on the CDC_Transmit_FS(), which can't transmit more than 64 bytes data for original code, and need to modify some code, otherwise the some error occurs. Did you check the number of received data is correct?
Reference:
I can't receive more than 64 bytes on custom USB CDC class based STM32 device
I'm not sure your ADC_DATA_SIZE size; if it's larger than 64 bytes, maybe you can modify to smaller than 64 bytes and try again and check whether or not the data is correct. I am not sure if it is affected by this problem, but I think you can give it a try.
On the other hand, it may also be necessary to GND the ADC IN pins not used by AD7606 to avoid interference between channels.
Or you can try other communication (I2C, SPI, UART...etc) to send the data.
If there is no problem with other communication methods, there is a high chance that it is a problem with CDC_Transmit_HS(). If there are problems with other transmission methods, you may have to check whether there is a conflict between the ADC conversion time or the transmission time.

Why would comparisons on my Xmega work when a breakpoint is set on that line, but not when running normally?

I am using an Xmega384C3, and all I am trying to do is send a signal down an output port and read it on an input port. I have PORTC set as an output port, and PORTA set as an input port. The corresponding pins on each port are shorted together (PortA pin 0 to PortC pin 0, etc).
My issue appears in the following code:
uint8_t i;
int count = 0;
for(i=2; i<8; i++) {
PORTC.OUT = (1 << i);
if (PORTA.IN == PORTC.OUT) {
count++;
}
}
if (count == 6) {
//success
}
I basically just want to read that when I send a logic high down a pin on PORTC, that I can read it on the corresponding pin on PORTA. When I let the code run normally, it does not find any matches and therefore never increments count. However if I add a break point on the line where the if comparison occurs, it then evaluates to true and increments count as expected. Additionally, I can see the ports have the correct values in Atmel Studio's I/O view feature. Any ideas?
It was a timing issue of not allowing long enough for the value on the pin to reflect what I set it as in the code. The issue was resolved by adding a 1 micro second delay using the delay function _delay_us(1) from the delay.h library.
See Any XMEGA Manual > I/O Ports > Overview > General I/O Pin Functionality
Before the IN register there is a synchronizer so the input signal does not instantly appear in the input register. A couple of clock cycles would be enough to wait.

Bit banging for SPI in ARM

I am trying to read the data from FXLS8471Q 3-Axis, Linear Accelerometer using SPI. I am using bit banging method to read the data from Accelerometer. I am using LPC 2184 ARM processor. I used the following code.
unsigned char spiReadReg (const unsigned char regAddr)
{
unsigned char SPICount;
unsigned char SPIData;
SPI_CS = 1;
SPI_CK = 0;
SPIData = regAddr;
SPI_CS = 0;
for (SPICount = 0; SPICount < 8; SPICount++)
{
if (SPIData & 0x80)
SPI_MOSI = 1;
else
SPI_MOSI = 0;
SPI_CK = 1;
SPI_CK = 0;
SPIData <<= 1;
}
SPI_MOSI = 0;
SPIData = 0;
for (SPICount = 0; SPICount < 8; SPICount++)
{
SPIData <<=1;
SPI_CK = 1;
SPIData += SPI_MISO;
SPI_CK = 0;
SPIData &=(0xFE);
}
SPI_CS = 1;
return ((unsigned char)SPIData);
}
But instead of getting valid value 0x6A , I am getting garbage value.
Please help me out to solve this problem;
SPIData &=(0xFE); as pointed out in another answer, is definitely wrong as it erases the bit you just received.
However, there are other major issues with your code.
An SPI slave device sends you data by setting the value of MISO on a rising or falling clock, depending on the type of device. However, you didn't wait in your code for the value to appear on MISO.
You control the communication by setting the clock to 1 and 0. The datasheet of the accelerometer says on page 19, that
Data is sampled during the rising edge of SCLK and set up during the falling edge of SCLK.
This means that in order to read from it, your processor needs to change the clock from one to zero, thereby signaling the accelerometer to send the next bit to the MISO. This means you did the reverse, you in your code read on a rising edge while you should be reading on the falling edge. After setting the clock to zero, you have to wait a little while until the value appears on MISO, and only then should you read it and add it to your SPIData variable. Table 9, SPI timing indicates how much you have to wait: at least 500 nanoseconds. That's not much, but if your CPU runs faster than 2 MHz then if you don't use a delay, you will try to read the MISO before the accelerometer had time to properly set it.
You also forgot the slave Select, which is actually required to indicate the beginning and the end of a datagram.
Check the Figure 7. SPI Timing Diagram in the datasheet, it indicates what you are required to do and in what order, to communicate with the device using SPI.
I also suggest reading about how the rotating registers of the SPI work, because it seems from its datasheet, that the accelerometer needs to receive a well specified number of bits before useful data appears on its output. Don't forget, as you send a single bit to the device, it also has to send a bit back to you, so if it didn't decode a command yet, it can only send gibberish. Your code, as the master, can only "push" bits in, and collect the bits which "pop out" on the other side. This means you have to send a command, and then send further bits until all the answer is pushed out to you bit by bit.
If you get stuck, I think you will have much more luck getting help on https://electronics.stackexchange.com/, but instead of just putting this same code there (which seems to be blindly copied from www.maximintegrated.com and has absolutely nothing to do with the problem you try to solve), I strongly recommend trying to understand the "Figure 7. SPI Timing Diagram" I was suggesting before, and alter your code accordingly.
Without understanding how the device you try to communicate with works, you will never succeed if you just blindly copy the code from a completely different project just because it says "spi" in the title.
SPIData &=(0xFE);
The above line is causing the problem. Here the LSB is reset to 0 (which contained the data bit just taken from MISO) -- basically you are destroying the bit you just read. Omitting the line should correct the problem.
be sure to compile your function with NO optimization
as that will corrupt the bitbang operation.
this is the code from the maxum site for the spiReadReg function.
(which looks like were you got your code.
However, this is just a guide for the 'general' sequence of operations for communicating with the maxim 1481 part.
the accel. part needs several setup commands and reads completely differently
Suggest reading the app notes and white papers available at freescale.com for the specific part number.
Those app notes/white papers will indicate the sequence of commands needed for setting up a specific mode of operation and how to request/interpret the resulting data.
There are a number of device specifics that your code has not taken into account.
Per the spec sheet the first bit transmitted is a read/write indicator, followed by 8 bits of register address, followed by 7 trash bits (suggest sending all 0's for the trash bits.) followed by the data bits.
Depending on the setup commands, those data bits could be 8 bits or 14 bits or multiple registers of 8 or 14 bits per register.

How to get silabs Si1141 out of suspended mode

Trying to read the PS1 values. But as im running the following code it keeps saying on "chip_stat" that its suspended.
main (void){
init(); // Configuration initialization
si1141_init(); // Si1141 sensor initialization
__delay_ms(30); // Delay to ensure Si1141 is completely booted, must be atleast 25ms
si1141_WriteToRegister(REG_IRQ_STATUS, 0xFF); // Clear interrupt source
signed int status;
while(1){
WriteToI2C(0x5A<<1); // Slave address
PutByteI2C(0x30); // chip_stat
ReadFromI2C(0x5A<<1); // Slave address
if((status = GetByteI2C(0x30)) == Sw_I2C_ERROR) // chip_stat
{
return Sw_I2C_ERROR;
}
Stop_I2C();
status++;;
}
}
The code im using to read the PS1 values is the following. Im reading the value 16705. Which keeps being the same on all measurements.
The value should go up and down from 0 - 32767, as it measures more or less movement.
signed int si1141_ReadFromRegister(unsigned char reg){
signed int data;
WriteToI2C(0x5A<<1); // Slave address
ReadFromI2C(0x5A<<1); // Slave address
if((data = GetByteI2C(Sw_I2C_LAST)) == Sw_I2C_ERROR)
{
return Sw_I2C_ERROR;
}
Stop_I2C();
return data;
}
main (void){
init(); // Configuration initialization
si1141_init(); // Si1141 sensor initialization
__delay_ms(30); // Delay to ensure Si1141 is completely booted, must be atleast 25ms
si1141_WriteToRegister(REG_IRQ_STATUS, 0xFF); // Clear interrupt source
signed int PS1;
while(1){
PS1 = si1141_ReadFromRegister(REG_PS1_DATA0) + (256 * si1141_ReadFromRegister(REG_PS1_DATA1)); // Proximity CH1
}
}
I linked the files for the i2c communication.
https://www.dropbox.com/s/q41vw444gjvj0qa/swi2c.c?dl=0
https://www.dropbox.com/s/1mshyz88o15hz8c/swi2c.h?dl=0
Rule out I2C errors first. Your software I2C library is no help at all.
Make sure you read registers PART_ID, REV_ID, SEQ_ED first and that the values match the data sheet resp. your expected values. This is to rule out I2C errors.
You have to take quite a few steps to get a single reading to get started.
Reset the Si114x. Program the HW_KEY. Program PS_LED21 to a sensible value. The ANs tell you how. Do not program a higher value than what your components can handle and what your design can support. This might destroy something if done incorrectly. Do not get any funny ideas about PS_ADC_GAIN either, or you will fry your device. Read the AN. Do not program PS_ADC_GAIN at this point.
Clear PSLED21_SELECT -- only PS2_LED, keep PS1_LED set for LED1, obviously -- and PSLED3_SELECT. This is probably optional, but the datasheet tells you to do it, so do it.
Next, program CH_LIST to PS1_EN, then send a PS_FORCE command.** Now read PS1 data from PS1_DATA0 and PS1_DATA1. Done.
It may be easier to test with ALS first to rule out saturating your sensor with some stray infrared (think setting sun as you work through the night).
** For the command protocol, you have to implement the command/response protocol laid out in the datasheet. I suggest you test with reset and nop first to verify your code.

PIC16F1459 I2C Master Ack Issue with 24LC32

I'm facing a weird issue. I've always used bit bangin I2C functions on my PIC16F1459, but now I want to use the MSSP (SPI,I2C Master Slave Peripheral). So I've started writing the functions according to the datasheet, Start, Stop, etc. The problem I have is my PIC won't ACK the data I send to the I2C EEPROM. It clearly says in the datasheet that the ACK status can be found at SSPCON2.ACKSTAT. So my guess was to poll this bit until the slave responds to my data, but the program hangs in the while Loop.
void vReadACK (void)
{
while (SSPCON2.ACKSTAT != 0);
}
And here's my write function, my I2CCheck function and I2C Master Initialization function
void vI2CEcrireOctet (UC ucData, UC ucRW)
{
vI2CCheck();
switch (ucRW)
{
case READ:
SSPBUF = ucData + 1;
break;
case WRITE:
SSPBUF = ucData + 0;
break;
}
vReadACK();
}
void vI2CCheck (void)
{
while (SSPCON2.ACKEN); //ACKEN not cleared, wait
while (SSPCON2.RCEN); //RCEN not cleared, wait
while (SSPCON2.PEN); //STOP not cleared, wait
while (SSPCON2.SEN); //Start not cleared, wait
while (SSPCON2.RSEN); //Rep start not cleared, wait
while (SSP1STAT.R_NOT_W); //TX not done wait
}
void vInitI2CMaster (void)
{
TRISB4_bit = 1; //SDA IN
TRISB6_bit = 1; //SCL IN
SSP1STAT.SMP = 1; //No slew rate
SSP1STAT.CKE = 0; //Disable SMBus inputs
SSPADD = 0x27; //100 KHz
SSPCON1 = 0b00101000; //I2C Master mode
SSPCON3 = 0b00000000; //Rien de slave
}
Just so you know, 24LC32A WriteProtect tied to VSS, A2-A1-A0 tied to GND, so adress 0xA0. 4k7 pull-ups are on I2C line. PIC16F1459 at 16MHz INTOSC.
I'm completely stuck. I've went through the MSSP datasheet 5 to 6 times without finding any issue. Can you guys help?
And here's my logic analyzer preview (removing the while inside vReadAck() )
Well it looks like I've found the answer to my question. What I was doing was the exact way of doing this. The problem seemed to be the Bus Free Time delay required for the slave to respond. At 16Mhz, my I2C was probably too fast for the EEPROM memory. So I've added a small Delay function right after the stop operation, so the write sequences are delayed and BAM, worked.
Bus free time: Time the bus
must be free before a new
transmission can start.
Despite the fact you "totally know" know "PIC won't ACK the data I send to the I2C EEPROM" because it's not supposed to, you still seem to misunderstand how I2C acknowledgements are supposed to work. They're called acknowledgements because they can be both positively (ACK) and negatively (NAK) acknowledged. If you look at the the analyzer screen shot you posted you'll find that its quite clearly labelled each byte being sent as having been NAK'ed by the transmitter.
To properly check for I2C ACKs you should be polling the trailing edge of the ACKTIM bit, and then checking the ACKSTAT bit to find out whether the slave transmitted an ACK or a NAK bit. Something like this:
int
vReadACK() {
while(!SSPCON3.ACKTIM);
while(SSPCON3.ACKTIM);
return SSPCON2.ACKSTAT;
}
As for why your slaved device is apparently NAKing each byte it isn't clear from the code you've posted, but there's a couple of notable omissions from your code. You need to generate start and stop conditions but you've shown no code to do this.

Resources