Saturday, December 26, 2015

Timer0 interrupt for pic18f

Normal simple program work in an order entity in forever loop, the execution of function is one by one, from start to the end and back to the start again. Although the program is executed in a predictable manner, but the control of function's occurrence is hard in forever loop. For example, several tasks need to be run at different timings, task1 every 10ms, task 2 every 100ms, and task 3 every 1s. A timer is needed to keep track of the program execution and run the task at each time interval.
To enable precise control of tasks, timer needs to work independently from forever loop and interrupt the forever loop at pre-set time interval.

The below tutorial uses two leds to show the tasks execution in 1s and 2s.
Timer0 is used where a 10ms flags is raised at each interrupt. At each 10ms, a counter is increased, when the 1s is reached, led1 will blinks, for the next second, led2 will blinks.

There are three ways to set up the timer0 interrupt which are high interrupt, and low interrupt.
In this tutorial, high interrupt timer0 is shown.

For starter, always read the datasheet but microchip wikidot has prepared a good explanation for developer.

The timer0 register, always refer back when setting the timer0 module.

The basic structure of timer0 is

It can serves as timer or a counter, the timer source is MCU frequency divided by 4 while pin interrupt is T0CK1.

To enter timer mode, register TMR0CS need to be cleared.

Another important register is prescaler

Clear PSA to assign prescalar to timer0.  Then select the suitable prescaler bit.

I like to do the timer calculation in hertz, for 10ms, it is 100Hz.
So the calculation is
100 = (Fosc/4)/(PSx rate)/timer_value;

my clock frequency is 48Mhz, PSx rate I choose 64 as it can be divided nicely with 48Mhz.

100 = (12,000,000)/64/timer_value;
100 = 18750/timer_value;
timer_value=1875;

So in this case, 16 bits timer is needed, to load the suitable value,
we need take 0xFFFF - 1875, which is 63755.
Another point worth notice is "Any write to the TMR0 register will cause a 2 instruction cycle (2TCY) inhibit", so 63755 needs add with 2. The correct reload value is 63757.

The setting of timer0 module is as follow:
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Reset Timer0 to 0x0000
TMR0H = 0;              
TMR0L = 0;

// Clear Timer0 overflow flag
INTCONbits.TMR0IF = 0;  
INTCONbits.TMR0IE = 0;

//Set up timer0 see abive
T0CONbits.TMR0ON=0;         // do not on timer0 first
T0CONbits.T08BIT=0;            // select 16 bits timer
T0CONbits.T0CS=0;               // timer0
T0CONbits.PSA=0;                 // on prescalar
T0CONbits.T0PS=5;               // divide by 64

INTCON2bits.TMR0IP=1;      // enable priority interrupt
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

Refer to c18 compiler header file for timer,
(located at "C:\Program Files (x86)\Microchip\mplabc18\v3.47\src\pmc_common\Timers")
I rewrote the timer file and includes following function.

void Timer0_Init(void);
void Timer0_Start(void);
void Timer0_Stop(void);
void Timer0_Write(unsigned int timer0);

When doing initialisation for timer0 at main file,
    //------------------------------------------------------------------------------
    //  Init PIC periphery
    //------------------------------------------------------------------------------
RCONbits.IPEN=1;                  // enable priority interrupt
Timer0_Init();                           // initialise timer0
Timer0_Write(63661);             // load the value to timer0
Timer0_Start();                        // start the timer0
INTCONbits.GIEH=1;            // enable high priority interrupt


Before the interrupt service routine, following interrupt vector table need to be initialised,
//----------------------------------------------------------------------------
// High priority interrupt vector
void InterruptHandlerHigh() ;
#pragma code InterruptVectorHigh = 0x08       // 0x08 for high interrupt vector
void InterruptVectorHigh(void) {
  _asm
    goto InterruptHandlerHigh                             //jump to interrupt routine
  _endasm
}

