Skip to main content
Associate III
March 14, 2026
Solved

STM8: Problem displaying ADC value

  • March 14, 2026
  • 6 replies
  • 430 views

Hi Guys,

Based on some tutorials, I am trying to execute ADC module of STM8S105 and display the ADC result in 16x2 LCD display. The problem is : thousandth digit place is showing alphabetic character right from "A"  when I increase voltage on ADC channel from 0. Please see my code as well as adc.h file. I am eagerly awaiting someone to suggest where I have made the mistake.

 

 #define LCD_RS GPIOD, GPIO_PIN_2
 #define LCD_EN GPIOD, GPIO_PIN_3
 #define LCD_DB4 GPIOD, GPIO_PIN_4
 #define LCD_DB5 GPIOD, GPIO_PIN_5
 #define LCD_DB6 GPIOD, GPIO_PIN_6
 #define LCD_DB7 GPIOD, GPIO_PIN_7
 
 #include "STM8S.h"
 #include "lcd.h"
#include "adc1.h"
main()
{
//Variable declarations

unsigned int test_var ;
char d4,d3,d2,d1;
gpio_adc_init();
adc_init();
Lcd_Begin();
Lcd_Clear();
Lcd_Set_Cursor(1,1);
Lcd_Print_String("STM8S103F3P3 LCD");
delay_ms(5000);
Lcd_Clear();
Lcd_Set_Cursor(1,1);
Lcd_Print_String("Circuit Digest");
Lcd_Set_Cursor(2,1);
Lcd_Print_String("Test: ");
while (1)


{ 
test_var = read_adc_value();
d4 = test_var%10 + '0';
d3 = (test_var/10)%10 + '0';
d2 = (test_var/100)%10 + '0';
d1 = (test_var/1000) + '0';
Lcd_Set_Cursor(2,6);
Lcd_Print_Char(d1);
Lcd_Print_Char(d2);
Lcd_Print_Char(d3);
Lcd_Print_Char(d4);
delay_ms(1000);
//test_var++;
}
}

#ifdef USE_FULL_ASSERT
void assert_failed(uint8_t* file, uint32_t line)
{
 while (1)
 {
 }
}
#endif

adc.h

 

uint16_t value;

void gpio_adc_init(void) {
	CLK_PeripheralClockConfig(CLK_PERIPHERAL_ADC, ENABLE);
	GPIO_Init(GPIOB, GPIO_PIN_4, GPIO_MODE_IN_FL_NO_IT);
			}
										
void adc_init(void) {												
ADC1 -> CSR = 0x04;
ADC1 -> CR1 = 0x40;
ADC1 -> CR2 = 0x00;
ADC1 -> TDRH = 0xFF;
ADC1 -> TDRL = 0xFF;
ADC1 -> CR1 |= ADC1_CR1_ADON;
			 }
						 
										
uint16_t read_adc_value(void) {
 // Start the conversion process
 ADC1_StartConversion(); 

 // Wait until the end of conversion (EOC) flag is set
 while(ADC1_GetFlagStatus(ADC1_FLAG_EOC) == FALSE);

 // Read the 10-bit value (clears the EOC flag automatically when the data registers are accessed correctly)
 value = ADC1_GetConversionValue(); 

 ADC1_ClearFlag(ADC1_FLAG_EOC); // Explicitly clear the flag if needed, depending on read method and mode

 return value;
}
Best answer by Peter BENSCH

For testing, the suggestion of @AA1 is very good, but it has one disadvantage: functions like printf, snprintf, etc., occupy quite a lot of flash memory, which is usually quite limited in an STM8S105, which only provides 16KB or 32KB. In this respect, the original version by @ankhola is more resource-efficient, it just needs to be adapted to work.

First of all, @AA1 correctly pointed out that the data alignment is left by default. However, it can be very easily changed via CR2:

ADC1->CR2 = 0x08; // RIGHT alignment (ALIGN = 1)

