Experimenting external parts

You now have learned enough and can start to interface your PIC with externals parts. Without being exhaustive, this chapter explains how to use a PIC with several widely used parts, like LCD screen.

Hard Disks - IDE/PATA

IDE Paralel ATA hard disk drive tutorial

Introduction to hard disks drives

If your are like me, you have too many old hard disks laying around. I have gathered quite a collection of drives from PC's I have had in the past. Now you can dust off your drives and put them in your circuit. I have extra drives ranging in size from 171MB to 120GB.

Before you start, make sure you use a drive you do not care about. We are not responsible for your drive of the data that is on it.

You can find more general info at http://en.wikipedia.org/wiki/Parallel_ATA, and you can find more detailed technical info at http://www.gaby.de/gide/IDE-TCJ.txt

Drive Types - PATA vs SATA

There are two types of hard disks PATA (parallel ata) and SATA (serial ata). In this tutorial we will use PATA, these drives use a 40 pin IDE connector. The newer type of drive SATA has only 7 pins but there is no Jallib library for these drives at the moment. Both types of hard disks are available with massive amounts of data space.

Drive Data Size

The current jallib library will accept drives up to 128GB. The 128GB limit is due to and addressing limitation, this is the 28 bit addressing limitation.The max address you will be able to reach is hex 0xFFFFFFF. If you multiply this address by 512 bytes (1 sector) you get a max size of 137,438,952,960 bytes, yes this does equal 128GB. Eventually I may upgrade the library for 48bit addressing which will allow up to a max drive size hex 0xFFFFFFFFFFFF * 512 = 128PB (Petabytes). But now that I think about it, 128 GB should be enough!

Actual Size

The most common drive sizes today are 3.5" and 2.5". The 3.5 inch drives are commonly used in desktop computers, 2.5" drives are used in laptops. The 2.5" drives are nice for your circuit because they do not require a 12v supply voltage, and they use much less power.

If you wish to use a 2.5" laptop hard drive, you may need a 2.5" to 3.5" IDE adapter like this one:

Build a breadboard connector

Now, if your going to put one of these into your circuit, you'll need to plug the drive into your breadboard. I took a 40pin IDE connector off an old motherboard. The easiest way to get large components of a board is to use a heat gun on the bottom side of the board to melt the solder on all pins at once.

Now take this connector and stick it into some blank breadboard and add some pins. The blank breadboard I cut is 4 holes wide by 20 long. Put the connector in the middle and connect the pins on the outside, join each pin with each pin of the connector.

Of course you will also need a 40pin IDE cable, I like the ones with the notch so you don't plug it in backwards. Here's the one I made:

Circuit Power

It is very important that you have enough power to drive your circuit. Hard drives need a lot of amps to run, especially the 3.5" drives, so make sure you have a decent 5v and 12v power supply. I suggest that you DO NOT use your PC's power supply to drive your circuit. You can easily short circuit your power supply and blow up your PC. If you really insist on doing this, you better put a fuse on both 5v and 12v between your PC and your circuit. Just remember that I told you not to!

IDE Connector Pin-out

Pin 1 on the IDE cable is the red stripe. Here the pin out for the male connector I took off a motherboard:

Build the circuit

PIN FUNCTION PIN FUNCTION
1 /RESET 2 GND
3 D7 4 D8
5 D6 6 D9
7 D5 8 D10
9 D4 10 D11
11 D3 12 D12
13 D2 14 D13
15 D1 16 D14
17 D0 18 D15
19 GND 20 NO PIN
21   22 GND
23 /IOWR - READ Pin 24 GND
25 /IORD - Write Pin 26 GND
27   28 ALE - 1K resistor to 5v
29   30 GND
31   32  
33 A1 34  
35 A0 36 A2
37 /CS0 (to 5v) 38 /CS1 (to GND)
39 ACT - BUSY LED 40 GND

Build the circuit below. As you can see it is quite simple. As you can see, it only requires 3 resistors, a led and a bunch of wire. You can put a reset button on the IDE connector if you like, but I have found no use for it so I connect it direct to 5v.

Here's what the completed circuit should look like (don't turn on the power yet):

Compile and write the software to your PIC

The hard disk lib (pata_hard_disk.jal) and a sample file (16f877_pata_hard_disk.jal) will be needed for this project. You will find these files in the lib & sample directories of your jallib installation.

The most up to date version of the sample & library can be found at:

Sample file - http://jallib.googlecode.com/svn/trunk/sample/16f877a_pata_hard_disk.jal

Library file - http://jallib.googlecode.com/svn/trunk/include/external/storage/pata_hard_disk/pata_hard_disk.jal

Now lets test it and make sure it works. Compile and program your pic with 16f877_sd_card.jal from your jallib samples directory. If you are using another pic, change the "include 16f877" line in 16f877_sd_card.jal to specify your PIC before compiling.

Now that you have compiled it, burn the .hex file to your PIC with your programmer

Power It Up

Plug your circuit into your PC for serial port communication at 115200 baud rate. Now turn it on. You should get data similar to the image below onto your serial port. You will also hear the hard drive turn on and off because of one of the examples in the sample file.

Serial Port Output

Some of the data is not shown in the above image. If your disk is formatted with fat32 you may be able to see some readable data from the boot sector. On my drive formatted with fat32 I can read "Invalid partition table Error loading operating system" (not shown in the image). The AA BB CC DD EE are read/write examples that will be shown below. The last set of data is from the Identify Drive command.

You now have a working hard disk circuit!

Understand and modify the code

I will go over some of the key points you need to know about hard disk coding. Open the sample file with an editor if you have not done so already. The code in the sample file may change, therefore it may be different then what you see here. The sample file you have downloaded will always be tested and correct.

Include the chip

Select the PIC you wish to use and your clock frequency

-- include chip
include 16F877a                    -- target PICmicro
pragma target clock 20_000_000     -- oscillator frequency
-- configure fuses
pragma target OSC  HS              -- HS crystal or resonator
pragma target WDT  disabled        -- no watchdog
pragma target LVP  disabled        -- no Low Voltage Programming

Disable all analog pins and wait for power to stabilize

enable_digital_io() -- disable all analog pins if any
_usec_delay (100_000) -- wait for power to stabilize

Setup serial port and choose baud rate 115200

-- setup uart for communication
const serial_hw_baudrate  = 115200   -- set the baud rate
include serial_hardware
serial_hw_init()
Include the print library
include print       -- include the print library

Setup the hard disk library constants/settings

The registers Alternate Status, Digital Output, and Drive Address registers will only be used by advanced users, so keep the default PATA_HD_USE_CS0_CS1_PINS = FALSE

The pins /iowr, /iord, /cs0, /cs1 are active low pins that are supposed to require an inverter. If you leave PATA_HD_NO_INVERTER = TRUE, the PIC will do the inversion for you. You will most likely want to keep the default "TRUE".

-- setup hard disk library

-- uses additional code space to add a speed boost to sector_read procedures
const bit PATA_HD_READ_EXTRA_SPEED = FALSE 

-- set true if you will use Alternate Status, 
-- Digital Output or Drive Address registers
const byte PATA_HD_USE_CS0_CS1_PINS = FALSE 

-- if true, an external inverter chip is not 
-- needed on /iowr, /iord, /cs0, /cs1 pins
const bit PATA_HD_NO_INVERTER = TRUE 

Setup pin assignments

Yes, pata hard disks have a lot of pins. You will need two full 8pin port's (port B and port D of 16F877) for data transfer, three register select pins, one read pulse pin and one write pulse pin. A total of 19 io pins. I am able to comment out cs1/cs0 and save pins because of the constant we set.

