adding an sd card to your arduino
instantly gives you
access to mass storage team this with an
lcd screen
and you can now display saved images
using only your microcontroller
but how do we translate bitmap image
files to pixels on the screen
let me show you how
hi and welcome to bytes and bits i've
done a few videos now
working with this lcd um touch screen
with the built-in sd card and i saw in
the previous video
that we can very easily use some library
software to read
bitmap images from our sd card and then
display them on the screen itself
now using libraries is a very quick and
easy way of adding functionality
into your arduino sketches but sometimes
it's a bit of a double-edged sword
where the libraries that you load can
sometimes contain a lot of extra
software that you don't actually need
and of course that uses up valuable
programming space
on your arduino so there is another way
of doing it where we can
have a go at writing our own library
software
now obviously this is a bit more
involved and a bit harder
but it's actually a hell of a lot more
fun where we can actually get right down
into nitty gritty
of how these things work so in this
video i'm going to show you how we can
look
at how a bitmap file is organized
and stored on our sd card and then using
the very low
level um reading and writing
capabilities of
our sd card library in the arduino
we can really read that information in
and translate it ourselves and of course
this then
means we open up this sort of idea where
we can really look at any file type
stored on our sd card and if we've got
enough knowledge
and skill we can obviously then program
our own libraries to read those
files in or create those files so that
we can then translate them
off to other pcs and so on
so let's make a start and have a look at
how we can then write our own
bitmap file library on our arduino
to get started we need to connect up all
the hardware
i've covered this in detail in a couple
of previous videos so i'll put links to
those up in the top right corner
and also down in the description and
also put a link off to the project page
on my main website for this
where you can get all of the circuit
diagrams and of course download all of
the code
now that we've got our hardware sorted
out we need to have a look at the actual
bitmap
file format so any file is really just
a series of data bytes stored
in a stream as these things called files
on
a on a hard drive or our sd card so
really we
if we read those bytes of information in
obviously they're stored in a certain
format and certain bits of them mean
certain things
and that's really what we have to have a
look at and understand
so if we go to wikipedia's entry for the
bmp file format
this gives us a very detailed
explanation of how it all fits together
but if we look down we'll see that we
come to a file structure section
so what this tells us is that the actual
file that we will find
on our sd card is split up into a number
of areas
so the first 14 bytes of data that is
the bitmap
file header which tells us sort of
general information
about the way in which the file is
organized we'll then find that there's
the second block of data
which is this dib header and that will
store a number of information
about the size of the image and how the
pixels are actually formatted
we then come to a number of optional
areas depending on how
the image is stored we might have some
compression information
we might have some color information
we might then have a small gap in the
data
and we'll see how we can detect whether
that there's the gap there or not
when we come to pars the
file header there's then the actual
pixel data itself
some other sort of alignment issues and
then some color profile information
so this all sounds sort of quite
complicated at the moment but if we then
go down a bit further we'll start to see
how this is all broken down
so each of these sections then is just a
series
of um bytes of data and those bytes of
data then obviously
mean something and we have to read them
in a certain way so if we look at the
bitmap file header
so this column here tells us it's offset
within the file so if we open a file
using
our sd card open file
method we will end up with our data
pointer sitting at the very first byte
of that data so as we start to read
bytes we will start to read them in this
order
so the first two bytes we read in is
this header field value
which identifies what type of file this
is
so it's a two characters or two bytes
and we're expecting those to be saying
bm
for a bitmap file but there are other
versions here
and again as we write our code
we will probably decide that we will
only filter out
files which have this code bm in them
otherwise we'll have to obviously then
write code to handle all of these other
file formats
so they're all bitmap file formats but
they are all slightly different
so the next four bytes we read in so
this is a 32-bit integer number
that tells us the size of the whole file
in bytes we then have two 16-bit
numbers so two bytes followed by two
bytes and those are reserved for
further use and we then have the next
four bytes of data representing another
32-bit integer which tells us the
starting address
of the actual pixel data so remember if
we scroll back up here a bit
we have our bitmap file header our dib
header
we have a number of optional bits and
then our pixel data
so this number this 32-bit integer
number
tells us the offset of the start of this
block of data
so we can always get directly to the
pixel information
so that's our our bitmap file header
which is then followed by our bitmap
information header or this dib
header and again there's various
versions of this
but what we'll find is that there's
actually a core
that they all relate to so
that is the actual core and there is
then an example further down here
of what's known as a bitmap info header
and this is the generic
sort of um header that we're going to
find
so it includes the core plus a few extra
values
and we'll find that all of the other
variations
in effect just include this and then
extend with certain values
after that so this so we can really take
this
idea here as being our default
and again if we look through that we'll
see then that
so the offset here this follows on from
the end off this bitmap header info
so so zero a plus four four bytes
takes us then to zero a okay so we're
just simply reading in
bytes after the header info the bitmap
header info
so the first four bytes is a 32-bit
integer
which will tell us the size of this
header header um
and again that that will usually be 40
bytes because that covers this header
here
um the the extensions are less common
we then have another 32-bit integer
which tells us
the width of the bitmap now this
is usually a signed integer everything
else is an unsigned integer but this is
a signed integer
and negative values um do actually mean
something but they are not very common
so we're probably going to ignore that
so the next four bit value then of
course is the height of the pixels
the next two byte number so a 16 bit
number here
is the number of color planes and and
that that
has to be one otherwise we won't be able
to read this value
next we're looking at the number of bits
per pixel
so this is where we can have either one
bit which will give us a monochrome
image
right up to our 32 bits per per pixel
which is our 24 bit color plus it
bits for our alpha channel or or our
transparency
so we're going to be dealing only with
24 bit
pixel bit bits per pixels so we want to
make sure that when we read
our our header that we check this value
for it being
24 next
we have another um 32-bit
integer so if another four bytes which
is the compression method
and we'll want this to be zero we won't
want to be handling any compression
in our code we then have another 32-bit
integer value so another four bytes for
the image size
then we have a further two 32-bit images
which will look at the horizontal
resolution
and this again is is in effect are our
pixels per
inch value again it is pixels per meter
here
um that's really what it relates like
your dots per inch whatever you want to
call that
we then have the number of colors in our
color palette
and usually that will be zero and that
will just simply then say that
um if we've got a 24 bit
bits per pixel then we'll have two to
the power of 24
colors in our palette and again then we
have
important colors used and that that
really isn't a used value in it
but you can see here we have a we have
to read a number of bytes per
value to get through this header
so really um that gives us the structure
of our header information after that
the these if i come back up here these
optional areas we're really not going to
bother with those
and as we've seen there is a an integer
value in the bitmap file header which
lets
us jump straight across so we read our
we read these two blocks of headers
and then that data value will let us
jump straight down then
to our pixel data so we don't have to
worry about
reading this block here in these these
optional values or actually worrying
about them
so i've created an excel spreadsheet
that summarizes this data so we can see
here
each of the individual data
values the size and bytes that we need
to read in for each of those
and then what those values actually mean
so we can simply
when we open our file we will simply
start reading in these values
and then that will let us decode exactly
how this image has been stored
and where all of the pixel data is so
that we can go ahead
and read that in and start sending that
off to our lcd screen
and as usual if you um look in the
description
you'll find a link to my main website
where
there'll be a page for this part of the
project and you'll find all of this
data on on that web page along with any
code of course that we develop
during this during this video so do make
sure you do visit that
and have a look for that information
to code this image format then we'll
we'll make a start with
our base code which has our
tft or our lcd screen and information
here
our touch screen and our sd card
and again all this code you'll find a
link in the description down below
so it's just setting everything up and
connecting it up and initializing those
objects so at this point down here
we should now have our lcd setup our
touch setup
and be able to communicate with our sd
card
so i'm going to implement this bitmap
file reader as an
object which will make it easy for us to
then extract that at a later time
into our own personal library file which
we could then use in other projects if
we want
so if i just paste in my actual code
there
and let's go through and see what this
does
so if you're not familiar with um using
objects
in uh c plus plus um it it's it is
just a way of formatting data
and uh functions so we have to define
something called a
class and this is effect the the
template for our data structure
for our object so i'm defining a class
which i'm going to call a bitmap handler
inside that class then we have to define
a number of
data members or data tributes and then
functions which we call methods when
they're inside a class
so some of these are going to be what
are known as private
and and these are our data attributes
and functions
which you can only access when you're
inside the object
and again don't worry don't worry if
this doesn't mean so much to you
but object oriented coding is something
which you you really should get your
head around
um it does make things a lot easier when
you come to develop more complex
software
so what i'm doing here is i'm creating
some
values within my object which are going
to let me
run that so i'm going to have a boolean
file okay flag just going to tell me
whether the file i'm reading in
is okay or not and that's both in terms
of whether we can actually access the
file
or whether it's the right format and so
on we're going to need some sort of file
pointer
which will let us access that on an sd
card so we're using a file object here
and that will handle that and of course
we'll need to supply
a file name for the actual file we're
going to read
as you saw in the explanation of the
file format
we have a number of integers
which are stored as individual bytes so
we'll have to have a way of reading in
those individual bytes and then
converting them into
either or 8-bit 16-bit
or or 32-bit integer values
so i've written three little methods in
here which will do that for us
so really what's happening is we will
ask one of these methods to read
the next block of data to translate that
into an integer value
and then return that to us so we can
then store that
within our object so
we there weren't any of the values which
were eight bit values but i put that in
for completeness here so really what
we're saying is
um we're doing a check so remember we're
going to be using this
data attribute so bitmap file is going
to connect us to the actual
file that we want to read so if that
hasn't been opened yet
then obviously we need to just exit out
of here otherwise
we're simply going to read the next byte
from the file
and return that as an integer because
again that will just be a single 8-bit
value
it's actually a little bit more complex
than when we start to read our 16-bit
and 32-bit numbers
so we know that our 16-bit number is
going to consist of the next
two bytes of information so again we're
reading in these two bytes
now the data in a bitmap file
is stored in what's known as little
endian format
and what that means is that the two
bytes of data
we will store the least significant byte
first
and then followed by the most
significant byte so the first byte we
read
is the lower eight bits and the second
byte we read
is the upper eight bits so we're reading
those two bytes in
and then we're just using this little um
line of code here
we're taking our most significant byte
and we're shifting it up eight bits to
make it um to the
upper eight bits of the 16 bits and then
just simply adding on the least
significant byte
and that will then give us our full 16
bit number
similarly with our 32-bit numbers we
have to read in four bytes and again
these are in little endian format as
well
um so we're reading in the lower eight
by
eight bits then the second block of it
bits
third and then the most significant
block of eight bits
and again we're then just shifting those
along by doing this
um bit shift value so we're taking our
our most significant
bit byte we're shifting it along 24 bits
and then just simply adding in the other
ones behind it and that will then build
up
our full 32-bit number
so that private set of data and all
is going to be used by the class
it when it's processing the data we then
come into
our public area and this is
both data attributes or variables in
effect
that we will be able to read from
outside of the class so in our main code
once we create an object and and get it
to read in the file and so on
we'll be able to then look at these
values from the outside
okay so public data here and you can see
all i'm doing is creating a
an effect a data tribute or or a
variable as i said
um to hold each of the values in those
headers
in those headers so we've got our header
field and that's the one that will
identify it as a bitmap that the bm
image
the file size and the image offset which
tells us where the pixels start
we've then got this dib header that we
went through with all the values in
there
including the size of the image and the
bits per pixel and so on
so what we will do when we
create an instance of this object
is that we will end up calling this
function here so this function
has got the same name as the
gas and that will make it into what's
known as a constructor
so as soon as we create an object
based on this class this function here
or this method
will be automatically called
and we're going to pass in the file name
then as a string object
of the file that we want to process
so when we first cut when we first
instantiate our object then
okay we at this point we don't know if
the file is okay so we're going to set
that
file flag to being full send we don't
yet have a valid file
we're then going to take this file name
that's coming in as a parameter
and we're going to store that inside our
objects
data attribute this this bnp file name i
remember that was one of the private
ones
so we have a log of which file we're
working with
we're then going to try and open that
file
in read mode and if that file doesn't
open
we're going to serial print a little
error message and of course mark this
file as
not okay and at that point we will then
actually exit out of our constructor
function
because all of this block here will be
skipped over
and we will then not be able to to to
work with that
but if that file is okay so at this
point here we've been able to open the
file
we're then going to go off and read the
file headers
so let's have a look at what reading the
file headers actually does
this function here so it's going to
return back
a boolean value whether it managed to
read the headers or not
and again that will then let us work out
whether this file is in the right format
or not
so again we're making sure that our file
is open
and we're then going to reset any file
pointers
back to zero okay so we may have been
doing some reading of the file
in other ways so this just confirms or
just
make sure that our file pointer is
pointing at the very first
byte of our file so we then start
reading in the header so
when we're reading in each of these data
values so remember these are all the
data values
defined in our headers and we've then
created
individual data attributes to hold each
of these
so our our header field value
is the first 16-bit integer that we read
in
and then we're simply going through
reading them all in here so remember we
had
two 16-bit integers
which were reserved for various
applications that create these bitmap
files
so we we still have to read them in but
we don't need to capture the value so
we're just reading in a 16 bit value
and just throwing it away so as we go
through all of these
reads we will then be building up all of
the header values
as data attributes in our object
and when we get to the end here we will
then
either return back true we managed to
um read all those headers
or false that there was something wrong
and we weren't able to read those
headers in and in effect at this point
it just means that the file wasn't open
so coming back up to our our constructor
function then
so we are calling this inside an if
statement
so that actually will execute the
function to read in the headers
and then check its return value swift so
we said if this returns back a
false then we weren't able to read that
in and we take the appropriate action
and mark this file as not okay
so if the file headers are read
correctly then we want to check them so
again we're doing a very similar thing
here where we're going to
call a method called check file headers
so if we come down to that
and this is where we are reading in and
examining those
header values so we said that
our first header value then is the
header fail
field and that and that needs to be
equal to this character
string bm and in hexadecimal that's
going to be uh
4d42 so if
our header field value that we read in
during our read file headers so there's
the first two bytes
if it isn't the bm then we're going to
return back false which is going to
signify that we have not got the correct
file type
we're then checking our color plane
value which must be equal to 1
we're then checking our bits per pixel
value which must be 24 bits per pixel
because that's the only one we're going
to be dealing with here
we don't want any compression so if all
of these
checks pass we will return back true
otherwise we'll send back
false for each individual one so back
into our
um our constructor function so again
so if the check file headers returns
back false then that's an
error and we mark that otherwise we're
gonna say this file is all okay
and we're gonna mark it as okay
yeah so once we come out of this if
statement so this
this if statement matches up with the
file being able to be opened
so once we've got through reading in the
headers and checking them
we then want to make sure that we close
our file so that we when we exit out of
our constructor function
our file is closed so that we can then
carry on and work with that
at the next operation if i come down
to the setup function again normal idea
here where we're setting up all of the
bits of hardware
so at this point we should now be ready
to do some coding
so if i paste in there our
object declaration so we're creating a
variable called
bmh for bitmap handler the type of this
variable is going to be
the class bitmap handler and the way in
which we instantiate that variable then
is we
are calling the bitmap handler
and that will of course call that
constructor function
and we need to pass in a string
which is going to be the file name that
we want to use so i have
another file on there called
balloons.bitmap which is another 24-bit
um bitmap file again i i've sized all of
these bitmap files to
fill my screen which is a 320
by 240 pixel image
so we should be able to um upload that
into our arduino and get back some
information
about our bitmap so let's just upload
that into
our arduino
and i'm going to check on my serial
monitor
and there we have our response back from
our constructor saying that this bitmap
file
is okay so at this point
our object does have all the information
about that bitmap file loaded into
its member tributes so we could actually
access those
doing our our bmh dot uh we could get
the header
um field value and in that way there
and we could have a look at what those
actually are but what we could also do
is we could
create an extra little function or
method inside our class which will
actually print out all the details about
this file so let's
create a new method in there called
serial print headers
which is basically going to take all of
that information it's just read and all
that header information
and print that out on our serial
port so if i scroll up
to this function so inside our object
i've already created this method called
serial print headers which doesn't
return any values
and it's really going through each of
the data attributes
in our class i'm printing those out so
saying telling us which
what data field this is and then it's
value so you can see that we're just
going through all of the
actual values that we've read from our
bitmap file
so let's upload that into our arduino
and have a look at our serial monitor
and there we have our bitmap file
information coming out so again this is
just reading in all of the header
information from that file
so our file name our header field value
which is our bm
are our file size the image offset tells
us which
byte is the start of the pixel data our
image width and height and again
remember i've
saved everything on my images to fill
my tft screen our color planes
are 24 bits per pixel and no compression
so that's all the information then that
we need to make sure that this
image is one that we can then handle
with the code that we're going to write
to to now decode the pixel information
to work out how these pixels are stored
we first of all need to return to the
wikipedia entry
on the bitmap file format and we'll come
to a section a bit further down
which talks about pixel storage so
really what's happening here
is that the bytes in the
file represent
each of the pixels in our image and
those are packed
into rows so as we start
reading a series of bytes from the pixel
area we're going to be getting
individual pixels
that will build the image up in rows
now the rows that we're building up if
we look at this a bit further down
so usually those are stored as a bottom
up image so the first pixels that we
read
in the file will represent the bottom
left-hand corner of the image and
obviously
on our lcd screen usually
we will use the top uh top left corner
as the zero zero pixel so we do have to
make sure that the the rows we
draw represent the rows on the image
there is one other thing in here as well
we did see that whenever we
looked at the height value in the
headers
that was a signed integer and there is
if we look at this bit here
there is a a a format they use so if you
find that the height value
in the dib header is a negative value
what that means is that the the
magnitude of that value is the actual
height
but the fact that it's negative means
that it has been flipped
so instead of going from bottom to top
the rows
will be in the order of top to bottom so
that is something to look out for i'm
not going to
handle that in this bit of code but
again
something you can obviously add in
yourself at a later date
the last thing with the pixels then is
that
they have to be in
multiples of four so whenever we
create a a and a row of pixels
on on the sd card and bitmap file
we will take the pixels in a row we're
using
three bytes per pixel so our our red
green and blue
and as well on on that aspect um
make sure that we understand that the
color values will be
stored in the order blue green red so
the first byte we read will represent
the blue component
then the green then the red but when we
create a row
we'll be using three bytes per pixel
but when we get to the end of the row
the
total number of bytes used to represent
that row
must be a multiple of four so in other
words it's saying here the pixel array
is a block of 32 bit d
words so we must end up
at a four byte boundary and really what
we're going to do there is just simply
if we
end up with say we end up with uh 30
bytes worth of pixel information
then we need to make that up to 32 bytes
so we'll just simply put two um bytes
with zero in them
at the end of that just to pad it out
um so that's that's quite a complex way
of of looking at storing this data let
me just summarize that a bit more easily
by showing you a diagram
with an image example
let's consider a small image with two
rows
of three pixels each and each of these
pixels being a different color
so the way this would be stored on our
sd
card then is we will first of all take
the bottom row
and convert that into three three
byte sequences so again those
each three byte group will represent one
pixel
and those will be stored in the order of
blue
green red and similarly
our second row so the upper row will
also be stored
in the same way so byte
zero the very first byte in our pixel
data
will represent the blue component of the
very bottom
left corner pixel for this row then
we'll have a further eight bytes making
a total of nine bytes
which will represent all the color
values
for those three pixels now
our second row because we need to be on
these four
byte boundaries that must start at a
byte
which is a multiple of four
and what this means is that our
first row requires three extra bytes of
information
to pad that out so we have 12 bytes on
our top row
which means that the second row will
then start at this
four byte boundary and of course since
every row contains the same number of
pixels they will all require this same
three byte padding so let's come back
into the code for our class and let's
add a render image method
so i have it um already put into my
class here so we have our render image
method
and for that we'll need to supply it
with a number of parameters
these two the screen x and screen y that
will tell us where
on the screen we want to place this
image and of course that's going to
denote the
top left hand corner and again what for
our screen coordinates
the the 0 0 position is actually in the
very top left corner off the screen
and of course we're measuring down from
there and as we've just seen that that's
different to the way that the image is
stored
on our file so we'll have to cope with
that for a second
i'm also then passing in this reference
to
the screen object so we know that
if i go up the very top of the screen
here
we know that we we have defined a global
variable this tft
which is an instance of the ili 9341
driver that actually does the talking to
the
lcd screen itself and and we could if we
want
within our class just reference that
global variable
but that's actually um not a good thing
to do
our our class here we may eventually
want to
extract that out and make it into a
little standalone library
which we can then use and just simply
import that into
other arduino projects now if if this
library
is relying on there being a a named
global variable tft then obviously our
other software
has to have that global variable as well
so we are
our library is now dependent upon
a certain number of variables being in
existence in our sketch and that is not
a good way to code
so what we're doing here is we are
passing in
a reference to that object so whatever
screen object we happen to be using in
our sketch
we can just simply pass that in and this
class will now render this image
on that particular screen so that's just
that's just a bit of good programming
practice for you there
now the actual render image function
itself
if we think about it this class will
already have been
instantiated using its constructor
function
so when we instantiate this bitmap
handler function
our constructor function down here will
be called
so it will already know which particular
file it's dealing with and it will have
checked that file
and we should now know that that file is
able to be used
so when we come down to our render image
function
we're going to have a block of local
variables that we're going to be using
as we
pars this file and i'll say we will go
through those as we meet them in the
actual code itself
but at this point we know we don't need
to
check the file we just simply need to
work out if the previous checks on the
file all went okay
so at this point if this this data
attribute
file okay if that contains a true value
then we know that our our
object has already
read in the file information and checked
it so if it's not true
then we really want to just get back out
of this render function without doing
anything
we then want to make sure that the place
where we've been
asked to place the image is actually on
our screen so if it's actually off the
edge of the screen there's no point
trying to draw it
and that's all we're doing here just
doing some limit checking and again
our our screen dot width that's a method
in that ili 9341 driver software
that actually tells us how wide the
screen is and how high the screen is
so the image that we're going to use
well we have already then
as i said read the header information
and these two did attributes image width
and image height
we will have set that elsewhere in this
class
and that will be the um the actual
image file pixel width
and pixel height so we need to make sure
how much of this we actually need to
draw to fit it on the screen
so we start off by just saying our
displayed width
and our displayed height so this is the
number of pixels we're going to draw
on the screen we set those to the full
image with an image height
we then check that if we place the top
left corner of this image
at these positions screen x and screen y
is it going to fit on the screen
so if it does fit on the screen then
that's fine we just leave those
displayed within displayed height
as they are but if it's actually going
to go off the edge of the screen
we then reduce our displayed width and
our displayed height
values to only show the number of pixels
required
to hit the either the right hand edge of
the screen or the bottom of the screen
so we're really going to just going to
clip it at that area
and that's all that block of code there
is doing so at this point in our codes
once we reach this line down here
we now know um that our image is correct
or at least it's a file that we can work
with we know that if we place it at
screen x and screen y
we know that if we write out
this displayed width and displayed
height rectangular area
then that image will fit on the screen
so we've got everything ready
to start actually putting pixels onto
that lcd
we now need to work out how we can grab
hold of the pixel information
out of the bitmap file
so we've already seen that each row of
pixels
is stored in the bitmap file um one
after the other
now we want to be able to pick out a
particular row
so that we can then draw that on our lcd
so we'll need to be able to
access each row and the way we do that
is
to look at our pixel data offsets
remember
we had up here somewhere
we had our image offset value
and that was the byte address
within our sd card file
of the first byte of pixel image data
so so position image
offset in our file gives us the first
byte of pixel data
so coming back down to our uh render
function okay so to get
a particular row we need to
move to that pixel data start
position if we want to get
the very first row then we just start
reading data from there
if we want to get a row that's higher
than that we then have to skip
a number of blocks past
that start position okay and then the
size of each block
will be the size of one row of pixels
so we need to work out how many bytes
are needed to store
one row of pixels in our image file
and that's we're working out here so we
know that we're using three bytes per
pixel
so three times the image width will give
us the total number of color bytes of
information
in this row we then know that each row
must contain a multiple of four
bytes and that's what we're doing here
in this block
so that is the number
of bytes of color information
percentage sign here is the modulo
operator
so modulo 4 will in effect
do a division by 4 and tell us what the
remainder is
so that will tell us how far into the
last
block of four bytes this
color information goes so if if in our
in our previous example
we saw that we had nine bytes of color
data
so nine modulo four would be
one so so nine divided by four is two
remainder one so this would give us one
that says we are one byte into the next
block of four
so if we do 4 minus
this value that will tell us how many
bytes we need to
add on to get us to the next 4 byte
boundary
so again in our example so 9 modulo 4
gives us 1 4
minus 1 gives us 3 so we need to add on
3 bytes to give us that full 12 byte row
now the problem we have is that if we
happen to get b actually on
a 4 byte boundary then our
our color byte width
modulo 4 will actually give us zero
which will mean we're saying we need
four bytes to get to the next four byte
boundary
which is is in a way true but we're
actually at a four byte boundary so if
we
modulo that number by four again
that will make sure that if we do if we
do get an answer off we need four bytes
to get to the next boundary
this will bring it back down to zero
because again four divided by four is
is one remainder zero so the modulo
gives us zero there
so adding total number of color bytes
to the padding bytes that gives us the
total number of bytes
for each row of pixel data
so once we've calculated that we've
really got all the information we need
to start writing as pixels so the next
line then is of course to reopen that
file
and then we're going to use one of the
low level functions
of our lcd graphics library
so again the adafruit library has got a
number of functions to draw
things like lines and so on but it also
has some lower level functions where we
can simply
write pixel information very quickly out
to the screen
and what we're doing here is we're doing
was we're using this
set address window and what that lets us
do
is to define an area on the screen
so rectangular area and you can see here
we're using our screen x screen y
to be the top left corner of this area
and then that area is going to be
display with pixels wide
and displayed height pixels tall
what then happens is all we have to do
is start
sending pixel color data out
and our lcd screen will simply take the
area that we've now set as the active
area
and start filling it from this top left
corner position so we'll fill
the top row of pixels first and we just
simply keep sending it pixel information
until it fills up that whole target area
and that is a very fast way of writing
out to these lcd screens remember
everything goes across this spi channel
which is a serial channel so in effect
that is it is
a slow way of doing it but this means we
don't have to send lots of other command
information around it
we just simply say here is an area i
want you to fill
and then we just blast out the color
pixel data
now you'll see as well that we've had to
surround that
method call with a start write and an
android
so when we need to talk to the lcd
screen we do need to make sure that we
address that
so we have other devices sitting on our
spi
channel again we have our touch screen
and our sd card itself
so what this does it's it the start
right
initiates the conversation and tells the
spi channel to
talk to the lcd screen we then send the
lcd screen some command codes
to set up the address window and then we
tell the lcd screen we have finished
talking to you
it's important you do that because
obviously if we leave
the channel open to the lcd screen and
then go off and try
and access the sd card the our hardware
is going to get confused as to which spi
device
we're actually talking to the higher
level
functions in the graphics library handle
all this for you
but now we're working at a very lower
level we need to start initiating these
conversations
and closing these conversations
ourselves
so at this point we open the channel
send the commands and then close the
channel so we can now talk to other
devices
a little bit of debugging here so just
we can see what's going on
inside our system and then we get into
the actual
pixel data so we're going to use
two for loops so our first for loop
will handle the rows and then our inner
for loop will handle the actual pixel so
or affect the columns
so our first row for loop
that's going to loop from row 0 which is
the top row
as displayed on our lcd screen and it
will count down displayed height pixels
from there
so that's all this this loop is doing
now to get hold of the pixel information
we need to be able to pull out
a particular rows worth of data
and we know that if we're drawing the
top row
in our lcd display
that that's actually going to be the
last row stored
in our sd card file in the bitmap file
so we need to be able to work out for
any particular row that we're drawing
where in the bitmap file is that block
of data stored
so we need to calculate
a an offset value so that is the the
byte position
within our file where the start of this
rows
color information is and that's what
this calculation is doing here
so this image offset that that's a value
we've read from the header
of the bitmap file and that tells us the
very first piece of pixel data and of
course that's the very start
of the very bottom row of pixels on the
image
this bit of code here that tells us
how many rows above
the bottom row the pixel row that we're
looking at
is so so pixel row is the lcd screen
row that we want and this tells us how
many rows that is above
the bottom row in the actual image
then for each row we need to skip ahead
bytes per row bytes so this
calculation here so that's
how many rows above the last row we are
times the number of bytes per row added
on to the offset
of the very first row that should then
give us
the starting byte position
of the first byte of information for
this
rows pixels okay so that's all we're
working out there
because what we're going to make use of
is the idea that in
in these files as we read them from our
sd card
we're working with what's known as a
file pointer so so far we've just simply
opened a file
and we've started reading and we've got
the first byte full but second byte and
so
on but what the system is actually doing
is it's keeping a track of
which byte we're about to read so when
we first open the file
it sets this file pointer to zero so the
first byte we're going to read
is the very first byte in the file
itself so position zero
as we then read a byte that file pointer
moves on to position one so the next
time we read a byte
we'll read byte position one and so on
but there is a special method within our
sd card handler
that lets us reset this file pointer to
wherever we want
so we don't have to read things
sequentially what we can do
is we can actually say okay i've
calculated
an offset that i need to get to so i can
simply set
my file pointer to that offset and then
as i start to read bytes of information
in
i will read them starting at that
position and that's what we're doing
here with the seek method
so we do need to make sure that our else
is this is just um
making sure that we are not talking to
the lcd screen so we're saying to the
screen
and any writing that you're doing at the
moment
we then of obviously this
bmp file is one of the attributes
in our class and of course that
is a connection a file connection out
to the file that we're reading from the
sd card so we're setting the seek method
and we're setting its file pointer then
to the first byte
of the row of data we want
we're going to be writing individual
pixels out
to this lcd screen so to speed that up a
bit instead of
reading a pixel from the sd card then
writing it out to the lcd
and then going back and reading another
pixel from the sd card and so on
we're gonna try and do it in blocks so
we're going to read a block of pixels
from the sd card and then we're going to
write those
out to our
lcd screen so i've created a little
pixel buffer here um and a pixel buffer
counter so really what we're doing is
if i just scroll up just a wee bit
we have created a pixel buffer so that's
using these
unsigned eight bit integers okay so
a single byte of information so that's
an array
i'm doing 10 pixels at a time and of
course that means that i will need
30 bytes to store 10 pixels worth
of color data i'm then using
a pixel buffer counter which in effect
is like a file pointer
which is going to tell us where in the
buffer we're currently reading from so
initially we're going to set that to the
end of the buffer and that signifies
that the buffer is
empty so in other words we've got to the
very last
pixel in our buffer which means that
of course then that it needs to be
refilled so we come back
down to our our column for loop
okay so again we initialize our pixel
over counter to the end of the buffer so
that signifies that it is empty and what
we're saying is
if our pixel buffer counter is at the
end of the buffer
in other words we've used up all of our
pixels
then we're going to read enable another
block of pixels
so again we want to make sure that our
screen
is not being talked to we then use this
um read function now the read function
so far we've just been re
doing read with an uh brackets after it
there is an extended or an overridden
version of that read method
where we can give it an array and then a
number of pixels that we want to read
into that array
so again our pixel buffer has got 30
elements in it so we simply say
read from our file into
our pixel buffer array and then size of
pixel buffer bytes
so again that will be 30. so this will
read in
a block of 30 bytes of information into
our pixel buffer array at that point
we set our pixel buffer counter which
again is our pointer down to zero in
other words we are now
reading from the first position in our
array which of course is index number
zero
and then we turn back on the ability to
write out to
our lcd screen because most of this loop
is going to be writing to the lcd screen
just with a very short pause in that
writing process
where we read in some more pixels
so as this loop goes through
we initially fill up our buffer
then we know that the data that we read
from our sd card is in the order of a
blue
green and red pixel bytes so
the first byte we read then from our
pixel buffer is going to be the blue
information
we read it from the pointer position
and by putting plus plus after
pixel buffer counter it will perform
this
read first so we read this particular
byte of information
and then it automatically once we've
done that increments
pixel buffer counter so it points to the
next byte
we then read in the green pixel and of
course that has now been
pointed to the next byte after we've
read that we then move
on one more to the next byte and then we
read in our red byte
and after that again we move our pointer
on one
so the next time we come around we will
be ready to read in the blue one again
so we read in one pixel's worth of data
and then we write that out to our screen
so we have the special screen.right
color value
method we need to supply it with a
16-bit color value and because because
that's what this lcd screen works with
so there is a special method within the
screen driver
which is this color 565 we then give it
an
rg and b values and we tell it to output
one pixel so that's what's happening
here so
so that creates the 16-bit color value
from our 24-bit color value and we say
write that
out to the next pixel
and then our loop goes through so at
this point our pixel buffer counter
is still somewhere inside our pic it
hasn't got to the end of our pixel
buffer
so we simply then send a bite about a
byte and then
after 10 bytes we come back round
and our pixel buffer counter has now
reached the end of our buffer
and we then read in the next block of
data
okay so that lets us then work through
each row
finding the start position of that rows
color
bytes we then work all the way through
that row
outputting each individual pixel and
then we come back up
and we start the next row okay so in
this way
we are working our way from the top
of the lcd area to the bottom of the lcd
area
while simultaneously then working from
the
first um or or
the last row of data in our
file image towards the first row of
image data
right so that should give us
our our pixels coming out of course once
we've finished the last row
we need to make sure that we end the
writing process
to our lcd screen and at that point we
finished with
our file so we need to close our file
and it's always a good idea
to close your files um when you finish
working with them
so that really should let us now
write our image to the screen so if we
come back down to
our main function okay
so we have instantiated a bitmap handler
object
we are outputting these headers so we
can see what's going on
then the last thing we do of course then
is call this render image method
so our bitmap handler the bmh dot render
image
pass it in a reference to our global
variable which is
our tft handler object and then tell it
to print it
in the very top left corner so position
zero zero
so let's give that a go and see if it
works
and there we have our bitmap displaying
on our screen
so we can start to have a bit of a play
around with the scene so we know that if
we
position our image slightly away from
the edge
so that's 50 pixels across 100 pixels
down
so we've now got that larger image
positioned on the screen but being
clipped at the right
hand and bottom edges and then i've also
got
a smaller image in here so remember
these images at the moment are
are full size on the screen but i have
an image
called small
and there we have that image now
displaying completely on the screen
with of course borders then to the right
and to the bottom
so we've really got a little library
that lets us
specify a 24-bit bitmap file
position it anywhere on the screen and
it will then be drawn either full size
if there's enough room
or clip to fit onto our lcd screen so we
can then use this now
for positioning images around maybe as a
a an actual photographic background for
our control panels
and on top of that we can then draw the
needles for each of the dials and so on
or just some sort of um decoration on
our screen or some splash screens or
whatever we want to do with that
so as a last little play around i've
written a little slideshow application
so it's using pretty much the same
software i've added a few extra bits in
here where it's going to handle reading
the images
or little image file names from our sd
card
all of this code and the sketches
themselves you can download them from my
website
and i'll put links in the description
down below to the page for this project
now so do visit that to get hold of all
of this code
the the bitmap handler class is exactly
as we've developed
um in the rest of this video i did make
one little change i did say there's one
error that i had
missed in the render image
method i had only been checking for
the screen chords remember we give it
the top left corner of where we want the
image to be
i i checked for going off the right hand
edge and off the bottom
but not going off the left hand edge or
above the top so i've just added that
in there and just checking that they're
not um less than zero at either of these
values
um but the rest of it then if we come in
in our setup function so after we've
initialized all the hardware
um all i'm doing here is i'm opening up
the root folder of the sd
card and i'm using some of the
next file functions so what we can do
here is we can just open up
the sd card at the root directory that's
setting my my directory pointer to the
root directory
and then creating a file object which is
going to hold the next
file name so i'm really going through
i'm initializing my images array
so i'm creating an array which is going
to hold the file name of each of the
images that i
find and then i'm using a while loop
here so while there is a next file to
open
i check if it's a directory so if it's
not a directory
then we check to see if it contains
dot bmp in the file name
at that point it will log that in my
images array
as a file that we need to process and it
just simply loops
drawing through that so every time we
call the method open next file
that checks to see if i have another
file to work with
and again that will then create an array
of all those file names
so after that we just simply close those
files down
and then in the loop the main loop of
our sketch all we're doing is we're
keeping a little counter as to which
image we are currently showing and we're
just looping through our array
initializing our bitmap my handler to
work with that image showing it on the
screen
and then waiting for one second before
we go off and find the next image to
show
so let me just show you that working
and as our sketch runs then we get our
first image
and then we have our slideshow working
through each of the images that i've
saved
on this sd card
and we can see that with the small image
then it obviously only overwrites the
area of screen that actually contains
the image
so that's where you can start to use
background images
and then put other images over the top
or to say
if you were doing something like a
control panel you could then draw
various needles so you have a nice
display for your
control panel and then just filling in
the needles and only needing to draw
those each frame
to show your results
so that's another project then using our
arduino
our lcd screen and our sd card
and we've seen then how we can just
simply
any file that is on our sd card
is just simply a collection of bytes of
information
and we can then write some software to
look at those bytes of information
decode them and then in this particular
instance then using a bitmap image
translate that data into actual pixels
on
our lcd screen but hopefully that's
showing you that
this this idea of of using our sd card
we access it using the arduino at a very
low level and just
treat any file at all is just simply a
a block of data and providing that we
know the format of that data again we've
looked this up on wikipedia
we'll be able to interpret that and then
handle it in whatever way we want
and of course going the opposite way
creating files as long as we know that
data format
we can of course then write those bytes
out onto our sd card
in the correct format and then that will
be readable in
other machines so hopefully you've
enjoyed the video um if you have please
do consider
hitting that subscribe button down below
and turning on your notifications so
that you are notified
as soon as i create any more content
so hopefully i will see you in another
video very soon
and bye for now
don't forget to visit the course pages
for this project
there you'll be able to download the
code for this lesson and get lots of
extra hints and tips
you'll also get access to all my other
programming electronics
and gaming projects all the links are in
the description below
for more games programming electronics
projects
and retro gaming please make sure you
like this video
subscribe to my youtube channel and
visit my website
i