Strange magnetometer readout - arduino

I am attempting to readout the data from my MPU-9150 magnetometer and getting some odd numbers. I have accessed the magnetometer within the IMU and am getting data which changes with the orientation of the IMU but is not within the range specified by the product specification guide. I'm think its probably something to do with either the variable type i am using to store the data or the method i am using to manipulate the twos compliment data to make it readable, so here is the code anyway...
void MPU9150::getMag(double* mag_X, double* mag_Y, double* mag_Z){
uint8_t asax, asay, asaz;
I2Cdev::writeByte(MPU9150_ADDRESS, INT_PIN_CFG, 0x02);//Set i2c bypass enable pin to true to access magnetometer.
I2Cdev::writeByte(MPU9150_MAG_ADDRESS, MPU9150_MAG_CNTRL, 0x0f);//Fuse Rom access mode.
I2Cdev::readBytes(MPU9150_MAG_ADDRESS, MPU9150_MAG_ASAX, 3, buffer);//Get sensitivity adjustment values.
asax = buffer[0];
asay = buffer[1];
asaz = buffer[2];
// Serial.print("asax = "); Serial.print(asax); Serial.print("\n");
// Serial.print("asay = "); Serial.print(asay); Serial.print("\n");
// Serial.print("asaz = "); Serial.print(asaz); Serial.print("\n\n");
I2Cdev::writeByte(MPU9150_MAG_ADDRESS, MPU9150_MAG_CNTRL, 0X01);//Enable the magnetometer.
delay(10);
I2Cdev::readBytes(MPU9150_MAG_ADDRESS, MPU9150_MAG_XOUT_L, 6, buffer);//Read magnetometer readings.
mx = ((((int16_t)buffer[0]) << 8) | buffer[1]) * 0.3;
my = ((((int16_t)buffer[2]) << 8) | buffer[3]) * 0.3;
mz = ((((int16_t)buffer[4]) << 8) | buffer[5]) * 0.3;
*mag_X = mx * ((((asax - 128)*0.5)/(128)) + 1);//Adjust readings with sensitivity adjustment values.
*mag_Y = my * ((((asay - 128)*0.5)/(128)) + 1);
*mag_Z = mz * ((((asaz - 128)*0.5)/(128)) + 1);
}
The decimal range for each axis should be between 4096 and -4096, and there are 6 data registers each containing 8 bit high and low values for each axis. The data that i am getting seems to be in the range >10000 to <-10000 with the highest value i have seen at 9830. If anyone has any ideas they would be appreciated, thanks :)
EDIT: Buffer is a private integer array, used to hold the bytes from the axis registers.
private:
uint8_t buffer[14];
Also reversing the order of the bytes (as i have realised should be done as the first byte in each register is low and the second high) has resulted in the range of the output reduced to a range of about -170 to 170...
mx = (((int16_t)buffer[1]) << 8) | buffer[0];
my = (((int16_t)buffer[3]) << 8) | buffer[2];
mz = (((int16_t)buffer[5]) << 8) | buffer[4];

Related

How to show distance below than cm use sensor TF-Luna Lidar

