Monday, December 29, 2008
There are probably no JAL users that have not used delay.jal - the jallib library that let your program wait for a specified amount of time. Very usefull in most situations, but has one limitation: it blocks your program so can't do anything else while you are waiting.
Some applications need to take care of multiple tasks at the same time. Imagine a small robot, which has just decided it wants to move forward for 3 seconds. But while doing so, it has to guard it's front whiskers in case it runs into an object. A call like:
would not allow this. This case requires a non-blocking delay, like the one provided by timer0_isr_interval.jal that generates an interrupt at a regular interval and has an ISR (Interrupt Service Routine) that deals with the delay you require.
The setup of this library is straight-forward. First you define the interval of the interrupt you want. With a higher value, more interrupts are generated. This gives a higher resolution of your delay, but also puts a high background load on your PIC. Be carefull if you go beyond 1000 (1kHz). The lowest possible rate depends on the clock frequency you use and is 77 with an 16f877 on 20 MHz.
const timer0_isr_rate = 1000 -- 1 kHz isr rate
Next, you need to specify the number of slots. A slot is used to store the end-time of a delay-period so you need one slot for each concurrent delay.
const DELAY_SLOTS = 2 -- support 2 delays at the same time
Now, include the library and call it's init function:
timer0_isr_init() -- init timer0 isr
Now we are ready to use the delay functions. To demonstrate it's use, we take two LEDs and let them blink at their own interval:
if (check_delay(0)) then
set_delay(0, 409) -- 409 ticks on delay-slot 0
led = !led
if (check_delay(1)) then
set_delay(1, 619) -- 619 ticks on delay-slot 1
led2 = !led2
It's that simpe! check_delay() takes a slot number as a param and returns true if the delay has expired and false if it has not. When it has expired, the next delay is set with set_delay(). Note that the delay-parameter of this procedure is in 'ticks' - the number of times an interrupt occurs. In this example, we set the interrupt interval at 1000, so one tick is 1 milisecond.
And then the only task left is to invert the led.
In the above example, there are two LEDs blinking at a different (prime number) interval and it takes over 250.000 ticks for the pattern to repeat itself.
Before I get to the conclusion of this article, there is one usefull feature I'd like to show you. The ISR also has (word) counter that is incremented at the tick rate. This is usefull if you want to execute tasks at isr interval, like scanning a keyboard:
if (isr_counter != prev_isr_counter) then
prev_isr_counter = isr_counter
-- put here code that you want
-- to execute each tick.
And if you want an other rate than every tick, use a delay slot!
We've seen an easy way to keep track of multiple delays. It enables you to create applications with complex timing. It also enables you to guard responses and act if there is none within the time set. And you can stop checking a delay or set it again (based on the time it is set again) when you feel like it.
But make sure you go trough you main loop at a fast rate and don't use the classic delay() functions. A blinking led in the main loop helps to detect blocking code that prevents execution of the main loop at a proper rate.
The timer0_interval.jal samples of jallib provides a working example for the code shown.
Saturday, December 13, 2008
There is a lot of info on serial port hardware setup on the Internet. To get started it is good to know:
- PC's all used to have serial ports with 9-pin male sub-D connectors. Today, standard serial ports become less common, especially on laptop computers. In case your PC does not have a serial port, get an USB to serial convertor.
- Pin 2 and 3 of this connector contain the send and receive signal of the serial port. Pin 5 is ground.
- The voltage on such a serial port range from +/- 3V to +/- 12V, wich is not compatible with the 0/5V range of PIC controlers. Use a 'level shifter' (often based on a max232 or alike) to convert the signal.
- The output of the level shifter is connected to the RX and TX pin of the PIC.
If you need more info on this, Google is your friend!
Now you have the hardware ready, you need to configure the Jallib serial_hardware lib, that uses the build-in USART to send and receive characters.
First, we have to set the desired speed:
const serial_hw_baudrate = 115_200
Next, include the library and call init:
Now you are ready to use the serial interface. So you can send a char like:
or use the pseudo-var interface to do the same:
serial_hw_data = "!"
To read a char, you can use the function serial_hw_read(). If there is a character waiting, it puts the char into the parameter var and returns true. If there is no character waiting, it returns false. So:
if serial_hw_read(char) then -- non-blocking
serial_hw_write(char) -- that's the echo...
end ifecho's back each character it receives. Be sure to call serial_hw_read often enough so no characters are lost.
An alternative way to receive characters is the pseudo-var:
char = serial_hw_data -- blocking receive
serial_hw_data = char
The first line puts the received character in variable char and the next echoes it back.
We've seen above that sending by the pseudo var is the same as calling the function. When receiving, there is a major difference: where there is no character waiting, function serial_hw_read immediately returns and program execution continues. This is called non-blocking.
When there is no character waiting when the pseudo-var is used, the program will wait until a valid character is received. So if it does not receive anything, it will wait forever!
How to continue
We've seen how to setup a hardware serial port, send characters and receive characters - either blocking or non-blocking. The test-program sample_serial_hardware.jal, provides a working example for the code shown.
Friday, December 12, 2008
After the blink example, follow the steps:
First you have to setup the i2c bus hardware.
The first thing to concider is if you can and want to use the MSSP to handle i2c (i2c hardware) or handle i2c in software.
The first option - i2c hardware - is possible for most (but not all) PICs with MSSP. The advantage is that this option is generally faster then software i2c. If you choose i2c hardware, you will use the pre-defined i2c clock and i2c data pins. (scl and sda).
With i2c in software, pick any free io pin for i2c clock and i2c data.
Now you have choosen the pins, you need to setup the hardware. Connect clock, data and ground from the PIC to the i2c slave, place pull-up resistors (e.g. 1k5) from both clock and data to the pic power supply and power the slave device. For more details on this, check the i2c slave datasheet or look on the Internet.
Now you have the hardware ready, you need to configure the software.
First, define the pins used, for example on an 16F877a:
var volatile bit i2c_scl is pin_c3
var volatile bit i2c_scl_direction is pin_c3_direction
var volatile bit i2c_sda is pin_c4
var volatile bit i2c_sda_direction is pin_c4_direction
Second, define the next two constants:
const word _i2c_bus_speed = 1 ; * 100kHz
const bit _i2c_level = true ; i2c levels (not SMB)
And now you can include the 'level0' i2c library. This is the library that creates the i2c signals and comes in two flavors:
Both libraries have the same interface to send and receive bytes. You can build complex messages with this interface but in most cases, it is easier to use the level1 layer. To use this, define an array for transmit and an array for receive and include the library:
var byte i2c_tx_buffer
var byte i2c_rx_buffer
The size of the two buffers are the maximum size of a message you will send or receive. When a buffer is too short, you might get compile errors or - worse - unexpected behavior. When the buffers are too long, memory will be exhausted faster. When in doubt: enlarge the buffer a few bytes!
Read from an i2c eeprom
Now we are ready to communicate with the i2c slave. The format of the messages depend on the device, so you should consult the slave's documentation. There is an example below of communicating with an i2c eeprom (24lc256).
First thing to know of a slave is it's address. An i2c address is 7 bits and is stored in the higher 7 bits of a byte. The lowest bit is set to zero. The i2c eeprom address is 0xA0 (160 decimal).
Now we want to read data from the eeprom. Let's assume we want to start at internal (eeprom) location 1234 and need 3 bytes. From the 24lc256 datasheet, we learn that we have to write a 2-byte (= one word) location. Subsequently, we can read data. Or in JAL:
-- Send word location to device 0xA0 and read 3 bytes data
r = i2c_receive_wordaddr(0xA0, 1234, 3)
serial_hw_data = " "
serial_hw_data = " "
So only one call to i2c_receive_wordaddr to do the i2c write and read! This function takes 3 params: the i2c slave device address, the value of the 2-byte location code and the number of byte to read after the location code is sent.
The result is stored in i2c_rx_buffer and is printed on the serial port by the example. (Have a look at 'print_serial_numbers.jal' for more details on the use of serial comms.)
You might have notices the return value r in the sample above. This bit value is true if the operation was succesfull. If false, communication has failed. Add code to check the return value and handle errors in your program!
i2c_receive_byteaddr is a similar function which uses a 1-byte location code.
Arbitrary location code lengths can be sent by using i2c_send_receive. This function is used in the example below.
Write to an i2c eeprom
In most cases, you also want to write to the eeprom. This is how you write value 99 to location 2 of the eeprom:
-- write part (increment 3rd byte at 0x0002)
i2c_tx_buffer = 0 -- high byte location in i2c eeprom
i2c_tx_buffer = 2 -- low byte location in i2c eeprom
i2c_tx_buffer = 99 -- data
r = i2c_send_receive(0xA0, 3, 0)
Function i2c_send_receive takes the slave address as the first parameter. The second one defines the number of bytes to be sent to the slave from i2c_tx_buffer. The third param defines the number of bytes to be received from the slave (and stored in i2c_rx_buffer). In the example above, we don't want to receive any information so the third param is 0 and i2c_rx_buffer is not used.
How to continue.
We've seen how to setup an i2c master, either using hardware or software. And we read data from an i2c eeprom and wrote a byte to it. The test-program test_i2c_sw_l1.jal, preceded by a board file like board_16f877a_dwarf.jal, provides a working example for the code shown. And in the near future, we intend to add samples of i2c code to jallib that are ready to compile. Adapt it to your specific slave and off you go!
And if you can't get it to work, spent 2 euro on an 24lc256 as a 'i2c reference device', so you can check if your PIC and it's program are working like they should.