Friday, July 27, 2018

Repurpose Old Electronics: Learning from an old wireless floor heating controller/thermostat [ Part 1]



In this project we are going to dig the details of a floor heating controller with wireless thermostats from LK systems.  The idea is to learn more about basics of AVR microcontrollers


WARNING: In this case, the original device is decommissioned and using a modified firmware in a device which is in use is not recommended. The author is not liable for the potential harm from such an act. 

The device uses an atmega649 as a controller in the receive unit which controls the actuators for hot water to the floor heating system.  There are several channels which controls the flow of hot water based up on the temperature measurements and user set values from sensors/thermostats placed in different rooms. 

The sensors are fitted with a thermistor for measuring the temperature and a potentiometer for user inputs (heat level needed).  Sensors use an atmega48 and the communication between the sensors and the control unit is via 868MHZ ism band using an FSK module.

The idea of this project is to understand the device and write smaller modules to test and control different elements of the system (basic level) and the possibility for writing custom firmware and repurpose the thermostat for fun.

Some of the possibilities are:


  • Wireless thermometer/weather station
  • Wireless sprinkler controller & soil moisture measurement
  • Wireless water pump controller based on overhead tank levels
  • Wireless post box monitor & many more wireless sensor applications
  • Adding Internet connectivity to semi smart old thermostats –adding ESP8266


The learning points and different steps in the project :


  • Write a simple  avr-gcc project to control the custom LCD on the device (using built in LCD controller on avr)
  • Learn more about the interrupts and timers in an AVR
  • Wireless communications, Manchester coding and Frequency Shift Keying modules
  • Using the real time clock (RTC) system in an AVR with 32khz crystals & Asynchronus timer
  • Learn more on power saving and sleep modes in AVR
  • Controlling the Thermo electric Actuators,  Battery Backups (RTC) and PWM
  • Controlling a beeper on an AVR  (simultaneous use of all the above functions + beep without delay/PWM)
  • State machines & Button debouncing
  • Adding  a bootloader (TinySafeBoot) in AVR projects 

Hardware Internals

In the figure below the internals of controller and the sensor (thermostat) are shown. Good quality double sided printed circuit board with perfections in every aspects. The clever choice and use of micro controller pins and quality assurance in the components are noticeable. The device did a good job in the past 9 -10 years and  is currently replaced with a smarter/connected systems emerged in the recent years.  The battery backups, display controller and on-board converters are excellent and i included only the controller board. There is one more excellent board with control relays are used to 
manage the thermo electric actuators and pumps (and extra opto isolated ports)

Inside view of the device and both sides are shown in the same figure & flipped to make the opposite sides to coiside with each other

The internals of the sensor/thermostat board with thermistor/user knob is shown below

Thermostat remote/sensor with atmega48, the rf module is removed

Tools needed for the project

  1. A serial programmer for the avr  ( you can use an arduino as isp)
  2. Compiler and tool chain (avr-gcc, avrdude, text editor)
  3. A usb to serial  adapter  (for debug, adding serial bootloader & get rid of isp)

Taming the LCD controller in an AVR

In this project we start with writing a simple LCD driver. Atmega649 has a built in LCD controller.
The glass type lcd (as seen in figure) needs an AC voltage to make its segment darker (active). To minimize the use of  micro-controller pins the lcd uses multiplexing and around 50+ segments are present on the lcd (for digits and symbols) 

To start with the project we need to  get hold of the datasheet for atmega649 & atmega48. Look at the pin descriptions and make a small printout to keep  aside. 

The first thing to do is to identify the ICSP & Serial pins (RX, TX) and see if they are easily exposed.
Luckily in this case the manufacturer is very open and have kept all neat and clean.

In the figure below you can see the LCD pins used (22 pins) where 19 segments and 3 channels for multiplexing (19 X 3= 57 possibilities)

Important pins and their usage in the controller unit