Furthermore, you should include a limit for test_var in your code, e.g. 9999:

while (1)
{
 uint16_t test_var = read_adc_value();

 if (test_var > 9999) { test_var = 9999; } // protection optional

 char d4 = (test_var % 10) + '0';
 char d3 = (test_var / 10 % 10) + '0';
 char d2 = (test_var / 100 % 10) + '0';
 char d1 = (test_var / 1000) + '0';

 Lcd_Set_Cursor(2, 6);
 Lcd_Print_Char(d1);
 Lcd_Print_Char(d2);
 Lcd_Print_Char(d3);
 Lcd_Print_Char(d4);

 delay_ms(1000);
}

With this, your example should already work. However, it still shows leading zeros, which can be removed with an additional function:

void Lcd_Print_Uint(uint16_t value)
{
 char buf[6]; // max 5 digits + '\0'
 int i = 0;

 if (value == 0)
 { buf[i++] = '0'; } // special case 0
 else
 {
 while (value > 0 && i < 5) // store digits from back to front
 {
 buf[i++] = (value % 10) + '0';
 value /= 10;
 }
 }
 buf[i] = '\0';

 for (int j=0; j<i/2; j++) // reverse string because we started from the back
 {
 char tmp = buf[j];
 buf[j] = buf[i-j-1];
 buf[i-j-1] = tmp;
 }

 Lcd_Print_String(buf);
}

You could then use this function with:

while (1)
{
 uint16_t test_var = read_adc_value();

 if (test_var > 9999) { test_var = 9999; } // protection optional

 Lcd_Set_Cursor(2, 6);
 Lcd_Print_Uint(test_var);
 delay_ms(1000);
}

Hope that helps?

Regards
/Peter

 

6 replies

AA1
Senior III
March 14, 2026

Try this:

char str[8];
sprintf(str, "%04u", test_var);
Lcd_Print_String(str);

If the problem continues, the problem should be Data alignment. By default Data alignment is left. Change it to right.

 

ankholaAuthor
Associate III
March 14, 2026

I have inserted the snippet of code just below the "read_adc_value()" function in while loop and deleted the rest. Code did not build. Errors are attached.

 

#error cpstm8 main.c:35(0+4) misplaced local declaration
#error cpstm8 main.c:36(7) missing prototype
#error cpstm8 main.c:37(17+3) incompatible argument type
#error cpstm8 main.c:36(8+3) str undefined

 

while (1)