I want to ask about TF-Lunar Lidar, I've written code to know the distance in "cm" by reading the array data, right now I need some help about how to read the data so it can be showing below "cm", (mm or below but in decimal).
this is the code
#include <SoftwareSerial.h> //header file of software serial port
SoftwareSerial Serial1(2,3); //define software serial port name as Serial1 and define pin2 as RX and pin3 as TX
/* For Arduinoboards with multiple serial ports like DUEboard, interpret above two pieces of code and
directly use Serial1 serial port*/
float dist; //actual distance measurements of LiDAR
int strength; //signal strength of LiDAR
float temprature;
int check; //save check value
int i;
int uart[9]; //save data measured by LiDAR
const int HEADER=0x59; //frame header of data package
void setup() {
Serial.begin(9600);
Serial1.begin(115200); //set bit rate of serial port connecting LiDAR with Arduino
}
void loop() {
if (Serial1.available()) { //check if serial port has data input
if(Serial1.read() == HEADER) { //assess data package frame header 0x59
uart[0]=HEADER;
if (Serial1.read() == HEADER) { //assess data package frame header 0x59
uart[1] = HEADER;
for (i = 2; i < 9; i++) { //save data in array
uart[i] = Serial1.read();
}
check = uart[0] + uart[1] + uart[2] + uart[3] + uart[4] + uart[5] + uart[6] + uart[7];
if (uart[8] == (check & 0xff)){ //verify the received data as per protocol
dist = uart[2] + uart[3]*256; //calculate distance value
strength = uart[4] + uart[5] * 256; //calculate signal strength value
temprature = uart[6] + uart[7] *256;//calculate chip temprature
temprature = temprature/8 - 256;
Serial.print("dist = ");
Serial.print(dist);//output measure distance value of LiDAR
Serial.print('\t');
Serial.print("strength = ");
Serial.print(strength); //output signal strength value
Serial.print('\t');
Serial.print("Chip Temprature = ");
Serial.print(temprature);
Serial.print(" celcius degree"); //output chip temperature of Lidar
Serial.print('\t');
Serial.print("check");
Serial.println(check);
}
}
}
}
}
I don't believe your code is flawed in any way, but your request is quite impossible. If my understanding is correct, the Luna's max resolution is 1 cm, so you shouldn't have any data for mm or decimals past cm. See specs as listed on dfrobot. https://www.dfrobot.com/product-1995.html

How do I properly draw a frame with an ILI9341? Arduino

I have a 2.8 inch TFT LCD from Adafruit driven by an ILI9341. To draw on the LCD, I use the commands 0x2A to set the width, 0x2B to set the height, and 0x2C to color each pixel (I2C). My problem is that I currently draw the background as a rectangle, and then each rectangle required. This creates a displeasing look since the colors start to mix and create disturbing, angled lines between the smaller rectangles. These are the functions I use to draw:
void drawRectangle(long sX, long sY, long eX, long eY, uint16_t clr) {
send32bitData(0x2A, (sX << 16) | eX);
send32bitData(0x2B, (sY << 16) | eY);
oneColor(clr, (eX - sX + 1) * (eY - sY + 1));
}
void oneColor(uint16_t clr, long area) {
uint8_t hi = clr >> 8;
uint8_t lo = clr;
uint8_t dHi = clr >> 6, bHi = clr >> 14, dLo = clr << 2, bLo = clr >> 6;
sendCommand(0x2C, true);
PORTC = 0x6;
for (long i = area; i--; i > 0) {
PORTC = 0x6;
PORTD = dHi;
PORTB = bHi;
PORTC = 0x2;
PORTC = 0x6;
PORTD = dLo;
PORTB = bLo;
PORTC = 0x2;
}
PORTC = 0x7;
}
Here's a video of that line. I'm assuming that I'm drawing wrong. I'd like to keep a huge array with colors for each pixel, but I have an Arduino Uno that 2048 bytes of variable space. If I had that array, then I would draw one rectangle for the whole screen and send each color to the memory. One thing that worked was drawing extremely slow. But, that's slow... I want to draw at ~70 fps while keeping each frame smooth.

PLL Voltage scalling STM32