If you are  familiar with other avr projects , it uses some registers ( basically memory addresses ) to indicate different control routines.  For LCD segments, there are certain memory locations in the avr  where if we write 1, the corresponding lcd sement turns on and vice versa

To initialize and use the LCD controller we need to set up some control registers with proper values to indicate the (frame rate of lcd, the duty cycles, the number of segment pins used (pin mask) and common pins used and to indicate if lcd need to be enabled or not and there is an interrupt which gets fired when a new frame is drawn on the lcd)

After going through the data sheet, i have arrived at the following results (read page 228 of the manual for finer details )

Setting up the LCD


LCDCRA – LCD Control and Status Register A
LCDEN: LCD Enable ( Writing this bit to one enables the LCD Controller)
LCDIE: LCD Interrupt Enable ( if set as 1, LCD Interrupt at the beginning of a new frame)

LCD Memory map

LCD memory map where flipping the bit turns a segment on or off. The challenge is to identify which bit and which segment


After a bit of experimenting ( dont have a jtag lying around to debug and flip the registers) to identify the relation between segments and memory locations, an algorithm was developed to turn on and off the segments in response to the numbers and text ( limited to what is possible with seven segment)

Preceding number indicates the digit (1-4) location  and letter indicate the segment as shown in bottom. Other entities are special characters and symbols built in to indicate days , channel etc

Source code for LCD Driver

Use all four files (main.c, lcd.h, lcd.c, Makefile) in a folder and run make all && make flash
(you may have to edit Makefile to set your programmer)

main.c (test program)



 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/************************************************************//**
 *  ...written by RV
 *  .. Free to use and leave credits
 *  .. credits to avr rf_lib/appnotes/datasheets/avrfreaks website
 *  .. adding new functions to be integrated back
 ****************************************************************/

#ifndef F_CPU
#define F_CPU 1000000
#endif

#include <avr/io.h>
#include <util/delay.h>        
#include "lcd.h"

void main(void)
{ 

   _delay_ms(2000);   
   LCD_Init();               // initialize the LCD
   LCD_WriteNum(1234); 
   _delay_ms(1000);
   LCD_clear();
   for(;;) //infinite loop
 {       
        // TO DO MOVE TO STATE MACHINE  
  LCD_puts("Welcome to Thermos123");
  _delay_ms(1000);

    }

}


lcd.h



  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
/************************************************************//**
 *  ... written by RV
 *  .. Free to use and leave credits
 *  .. credits to avr appnotes/datasheets/avrfreaks website
 *  .. adding new functions to be integrated back
 ****************************************************************/

#ifndef LCD_H
#define LCD_H

#include <avr/io.h>
#include <avr/pgmspace.h>
#include <util/delay.h> 
#include <stdint.h>
#include <avr/interrupt.h>

#define SetBit(number,bit) (number|=(1<<bit)) 
#define CLR_BIT(p,n) ((p) &= ~((1) << (n)))

#define LCD_LCDREGS_START          ((unsigned char*)&LCDDR00)
#define FALSE  0 
#define  TRUE 1
#define  LCDCOLON 7 //R5
#define  LCDDOT 7   //R0
//SYMBOLS IN THE LCD
#define  LCDR3 0   //R0
#define  LCDR7 6   //R1
#define  LCDD6 7   //R1
#define  LCDD1 2   //R2
#define  LCDD2 1   //R2
#define  LCDD5 0   //R2
#define  LCDR2 0   //R5
#define  LCDD7 7   //R6
#define  LCDR6 6   //R6 
#define  LCDR4 2   //R7
#define  LCDD3 1   //R7
#define  LCDD4 0   //R7
#define  LCDR1 0   //R10
#define  LCDU1 1   //R10
#define  LCDU2 3   //R10
#define  LCDU3 6   //R10
#define  LCDU4 7   //R10
#define  LCDU5 2   //R11
#define  LCDU6 3   //R11
#define  LCDU7 5   //R11
#define  LCDR5 6   //R11
#define  LCDR8 7   //R11

