DDS and spurious signals – The role of the post-DDS-amplifier (Part I)

Abstract

When building a direct-digital-synthesis (DDS)  frequency generator, the engineer has to take into account one inherit shortcoming of this state-of-the-art technology: Spurious signals that are an unwanted product of generating a radio frequency signal with a synthesizer. These signals occur in a wide range of frequencies and are a limiting factor for receiver performance particularly sensitivity. A method of examining and evaluating these unwanted signal products will be described and some guidelines for amplifier design will be presented.

Introduction

Spurious signals in a DDS system originate from various signal sources in

  1. the microcontrollers (MCU) driving the DDS. The responsible parts inside the MCU are clock oscillators, dividers, pulse-width-modulation (PWM) timers etc.
  2. the DDS chip itself mainly from clock dividers and the digital-analog-converters (DACs) used.

Various factors contribute to the problem. First the topology of the synthesizer itself. These systems contain a DAC to form a sine wave signal out of a computer calculated synthesizing data model. DACs have a wide variety of bitwidth. A rule of the thumb is: The more bits the DAC has, the lower the number and the weaker the spurious signals will be. 14-bit DACs by ANALOG DEVICES e. g. perform sufficiently for low noise receivers.

Another topic is clock rate. The occurrence of unwanted signals out of the synthesis process is a reciprocal function of clock rate. So it is highly recommended to use the highest possible clock rate the respective chip is designed for. When experimenting with the AD9951 the following findings occured: From 200MHz primary clock the number of spurious signals significantly decreases. The usage of an internal clock multiplier (if available) is not recommended since it will deteriorate phase noise because another oscillator is added to the signal generating chain..

So far the theory that is common today. Another aspect should now be brought into discussion: The role of the stages that are the successors behind the mere synthesizer. First stage usually is an amplifier that is used to lift the signal level of the synthesizer (usually about 1Vpp.) to a level that it is needed for a certain type of mixer.

The DDS circuit

To eliminate any weakness in the basic generator underlying this research a high-level performance synthesizer has been constructed. This ensures a pure sine wave output which is essential because we want to examine the potentially negative outcomes of the various small signal amplifiers succeeding the synthesizer.

The DDS chip used in this circuit is the AD9951 by Analog Devices that incorporates a 14-bit digital-analog-converter (DAC). Clock rate for the chip here is 200MHz (400MHz max. according to data sheet), clock output is 1.8Vpp. which is the maximum signal level that is suitable for this 1.8V-technology based DDS.

The AD9951 DDS integrated circuit needs 2 supply voltages: 1.8V for the digital and analog circuits and 3.3V for DVDD_I/O, the output driver voltage.

Controls lines are 5V applicable which makes the DDS suitable for being controlled by a 5V microcontroller as well as a 3.3V system.

AD9951 DDS synthesizer (DK7IH 2018)
AD9951 DDS synthesizer (by DK7IH 2018)

The signal outlet in this case is made from a symmetrical transformer (3 parallel windings 10 turns each on a FT43-37 core) using the IOUT1 and its corresponding paraphase outlet IOUT2. To use the balanced output is another effective possibility to reduce spurious signals as well as to enhance signal voltage by about 3 to 5 dB.

As first step the unamplified signal shall be inspected. In a wider spectroscopic range (f0=8.65MHz, fgen.=16MHz, f1=250.0MHz) the signal performs as shown below where f0 and f1 are the edge frequencies of the spectrum analyzer and fgen. is the output frequency of the DDS):

AD9951 wide spektrum analysis
AD9951 wide spektrum analysis

A first spurious signal can be detected with a signal level of more than 40dB below the generated signal. The signals at around 100MHz supposedly are strong FM stations in the VHF radio band whose energy from a nearby radio tower is coupled into the laboratory via the short wave antenna cable ending on top of the workbench. The peaks around 200 MHz are likely generated by the DDS clock oscillator.

Switching to a more narrow spectrum, we get this reading:

AD9951 narrow spectroscopic analysis
AD9951 narrow spectroscopic analysis

Remarkable that there is no even first harmonic that should be expected around the 32 MHz region. It is highly probable that this elimination of the 1st harmonic is caused by the symmetrical decoupling of the signal from the DDS. Hence we know that push-pull operated amplifiers reduce distortion and therefore tend to minimize even harmonics production.

Remarkable on the right falling edge of the main peak there another signal occurs hidden by the main curve which requires  further examination.

Examining amplifiers for a DDS system

1.) Bipolar RF preamplifier circuit (adapted from DeMaw et. al., Solid state design for the Radio Amateur)

The first amplifier under test is a simple circuit containing a bipolar transistor. To reduce distortion emitter degeneration and negative feedback (from collector to base) have been installed. This is an amplifier that is often used in rf amplifiers as first stage of the power strip. Therefore it should contribute less to the overall distortion of the circuit.

Overall voltage gain with a 2SC829 RF transistor (fT=230MHz) is 13dB with 1 MHz, decreasing about 3 db per octave, power gain has not been evaluated.

DDS amplfier, DUT 1
DDS amplfier, DUT 1

With the settings of the spectrum analyzer unchanged it turns out that this amplifier obviously produces new signals that are prone to disturb the receiver of a radio where this amplifier is installed:

AD9951_narrow_spektrum_DK7IH_2018_with_amp_1

Signal level is about 2V pp.

One countermeasure is to carefully check the input level of the amplifier. Excessive input voltage will bring the amplifier into the clipping area thus generating IMD products and harmonics. We usually do not only observe the output signal with an oscilloscope but use the spectrum analyzer in parallel. This ensures optimized signal quality.

Proper biassing is essential for this type of amplifier, aside from the linearization described before. The operating point (also referred to as “Q-point) must be set in the middle of the linear part of the IBE->IC function.

bipolar-transistor-ibe-ic-function

Usually this is achieved by applying a positive voltage (for NPN transistor) so that a given “quiescent” current flow through the base-emitter line. In the most simple case a voltage divider with the base connected to the joint of the two resistors works satisfactory.

Any AC voltage applied now will alter the voltage sum of DC (quiescent) and AC around the Q-point.

Vres.= VDC+VAC

whereas VAC=V0*sin(ωt)

(To be continued)

by Peter, DK7IH

Advertisements

Interfacing an LCD12864 (ST7920 controller) to a microcontroller

I am currently planning a new all band transceiver that might be a little larger than the “Microtransceivers” I built this  year. For this project I bought one of those larger graphic display modules that are sold from incountable Chinese vendors:

LCD-12864-ST7920
LCD-12864-ST7920

Unfortunately I did not find any “non-Arduino” modules on the web to meet my requirements. So I decided to write my own code (mainly as an academic exercise 😉 ) in standard C language for the AVR controller family. You will find this source code by the end of this paper.

General aspects to know