Interrupt service routine:
//----------------------------------------------------------------------------
// High priority interrupt routine
#pragma code
#pragma interrupt InterruptHandlerHigh
//
// Timer0 overflow
void InterruptHandlerHigh() 
{
    if (INTCONbits.TMR0IF) {
INTCONbits.TMR0IF = 0;                 //clear interrupt flag

//set timer int on overflow
    Timer0_Write(63661);                      //write the timer0 value

counter++;
if(counter>=100)                              //count until 1second
{
counter=0;
flag_1sec=1;

}

if(flag_1sec==1)                             //led1 toggle every 1 second
{
flag_1sec=0;
flag_2sec++;
flag_led1=1;
}

if(flag_2sec==2)                           //led2 toggle every 2 second
{
flag_2sec=0;
flag_led2=1;
}
}
}

At the interrupt service routine, I do a counter until 100 to get 1 sec timing, then I raise a flag for led1 at every 1 second; at 2 seconds interval, I raise another flag for led2.

After that, I service the flag for led1, led2 at main routine. (Just a simple blinking)

The overall source code is shown at github.


Friday, October 2, 2015

USB-CDC and HD44780 and DS18B20

Previously, I was learning about the USB-CDC communication and testing the Microchip Libraries for Application. I never really applying the code for my own use. So I was thinking to write something that transfer a string from PC to MCU and display it on HD44780; In addition, I was hoping to transfer string of data from MCU to PC. The idea is getting temperature from DS18B20, convert it to string, and send it to PC whenever I press a push button. While, from the PC side, I am intended to display the message that I have entered in PC terminal software.

To get started, I need to learn about one wire communication, and it turns out to be quit easy. The maxim website has detail descriptions and source code on implementation. There are quit a lot of documents, but the fundamental guide is this one.
It teaches the fundamental of one wire operations like:
write '1' bit
write '0' bit
read bit
and reset
*picture is taken from Maxim website.

After you get the basic idea, application note AN1199 from microchip can really helpful (because I am using the PIC18f). Best of all, it come with source code which compatible with C18, plus detail description of each function is included in AN1199.

Since the purpose is learning, I rewrite the whole source code to suit my own need by referring to microchip's code and some other tutorials I found on-line.

Continuing that, I need to learn the DS18B20 operation, there is one that suit my taste is from nicholas sirirak. Very detail guide and implementation, the best I found so far. Second best, the official datasheet. I read the nicholas sirirak tutorial, and refer back datasheet for part that I don't understand. So far, this approach work very good for me.

The simplest way of getting temperature data is single bus single sensor connection.
The steps are as follow:
1. Reset DS18B20
2. Write SKIP ROM command
3. Write CONVERT T command
4. Wait 750 millisecond or use polling
5. Reset DS18B20 again
6. Write SKIP ROM command
7. Write READ SCRATCHPAD command
8. Read  temperature

The implementation steps are found in the datasheet as follow:

The config can be simply ignored since we are not going to use it.

The DS18B20 actually sending the unsigned int temperature data in two bytes. So in the end, both bytes are combined to form one complete temperature data. The implementation code is as follow:

After that, we need to process the data into something readable.
By referring to table below,

we can see that last 4 bits are for decimal point, and bit 5 to bit 12 are for integer.

In order to process every data in integer format, I create a array that store the decimal point in integer format. Eliminating the floating point computation and save the ram memory.


4 bits maximum value equal to 16, and you take 1 divided by 16, you get the precision until 0.0625. When you pass the last 4 bits of temperature data, the decimal point is directly passed back. Save the computation effort by processing all data in integer.

Later, when I check the microchip cdc function file as shown below,

it seem that it is easier for me to convert the integer to array before passing to "putUSBUSART" function.

it is easy to process the positive temperature. However, for negative temperature, I need to one complement it, then add "one" like following:

temperature =~temperature ;
temperature =temperature +1;

then, the negative number is converted to positive number. 

Let take one example:
actual value                           |                   +10.125                 |                      -10.125
representation in binary        |        0000 0000 1010 0010      |           1111 1111 0101 1110
bitwise "NOT"                      |                         -                       |           0000 0000 1010 0001
add "1"                                  |                        -                        |         0000 0000 1010 0010

I think this process should be called "2nd complement" if I not mistaken.


After I was able to convert all numbers to array, I need to test it, just have the urge to test it.
For high temperature testing, I go and boil the water.


Well, the temperature is lower than I have expected. And it cool down so fast.