#define LCD_SCROLLCOUNT_DEFAULT    32
#define LCD_DELAYCOUNT_DEFAULT     20
#define LCD_TEXTBUFFER_SIZE        40
#define LCD_SEGBUFFER_SIZE         19
#define LCD_DISPLAY_SIZE           4

#define LCD_FLAG_SCROLL            (1 << 0)
#define LCD_FLAG_SCROLL_DONE       (1 << 1) 
#define FALSE  0 
#define  TRUE 1

//                                  LCD Text            + Nulls for scrolling + Null Termination
static volatile char     TextBuffer[LCD_TEXTBUFFER_SIZE + LCD_DISPLAY_SIZE    + 1] = {};
static volatile uint8_t  StrStart        = 0;
static volatile uint8_t  StrEnd          = 0;
static volatile uint8_t  ScrollCount     = 0;
static volatile uint8_t  UpdateDisplay   = FALSE;
static volatile uint8_t  ScrollFlags     = 0;

#define LCD_SPACE_OR_INVALID_CHAR  38
#define LCD_CONTRAST_LEVEL(level)  do{ LCDCCR = (0x0F & level); }while(0)
#define LCD_WAIT_FOR_SCROLL_DONE() do{ while (!(ScrollFlags & LCD_FLAG_SCROLL_DONE)) {} }while(0)

static unsigned const char digitmap[] PROGMEM={
0b01101111, // 0   "0"          AAA
0b00100100, // 1   "1"         F   B
0b01110011, // 2   "2"         F   B
0b01110110, // 3   "3"          GGG
0b00111100, // 4   "4"         E   C
0b01011110, // 5   "5"         E   C
0b01011111, // 6   "6"          DDD
0b01100100, // 7   "7"
0b01111111, // 8   "8"
0b01111110, // 9   "9"
0b01111101, // 65  'A'
0b00011111, // 66  'b'
0b01001011, // 67  'C'
0b00110111, // 68  'd'
0b01011011, // 69  'E'
0b01011001, // 70  'F'
0b01001111, // 71  'G'
0b00111101, // 72  'H'
0b00100100, // 73  'I'
0b00100110, // 74  'J'
0b00111101, // 75  'K'  Same as 'H'
0b00001011, // 76  'L'
0b00000000, // 77  'M'  NO DISPLAY
0b00010101, // 78  'n'
0b01101111, // 79  'O'
0b01111001, // 80  'P'
0b01111100, // 81  'q'
0b00010001, // 82  'r'
0b01011110, // 83  'S'
0b00011011, // 84  't'
0b00101111, // 85  'U'
0b00101111, // 86  'V'  Same as 'U'
0b00000000, // 87  'W'  NO DISPLAY
0b00111101, // 88  'X'  Same as 'H'
0b00111110, // 89  'y'
0b01110011, // 90  'Z'  Same as '2'
0b00000000, // 32  ' '  BLANK
0b00010000, // 45  '-'  DASH
0b00000000, // 46  '.'  PERIOD
};



// PROTOTYPES:
void LCD_Init(void);
void LCD_WriteChar(const char Byte, const char Digit);
void LCD_clear(void);
void LCD_test(void);
void LCD_WriteNum(int number);
void LCD_showcolon(unsigned char show);
void LCD_showsymbol( int BYTESYMBOL,unsigned char show,int register_row);
void LCD_puts(const char *Data);

#endif

lcd.c


  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
/************************************************************//**
 *  ... written by RV
 *  .. Free to use and leave credits
 *  .. credits to avr appnotes/datasheets/avrfreaks website
 *  .. adding new functions to be integrated back
 ****************************************************************/
#include "lcd.h"