I am working with the STM32F767 Nucleo board and currently, I trying to set the PLL as the system clock. While I have been able, thanks to an example generated by the CubeMX, I really don't understand why it must be done so. The setup is:
HSI = 16MHz
PLLM = 8
VCO_Input_Frequency = 16/8 = 2MHz
PLLN = 144
Frequency = 144*2 = 288MHz
PLLP = 6
PLL_Output_Frequency = 288/6 = 48MHz
PPRE1 = 2
APB1_Frequency = 24MHz
APB1_Frequency_Timer = 2*24MHz = 48MHz
The following line of code is what is buging me:
//Sets the voltage scaling mode to 3, VOS = 0x1 = b1
PWR->CR1 |= PWR_CR1_VOS_0;
a = PWR->CR1; //Small delay
When this line is commented the period is 19.7ms and when is active the period is 20ms, as expected. It is very strange, this is how the generated code from CubeMX does. It makes the voltage scaling to 1 (low performance). I don't understand how seting the voltage scaling equals to 1 makes the PLL works correctly.
Down below is the code that configure the PLL:
void sys_clock_init(void){
int a;
//Sets the wait states to 1
FLASH->ACR |= 0x01;
a = FLASH->ACR; //Small delay
//Enables the power interface (for the power controller)
RCC->APB1ENR |= RCC_APB1ENR_PWREN;
a = RCC->APB1ENR; //Small delay
//Clears the bits for the voltage scaling
PWR->CR1 &= ~(PWR_CR1_VOS);
//Sets the voltage scaling mode to 3, VOS = 0x1 = b1
PWR->CR1 |= PWR_CR1_VOS_0;
a = PWR->CR1; //Small delay
//Makes HSI the source of the PLL
RCC->PLLCFGR &= ~(0x400000);
//Clears the bits for the different factors
RCC->PLLCFGR &= ~(RCC_PLLCFGR_PLLM);
//Sets the PLLM = 0x08 = b100
RCC->PLLCFGR |= (RCC_PLLCFGR_PLLM_3);
//Clears the PLLN bits
RCC->PLLCFGR &= ~(RCC_PLLCFGR_PLLN);
//Sets PLLN = 0x90 = b10010000
RCC->PLLCFGR |=(RCC_PLLCFGR_PLLN_7 | RCC_PLLCFGR_PLLN_4);
//Clears the PLLP bits
RCC->PLLCFGR &= ~(RCC_PLLCFGR_PLLP);
//Sets the PLLP = 0x02 = b10
RCC->PLLCFGR |=(RCC_PLLCFGR_PLLP_1);.
//Clears the PPRE1 bits
RCC->CFGR &= ~(RCC_CFGR_PPRE1_2 | RCC_CFGR_PPRE1_1 | RCC_CFGR_PPRE1_0);
//Set bit PPRE1 = 0x02 = b100
RCC->CFGR |= (RCC_CFGR_PPRE1_2);// | RCC_CFGR_PPRE1_0);
//Turns the PLL ON
RCC->CR |= RCC_CR_PLLON;
//Waits for the PLL to be ready
while(!((RCC->CR & RCC_CR_PLLRDY) == RCC_CR_PLLRDY));
//Clears the switch bits
RCC->CFGR &= ~(RCC_CFGR_SW);
//Set the PLL as the System Clock
RCC->CFGR |= (RCC_CFGR_SW_1);}
I have also tested commenting the lines that sets VOS bits on the CubeMX code and the period is 19.75ms like mine.
This is my code to get that board to 16Mhz using the PLL and the external clock.
static int clock_init ( void )
{
unsigned int ra;
//switch to external clock.
ra=GET32(RCC_CR);
ra|=1<<16;
PUT32(RCC_CR,ra);
while(1) if(GET32(RCC_CR)&(1<<17)) break;
if(1)
{
ra=GET32(RCC_CFGR);
ra&=~3;
ra|=1;
PUT32(RCC_CFGR,ra);
while(1) if(((GET32(RCC_CFGR)>>2)&3)==1) break;
}
//HSE ready
//PLLM aim for 2mhz so 8/4=2
//PLLN input is 2, want >=100 and <=432 so between 50 and 216
//PLLP 16Mhz*8 = 128, 16MHz*6 = 96, not enough
//so PLLP is 8 VCO 128 so PLLN is 64
//don't really care about PLLQ but have to have something so 8
PUT32(RCC_PLLCFGR,0x20000000|(8<<24)|(1<<22)|(3<<16)|(64<<6)|(4<<0));
ra=GET32(RCC_CR);
ra|=1<<24;
PUT32(RCC_CR,ra);
while(1) if(GET32(RCC_CR)&(1<<25)) break;
ra=GET32(RCC_CFGR);
ra&=~3;
ra|=2;
PUT32(RCC_CFGR,ra);
while(1) if(((GET32(RCC_CFGR)>>2)&3)==2) break;
return(0);
}
PUT32/GET32 are abstraction functions to do str/ldr. I will try either 48 HSE or 48Mhz HSI, and post what I find.
static int clock_init ( void )
{
unsigned int ra;
//PLLM aim for 2mhx so 16/8 = 2;
//PLLN input is 2, want >=100 and <=432 so between 50 and 216
//PLLN = 144, VCO 288
//PLLP = 6, output 288/6 = 48MHz
//don't really care about PLLQ but have to have something so 6
PUT32(RCC_PLLCFGR,0x20000000|(6<<24)|(0<<22)|(2<<16)|(144<<6)|(8<<0));
ra=GET32(RCC_CR);
ra|=1<<24;
PUT32(RCC_CR,ra);
while(1) if(GET32(RCC_CR)&(1<<25)) break;
ra=GET32(RCC_CFGR);
ra&=~3;
ra|=2;
PUT32(RCC_CFGR,ra);
while(1) if(((GET32(RCC_CFGR)>>2)&3)==2) break;
return(0);
}
The uart works, but that is not saying much.
With the default of scale 1 I do see that a little fast. but if I use the 8MHz HSE then it looks better. I use systick to count 120 seconds. set systick to roll over every 1million counts then wait for 120 rollovers, compare to a stopwatch/timer.
Next using 16000000 in systick and counting 900 rollovers that should be 5 minutes and it is to within a second since comparing visually to a timer is only that accurate. Using HSE, scaling 1 (default)
Using HSI scaling 1 it is off by a few seconds to get 19.7ns though would be a lot of seconds and I don't see that many.
Now using HSI scaling 3:
static int clock_init ( void )
{
unsigned int ra;
ra=GET32(RCC_APB1ENR);
ra|=1<<28; //PWR enable
PUT32(RCC_APB1ENR,ra);
PUT32(FLASH_ACR,0x00000001);
PUT32(PWR_CR1,0x4000);
GET32(PWR_CR1);
//PLLM aim for 2mhx so 16/8 = 2;
//PLLN input is 2, want >=100 and <=432 so between 50 and 216
//PLLN = 144, VCO 288
//PLLP = 6, output 288/6 = 48MHz
//don't really care about PLLQ but have to have something so 6
PUT32(RCC_PLLCFGR,0x20000000|(6<<24)|(0<<22)|(2<<16)|(144<<6)|(8<<0));
ra=GET32(RCC_CR);
ra|=1<<24;
PUT32(RCC_CR,ra);
while(1) if(GET32(RCC_CR)&(1<<25)) break;
ra=GET32(RCC_CFGR);
ra&=~3;
ra|=2;
PUT32(RCC_CFGR,ra);
while(1) if(((GET32(RCC_CFGR)>>2)&3)==2) break;
return(0);
}
Does appear to be more accurate. 5 minutes measured as 5 minutes to the second. So it appears perhaps that the documentation isn't correct with respect to the accuracy of HSI (as there is this exception using default scaling).
48Mhz -> 20.83ns
20.62 - 21.04 with the documented error.
There is a reason for using external clocks. If you are interested in more accuracy since you have the NUCLEO board use the external clock HSE not the internal HSI.
Hmmm, actually 1% for the 16Mhz which is 3% when multiplied by 3 to get 48MHz. I think using the divisor in the PLL makes it worse, but I would have to ponder that some more.
20.21 to 21.46 is the range you should see at the calibration temperature, then vary from that based on die temp.

