I am currently working on a quadcopter with altitude hold feature. For this I need an accurate sensor with around 10cm resolution.
There are some barometric pressure availiable which are small, cheap and have the required accurancy.
I made a comparison between the:
LPS25H
MS5611 01b03
BMP180
BMP280
I used an Arduino NANO at 16MHz and read all of the sensors at 25Hz. From the datasheets we can get the figures below:
LPS25H: 24bit ADC, 128 avg, best update rate: 25Hz
MS5611: 24bit ADC, 4096 osr, best update rate: 50Hz
BMP180: 19bit ADC, 8 osr, best update rate: 33Hz
BMP280: 20bit ADC, 16 osr_p, 2osr_t, best update rate: 26Hz
If you use the settings above and you look at the output data you can calculate the effectiv number of bits. To do this I plotted the raw pressure values as binary and look for the constantly changing bits in the data. I only considered 4 consecutive samples to eliminate drifft or changing athmospheric pressur as a factor. Now the resolutions look like this:
LPS25H: 14bit
MS5611: 17bit
BMP180: 14bit
BMP280: ...
But to be sure which sensor is the best I made an experiment and compared all sensors against each other. In the graph below you can see the result(click to enlarge).
The black line indicates the "true height". All sensors have been filtered with a moving average of 10 samples.
You can see that the MS5611 gives the best results in terms of accurancy. Below you can see a comparison of the standard diviation and the peak to peak values of the sensors
LPS25H: sigma=60cm, pkpk=2,85m
BMP180: sigma=35cm, pkpk=1,52m
BMP280: sigma=14cm, pkpk=0,65m
MS5611: sigma=12cm, pkpk=0,62m
In terms of computation the lps25h is the best, because it is already temperature compensated. The bmp180 is the worst when we talk about processing power. Because the algorithm to compensated it is quite long.
I always wanted a TFT GUI with a touchscreen interface and it should behave like a smartphone. Pretty ambitious at first, but as I looked into it I came up with simplifications and ways to actually make it happen.
The screen
The tft-controller is the most important part of the project and it needs to have some kind of hardware-scrolling build into it. I started with the very popular ILI9325 and most of the functionality was developed under this controller. But this controller has only a basic implementation of the hardware-scrolling. It is capable of rolling scrolling without any fixed areas. So there is no easy way of adding permanent buttons or text to the screen.
I searched some time for a more suitable controller and settled on the more advanced ILI9341.
This controller can perform vertical rolling scrolling with a top- and a bottom-fixed-area.
Because I wanted the fasted updated rate possible I need a parallel interface to the screen, which was pretty hard to find. I managed to find one on ebay.
The micro
All my projects so far used a pic-microcontroller and I like them because of their rich hardware features. There are hundreds of PICs available, so it is a challenge to pick the right one. I used the 18f47j53 because of several features:
8-bit parallel port
SPI port with DMA
12 MIPS
This micro has an 8-bit parallel port but the screen i picked is configured for 16bit data.
In the datasheet of the PIC there is a mode for multiplexing 16bit data onto one 8bit bus, but in this mode the PIC sends two wr-strobs.
I ended up with a bit of a hack. I used the 8 bit data mode with multiplexed adress. In this mode the PIC sends on adress-latch-enable ALE and one wr_strobe, which is perfect.
My final schematic looks like this.
If you have the parallel-port configured, the data transfer looks like this:
//------------------------------------------------------------------------------
//
// Sends a byte of data to the tft
//
// @param data data which should be send
//
void TFTData(uns16 data)
{
while(BUSY);
TFT_DATA;
PMADDRL = data.low8;
PMDIN1L = data.high8;
}
But if you are tansferring a stream of data, you can set the mode to TFT_DATA once and stream out data.
On a PIC a RAM to SFR transfer like this compiles to MOVFF instruction which takes two cycles(8 clock cycles). In the above drawing you can see that with minimal waiting times the parallel-port takes 8 clock cycles for a transfer.
The transfer starts on writing to PMDIN1L. So, by the time you have written the next byte to PMADDRL the parallel-port finished the transfer. So you don't need to check the BUSY-bit.
One last thing. The data in PMADDRL remains in the register after the transfer.
Why is this important?
Because all graphics functions rely on single pixeles and rectangles. And it is important to get these functions as fast as possible.
The DrawFullRect function is implemented by unrolling the pixel loop and sending blocks of pixels with the same color
All of this allows a pixel-transferrate of 6MHz for pixels with the same color. A screenfill takes about 13ms.
Graphics
The scrolling works by using the hardware scrolling function. First I am moving the content by some movment in y direction. Then I set up some boundaries for the graphics library. So now it is possible to draw a complet page everytime, but only the slice which wrapped around and therefor has to be updated, gets drawn by the graphics functions.
Here is a simple example of what I mean.
//------------------------------------------------------------------------------
//
// Draws a single pixle onto the screen
//
// @param x x position
// @param y y position
// @param color color of the screen
//
void DrawPixel(glcdPos x, glcdPos y, glcdColor color)
{
// check if pixel is on screen
if(x > GetWidth()) return;
if(y > bounds_y_max) return;
if(y < bounds_y_min) return;
SetBox(x,y, x+1,y+1);
PixData(color);
}
This works for all drawing function: pix, line, rect, circle and bmp. You only have to construct some function which can draw a specific page to the screen and by calling the scrolling function just befor it, it handles all the scrolling and sets up those boundaries.
How to use
Here is a small example programm, which shows how simple a list is integrated.This app displays a scolling list with no fixed areas. Every page has a different color and some text. There are 4 pages.
void testScrollPage(uns8 page)
{
if(page == 0) return;
static const glcdColor color_table[] = {BLUE, GREEN, RED, BROWN};
glcdColor color = color_table[page-1];
// Draw BG
ClearLcd(color);
printf(" PAGE: %1u",page,120,150,WHITE,color,X_CENTER|BOLD,2);
}
void testDrawPage(uns8 page)
{}
void testApp(void)
{
while(1)
{
// get a valid touch
EventManager();
uns16 x @ e.touch_data.x;
uns16 y @ e.touch_data.y;
switch(e.type)
{
case BACK_APP:
case CLOSE_APP:
e.type = NONE;
return;
break;
case OPEN_APP:
// OPEN_APP event must be cleared by app
e.type = NONE;
// 4 pages, listheight is 320px per page, no fixed areas
InitScroller(TEST, 4, 320*4,0,0);
ScrollPageTo(0);
break;
case TOUCH_DOWN:
break;
case TOUCH_MOVE:
break;
case TOUCH_UP:
break;
case NONE:
break;
}
ScrollPageBy(e.dy);
}
}
Here is a more usefull example programm of a list with tiles. Each tile has it's own title and you could add more to the data struct if you want. e.g. some description or a path to an icon. On top of the screen is a fixed area for the actionbar. You can place your app title or some button there.
In the code below you can see that there are two functions which draw pages.
The first(testListScrollPage) and most important draws the page with the above described sclice methode.
The second(testListDrawPage) one can always draw on the screen. This on is for dynamic changes like the highlighting of the tiles or if you want to update a clock which is displayed on some tile.
If you touch on one tile the tileDetails function gets called but you could call a diffrent function for every tile or call the same function and use parameters.
For example every tile has a title and a value. And you draw a slider based on that value. When you touch on a tile you can use the x-pos of the touch as input and change the slider and the value.