Tag Archives: barometer

I2C and MS5611 precision Barometer using STM32

I2C or ISP protocol, what chice?

Both options are okay, ISP protocol is faster, can run over 40 MHz, but sometimes we have more than one device, so it is better sometimes to use I2C protocol.  After all, ADC conversion on this device may take up to 10 mS, so faster protocol will not yield faster reading.

How to connect MS5611 to STM32

First we need to look at schematic diagram to know how to connect MS5611 to STM32 board(s). In our case of I2C interfacing, we should look at second part of the picture below. (I included whole datasheet even more below.  Note that in the case of I2C, pin CSB play different role than in ISP mode. Leave it unconnected for address 0x77, or connect to Vdd in the case you already have the same device, this time address will be 0x76. For STM32f10x family, SDA pin is connected to GPIO B7 and SCL to GPIO B6. As this page is intended for learning I2C protocol on STM32 micro-controllers, I will talk here only about this protocol. SPI will come later, and if you are subscribed to the website, you will get e-mail notification about updates. Back to connections – pin PS on MS5611 must be on Vdd, since it is Protocol Select. In the document it says that it is low voltage device, and 3V is mentioned, but absolute maximum rating is 4V, so if STM32 works on 3.3V, that is exactly what we want.

MS5611

On the board GY-63, it is confusing where is SDI for SPI protocol. It is the same pin as SDA, but someone forget to add that pin name “SDA/SDI”. In both cases SDI/SDA gives us data, while SCL or SCLK as is on datasheet has clock. We here need for I2C  SDA and SCL only for communication. Vcc (or Vdd in the document) goes to +3.3V, GND to GND of STM32, and PS to Vdd bypass or separate wire to available +3.3V on the STM32 board.

MS5611

Here is documentation abut MS5611 precision barometer:

Download (PDF, 579KB)

For complete work in Keil, we need 7 files, all seven should be in the same directory of the project with name, for example MS5611 barometer. It has thermometer too with two digits decimal precision. Your choice of project name, but name of individual component must be as is the name of .c and .h files, since it depends of ‘#include’ statements.  Here I will give you all 7 files, of which first is barometer.c (containing main() function), wire.c, wire.h, delayUs.c, delayUs.h, printMsg.c, printMsg.h . All MCU programming is done using ST-link dongle. For data transfer to the computer, you need FTDI usb dongle.

barometer.c file:

/* More on my website: http://wp.me/p7jxwp-nD */

#include "stm32f10x.h"
#include "printMsg.h"
#include "delayUs.h"
#include "wire.h"
#include "math.h"

uint8_t  buffer[5];  //twi buffer, for now 32 elements, for other purpose (e.g. for OLED it is 1024) change as you wish
uint8_t  address=0x77, length=0;
uint16_t calibrationData[7];
int64_t OFF, OFF2, SENS, SENS2;
int pressure, D1, D2, dT, P, Pa, TEMP, T2;

void setupSensor(void)
{
	twiSend(address, 0x1E,1); //just send 1 byte that tells MS5611 to reset
	delay(20000); //delay 10 mS needed for device to execute reset
	for (int i=1;i<=6;i++)
	{
	twiReceive(address, 0xA0+i*2, 2); //read all 14 bytes for callibration data from PROM
	//printMsg("b0= 0x%x, b1= 0x%x, b2= 0x%x \n",buffer[0], buffer[1], buffer[2]); //for debug purposes
  delay(50); //at least 40 uS
	calibrationData[i] = buffer[0]<<8|buffer[1]; //pair of bytes goes into each element of callibrationData[i], global variables, 14 uint8_t into 7 uint16_t
  }
	//delay(50);
	//printMsg("b0= %d, b1= %d, b2= %d \n",buffer[0], buffer[1], buffer[2]); //for debug purposes
}

int getPressure(void)
{
	D1=0;D2=0;
	twiSend(address, 0x48,1); //set D1 OSR=4096 (overscan, maximum) 0x48
	delay(25000);//must be 15 mS or more
	twiReceive(address, 0x00, 3); //initiate and read ADC data, 3 bytes
	//printMsg("b0= 0x%x, b1= 0x%x, b2= 0x%x ===========\n",buffer[0], buffer[1], buffer[2]); //for debug purposes
	D1 = D1<<8 | buffer[0]; //shifting first MSB byte left
	D1 = D1<<8 | buffer[1]; //another byte
	D1 = D1<<8 | buffer[2]; //LSB byte last
	twiSend(address, 0x58,1); //set D2 OSR=4096 (overscan, maximum) 0x58
	delay(25000); //must be 15 mS or more
	twiReceive(address, 0x00, 3); //initiate and read ADC data, 3 bytes
	D2 = D2<<8 | buffer[0]; //shifting first MSB byte left
	D2 = D2<<8 | buffer[1]; //another byte
	D2 = D2<<8 | buffer[2]; //LSB byte last
		
	dT = D2 - ((int)calibrationData[5] << 8);
  TEMP = (2000 + (((int64_t)dT * (int64_t)calibrationData[6]) >> 23)); //temperature before second order compensation
  if (TEMP<2000)  //if temperature of the sensor goes below 20°C, it activates "second order temperature compensation"
    {
      T2=pow(dT,2)/2147483648;
      OFF2=5*pow((TEMP-2000),2)/2;
      SENS2=5*pow((TEMP-2000),2)/4;
      if (TEMP<-1500) //if temperature of the sensor goes even lower, below -15°C, then additional math is utilized
        {
          OFF2=OFF2+7*pow((TEMP+1500),2);
          SENS2=SENS2+11*pow((TEMP+1500),2)/2;
        }
    }
    else 
      {
          T2=0;
          OFF2=0;
          SENS2=0;
      }
  TEMP = ((2000 + (((int64_t)dT * (int64_t)calibrationData[6]) >> 23))-T2); //second order compensation included
  OFF = (((unsigned int)calibrationData[2] << 16) + (((int64_t)calibrationData[4] * dT) >> 7)-OFF2); //second order compensation included
  SENS = (((unsigned int)calibrationData[1] << 15) + (((int64_t)calibrationData[3] * dT) >> 8)-SENS2); //second order compensation included
  P = (((D1 * SENS) >> 21) - OFF) >> 15; 
  return P; //returns back pressure P
}

int main(void)
{
//In new (2019/03/01) added twiScan(); function. You may use it to find your I2C device. Just remove // coment characters.
          //twiScan();
	  RCC->APB2ENR |= RCC_APB2ENR_IOPCEN;
	  GPIOC->CRH = 0x00300000;
    GPIOC->BSRR = GPIO_BSRR_BR13; //let's lit blue LED 
	  RCC->APB2ENR |= RCC_APB2ENR_IOPBEN | RCC_APB2ENR_AFIOEN;
	  usart_1_enable();
	  twiEnable();
	  setupSensor(); //set essential things
	  //printMsg("works? or stuck somewhere?"); //debug mode :)
	while(1)
	{	
    pressure = getPressure(); //get pressure and temperature, calculate pressure offset with callibration data and temperature
	  printMsg("Temperature of the sensor= %.2f, barometric pressure= %.2f\n", (float)TEMP/100, (float)pressure/100);	
		delay(2000000); //every 2 seconds it send data over the USART1 (equivalent of Serial.print on Arduino)
		//for(;;); //execute once, just for debug purpose
	}
}

wire.c file:

/* Two Wire Interface, I2C (or IIC), here will be called 'twi', and we have
   only twiEnable(), twiSend() and twiReceive(). The twiSend() function is 
	 fairly simple, we just send address of the device shifted to the left by
	 1 bit, or-red | zero (0) at free space that tell I2C bus it is for write operation.
	 The receive twiReceive() function works by sending address also shifted left
	 one bit with logic or | zero (0) at empty bit (LSB), but then we must send command 
	 to the device depending what device has. After command, we stop (although
	 we can remove STOP condition and continue to "repeated start", then we
	 must change bit after address of the device, now it is one (1) that tells
	 I2C bus we want to read. If we try only read from some address, device
	 don't know what to send. So we must first issue command, then read. For
	 specific command set read datasheet of particular device - it is different
	 for all different devices. More on my website: http://wp.me/p7jxwp-nD */

#include "stm32f10x.h"
#include "delayUs.h"
#include "wire.h"
#include "printMsg.h"

void twiEnable(void) 
{
  //just set all registries, but NOT START condition - execute once in main.c
	RCC->APB2ENR |= RCC_APB2ENR_IOPBEN | RCC_APB2ENR_AFIOEN; //B port enabled, alternate function 
	RCC->APB1ENR |= RCC_APB1ENR_I2C1EN; //I2C 1 enabled 
	GPIOB->CRL = 0xFF000000;// setting just pins B7 (SDA) and B6 (SCL), while leaving the rest intact 50 MHz!
	I2C1->CR2 |= 50; // GPIO clock freq=50 MHz MUST !!! be equal APB frequency (GPIO, 2, 10 or 50 MHz)
	I2C1->CCR |= I2C_CCR_FS; //fast mode
	I2C1->CCR |= 30; //not sure for 400 000 - (10= 1.2 MHz, 15=800 kHz, 30=400 kHz)
	I2C1->TRISE |= 51; // maximum rise time is 1000 nS
	I2C1->CR1 |= I2C_CR1_PE; 
}

void twiScan(void)
{		int a=0; 
	 	for (uint8_t i=0;i<128;i++)
   {
			I2C1->CR1 |= I2C_CR1_START;
			while(!(I2C1->SR1 & I2C_SR1_SB));
			I2C1->DR=(i<<1|0); 
			while(!(I2C1->SR1)|!(I2C1->SR2)){}; 
			I2C1->CR1 |= I2C_CR1_STOP; 
			delay(100);//minium wait time is 40 uS, but for sure, leave it 100 uS
			a=(I2C1->SR1&I2C_SR1_ADDR);
			if (a==2)
		 {
				printMsg("Found I2C device at adress 0x%X (hexadecimal), or %d (decimal)\n",i,i);
		 }
	 }
}
	
/* Command or commands, or sending bytes, just the same name of the variable 'command' */
void twiSend(uint8_t address, uint8_t command, uint8_t length)
{
	I2C1->CR1 |= I2C_CR1_START; //START condition 
	while(!(I2C1->SR1 & I2C_SR1_SB));
	I2C1->DR=(address<<1|0); //sending address of the device, 0 = sending
  while(!(I2C1->SR1 & I2C_SR1_ADDR)|!(I2C1->SR2));		
	I2C1->DR=command; //filling data register with byte, if single - command, multiple - command(s) and data
	for (uint8_t i=0;i<length;i++)
	{ 
		I2C1->DR=buffer[i]; //filling buffer with command or data
		delay(60);
	}
	I2C1->CR1 |= I2C_CR1_STOP;
}

void twiReceive(uint8_t address, uint8_t command, uint8_t length) 
{
	I2C1->CR1 |= I2C_CR1_ACK;
  I2C1->CR1 |= I2C_CR1_START; //start pulse 
	while(!(I2C1->SR1 & I2C_SR1_SB));
	I2C1->DR=(address<<1|0); //sending address of the device, 0 = sending
	while(!(I2C1->SR1 & I2C_SR1_ADDR)|!(I2C1->SR2 & I2C_SR2_BUSY));
	I2C1->DR=command; //sending command to the device in order to request data
	I2C1->CR1 |= I2C_CR1_START; //REPEATED START condition to change from sending address + command to receive data
	while(!(I2C1->SR1 & I2C_SR1_SB));
	I2C1->DR=(address<<1|1); //sending address of the device, 1 = reading 
	while(!(I2C1->SR1 & I2C_SR1_ADDR)|!(I2C1->SR2));
	
if (length==1)  //receiving single byte, N=1
	{
		while(!(I2C1->SR1)|!(I2C1->SR2));
		I2C1->CR1 &= ~I2C_CR1_ACK; //this will send later NAK (not acknowledged) to signal it is last byte
		I2C1->CR1 |= I2C_CR1_STOP; //issuing STOP condition before (!) reading byte
		buffer[0]=I2C1->DR; //single byte is read AFTER NAK (!) and STOP condition
	} 
	if (length==2) //receiving two bytes, N=2
	{
		while(!(I2C1->SR1)|!(I2C1->SR2));
		I2C1->CR1 &= ~I2C_CR1_ACK; //this will send later NAK (not acknowledged) before last byte
    I2C1->CR1 |= I2C_CR1_STOP;
		buffer[0]=I2C1->DR; //reading N-1 byte, next to last byte is in DR, last one still in shift register
		while(!(I2C1->SR1 & I2C_SR1_RXNE)|!(I2C1->SR2));
		buffer[1]=I2C1->DR; //read last N byte now available 
	} 
  if (length>2) //receiving more than two bytes, N>2
	{
		
	  for (uint8_t i=0;i<length;i++)
	  { 
			                     
		  if (i<(length-3))      // if it is not N-2, then read all bytes
			{
				while(!(I2C1->SR1 & I2C_SR1_RXNE)|!(I2C1->SR2));
				buffer[i]=I2C1->DR;  
			}
		  else if (i==length-3) // if it is N-2 then read 
			{
				while(!(I2C1->SR1)|!(I2C1->SR2));
				buffer[i]=I2C1->DR; 
				while(!(I2C1->SR1 & I2C_SR1_RXNE)|!(I2C1->SR2));
				I2C1->CR1 &= ~I2C_CR1_ACK; //this will send later NAK (not acknowledged) before last byte
				I2C1->CR1 |= I2C_CR1_STOP;
			}
	    else if (i==length-2) // if it is N-1 then read
			{
				while(!(I2C1->SR1 & I2C_SR1_RXNE)|!(I2C1->SR2));
				buffer[i]=I2C1->DR; 
			}
			else if (i==length-1) // else it is N byte 
			{
				while(!(I2C1->SR1 & I2C_SR1_RXNE)|!(I2C1->SR2)){};
		    buffer[i]=I2C1->DR;  
			}
    } 
 }
}

wire.h file:

#ifndef wire_h
#define wire_h

#include <stdint.h>

extern uint8_t  address, command, length; 
extern uint16_t calibrationData[];
extern uint8_t  buffer[];
extern uint8_t  status1;
extern uint8_t  status2;

void twiEnable(void);
void twiSend(uint8_t address, uint8_t command, uint8_t length);
void twiReceive(uint8_t address, uint8_t command, uint8_t length);


#endif

delayUs.c file:

void delay(unsigned long cycles)
{
  while(cycles >0)
	{
		asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");
		asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");
		asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");
		asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");
		asm("nop");asm("nop");asm("nop");  //to get 1 uS if delay(1)
  cycles--; 
	}
}

delayUs.h file:

#ifndef delayUs_h
#define delayUs_h

extern void delay(unsigned long cycles);

#endif

printMsg.c file:

#include "stm32f10x.h"
#include "stdint.h"
#include <stdio.h>
#include <stdlib.h>
#include "stdarg.h"
#include "string.h"
#include "printMsg.h"

char buff[256];

void usart_1_enable(void)
{
  //enabling pin A9 for alternating funct. for uart/usart
 RCC->APB2ENR |= RCC_APB2ENR_IOPAEN | RCC_APB2ENR_AFIOEN | RCC_APB2ENR_USART1EN; //clock to GPIO A enabled, port A(2), alt.funct.en(0), usart1 clock enabled(14)
	GPIOA->CRH |= GPIO_CRH_CNF9_1 | GPIO_CRH_MODE9_0 | GPIO_CRH_MODE9_1; //port A9
	GPIOA->CRH &= ~GPIO_CRH_CNF9_0; //port A9
	GPIOA->CRH &= ~(GPIO_CRH_MODE10_0|GPIO_CRH_MODE10_1); //port A10 is RX
	GPIOA->CRH |= GPIO_CRH_CNF10_0; //port A10 is RX
	
	//GPIOA->CRH = 0x444444B4; // A9 is alternate output, 50 MHz, push-pull - not this time short version (!)
	//clkPer/(baudRx_16bit)=72MHZ/9600 = 7500 = 0x1D4C
	/* Remove comment line for speed that you want to use*/
	//USART1->BRR = (0xEA60); //   1200 Baud
	//USART1->BRR = (0x7530); //   2400 Baud
	//USART1->BRR = (0x3A98); //   4800 Baud
	//USART1->BRR = (0x1D4C); //   9600 Baud
	//USART1->BRR = (0x1388); //  14400 Baud
	//USART1->BRR = (0xEA6) ; //  19200 Baud
	//USART1->BRR = (0x9c4) ; //  28800 Baud
	//USART1->BRR = (0x753) ; //  38400 Baud
	//USART1->BRR = (0x505) ; //  56000 Baud
	//USART1->BRR = (0x4E2) ; //  57600 Baud
	USART1->BRR = (0x271) ; // 115200 Baud
	//USART1->BRR = (0x232) ; // 128000 Baud
	//USART1->BRR = (0x119) ; // 256000 Baud
	//USART1->BRR = (0x8C)  ; // 512000 Baud
	//USART1->BRR = (0x46)  ; // 1024000 Baud
	//USART1->BRR = (0x23)  ; // 2048000 Baud
  //USART1->BRR = (0x18)  ; // 3000000 Baud (3 MHz, max speed that HTerm can get, non-standard speed)
	
	
	USART1->CR1 |= USART_CR1_TE; //transmitter enable
	USART1->CR1 |= USART_CR1_RE; //receiver enable
	USART1->CR1 |= USART_CR1_UE; //usart enable
}

void printMsg(char *msg, ...)
{
	//char buff[120]; //was 80
	va_list args;
	va_start(args,msg); 
	vsprintf(buff,msg,args);

	for(int i=0;i<strlen(buff);i++)
	{
	  USART1->DR = buff[i];
	  while(!(USART1->SR & USART_SR_TXE)); //wait for TXE, 1 = data transferred
  }
}

printMsg.h file:

#ifndef printMsg_h
#define printMsg_h

extern int len;
extern char buff[];

void usart_1_enable(void);
void printMsg(char *msg, ...);

#endif

Here is the video:

Some related documents

Download (PDF, 125KB)

External link for STM32f10x series.

Arduino altimeter

Altimeter: Another problem with losing data during lost signal:

Altimeter sketch, Tried to change library from VirtualWire.h to RadioHead.h, but memory on Arduino nano is already too much populated, working memory for variables. So… Don’t know what to do… OLED display take 1024 bytes (1 kB), maybe I should to consider different display?

Altimeter programming problemsaltimeter

Altimeter: some outdoor test is done with one big mistake…

This video is made before utilizing “second order temperature compensation” in sketch of the transmitter. It looks complicated, but it turns out that is actually simple to apply (or I become somewhat good in programming). 😀 What happens is that I set zero indoor where temperature is close to 20°C, but outdoor in early morning was just 7°C, which gives me error of about -6 meters. Now things are better, error is still present but no more than say – 3 meters. BUT (!), now this error is within ballpark of maximum -3 meters error if temperature changes dramatically, which in most situations is not the case. Problem may occur during winter time, when someone want to fly briefly, and bring outside “warm” quad with sensor at say 20°C, then begins to fly – when set zero on ground, fly, and back, on ground should be again zero. For this reason, give quad and sensor some time to accommodate to whatsoever temperature outdoor is. Sensor is temperature dependent, and between 20°C and maximum operating temperature of 85°C it has pretty linear reading. The problem begins below 20°C, and exponentially rise with lowering temperature. But, since we are not interested in correct atmospheric pressure, but rather correct measured altitude, such error of -3 meters (which is about 0.5 mb, or 0.5 hPa), is next to nothing if quad fly very high AND (!) in meanwhile temperatures drops drastically. Down below is sketch for Attiny85, which is now corrected with added additional math. In the worst case of temperature drop, error is no more than -16 meters at temperatures between -15°C and -40°C DIFFERENCE (!) between starting temperature and temperatured during flight. And, I doubt that anyone has actual will to fly on such cold and freezing butt temperature. 😀

Altimeter: transmitter under 3 grams! (2.75 g with antenna)

altimeter

On this ‘remote’ altimeter, still need to add ISP connector with 6 wires for re-programming firmware. Just in case that something need to be changed. Else, it is under 3 grams, but as ‘features’ growing, it will be slightly above 3 grams. For example, this 1/4 lambda antenna is okay, but full dipole (1/2 lambda) is better for more range. Just need to finish receiver end, post it on this page, and test maximum possible range. I am hoping for 1 km or more, but will see. Also, not sure how it will looks like on the quadcopter. For rockets, there will be different version – one without transmitter, just recording max altitude in EEPROM, then red with some base station, but that is future plan. Or, maybe I will find some good and lightweight LCD display… don’t know… For quadcopters, it is good to monitor altitude dynamicaly – in flight, but for rockets… eh, it goes hight as it goes – no way to monitor it in real time.

Arduino Altimeter – first steps are done

Altimeter – Beta version works on 433 MHz, base station should be pressed “zero” to set zero of the vehicle (airplane, quadcopter…), and then it will calculate altitude by receiving data from vehicle to the base station. So far, I have some problem with OLED display and it’s library: it uses too much memory and some instability occurs when it is out of range and not used for longer period. Just need to see what may be done…

Altimeter

Download (PDF, 98KB)

Working day and night… not only this project, but other things as well. Just some short video:

On this video, pressure sensor MS5611-01BA01 is used. By clicking on this link, you will get datasheet if you are interested.

Another PDF file MS5611-01BA03, much detailed, where at pages 17, 18 and 19 – diagrams showing error in pressure and temperature measurements without applying “Second order temperature compensation”, where sensor below 20°C increasing interpreting pressure reading exponentially as temperature goes really low. This math correcting it as much as possible. Still some error remains, but with peak of about 1.8 mBar at temperatures between -15°C and -40°C. Without this, additional math, error at such temperatures are up to 28 mBar at -40°C. At some “normal” local atmospheric pressures of say 1000 mBar, error of 1 mBar equals 8.426 meters. As pressure goes down, this error goes down. So at very high altitude, where temperatures are low – error becomes less important. I will be more worried about LiPo battery, than about sensor.

Another changes

Changed way of displaying Actual altitude, and Maximum altitude reached, shown as A: and M: respectively. Both in feet and meters.

altimeter

And got idea about barometric pressure (for altitude calculation), humidity and temperature of the base station. Later will be added another barometric sensor in the case of rapid change in weather, as is case past few days prior to rain. This second sensor together with humidity and temperature will make it almost complete meteorologic station, mobile one. Just missing wind speed, and few other parameters, but so far – it looks much better and more “rich” than before. 😀 To read meteo-data, just switch into second position, while altimeter continues to measure altitude and “remember” maximum reached. MaxAlt is done by simple code:

if (maxAltitude<=altitude) {maxAltitude=altitude;}

altimeter

I wish to have better and bigger display in order to show all data at once instead changing “pages” on OLED, but here it is, what it is… New Nextion display ordered, just waiting to arrive.

First codes for TX, test phase

So far it works as a “Packet Radio” on 433 MHz, the ISM frequency. Transmitter and sensor + Attiny85 is chosen so that whole sending device will be under 3 grams – good for any vehicles. Unfortunately, it can’t work as altimeter not as vario + altimeter as planed. The main problem is in receiver side; if I combine relative slow transmission, some 24 packets maximum, tone sounds crazy and has delay. So, later version of the Arduino altimeter will has switch to chose altimeter or vario with the same circuit. For now, I did only altimeter, everything else will be added later (for those who found this page before end of test or beta phase of developing).

Some errors corrected

In the example below, initial idea is used from Arduino Vario by Rolf R. Bakke. He made initial code in such way that pressure is with OSR (OverSampling Ratio) for pressure, which is 4096 , so that resulting RMS (Root Mean Square) is 0.012 mBar (the lower number – the more precision). But (!), because higher OSR requires longer conversion inside MS5611 sensor chip, this value requires delay of 9.04 ms (minimum 7.40 ms, typical value is 8.22 ms and maximum value is 9.04 ms), So, for sake of ‘safety margins’, he uses 10 ms delay for pressure reading, since ADC need this time in OSR mode 4096. But, he uses OSR for temperature of just 256, which is equivalent of RMS of 0.012 °C, which is okay for vario, but not for altimeter – numbers jumping up and down too much. He probably made this decision because vario should be very fast, and already present delay of 10 ms + 1 ms at another command, adding more delay may result in too slow vario to be useful for sailplanes or gliders. The more samples per second – the faster the response.

Vario is one thing, altimeter another

For this reason, I changed call function from “D2 = getData(0x50, 1);” to “D2 = getData(0x58, 10);”, which has RMS of 0.002 °C – much better temperature correction data (six time better temperature correction than in the case of OSR 256, which gives RMS of 0.012 °C), but ten times longer to read (instead 1 ms, it needs over 9 ms to complete oversampling). The same as above for pressure, I am using 10 ms for good measure to prevent error(s). Anyone who want to experiment, try change from 10 ms to 1 – terrible error occur, instead some ‘reasonable’ pressure of say 1010 mbar, there is nonsense, something like -2.5 mbar. ADC converter just can’t cope with that speed, and reading sensor ends in big, really big error.

Why then in Attiny85 code is 1000 instead 10 ms?!

This is mystery to me. I had no time to investigate, but I suspect that library ‘VirtualWire’ resulting in such strange thing. It is actually good to have tens of microseconds (!) instead of thousands, or 1 ms (1000 µs = 1 ms). Since this strange thing is there, anyone who want to change some delay should multiply wanted value by factor of 100 to get proper delay in milliseconds. For example ‘delay(1000)’ usually means 1 second, but in example below, it will be delayed just 10 ms. So, for whole second, it should be ‘delay(100000)’. There is some limit, where delay can’t be set to high value, but instead should be used ‘for/to/next’ loop for more delay, if needed. For example 12 seconds: “for (i=1;i<=12;i++) {delay(100000)}”. This will be delay of 1 second repeated 12 times – 12 seconds. But, this is out of scope for this altimeter.

// Altimeter code by Milan Karakaš 2016
// Revision 2 - some precission errors corrected
// Revision 3 - added "second order temperature compensation",
// which corrects pressure error when temperature goes below 20°C.
// 315 MHZ or 433 MHz ASK (OOK) transmitter
// MS5611 sensor - just altimeter for now 
// Vehicle ID or just ID - set as you wish
// WARNING!!! delay 1000 ms -> delay 100000, 
// or two zerros to add, or multiply by 100
// don't know why it happens, so 1 ms should be
// writen as 100, not 1 as usually...
// Note that this is still beta version, need testing!

#include <TinyWireM.h>
#include <VirtualWire.h>
#include <Average.h>

#define n 4 //define number of average 
// the biger number n, the longer pause between two transmissions
Average<long>ave(n);
unsigned int calibrationData[7];
unsigned long time = 0;
long pressure, D1, D2, dT, P, Pa, TEMP, T2;
int ddsAcc, volt;
int64_t OFF, OFF2, SENS, SENS2;
byte data[7]; //number of data in array - 1 byte fir ID and 4 bytes for pressure
byte ID=0x2A; //"Vehicle ID" - set different value for each vehicle, also on RX side "myTX=0x2A"
float vref=5.07;

void setup()
{
 TinyWireM.begin(); //begins to comunicate with pressure sensor
 vw_set_tx_pin(1);  //set pin 4 (physical pin 3) on Attiny 85
 vw_setup(4800);    // Bits per sec
 setupSensor();     //call function for setup of the MS5611 sensor
// pinMode(4,INPUT);  //voltage measurement input
//vw_set_ptt_pin(3);  //depends of type of the transmitter 
                    //some no needs this, so use LED instead :)
                    //is is better to disable this pin, because
                    //some boards as is USB version of Attiny85
                    //uses this pins for something else
}

void loop()
{

for (int i=0;i<n;i++) //
  {
 getPressure();
 ave.push(P);
  }
Pa=(ave.mean()); //mean value of ave number (for example ave(10) is ten samples averaged)
volt=((analogRead(2)*vref)/1024)*100;

data[0]=ID;     //sending ID to array
data[1] = Pa;          //split long into first byte
data[2] = (Pa >> 8);   //split long into second byte
data[3] = (Pa >> 16);  //split long into third byte
data[4] = (Pa >> 24);  //split long into fourth and last byte
data[5] = volt;
data[6] = (volt >>8);

vw_send(data,7);       //now sending ID and four bytes to the receiver
vw_wait_tx();          //waiting until transmitter ends whole packet
delay(1000);// WARNING!!! delay 1000 ms -> delay 100000, two zerros add, or multiply by 100
/*This delay should be changed together with "Vehicle ID" in the case that multiple users 
 * fly at the same time to avoid overlaping and too much interferences. Receiver side has
 * CRC error checking, and in the case of interferences, it will just drop wrong packet and
 * continue listening until valid packet(s) is/are received. ID is single byte, and can be 
 * anything from 0 to 255 or in hexadecimal from 0x00 to 0xFF. In this case above of 1 mS 
 * delay, together with averaging of 4 samples from the MS5611 sensor, it gives 5 packets 
 * per second at 4800 bits per second. Sufficiently good for "normal" flight of quadcopter.
 */
}

//subfunction to get pressure
long getPressure()
{
//long D1, D2, dT, P, T2;
//long TEMP;
//int64_t OFF, SENS;

D1 = getData(0x48, 1000);
D2 = getData(0x58, 1000);

dT = D2 - ((long)calibrationData[5] << 8);
TEMP = (2000 + (((int64_t)dT * (int64_t)calibrationData[6]) >> 23)); //temperature before second order compensation
if (TEMP<2000)  //if temperature of the sensor goes below 20°C, it activates "second order temperature compensation"
{
  T2=pow(dT,2)/2147483648;
  OFF2=5*pow((TEMP-2000),2)/2;
  SENS2=5*pow((TEMP-2000),2)/4;
  if (TEMP<-1500) //if temperature of the sensor goes even lower, below -15°C, then additional math is utilized
  {
    OFF2=OFF2+7*pow((TEMP+1500),2);
    SENS2=SENS2+11*pow((TEMP+1500),2)/2;
  }
}
else 
{
T2=0;
OFF2=0;
SENS2=0;
}
TEMP = ((2000 + (((int64_t)dT * (int64_t)calibrationData[6]) >> 23))-T2); //second order compensation included
OFF = (((unsigned long)calibrationData[2] << 16) + (((int64_t)calibrationData[4] * dT) >> 7)-OFF2); //second order compensation included
SENS = (((unsigned long)calibrationData[1] << 15) + (((int64_t)calibrationData[3] * dT) >> 8)-SENS2); //second order compensation included
P = (((D1 * SENS) >> 21) - OFF) >> 15; 
return P; //returns back into main loop data about pressure P
}

long getData(byte command, int del) //function to getting data from the sensor
{
long result = 0;
twiSendCommand(0x77, command);
delay(del);
twiSendCommand(0x77, 0x00);
TinyWireM.requestFrom(0x77, 3);
if(TinyWireM.available()!=3);  // serial print je bio ovdje

for (int i = 0; i <= 2; i++)
{
result = (result<<8) | TinyWireM.read(); //read
}
return result;
}

//lets setup darn sensor :)
void setupSensor()
{

twiSendCommand(0x77, 0x1e);
delay(1000); //timing important - 1 ms = 1 ms * 100

for (byte i = 1; i <=6; i++)
{
unsigned int low, high;

twiSendCommand(0x77, 0xa0 + i * 2);
TinyWireM.requestFrom(0x77, 2);
if(TinyWireM.available()!=2);// Serial.println("Error: calibration data not available"); */
high = TinyWireM.read();
low = TinyWireM.read(); //read
calibrationData[i] = high<<8 | low;
}
}

//twi, whatsoever this means
void twiSendCommand(byte address, byte command)
{
  TinyWireM.beginTransmission(address);
  TinyWireM.write(command); //write
  TinyWireM.endTransmission();
}

 

Base station

Base station has OLED display for now, because found only this one. Later will consider make various options; OLED, TFT, numeric LCD, graphic LCD…

Just need time… First code for base station is here (sorry, no diagram yet, working whole night – for those who found this page in the meanwhile)

EDITED, forgot to assign Arduino Pin 9 and Pin 10 to the button and switch.

It is used later in program, but somehow forgot to set it in setup() function. It is ‘pinMode(9,INPUT);’ , as well as ‘pinMode(10,INPUT);’, also internal pull-up resistor ‘digitalWrite(9,HIGH);’ and ‘digitalWrite(10,HIGH);’  :

// Altimeter code by Milan Karakaš 2016
// Revision 2 - some precission errors corrected
// Revision 3 - sorted OLED screen; first screen showing Actual Altitude
// and Maximum reached Altitude, second screen showing barometric pressure
// Humidity and temperature sensors showing humidity and temperature on 
// the base station - very important is to know humidity, because every 
// flying object 'float' better when humidity is low (!)

#include <VirtualWire.h>
#include <SPI.h>
#include <Wire.h>
#include <Adafruit_SSD1306.h>
#include <DHT.h>

#define DHTPIN 2
#define DHTTYPE DHT22
#define OLED_RESET 4
Adafruit_SSD1306 display(OLED_RESET);
#define degree_GLCD_HEIGHT 8
#define degree_GLCD_WIDTH 8
DHT dht(DHTPIN, DHTTYPE);
static const unsigned char PROGMEM degree_glcd_bmp[]=
{0x60, 0x90, 0x90, 0x60, 0x0,};
float altitude, maxAltitude,setpress,t,h;

uint8_t buf[7];
uint8_t buflen = 7;
long P;
byte ID, myTX=0x2A; //be sure that on your TX, the same ID "myTX is the same", here 0x2A hexacecmal
float volt;
float battOK=3.7; //minimum desired voltage for safe flight

void setup()
{
pinMode(9,INPUT); //this pin I forgot to include! Sorry people. 
digitalWrite(9,HIGH); //this one serves instead external 'pull-up' resistor - every time you have INPUT and provide HIGH to the output, it internally enable that resistor.
pinMode(10,INPUT); //also this one for switching display for various data sets.
digitalWrite(10,HIGH);//I think that one needs to, but actually this is switch, pin 10 goes to GND for one display option, or to +5V (or 3.3V) for another display option. Will turn pull-up resistor just in case.
display.begin(SSD1306_SWITCHCAPVCC, 0x3c);  // initialize with the I2C addr 0x3C (for the 128x32)
display.clearDisplay();
vw_set_rx_pin(7);
vw_setup(4800);
vw_rx_start();
dht.begin();
}

void loop()
{
  if (vw_get_message(buf,&buflen)) 
   {
     byte ID=buf[0];
   if (ID==myTX) 
   // if vehicle ID is wrong, it will just freeze last result, and do nothing
   // until "proper" connection is established, or proper ID is read - in the
   // case of more than one vehicle, each other will NOT listen (no 'crosstalk', 
   // so it will not showing wrong data - just vehicle which has proper ID
   {
     long P=((long)buf[1])+((long)buf[2]<<8)+((long)buf[3]<<16)+((long)buf[4]<<24);
     float volt=((buf[5])+(buf[6]<<8))/(float)100;
     if (volt<battOK) 
      {
      tone(3,800); 
      delay(50);
      noTone(3);
      }
     int line = (64-((volt-3.3)*71));
     altitude=145366.45*(1-pow(((P/(float)100)/(setpress/(float)100)),0.190284));
    
  // Sensor readings may also be up to 2 seconds 'old' (its a very slow sensor)
  float h = dht.readHumidity();
  // Read temperature as Celsius
  float t = dht.readTemperature();

     if (maxAltitude<=altitude) {maxAltitude=altitude;}

     if (digitalRead(9)==1)
      { delay(100);
      //by pressing button, Actual Altitude, and Maximum or Memorized Altitude
      //is set to zero as well
      //In the case of some major error, please press reset button on your Arduino
       setpress=P; 
       maxAltitude=altitude;
      }

     if (digitalRead(10)==1)
      {
        //write results, A: stands for Actual Altitude, 
        //M: stands for Maximum or Memorized Altitude
        display.clearDisplay();
        display.setTextSize(2);
        display.setTextColor(1);
        display.setCursor(0,0);
        display.print("A:");
        display.print(altitude,1);
        display.setCursor(0,16);
        display.setTextSize(2);
        display.print("A:");
        display.print(altitude*0.3048,1);
        display.setTextSize(2);
        display.setCursor(0,32);
        display.print("M:");
        display.println(maxAltitude,1);
        display.print("M:");
        display.print(maxAltitude*0.3048,1);
        //write designations as is feet and meters (ft, m)
        display.setTextSize(1);
        display.setCursor(113,3);
        display.println("ft");
        display.setCursor(118,19);
        display.setTextSize(1);
        display.print("m");
        display.setCursor(113,36);
        display.print("ft");
        display.setCursor(118,51);
        display.print("m");
        display.drawLine(127,64,127,line,1);
        display.display();
      }
      else 
      {
        //if switch is in different postion, show Actual Pressure,
        //humidity and temperature for weather station + battery status
        display.clearDisplay();
        display.setTextSize(2);
        display.setTextColor(1);
        display.setCursor(0,0);
        display.print("P:");
        display.print((float)P/100,2);
        display.setCursor(0,48);
        display.print("Bat: ");
        display.print((float)volt,2);

        display.setTextSize(2);
        display.setCursor(0,16);
        display.print("H:  ");
        display.println(h,2);
        display.print("T:  ");
        display.print(t,2);
        //lets make some symbols
        display.setTextSize(1);
        display.setCursor(108,03);
        display.println(" mb");
        display.setCursor(117,18);
        display.print("%");
        //Bitmap for symbol for degrees 
        display.drawBitmap(110,31,degree_glcd_bmp,8,8,1);
        display.setCursor(115,31);
        display.setTextSize(2);
        display.print("C");
        display.setCursor(115,48);
        display.print("V");
                display.setCursor(118,51);
        display.print("m");
        display.drawLine(127,64,127,line,1);
        display.display();
      }
   }
   // if this point is reached, probably ID is wrong, and it will back into loop again
   //else if (ID!=myTX) {tone(3,3000);delay(50);noTone(3);} 
   //do not apply code above - too loud, headache :D just leave it "open" to back into loop
   
   }

 }

 

The same MS5611 sensor is used in Arduino variometer. And it is cheaper than ever on Banggood.com

This is all for now

Come back soon, will be updated… Escpecially diagram and videos… Spring time requires hard work in my backyard, but also I did now and then some job for money, and this is main reason of delay of everything.