-- pin assignments
alias     pata_hd_data_low              is portb   -- data port (low bits)
alias     pata_hd_data_low_direction    is portb_direction
alias     pata_hd_data_high             is portd   -- data port (high bits)
alias     pata_hd_data_high_direction   is portd_direction

alias     pata_hd_a0                    is pin_a3
alias     pata_hd_a0_direction          is pin_a3_direction
alias     pata_hd_a1                    is pin_a1
alias     pata_hd_a1_direction          is pin_a1_direction
alias     pata_hd_a2                    is pin_a0
alias     pata_hd_a2_direction          is pin_a0_direction

alias     pata_hd_iowr                  is pin_e0
alias     pata_hd_iowr_direction        is pin_e0_direction
alias     pata_hd_iord                  is pin_a4
alias     pata_hd_iord_direction        is pin_a4_direction

;alias     pata_hd_cs1                   is pin_a3
;alias     pata_hd_cs1_direction         is pin_a3_direction
;alias     pata_hd_cs0                   is pin_a4
;alias     pata_hd_cs0_direction         is pin_a4_direction

pata_hd_a0_direction = output    -- register select pin
pata_hd_a1_direction = output    -- register select pin
pata_hd_a2_direction = output    -- register select pin

pata_hd_iowr_direction = output  -- used for write pulse
pata_hd_iord_direction = output  -- used for read pulse

;pata_hd_cs1_direction = output   -- register select pin
;pata_hd_cs0_direction = output   -- register select pin

Now include the library

include pata_hard_disk           -- include the parallel ata ide hard disk library
pata_hd_init()                   -- initialize startup settings

Add a separator procedure, This will be used to display "------" onto the serial port between examples.

-- procedure for sending "-----------------" via serial port
procedure separator() is
   serial_hw_data = 13
   serial_hw_data = 10
   const byte str3[] = "---------------------------------------"
   print_string(serial_hw_data, str3)
   print_crlf(serial_hw_data)
end procedure

It is always a good idea to send something to the serial port so we know the circuit is alive. Let's send "Hard Disk Sample Started"

-- Send something to the serial port
separator()             -- send "----" via serial port
var byte start_string[] = "HARD DISK SAMPLE STARTED"
print_string(serial_hw_data,start_string)
Declare some user variables
-- variables for the sample
var word step1
var byte data

EXAMPLES

OK, now that everything is setup, we are ready for some examples.

You will find that these examples are identical to the ones in the SD Card tutorial. This makes it easy for you to switch between using a hard drive and a SD Card.

Before we get stated, you may want to get to know your hard drive and it's size. This way you will know what the maximum addressable sector is.

On newer drives, you will see on the front sticker the number of LBA's. This is the number of sectors on the drive. We must subtract one from the number of LBA's to get the highest addressable sector since the 1st sector is at address 0. My drive says "60058656" LBA's, therefore the last sector is at 60058656 - 1.

Each sector is 512 bytes, so the actual size of this drive is 60058656 * 512 = 30GB

On the sticker of some older drives, you will see CYL, HEADS,SEC/T. You can calculate the number of sectors with: (cylinders * heads * sectors per track). Then you may multiply that by 512 if you wish to get the size of the drive in bytes.

I have left a few ways to read and write to hard disks. The usage you choose may will on the PIC data space you have, and what your application is. On a smaller PIC, you will only be able to run examples #1, #2, #5 and #6. I'll explain as I go.

Example #1 - Read data at sector 0

This is a low memory usage way of reading from the hard disk, however it is slower then some of the other examples later on. This method requires the use of pata_hd_start_read(), pata_hd_data_byte, and pata_hd_stop_read(). You'll see that the usage is quite simple.

Note: The variable pata_hd_data_byte is not actually a variable, it is a procedure that looks & acts like a regular variable. This is called a pseudo variable. You may use this variable to read data or write data, as shown in these examples.

The steps are:

  1. Start reading at a sector address. In this case, sector 0 (the boot sector)
  2. Loop many times while you read data. One sector is 512 bytes, we will read two sectors.
  3. Store each byte of data into the variable "data". You can retrieve the data by reading the pseudo variable pata_hd_data_byte
  4. Do something with the data. Let's send it to the serial port.
  5. End the loop
  6. Tell the hard disk we are done reading. The hard disk light will go out at this step.
pata_hd_start_read(0)      -- get sd card ready for read at sector 0
for 512 * 2 loop           -- read 2 sectors (512 * 2 bytes)
  data = pata_hd_data_byte -- read 1 bytes of data
  serial_hw_write(data)    -- send byte via serial port
end loop
pata_hd_stop_read()        -- tell sd card you are done reading

Ok, we're done our example, so lets separate it from the next one with the separator() procedure to send some "-----" characters and a small delay.

separator()                -- separate the examples with "----"
_usec_delay(500_000)       -- a small delay

Example #2 - Writing data

This example is similar to example #1, but we will be writing data to the hard disk. It requires low memory usage. As with the first example, we will be required to use 3 procedures. pata_hd_start_write(), pata_hd_data_byte and pata_hd_stop_write()

Here are the steps:

  1. Start writing at a sector address. I choose sector 20 since it seems that it will not mess up a fat32 formatted drive, I could be wrong!
  2. Loop many times while you write your data. In this example, I am writing to 1 sector + 1/2 sector. The 2nd half of sector 2 will contain all 0's. The end of sector 2 will contain 0's because hard disks will only write data in blocks of 512, and therefore any data you have there will be overwritten.
  3. Write some data. This time we are setting the value of the pseudo variable pata_hd_data_byte. Writing to this variable will actually send data to the hard disk. We are sending "A", so you will expect to read back the same data later on.
  4. End your loop
  5. Tell the hard disk we are done writing. The hard disk light will go out at this step.
pata_hd_start_write(20)    -- get sd card ready for write at sector 20
for 512 + 256 loop         -- loop 1 sector + 1 half sector (512 + 256 bytes)
  pata_hd_data_byte = "A"  -- write 1 bytes of data
end loop
pata_hd_stop_write()       -- tell sd card you are done reading

Now of course you will want to read your data back, which will be the same as in example #1, but at sector 20.

pata_hd_start_read(20)          -- get sd card ready for read at sector 20
for 512 + 256 loop         -- loop 1 sector + 1 half sector (512 + 256 bytes)
  data = pata_hd_data_byte       -- read 1 bytes of data
  serial_hw_write(data)    -- send byte via serial port
end loop
pata_hd_stop_read()             -- tell sd card you are done reading

Example #3 - Read and write data using a sector buffer (a 512 byte array)

In this example, we will use a 512 byte array for reading and writing. This 512 byte array is called a sector buffer. This method is very fast, however it will require a PIC that can fit the 512 bytes of data in it's ram space. I find it is also easier to use. I suggest PIC18f4620 with the same schematic.

For writing, You will need only need to write data to the sector buffer array, then use the pata_hd_write_sector_address() procedure.

Lets go through the steps, first for writing data:

  1. Loop 512 times (the size of the sector buffer)
  2. Set each data byte in the array
  3. End your loop
  4. Write the data to the hard disk at a sector address.
  5. Repeat the above to write more sectors.
-- fill the sector buffer with data
for 512 using step1 loop                     -- loop till the end of the sector buffer
   pata_hd_sector_buffer[step1] = "B"        -- set each byte of data
end loop
-- write the sector buffer to sector 20
pata_hd_write_sector_address(20)

Here we will write another sector (to sector 21, the next sector)

for 512 using step1 loop                     -- loop till the end of the sector buffer
   pata_hd_sector_buffer[step1] = "C"        -- set each byte of data
end loop
-- write the sector buffer to sector 21
pata_hd_write_sector_address(21)