void LCD_disable(void)
{
/* Wait until a new frame is started. */
while ( !(LCDCRA & (1<<LCDIF)) )
;
/* Set LCD Blanking and clear interrupt flag */
/* by writing a logical one to the flag. */
LCDCRA = (1<<LCDEN)|(1<<LCDIF)|(1<<LCDBL);
/* Wait until LCD Blanking is effective. */
while ( !(LCDCRA & (1<<LCDIF)) )
;
/* Disable LCD */
LCDCRA = (0<<LCDEN);
}

void LCD_Init(void)
{
/* Use 32 kHz crystal oscillator */
/* 1/3 Bias and 1/3 duty, SEG21:SEG24 is used as port pins */
LCDCRB = (1<<LCDCS) | (1<<LCDMUX1)| (1<<LCDPM2);
/* Using 16 as prescaler selection and 7 as LCD Clock Divide */
/* gives a frame rate of 49 Hz */
LCDFRR = (1<<LCDCD2) | (1<<LCDCD1);
/* Set segment drive time to 125 ┬Ás and output voltage to 3.3 V*/
LCDCCR = (1<<LCDDC1) | (1<<LCDCC3) | (1<<LCDCC2) | (1<<LCDCC1);
/* Enable LCD, default waveform and no interrupt enabled */
//LCD Control and Status Register A
LCDCRA = (1<<LCDEN) | (1<<LCDIE);
sei();
//LOW POWE Wave form & LCDIF is interrupt flag & LCDIE is enable interrupt
//LCDCRA = (1<<LCDAB);
}

void LCD_puts(const char *Data)
{
 uint8_t LoadB       = 0;
 uint8_t CurrByte;

 do
 {
  CurrByte = *(Data++);
  
  switch (CurrByte)
  {
   case '0'...'9':
    TextBuffer[LoadB++] = (CurrByte-48);
    break;  
   case 'A'...'Z':                              
    TextBuffer[LoadB++] = (CurrByte-55);    
    break;    
   case 'a'...'z':                              
    TextBuffer[LoadB++] = (CurrByte-87);   
    break;  
   case 0x00:                                   
    break;
   default:                                    
    TextBuffer[LoadB++] = LCD_SPACE_OR_INVALID_CHAR;
  }
 }
 while (CurrByte && (LoadB < LCD_TEXTBUFFER_SIZE));
 
 ScrollFlags = ((LoadB > LCD_DISPLAY_SIZE)? LCD_FLAG_SCROLL : 0x00);

 for (uint8_t Nulls = 0; Nulls < 7; Nulls++)
   TextBuffer[LoadB++] = LCD_SPACE_OR_INVALID_CHAR;  // Load in nulls to ensure that when scrolling, the display clears before wrapping
 
 TextBuffer[LoadB] = 0x00;                           // Null-terminate string
 
 StrStart      = 0;
 StrEnd        = LoadB;
 ScrollCount   = LCD_SCROLLCOUNT_DEFAULT + LCD_DELAYCOUNT_DEFAULT;
 UpdateDisplay = TRUE;
}

/*
ISR for lcd 
*/
ISR(LCD_vect, ISR_NOBLOCK)
{
    unsigned char cbyte; 
 if (ScrollFlags & LCD_FLAG_SCROLL)
 {
  if (!(ScrollCount--))
  {
   UpdateDisplay = TRUE;
   ScrollCount   = LCD_SCROLLCOUNT_DEFAULT;
  }
 }

 if (UpdateDisplay)
 {
  for (uint8_t Character = 0; Character < LCD_DISPLAY_SIZE; Character++)
  {
   uint8_t Byte = (StrStart + Character);

   if (Byte >= StrEnd)
     Byte -= StrEnd;   
   cbyte = pgm_read_byte(&(digitmap[(int)TextBuffer[Byte]]));
   LCD_WriteChar(cbyte, Character);   
   //LCD_WriteChar(TextBuffer[Byte], Character);
  }
  
  if ((StrStart + LCD_DISPLAY_SIZE) == StrEnd)    // Done scrolling message on LCD once
    ScrollFlags |= LCD_FLAG_SCROLL_DONE;
  
  if (StrStart++ == StrEnd)
    StrStart     = 1;
  UpdateDisplay  = FALSE;                         // Clear LCD management flags, LCD update is complete
 }
}