{ 
test_var = read_adc_value();
char str[8];
sprintf(str, "%04u", test_var);
Lcd_Print_String(str);
AA1
Senior III
March 14, 2026

Variables definition must be before any other code. Try this:

while (1)
{ 
char str[8];
test_var = read_adc_value();
sprintf(str, "%04u", test_var);
Lcd_Print_String(str);

 

Peter BENSCH
Peter BENSCHBest answer
Technical Moderator
March 14, 2026

For testing, the suggestion of @AA1 is very good, but it has one disadvantage: functions like printf, snprintf, etc., occupy quite a lot of flash memory, which is usually quite limited in an STM8S105, which only provides 16KB or 32KB. In this respect, the original version by @ankhola is more resource-efficient, it just needs to be adapted to work.

First of all, @AA1 correctly pointed out that the data alignment is left by default. However, it can be very easily changed via CR2:

ADC1->CR2 = 0x08; // RIGHT alignment (ALIGN = 1)

Furthermore, you should include a limit for test_var in your code, e.g. 9999:

while (1)
{
 uint16_t test_var = read_adc_value();

 if (test_var > 9999) { test_var = 9999; } // protection optional

 char d4 = (test_var % 10) + '0';
 char d3 = (test_var / 10 % 10) + '0';
 char d2 = (test_var / 100 % 10) + '0';
 char d1 = (test_var / 1000) + '0';

 Lcd_Set_Cursor(2, 6);
 Lcd_Print_Char(d1);
 Lcd_Print_Char(d2);
 Lcd_Print_Char(d3);
 Lcd_Print_Char(d4);

 delay_ms(1000);
}

With this, your example should already work. However, it still shows leading zeros, which can be removed with an additional function:

void Lcd_Print_Uint(uint16_t value)
{
 char buf[6]; // max 5 digits + '\0'
 int i = 0;

 if (value == 0)
 { buf[i++] = '0'; } // special case 0
 else
 {
 while (value > 0 && i < 5) // store digits from back to front
 {
 buf[i++] = (value % 10) + '0';
 value /= 10;
 }
 }
 buf[i] = '\0';

 for (int j=0; j<i/2; j++) // reverse string because we started from the back
 {
 char tmp = buf[j];
 buf[j] = buf[i-j-1];
 buf[i-j-1] = tmp;
 }

 Lcd_Print_String(buf);
}

You could then use this function with:

while (1)
{
 uint16_t test_var = read_adc_value();

 if (test_var > 9999) { test_var = 9999; } // protection optional

 Lcd_Set_Cursor(2, 6);
 Lcd_Print_Uint(test_var);
 delay_ms(1000);
}

Hope that helps?

Regards
/Peter

 

ankholaAuthor
Associate III
March 15, 2026

Thanks both of you. Right aligning the ADC result, the problem is over. As stated by Peter, "However, it still shows leading zeros", it is mentioned that the leading character, i.e. thousandth digit was showing alphabets starting from "A" to special characters. I could not understand this weird behavior.

 

Regards. 

Peter BENSCH
Technical Moderator
March 15, 2026

Your last comment with the strange characters refers to the previous case with left-aligned results, right?

Well, just imagine that you’re getting results in the upper range. Then you work out the corresponding value, e.g. 0x3ff = 1023, look at section 24.8. data alignment in RM0016 and work out what d1 gives in your calculation. And finally, you check the character table for the 16x2 LCD to see what is displayed with that hex value.

ankholaAuthor
Associate III
March 15, 2026

Thanks Peter. I got the point. actually I thought that whatever alignment settings is configured in code, it will be handled by peripheral library function which shows as follows. However, I shall take the liberty to ask another query to you. While printing the value (d1 to d4), Cursor is set once. But the column is advancing as usual. How it is advancing?

 

Regards.

 

Library Function:

uint16_t ADC1_GetConversionValue(void)
{
 uint16_t temph = 0;
 uint8_t templ = 0;
 
 if ((ADC1->CR2 & ADC1_CR2_ALIGN) != 0) /* Right alignment */
 {
 /* Read LSB first */
 templ = ADC1->DRL;
 /* Then read MSB */
 temph = ADC1->DRH;
 
 temph = (uint16_t)(templ | (uint16_t)(temph << (uint8_t)8));
 }
 else /* Left alignment */
 {
 /* Read MSB first*/
 temph = ADC1->DRH;
 /* Then read LSB */
 templ = ADC1->DRL;
 
 temph = (uint16_t)((uint16_t)((uint16_t)templ << 6) | (uint16_t)((uint16_t)temph << 8));
 }
 
 return ((uint16_t)temph);
}

 

My Query:

 Lcd_Set_Cursor(2, 6);
 Lcd_Print_Char(d1);
 Lcd_Print_Char(d2);
 Lcd_Print_Char(d3);
 Lcd_Print_Char(d4);

 

Peter BENSCH
Technical Moderator
March 16, 2026

The column advances because the HD44780‑compatible LCD controller automatically moves the cursor after each character write, not because Lcd_Set_Cursor() is called multiple times. Therefore, d1, d2, d3 and d4 appear in consecutive columns (6, 7, 8, 9) even though the cursor is explicitly set only once in the code.

The ADC function is unrelated; it merely provides the data that you later display.

ankholaAuthor
Associate III
March 17, 2026

Thanks Peter.