Complete Introduction to AVR Microcontroller Programming: From Setup to First Program
Atmel AVR 8-bit and 32-bit microcontrollers deliver a unique combination of performance, power efficiency, and design …
By Prabeesh Keezhathra
- 7 minutes read - 1317 wordsInterfacing an LCD display with a microcontroller opens up possibilities for creating user-friendly embedded systems with real-time data visualization. In this tutorial, we’ll build a practical ADC voltage monitor using the MSP430 LaunchPad and a standard 16x2 LCD display.
This project demonstrates core embedded programming concepts including ADC configuration, LCD communication protocols, and real-time data display - essential skills for any embedded systems developer.
Our system reads analog voltage from a potentiometer connected to the MSP430’s ADC channel and displays the digital value on an LCD screen in real-time. This fundamental pattern applies to countless sensor monitoring applications in embedded systems.
The MSP430’s 10-bit ADC provides precise analog-to-digital conversion essential for sensor interfacing. Here’s how the conversion process works:
The MSP430 LaunchPad operates at 3.6V, giving us a working voltage range of 0V to 3.6V. The 10-bit ADC resolution means:
Minimum Input (0V):
Binary: 0000000000
Decimal: 0
Maximum Input (3.6V):
Binary: 1111111111
Decimal: 1023
This gives us a resolution of 3.6V ÷ 1023 = 3.52mV per step, providing excellent precision for most sensing applications.
To convert ADC readings back to voltage:
Voltage = (ADC_Value × 3.6V) ÷ 1023
Critical Design Decision: The MSP430 operates at 3.6V while standard LCD displays require 5V for reliable operation. This mixed-voltage design requires careful consideration:
The LCD uses a 4-bit interface to minimize pin usage while maintaining functionality:
1// LCD Pin Assignments
2#define LCM_PIN_RS BIT2 // P1.2 - Register Select
3#define LCM_PIN_EN BIT1 // P1.1 - Enable Signal
4#define LCM_PIN_D7 BIT7 // P1.7 - Data Bit 7
5#define LCM_PIN_D6 BIT6 // P1.6 - Data Bit 6
6#define LCM_PIN_D5 BIT5 // P1.5 - Data Bit 5
7#define LCM_PIN_D4 BIT4 // P1.4 - Data Bit 4
Potentiometer Connection: Connect to P1.0 (ADC Channel A0)
1void adc_init()
2{
3 // Configure ADC: 10-bit resolution, internal reference
4 ADC10CTL0 = ADC10ON | ADC10SHT_2 | SREF_0;
5
6 // Select input channel A0, single-conversion mode
7 ADC10CTL1 = INCH_0 | SHS_0 | ADC10DIV_0 | ADC10SSEL_0 | CONSEQ_0;
8
9 // Enable analog input on P1.0
10 ADC10AE0 = BIT0;
11
12 // Enable conversions
13 ADC10CTL0 |= ENC;
14}
Key Configuration Details:
ADC10SHT_2: Sets sample-and-hold time for accurate conversionsSREF_0: Uses VCC and VSS as voltage referencesINCH_0: Selects input channel A0 (P1.0)The LCD uses a parallel interface with specific timing requirements:
1void PulseLcm()
2{
3 // LCD Enable pulse sequence for data transfer
4 LCM_OUT &= ~LCM_PIN_EN; // Pull EN low
5 __delay_cycles(200); // Wait for setup time
6
7 LCM_OUT |= LCM_PIN_EN; // Pull EN high
8 __delay_cycles(200); // Hold time
9
10 LCM_OUT &= ~LCM_PIN_EN; // Pull EN low
11 __delay_cycles(200); // Recovery time
12}
Timing Critical: LCD requires precise enable pulse timing for reliable data transfer.
1void main(void)
2{
3 adc_init();
4 int i, adc_value;
5 char display_buffer[5];
6
7 WDTCTL = WDTPW + WDTHOLD; // Disable watchdog timer
8 InitializeLcm();
9
10 while(1)
11 {
12 // Start ADC conversion
13 start_conversion();
14
15 // Wait for conversion completion
16 while(converting());
17
18 // Read conversion result
19 adc_value = ADC10MEM;
20
21 // Convert to ASCII string
22 itoa(adc_value, display_buffer, 10);
23
24 // Update LCD display
25 ClearLcmScreen();
26 PrintStr(display_buffer);
27
28 // Simple delay for display stability
29 for(i = 0; i < 5000; i++);
30 }
31}
Here’s the full implementation with comprehensive LCD and ADC handling:
1#include <msp430.h>
2
3// LCD Pin Definitions
4#define LCM_DIR P1DIR
5#define LCM_OUT P1OUT
6
7#define LCM_PIN_RS BIT2 // P1.2
8#define LCM_PIN_EN BIT1 // P1.1
9#define LCM_PIN_D7 BIT7 // P1.7
10#define LCM_PIN_D6 BIT6 // P1.6
11#define LCM_PIN_D5 BIT5 // P1.5
12#define LCM_PIN_D4 BIT4 // P1.4
13#define LCM_PIN_MASK ((LCM_PIN_RS | LCM_PIN_EN | LCM_PIN_D7 | LCM_PIN_D6 | LCM_PIN_D5 | LCM_PIN_D4))
14
15#define FALSE 0
16#define TRUE 1
17
18// ADC Functions
19void adc_init()
20{
21 ADC10CTL0 = ADC10ON | ADC10SHT_2 | SREF_0;
22 ADC10CTL1 = INCH_0 | SHS_0 | ADC10DIV_0 | ADC10SSEL_0 | CONSEQ_0;
23 ADC10AE0 = BIT0;
24 ADC10CTL0 |= ENC;
25}
26
27void start_conversion()
28{
29 ADC10CTL0 |= ADC10SC;
30}
31
32unsigned int converting()
33{
34 return ADC10CTL1 & ADC10BUSY;
35}
36
37// LCD Functions
38void PulseLcm()
39{
40 // pull EN bit low
41 LCM_OUT &= ~LCM_PIN_EN;
42 __delay_cycles(200);
43 // pull EN bit high
44 LCM_OUT |= LCM_PIN_EN;
45 __delay_cycles(200);
46 // pull EN bit low again
47 LCM_OUT &= (~LCM_PIN_EN);
48 __delay_cycles(200);
49}
50
51void SendByte(char ByteToSend, int IsData)
52{
53 // clear out all pins
54 LCM_OUT &= (~LCM_PIN_MASK);
55 LCM_OUT |= (ByteToSend & 0xF0);
56
57 if (IsData == TRUE)
58 {
59 LCM_OUT |= LCM_PIN_RS;
60 }
61 else
62 {
63 LCM_OUT &= ~LCM_PIN_RS;
64 }
65
66 PulseLcm();
67 LCM_OUT &= (~LCM_PIN_MASK);
68 LCM_OUT |= ((ByteToSend & 0x0F) << 4);
69
70 if (IsData == TRUE)
71 {
72 LCM_OUT |= LCM_PIN_RS;
73 }
74 else
75 {
76 LCM_OUT &= ~LCM_PIN_RS;
77 }
78
79 PulseLcm();
80}
81
82void LcmSetCursorPosition(char Row, char Col)
83{
84 char address;
85 // construct address from (Row, Col) pair
86
87 if (Row == 0)
88 {
89 address = 0;
90 }
91 else
92 {
93 address = 0x40;
94 }
95
96 address |= Col;
97 SendByte(0x80 | address, FALSE);
98}
99
100void ClearLcmScreen()
101{
102 // Clear display, return home
103 SendByte(0x01, FALSE);
104 SendByte(0x02, FALSE);
105}
106
107void InitializeLcm(void)
108{
109 LCM_DIR |= LCM_PIN_MASK;
110 LCM_OUT &= ~(LCM_PIN_MASK);
111 __delay_cycles(100000);
112 LCM_OUT &= ~LCM_PIN_RS;
113 LCM_OUT &= ~LCM_PIN_EN;
114 LCM_OUT = 0x20;
115 PulseLcm();
116 SendByte(0x28, FALSE);
117 SendByte(0x0E, FALSE);
118 SendByte(0x06, FALSE);
119}
120
121void PrintStr(char *Text)
122{
123 char *c;
124 c = Text;
125
126 while ((c != 0) && (*c != 0))
127 {
128 SendByte(*c, TRUE);
129 c++;
130 }
131}
132
133void main(void)
134{
135 adc_init();
136 int i, a;
137 char b[5];
138 WDTCTL = WDTPW + WDTHOLD; // Stop watchdog timer
139 InitializeLcm();
140
141 while(1)
142 {
143 start_conversion();
144 while(converting());
145 a = ADC10MEM;
146 itoa(a, b, 10); // integer to ASCII
147 ClearLcmScreen();
148 PrintStr(b);
149 for(i = 0; i < 5000; i++);
150 }
151}
This foundation project can be extended for numerous practical applications:
LCD Not Displaying: Check 5V power supply and enable pulse timing Inconsistent Readings: Verify ADC reference voltage and input connections Display Flickering: Adjust refresh delay in main loop Garbled Characters: Confirm 4-bit data line connections
{% youtube hsM_o5hNUmg %}
Watch the complete project in action, demonstrating real-time ADC value updates as the potentiometer is adjusted.
This LCD interfacing tutorial provides a solid foundation for embedded systems with human-machine interfaces. The combination of ADC sensing and LCD display creates the building blocks for countless embedded applications requiring real-time data visualization.
For more advanced MSP430 programming techniques, explore our related tutorials on embedded systems development and sensor interfacing projects.
{% youtube hsM_o5hNUmg %}