For less than zero temperature, I put some salt in water and put it in freezer until it almost frozen.
Finally, I get the negative temperature. My negative temperature conversion to positive temperature is proven to be correct.

To ensure the accuracy of the ds18b20, I need to compare the reading with a real temperature meter. This is where my multimeter come in - vc890c.

My most satisfied equipment so far, acceptable quality and functions with such low price. Come with a thermostat which has wide sensing range from -20 Celsius to 1000 Celsius.

It is a two ways comparison, if the reading is near, then I assume both devices are working good. Actually, it increase the level of confidence of me in China branded multimeter.

I compare the sensing in room temperature and in cold water, the reading is the same, vc890c precision is single digit, while the ds18b20 precision until 4 decimal points.


I have decent multimeter and working code of ds18b20.

Now it the transferring the data to PC. I am using my microchip MLA project file and start editing from that main file.

First include all the library files,

Go to the function - void UserInit(void) and include the lcd initialisation.


Later on, go to ProcessIO(void) and start editing from there.
I want to transmit temperature data each time I pressed a button.
So at the "if(buttonPressed)",
add this instruction "DS18B20_RawTempToBCD(DS18B20_Buffer, &length);".

I want each time I send a temperature data, it will start at new line. From ASCII code, new line code would be 0x0A, so I add 0x0A at end array by using this code "DS18B20_Buffer[length]=0x0A;".
After that, time to send the array using "putUSBUSART(DS18B20_Buffer,length+1);".
I add the 0x0A at the end of array, so I have to increase the DS18B20_Buffer by 1, so I add the length+1 at the putUSBUSART function.

Now, we need to take care of the data sent from PC to micrcontroller,
still in the ProcessIO(void) function,
Go to "if(USBUSARTIsTxTrfReady())", if the numBytesRead contains data, clear the lcd16x2 display,
then display the data at numBytesRead using this instruction "HD44780_WriteData(USB_Out_Buffer[i]);"

That all, from the microcontroller side.

For the PC side, I am using the Termite- Termite is an easy to use and easy to configure RS232 terminal, with an interface similar to that of "messenger" or "chat" programs.

The temperature data obtained is as follow,

At the Termite side, I type the string "hello world" and it is displayed in microcontroller side.

All the codes are shared in github.









Saturday, September 5, 2015

Taobao - abundance supply of cheap tools and components

China has been a great supplier for cheap electronic tools and modules. Even the sparkfun is sourcing their multimeter, soldering station from China, which in turn, show the reliability and usability of China tools.

When you go jalan pasar, most of the components and tools, which I believe also sourced from China.

Recently, taobao has that 2rmb promotion for delivery (direct from china to malaysia) for first time user.

Best of all, you are able to pay taobao using the malaysia online banking system.

A full tutorial is provided by taobao.

Taking this opportunity, I have registered an account and start finding for cheap multimeter and soldering station.

It is lucky that I know Chinese, make it easy for me to search for particular items.
But I also use baidu to help me do the translation and i copy the exact phase to taobao. Although you can use english to search but the results is not as many as your Chinese search term.


The search results return a series of options,


 and depending on cost, I choose the best selling one.

The victor vc890c+, has always been a popular choice. Good review, maybe due to it's fluke like look.


There are a few packages to choose, some with extra chargeable battery, a pouch, probe...
I buy an extra probe. And it only cost me roughly RM60. I think is fairly a good bargain for a multimeter with ac voltage/current (sensing range is until 20 ampere), capacitance, temperature, resistance sensing. Furthermore, transistor NPN/PNP checking is included. A quick search on vc890c+ in lelong, it cost around RM100. So, final conclusion, is a damn good deal.


I have a urge to build my own electronic home lab. So a basic two tools, I think it will be multimeter and solder station. For the soldering station, there are two variants, soldering iron, hot air, and combination or two. At first, I plan to buy soldering iron with hot air reflow, as the price is cheap as well. After saw the technician in my company, on how they can solder smt component easily using single solder iron. I have decided to purchase a soldering station with temperature control.


and I choose the cheapest best selling 936 series.
The Hakko has a tremendous success on soldering station 936, now every China soldering brands will have at least one model with 936 name.

The one I chose is kasadi 936, the name is nice and the advertisement look very promising on assuring the quality. Furthermore, at the taobao page, the reviews on Kasadi 936 are quit ok and has some small defects but still acceptable.

