There are quite a few similar questions, but none seem to have quite the same issue. I am connecting an STML4 MCU to a 6-axis sensor (LSM6DS3). I have successfully implemented everything in I2C, but would like the extra speed of SPI (and DMA, if I can get these first steps working...). So for a first step, I am trying to read the WHO_AM_I register (0x0F) of the device, which should reply with 0x69. Here is the code:
uint8_t who = 0;
// Sanity check/debugging aid should get 0x5D
who = 0x43 + 0x1A;
// Set SS low
GPIO_WritePin (GPIOB, LL_GPIO_PIN_7, GPIO_PIN_RESET);
// while tx buffer is in use, wait
while (!LL_SPI_IsActiveFlag_TXE(SPI1));
// Send READ command to the WHO_AM_I register
(SPI1->DR) = 0x8F;
// while rx buffer is in use, wait
while (!LL_SPI_IsActiveFlag_RXNE(SPI1));
// Get data off the register
who = (SPI1->DR);
// Wait for everything to wrap up before setting SS high again
while (LL_SPI_IsActiveFlag_BSY(SPI1));
// OK, now we can set SS high
GPIO_WritePin (GPIOB, LL_GPIO_PIN_7, GPIO_PIN_SET);
On the scope/analyzer, I see everything run as expected, including the sensor sending back 0x69. However, when I set a break on the other side of this code block, I see that who goes from 0 to 0x5D to 0xFF. It never reads the 0x69. I looked through other code examples and some people have a second transmit with the data set to some dummy value (usually 0xFF or 0x0), so I also tried that, but the sensor seems to get confused during the second attempt and who ends up being 0x48. I have tried every permutation of waiting for the RXNE/TXE/BSY flags that I could have, as well as many many other things to get the variable to correctly read the SPI1 data register, including reading other registers off the sensor, but all to no avail.
So then the question is, how does one correctly read values off this register?
I should also mention that I can successfully write to the device's registers. I can send the command I want, then read it back and see that it worked on the scope, even though I can never get the values assigned to a variable in code. I always get 0xFF.
I include a screen of my analyzer showing the sensor sending back 0x69 from a single read request, as well as the garbled mess it sends if I try the "dummy transmit" method.
SPI always (if the receiver is enabled) receives the data when you transfer.
This is the problem with the libraries that you do not know what is there. SPI is a lot easier to program using registers.
I assume that your data is 8bits.
You need to set the 1/4 (one byte) FIFO threshold during the SPI initialization by:
SPI1 -> CR2 |= SPI_CR2_FRXTH;
next you need to read the data from the FIFO after every write (you need also to force the compiler to use the correct size (in this case 8bits) load and store instructions):
*(volatile uint8_t *)&SPI1->DR = 0x8F; // write exactly 8 bits to the FIFO
while (!LL_SPI_IsActiveFlag_RXNE(SPI1));
dummy = *(volatile uint8_t *)&SPI-> DR;
*(volatile uint8_t *)&SPI1->DR = 0; // dummy write
while (!LL_SPI_IsActiveFlag_RXNE(SPI1));
who = *(volatile uint8_t *)&(SPI1->DR);
I do not know what is the point of using the LL libraries.
instead of
while (!LL_SPI_IsActiveFlag_RXNE(SPI1));
use registers
while (!(SPI1 -> SR & SPI_SR_RNE));
You can also wrap it into the function:
uint8_t SPI_ReadWrite8(SPI_TypeDef *spi, uint8_t data)
{
while(!(spi -> SR & SPI_SR_TXE));
*(volatile uint8_t *)&spi->DR = data;
while (!(spi -> SR & SPI_SR_RNE));
return *(volatile uint8_t *)&spi-> DR;
}
And
SPI_ReadWrite8(SPI1, 0x8f);
who = SPI_ReadWrite8(SPI1, 0);
Related
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.
I'm trying to interface these SPH0645 Mics (I2S) to a STM32f767ZI board.
I have wired it correctly, now just trying to test the mic by passing the data through UART to my pc. Hoping someone can point me in the correct direction.
I have tried passing straight to the UART transmit. However I think I may need some datahandling - I am receiving from the UART, but sometimes just 0 or other times just gibberish which is not from the Mic as it still transmits even when i disconnect the mic.
I2S mic is receiving data in 24 bits in 32 bit frame, the last 8 bits are junk. The protocol is Big Endian, I am thinking that the HAL library handles this, however I am not completely sure.
uint16_t data;
while (1)
{
/* USER CODE END WHILE */
HAL_StatusTypeDef result= HAL_I2S_Receive(&hi2s1,&data,2,100);
HAL_UART_Transmit(&huart3,&data,2,100);
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
What did I miss?
Check the return value of HAL_I2S_Receive(&hi2s1, &data, 1, 100)
Did you verify HAL_I2S_Receive expects uint32_t* as 2nd argument? I think it shall expect uint16_t*
Using HAL_UART_Transmit you want to transmit the data over UART. Shouldn't you pass data as an argument to HAL_UART_Transmit ?
is there a simple way to "bridge" 2 U(S)ART Lines (USART1 & UART5 on STM32F107) by software?
I need to forward the data which are incoming at USART1 to the UART5 and reverse.
Current MCU is STM32F107VCT7
Main reason why I want to do that is to update a Device over UART which is connected to my MCU. The MCU is connected to PC.
PC --- STM32 --- Other Device
Could you just write the data the data to the appropriate usart registers? That is, if you have a byte come through on the usart1 line and it is stored in the usart1 data register, read it and write it to the usart5 data register and set the bit to let the chip know it is ready transfer the byte. Do the same for the usart5 to usart1 bridge.
If you are concerned about reading/writing multiple bytes consider adding transmit and receive buffers to handle this.
If we were to do a polling method, the code would look something like (note: I mainly deal with AVR so I may be off with my register names but the method should be along these lines):
// Check to see if data has been written to usart1 and transfer it usart5.
if(USART1_CR1&(1 << USART1_SR_RXNE))
{
// May want to avoid race conditions, so disable interrupts.
// Write the data in usart1 to usart5
USART5_DR = USART1_DR;
// Set the data ready bit on the usart5 control register.
USART5_CR1 |= (1 << USART_SR_RXNE);
// Enable interrupts.
}
I used this tutorial for STM32 language. Not the point about disabling interrupts. You may also want to write to a buffer.
Simply copying bytes from one UART to the other will not work.
If the sending side (PC serial adapter) is just by 0.1% faster than the MCU, it will start dropping bytes after the 1000th byte. The frequency accuracy of the STM32F107 internal clock is given as -1.1% to +1.8% at room temperature in the datasheet, so it might as well happen earlier, and the other two participants might not be perfectly accurate either.
Dropping bytes in the middle of a firmware update to an otherwise inaccessible part is not going to be funny.
You need circular (FIFO) buffers both ways
The size of the buffer is determined by the size of the data packets, and the sum of the frequency inaccuracies in the participating devices. E.g. if there is 64 kB data in one block, and both devices have +/- 2% frequency accuracy, then you'd need at least 65536*0.04 ~ 2622 bytes buffer.
Do this in an endless loop,
check for RXNE in USART1->SR
if set, read the data from USART1->DR and put it at the head of the buffer (complain loudly if the buffer is full)
check for TXE in UART5->SR`
if set, AND the buffer is not empty, write the byte at the tail of the buffer into UART5->DR
do the same for the other direction
Simple both way communication method assuming the same speed of the both UARTs.
volatile uint8_t data[2];
void USART1_IRQHandler(void)
{
if(USART1 -> SR & USART_SR_RXNE)
{
data[0] = USART1 -> DR;
USART5 -> CR1 |= USART_CR1_TXEIE;
}
if( (USART1 -> CR1 & USART_CR_TXEIE) && (USART1 -> SR & USART_SR_TXE))
{
USART1 -> CR1 &= ~USART_CR1_TXEIE;
USART1 -> DR = data[1];
}
}
void USART5_IRQHandler(void)
{
if(USART5 -> SR & USART_SR_RXNE)
{
data[1] = USART5 -> DR;
USART1 -> CR1 |= USART_CR1_TXEIE;
}
if( (USART5 -> CR1 & USART_CR_TXEIE) && (USART5 -> SR & USART_SR_TXE))
{
USART5 -> CR1 &= ~USART_CR1_TXEIE;
USART5 -> DR = data[0];
}
}
I am trying to use an lcd screen on my stm32F3discovery.
The screen is made of an lcd 16 characters on 2 lines and an I2C module.
Here is the link of the product:
https://www.aliexpress.com/item/32763867041.html?spm=a2g0s.9042311.0.0.27424c4dsV7dLS
On the back of the screen I can see written: QAPASS 1602A
On the chip of the I2C module I can see written: PCF8574T
Here is the datasheet of the chip:
https://www.nxp.com/docs/en/data-sheet/PCF8574_PCF8574A.pdf
I tried to follow this tutoriel (the closest from what I am trying to do):
https://www.youtube.com/watch?v=1COFk1M2tak
I use the HAL library, the main function to send data is "HAL_I2C_Master_Transmit".
Here is the description of the function in "HAL_I2C_Master_Transmit":
#brief Transmits in master mode an amount of data in blocking mode.
#param hi2c Pointer to a I2C_HandleTypeDef structure that contains the configuration information for the specified I2C.
#param DevAddress Target device address: The device 7 bits address value in datasheet must be shifted to the left before calling the interface
#param pData Pointer to data buffer
#param Size Amount of data to be sent
#param Timeout Timeout duration
#retval HAL status
HAL_StatusTypeDef HAL_I2C_Master_Transmit(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t Timeout)
//I initialise a buffer
//I use a for loop to find the address of my slave: 0x3D (even tho on the //datasheet it's 0x3F, looks like A1 is bridged :O )
//I use the HAL_I2C_Master_Transmit function
//I move the address one bit to the left
//I reuse the HAL_I2C_Master_Transmit
//Nothing happens on the screen
//Here is my code (I tried to remove the useless comments):
#include "main.h"
I2C_HandleTypeDef hi2c1; // Init generated bu CubeMX
SPI_HandleTypeDef hspi1; // Init generated bu CubeMX
PCD_HandleTypeDef hpcd_USB_FS; // Init generated bu CubeMX
uint16_t adresseLCD; // the variable I put the slave address on
uint8_t buffer[]="123"; // The buffer I wanna see on the screen
void SystemClock_Config(void); // Generated by CubeMX
static void MX_GPIO_Init(void); // Generated by CubeMX
static void MX_I2C1_Init(void); // Generated by CubeMX
static void MX_SPI1_Init(void); // Generated by CubeMX
static void MX_USB_PCD_Init(void); // Generated by CubeMX
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_I2C1_Init();
MX_SPI1_Init();
MX_USB_PCD_Init();
adresseLCD=0x3D;
HAL_I2C_Master_Transmit(&hi2c1, adresseLCD, buffer, 1, 1000);
adresseLCD=adresseLCD*2; // Yes I could have used "adresseLCD<<1" but I
//am not used to that
HAL_I2C_Master_Transmit(&hi2c1, adresseLCD, buffer, 1, 1000);
while(1)
{
}
}
I expected something to show on the screen (even random values) but nothing appears (it lights up though).
I get no error (only warnings because I use "1" instead of "Pin_ON" when I WritePIn)
You expect it wrong.
First of allŠ± the I2C module, which is soldered on back, it is just simple serial-to parallel convertor. When you write data on I2C bus, it sets its 8 outputs according to 8 received bits in data bytes. When you read data bytes, it switches into input mode and read logical levels on 8 pins and transmits it over serial wire. More detailed description you can read in the datasheet on PCF8574 you have provided.
I.e. this part does not perform any "magic" which will take characters on input and output them on display. That means the outputting of random data is futile.
You need to know two things more:
how this module is connected to the display driver
what kind of display driver is used and how to utilize it.
Answer to the first question may be found in the Internet:
(taken from here)
You can see the display is connected in 4-bit mode, outputs P4-P7 (i.e. what you transmit in four most significant bits of data bytes) are connected to data lines of the display, while outputs P0-P2 are connected to control lines RS, R/W, EH and P3 is used to control the backlight.
Knowing this we came to the second question. I can only suggest, but more likely your display module have Hitachi HD44780 IC on it. In the datasheet on it you can found the information what data should be output on the control lines.
You can find pin description at page 8:
bit RS selects whatever it be a command (0) or a data (1)
bit R/W chooses write opertion (0) or read (1)
and bit E is actually a strobe. On the falling edge, i.e. when it changes from 1 to 0, the display driver reads data from data lines. That means to pass 4 bits of data you should perform 2 write operations: first one with bit 2 is set high, second with all other bits are the same, but the bit 2 is zero.
Now you can read list of instructions at page 25 of datasheet. And initialization sequence for the 4-bit mode at page 46 (Figure 24). Note for each line of bits there you actually send 2 data bytes: with bit 2 high and then with bit 2 low.
Note, in 4-bit mode all commands and data consist of two write phases: first - top half, then bottom half of a byte. Each phase is 2 data writes to I2C module, with E bit high and low, i.e. you'll need to send 4 bytes to output 1 byte of data.
So basically in order to transmit data from you STM32 to the LCD display driver (HD44780) you need to emulate the latter's interface signalling via the I2C interface chip (PCF8574).
In other words, MCU will send I2C commands that will toggle the I2C "backpack" chip such that it should emulate the right signaling for the LCD driver.
This happens somehow easy whne you are using the HAL_I2C_Master_*() methods. In the buffer array you specify the state of the pins on the LCD as you want them and in the order [0], [1], [2]... etc. For example, let's say that we have DB[7:4] connected to the upper 4 bits of the PCF I2C expander. We can setup the following:
buffer[0] = 0xD0 ; // 0b11010000;
buffer[1] = 0xA0 ; // 0b10100000;
buffer[2] = 0xF0 ; // 0b11110000;
HAL_I2C_Master_Transmit(&hi2c1, adresseLCD, buffer, 3, 10); // Note 3 bytes are sent
Once the buffer is prepared, the HAL_I2C_Master_Transmit() call will send the tree bytes consecutively, thus toggling the DB pins as you have mentioned:
DB7: 1 -> 1 -> 1
DB6: 1 -> 0 -> 1
DB5: 0 -> 1 -> 1
DB4: 1 -> 0 -> 1
The same can be applied to all 8 pins on the PCF chip. It is also worth noticing that this sequential IO update also creates a bit less of I2C communication overhead as it only addresses the I2C slave chip once :) .
By the way, I am currently working on a LCD over I2C Library for STM32F1xx (probably it will fit other STM32F series). You can check it out on github (still WIP):
https://github.com/meteowrite/stm32_i2cLcd
Cheers
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.