What to do first: RTFM! Data sheets for this module are widely available and I strongly recommend reading one of them. The problem is: You can get ones that don’t cover even the minimum you must know to get the thing working. Other are more suitable (Example). Read them before you start! At least once and by skimming. I will only refer to the things that I think are not clear in the data sheets or are controversial between the various versions of the papers.

Hardware: These LCD modules have 2 controllers of the ST7920-type inside. The screen is 128 pixels wide and 64 pixels high. The modules have built-in ROM-based standard character sets like they are familiar from the well-known line oriented modules like the 16×2 ones. But there is also the possibility to drive them in full graphics mode with your own fonts to be used.

The LCD module communicates via 8- or 4-bit parallel mode or a serial “SPI” derivate.

Connectors

The LCD12864 is wired to the rest of the circuit by a 20-pin header strip in 2.54mm  (0.1″) spacing. The pins are

  • VSS (0V or “minus”)
  • VDD (2.7 to 5.5V, “plus”)
  • VO (a contrast set but without function with my module)
  • RS (determining if there is data (pin to VDD i. e. “hi”) or an instruction (pin to VSS i. e. “low”) transferred)
  • RW (“low” when writing data, “high” when reading data from the module)
  • E (the “enabled” pin that goes high when a byte of data is transferred)
  • D0:D7: The parallel data lines
  • PSB (set “low” if you project serial communication or “high” if you project parallel communication (4- or 8 bit bus width  possible))
  • NC: no connection
  • RST: The reset pin, must be pulled to GND for 1 ms or so and then set to high to reset the module when program starts
  • VOUT: A reference voltage but not used in my project
  • BLA: Backlight, connected to +12 V via R=270Ohm
  • BLK: Backlight cathode, connected to GND

If you want to run my software, connect the microcontroller to the LCD module as follows:

  • LCD-Data: PD0..PD7
  • RS: PC0
  • RW: PC1
  • E: PC2
  • RST PC3
  • PSB: GND
  • BLA to +12V via R=270 Ohms
  • BLK to GND

Driving the module by software

In my software I use 8 bit parallel transfer because it is the fastest way to get the data displayed. I use the full PORTD of the microcontroller for this purpose. In general this LCD module is something for the “bigger” controllers if you intend to do something more than just displaying funny messages. 😉

Initializing

Before the first data can be displayed the module must be reset and subsequently initialized:

//Init LCD
void lcd_init(void)
{ 
    //Reset
    PORTC &= ~(8);
    _delay_ms(1);
    PORTC |= 8;
    _delay_ms(40);

    lcd_write(0, 0x30); //Use 8-bit mode parallel
    _delay_ms(1);

    lcd_write(0, 0x0C); //All on Cursor on, Blink on , Display on
    _delay_ms(1);

    lcd_write(0, 0x01); //Perform CLS in text mode to eliminate random chars from screen
    _delay_ms(20);

    lcd_write(0, 0x34); //Switch to extended mode, redefine function set
    _delay_us(100);

    lcd_write(0, 0x36); //Add graphic mode
    _delay_us(100);

    lcd_write(0, 0x12); //Display control and display ON
    _delay_us(100);
}

I found that getting the module ready for work correctly is not easy. I encountered the problem that I still had some random characters of the text mode on my graphics screen after having switched the LCD to graphics mode. To solve this problem I did the initialization procedure in the following manner:

  1. Set 8-Bit parallel mode first,
  2. Switch the module to a standard text module behavior,
  3. Clear the screen,
  4. Switch the module to extended and graphics mode and perform the remaining initialization process.
  5. Clear screen once again and you are “ready for take off”.

After you have switched to the “Extended instruction set” you can access the pixels of the module individually.

How to set and reset pixels in graphics mode

The data sheets I browsed through concerning this aspect were not concise. Following is the correct addressing mode for my module:

GDRAM (Graphics RAM) organisation of LCD12864 (ST7920)
GDRAM (Graphics RAM) organisation of LCD12864 (ST7920)

You can see 16 banks each 16 pixels wide and 32 pixels high. 16 pixels are treated as one integer, consisting of MSB (bit 15:8) and LSB (bit 7:0). They are read from the left side (MSB) to the right end (LSB).

If you want to set a 16 bit section, you must specify the graphics address of the line you want to set. This address is stored in the so-called “GDRAM” (graphics data ram).

X coordinate is the bank starting with no. 0 to no. 15 max. Y coordinate is a number between 0:31 referring to the respective line that shall be set.

Hint: If you are about to access the lower part of the screen, you must use a “bank” numbered >= 8. To prevent the screen from looking “broken” it is necessary to set row back to a value diminished by 0x20 (32 dec.) and the “bank” increased by the value of 0x08 (8 dec.).

if(row & 0x20) //Enter lower part of screen => go to next page bank
{
    row &= ~0x20;
    col |= 8;
}

After having written an address to the module the address counter automatically increases by 1 for the horizontal part of the address. The vertical address remains unchanged. This increment for the X direction can be compensated by defining the specified GDRAM address each time you project to set or reset a 16-bit line of data.

You will totally have to transfer 4 bytes to set one line of 16 pixels:

1. Set vertical address(Y) for GDRAM
2. Set horizontal address(X) for GDRAM
3. Write D15:D8 to GDRAM (first byte)
4. Write D7:D0 to GDRAM (second byte)

(excerpt from data sheet)

Setting GDRAM address

Before you can write data to a GDRAM cell you must state where this data shall be displayed. Therefore you specify the GDRAM (here “DDRAM”) address:

LCD12864_set_CDRAM_adr

DB7 is set to one, so 80H (0x80) has to be added to the address value for each writing.

Here is the software code for this procedure:

//Set address
lcd_write(0, 0x80 + row);  //”0″ means “instruction”
lcd_write(0, 0x80 + col);
lcd_write(1, msb); //”1″ means “data”
lcd_write(1, lsb);

Hint: If you use a font that is only 8 bits wide you will have to cope with the following problem: When writing one character (8 bits wide) you will override the other 8 bits that might already be on the screen because you can only address 16 bits at once. So, before you write a character to the LCD, you have to read the GDRAM for the other half of the character and store this value. Later you assemble both characters (old one and new one) to a complete 16-bit value and write this data back to the LCD. You also have to find out if the new char is the left or the right one of a 16-bit cell.

This works out as:

//Set address
lcd_write(0, 0x80 + row + t1);
lcd_write(0, 0x80 + col);

//Get old values of 2 GDRAM bytes 
v1 = lcd_read(1); //Dummy read required!
v1 = lcd_read(1);
v2 = lcd_read(1);

//Set address
lcd_write(0, 0x80 + row);
lcd_write(0, 0x80 + col);

if(!inv) //Char normal or inverted?
{
    ch = font[ch0 * FONTHEIGHT + t1];
}
else 
{
     ch = ~font[ch0 * FONTHEIGHT + t1];
}