Arduino read 20 bit number from 3 registers

I have an ADXL355 (EVAL-ADXL355-PMDZ) that I am trying to test against a very expensive industrial grade sensor. I am using I2C and I am able to read the device properties and settings as described in the datasheet.
The issue I'm having is how to read the 3 ZDATA (or XDATA, YDATA) registers as a single value. I have tried two approaches. Here is the first:
double values[3];
Wire.beginTransmission(addr);
Wire.write(0x08); // ACCEL_XAXIS
Wire.endTransmission();
Wire.requestFrom(addr, 9, true); // Read 9, 3 for each axis
byte x1, x2, x3;
for (int i = 0; i < 3; ++i){
x3 = Wire.read();
x2 = Wire.read();
x1 = Wire.read();
unsigned long tempV = 0;
unsigned long value = 0;
value = x3;
value <<= 12;
tempV = x2;
tempV <<= 4;
value |= tempV;
tempV = x1;
tempV >>= 4;
value |= tempV;
values[i] = SCALEFACTOR * value;
}
This will produce values that approach 1g for negative gravity and 3g for positive gravity. Also the unloaded axes will sometimes show offscale high instead of -0.0g. They bounce from 0.0 to 4.0 g's. This tells me I have a sign problem which I'm sure comes from using unsigned long. So I attempted to read it as a 16 bit value and retain the sign.
double values[3];
Wire.beginTransmission(addr);
Wire.write(0x08); // ACCEL_XAXIS
Wire.endTransmission();
Wire.requestFrom(addr, 9, true); // Read 9, 3 for each axis
byte x1, x2, x3;
for (int i = 0; i < 3; ++i){
x3 = Wire.read();
x2 = Wire.read();
x1 = Wire.read();
long tempV = 0;
long value = 0;
value = x3;
value <<= 8;
tempV = x2;
value |= tempV;
values[i] = SCALEFACTOR * value;
}
This produced values are good in terms of sign but they are (as expected) much lower in magnitude than they are supposed to be. I tried to create a 20 bit number like this long value:20; but I received
expected initializer before ':' token
same error for int.
How do I properly read from 3 registers to obtain a correct 20 bit value?
First of all, you really want to use unsigned types when using the left and right shift operators (see this question).
Taking a look to the avr-gcc type layout we learn that long are represented on 4 bytes (i.e. 32 bits) so they are long enough (no pun intended) to "hold" your 20 bits numbers (XDATA, YDATA, and ZDATA). On the other hand, int are represented on 2 bytes (i.e. 16 bits) and thus should not be used in your case.
According to the datasheet you linked page 33, the numbers are formatted as two's complement. Your first example correctly set the last 20 bits of your unsigned, 32 bits long value (in particular the left justification handling — right-shifting x1 by four — already looks correct) but the "new" 12 most significants bits are always set to 0.
To perform sign extension, you need to set the "new" 12 most significant bits to 0 if the number is a positive value, 1 if the number is a negative value (adaptation of your first example):
...
value |= tempV;
if (x3 & 0x80) /* msb is 1 so the number is a negative value */
value |= 0xFFF00000;
From there, what you should observe is about the same behaviour as previously: high positive values instead of small negative ones (but even higher than previously). This is caused by the fact that while your value is correct bitwise speaking, it is still intepreted as unsigned. This can be worked around by forcing the compiler to use value as signed:
values[i] = SCALEFACTOR * (long)value;
And now it should be working.
Note that this answer use the fact that your C/C++ implementation use two's complement to represent negative integers. While very rare in practice, the standard allow other representations (see this question for examples).
Here is one way to make it work. It does use bitshifting on a signed value. Various sources have said that this is a potential bug as it is implementation defined. It worked on my platform.
typedef union {
byte bytes[3];
long value:24;
} accelData;
double values[3];
Wire.beginTransmission(addr);
Wire.write(0x08); // ACCEL_XAXIS
Wire.endTransmission();
Wire.requestFrom(addr, 9, true); // Read 9, 3 for each axis
accelData raw;
for (int i = 0; i < 3; ++i){
raw.bytes[2] = Wire.read();
raw.bytes[1] = Wire.read();
raw.bytes[0] = Wire.read();
long temp = raw.value >> 4;
values[i] = SCALEFACTOR * (double)temp;
}
I prefer the solution presented by Alexandre Perrin.
union
{
struct
{
unsigned : 4;
unsigned long uvalue : 20;
};
struct
{
unsigned : 4;
signed long ivalue : 20;
};
unsigned char rawdata[3];
}raw;
for (int i = 0; i < 3; ++i){
raw.bytes[2] = Wire.read(); //if most significant part is transfered first
raw.bytes[1] = Wire.read();
raw.bytes[0] = Wire.read();
values[i] = SCALEFACTOR * (double)raw.ivalue;
}