I buy a package which consists solder, solder flux, sucker, tweezer........
Easier for me, as I dont want to buy the items separately as it will troublesome and no guarantee it will be cheaper.

Also, I add in solder cleaner (Reason: Cheap). This type of cleaner is suitable for lazy me, dont need pour water, use and toss item but it can last for quit some times.


In this order, my friend "tumpang" a pair of wifi esp8266 module and a pair of bluetooth ble 4.0 module.




Total delivery fee for these items is roughly 9rmb, 7rmb is charged to deliver the wireless module to a warehouse in china. After consolidating all items, 2rmb is charged for delivery to malaysia. The multimeter and solder station seller doesn't charge fee for delivery in china.


You can check the status of your items from time to time. Even bar code is provided for easy checking, but I never try it.



Taobao will hold your payment for roughly 10 days (I think). You can extend the payment date if the items is not reached within 10 days. Also you can ask for refund but terms and conditions applied (I have no idea on this but it is refundable with conditions).

Nonetheless, my items arrived less than one week (6 days to be exact).



The multimeter set

Solder station


I didnt open my friend wireless module, it is quit light, almost felt empty. I let him open himself.

Total cost for multimeter and soldering station, cost me RM130, but is a good buy. In the mean time, I am going to let it collecting dust in cupboard.





Friday, July 24, 2015

HD44780 lcd16x2 - custom character

The HD44780 has character generator RAM (CGRAM) to store user-defined character pattern. There are two font types - 5x8, 5x10. For 5x8 dots, 8 character patterns can be written, and for 5x10 dots, 4 character patterns can be written.

Since 2 lines mode is always preferred, the below setting is for font 5x8.

To understand the process of storing custom character, the correspondence between EPROM Address Data and Character Pattern (5x8 Dots) is needed to be studied.
For each custom character for 5x8 font, 8 lines of address are needed to store one character pattern.
So address, A2, A1, and A0 are the 8 selectable line position of the character pattern.
while data O4, O3, O2, O1, O0 correspond to character pattern data.
Last, the A5, A4, and A3 represent the 8 selectable address to a character.

The relationship between CGRAM Addresses, Character Codes (DDRAM) and Character
Patterns (CGRAM Data) can be better shown at figure below

The CGRAM address bits 0,1,2 are the line pattern position,
The CGRAM address bits 3,4,5 are the custom character position, it is linked with DDRAM location.

The DDRAM address bits 3 (*) indicates no effect which means address 0000 *000 can represent two locations 0000 0000, and 0000 1000. Both locations store the same custom character.

To go to CGRAM address the below instruction is used.

The address bits DB5, DB4, DB3 are custom character position,
while address bits DB2, DB1, DB0 are line pattern position.

When adding a custom character, it is only required to point to custom character position (DB5, DB4, DB3), and leave the line pattern position (DB2, DB1, DB0) equal to zero.
As the first line pattern is written, the HD44780 will automatically point to next location as (DB2, DB1, DB0) will be increased accordingly. So, pointing to next CGRAM location is not needed in next line pattern writing.

(DB5, DB4, DB3) - Custom character position
(DB2, DB1, DB0) - Line pattern position

To create custom character, use the instruction as follow:
rom char battery1[8] = {
        0b01110,
    0b01010,
    0b10001,
    0b10001,
    0b10001,
    0b10001,
    0b10001,
    0b11111
    };
rom specific that this data is saved at flash memory.

To save the custom character, use the instruction
HD44780_AddCharacter(0, battery1);


All the custom characters are stored at the specific location as shown at figure below:


HD44780_AddCharacter(0, battery1);
location 0 is upper bits 0000, lower bits 0000.

To write the custom character to lcd display, just simply use the write data to DDRAM such as:
HD44780_WriteData(0x00);

BTW, to display the  character "0", HD44780_WriteData(0x30) and HD44780_WriteData("0") will get the same result.

Finally, the display of custom character - battery. I let the led1 and led2 blinking in forever loop (just for fun).


I keep all the source code at github.





Friday, July 10, 2015

HD44780 lcd16x2 and c18