if(odd) //"Odd" or "even" position in 16 bit integer?
{ 
    //Write data on RIGHT side of existing character
    lcd_write(1, v1);
    lcd_write(1, ch);
}
else 
{ 
    //Write data on LEFT side of existing character
    lcd_write(1, ch);
    lcd_write(1, v2);
}

This only prints out one line of the char, please see the full code attached to this article to get the full information for printing the full character to the LCD screen!

How to use my software

I always put all of my code into one C-file because I cannot post ZIP-files with header-files etc. here. Sorry for that!

There are various functions that you can use to write text or data to the screen:

void lcd_putchar(int, int, unsigned char, int);
void lcd_putchar2(int, int, unsigned char, int);
void lcd_putchar3(int, int, unsigned char, int);

  • lcd_putchar() prints a character defined in the font in normal size. If you want to invert the character looking, set the last “int” to 1.
  • lcd_putchar2() produces the same character in double height.
  • lcd_putchar3() set a character in double height and double width.

void lcd_putstring_a(int, int, char*, int, int);
void lcd_putstring_b(int, int, char*, int);

  • lcd_putstring_a() prints a “0”-terminated string to a given position (row, col, string, height (0==normal, 1==double) and inverted.
  • lcd_putstring_b() does the same in double height and double width.

First three parameters are always column, row and data. Following additional information about size, character inverted printing etc. You will find that in the code by the end of this paper.

The most complex function is

  • void lcd_putnumber(int, int, long, int, int, int);

which converts a number to a string and subsequently displays it. Parameters are

void lcd_putnumber(col, row, number, decimal, size, invert);

“Number” can be a long variable or an integer or even char. “Decimal” sets the decimal separator (counted from the right) if wanted, if not set this parameter to “-1”, “Size” is 0 for normal and 1 for double height. “Invert” = 1 for inverted and 0 for normal appearance.

The full software

I apologize for the ugly looking code but the web based “beautifier” and highlighter destroyed my indenting to a max. ! 😦

So, if there are still questions, feel free to mail me: peter(dot)rachow(ät)web(dot)de!

73 de Peter

/*****************************************************************/
/*           LCD12864-ST7920-Demo with ATMega32                  */
/*  ************************************************************ */
/*  Mikrocontroller:  ATMEL AVR ATmega32, 8 MHz                  */
/*                                                               */
/*  Compiler:         GCC (GNU AVR C-Compiler)                   */
/*  Author:           Peter Rachow (DK7IH)                       */
/*  Letzte Aenderung: 2018-12-25                                 */
/*****************************************************************/

// O U T P U T for LCD 

//Connection LCD to uC:
//LCD-Data: PD0..PD7
//RS:       PC0
//RW:       PC1
//E:        PC2
//RST       PC3

#define F_CPU 8000000
#define FONTHEIGHT 8

#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>

#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/sleep.h>
#include <avr/eeprom.h>
#include <util/delay.h>

int main(void);

void lcd_write(char, unsigned char);
char lcd_read(char);
void set_rs(char);
void set_e(char);
void set_rw(char);
int is_lcd_busy(void);
void lcd_init(void);
void lcd_cls(void);
void lcd_putchar(int, int, unsigned char, int);
void lcd_putchar2(int, int, unsigned char, int);
void lcd_putchar3(int, int, unsigned char, int);
void lcd_putstring_a(int, int, char*, int, int);
void lcd_putstring_b(int, int, char*, int);
void lcd_putnumber(int, int, long, int, int, int);

//STRING FUNCTIONS
int int2asc(long, int, char*, int);

//Font for graphics LCD 5x8
unsigned char font[] =
{
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,	// 0x00
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,	// 0x01
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,	// 0x02
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,	// 0x03
0x00,0x04,0x0E,0x1F,0x1F,0x0E,0x04,0x00,	// 0x04
0x04,0x0E,0x0E,0x04,0x1F,0x1F,0x04,0x00,	// 0x05
0x00,0x04,0x0E,0x1F,0x1F,0x04,0x0E,0x00,	// 0x06
0x0E,0x1F,0x15,0x1F,0x11,0x1F,0x0E,0x00,	// 0x07
0x0E,0x11,0x1B,0x11,0x15,0x11,0x0E,0x00,	// 0x08
0x00,0x0A,0x1F,0x1F,0x1F,0x0E,0x04,0x00,	// 0x09
0x0E,0x11,0x1B,0x11,0x15,0x11,0x0E,0x00,	// 0x0A
0x00,0x07,0x03,0x0D,0x12,0x12,0x0C,0x00,	// 0x0B
0x0E,0x11,0x11,0x0E,0x04,0x0E,0x04,0x00,	// 0x0C
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,	// 0x0D
0x03,0x0D,0x0B,0x0D,0x0B,0x1B,0x18,0x00,	// 0x0E
0x00,0x15,0x0E,0x1B,0x0E,0x15,0x00,0x00,	// 0x0F
0x08,0x0C,0x0E,0x0F,0x0E,0x0C,0x08,0x00,	// 0x10
0x02,0x06,0x0E,0x1E,0x0E,0x06,0x02,0x00,	// 0x11
0x04,0x0E,0x1F,0x04,0x1F,0x0E,0x04,0x00,	// 0x12
0x0A,0x0A,0x0A,0x0A,0x0A,0x00,0x0A,0x00,	// 0x13
0x0F,0x15,0x15,0x0D,0x05,0x05,0x05,0x00,	// 0x14
0x0E,0x11,0x0C,0x0A,0x06,0x11,0x0E,0x00,	// 0x15
0x00,0x00,0x00,0x00,0x00,0x1E,0x1E,0x00,	// 0x16
0x04,0x0E,0x1F,0x04,0x1F,0x0E,0x04,0x0E,	// 0x17
0x04,0x0E,0x1F,0x04,0x04,0x04,0x04,0x00,	// 0x18
0x04,0x04,0x04,0x04,0x1F,0x0E,0x04,0x00,	// 0x19
0x00,0x04,0x06,0x1F,0x06,0x04,0x00,0x00,	// 0x1A
0x00,0x04,0x0C,0x1F,0x0C,0x04,0x00,0x00,	// 0x1B
0x00,0x00,0x00,0x10,0x10,0x10,0x1F,0x00,	// 0x1C
0x00,0x0A,0x0A,0x1F,0x0A,0x0A,0x00,0x00,	// 0x1D
0x04,0x04,0x0E,0x0E,0x1F,0x1F,0x00,0x00,	// 0x1E
0x1F,0x1F,0x0E,0x0E,0x04,0x04,0x00,0x00,	// 0x1F
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,	// 0x20
0x04,0x0E,0x0E,0x04,0x04,0x00,0x04,0x00,	// 0x21
0x1B,0x1B,0x12,0x00,0x00,0x00,0x00,0x00,	// 0x22
0x00,0x0A,0x1F,0x0A,0x0A,0x1F,0x0A,0x00,	// 0x23
0x08,0x0E,0x10,0x0C,0x02,0x1C,0x04,0x00,	// 0x24
0x19,0x19,0x02,0x04,0x08,0x13,0x13,0x00,	// 0x25
0x08,0x14,0x14,0x08,0x15,0x12,0x0D,0x00,	// 0x26
0x0C,0x0C,0x08,0x00,0x00,0x00,0x00,0x00,	// 0x27
0x04,0x08,0x08,0x08,0x08,0x08,0x04,0x00,	// 0x28
0x08,0x04,0x04,0x04,0x04,0x04,0x08,0x00,	// 0x29
0x00,0x0A,0x0E,0x1F,0x0E,0x0A,0x00,0x00,	// 0x2A
0x00,0x04,0x04,0x1F,0x04,0x04,0x00,0x00,	// 0x2B
0x00,0x00,0x00,0x00,0x00,0x0C,0x0C,0x08,	// 0x2C
0x00,0x00,0x00,0x1F,0x00,0x00,0x00,0x00,	// 0x2D
0x00,0x00,0x00,0x00,0x00,0x0C,0x0C,0x00,	// 0x2E
0x00,0x01,0x02,0x04,0x08,0x10,0x00,0x00,	// 0x2F
0x0E,0x11,0x13,0x15,0x19,0x11,0x0E,0x00,	// 0x30
0x04,0x0C,0x04,0x04,0x04,0x04,0x0E,0x00,	// 0x31
0x0E,0x11,0x01,0x06,0x08,0x10,0x1F,0x00,	// 0x32
0x0E,0x11,0x01,0x0E,0x01,0x11,0x0E,0x00,	// 0x33
0x02,0x06,0x0A,0x12,0x1F,0x02,0x02,0x00,	// 0x34
0x1F,0x10,0x10,0x1E,0x01,0x11,0x0E,0x00,	// 0x35
0x06,0x08,0x10,0x1E,0x11,0x11,0x0E,0x00,	// 0x36
0x1F,0x01,0x02,0x04,0x08,0x08,0x08,0x00,	// 0x37
0x0E,0x11,0x11,0x0E,0x11,0x11,0x0E,0x00,	// 0x38
0x0E,0x11,0x11,0x0F,0x01,0x02,0x0C,0x00,	// 0x39
0x00,0x00,0x0C,0x0C,0x00,0x0C,0x0C,0x00,	// 0x3A
0x00,0x00,0x0C,0x0C,0x00,0x0C,0x0C,0x08,	// 0x3B
0x02,0x04,0x08,0x10,0x08,0x04,0x02,0x00,	// 0x3C
0x00,0x00,0x1F,0x00,0x00,0x1F,0x00,0x00,	// 0x3D
0x08,0x04,0x02,0x01,0x02,0x04,0x08,0x00,	// 0x3E
0x0E,0x11,0x01,0x06,0x04,0x00,0x04,0x00,	// 0x3F
0x0E,0x11,0x17,0x15,0x17,0x10,0x0E,0x00,	// 0x40
0x0E,0x11,0x11,0x11,0x1F,0x11,0x11,0x00,	// 0x41
0x1E,0x11,0x11,0x1E,0x11,0x11,0x1E,0x00,	// 0x42
0x0E,0x11,0x10,0x10,0x10,0x11,0x0E,0x00,	// 0x43
0x1E,0x11,0x11,0x11,0x11,0x11,0x1E,0x00,	// 0x44
0x1F,0x10,0x10,0x1E,0x10,0x10,0x1F,0x00,	// 0x45
0x1F,0x10,0x10,0x1E,0x10,0x10,0x10,0x00,	// 0x46
0x0E,0x11,0x10,0x17,0x11,0x11,0x0F,0x00,	// 0x47
0x11,0x11,0x11,0x1F,0x11,0x11,0x11,0x00,	// 0x48
0x0E,0x04,0x04,0x04,0x04,0x04,0x0E,0x00,	// 0x49
0x01,0x01,0x01,0x01,0x11,0x11,0x0E,0x00,	// 0x4A
0x11,0x12,0x14,0x18,0x14,0x12,0x11,0x00,	// 0x4B
0x10,0x10,0x10,0x10,0x10,0x10,0x1F,0x00,	// 0x4C
0x11,0x1B,0x15,0x11,0x11,0x11,0x11,0x00,	// 0x4D
0x11,0x19,0x15,0x13,0x11,0x11,0x11,0x00,	// 0x4E
0x0E,0x11,0x11,0x11,0x11,0x11,0x0E,0x00,	// 0x4F
0x1E,0x11,0x11,0x1E,0x10,0x10,0x10,0x00,	// 0x50
0x0E,0x11,0x11,0x11,0x15,0x12,0x0D,0x00,	// 0x51
0x1E,0x11,0x11,0x1E,0x12,0x11,0x11,0x00,	// 0x52
0x0E,0x11,0x10,0x0E,0x01,0x11,0x0E,0x00,	// 0x53
0x1F,0x04,0x04,0x04,0x04,0x04,0x04,0x00,	// 0x54
0x11,0x11,0x11,0x11,0x11,0x11,0x0E,0x00,	// 0x55
0x11,0x11,0x11,0x11,0x11,0x0A,0x04,0x00,	// 0x56
0x11,0x11,0x15,0x15,0x15,0x15,0x0A,0x00,	// 0x57
0x11,0x11,0x0A,0x04,0x0A,0x11,0x11,0x00,	// 0x58
0x11,0x11,0x11,0x0A,0x04,0x04,0x04,0x00,	// 0x59
0x1E,0x02,0x04,0x08,0x10,0x10,0x1E,0x00,	// 0x5A
0x0E,0x08,0x08,0x08,0x08,0x08,0x0E,0x00,	// 0x5B
0x00,0x10,0x08,0x04,0x02,0x01,0x00,0x00,	// 0x5C
0x0E,0x02,0x02,0x02,0x02,0x02,0x0E,0x00,	// 0x5D
0x04,0x0A,0x11,0x00,0x00,0x00,0x00,0x00,	// 0x5E
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x3F,	// 0x5F
0x0C,0x0C,0x04,0x00,0x00,0x00,0x00,0x00,	// 0x60
0x00,0x00,0x0E,0x01,0x0F,0x11,0x0F,0x00,	// 0x61
0x10,0x10,0x1E,0x11,0x11,0x11,0x1E,0x00,	// 0x62
0x00,0x00,0x0E,0x11,0x10,0x11,0x0E,0x00,	// 0x63
0x01,0x01,0x0F,0x11,0x11,0x11,0x0F,0x00,	// 0x64
0x00,0x00,0x0E,0x11,0x1E,0x10,0x0E,0x00,	// 0x65
0x06,0x08,0x08,0x1E,0x08,0x08,0x08,0x00,	// 0x66
0x00,0x00,0x0F,0x11,0x11,0x0F,0x01,0x0E,	// 0x67
0x10,0x10,0x1C,0x12,0x12,0x12,0x12,0x00,	// 0x68
0x04,0x00,0x04,0x04,0x04,0x04,0x06,0x00,	// 0x69
0x02,0x00,0x06,0x02,0x02,0x02,0x12,0x0C,	// 0x6A
0x10,0x10,0x12,0x14,0x18,0x14,0x12,0x00,	// 0x6B
0x04,0x04,0x04,0x04,0x04,0x04,0x06,0x00,	// 0x6C
0x00,0x00,0x1A,0x15,0x15,0x11,0x11,0x00,	// 0x6D
0x00,0x00,0x1C,0x12,0x12,0x12,0x12,0x00,	// 0x6E
0x00,0x00,0x0E,0x11,0x11,0x11,0x0E,0x00,	// 0x6F
0x00,0x00,0x1E,0x11,0x11,0x11,0x1E,0x10,	// 0x70
0x00,0x00,0x0F,0x11,0x11,0x11,0x0F,0x01,	// 0x71
0x00,0x00,0x16,0x09,0x08,0x08,0x1C,0x00,	// 0x72
0x00,0x00,0x0E,0x10,0x0E,0x01,0x0E,0x00,	// 0x73
0x00,0x08,0x1E,0x08,0x08,0x0A,0x04,0x00,	// 0x74
0x00,0x00,0x12,0x12,0x12,0x16,0x0A,0x00,	// 0x75
0x00,0x00,0x11,0x11,0x11,0x0A,0x04,0x00,	// 0x76
0x00,0x00,0x11,0x11,0x15,0x1F,0x0A,0x00,	// 0x77
0x00,0x00,0x12,0x12,0x0C,0x12,0x12,0x00,	// 0x78
0x00,0x00,0x12,0x12,0x12,0x0E,0x04,0x18,	// 0x79
0x00,0x00,0x1E,0x02,0x0C,0x10,0x1E,0x00,	// 0x7A
0x06,0x08,0x08,0x18,0x08,0x08,0x06,0x00,	// 0x7B
0x04,0x04,0x04,0x00,0x04,0x04,0x04,0x00,	// 0x7C
0x0C,0x02,0x02,0x03,0x02,0x02,0x0C,0x00,	// 0x7D
0x0A,0x14,0x00,0x00,0x00,0x00,0x00,0x00,	// 0x7E
0x04,0x0E,0x1B,0x11,0x11,0x1F,0x00,0x00,	// 0x7F
0x0E,0x11,0x10,0x10,0x11,0x0E,0x04,0x0C,	// 0x80
/* 

//If you operate a microcontroller with more memory space 
//than an ATmega32 you can also use the following 127 characters!

0x12,0x00,0x12,0x12,0x12,0x16,0x0A,0x00,	// 0x81
0x03,0x00,0x0E,0x11,0x1E,0x10,0x0E,0x00,	// 0x82
0x0E,0x00,0x0E,0x01,0x0F,0x11,0x0F,0x00,	// 0x83
0x0A,0x00,0x0E,0x01,0x0F,0x11,0x0F,0x00,	// 0x84
0x0C,0x00,0x0E,0x01,0x0F,0x11,0x0F,0x00,	// 0x85
0x0E,0x0A,0x0E,0x01,0x0F,0x11,0x0F,0x00,	// 0x86
0x00,0x0E,0x11,0x10,0x11,0x0E,0x04,0x0C,	// 0x87
0x0E,0x00,0x0E,0x11,0x1E,0x10,0x0E,0x00,	// 0x88
0x0A,0x00,0x0E,0x11,0x1E,0x10,0x0E,0x00,	// 0x89
0x0C,0x00,0x0E,0x11,0x1E,0x10,0x0E,0x00,	// 0x8A
0x0A,0x00,0x04,0x04,0x04,0x04,0x06,0x00,	// 0x8B
0x0E,0x00,0x04,0x04,0x04,0x04,0x06,0x00,	// 0x8C
0x08,0x00,0x04,0x04,0x04,0x04,0x06,0x00,	// 0x8D
0x0A,0x00,0x04,0x0A,0x11,0x1F,0x11,0x00,	// 0x8E
0x0E,0x0A,0x0E,0x1B,0x11,0x1F,0x11,0x00,	// 0x8F
0x03,0x00,0x1F,0x10,0x1E,0x10,0x1F,0x00,	// 0x90
0x00,0x00,0x1E,0x05,0x1F,0x14,0x0F,0x00,	// 0x91
0x0F,0x14,0x14,0x1F,0x14,0x14,0x17,0x00,	// 0x92
0x0E,0x00,0x0C,0x12,0x12,0x12,0x0C,0x00,	// 0x93
0x0A,0x00,0x0C,0x12,0x12,0x12,0x0C,0x00,	// 0x94
0x18,0x00,0x0C,0x12,0x12,0x12,0x0C,0x00,	// 0x95
0x0E,0x00,0x12,0x12,0x12,0x16,0x0A,0x00,	// 0x96
0x18,0x00,0x12,0x12,0x12,0x16,0x0A,0x00,	// 0x97
0x0A,0x00,0x12,0x12,0x12,0x0E,0x04,0x18,	// 0x98
0x12,0x0C,0x12,0x12,0x12,0x12,0x0C,0x00,	// 0x99
0x0A,0x00,0x12,0x12,0x12,0x12,0x0C,0x00,	// 0x9A
0x00,0x00,0x01,0x0E,0x16,0x1A,0x1C,0x20,	// 0x9B
0x06,0x09,0x08,0x1E,0x08,0x09,0x17,0x00,	// 0x9C
0x0F,0x13,0x15,0x15,0x15,0x19,0x1E,0x00,	// 0x9D
0x00,0x11,0x0A,0x04,0x0A,0x11,0x00,0x00,	// 0x9E
0x02,0x05,0x04,0x0E,0x04,0x04,0x14,0x08,	// 0x9F
0x06,0x00,0x0E,0x01,0x0F,0x11,0x0F,0x00,	// 0xA0
0x06,0x00,0x04,0x04,0x04,0x04,0x06,0x00,	// 0xA1
0x06,0x00,0x0C,0x12,0x12,0x12,0x0C,0x00,	// 0xA2
0x06,0x00,0x12,0x12,0x12,0x16,0x0A,0x00,	// 0xA3
0x0A,0x14,0x00,0x1C,0x12,0x12,0x12,0x00,	// 0xA4
0x0A,0x14,0x00,0x12,0x1A,0x16,0x12,0x00,	// 0xA5
0x0E,0x01,0x0F,0x11,0x0F,0x00,0x0F,0x00,	// 0xA6
0x0C,0x12,0x12,0x12,0x0C,0x00,0x1E,0x00,	// 0xA7
0x04,0x00,0x04,0x0C,0x10,0x11,0x0E,0x00,	// 0xA8
0x1E,0x25,0x2B,0x2D,0x2B,0x21,0x1E,0x00,	// 0xA9
0x00,0x00,0x3F,0x01,0x01,0x00,0x00,0x00,	// 0xAA
0x10,0x12,0x14,0x0E,0x11,0x02,0x07,0x00,	// 0xAB
0x10,0x12,0x14,0x0B,0x15,0x07,0x01,0x00,	// 0xAC
0x04,0x00,0x04,0x04,0x0E,0x0E,0x04,0x00,	// 0xAD
0x00,0x00,0x09,0x12,0x09,0x00,0x00,0x00,	// 0xAE
0x00,0x00,0x12,0x09,0x12,0x00,0x00,0x00,	// 0xAF
0x15,0x00,0x2A,0x00,0x15,0x00,0x2A,0x00,	// 0xB0
0x15,0x2A,0x15,0x2A,0x15,0x2A,0x15,0x2A,	// 0xB1
0x2A,0x3F,0x15,0x3F,0x2A,0x3F,0x15,0x3F,	// 0xB2
0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04,	// 0xB3
0x04,0x04,0x04,0x3C,0x04,0x04,0x04,0x04,	// 0xB4
0x06,0x00,0x04,0x0A,0x11,0x1F,0x11,0x00,	// 0xB5
0x0E,0x00,0x04,0x0A,0x11,0x1F,0x11,0x00,	// 0xB6
0x0C,0x00,0x04,0x0A,0x11,0x1F,0x11,0x00,	// 0xB7
0x1E,0x21,0x2D,0x29,0x2D,0x21,0x1E,0x00,	// 0xB8
0x14,0x34,0x04,0x34,0x14,0x14,0x14,0x14,	// 0xB9
0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,	// 0xBA
0x00,0x3C,0x04,0x34,0x14,0x14,0x14,0x14,	// 0xBB
0x14,0x34,0x04,0x3C,0x00,0x00,0x00,0x00,	// 0xBC
0x00,0x04,0x0E,0x10,0x10,0x0E,0x04,0x00,	// 0xBD
0x11,0x0A,0x04,0x1F,0x04,0x1F,0x04,0x00,	// 0xBE
0x00,0x00,0x00,0x3C,0x04,0x04,0x04,0x04,	// 0xBF
0x04,0x04,0x04,0x07,0x00,0x00,0x00,0x00,	// 0xC0
0x04,0x04,0x04,0x3F,0x00,0x00,0x00,0x00,	// 0xC1
0x00,0x00,0x00,0x3F,0x04,0x04,0x04,0x04,	// 0xC2
0x04,0x04,0x04,0x07,0x04,0x04,0x04,0x04,	// 0xC3
0x00,0x00,0x00,0x3F,0x00,0x00,0x00,0x00,	// 0xC4
0x04,0x04,0x04,0x3F,0x04,0x04,0x04,0x04,	// 0xC5
0x05,0x0A,0x0E,0x01,0x0F,0x11,0x0F,0x00,	// 0xC6
0x05,0x0A,0x04,0x0A,0x11,0x1F,0x11,0x00,	// 0xC7
0x14,0x17,0x10,0x1F,0x00,0x00,0x00,0x00,	// 0xC8
0x00,0x1F,0x10,0x17,0x14,0x14,0x14,0x14,	// 0xC9
0x14,0x37,0x00,0x3F,0x00,0x00,0x00,0x00,	// 0xCA
0x00,0x3F,0x00,0x37,0x14,0x14,0x14,0x14,	// 0xCB
0x14,0x17,0x10,0x17,0x14,0x14,0x14,0x14,	// 0xCC
0x00,0x3F,0x00,0x3F,0x00,0x00,0x00,0x00,	// 0xCD
0x14,0x37,0x00,0x37,0x14,0x14,0x14,0x14,	// 0xCE
0x11,0x0E,0x11,0x11,0x11,0x0E,0x11,0x00,	// 0xCF
0x0C,0x10,0x08,0x04,0x0E,0x12,0x0C,0x00,	// 0xD0
0x0E,0x09,0x09,0x1D,0x09,0x09,0x0E,0x00,	// 0xD1
0x0E,0x00,0x1F,0x10,0x1E,0x10,0x1F,0x00,	// 0xD2
0x0A,0x00,0x1F,0x10,0x1E,0x10,0x1F,0x00,	// 0xD3
0x0C,0x00,0x1F,0x10,0x1E,0x10,0x1F,0x00,	// 0xD4
0x04,0x04,0x04,0x00,0x00,0x00,0x00,0x00,	// 0xD5
0x06,0x00,0x0E,0x04,0x04,0x04,0x0E,0x00,	// 0xD6
0x0E,0x00,0x0E,0x04,0x04,0x04,0x0E,0x00,	// 0xD7
0x0A,0x00,0x0E,0x04,0x04,0x04,0x0E,0x00,	// 0xD8
0x04,0x04,0x04,0x3C,0x00,0x00,0x00,0x00,	// 0xD9
0x00,0x00,0x00,0x07,0x04,0x04,0x04,0x04,	// 0xDA
0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,	// 0xDB
0x00,0x00,0x00,0x00,0x3F,0x3F,0x3F,0x3F,	// 0xDC
0x04,0x04,0x04,0x00,0x04,0x04,0x04,0x00,	// 0xDD
0x0C,0x00,0x0E,0x04,0x04,0x04,0x0E,0x00,	// 0xDE
0x3F,0x3F,0x3F,0x3F,0x00,0x00,0x00,0x00,	// 0xDF
0x06,0x0C,0x12,0x12,0x12,0x12,0x0C,0x00,	// 0xE0
0x00,0x1C,0x12,0x1C,0x12,0x12,0x1C,0x10,	// 0xE1
0x0E,0x0C,0x12,0x12,0x12,0x12,0x0C,0x00,	// 0xE2
0x18,0x0C,0x12,0x12,0x12,0x12,0x0C,0x00,	// 0xE3
0x0A,0x14,0x00,0x0C,0x12,0x12,0x0C,0x00,	// 0xE4
0x0A,0x14,0x0C,0x12,0x12,0x12,0x0C,0x00,	// 0xE5
0x00,0x00,0x12,0x12,0x12,0x1C,0x10,0x10,	// 0xE6
0x00,0x18,0x10,0x1C,0x12,0x1C,0x10,0x18,	// 0xE7
0x18,0x10,0x1C,0x12,0x12,0x1C,0x10,0x18,	// 0xE8
0x06,0x00,0x12,0x12,0x12,0x12,0x0C,0x00,	// 0xE9
0x0E,0x00,0x12,0x12,0x12,0x12,0x0C,0x00,	// 0xEA
0x18,0x00,0x12,0x12,0x12,0x12,0x0C,0x00,	// 0xEB
0x06,0x00,0x12,0x12,0x12,0x0E,0x04,0x18,	// 0xEC
0x06,0x00,0x11,0x0A,0x04,0x04,0x04,0x00,	// 0xED
0x00,0x0E,0x00,0x00,0x00,0x00,0x00,0x00,	// 0xEE
0x0C,0x0C,0x08,0x00,0x00,0x00,0x00,0x00,	// 0xEF
0x00,0x00,0x00,0x0E,0x00,0x00,0x00,0x00,	// 0xF0
0x00,0x04,0x0E,0x04,0x00,0x0E,0x00,0x00,	// 0xF1
0x00,0x00,0x1F,0x00,0x00,0x1F,0x00,0x00,	// 0xF2
0x30,0x1A,0x34,0x0B,0x15,0x07,0x01,0x00,	// 0xF3
0x0F,0x15,0x15,0x0D,0x05,0x05,0x05,0x00,	// 0xF4
0x0E,0x11,0x0C,0x0A,0x06,0x11,0x0E,0x00,	// 0xF5
0x00,0x04,0x00,0x1F,0x00,0x04,0x00,0x00,	// 0xF6
0x00,0x00,0x00,0x0E,0x06,0x00,0x00,0x00,	// 0xF7
0x0C,0x12,0x12,0x0C,0x00,0x00,0x00,0x00,	// 0xF8
0x00,0x00,0x00,0x0A,0x00,0x00,0x00,0x00,	// 0xF9
0x00,0x00,0x00,0x08,0x00,0x00,0x00,0x00,	// 0xFA
0x08,0x18,0x08,0x08,0x00,0x00,0x00,0x00,	// 0xFB
0x1C,0x08,0x0C,0x18,0x00,0x00,0x00,0x00,	// 0xFC
0x18,0x04,0x08,0x1C,0x00,0x00,0x00,0x00,	// 0xFD
0x00,0x00,0x1E,0x1E,0x1E,0x1E,0x00,0x00,	// 0xFE
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 	// 0xFF
*/
};
	
  /////////////////////////////////////
 // Functions for LCD12864 control  //
/////////////////////////////////////
//Write instruction (code==0) or data (code==1) to LCD
void lcd_write(char lcdmode, unsigned char value)
{
	DDRD = 0xFF;     //Set port for write operation
	
	set_rw(0);	     //Write operation
	set_rs(lcdmode); //0 for instruction, 1 for data
       
	PORTD = value;
    set_e(1);
    _delay_us(10);
	set_e(0);	
		
	set_rs(0);
}    

//Read data from LCD
char lcd_read(char lcdmode)
{
	unsigned char value;
	
	DDRD = 0x00;       //Set port for read operation
	//PORTD = 0xFF;      //Activate pull-up resistors to ensure proper data transmission
    
    set_rw(1);	       //Read operation
	set_rs(lcdmode);   //Get value 0: for busy flag, 1 for other data
	
	set_e(1);          //Read data
    _delay_us(10);       
	value = PIND;
    set_e(0);	
	
	//set_rs(0);
	
	return value;
}    

//Set RW line
void set_rw(char status)  
{
    if(status)
	{
        PORTC |= 2;
	}	
    else
	{
	    PORTC &= ~(2);
	}	
}

//Set RS line
void set_rs(char status) 
{
    if(status)
	{
        PORTC |= 1;
	}	
    else
	{
	    PORTC &= ~(1);
	}	
}

//Set E line
void set_e(char status)  
{
    if(status)
	{
        PORTC |= 4;
	}	
    else
	{
	    PORTC &= ~(4);
	}	
}

//Check for busy flag (BF)
int is_lcd_busy(void)
{
	int v = lcd_read(0);
	_delay_us(10);
	v = lcd_read(0);
			
	if(v & 0x80)
	{
		return -1;
	}
	else
	{
		return 0;
	}	
		
}

//Send one character to LCD (Normal size)
//
void lcd_putchar(int row0, int col0, unsigned char ch0, int inv)
{
	int t1;
	int odd = 0;
	unsigned char v1, v2;
	int col = col0 / 2;
	int row = row0 * FONTHEIGHT;
	unsigned char ch;
		
	if(row & 0x20)  //Enter lower part of screen => go to next page
	{
        row &= ~0x20;
        col |= 8;
    }
        
	if(col0 & 1) //Detect odd coloumn
	{
		odd = 1;
	}
		
	for(t1 = 0; t1 < FONTHEIGHT; t1++)
    {
	    //Set address
        lcd_write(0, 0x80 + row + t1);
        lcd_write(0, 0x80 + col);
             
        //Get old values of 2 GDRAM bytes	
	    v1 = lcd_read(1);                
        v1 = lcd_read(1);
        v2 = lcd_read(1);

        //Set address
        lcd_write(0, 0x80 + row + t1);
        lcd_write(0, 0x80 + col);
        
        if(!inv)
        {
			ch = font[ch0 * FONTHEIGHT + t1];
		}
		else	
        {
			ch = ~font[ch0 * FONTHEIGHT + t1];
		}
		     
        if(odd)
        {     
            //Write data on RIGHT side of existing character
            lcd_write(1, v1);
            lcd_write(1, ch);
        }
        else    
        {   
			//Write data on LEFT side of existing character
            lcd_write(1, ch);
            lcd_write(1, v2);
        }
    }    
}   

//Send one character to LCD (DOUBLE size and normal width)
//
void lcd_putchar2(int row0, int col0, unsigned char ch0, int inv)
{
	int t1, t2;
	int odd = 0;
	unsigned char v1, v2;
	int col = col0 >> 1;
	int row = row0 * FONTHEIGHT;
	unsigned char ch;
		
	if(row & 0x20)  //Enter lower part of screen => go to next page
	{
        row &= ~0x20;
        col |= 8;
    }
        
	if(col0 & 1) //Detect odd coloumn
	{
		odd = 1;
	}
		
	for(t1 = 0; t1 < FONTHEIGHT; t1++)
    {
		if(!inv) //Calculate character position in array and xor invert number if needed
        {
			ch = (font[ch0 * FONTHEIGHT + t1]);
		}
		else	
        {
			ch = (~font[ch0 * FONTHEIGHT + t1]);
		}
		
		for(t2 = 0; t2 < 2; t2++)
		{
	        //Set address
            lcd_write(0, 0x80 + row + t1 * 2 + t2);
            lcd_write(0, 0x80 + col);
             
            //Get old values of 2 GDRAM bytes	
	        v1 = lcd_read(1);                
            v1 = lcd_read(1);
            v2 = lcd_read(1);

            //Set address
            lcd_write(0, 0x80 + row + t1 * 2 + t2);
            lcd_write(0, 0x80 + col);
        		     
            if(odd)
            {     
                //Write data on RIGHT side of existing character
                lcd_write(1, v1);
                lcd_write(1, ch);
            }
            else    
            {   
			    //Write data on LEFT side of existing character
                lcd_write(1, ch);
                lcd_write(1, v2);
            }    
        }
    }    
}   

//Send one character to LCD (DOUBLE size and DOUBLE width)
//
void lcd_putchar3(int row0, int col0, unsigned char ch0, int inv)
{
	int t1, t2;
	unsigned char ch;
	//unsigned int i[16] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
	unsigned int i[FONTHEIGHT] = {0, 0, 0, 0, 0, 0, 0, 0};
	int col = col0 >> 1;
	int row = row0 * FONTHEIGHT;
	
	if(row & 0x20)  //Enter lower part of screen => go to next page
	{
        row &= ~0x20;
        col |= 8;
    }
        
		
	for(t1 = 0; t1 < FONTHEIGHT; t1++)
    {
		if(!inv) //Calculate character position in array and xor invert number if needed
        {
			ch = (font[ch0 * FONTHEIGHT + t1]);
		}
		else	
        {
			ch = (~font[ch0 * FONTHEIGHT + t1]);
		}
		
		//Double 8 to 16 bits
	    i[t1] = 0;
		for(t2 = 7; t2 > -1; t2--)
		{
			if(ch & (1 << t2))
			{
				i[t1] += (1 << ((t2 << 1) + 1)) + (1 << (t2 << 1)); //Double bit pattern 2 by 1
			}
		}
	}
	
	t2 = 0;
	for(t1 = 0; t1 < FONTHEIGHT; t1++)
	{
		for(t2 = 0; t2 < 2; t2++)
		{
	        //Set address
            lcd_write(0, 0x80 + row + t1 * 2 + t2);
            lcd_write(0, 0x80 + col);
                       
            lcd_write(1, ((i[t1] >> 8) & 0xFF));
            lcd_write(1, i[t1] & 0xFF); 
            //lcd_putnumber(t1, 0, i[t1] , -1, 0, 0);
        }    
    }    
		
}	

//Send string (\0 terminated) to LCD normal or double height
void lcd_putstring_a(int row, int col, char *s, int size, int inv)
{
    unsigned char t1;

    for(t1 = col; *(s); t1++)
	{
		if(!size)
		{
            lcd_putchar(row, t1, *(s++), inv);
        }
        else    
        {
            lcd_putchar2(row, t1, *(s++), inv);
        }
	}	
}

//String in DOUBLE height and DOUBLE width
void lcd_putstring_b(int row, int col, char *s, int inv)
{
    unsigned char t1;

    for(t1 = col; *(s); t1++)
	{
	    lcd_putchar3(row, t1 * 2, *(s++), inv);
	}	
}

//Clear LCD
void lcd_cls(void)
{
	int x, y;
    for(x = 0; x < 16; x++)
    {
		for(y = 0; y < 64; y++)
		{
			//Set address
            lcd_write(0, 0x80 + y);
            lcd_write(0, 0x80 + x);
             
            //Write data
            lcd_write(1, 0);
            lcd_write(1, 0);
        }
    }    
}

//Convert a number to a string and print it
//col, row: Coordinates, Num: int or long to be displayed
//dec: Set position of decimal separator
//
//inv: Set to 1 if inverted charactor is required
void lcd_putnumber(int col, int row, long num, int dec, int lsize, int inv)
{
    char *s = malloc(16);
	if(s != NULL)
	{
	    int2asc(num, dec, s, 16);
	    lcd_putstring_a(col, row, s, lsize, inv);
	    free(s);
	}	
	else
	{
		lcd_putstring_a(col, row, "Error", 0, 0);
	}	
}


//Init LCD
void lcd_init(void)
{            
    //Reset
    PORTC &= ~(8);
    _delay_ms(1);
    PORTC |= 8;
    _delay_ms(40);
    
    lcd_write(0, 0x30);	//Use 8-bit mode parallel
    _delay_ms(1);
         
    lcd_write(0, 0x0C); //All on Cursor on, Blink on , Display on
    _delay_ms(1);
        
    lcd_write(0, 0x01); //Perform CLS in text mode to eliminate random chars from screen
    _delay_ms(20);
    
    lcd_write(0, 0x34); //Switch to extended mode, redefine function set
    _delay_us(100);
    
    lcd_write(0, 0x36); //Add graphic mode
    _delay_us(100);
                   
    lcd_write(0, 0x12); //Display control and display ON
    _delay_us(100);
}

//////////////////////
// STRING FUNCTIONS //
//////////////////////
//INT 2 ASC
int int2asc(long num, int dec, char *buf, int buflen)
{
    int i, c, xp = 0, neg = 0;
    long n, dd = 1E09;

    if(!num)
	{
	    *buf++ = '0';
		*buf = 0;
		return 1;
	}	
		
    if(num < 0)
    {
     	neg = 1;
	    n = num * -1;
    }
    else
    {
	    n = num;
    }

    //Fill buffer with \0
    for(i = 0; i < 12; i++)
    {
	    *(buf + i) = 0;
    }

    c = 9; //Max. number of displayable digits
    while(dd)
    {
	    i = n / dd;
	    n = n - i * dd;
	
	    *(buf + 9 - c + xp) = i + 48;
	    dd /= 10;
	    if(c == dec && dec)
	    {
	        *(buf + 9 - c + ++xp) = '.';
	    }
	    c--;
    }

    //Search for 1st char different from '0'
    i = 0;
    while(*(buf + i) == 48)
    {
	    *(buf + i++) = 32;
    }

    //Add minus-sign if neccessary
    if(neg)
    {
	    *(buf + --i) = '-';
    }

    //Eleminate leading spaces
    c = 0;
    while(*(buf + i))
    {
	    *(buf + c++) = *(buf + i++);
    }
    *(buf + c) = 0;
	
	return c;
}


int main(void)
{
    // Set ports for LCD output and input data
    DDRC = 0x0F; //LCD RS, RW, E and RST at PC0:PC3
	DDRD = 0xFF; //LCD data on PD0:PD7
   	                 		
	//Display init
	_delay_ms(100);
    lcd_init();
	_delay_ms(100);
	
    lcd_cls();
        
    lcd_putstring_a(0, 0, "LCD 12864 ST7920", 0, 0);
    lcd_putstring_a(1, 0, "   DK7IH 2018   ", 0, 1);
    lcd_putstring_a(2, 0, "Graphical Fonts:", 0, 0);
    lcd_putstring_a(3, 0, "8x8px.", 0, 0);
    lcd_putnumber(4, 0, 1234, 1, 0, 0);    
       
	lcd_putstring_a(4, 0, "16x8px.", 1, 0);
	
	lcd_putstring_b(6, 0, "16x16px.", 0);
			    
    for(;;) 
	{
    
    
	}
	return 0;
}