Artistic shot of the experiment

Exploring STC MCU part 6 – Feeling the environment

STC microcontrollers sometimes comes with built-in ADC. It can be a little bit tricky to use as the relevant pins have to be switched between analog and digital mode.

Analog or Digital?

On IAP15W4K61S4, there is a 8-channel power-referenced successive approximation ADC with its inputs connected to port 1 pins. Instead of tapping out the input line as commonly found on AVRs, STC implemented an input switch that will disconnect the pin from digital I/O if it is used in analog mode and vice versa. So in order to use the ADC, we need to switch the pin into analog mode before initializing a conversion.

Also the register in question is a write-only register and can have undefined behavior if read. So if you are emulating the AVR behavior you must not use the usual bit logic statements, use straight assignment instead.

References

The ADC in STC15 series is hardwired to be referenced from power rail, so if you are using any external reference you have to measure the power voltage first.

Implementation

Here is how the ADC interface can be written:

#include "adc.h"

#include <STC15F2K60S2.H>
#include "systick.h" // for delay()
#include "wdt.h" // for wait()

void adc_init(void)
{
	// Put pins P1.[0..5] into high impendence mode (so the pullup won't
	// interfere with the readings, although in this demo the LM35 probably
	// wouldn't mind and for more sensitive readings op amp isolation is required
	// anyway. We also must leave pins P1.6 and P1.7 alone as those are crystal
	// pins.
	P1M1 |= 0x3f;
	P1M0 &= ~0x3f;
	
	// ADC uses left justified output
	CLK_DIV |= 0x20;
	
	// Power on the ADC and allow for settle.
	ADC_CONTR = 0xe0;
	delay(5);
}

unsigned short adc_read(unsigned char channel)
{
	if (channel > 5)
	{
		return 0; // Only channels 0-5 are allowed.
	}
	
	// Enable ADC input on the pin, allowing for settle time.
	P1ASF |= 1 << channel;
	delay(2);
	
	ADC_CONTR = (ADC_CONTR & 0xe0) | channel; // Start conversion on the channel.
	ADC_CONTR |= 0x08;
	
	wait(!(ADC_CONTR & 0x10)); // Wait for the ADC conversion to finish.
	ADC_CONTR &= ~0x10;
	
	// Restore the pin.
	P1ASF = 0;
	
	return ADC_RES << 8 | ADC_RESL;
}

And to use it

unsigned long vcc;

int main(void)
{
	unsigned long ref_reading;
	P4M0 &= ~(1 << 5);
	P4M1 &= ~(1 << 5);
	EA = 1;
	wdt_init();
	systick_init();
	adc_init();
	serial_open(115200);
	
	// Calibrate the VCC
	ref_reading = adc_read(4);
	vcc = 2500UL * 1024UL / ref_reading;
	
	puts("Ready.");
	printf("VCC = %lumV\r\n", vcc);
	
	for (;;)
	{
		unsigned long temp_reading = adc_read(0);
		float temp = vcc * temp_reading / 10240.0;
		printf("Temperature = %f deg. Cels.\r\n", temp);
		P45 = !P45;
		delay(500);
		yield();
	}
}

Leave a Reply