The HD44780 is a common display used in hobbies project, the tutorials, datasheets, and implementation guide are all over the web. Even with available source code on the web, I am planning to write my driver using PIC18f. Previously, I had modified a source code online for msp430, it is a 4 bits data line communication. This time I am planning to write my own 8 bits data line communication 16x2 lcd driver using c18.

I write and test my code for on-going two months. Nothing work and I have no idea what went wrong as I wrote the code according to data sheet. Maybe the HD44780 has it's variant which make the implementation slightly difference from one and other.

I have to search on-line and read other people source code to understand how the initialisation of lcd is done. After some research, I found that elm-chan website has the most detail guide on implementation and the most accurate one also. For the source code, I found that armandas is the best written source code so far. I do follow some of the way armandas naming and code structure. Also there is simplified but detail datasheet from spickles.

The main problem I faced is the initialisation part, so I am going to start with this. There are two types of initialisation process. First is setup with internal reset, second is initialisation with software. The internal reset procedure is relatively easy, and protostack even shows an initialisation example using solely dip switch. This is pretty amazing as no a single MCU is involved in this.

Second type is the initialisation by instruction.
The initialisation diagram is as follow:

I had highlighted the changes I made.

For the last 4 steps, the long delay is needed as weird character will come out. Usually weird character mean the time needed for the lcd to process data is not enough.
For the third step from last, I found that 0b00001000 is not the correct initialisation. Although most of the datasheet online, and even microchip application note have shown that 0b00001000 is the correct value. Nonetheless, I do encounter some problem when initializing the lcd. Maybe the HD44780 variant is different somehow.

In this configuration, I choose, 8 bits data transmission and 2 lines display.

..............................................................................................................................................

For the 16x2 lcd, a pointer to particular location in lcd is important. The HD44780 in (8 bits, 2 lines configuration) able to store 80 locations. But only the first 16 locations of the 1st line and 2nd line able to be displayed.

The DDRAM address mapping is as follow:

The numbering is at hexadecimal format.
To point to first row, third column, DDRAM is set at 0x02.
To point to second row, third colum, DDRAM is set at 0x42.

The pseudo code is easy
if first row, DDRAM = address_point_by_user;
if second row, DDRAM = address_point_by_user + 0x40;

..............................................................................................................................................

Microchip has two simple rules to follow when apply read and write operation to IO port which are "0" for output and "1" for input and the second rule is "read from port, write to latch".
In order to write to output, the IO direction is set to output and the write operation is written to latch as follow:

#define HD44780_initPinIO() {TRISBbits.RB4=0; TRISBbits.RB5=0; TRISD=0x00;}
#define HD44780_EN_PIN LATBbits.LATB5
#define HD44780_RS_PIN LATBbits.LATB4
#define HD44780_DATA_PORT LATD

..............................................................................................................................................

To keep the coding easy to use and easy to understand, only three functions are included in the hd44780.h which are

void HD44780_Init(void);
void HD44780_WriteString(const rom char *s);
void HD44780_GoToPoint(char row, char col);

HD44780_Init()-must be defined before the two other functions.
HD44780_WriteString(const rom char *s)-write words to display.
HD44780_GoToPoint(char row, char col)-go to particular location of lcd.

Nevertheless, hd44780.c file can be modified to cater the user's need as most functions are hidden in hd44780.c.
..............................................................................................................................................

Simple note:
One pulse               - EN high, delay 50 us, EN low.
RS pin                    - 0 = COMMAND; 1 = DATA.
DDRAM location  - Location at lcd display.
rom                        - c18 keyword to store data in flash.

..............................................................................................................................................

Output:


Most people would prefer the "hello world" message, but I decide to display 4 alphabet at 4 different locations. ^^

The source code can be found at github.





Friday, June 12, 2015

PIC18f system clock and delay routine using C18

I just started with the PIC18f, so I planning to go into fundamental. With bunch of tutorials and forum discussions online, I still encounter some difficulties to get started with PIC18f.
Simple problem, yet I do a lot of readings before I successfully compile the led blinking (with 1 second delay).

The first problem is setting the system clock. And the second problem is using the C18 default delay routine.

The PIC18f4550/PIC18f4553 has three clock blocks which are primary oscillator, secondary oscillator, and internal oscillator.