OK, it's time to read back the data, which is exactly the opposite of writing. For reading, we will use the pata_read_sector_addres() procedure first, then we can read data from the sector buffer array.

  1. Request data from the hard disk at a sector address.
  2. Loop 512 times (the size of the sector buffer)
  3. Send each byte to the serial port.
  4. End your loop.
  5. Repeat the above to read more sectors.
-- read back the same sectors
-- read sector 20 into the sector buffer
pata_hd_read_sector_address(20)
-- now send it to the serial port
for 512 using step1 loop                     -- loop till the end of the sector buffer
   serial_hw_write(pata_hd_sector_buffer[step1]) -- send each byte via serial port
end loop

Here we will repeat the above to read the next sector (sector 21)

-- read sector 21 into the sector buffer
pata_hd_read_sector_address(21)
-- now send it to the serial port
for 512 using step1 loop                     -- loop till the end of the sector buffer
   serial_hw_write(pata_hd_sector_buffer[step1]) -- send each byte via serial port
end loop

EXAMPLE #4 - Another method for reading and writing sectors

Example #4 is pretty straight forward. I am not going to go into too much detail on this one. It is a combination of examples 2 and 3. It is about the same speed as example #3.

-- get sd card ready for write at sector 20
pata_hd_start_write(20)
-- fill the sector buffer with data
for 512 using step1 loop                   -- loop till the end of the sector buffer
  pata_hd_sector_buffer[step1] = "D"       -- set each byte of data
end loop
-- write the sector buffer to the sd card
pata_hd_write_sector()
-- fill the sector buffer with new data
for 512 using step1 loop                   -- loop till the end of the sector buffer
  pata_hd_sector_buffer[step1] = "E"       -- set each byte of data
end loop
-- write the sector buffer to the sd card
pata_hd_write_sector()                     -- write the buffer to the sd card
-- tell sd card you are done writing
pata_hd_stop_write()
--
-- read back both of the same sectors
-- get sd card ready for read at sector 20
pata_hd_start_read(20)
-- read the sector into the sector buffer
pata_hd_read_sector()
-- now send it to the serial port
for 512 using step1 loop                   -- loop till the end of the sector buffer
  serial_hw_write(pata_hd_sector_buffer[step1]) -- send each byte via serial port
end loop
-- read the next sector into the sector buffer
pata_hd_read_sector()
-- now send it to the serial port
for 512 using step1 loop                   -- loop till the end of the sector buffer
  serial_hw_write(pata_hd_sector_buffer[step1]) -- send each byte via serial port
end loop
pata_hd_stop_read()                          -- tell sd card you are done reading

EXAMPLE #5 - Sending a command to the hard disk (Spin Up/ Spin Down)

Hard drives have other features that may be useful. In this short example, I will show how to turn on and off the hard disk motor.

To turn on/off the hard disk motor, you will be writing to the "command register", and you will be sending the "spin down" command. If you browse through the hard disk library file pata_hard_disk.jal, you will see some constants that you may use for other commands. For more information, you can read "connecting ide drives by tilmann reh" at http://www.gaby.de/gide/IDE-TCJ.txt

With this "spin down" command, you will actually hear the hard drive motor turn off

pata_hd_register_write (PATA_HD_COMMAND_REG,PATA_HD_SPIN_DOWN)    -- turn off motor

Now give some delay.

_usec_delay(5_000_000) -- 5 sec delay

Then of course turn the drive motor back on

pata_hd_register_write (PATA_HD_COMMAND_REG,PATA_HD_SPIN_UP)      -- turn on motor

EXAMPLE #6 - Identify Drive Command

The identify drive command loads 512 bytes of data for you that contains information about your drive. You can retrieve info like drive serial number, model number, drive size, number of cylinders, heads, sectors per track and a bunch of other data required by your PC. Of course you can read more info on this in "connecting ide drives by tilmann reh" at http://www.gaby.de/gide/IDE-TCJ.txt

You will have to follow these steps to receive this drive information:

  1. Send the "Identify Drive" command to the command register
  2. Wait till the hard drive is ready for you to read it
  3. read the data, and do something with it (send it to the serial port)
-- send the identify drive command
pata_hd_register_write(PATA_HD_COMMAND_REG,PATA_HD_IDENTIFY_DRIVE)

-- check if drive is ready reading and set data ports as inputs
-- this MUST be used before reading since we did not use pata_hd_start_read
pata_hd_data_request(PATA_HD_WAIT_READ)

-- Read 512 bytes
for 512 loop                            -- 256 words, 512 bytes per sector
   data = pata_hd_data_byte
   serial_hw_data = data
end loop                                -- drive info high/low bytes are in reverse order

Your Done!

That's it, Now you can read & write to all those hard drives you have laying around. You can read raw data from drives and possibly even get back some lost data.

Alright, go build that hard disk thingy you where dreaming about!

LCD Display - HD44780-compatible

In this "Step by Step" tutorial, we're going to (hopefully) have some fun with a LCD display. Particularly one compatible with HD44780 specifications, which seems to be most widely used.

Setting up the hardware

As usual, there are plenty resources on the web. I found this one quite nice, covering lots of thing. Basically, LCDs can be accessed with two distinct interfaces: 4-bit or 8-bit interfaces. 4-bit interface requires less pins (4 pins), but is somewhat slow, 8-bit interface requires more pins (8 pins) but is faster. jallib comes with the two flavors, it's up to you deciding which is most important, but usually, pins are more precious than speed, particularly when using a 16F88 which only has 16 I/O pins (at best). Since 4-bit interface seems to be most used, and we'll use this one here...

So, I've never used LCD, to be honest. Most guys consider it as an absolute minimum thing to have, since it can help a lot when debugging, by printing messages. I tend to use serial for this... Anyway, I've been given a LCD, so I can't resist anymore :)

The LCD I have seems to be quite a nice beast ! It has 4 lines, is 20 characters long.



Looking closer, "JHD 204A" seems to be the reference. Near the connector, only a "1" and a "16". No pin's name.







Googling the whole points to a forum, and at least a link to the datasheet. A section describes the "Pin Assignement". Now I'm sure about how to connect this LCD.



For this tutorial, we're going to keep it simple:
  • as previously said, we'll use 4-bit interface. This means we'll use DB4, DB5, DB6 and DB7 pins (respectively pin 11, 12, 13 and 14).
  • we won't read from LCD, so R/W pin must be grounded (pin 5)
  • we won't use contrast as well, V5 pin (or Vee) must be grounded (pin 3)

Including pins for power, we'll use 10 pins out of the 16 available, 6 being connected to the PIC (RS, EN and 4 data lines).

For convenience, I soldered a male connector on the LCD. This will help when building the whole on a breadboard.



So we now have everything to build the circuit. Here's the schematic. It also includes a LED, it will help us checking everything is ok while powering up the board.



Using a breadboard, it looks like this:







Writing the software

For this tutorial, we'll use one of the available samples from jallib repository. I took one for 16f88, and adapt it to my board (specifically, I wanted to use PORTA when connecting the LCD, and let PORTB's pins as is).

As usual, writing a program with jallib starts with configuring and declaring some parameters. So we first have to declare which pins will be connected:

 -- LCD IO definition
var bit lcd_rs           is pin_a6              -- LCD command/data select.
var bit lcd_rs_direction is pin_a6_direction
var bit lcd_en           is pin_a7              -- LCD data trigger
var bit lcd_en_direction is pin_a7_direction

var byte lcd_dataport is porta_low              -- LCD data  port
var byte lcd_dataport_direction is porta_low_direction

-- set direction
lcd_rs_direction = output
lcd_en_direction = output
lcd_dataport_direction = output