void LCD_WriteChar(const char Byte, const char Digit)
{
 //The function takes in segment map (0bxxxxxxxx) and digit position (0-3)
 //Simple and direct update alternateively use the LCD interrupt and a buffer to store the changes
 //and write it to the register during the refresh interrupt
 //function takes the segmnt map and update the lcd register appropriately
 //nothing to do with more digits than available in the current lcd  
 //odd digit location in register map are adhjascent, refer datasheet
 if (Digit>>2) //number 4 and above will b etrue after 2 shift to right
  return;
 unsigned char* BuffPtr = (unsigned char*)(LCD_LCDREGS_START + (Digit >> 1));
    char MaskedSegData=0;
     //loop for the three groups of lcd register rows for a difgit ie 3 common planes/multiplex
  for (char BNib = 0; BNib < 3; BNib++)
  {   
      if(BNib == 0)
   //pick first 3 segments 
   MaskedSegData = (Byte & 0b00000111);
      if(BNib == 1)
   { 
    MaskedSegData = (Byte & 0b00111000);
    MaskedSegData >>=3;
   }
      if(BNib == 2)
   {
   //pick last 1 segments  
   MaskedSegData = (Byte & 0b01000000);
      MaskedSegData >>=5;    
      if (Digit ==1)
     *BuffPtr = ((*BuffPtr & 0b11011111) | (MaskedSegData<<4));
     if (Digit ==3)
     *BuffPtr = ((*BuffPtr & 0b11101111) | (MaskedSegData<<3));
   if (Digit ==0)    
    *BuffPtr = ((*BuffPtr & 0b11111011) | MaskedSegData<<1); 
   if (Digit ==2)
     *BuffPtr = ((*BuffPtr & 0b11111101) | MaskedSegData);
      }
   //to avoid messing adjascent non numeric segments
   else//quick n dirty update of Bnib 3 will erase adjascent
            {
      if (Digit ==1)
     *BuffPtr = ((*BuffPtr & 0b10001111) | (MaskedSegData<<4));
     if (Digit ==3)
     *BuffPtr = ((*BuffPtr & 0b11000111) | (MaskedSegData<<3));
   if (Digit ==0)    
    *BuffPtr = ((*BuffPtr & 0b11110001) | MaskedSegData<<1); 
   if (Digit ==2)
     *BuffPtr = ((*BuffPtr & 0b11111000) | MaskedSegData);
            } 

   BuffPtr += 5;  
  } 
}

void LCD_WriteNum(int number)
//show a number from 0000-9999 on the lcd
//add negative handling later
{
if ((number>9999)||(number<0)) //number 4 and above will b etrue after 2 shift to right
 return;
//clear lcd 
LCD_clear();  
//take the 1s place or digit0
int digit;
unsigned char byte;
digit=number%10;
byte = pgm_read_byte(&(digitmap[digit]));
LCD_WriteChar(byte, 3);
//pick next
digit=(number/10)%10;
byte = pgm_read_byte(&(digitmap[digit]));
LCD_WriteChar(byte, 2);
digit=(number/100)%10;
byte = pgm_read_byte(&(digitmap[digit]));
LCD_WriteChar(byte, 1);
digit=(number/1000)%10;
byte = pgm_read_byte(&(digitmap[digit]));
LCD_WriteChar(byte, 0);
 
}


void LCD_showcolon(unsigned char show)
{
    unsigned char* BuffPtr = (unsigned char*)(LCD_LCDREGS_START);
    BuffPtr+=5; //location for colon 
 (show==FALSE) ? CLR_BIT(*BuffPtr,LCDCOLON) : SetBit(*BuffPtr ,LCDCOLON);
}