The primary oscillator is normally used for high speed oscillator (>=4MHz), secondary oscillator for low speed oscillator such as 32.768kHz. Internal oscillator has two different clock signals; main output is an 8 MHz clock source and second clock source is the internal RC oscillator provides a nominal 31 kHz output.

Primary oscillator has 4 modes which are HS, HSPLL, XT, XTPLL. Depending of frequency of oscillator, suitable mode need to be assigned as shown as table below:

Resonators Freq
Mode
4.0MHz
XT
8.0MHz/16.0MHz
HS

Crystal Freq
Mode
4.0MHz
XT
4.0MHz/8.0MHz/20.0MHz
HS

For some projects, MCU doesnt need to operate at high speed, config CPUDIV can be used to scale the operating frequency into lower speed.

For some applications that require high speed clock, MICROCHIP provides PLL (phase locked loop) function, it is a frequency multiplier to generate a fixed 96MHz from a fixed 4MHz input. On top of that, a PLLDIV is assigned to divide the oscillator frequency to 4MHz which enable the adoption of oscillator with various frequency range.

















The PIC18f's maximum allowable frequency is 48MHz, so after the PLL the 96MHz is divided into half to suit the MCU operating frequency.

The overall setting for CPU frequency is as follow:
1:      #pragma config PLLDIV  = 5                        // (20 MHz crystal)
2:      #pragma config CPUDIV  = OSC1_PLL2    // Divide 96MHz with two
3:      #pragma config USBDIV  = 2                      // Clock source from 96MHz PLL/2
4:      #pragma config FOSC   = HSPLL_HS         // HS oscillator, PLL enabled (HSPLL)  

For more information on setting, open the mplab, go to Help->Topics...->PIC18 Config Settings. From there, choose selected microcontroller, and all settings are listed and explained.

........................................................................................................................................
For PIC18f4553/4550, maximum CPU Speed is 48 MHz (12 MIPS) which means at CPU operating frequency of 48 MHz, it is able to run 12 millions instruction per second. So one instruction need 4 cycles to process.

In order to have one second delay, you need to do nothing for 12 millions instruction time.
For one micro second delay, 12 instructions of _do_nothing are needed. Which in turn,  two micro second delay, 24 (12x2) instructions of _do_nothing are needed.

Microchip c18 provides <delays.h> library to help set the timing, the <delays.h> is located at
"C:\Program Files (x86)\Microchip\mplabc18\v3.47\h\delays.h"

I wrote my own routine on top on <delays.h> and name it as "delay.h"
For one micro second -
#define delay1MicroSecond()    {Delay10TCYx(1); Delay1TCY(); Delay1TCY();}
// delay 12 instructions

#define delay10MicroSecond() Delay10TCYx(12)      // delay 120  instructions

#define delay1MiliSecond() Delay1KTCYx(12)     //delay 12000 instructions

#define delay100MiliSecond() Delay10KTCYx(120) //delay 1,200,000 instructions

#define delay200MiliSecond() Delay10KTCYx(240) //delay 2,400,000 instructions

All delay routines are in the multiplication of 12 (provided that the CPU operating frequency is 48MHz).

For other CPU frequency, scaling is needed to get the correct timing.
Examples:
                  48MHz = 12 MIPS        // for one micro second, delay 12 instructions
                  32MHz =   9 MIPS        // for one micro second, delay   9 instructions
                  24MHz =   6 MIPS        // for one micro second, delay   6 instructions
                  16MHz =   3 MIPS        // for one micro second, delay   3 instructions

..............................................................................................................................................

To blink led,
the pic18f has one general rule to read/write IO - read from port and write to latch.

First set the IO pin to output,
#define mInitAllLEDs()     TRISBbits.TRISB6=0; TRISBbits.TRISB7=0; //define output
//pic18f rule for input/output is
//0 = output;
//1 = input;

#define mLED_1              LATBbits.LATB6    // write to particular bit (pin b6)
#define mLED_2              LATBbits.LATB7    // write to particular bit (pin b7)

// to on led just assigned mLED_1 = 1;
// to off led just assigned mLED_1 = 0;
..............................................................................................................................................

Now you can start with blinking led, all the source codes can be downloaded at github.