This is, pin by pin, the translation of the schematics. Maybe except porta_low. This represents pin A0 to A3, that is pins for our 4 lines interface. porta_high represents pin A4 to A7, and porta reprensents the whole port, A0 to A7. These are just "shorcuts".

We also have to declare the LCD geometry:

const byte LCD_ROWS     = 4      -- 4 lines
const byte LCD_CHARS    = 20     -- 20 chars per line

Once declared, we can then include the library and initialize it:

include lcd_hd44780_4   -- LCD library with 4 data lines
lcd_init()              -- initialize LCD

For this example, we'll also use the print.jal library, which provides nice helpers when printing variables.

include print

Now the main part... How to write things on the LCD.

  • You can either use a procedure call: lcd_write_char("a")
  • or you can use the pseudo-variable : lcd = "a"
  • lcd_cursor_position(x,y) will set the cursor position. x is the line, y is the row, starting from 0
  • finally, lcd_clear_screen() will, well... clear the screen !

Full API documentation is available on jalapi.

So, for this example, we'll write some chars on each line, and print an increasing (and incredible) counter:

const byte str1[] = "Hello world!"      -- define strings
const byte str2[] = "third line"
const byte str3[] = "fourth line"

print_string(lcd, str1)                 -- show hello world!
lcd_cursor_position(2,0)                      -- to 3rd line
print_string(lcd, str2)
lcd_cursor_position(3,0)                      -- to 4th line
print_string(lcd, str3)

var byte counter = 0

forever loop                            -- loop forever

   counter = counter + 1                -- update counter
   lcd_cursor_position(1,0)                   -- second line
   print_byte_hex(lcd, counter)         -- output in hex format
   delay_100ms(3)                       -- wait a little

   if counter == 255 then               -- counter wrap
      lcd_cursor_position(1,1)                -- 2nd line, 2nd char
      lcd = " "                         -- clear 2nd char
      lcd = " "                         -- clear 3rd char
   end if

end loop
The full and ready-to-compile code is available on jallib repository:

You'll need latest jallib-pack, available on jallib's download section.

How does this look when running ?

Here's the video !

http://www.youtube.com/watch?v=hIVMuaz8OS8

IR Ranger with Sharp GP2D02

Sharp IR rangers are widely used out there. There are many different references, depending on the beam pattern, the minimal and maximal distance you want to be able to get, etc... The way you got results also make a difference: either analog (you'll get a voltage proportional to the distance), or digital (you'll directly get a digital value). This nice article will explain these details (and now I know GP2D02 seems to be discontinued...)

Overview of GP2D02 IR ranger

GP2D02 IR ranger is able to measure distances between approx. 10cm and 1m. Results are available as digital values you can access through a dedicated protocol. One pin, Vin, will be used to act on the ranger. Another pin, Vout, will be read to determine the distance. Basically, getting a distance involves the following:

  1. First you wake up the ranger and tell it to perform a distance measure
  2. Then, for each bit, you read Vout in order to reconstitute the whole byte, that is, the distance
  3. finally, you switch off the ranger
The following timing chart taken from the datasheet will explain this better.
Figure 1. GD2D02 IR ranger : timing chart

Note: the distances obtained from the ranger aren't linear, you'll need some computation to make them so.

Sharp GP2D02 IR ranger looks like this:

  • Red wire is for +5V
  • Black wire ground
  • Green wire is for Vin pin, used to control the sensor
  • Yellow wire is for Vout pin, from which 8-bits results read
(make a mental note of this...)

Interfacing the Sharp GP2D02 IR ranger

Interfacing such a sensor is quite straight forward. The only critical point is Vin ranger pin can't handle high logic level of the PIC's output, this level mustn't exceed 3.3 volts. A zener diode can be used to limit this level.

Note: be careful while connecting this diode. Don't forget it, and don't put it in the wrong side. You may damage your sensor. And I'm not responsible for ! You've been warned... That's said, I already forgot it, put it in the wrong side, and thought I'd killed my GP2D02, but this one always got back to life. Anyway, be cautious !

Here's the whole schematic. The goal here is to collect data from the sensor, and light up a LED, more or less according to the read distance. That's why we'll use a LED driven by PWM.

Figure 2. Interfacing Sharp GP2D02 IR range : schematic

Here's the ranger with the diode soldered on the green wire (which is Vin pin, using your previously created mental note...):



I've also added thermoplastic rubber tubes, to cleanly join all the wires:



Finally, in order to easily plug/unplug the sensor, I've soldered nice polarized connectors:



Writing the program

jallib >=0.3 contains a library, ir_ranger_gp2d02.jal, used to handle this kind of rangers. The setup is quite straight forward: just declare your Vin and Vout pins, and pass them to the gp2d02_read_pins(). This function returns the distance as a raw value. Directly passing pins allows you to have multiple rangers of this type (many robots have many of them arranged in the front and back sides, to detect and avoid obstacles).

Using PWM libs, we can easily make our LED more or less bright. In the mean time, we'll also transmit the results through a serial link.