//void rf_rx_set_io(volatile uint8_t* reg, uint8_t pin)

void LCD_showsymbol( int BYTESYMBOL,unsigned char show,int register_row)
{
 unsigned char* BuffPtr = (unsigned char*)(LCD_LCDREGS_START);
    BuffPtr+=register_row; //location for colon 
 (show==FALSE) ? CLR_BIT(*BuffPtr,BYTESYMBOL) : SetBit(*BuffPtr,BYTESYMBOL);
 
}

void LCD_clear(void)
{
        LCD_WriteChar(0b00000000, 0);
        LCD_WriteChar(0b00000000, 1);
        LCD_WriteChar(0b00000000, 2);
        LCD_WriteChar(0b00000000, 3);
}

void LCD_test(void)
{ //jump across the digit 
 for (int j=0; j<4;j++)
  {
   //numbers only first 10 in the map
   for (int i=0; i<9;i++)
    {
     unsigned char byte = pgm_read_byte(&(digitmap[i]));
     LCD_WriteChar(byte, j);
    _delay_ms(500);
    LCD_showcolon(TRUE);
    LCD_showsymbol( LCDR3,TRUE,0);
    _delay_ms(500);
    LCD_showcolon(FALSE);
    }

  }
   LCD_clear(); 
}

Makefile


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
# Name: Makefile
# Author: <insert your name here>
# Copyright: <insert your copyright message here>
# License: <insert your license reference here>

# DEVICE ....... The AVR device you compile for
# CLOCK ........ Target AVR clock rate in Hertz
# OBJECTS ...... The object files created from your source files. This list is
#                usually the same as the list of source files with suffix ".o".
# PROGRAMMER ... Options to avrdude which define the hardware you use for
#                uploading to the AVR and the interface where this hardware
#                is connected.
# FUSES ........ Parameters for avrdude to flash the fuses appropriately.

#DEVICE     = atmega169
DEVICE     = atmega649
CLOCK      = 1000000
PROGRAMMER = -c avrisp -P /dev/ttyACM0 -b 19200 
OBJECTS    = main.o\
    lcd.o  

TSB = tsb /dev/ttyUSB0 fw 

######################################################################
######################################################################

# Tune the lines below only if you know what you are doing:

AVRDUDE = avrdude $(PROGRAMMER) -p $(DEVICE)
COMPILE = avr-gcc -std=gnu99 -Wall -Os -DF_CPU=$(CLOCK) -mmcu=$(DEVICE) $(INC)

# symbolic targets:
all: main.hex

.c.o:
 $(COMPILE) -c $< -o [email protected]

.S.o:
 $(COMPILE) -x assembler-with-cpp -c $< -o [email protected]
# "-x assembler-with-cpp" should not be necessary since this is the default
# file type for the .S (with capital S) extension. However, upper case
# characters are not always preserved on Windows. To ensure WinAVR
# compatibility define the file type manually.

.c.s:
 $(COMPILE) -S $< -o [email protected]

flash: all
 $(AVRDUDE) -U flash:w:main.hex:i

# if you use a bootloader, change the command below appropriately:
load: all
 $(TSB) main.hex

clean:
 rm -f main.hex main.elf $(OBJECTS) *~

# file targets:
main.elf: $(OBJECTS)
 $(COMPILE) -o main.elf $(OBJECTS)

main.hex: main.elf
 rm -f main.hex
 avr-objcopy -j .text -j .data -O ihex main.elf main.hex
# If you have an EEPROM section, you must also create a hex file for the
# EEPROM and add it to the "flash" target.

# Targets for code debugging and analysis:
disasm: main.elf
 avr-objdump -d main.elf

cpp:
 $(COMPILE) -E main.c 

END

The more details will be updated in the subsequent posts