why fundamental Frequency and magnitude are not null when microphone is off?

I would like to make real time audio processing with Qt and display the spectrum using FFTW3.
What I've done in steps:
I capture any sound from computer device and fill it into the buffer.
I assign sound samples to double array
I compute the fundamental frequency.
when I'm display the fundamental frequency and Magnetitude when the microphone is on but no signal(silence) , the fundamental frequency is not what I expected , the code don't always return zero , sometimes the code returns 1500Hz,2000hz as frequency
and when the microphone is off (mute) the code don't return zero as fundamamental frequency but returns a number between 0 and 9000Hz. Any help woulbd be appreciated
here is my code
QByteArray *buffer;
QAudioInput *audioInput;
audioInput = new QAudioInput(format, this);
//Check the number of samples in input buffer
qint64 len = audioInput->bytesReady();
//Limit sample size
if(len > 4096)
len = 4096;
//Read sound samples from input device to buffer
qint64 l = input->read(buffer.data(), len);
int input_size= BufferSize;
int output_size = input_size; //input_size/2+1;
fftw_plan p3;
double in[output_size];
fftw_complex out[output_size];
short *outdata = (short*)m_buffer.data();// assign sample into short array
int data_size = size_t(outdata);
int data_size1 = sizeof(outdata);
int count = 0;
double w = 0;
for(int i(chanelNumber); i < output_size/2; i= i + 2) //fill array in
{
w= 0.5 * (1 - cos(2*M_PI*i/output_size)); // Hann Windows
double x = 0;
if(i < data_size){
x = outdata[i];
}
if(count < output_size){
in[count] = x;// fill Array In with sample from buffer
count++;
}
}
for(int i=count; i<output_size; i++){
in[i] = 0;
}
p3 = fftw_plan_dft_r2c_1d(output_size, in, out, FFTW_ESTIMATE);// create Plan
fftw_execute(p3);// FFT
for (int i = 0; i < (output_size/2); i++) {
long peak=0;
double Amplitudemax=0;
double r1 = out[i][0] * out[i][0];
double im1 = out[i][3] * out[i][4];
double t1 = r1 + im1;
//double t = 20*log(sqrt(t1));
double t = sqrt(t1)/(double)(output_size/2);
double f = (double)i*8000 / ((double)output_size/2);
if(Magnitude > AmplitudeMax)
{
AmplitudeMax = Magnitude;
Peak =2* i;
}
}
fftw_destroy_plan(p3);
return Peak*(static_cast<double>(8000)/output_Size);
What you think is silence might contain some small amount of noise. The FFT of random noise will also appear random, and thus have a random magnitude peak. But it is possible that noise might come from equipment or electronics in the environment (fans, flyback transformers, etc.), or the power supply to your ADC or mic, thus showing some frequency biases.
If the noise level is low enough, normally one checks the level of the magnitude peak, compares it against a threshold, and cuts off frequency estimation reporting below this threshold.

Resources