var volatile bit gp2d02_vin is pin_a4
var volatile bit gp2d02_vout is pin_a6
var bit gp2d02_vin_direction is pin_a4_direction
var bit gp2d02_vout_direction is pin_a6_direction
include ir_ranger_gp2d02
-- set pin direction (careful: "vin" is the GP2D02 pin's name,
-- it's an input for GP2D02, but an output for PIC !)
gp2d02_vin_direction = output
gp2d02_vout_direction = input

var byte measure
forever loop
   -- read distance from ranger num. 0
   measure = gp2d02_read_pins(gp2d02_vin,gp2d02_vout)
   -- results via serial
   serial_hw_write(measure)
   -- now blink more or less
   pwm1_set_dutycycle(measure)
end loop
Note: I could directly pass pin_A4 and pin_A6, but to avoid confusion, I prefer using aliases.

A sample, 16f88_ir_ranger_gp2d02.jal, is available in jallib SVN repositoryjallib released packages, and also in , starting from version 0.3. You can access downloads here.

Building the circuit on a breadboard

Building the circuit using a breadboard

I usually power two tracks on the side, used for the PIC and for the ranger:

Using the same previously created mental note, I connected the yellow Vout pin using a yellow wire, and the green Vin pin using a green wire...

Testing (and the video)

Time to test this nice circuit ! Power the whole, and check no smoke is coming from the PIC or (and) the ranger. Now get an object, like you hand, more or less closed to the ranger and observe the LED, or the serial output... Sweet !

http://www.youtube.com/watch?v=l5AZwv7LzyM

Memory with 23k256 sram

Author: Matthew Schinkel | Jallib Group

Learn how to use Microchip's cheap 256kbit (32KB) sram for temporary data storage

What is the 23k256 sram and why use it?

So, you need some data storage? Put your data on a 23k256!

If speed is your thing, this one is for you! This is FAST. According to Microchip's datasheet, data can be clocked in at 20mhz. The disadvantage to this memory however is that it will not hold it's memory when power is off since it is a type of RAM (Random Access memory).

If you wish to hold memory while power is off, you will have to go with EEPROM but it is much slower. EEPROM requires a 1ms delay between writes. In the time that I could write 1 byte to an EEPROM (1ms), I could write 2500 bytes to the 23k256 (if I can get my PIC to run fast enough).

Yet another advantage, is that it is only 8 pins (as you can see from the image). Other RAM memories have 10 or so address lines + 8 data lines. If you haven't guessed yet, we are sending serial data for reads & writes. We will be using SPI (Serial Peripheral Interface Bus).

I suggest you start by reading the SPI Introduction within this book first.

You can read more about the 23k256 here:

http://www.microchip.com/wwwproducts/Devices.aspx?dDocName=en539039

What will I learn?

We will be using the jallib sram_23k256 library & sample written by myself. With this library, we will be able to do the following:

1. Initialization settings for 23k256.

2. Read settings from the 23k256.

3. Read & Write one byte to/from a specific address

4. Use the 23k256 as a large byte, word or dword array (32k bytes, 16k words, 8k dwords)

5. Fast read/write lots of data.

OK, lets get started

I suggest you start by compiling and writing the sample file to your PIC. We must make sure your circuit is working before we continue. As always, you will find the 23k256 sample file in the sample directory of your jallib installation "16f877_23k256.jal"

You will need to modify this sample file for your target PIC.

Note: Jallib version 0.5 had this following line in the library file, but in the next version (0.6) it will be removed from the library and you will have to add it to your sample file before the line include sram_23k256.
const bit SRAM_23K256_ALWAYS_SET_SPI_MODE = TRUE

Here's the 23K256 pin-out diagram:

Now build this schematics. Notice the resistors for 5v to 3.3v conversion. Also notice that we are using the PIC's hardware SPI port pins. These pins are named (SDO, SDI, SCK) + One chip select pin of your choice.

Note: This is a 3.3V Device

Plug in your serial port and turn it on (serial baud rate 38400). If it is working, you will get mostly the hex value "11" to your PC's serial port. Here's an image from my serial port software:

If it is not working, lets do some troubleshooting. First start by checking over your schematic. If your schematic is correct, the most likely problem is voltage levels. Check over your PIC's datasheet to see what the PIN types are, if any of the pins have CMOS level outputs, you will not need voltage conversion resistors.

In the past, I have had issues with the voltage conversion resistors on the SCK line.

Setup the devices

Since the beginning initialization has already been written for you, and you already know how to include your PIC, you can skip this section and go down to the 23k256 Usage section if you wish.

Take a look at the sample file you have. As you know, firstly, we will include your chip, disable all analog pins and setup serial communication.

include 16F877a                    -- target PICmicro
--
-- This program assumes a 20 MHz resonator or crystal
-- is connected to pins OSC1 and OSC2.
pragma target clock 20_000_000     -- oscillator frequency
-- configure fuses
pragma target OSC  HS              -- HS crystal or resonator
pragma target WDT  disabled        -- no watchdog
pragma target LVP  disabled        -- no Low Voltage Programming

enable_digital_io()                -- disable analog I/O (if any)

-- setup uart for communication
const serial_hw_baudrate  = 38400   -- set the baudrate
include serial_hardware
serial_hw_init()

As stated before, the 23k256 MUST be connected to the pic's SPI port, so let's setup the SPI port as well as the SPI library. We do not need to alias SPI hardware pins to another name. First include the library, then set the pin directions for the 2 data lines and the clock line:

-- setup spi
include spi_master_hw         -- includes the spi library
-- define spi inputs/outputs
pin_sdi_direction = input    -- spi input
pin_sdo_direction = output   -- spi output
pin_sck_direction = output   -- spi clock

Now that SPI data/clock pins are setup, the only pin left to define is the 23k256 chip select pin. If you have more then one device on the SPI bus, this chip select pin setup should be done at the beginning of your program instead. This chip select pin can be any digital output pin you choose to use.

-- setup chip select pin
ALIAS sram_23k256_chip_select             is pin_a2
ALIAS sram_23k256_chip_select_direction   is pin_a2_direction
-- initial settings
sram_23k256_chip_select_direction = output    -- chip select/slave select pin
sram_23k256_chip_select = high                -- start chip slect high (chip disabled)
--

Choose SPI mode and rate. 23k256 uses SPI mode 1,1

We will start with peed SPI_RATE_FOSC_16. (oscillator/16). These are the speeds that are available:

SPI_RATE_FOSC_4 -- Fastest

SPI_RATE_FOSC_16 -- Mid speed

SPI_RATE_FOSC_64 -- Slower

SPI_RATE_TMR -- Use timer

spi_init(SPI_MODE_11,SPI_RATE_FOSC_16) -- init spi, choose mode and speed

This line tells the PIC to set the SPI mode before each read & write. If you have multiple devices on the SPI bus using different modes, you will need to set this to TRUE

const byte SRAM_23K256_ALWAYS_SET_SPI_MODE = TRUE

Now we can finally include the library file, and initialize the chip:

include sram_23k256 -- setup Microchip 23k256 sram
sram_23k256_init(SRAM_23K256_SEQUENTIAL_MODE, SRAM_23K256_HOLD_DISABLE)  -- init 23k256 in sequential mode

23k256 Usage

I'm going to go over this quickly since the code is simple.

Read & Write Byte

Write hex "AA" to address 1:

sram_23k256_write(1,0xAA)   -- write byte

Now read it back:

var byte data
sram_23k256_read(1, data) -- read byte

Byte Array

You can use the 23k256 as a large byte, word or dword array like this:

-- Example using 23k256 as a 32KByte array (at array address 2)
var byte data1
sram_23k256_byte[2] = 0xBB   -- set array byte 2 to value 0xBB
data1 = sram_23k256_byte[2]  -- read array byte 2, data2 should = 0xBB

-- Example using 23k256 as a 16K word array
var word data2
sram_23k256_word[3] = 0xEEFF     -- set array word 3 to value 0xEEFF
data2 = sram_23k256_word[3]      -- read array word 3, data2 should = 0xEEFF

-- Example using 23k256 as a 8K dword array
var dword data3
sram_23k256_dword[3] = 0xCCDDEEFF -- set array dword 3 to value 0xCCDDEEFF
data3 = sram_23k256_dword[3]      -- read array dword 3, data2 should = 0xCCDDEEFF

If you are looking for a quick way to write lots of data, you can use the start_write, do_write and stop_write procedures. You should not use any other SPI devices on the same SPI bus between start_write() and stop_write()

sram_23k256_start_write (word in address) -- sets the address to write to

sram_23k256_do_write (byte in data) -- send the data

sram_23k256_stop_write() -- stops the write process

Here's an example:

-- Example fast write lots of data
sram_23k256_start_write (10)
for 1024 loop
  sram_23k256_do_write (0x11)
end loop
sram_23k256_stop_write()

This works the same for the read procedures:

sram_23k256_start_read (word in address) -- sets the address to read from

sram_23k256_do_read (byte out data) -- get the data

sram_23k256_stop_read() -- stop the read process

-- Example fast read lots of data
sram_23k256_start_read (10)
for 1024 loop
  sram_23k256_do_read (data1)
  serial_hw_write (data1)
end loop
sram_23k256_stop_read()

Your done, enjoy!

RC Servo Control & RC Motor Speed Control

PIC RC servos and RC speed controllers used in the Radio Control hobby.

Servo Control Intro

RC or R/C (Radio Control) servos and RC motor speed controllers are used in the radio control hobby to control things like RC Cars, RC airplanes, boats, robots, etc.

Servos are used for there positioning capability and strength. Small, regular sized servos can be bought at a local hobby store for $10 or less. Of course there are more expansive ones depending on the quality and size. These servos normally plug into your radio control receiver, but today we will connect it to your PIC.

I will mostly be talking about RC servos, but you will also be able to connect a RC speed controller since they use the same technology. These speed controllers are made up of power MOS FETs to allow 12v at 50A+ to control the speed of a motor via PWM.

The only way to really know how something really works is to take it apart! I found some gears, a potentiometer some electronics and a motor. The servos gears are to give it the strength it needs to move whatever it is you want to move in your project. The servo knows it's position by reading a voltage off the potentiometer that gets turned by the gears which of course gets turned by the motor. After a signal is given, the servo will move to the correct location.

To control a servo, we need to send it a PWM (pulse width modulation) signal. Thankfully it will all be taken care of by the Jallib library I have created. A pulse will be sent every 20ms, and each pulse will be a width between 0.5ms and 2.5ms. The pulse width will vary depending on the position you have chosen. Servo pulse width required can very depending on the servo manufacturer, therefore the library has been created with some default values that you may change to get a full movement from left to right.

Here's a YouTube video of one servo moving and it's signal on my oscilloscope: http://www.youtube.com/watch?v=zA3anG0YZD4

Servos come with a verity of connector types but always have 3 wires. One is ground, one is power and the other is signal. Take a look at this guys connector pinouts:

http://www.horrorseek.com/home/halloween/wolfstone/Motors/svoint_RCServos.html

Here's an image of my RC servo connector (left), I will use some pins (right), to plug my servo into my breadboard.

There are two ways of implementing servos into your project. You may either have your servos connected to your main PIC, or to an external PIC. For smaller projects you will choose to control your servo from your main PIC, this is the method I will show you.

If your main PIC is needed to run some heavy code, or if you need more then 24 servos, you may wish to use external PIC(s) via I2C interface. There is a library and two samples for using an external PIC via I2c bus. I will not discuss this method here.

Servo control via your main PIC

This method will allow you to plug up to 24 servos into your main PIC. Any digital capable I/O pin on your PIC should be able to run your servo signal, always lookup your pin in the datasheet. Use a pull up resistor on open drain pins. You will need to choose a PIC with a hardware timer, 8 servos can run on each timer module.

The library supports timer0, timer1 and timer3. You can do a quick search for "timer0 module" in your datasheet to see if you have a timer, most PICs do have at least one timer. The library will give you an error if you if you do not have a timer when you try to compile your code.

I have chosen 18F4620 (3 timers), but I have also tested it on 16f877a (2 timers), and 18f452(3 timers) with the same schematic.

Build your circuit

The schematic is very very simple, just take your blink circuit and plug in your servo.

The Code

Since your main PIC will be controlling the servos, the PIC is acting as a master device and therefore we will be using the servo_rc_master library.

The library can be found under Jallib SVN at trunk\include\external\motor\servo\servo_rc_master.jal

The sample can be found in the Jallib SVN at trunk\sample\18f4620_servo_rc_master.jal

You can access the Jallib SVN at http://code.google.com/p/jallib/source/browse/

Let's start by including your PIC, and disable all analog pins

-- include chip
include 18f4620                    -- target PICmicro
pragma target clock 20_000_000     -- oscillator frequency
-- configuration memory settings (fuses)
pragma target OSC  HS              -- HS crystal or resonator
pragma target WDT  disabled        -- no watchdog
pragma target LVP  disabled        -- no Low Voltage Programming
pragma target MCLR external        -- reset externally

enable_digital_io()                -- disable analog I/O (if any)

This sample file will require 1 led, so define it now

-- led definition
alias led                is pin_a0
alias led_direction      is pin_a0_direction
--
led_direction = output

Now you may define the pins that will be used for each of your servos, I have defined 8 although I have not connected them all in my circuit.

-- setup servo pins
alias servo_1             is pin_b0
alias servo_1_direction   is pin_b0_direction
servo_1_direction = output
--
alias servo_2             is pin_b1
alias servo_2_direction   is pin_b1_direction
servo_2_direction = output
--
alias servo_3             is pin_b2
alias servo_3_direction   is pin_b2_direction
servo_3_direction = output
--
alias servo_4             is pin_b3
alias servo_4_direction   is pin_b3_direction
servo_4_direction = output
--
alias servo_5             is pin_b4
alias servo_5_direction   is pin_b4_direction
servo_5_direction = output
--
alias servo_6             is pin_b5
alias servo_6_direction   is pin_b5_direction
servo_6_direction = output
--
alias servo_7             is pin_b6
alias servo_7_direction   is pin_b6_direction
servo_7_direction = output
--
alias servo_8             is pin_b7
alias servo_8_direction   is pin_b7_direction
servo_8_direction = output
--
-- commenting out 9th servo
;alias servo_9             is pin_a0
;alias servo_9_direction   is pin_a0_direction
;servo_9_direction = output

Here we will define the min & mas movement. These values can be changed to limit the amount your servos can move. We will talk about this in detail later on, this is an important step. Changing these values will change the pulse width for all servos.

-- choose min & max servo movment / pulse size
const byte SERVO_MIN   = 50  -- default is 50  (0.5ms min pulse)
const byte SERVO_MAX   = 255 -- default is 255 (2.5ms max pulse)

Choose the timers your PIC will be using to control your servos. Each timer will take care of 8 servos. I have defined 8 servos, so I need only 1 timer. I have commented out the other 2 timers that I may use later on.

-- choose pic internal timers
const byte SERVO_USE_TIMER = 0            -- timer for servo's 1 to 8
;const byte SERVO_9_TO_16_USE_TIMER = 1    -- timer for servo's 9 to 16
;const byte SERVO_17_TO_24_USE_TIMER = 3   -- timer for servo's 17 to 24

I may now include the servo_rc_master library, and initialize the servos. Within the init() procedure, all servos will be centered.

include servo_rc_master -- include the servo library
servo_init()

If you wish to turn off a servo at any point in your program. This will turn off the servos motor by keeping the signal line low. You may set or unset the on/off bit for any servo as follows:

-- use this to turn off a servo
;servo_1_on = FALSE

Sometimes the servo you have may move in the opposite direction that you would like it to, so here you have an option of switching a servos direction. I have also noticed that some types of servos move in the reverse of others.

-- use this to reverse a servo
;servo_1_reverse = TRUE

The init procedure does center all servos, but you may want to start your servo at another location. Since I did not leave any delay yet, the servos did not actually have time to move to there center position.

I am going to center the servos again to show you an example of the correct way to move a servo. After I give the servos there move position, I will wait for 1sec so they have time to move to center.

You can use various delays or move in increments to slow the servo movement speed. 127 is center.

-- example center all servos
servo_move(127,1) -- center servo 1
servo_move(127,2) -- center servo 2
servo_move(127,3) -- center servo 3
servo_move(127,4) -- center servo 4
servo_move(127,5) -- center servo 5
servo_move(127,6) -- center servo 6
servo_move(127,7) -- center servo 7
servo_move(127,8) -- center servo 8
;servo_move(127,9)

_usec_delay (1_000_000) -- wait for servos to physically move

Now I will create my main loop and have 2 of the servos move to various positions.

-- example moving servos one and two and blink led
forever loop

   servo_move(255,1)
   servo_move(0,2)
   _usec_delay (1_000_000)
   led = !led

   servo_move(127,1) ;servo 1 centered
   servo_move(127,2) ;servo 2 centered
   _usec_delay (1_000_000)
   led = !led

   servo_move(0,1)
   servo_move(255,2)
   _usec_delay (1_000_000)
   led = !led

   servo_move(127,1) ;servo 1 centered
   servo_move(127,2) ;servo 2 centered
   _usec_delay (1_000_000)
   led = !led
end loop

So, that's it for the code. Simple right? I wish writing the library was that easy!

You can go ahead and turn on your circuit, you should see the led blink and the servos should be moving. Change and test your servo pinouts if needed. The two servos will move in opposite directions since my servo_move() procedure call values are different for each servo.

At this point, you should turn off the power when your servos are at the center position. We are turning the power off so you may remove the moving part on the top of your servo, and place it back on so it looks centered.

Here's a YouTube video of my two moving servos http://www.youtube.com/watch?v=QS8M07uuagY

Setting Your Servo Max & Min Movements

For my projects, I feel that it is important to set the servo min/max values. You may choose to either use the default values that I have set, or set your own. There are two reasons for setting these values correctly:

  1. You can get more movement out of your servo (far right to far left)
  2. You do not want your servo to try to move out of it's range. If your servo moves out of it's range for a long period of time, you may burn out the servos motor.

All manufacturers create there servos differently, there is no official specification for RC servos (that I know of).

So here are the steps:

  1. Set SERVO_MIN = 0 and SERVO_MAX to 255
  2. Set your servo_min values to restrict movement to one side. Directly after you call servo_init(), you should place this code:
    servo_move(0,1)
    forever loop
    end loop

    This will move your servo all the way to one side. You will hear your servo motor being ON all the time (not good for the motor).

  3. Now run your circuit and gradually increase servo_min value so the servo is at the correct location on one side. Try to get the servo to be 1mm from it's min location. You should not hear the motor running.
  4. Repeat step 2 by decreasing servo_max but use this:
    servo_move(255,1)
    forever loop
    end loop

Well, looks like your all set. I hope your having fun with Jalv2/Jallib!

SD Memory Cards

In this tutorial we will learn how to use an SD Card for mass data storage.

SD Card Introduction

SD Cards (Secure Digital Cards) are quite popular these days for things like digital camera's, video camera's, mp3 players and mobile phones. Now you will have one in your project! The main advantages are: small size, large data storage capability, speed, cost. It has flash storage that does not require power to hold data. The current version of the sd card library that we will be using in this tutorial works with "standard capacity" sd cards up 4gb in size. I hope to find time to add "high capacity" and "extended capacity" capability to the library.

SD Card have 2 data transfer types "SD Bus" and "SPI Bus". Most PIC's have an SPI port. The "SD Bus" is faster, however uses more pins. We will be using SPI in our circuit. For more info on SPI read the tutorial in this book: SPI Introduction. The SPI mode for SD Cards is 1,1.

We are not responsible for your data or SD card. Make sure you have nothing important on your SD card before you continue.

These SD Cards are 3.3v devices, therefore a 5v to 3v conversion is needed between the PIC and the sd card. We will use resistors to do the conversion, however there are many other methods. See http://www.microchip.com/3v/ for more information.

My favorite way of converting 5v to 3v is with 74HCT08. It must be HCT (not LS). 74LS08 will not work on a 3.3v power supply.

This circuit will use 16F877 If you are using a different PIC for your project, refer to the PIC's datasheet for pin output levels/voltage. For example, 18F452 has many pins that are 5v-input that give 3v-output. These pins show as "TTL / ST" - TTL compatible with CMOS level outputs in the datasheet and they will not require any voltage conversion resistors. If you are not sure, set a pin as an output, and make it go high then test with a volt meter.

Build a SD Card Slot

Before we can build our circuit, we will need to find ourselves an sd card slot that can plug into our breadboard. You can find pre-made sd card slots on ebay and other places around the net. It is quite easy to make your own anyways. I took one out of a broken digital camera and placed it on some blank breadboard and soldered on some pins. Here are some images of my sd card holder:

Build the circuit

Follow this schematic for 16f877, if you are using another PIC, check the pin-outs for the SPI bus. The pin-outs of your pic will show SDI, SDO, SCL and SS. The pin SS is the chip select pin, you can use any pin for it but the others must stay the same.

Compile and write the software to your PIC

With the use of the sd card lib (sd_card.jal) and a sample file 16f877a_sd_card.jal, we can easily put one in our own circuit for mass data storage! You will find these files in the lib & sample directories of your jallib installation.

The most up to date version of the sample & library can be found at:

Sample file - http://jallib.googlecode.com/svn/trunk/sample/16f877a_sd_card.jal

Library file - http://jallib.googlecode.com/svn/trunk/include/external/storage/sd_card/sd_card.jal

Now that our circuit is built, lets test it and make sure it works before we continue with more details. Compile and program your pic with 16f877a_sd_card.jal from your jallib samples directory. If you are using another pic, change the "include 16f877a" line in 16f877a_sd_card.jal to specify your PIC before compiling.

Now that you have compiled it, burn the .hex file to your PIC

Power It Up

Plug your circuit into your PC for serial port communication at 38400 baud rate. Now turn it on. Press the reset button in your circuit, you should get a result similar to this:

Serial Output

As you can see from the image, we got some actual readable data off the sd card as well as a bunch of junk. My sd card is formated with fat32, this is why I can actually read some of the data.

You now have a working sd card circuit!

Understand and modify the code

I'm just going to quickly go over some of the key points you need to know about sd cards. Open the sample file with an editor if you have not done so already.

The code in the sample file may change, therefore it may be different then what you see here. The sample file you have downloaded will always be tested and correct.

Include the chip

Specify the PIC you wish to use as well as your clock frequency

include 16f877a
--
pragma target OSC HS               -- HS crystal or resonator
pragma target clock 20_000_000     -- oscillator frequency
--
pragma target WDT  disabled
pragma target LVP  disabled

Disable all analog pins and wait for power to stabilize

enable_digital_io() -- disable all analog pins if any
_usec_delay (100_000) -- wait for power to stabilize

Setup serial port and choose baud rate 115200

-- setup uart for communication
const serial_hw_baudrate  = 115200   -- set the baud rate
include serial_hardware
serial_hw_init()

Include the print library

include print       -- include the print library

Setup SPI Settings - The data transfer bus.

Here you may change the chip select pin "pin_SS" and "pin_SS_direction" to another pin. SDI, SDO and SCK must stay the same for the SPI hardware library.

You may notice that we are not defining/aliasing pins sdi, sdo and sck. We do not need to define them with a line like "alias pin_sdo is pin_c5" because they are set within the PIC and cannot be changed. If we use the SPI hardware library, we must use the SPI hardware pins. We only need to define there direction like this "pin_sdo_direction = output".

You may also choose the SPI rate. According to the SPI hardware library, you can use SPI_RATE_FOSC_4 SPI_RATE_FOSC_16, SPI_RATE_FOSC_64 or SPI_RATE_TMR. The fastest is FOSC_4 (oscillator frequency / 4). For the fastest speeds, it is a good idea to keep your SD Card as close to the PIC as possible.

include spi_master_hw         -- includes the spi library
-- define spi inputs/outputs
pin_sdi_direction = input    -- spi input
pin_sdo_direction = output   -- spi output
pin_sck_direction = output   -- spi clock
--
spi_init(SPI_MODE_11,SPI_RATE_FOSC_4) -- init spi, choose mode and speed

alias sd_chip_select is pin_a5
alias sd_chip_select_direction is pin_a5_direction
sd_chip_select_direction = output

Setup the SD Card library and settings

Select sd card settings & Include the library file, then initalize the sd card.

Some sd cards may require a 10ms (or more) delay every time you stop writing to the sd card, you can choose weather or not to have this delay. If you are doing many small writes and are worried about speed, you may set SD_DELAY_AFTER_WRITE to "FALSE".

-- setup the sd card
const bit SD_ALWAYS_SET_SPI_MODE = TRUE
const bit SD_DELAY_AFTER_WRITE = TRUE
include sd_card              -- include the sd card ide hard disk library
sd_init()                    -- initialize startup settings

Add a separator procedure, This will be used to display "------" onto the serial port between examples.

-- procedure for sending 80 "-----------------" via serial port
procedure seperator() is
   serial_hw_data = 13
   serial_hw_data = 10
   const byte str3[] = "--------------------------------------------------------------------------------"
   print_string(serial_hw_data, str3)
   print_crlf(serial_hw_data)
end procedure

It is always a good idea to send something to the serial port so we know the circuit is alive. Let's send "Hard Disk Sample Started"

-- Send something to the serial port
seperator()             -- send "----" via serial port
var byte start_string[] = "SD CARD SAMPLE STARTED"
print_string(serial_hw_data,start_string)

Declare some user variables

-- variables for the sample
var word step1
var byte data

EXAMPLES

OK, now that everything is setup, we are ready for some examples. I have left a few ways to read and write to SD Cards. The usage you choose may will on the PIC data space you have, and what your application is. On a smaller PIC, you will only be able to run examples #1, #2, #5 and #6. I'll explain as I go.

You will find that these examples are identical to the ones in the hard disk tutorial. This makes it easy for you to switch between using a SD Card and a hard disk.

Example #1 - Read data at sector 0

This is a low memory usage way of reading from the sd card, however it is slower then some of the other examples later on. This method requires the use of sd_start_read(), sd_data_byte, and sd_stop_read(). You'll see that the usage is quite simple.

Note: The variable sd_data_byte is not actually a variable, it is a procedure that looks & acts like a regular variable. This is called a pseudo variable. You may use this variable to read data or write data, as shown in these examples.

The steps are:

  1. Start reading at a sector address. In this case, sector 0 (the boot sector)
  2. Loop many times while you read data. One sector is 512 bytes, we will read two sectors.
  3. Store each byte of data into the variable "data". You can retrieve the data by reading the pseudo variable sd_data_byte
  4. Do something with the data. Let's send it to the serial port.
  5. End the loop
  6. Tell the sd card we are done reading.
sd_start_read(0)           -- get sd card ready for read at sector 0
for 512 * 2 loop           -- read 2 sectors (512 * 2 bytes)
  data = sd_data_byte      -- read 1 bytes of data
  serial_hw_write(data)    -- send byte via serial port
end loop
sd_stop_read()             -- tell sd card you are done reading

Ok, we're done our example, so lets separate it from the next one with the separator() procedure to send some "-----" characters and a small delay.

separator()                -- separate the examples with "----"
_usec_delay(500_000)       -- a small delay

Example #2 - Writing data

This example is similar to example #1, but we will be writing data to the sd card. It requires low memory usage. As with the first example, we will be required to use 3 procedures. sd_start_write(), sd_data_byte and sd_stop_write()

Here are the steps:

  1. Start writing at a sector address. I choose sector 20 since it seems that it will not mess up a fat32 formatted sd card, I could be wrong!
  2. Loop many times while you write your data. In this example, I am writing to 1 sector + 1/2 sector. The 2nd half of sector 2 will contain all 0's. The end of sector 2 will contain 0's because SD Cards will only write data in blocks of 512, and therefore any data you have there will be overwritten.
  3. Write some data. This time we are setting the value of the pseudo variable pata_hd_data_byte. Writing to this variable will actually send data to the hard disk. We are sending "A", so you will expect to read back the same data later on.
  4. End your loop
  5. Tell the SD Card we are done writing.
sd_start_write(20)         -- get sd card ready for write at sector 20
for 512 + 256 loop         -- loop 1 sector + 1 half sector (512 + 256 bytes)
  sd_data_byte = "A"       -- write 1 bytes of data
end loop
sd_stop_write()            -- tell sd card you are done reading

Now of course you will want to read your data back, which will be the same as in example #1, but at sector 20.

sd_start_read(20)          -- get sd card ready for read at sector 20
for 512 + 256 loop         -- loop 1 sector + 1 half sector (512 + 256 bytes)
 data = sd_data_byte       -- read 1 bytes of data
  serial_hw_write(data)    -- send byte via serial port
end loop
sd_stop_read()             -- tell sd card you are done reading

Example #3 - Read and write data using a sector buffer (a 512 byte array)

In this example, we will use a 512 byte array for reading and writing. This 512 byte array is called a sector buffer. This method is very fast, however it will require a PIC that can fit the 512 bytes of data in it's ram space. I find it is also easier to use. I suggest PIC18f4620 with the same schematic.

For writing, You will need only need to write data to the sector buffer array, then use the sd_write_sector_address() procedure.

Lets go through the steps, first for writing data:

  1. Loop 512 times (the size of the sector buffer)
  2. Set each data byte in the array
  3. End your loop
  4. Write the data to the hard disk at a sector address.
  5. Repeat the above to write more sectors.
-- fill the sector buffer with data
for 512 using step1 loop                     -- loop till the end of the sector buffer
   sd_sector_buffer[step1] = "B"             -- set each byte of data
end loop
-- write the sector buffer to sector 20
sd_write_sector_address(20)

Here we will write another sector (to sector 21, the next sector)

for 512 using step1 loop                     -- loop till the end of the sector buffer
   sd_sector_buffer[step1] = "C"             -- set each byte of data
end loop
-- write the sector buffer to sector 21
sd_write_sector_address(21)

OK, it's time to read back the data, which is exactly the opposite of writing. For reading, we will use the pata_read_sector_addres() procedure first, then we can read data from the sector buffer array.

  1. Request data from the SD Card at a sector address.
  2. Loop 512 times (the size of the sector buffer).
  3. Send each byte to the serial port.
  4. End your loop.
  5. Repeat the above to read more sectors.
-- read back the same sectors
-- read sector 20 into the sector buffer
sd_read_sector_address(20)
-- now send it to the serial port
for 512 using step1 loop                     -- loop till the end of the sector buffer
   serial_hw_write (sd_sector_buffer[step1]) -- send each byte via serial port
end loop

Here we will repeat the above to read the next sector (sector 21)

-- read sector 21 into the sector buffer
sd_read_sector_address(21)
-- now send it to the serial port
for 512 using step1 loop                     -- loop till the end of the sector buffer
   serial_hw_write (sd_sector_buffer[step1]) -- send each byte via serial port
end loop

EXAMPLE #4 - Another method for reading and writing sectors

Example #4 is pretty straight forward. I am not going to go into too much detail on this one. It is a combination of examples 2 and 3. It is about the same speed as example #3.

-- get sd card ready for write at sector 20
sd_start_write(20)
-- fill the sector buffer with data
for 512 using step1 loop                   -- loop till the end of the sector buffer
  sd_sector_buffer[step1] = "D"            -- set each byte of data
end loop
-- write the sector buffer to the sd card
sd_write_sector()
-- fill the sector buffer with new data
for 512 using step1 loop                   -- loop till the end of the sector buffer
  sd_sector_buffer[step1] = "E"            -- set each byte of data
end loop
-- write the sector buffer to the sd card
sd_write_sector()                          -- write the buffer to the sd card
-- tell sd card you are done writing
sd_stop_write()
--
-- read back both of the same sectors
-- get sd card ready for read at sector 20
sd_start_read(20)
-- read the sector into the sector buffer
sd_read_sector()
-- now send it to the serial port
for 512 using step1 loop                   -- loop till the end of the sector buffer
  serial_hw_write(sd_sector_buffer[step1]) -- send each byte via serial port
end loop
-- read the next sector into the sector buffer
sd_read_sector()
-- now send it to the serial port
for 512 using step1 loop                   -- loop till the end of the sector buffer
  serial_hw_write(sd_sector_buffer[step1]) -- send each byte via serial port
end loop
sd_stop_read()                             -- tell sd card you are done reading

Now you can put whatever you want on your SD Card, or possibly read lost data off of it.

If you want to read files stored on the card by your PC, there is a FAT32 library and there will be a tutorial soon so you can easily browse, read and write to files and folders stored on your card.

What are you waiting for, go build something cool!

Sources

The Jallib SD Card Library - Written by Matthew Schinkel

SanDisk Secure Digital Card - http://www.cs.ucr.edu/~amitra/sdcard/ProdManualSDCardv1.9.pdf