Android app for remote control

 

This app can be used together with Arduino, ESP8266, Raspberry pi and many other modules where you require to send/receive data via WiFi.  By pressing an on or off button on the main screen the app will send the data specified in the settings tab to your module. Your Arduino, ESP8266, Raspberry pi can use this data to enable or disable any device like (home) lamps, televisions, modems, camera’s etc.

In the settings tab you can set up a destination IP address and port number to send data after you pressed a button on the main screen. In the settings tab you can predefine the transmitting data corrosponding to a button. After pressing the button the predefined data will be send to your module and can be handled there to activate led’s, lamps, televisions, fan’s or anything your project requires.

If the module sends the same data back to this app the app will detect this and change the status on the mainscreen. This is to make sure your module has a way to confirm the required action is done completely by the module.

The Android remote buttons app can be downloaded here.

The Atmega328 code

The software is a simplefied version of the software in my last blog. The last software “web server” was a bit more complicated, because a browser sends around 500 byters on average and thats a lot for this microcontroller. The Android app only sends around 50 bytes, depending what text you predefine. In the last blog I tried to keep the buffer small and therefor I had to programm some bufferoverflow corrections and thats not needed for this project. Besides that, a web page had to be send by the microcontroller to the web browser and that also takes a lot of bytes. With the Android app we only have to send about 50 bytes, again depending on the predefined text.

The working is simple. The ESP8266 waits for an incoming TCP/IP message send by the Android app. The ESP8266 passes this message to the microcontroller. The microcontroller checks if the incoming message match with some predefined values. If not it discarge the message. If it matches a led will be triggered and a message will appear on the lcd. After that the received message will be send back to the ESP8862 via the UART and the ESP8266 will convert it to TCP/IP and sends it back to the Android app. This whole loop just continues for ever. Download the full code for the microcontroller here and see the main changes compared to the framework below.

Application1.c

/*
 * application1.c
 *
 * Created: 8-2-2018 15:07:22
 * Author: HdH
 * Description: webserver controlling 4 outputs.
 */

#define F_CPU 16000000UL // 16 MHz clock speed for delay.h
#define PIN_LED1 10
#define PIN_LED2 9
#define PIN_LED3 6
#define PIN_LED4 8

#include <stdio.h>
#include <avr/io.h>
#include <util/delay.h>
#include <string.h>
#include <stdbool.h>
#include <avr/pgmspace.h>
#include "gpiolib.h"
#include "i2cdisplay.h"
#include "esp8266.h"



/****************************************************************************
	Bit and byte definitions
****************************************************************************/


extern int alarmCode;


void Application1(void){

uint8_t p;

	p = readInComing(0, NULL); // Checking incoming uart data
	switch (p){
	
		case BTN1RCVDON:
			writeI2cDisplayLine("Button1 on pressed",0,1);
			writePin(PIN_LED1, HIGH);
			esp8266_send(BTN1ONTEXT);
			resetIncoming();
			break;
		case BTN1RCVDOFF:
			writeI2cDisplayLine("Button1 off pressed",0,1);
			writePin(PIN_LED1, LOW);
			esp8266_send(BTN1OFFTEXT);
			resetIncoming();
			break;
		case BTN2RCVDON:
			writeI2cDisplayLine("Button2 on pressed",0,1);
			writePin(PIN_LED2, HIGH);
			esp8266_send(BTN2ONTEXT);
			resetIncoming();
			break;
		case BTN2RCVDOFF:
			writeI2cDisplayLine("Button2 off pressed",0,1);
			writePin(PIN_LED2, LOW);
			esp8266_send(BTN2OFFTEXT);
			resetIncoming();
			break;
		case BTN3RCVDON:
			writeI2cDisplayLine("Button3 on pressed",0,1);
			writePin(PIN_LED3, HIGH);
			esp8266_send(BTN3ONTEXT);
			resetIncoming();
			break;
		case BTN3RCVDOFF:
			writeI2cDisplayLine("Button3 off pressed",0,1);
			writePin(PIN_LED3, LOW);
			esp8266_send(BTN3OFFTEXT);
			resetIncoming();
			break;
		case BTN4RCVDON:
			writeI2cDisplayLine("Button4 on pressed",0,1);
			writePin(PIN_LED4, HIGH);
			esp8266_send(BTN4ONTEXT);
			resetIncoming();
			break;
		case BTN4RCVDOFF:
			writeI2cDisplayLine("Button4 off pressed",0,1);
			writePin(PIN_LED4, LOW);
			esp8266_send(BTN4OFFTEXT);
			resetIncoming();
			break;
			
		default:
			break;
	}
}

Esp8266.h

/*
 * esp8266.h
 *
 * Created: 8-2-2018 21:08:06
 *  Author: acer
 */ 


#ifndef ESP8266_H_
#define ESP8266_H_

//#define GETRECEIVED 1
#define USERDEFINEDRECEIVED 2
#define BTN1RCVDON 3
#define BTN1RCVDOFF 4
#define BTN2RCVDON 5
#define BTN2RCVDOFF 6
#define BTN3RCVDON 7
#define BTN3RCVDOFF 8
#define BTN4RCVDON 9
#define BTN4RCVDOFF 10
#define NOTHINGSET 11

#define BTN1ONTEXT "lamp on"
#define BTN1OFFTEXT "lamp off"
#define BTN2ONTEXT "tv on"
#define BTN2OFFTEXT "tv off"
#define BTN3ONTEXT "cam on"
#define BTN3OFFTEXT "cam off"
#define BTN4ONTEXT "fan on"
#define BTN4OFFTEXT "fan off"

void esp8266_init(void);
bool esp8266_send(const char* text1);
uint8_t readInComing(int timeout, const char* text1);
void resetIncoming(void);
uint8_t checkIncomingFlag(void);
char* getIP(void);

extern unsigned long int msCounter;
struct RcvFlag rcvFlag1;

struct RcvFlag {
	bool userDefinedReceived;
	bool btn1RcvdOn;
	bool btn1RcvdOff;
	bool btn2RcvdOn;
	bool btn2RcvdOff;
	bool btn3RcvdOn;
	bool btn3RcvdOff;
	bool btn4RcvdOn;
	bool btn4RcvdOff;
};


#endif /* ESP8266_H_ */

Esp8266.c

/*
 * esp8266.c
 *
 * Created: 8-2-2018 21:07:55
 *  Author: acer
 */ 

#define F_CPU 16000000UL // 16 MHz clock speed

#include <avr/io.h>
#include <util/delay.h>
#include <stdbool.h>
#include <string.h>
#include <stdlib.h>
#include <avr/pgmspace.h>
#include "timer4clocklib.h"
#include "i2cdisplay.h"
#include "uart.h"
#include "esp8266.h"



/****************************************************************************
	esp8266_init(), initialize the esp8266
****************************************************************************/
void esp8266_init(){
	
	uint8_t p;
	
	// Check the status of the ESP8266 if not ok reset
	_delay_ms(500);
	serialWrite("AT", CRLF);
	p = readInComing(1000, "OK\r\n");
	if (p != 2){
		writeI2cDisplayLine("Reset needed!", 0, 1);
		_delay_ms(4000);
		serialWrite("AT+RST", CRLF);
		p = readInComing(3000, "OK\r\n");
		if (p == 2){
			writeI2cDisplayLine("Reset done OK!", 0, 1);
			_delay_ms(1000);
		}
	}
	else{
		writeI2cDisplayLine("ESP8266 conn. OK", 0, 1);
		_delay_ms(1000);
	}
	
	// reset all flags generated by incoming data from the ESP8266 and empty the buffer.
	resetIncoming();
	
	
	// configure the server and activate the server on the ESP8266
	serialWrite("AT+CIPMUX=1", CRLF);
	p = readInComing(1000, "OK\r\n");
	if (p == 2){
		resetIncoming();
		writeI2cDisplayLine("AT+CIPMUX=1 set", 0, 1);
		_delay_ms(1000);
		serialWrite("AT+CIPSERVER=1,80", CRLF); //\r\n
		p = readInComing(1000, "OK\r\n");
		if (p == 2){
			resetIncoming();
			writeI2cDisplayLine("AT+CIPSERVER set", 0, 1);
		}
	}
	
	// init is done so clean up all flags again.
	resetIncoming();
}

/****************************************************************************
	esp8266_send, send uart data to the esp8266
****************************************************************************/
bool esp8266_send(const char* text1){
	
	int p;
	int textLength;
	char buf1[50] = {'\0'};
	char itoaBuf[3];


	if (text1 != NULL){
		strcpy(buf1, (char *) text1);
		textLength = strlen(buf1);
	}
	else{
		textLength = 0;
	}
	
	// the esp8266 requires the amount of bytes that will be send in chars.
	itoa(textLength+2, itoaBuf ,10);
	serialWrite("AT+CIPSEND=0,", NO_EOL);
	serialWrite(itoaBuf, CRLF);
	
	// the data is send wait on the esp8266 for a confirmation
	resetIncoming();
	p = readInComing(1000, "OK\r\n");
	if (p == 2){
		resetIncoming();
		writeI2cDisplayLine(buf1,0,2);
		serialWrite(buf1, CRLF);
		p = readInComing(500, "SEND OK\r\n");
		if (p == 2){
			resetIncoming();
			return true;
		}
		else{
			return false;
		}
	}
	else{
		return false;
	}
		
}

/****************************************************************************
	checkInComing, check/read data received by the esp8266
****************************************************************************/
uint8_t readInComing(int timeOut, const char* text1){ // ipv int
	
	char buf1[30] = {'\0'};
	uint8_t flag;
	
	uint8_t p; //unsigned char p;
	
	uint8_t lengthUsrDef;
	uint8_t lengthBtn1On, lengthBtn1Off, lengthBtn2On, lengthBtn2Off, lengthBtn3On, lengthBtn3Off, lengthBtn4On, lengthBtn4Off;
	
	uint8_t posUsrDef = 0;
	uint8_t posBtn1On = 0;
	uint8_t posBtn1Off = 0;
	uint8_t posBtn2On = 0;
	uint8_t posBtn2Off = 0;
	uint8_t posBtn3On = 0;
	uint8_t posBtn3Off = 0;
	uint8_t posBtn4On = 0;
	uint8_t posBtn4Off = 0;
	
	lengthBtn1On = strlen(BTN1ONTEXT);
	lengthBtn1Off = strlen(BTN1OFFTEXT);
	lengthBtn2On = strlen(BTN2ONTEXT);
	lengthBtn2Off = strlen(BTN2OFFTEXT);
	lengthBtn3On = strlen(BTN3ONTEXT);
	lengthBtn3Off = strlen(BTN3OFFTEXT);
	lengthBtn4On = strlen(BTN4ONTEXT);
	lengthBtn4Off = strlen(BTN4OFFTEXT);
	
	
	if (text1 != NULL){
		strcpy(buf1, (char *) text1);
		lengthUsrDef = strlen(buf1);
	}
	else{
		lengthUsrDef = 0;
	}
	
	flag = checkIncomingFlag();			
	if (flag != NOTHINGSET){
		return flag;
	}

	
	if(timeOut != 0){ // user gives predefined time
		unsigned long stop = msCounter + timeOut;
		do{
			p = getChar();
			while (p != 0x0){
				
				posUsrDef = (p == buf1[posUsrDef]) ? posUsrDef + 1 : 0;
				if (lengthUsrDef > 0 && posUsrDef == lengthUsrDef){ // user defined
					rcvFlag1.userDefinedReceived = true;
					return USERDEFINEDRECEIVED;
				}
				p = getChar();
			}
		}while (msCounter < stop); } else if(timeOut == 0){ // if no time out set, used in the app to continuously check for incoming data p = getChar(); while (p != 0){ posUsrDef = (p == buf1[posUsrDef]) ? posUsrDef + 1 : 0; posBtn1On = (p == BTN1ONTEXT[posBtn1On]) ? posBtn1On + 1 : 0; posBtn1Off = (p == BTN1OFFTEXT[posBtn1Off]) ? posBtn1Off + 1 : 0; posBtn2On = (p == BTN2ONTEXT[posBtn2On]) ? posBtn2On + 1 : 0; posBtn2Off = (p == BTN2OFFTEXT[posBtn2Off]) ? posBtn2Off + 1 : 0; posBtn3On = (p == BTN3ONTEXT[posBtn3On]) ? posBtn3On + 1 : 0; posBtn3Off = (p == BTN3OFFTEXT[posBtn3Off]) ? posBtn3Off + 1 : 0; posBtn4On = (p == BTN4ONTEXT[posBtn4On]) ? posBtn4On + 1 : 0; posBtn4Off = (p == BTN4OFFTEXT[posBtn4Off]) ? posBtn4Off + 1 : 0; if (lengthUsrDef > 0 && posUsrDef == lengthUsrDef){ // user defined
				rcvFlag1.userDefinedReceived = true;
				return USERDEFINEDRECEIVED;
			}
			if (lengthBtn1On > 0 && posBtn1On == lengthBtn1On){ // button 1 on
				rcvFlag1.btn1RcvdOn = true;
				return BTN1RCVDON;
			}
			if (lengthBtn1Off > 0 && posBtn1Off == lengthBtn1Off){ // button 1 off
				rcvFlag1.btn1RcvdOff = true;
				return BTN1RCVDOFF;
			}
			if (lengthBtn2On > 0 && posBtn2On == lengthBtn2On){ // button 2 on
				rcvFlag1.btn2RcvdOn = true;
				return BTN2RCVDON;
			}
			if (lengthBtn2Off > 0 && posBtn2Off == lengthBtn2Off){ // button 2 off
				rcvFlag1.btn2RcvdOff = true;
				return BTN2RCVDOFF;
			}
			if (lengthBtn3On > 0 && posBtn3On == lengthBtn3On){ // button 3 on
				rcvFlag1.btn3RcvdOn = true;
				return BTN3RCVDON;
			}
			if (lengthBtn3Off > 0 && posBtn3Off == lengthBtn3Off){ // button 3 off
				rcvFlag1.btn3RcvdOff = true;
				return BTN3RCVDOFF;
			}
			if (lengthBtn4On > 0 && posBtn4On == lengthBtn4On){ // button 4 on
				rcvFlag1.btn4RcvdOn = true;
				return BTN4RCVDON;
			}
			if (lengthBtn4Off > 0 && posBtn4Off == lengthBtn4Off){ // button 4 off
				rcvFlag1.btn4RcvdOff = true;
				return BTN4RCVDOFF;
			}
			
			p = getChar();
		}
	}
		
	return NOTHINGSET;
}

/****************************************************************************
	checkIncomingFlag, check the current status of what message is received at last
****************************************************************************/
uint8_t checkIncomingFlag(){
	

	if(rcvFlag1.userDefinedReceived == true){
		return USERDEFINEDRECEIVED;
	}
	
	else if(rcvFlag1.btn1RcvdOn == true){
		return BTN1RCVDON;
	}
	
	else if(rcvFlag1.btn1RcvdOff == true){
		return BTN1RCVDOFF;
	}
	
	else if(rcvFlag1.btn2RcvdOn == true){
		return BTN2RCVDON;
	}
	
	else if(rcvFlag1.btn2RcvdOff == true){
		return BTN2RCVDOFF;
	}
	
	else if(rcvFlag1.btn3RcvdOn == true){
		return BTN3RCVDON;
	}
	
	else if(rcvFlag1.btn3RcvdOff == true){
		return BTN3RCVDOFF;
	}
	
	else if(rcvFlag1.btn4RcvdOn == true){
		return BTN4RCVDON;
	}
	
	else if(rcvFlag1.btn4RcvdOff == true){
		return BTN4RCVDOFF;
	}
	else{
		return NOTHINGSET;
	}
}

/****************************************************************************
	resetIncoming, reset all flags to prepare for the next expected data from the esp8266
****************************************************************************/

void resetIncoming(){
	
	rcvFlag1.userDefinedReceived  = false;
	rcvFlag1.btn1RcvdOn = false;
	rcvFlag1.btn1RcvdOff = false;
	rcvFlag1.btn2RcvdOn = false;
	rcvFlag1.btn2RcvdOff = false;
	rcvFlag1.btn3RcvdOn = false;
	rcvFlag1.btn3RcvdOff = false;
	rcvFlag1.btn4RcvdOn = false;
	rcvFlag1.btn4RcvdOff = false;
}


/****************************************************************************
	getIP, just to know the IP as i forgot to make it static instead of dhcp...
****************************************************************************/

char* getIP(){
	
	uint8_t p;
	int posIpReceived = 0;
	char matchStaip[] = "STAIP,\"";
	int ipCharsCounter = 0;
	int staIpLength = strlen(matchStaip);
	static char tempBuffer[16];
	bool matched = false;
	
	serialWrite("AT+CIFSR", CRLF);
	_delay_ms(200); // wait for all the data from this cmd
	p = getChar();
	while (p != 0x0){
		
		if(p == matchStaip[posIpReceived]){
			posIpReceived++;
		}
		else{
			posIpReceived = 0;
		}
		
		if (staIpLength > 0 && posIpReceived == staIpLength){ // STAIP received
			matched = true;
		}
		
		if(matched == true){
			tempBuffer[ipCharsCounter] = p;
			ipCharsCounter++;
			if (ipCharsCounter > 16){
				ipCharsCounter = 0;
				matched = false;
				return tempBuffer;
			}
			
		}
		p = getChar();
	}
	
	return NULL;
	
}

Uart.h

/*
 * uart.h
 *
 * Created: 8-12-2017 13:36:22
 *  Author: acer
 */


#ifndef UART_H_
#define UART_H_

/****************************************************************************
	Bit and byte definitions
****************************************************************************/
#define CLOCKSPEED_CPU   16000000
#define BAUD    57600
#define BRC     ((CLOCKSPEED_CPU/16/BAUD) - 1)
#define TX_BUFFER_SIZE  40
#define RX_BUFFER_SIZE  200 // it's a bit big because of the CIFSR cmd returns a lot of bytes used in getIp()
#define CR 1
#define LF 2
#define CRLF 3
#define NO_EOL 4



/****************************************************************************
	Function definitions
****************************************************************************/

void UART_Init();
void serialWrite(char c[], uint8_t eol);
void UART_Transmit(unsigned char data );
char getChar(void);


#endif /* UART_H_ */

Uart.c

/*
 * uart.c
 *
 * Created: 8-12-2017 13:36:09
 *  Author: acer
 */ 

#define F_CPU 16000000UL // 16 MHz clock speed


#include <avr/io.h>
#include <stdlib.h>
#include <stdio.h>
#include <util/delay.h>
#include <avr/interrupt.h>
#include <string.h>
#include <stdbool.h>
#include "uart.h"


/****************************************************************************
	Bit and byte definitions
****************************************************************************/
char rxBuffer[RX_BUFFER_SIZE];
uint8_t rxWritePos = 0;
uint8_t rxReadPos = 0;


/****************************************************************************
	USART_Init, see important info below
****************************************************************************/
void UART_Init(){

	UBRR0H = (unsigned char)(BRC >> 8); //baud rate register set to 9600 BAUD
	UBRR0L = (unsigned char) BRC;

	UCSR0B = (1<<RXEN0) | (1<<TXEN0) | (1 << RXCIE0);
	UCSR0C = (1 << UCSZ01) | (3<<UCSZ00);
}


/****************************************************************************
	serialWrite, send data with optional crlf
****************************************************************************/
void serialWrite(char c[], uint8_t eol)
{
	int i;
	
	for(i = 0; i < strlen(c); i++)// put the string you want to write in a buffer. { UART_Transmit(c[i]); //put the char's one by one in the buffer } if (eol == CR){ UART_Transmit(0xd); //CR \r } else if (eol == LF){ UART_Transmit(0xa); //LF \n } else if (eol == CRLF){ UART_Transmit(0xd); //CR \r UART_Transmit(0xa); //LF \n } else if (eol == NO_EOL){ return; } } /**************************************************************************** ISR(USART_RX_vect), Fills up a buffer. ****************************************************************************/ ISR(USART_RX_vect) { rxBuffer[rxWritePos] = UDR0;// rxWritePos++; if(rxWritePos >= RX_BUFFER_SIZE)
	{
		rxWritePos = 0;
	}

}

/****************************************************************************
	USART_Transmit, 
****************************************************************************/
void UART_Transmit(unsigned char data ){
	
	while( !( UCSR0A & (1<<UDRE0)) ); // Wait for empty transmit buffer UDR0 = data; // Put data into buffer, sends the data } /**************************************************************************** getChar, ****************************************************************************/ char getChar(void) { char ret = '\0'; _delay_us(500); // to prevent reading faster then the bytes are coming in if(rxReadPos != rxWritePos) { ret = rxBuffer[rxReadPos]; rxReadPos++; if(rxReadPos >= RX_BUFFER_SIZE)//
		{
			rxReadPos = 0;
		}
	}
	
	return ret;
}

 

Thanks for reading!

Programming an IoT webserver

 

In this project I build a webserver with the ESP8266 as the interface between the TCP/IP protocol and the Atmega328 microcontroller. The interesting part is that both parts are very cheap and that makes it a practical way to connect home devices to the internet. It’s a bit basic now with only a few buttons to turn 4 led’s on or off via a webpage, but the led’s can be replaced with relay’s and a proper current driver for the relays. Then it will be possible to control all your home equipment remotely from any location in the world with an internet connection.

The hardware setup

In the schematic I removed the connection details of the lcd display to get the schematic on 1 page. In this blog you can find these connections. Keep in mind that the logic level converter and ESP8266 requires 3.3V instead of 5V.

Schematic esp8266 with the Atmega328p

 

The software

I preconfigured the esp8266 in my last blog with the settings that will not change like the ssid and WiFi password and the baudrate. All other settings are set in the esp8266_init() function in the esp8266 library. This library also contains a function to send data to the esp. The esp requires a noticiation of the amount of bytes that are going to be send and after an ok is received by the microcontroller the bytes can be send so just sending with the uart send function doesn’t work.

The basic working of the program is text getting send to the esp by an internet browser. The microcontroller reads it and checks it against some predefined text. If it matches then the display will show this and a GPIO gets turned on or off.

Because I only have 2Kb of data memory and a basic page with 8 buttons takes a lot of bytes, i used the PROGMEM macro to save the page in the program memory, because i have enough free space there.

The incoming UART data gets automatically saved via interrupts. To save some space I used a smaller buffer then the actual bytes that get send by an web browser. For this project only the first ca. 50 bytes send by the browser are interesting, because these bytes tells which button is pressed. So the rest of the bytes will not be used and a buffer overflow var is set. After the incoming message is handelled the buffer overflow can be reset by the flushBuffer function.

Download the full code here or have a look below.

esp8266.h

/*
 * esp8266.h
 *
 * Created: 8-2-2018 21:08:06
 *  Author: acer
 */ 


#ifndef ESP8266_H_
#define ESP8266_H_

#define GETRECEIVED 1
#define USERDEFINEDRECEIVED 2
#define BTN1RCVDON 3
#define BTN1RCVDOFF 4
#define BTN2RCVDON 5
#define BTN2RCVDOFF 6
#define BTN3RCVDON 7
#define BTN3RCVDOFF 8
#define BTN4RCVDON 9
#define BTN4RCVDOFF 10
#define NOTHINGSET 11



void esp8266_init(void);
bool esp8266_send(const char* text1);
uint8_t readInComing(int timeout, const char* text1);
void resetIncoming(void);
uint8_t checkIncomingFlag(void);
char* getIP(void);

extern unsigned long int msCounter;
struct RcvFlag rcvFlag1;

struct RcvFlag {
	bool getReceived;
	bool userDefinedReceived;
	bool btn1RcvdOn;
	bool btn1RcvdOff;
	bool btn2RcvdOn;
	bool btn2RcvdOff;
	bool btn3RcvdOn;
	bool btn3RcvdOff;
	bool btn4RcvdOn;
	bool btn4RcvdOff;
};


#endif /* ESP8266_H_ */

esp8266.c

/*
 * esp8266.c
 *
 * Created: 8-2-2018 21:07:55
 *  Author: acer
 */ 

#define F_CPU 16000000UL // 16 MHz clock speed

#include <avr/io.h>
#include <util/delay.h>
#include <stdbool.h>
#include <string.h>
#include <stdlib.h>
#include <avr/pgmspace.h>
#include "timer4clocklib.h"
#include "i2cdisplay.h"
#include "uart.h"
#include "esp8266.h"

/****************************************************************************
	Bit and byte definitions
****************************************************************************/

char getReceived[] = "GET / HTTP/1.1";
char btn1RcvdOn[] = "GET /?p1=on HTTP/1.1";
char btn1RcvdOff[] = "GET /?p1=off HTTP/1.1";
char btn2RcvdOn[] = "GET /?p2=on HTTP/1.1";
char btn2RcvdOff[] = "GET /?p2=off HTTP/1.1";
char btn3RcvdOn[] = "GET /?p3=on HTTP/1.1";
char btn3RcvdOff[] = "GET /?p3=off HTTP/1.1";
char btn4RcvdOn[] = "GET /?p4=on HTTP/1.1";
char btn4RcvdOff[] = "GET /?p4=off HTTP/1.1";

/****************************************************************************
	esp8266_init(), initialize the esp8266
****************************************************************************/
void esp8266_init(){
	
	uint8_t p;
	
	// Check the status of the ESP8266 if not ok reset
	_delay_ms(500);
	serialWrite("AT", CRLF);
	p = readInComing(1000, "OK\r\n");
	if (p != 2){
		writeI2cDisplayLine("Reset needed!", 0, 1);
		_delay_ms(1000);
		serialWrite("AT+RST", CRLF);
		p = readInComing(2000, "OK\r\n");
		if (p == 2){
			writeI2cDisplayLine("Reset done OK!", 0, 1);
			_delay_ms(1000);
		}
	}
	else{
		writeI2cDisplayLine("ESP8266 conn. OK", 0, 1);
		_delay_ms(1000);
	}
	
	// reset all flags generated by incoming data from the ESP8266 and empty the buffer.
	resetIncoming();
	flushBuffer();
	
	// configure the server and activate the server on the ESP8266
	serialWrite("AT+CIPMUX=1", CRLF);
	p = readInComing(1000, "OK\r\n");
	if (p == 2){
		resetIncoming();
		flushBuffer();
		writeI2cDisplayLine("AT+CIPMUX=1 set", 0, 1);
		_delay_ms(1000);
		serialWrite("AT+CIPSERVER=1,80", CRLF); //\r\n
		p = readInComing(1000, "OK\r\n");
		if (p == 2){
			resetIncoming();
			writeI2cDisplayLine("AT+CIPSERVER set", 0, 1);
			//_delay_ms(1000);
		}
	}
	
	// init is done so clean up all flags again.
	resetIncoming();
}

/****************************************************************************
	esp8266_send, send uart data to the esp8266
****************************************************************************/
bool esp8266_send(const char* text1){
	
	int p;
	int textLength;
	char buf1[700] = {'\0'};
	char itoaBuf[3];

	// the PROGMEM memory for saving the data memory usage.
	if (text1 != NULL){
		strcpy_P(buf1, (char *) text1);
		textLength = strlen(buf1);
	}
	else{
		textLength = 0;
	}
	
	// the esp8266 requires the amount of bytes that will be send in chars.
	itoa(textLength+2, itoaBuf ,10);
	serialWrite("AT+CIPSEND=0,", NO_EOL);
	serialWrite(itoaBuf, CRLF);
	
	// the data is send wait on the esp8266 for a confirmation
	p = readInComing(1000, "OK\r\n");
	if (p == 2){
		resetIncoming();
		flushBuffer();
		serialWrite(buf1, CRLF);
		p = readInComing(500, "SEND OK\r\n");
		if (p == 2){
			resetIncoming();
			flushBuffer();
			return true;
		}
		else{
			return false;
		}
	}
	else{
		return false;
	}
		
}

/****************************************************************************
	checkInComing, check/read data received by the esp8266
****************************************************************************/
uint8_t readInComing(int timeOut, const char* text1){ // ipv int
	
	char buf1[30] = {'\0'};
	uint8_t flag;
	
	uint8_t p; //unsigned char p;
	
	uint8_t lengthGetRcvd;
	uint8_t lengthUsrDef;
	uint8_t lengthBtn1On, lengthBtn1Off, lengthBtn2On, lengthBtn2Off, lengthBtn3On, lengthBtn3Off, lengthBtn4On, lengthBtn4Off;
	
	
	uint8_t posGetRcvd = 0;
	uint8_t posUsrDef = 0;
	uint8_t posBtn1On = 0;
	uint8_t posBtn1Off = 0;
	uint8_t posBtn2On = 0;
	uint8_t posBtn2Off = 0;
	uint8_t posBtn3On = 0;
	uint8_t posBtn3Off = 0;
	uint8_t posBtn4On = 0;
	uint8_t posBtn4Off = 0;
	
	lengthGetRcvd = strlen(getReceived);
	lengthBtn1On = strlen(btn1RcvdOn);
	lengthBtn1Off = strlen(btn1RcvdOff);
	lengthBtn2On = strlen(btn2RcvdOn);
	lengthBtn2Off = strlen(btn2RcvdOff);
	lengthBtn3On = strlen(btn3RcvdOn);
	lengthBtn3Off = strlen(btn3RcvdOff);
	lengthBtn4On = strlen(btn4RcvdOn);
	lengthBtn4Off = strlen(btn4RcvdOff);
	
	
	
	if (text1 != NULL){
		strcpy(buf1, (char *) text1);
		lengthUsrDef = strlen(buf1);
	}
	else{
		lengthUsrDef = 0;
	}
	
	flag = checkIncomingFlag();			
	if (flag != NOTHINGSET){
		return flag;
	}

	
	if(timeOut != 0){ // user gives predefined time
		unsigned long stop = msCounter + timeOut;
		do{
			p = getChar();
			while (p != 0x0){
				
				posUsrDef = (p == buf1[posUsrDef]) ? posUsrDef + 1 : 0;
				if (lengthUsrDef > 0 && posUsrDef == lengthUsrDef){ // user defined
					rcvFlag1.userDefinedReceived = true;
					return USERDEFINEDRECEIVED;
				}
				p = getChar();
			}
		}while (msCounter < stop); } else if(timeOut == 0){ // if no time out set, used in the app to continuously check for incoming data p = getChar(); while (p != 0){ //writeI2cDisplay(p, DATAREGISTER); posGetRcvd = (p == getReceived[posGetRcvd]) ? posGetRcvd + 1 : 0; posUsrDef = (p == buf1[posUsrDef]) ? posUsrDef + 1 : 0; posBtn1On = (p == btn1RcvdOn[posBtn1On]) ? posBtn1On + 1 : 0; posBtn1Off = (p == btn1RcvdOff[posBtn1Off]) ? posBtn1Off + 1 : 0; posBtn2On = (p == btn2RcvdOn[posBtn2On]) ? posBtn2On + 1 : 0; posBtn2Off = (p == btn2RcvdOff[posBtn2Off]) ? posBtn2Off + 1 : 0; posBtn3On = (p == btn3RcvdOn[posBtn3On]) ? posBtn3On + 1 : 0; posBtn3Off = (p == btn3RcvdOff[posBtn3Off]) ? posBtn3Off + 1 : 0; posBtn4On = (p == btn4RcvdOn[posBtn4On]) ? posBtn4On + 1 : 0; posBtn4Off = (p == btn4RcvdOff[posBtn4Off]) ? posBtn4Off + 1 : 0; if (lengthGetRcvd > 0 && posGetRcvd == lengthGetRcvd){ // "GET / HTTP/1.1"
				rcvFlag1.getReceived = true;
				return 1;
			}
			if (lengthUsrDef > 0 && posUsrDef == lengthUsrDef){ // user defined
				rcvFlag1.userDefinedReceived = true;
				return 2;
			}
			if (lengthBtn1On > 0 && posBtn1On == lengthBtn1On){ // button 1 on
				rcvFlag1.btn1RcvdOn = true;
				return 3;
			}
			if (lengthBtn1Off > 0 && posBtn1Off == lengthBtn1Off){ // button 1 off
				rcvFlag1.btn1RcvdOff = true;
				return 4;
			}
			if (lengthBtn2On > 0 && posBtn2On == lengthBtn2On){ // button 2 on
				rcvFlag1.btn2RcvdOn = true;
				return 5;
			}
			if (lengthBtn2Off > 0 && posBtn2Off == lengthBtn2Off){ // button 2 off
				rcvFlag1.btn2RcvdOff = true;
				return 6;
			}
			if (lengthBtn3On > 0 && posBtn3On == lengthBtn3On){ // button 3 on
				rcvFlag1.btn3RcvdOn = true;
				return 7;
			}
			if (lengthBtn3Off > 0 && posBtn3Off == lengthBtn3Off){ // button 3 off
				rcvFlag1.btn3RcvdOff = true;
				return 8;
			}
			if (lengthBtn4On > 0 && posBtn4On == lengthBtn4On){ // button 4 on
				rcvFlag1.btn4RcvdOn = true;
				return 9;
			}
			if (lengthBtn4Off > 0 && posBtn4Off == lengthBtn4Off){ // button 4 off
				rcvFlag1.btn4RcvdOff = true;
				return 10;
			}
			
			p = getChar();
		}
	}
		
	return NOTHINGSET;
}

/****************************************************************************
	checkIncomingFlag, check the current status of what message is received at last
****************************************************************************/
uint8_t checkIncomingFlag(){
	
	
	if(rcvFlag1.getReceived == true){
		return GETRECEIVED;
	}
	else if(rcvFlag1.userDefinedReceived == true){
		return USERDEFINEDRECEIVED;
	}
	
	else if(rcvFlag1.btn1RcvdOn == true){
		return BTN1RCVDON;
	}
	
	else if(rcvFlag1.btn1RcvdOff == true){
		return BTN1RCVDOFF;
	}
	
	else if(rcvFlag1.btn2RcvdOn == true){
		return BTN2RCVDON;
	}
	
	else if(rcvFlag1.btn2RcvdOff == true){
		return BTN2RCVDOFF;
	}
	
	else if(rcvFlag1.btn3RcvdOn == true){
		return BTN3RCVDON;
	}
	
	else if(rcvFlag1.btn3RcvdOff == true){
		return BTN3RCVDOFF;
	}
	
	else if(rcvFlag1.btn4RcvdOn == true){
		return BTN4RCVDON;
	}
	
	else if(rcvFlag1.btn4RcvdOff == true){
		return BTN4RCVDOFF;
	}
	else{
		return NOTHINGSET;
	}
}

/****************************************************************************
	resetIncoming, reset all flags to prepare for the next expected data from the esp8266
****************************************************************************/

void resetIncoming(){
	
	
	rcvFlag1.getReceived = false;
	rcvFlag1.userDefinedReceived  = false;
	rcvFlag1.btn1RcvdOn = false;
	rcvFlag1.btn1RcvdOff = false;
	rcvFlag1.btn2RcvdOn = false;
	rcvFlag1.btn2RcvdOff = false;
	rcvFlag1.btn3RcvdOn = false;
	rcvFlag1.btn3RcvdOff = false;
	rcvFlag1.btn4RcvdOn = false;
	rcvFlag1.btn4RcvdOff = false;
}


/****************************************************************************
	getIP, just to know the IP as i forgot to make it static instead of dhcp...
****************************************************************************/

char* getIP(){
	
	uint8_t p;
	int posIpReceived = 0;
	char matchStaip[] = "STAIP,\"";
	int ipCharsCounter = 0;
	int staIpLength = strlen(matchStaip);
	static char tempBuffer[16];
	bool matched = false;
	
	serialWrite("AT+CIFSR", CRLF);
	_delay_ms(200); // wait for all the data from this cmd
	p = getChar();
	while (p != 0x0){
		
		if(p == matchStaip[posIpReceived]){
			posIpReceived++;
		}
		else{
			posIpReceived = 0;
		}
		
		if (staIpLength > 0 && posIpReceived == staIpLength){ // STAIP received
			matched = true;
		}
		
		if(matched == true){
			tempBuffer[ipCharsCounter] = p;
			ipCharsCounter++;
			if (ipCharsCounter > 16){
				ipCharsCounter = 0;
				matched = false;
				flushBuffer();
				return tempBuffer;
			}
			
		}
		p = getChar();
	}
	
	return NULL;
	
}

application1.c

/*
 * application1.c
 *
 * Created: 8-2-2018 15:07:22
 * Author: HdH
 * Description: webserver controlling 4 outputs.
 */

#define F_CPU 16000000UL // 16 MHz clock speed for delay.h
#define PIN_LED1 10
#define PIN_LED2 9
#define PIN_LED3 6
#define PIN_LED4 8

#include <stdio.h>
#include <avr/io.h>
#include <util/delay.h>
#include <string.h>
#include <stdbool.h>
#include <avr/pgmspace.h>
#include "gpiolib.h"
#include "i2cdisplay.h"
#include "uart.h"
#include "esp8266.h"


void sendPage(void);

/****************************************************************************
	Bit and byte definitions
****************************************************************************/

const char htmlHeader[] PROGMEM = "HTTP/1.1 200 OK\r\nContent-Location: /buttons.html\r\nContent-Type: text/html\r\n";
const char htmlPart1[] PROGMEM = "<!DOCTYPE HTML><html><head><title>hdh</title></head><body>

<form method=\"GET\" >
"
"

 GPIO1 <input type=\"submit\" name=\"p1\" value=\"on\"><input type=\"submit\" name=\"p1\" value=\"off\">

"
"

 GPIO2 <input type=\"submit\" name=\"p2\" value=\"on\"><input type=\"submit\" name=\"p2\" value=\"off\">

"
"

 GPIO3 <input type=\"submit\" name=\"p3\" value=\"on\"><input type=\"submit\" name=\"p3\" value=\"off\">

"
"

 GPIO4 <input type=\"submit\" name=\"p4\" value=\"on\"><input type=\"submit\" name=\"p4\" value=\"off\">

"
"</form>


</body></html>\r\n";


extern int alarmCode;


void Application1(void){

uint8_t p;

	p = readInComing(0, NULL); // Checking incoming uart data
	switch (p){
		
		case GETRECEIVED:
			writeI2cDisplayLine("Get received",0,1);
			sendPage();
			break;
		case BTN1RCVDON:
			writeI2cDisplayLine("Button1 on pressed",0,1);
			writePin(PIN_LED1, HIGH);
			sendPage();
			break;
		case BTN1RCVDOFF:
			writeI2cDisplayLine("Button1 off pressed",0,1);
			writePin(PIN_LED1, LOW);
			sendPage();
			break;
		case BTN2RCVDON:
			writeI2cDisplayLine("Button2 on pressed",0,1);
			writePin(PIN_LED2, HIGH);
			sendPage();
			break;
		case BTN2RCVDOFF:
			writeI2cDisplayLine("Button2 off pressed",0,1);
			writePin(PIN_LED2, LOW);
			sendPage();
			break;
		case BTN3RCVDON:
			writeI2cDisplayLine("Button3 on pressed",0,1);
			writePin(PIN_LED3, HIGH);
			sendPage();
			break;
		case BTN3RCVDOFF:
			writeI2cDisplayLine("Button3 off pressed",0,1);
			writePin(PIN_LED3, LOW);
			sendPage();
			break;
		case BTN4RCVDON:
			writeI2cDisplayLine("Button4 on pressed",0,1);
			writePin(PIN_LED4, HIGH);
			sendPage();
			break;
		case BTN4RCVDOFF:
			writeI2cDisplayLine("Button4 off pressed",0,1);
			writePin(PIN_LED4, LOW);
			sendPage();
			break;
			
		default:
			break;
	}
}

void sendPage(){

	flushBuffer();
	resetIncoming();
	
	esp8266_send(htmlHeader);
	esp8266_send(htmlPart1);
	
	serialWrite("AT+CIPCLOSE=5", CRLF);
	
}

uart.h

/*
 * uart.h
 *
 * Created: 8-12-2017 13:36:22
 *  Author: acer
 */


#ifndef UART_H_
#define UART_H_

/****************************************************************************
	Bit and byte definitions
****************************************************************************/
#define CLOCKSPEED_CPU   16000000
#define BAUD    57600
#define BRC     ((CLOCKSPEED_CPU/16/BAUD) - 1)
#define TX_BUFFER_SIZE  40
#define RX_BUFFER_SIZE  300
#define CR 1
#define LF 2
#define CRLF 3
#define NO_EOL 4



/****************************************************************************
	Function definitions
****************************************************************************/

void UART_Init();
void serialWrite(char c[], uint8_t eol);
void UART_Transmit(unsigned char data );
uint8_t getChar(void);
void flushBuffer(void);


#endif /* UART_H_ */

uart.c

/*
 * uart.c
 *
 * Created: 8-12-2017 13:36:09
 *  Author: acer
 */ 

#define F_CPU 16000000UL // 16 MHz clock speed


#include <avr/io.h>
#include <stdlib.h>
#include <stdio.h>
#include <util/delay.h>
#include <avr/interrupt.h>
#include <string.h>
#include <stdbool.h>
#include "uart.h"


/****************************************************************************
	Bit and byte definitions
****************************************************************************/
char rxBuffer[RX_BUFFER_SIZE];
uint8_t rxWritePos = 0;
uint8_t rxReadPos = 0;
bool bufferOverFlow = false;
long bufferOverFlowWaitTime;


/****************************************************************************
	USART_Init, see important info below
****************************************************************************/
void UART_Init(){

	UBRR0H = (unsigned char)(BRC >> 8); //baud rate register set to 9600 BAUD
	UBRR0L = (unsigned char) BRC;

	UCSR0B = (1<<RXEN0) | (1<<TXEN0) | (1 << RXCIE0);
	UCSR0C = (1 << UCSZ01) | (3<<UCSZ00);
}


/****************************************************************************
	serialWrite, send data with optional crlf
****************************************************************************/
void serialWrite(char c[], uint8_t eol)
{
	int i; //was uint8
	
	//_delay_us(100);
	for(i = 0; i < strlen(c); i++)// put the string you want to write in a buffer. { UART_Transmit(c[i]); //put the char's one by one in the buffer } if (eol == CR){ UART_Transmit(0xd); //CR \r } else if (eol == LF){ UART_Transmit(0xa); //LF \n } else if (eol == CRLF){ UART_Transmit(0xd); //CR \r UART_Transmit(0xa); //LF \n } else if (eol == NO_EOL){ return; } } /**************************************************************************** ISR(USART_RX_vect), Fills up a buffer, to keep the buffer not to big I used a buffer overflow var in case the buffer exceeds. Internet explorer sends almost 500 bytes. the buffer overflow gets a reset by the flushbuffer function. ****************************************************************************/ ISR(USART_RX_vect) { if(rxWritePos >= RX_BUFFER_SIZE - 1){
		bufferOverFlow = true;
	}
	if (bufferOverFlow == false){
		rxBuffer[rxWritePos+1] = 0x0;
		rxBuffer[rxWritePos] = UDR0;//
		rxWritePos++;
	}
}

/****************************************************************************
	USART_Transmit, 
****************************************************************************/
void UART_Transmit(unsigned char data ){
	
	while( !( UCSR0A & (1<<UDRE0)) ); // Wait for empty transmit buffer
	UDR0 = data; // Put data into buffer, sends the data
}


/****************************************************************************
	getChar, 
****************************************************************************/


uint8_t getChar(void)
{
	uint8_t ret = 0x0;  // '\0';
	
	_delay_us(500); // to prevent reading faster then the bytes are coming in
	if(rxWritePos != 0){
		ret = rxBuffer[rxReadPos];
		rxReadPos++;
		if(ret == 0){ // last byte read, start from the beginning
			flushBuffer();
		}
	}
	return ret;
}


void flushBuffer(void){
	
	for(int i=0; i<RX_BUFFER_SIZE; i++){
		rxBuffer[i] = 0;
	}
	rxWritePos = 0;
	rxReadPos = 0;
	bufferOverFlow = false;
}

 

Even a simple webpage uses relatively a lot of memory when using a microcontroller. To get a better user interface I will make an android application to control the microcontroller instead of a web browser in a later blog. So stay tuned and thanks for reading!

 

Access control with RFID

In my last blog I created a library for UART communication. Now it can be used for an 125KHz RFID module. This module uses the UART to communicate with a microcontroller. After reading an RFID key it will send a 14 digit code over the UART. In this project the software reads the codes and compares it with a hard coded RFID code. When the codes are the same the user gets access. The results are shown on a lcd in this project, but it can also be send to a computer or where ever you want. See the below movie to get a view of the RFID project.

 

Mostly these types of modules are used to give someone access to a building or get some user data. Another nice thing is setting up a predifined configuration at home. For example some particular lights at home will light up for a user1 and other lights will light up when an other user scans his RFID key. Besides that it would also be fun if the TV gets turned on a particular channel corrosponding to an RFID code. The plan is setting this up in a future blog. For now see the schematic for this project below.

Schematic RFID project

 

 

The RFID module used in this project

 

 

The source code only got some minor changes compared to the last blog. See the changes below or download the full code here.

initialize.c

/*
 * initialize.c
 *
 * Created: 20-5-2017 15:07:22
 * Author: HdH
 * Description: initialization off the project
 */ 

#define PIN_LED1 6
#define PIN_LED2 7
#define PIN_LED3 8

#include <avr/interrupt.h>
#include "gpiolib.h"
#include "timer4clocklib.h"
#include "i2c.h"
#include "i2cdisplay.h"
#include "uart.h"


void initialize(){
	
	char openingText[] = "www.hdhprojects.nl";
	char rfidText[] = "RFID:";
	int i;

	cli();//interrupts disable
	tmr2_init(); // initialize timer2
	i2c_init(); // initialize i2c protocol
	i2cLcd_Init(); // initialize i2c lcd
	UART_Init(); // initialize the UART
	sei();//interrupts enable
	
	clear();
	setCursor(1,0);
	for(i=0; i<18; i++){		
		writeI2cDisplay(openingText[i], DATAREGISTER);
	}
	
	setCursor(0,1);
	for(i=0; i<5; i++){
		writeI2cDisplay(rfidText[i], DATAREGISTER);
	}
	
	pinMode(PIN_LED1,PINMODEOUTPUT); //set pin to output
	pinMode(PIN_LED2,PINMODEOUTPUT); //set pin to output
	pinMode(PIN_LED3,PINMODEOUTPUT); //set pin to output	
}

application1.c

/*
 * application1.c
 *
 * Created: 20-5-2017 15:07:22
 * Author: HdH
 * Description: Application for multi threat, show lcd clock and adc
 */

#define F_CPU 16000000UL // 16 MHz clock speed for delay.h
#define KEY1 "28005A08572D"



#include <avr/io.h>
#include <stdlib.h>
#include <util/delay.h>
#include <string.h>
#include "i2c.h"
#include "i2cdisplay.h"
#include "uart.h"


/****************************************************************************
	Bit and byte definitions
****************************************************************************/

extern int alarmCode;
char sendText2a[] = "Write me!";
char accessGranted[] = "Access granted!";
char accessDenied[] = "Access denied";
char receivedCode[14];


void Application1(void){

int i;
int p;

	if (getRcvdMsgLength() > 0){
		clearDspRange(0, 2, 20); // Clear the receive line on the lcd
		
		setCursor(6,1);
		i=0;
		p = getMsgChar();
		while (p != 0){ // Write the received message on the lcd and in a buffer for comparing
			receivedCode[i] = p;
			writeI2cDisplay(p, DATAREGISTER);
			p = getMsgChar();
			i++;
		}
		
		setCursor(0, 2);
		if(strstr(receivedCode, KEY1) != NULL){ // code is ok		
			for(uint8_t q=0; q<14; q++){
				writeI2cDisplay(accessGranted[q], DATAREGISTER);
			}
		}
		else{ // code is wrong
			for(uint8_t q=0; q<13; q++){
				writeI2cDisplay(accessDenied[q], DATAREGISTER);
			}
		}
	}
}


 

 

Communication via the UART

In this project I set up a library to communicate, via the Atmega328p UART, with a computer. The Atmega328 sends a text via the UART to a computer and you can write text back to the Atmega328. For writing text to the Atmega328 I used the software Teraterm. It is a standard terminal emulator, but this one has a lot of options and it is free. The text received back by the Atmega328 gets shown on the i2c lcd.

Building the UART project

The Atmega328p build-in USART can send/receive data synchronized with a clock or without a clock by using start and stop bits. I used it in asynchrone mode therefore I don’t use a clock as reference. The below schematic only shows the input and output to the computer in text labels. That means RS232PIN_2 should be connected to pin 2 of the communication cable to the computer and RS232PIN_3 to pin 3 and ground goes to pin 5.

Pin-out for the computer connection seen from the front side
Schematic connecting the Atmega328p to a computer

 

The code

First thing to do is initializing the UART. To do this a few things needed to be known beforehand. The first thing is the baudrate. Very common for most devices is a baud rate of 9600 bps so I used this speed. The baud rate is set in the 12 bit UBRR0 register. The formula to calculate the bits for the UBRR0 register is ((CLOCKSPEED_CPU/16/BAUD) – 1), but it’s also possible to just look up the correct value in a table with most common values. This table can be found in the datasheet.

By default pin 1 and 2 are used as general GPIO pin’s. To get them working as sending and receiving pins they have to be enabled in the UCSR0B  register. In this register the rx pin gets enabled by setting the RXEN0 bit high. And tx gets enabled by setting the bit TXEN0 high. Both rx and tx can be used with interrupts. In this case I enable the interrupts only for rx by setting the bit RXCIE0 high. For tx, the bytes get send on demand without triggering any interrupts. The advantage for enabling the rx trigger bit is that we can catch all bytes that are send even if the Atmega328p is doing other things. Everytime a byte is received it will be put in a buffer for later usage.

UCSRnB register

There are a few other bits in this register, but these are not used in this project. More info can be found in the datasheet.

The last register for initialization is the UCSR0C register. In this register the UMSELn1 and UMSELn0 are set to 0 to get asynchrone communication. The UPMnx bits are zet to 0 to select no checks for parity bits. The USBSn bit is left to 0 to select 1 stop bit. UCSZ01 and UCSZ00 are set to 1 to select 8 bits character size for the communication. The last bit UCPOLn is not set, because it’s only used for synchronized communication.

UCSRnC register

So far the initialization. In this project the code for the UART_Init() initialization function is taking care of the whole initialization process.

void UART_Init(){

	UBRR0H = (unsigned char)(BRC >> 8); //baud rate register set to 9600 BAUD
	UBRR0L = (unsigned char) BRC;

	//TXEN0 enables tx so no gpio anymore, when disabled the buffer will be finished first
	//RXEN0 enables rx
	//RXCIE0 enables the interrupt to be used with the ISR(USART_RX_vect)
	UCSR0B = (1<<RXEN0) |(1<<TXEN0) | (1 << RXCIE0);

	//Set frame format: 8data, 1stop bit, no parity.
	//UMSEL01 and UMSEL00 are both 0 to get asynchronous communication
	//UPM01 and UPM00 used to set parity, it's 0 here so no parity
	//USBS0 set to 0 is 1 stop bit. set to 1 is 2 stop bits.
	//USCZ00 - USCZ02 is used to set the character size, here it's set to 8 bits
	//UCPOL0 is clock parity and only used for synchronized transmission, not used
	UCSR0C = (1 << UCSZ01) | (3<<UCSZ00);
}


After initialization the other functions of the uart library can be used. I used the rx interrupt function ISR(USART_RX_vect) for writing all the received bytes in a buffer and when an “enter” is received it will copy everything before the “enter” in another buffer for later use. The getRcvdMsgLength() function will retreive the length of the received bytes. It’s a bit simular like the serial.available() function with Arduino. The function is used mainly to check if something is received at all. If there is something received the getMsgChar() function can be called to get the bytes in the buffer, one by one.

For the tx part the serialWrite(char c[]) function is sending all the bytes over the UART. It takes the length of the bytes that are needed to be send and sends them one by one. As I don’t have a keyboard on the Atmega328 I keep sending the same message “write me” to the computer after a message has received from the computer.

Below contains the code that is different from the original framework. The complete source code can be downloaded here.

Initialize.c

/*
 * initialize.c
 *
 * Created: 20-5-2017 15:07:22
 * Author: HdH
 * Description: initialization off the project
 */ 

#define PIN_LED1 6
#define PIN_LED2 7
#define PIN_LED3 8

#include <avr/interrupt.h>
#include "gpiolib.h"
#include "timer4clocklib.h"
#include "i2c.h"
#include "i2cdisplay.h"
#include "uart.h"


void initialize(){
	
	char openingText[] = "www.hdhprojects.nl";
	char sendText[] = "Send:";
	char receiveText[] = "Received:";
	int i;

	cli();//interrupts disable
	tmr2_init(); // initialize timer2
	i2c_init(); // initialize i2c protocol
	i2cLcd_Init(); // initialize i2c lcd
	UART_Init(); // initialize the UART
	sei();//interrupts enable
	
	clear();
	setCursor(1,0);
	for(i=0; i<18; i++){		
		writeI2cDisplay(openingText[i], DATAREGISTER);
	}
	
	setCursor(0,1);
	for(i=0; i<5; i++){
		writeI2cDisplay(sendText[i], DATAREGISTER);
	}
	setCursor(0,2);
	for(i=0; i<9; i++){
		writeI2cDisplay(receiveText[i], DATAREGISTER);
	}
	
	pinMode(PIN_LED1,PINMODEOUTPUT); //set pin to output
	pinMode(PIN_LED2,PINMODEOUTPUT); //set pin to output
	pinMode(PIN_LED3,PINMODEOUTPUT); //set pin to output	
}

 

Application1.c

/*
 * application1.c
 *
 * Created: 20-5-2017 15:07:22
 * Author: HdH
 * Description: Application for multi threat, show lcd clock and adc
 */

#define F_CPU 16000000UL // 16 MHz clock speed for delay.h

#include <avr/io.h>
#include <stdlib.h>
#include <util/delay.h>
#include "i2c.h"
#include "i2cdisplay.h"
#include "uart.h"


/****************************************************************************
	Bit and byte definitions
****************************************************************************/

extern int alarmCode;
char sendText2a[] = "Write me!";


void Application1(void){

int i;
int p;

	if (getRcvdMsgLength() > 0){
		clearDspRange(0, 3, 20); // Clear the receive line on the lcd
		p = getMsgChar();
		while (p != 0){ // Write the received message on the lcd
			writeI2cDisplay(p, DATAREGISTER);
			p = getMsgChar();
		}
		
		setCursor(6,1); // Set the cursor at the send position on the lcd
		for(i=0; i<9; i++){ // Send the predefined text to send via the UART
			writeI2cDisplay(sendText2a[i], DATAREGISTER);
		}
		serialWrite("Write me!\n\r");
	}
}



uart.h

/*
 * uart.h
 *
 * Created: 8-12-2017 13:36:22
 *  Author: acer
 */


#ifndef UART_H_
#define UART_H_

/****************************************************************************
	Bit and byte definitions
****************************************************************************/
#define CLOCKSPEED_CPU   16000000
#define BAUD    9600
#define BRC     ((CLOCKSPEED_CPU/16/BAUD) - 1)
#define TX_BUFFER_SIZE  20
#define RX_BUFFER_SIZE  20


/****************************************************************************
	Function definitions
****************************************************************************/
void UART_Init();
void serialWrite(char c[]);
void UART_Transmit(unsigned char data );
uint8_t getRcvdMsgLength();
char getMsgChar();
void emptyRxBuffer();


#endif /* UART_H_ */

 

uart.c

/*
 * uart.c
 *
 * Created: 8-12-2017 13:36:09
 *  Author: acer
 */ 

#include <avr/io.h>
#include <avr/interrupt.h>
#include <string.h>
#include "uart.h"

/****************************************************************************
	Bit and byte definitions
****************************************************************************/
char rxBuffer[RX_BUFFER_SIZE];
char rxFinalReceived[RX_BUFFER_SIZE];
uint8_t rxWritePos = 0;
uint8_t rxWriteFinalPos = 0;
char tempRcvd;
uint8_t rcvdMssgLength;
uint8_t pntr2CharInMsg = 0;


/****************************************************************************
	USART_Init, see important info below
****************************************************************************/
void UART_Init(){

	UBRR0H = (unsigned char)(BRC >> 8); //baud rate register set to 9600 BAUD
	UBRR0L = (unsigned char) BRC;

	//TXEN0 enables tx so no gpio anymore, when disabled the buffer will be finished first
	//RXEN0 enables rx
	//RXCIE0 enables the interrupt to be used with the ISR(USART_RX_vect)
	UCSR0B = (1<<RXEN0) |(1<<TXEN0) | (1 << RXCIE0);

	//Set frame format: 8data, 1stop bit, no parity.
	//UMSEL01 and UMSEL00 are both 0 to get asynchronous communication
	//UPM01 and UPM00 used to set parity, it's 0 here so no parity
	//USBS0 set to 0 is 1 stop bit. set to 1 is 2 stop bits.
	//USCZ00 - USCZ02 is used to set the character size, here it's set to 8 bits
	//UCPOL0 is clock parity and only used for synchronized transmission, not used
	UCSR0C = (1 << UCSZ01) | (3<<UCSZ00);
}


/****************************************************************************
	serialWrite, 
****************************************************************************/
void serialWrite(char c[])
{
	uint8_t i;
	for(i = 0; i < strlen(c); i++)// put the string you want to write in a buffer.
	{
		UART_Transmit(c[i]); //put the char's one by one in the buffer
	}
	
}


/****************************************************************************
	ISR(USART_RX_vect), Fills up a buffer, when an enter is received the msg 
	get copied to another buffer so you will receive bytes send in the mean time.
****************************************************************************/
ISR(USART_RX_vect)
{
	int i;
	int k;
	
	i = UDR0;
	
	rxBuffer[rxWritePos] = i; //save the received byte
		
	if (i == 0xa){ // \n = 0xa
		if (tempRcvd == 0xd){ // \r = 0xd
			for(k=0; k<rxWriteFinalPos; k++){ rxFinalReceived[k] = rxBuffer[k]; } rcvdMssgLength = rxWriteFinalPos - 1; // minus the \r\n rxWriteFinalPos = 0; rxWritePos = 0; // Start at the beginning of the buffer for the next message return; } } tempRcvd = i; // Remember byte before last byte to check if \r\n is received rxWritePos++; rxWriteFinalPos++; if(rxWritePos >= RX_BUFFER_SIZE) // If the buffer is full start at the beginning again
	{
		rxWritePos = 0;
	}
	
}

/****************************************************************************
	USART_Transmit, 
****************************************************************************/
void UART_Transmit(unsigned char data ){
	
	while( !( UCSR0A & (1<<UDRE0)) ); // Wait for empty transmit buffer UDR0 = data; // Put data into buffer, sends the data } /**************************************************************************** getRcvdMsgLength, Get the length until receiving a \n\r ****************************************************************************/ uint8_t getRcvdMsgLength(){ if (rcvdMssgLength == 0){ return 0; } return rcvdMssgLength; } /**************************************************************************** getMsgChar, Get the first char and point to the next char ****************************************************************************/ char getMsgChar(){ char rcvdChar; rcvdChar = rxFinalReceived[pntr2CharInMsg]; if(pntr2CharInMsg >= rcvdMssgLength ){
		rcvdMssgLength = 0;
		pntr2CharInMsg = 0;
		emptyRxBuffer();
		return 0;
	}
	
	pntr2CharInMsg++;
	return rcvdChar;
	
}

/****************************************************************************
	emptyBuffer, just to clear the rx buffer
****************************************************************************/
void emptyRxBuffer (){
	
	int i;
	
	for (i=0; i<=RX_BUFFER_SIZE; i++)
	{
		rxFinalReceived[i] = 0x0;
	}
}


For a future project I need to check if an “enter” is received therefor I already added the code for this in the uart library. To send text from the computer to the Atmega328p the following settings have to be set to be able to send text. Specially the “new line” transmit CR+LF in the menu setup -> terminal.

Teraterm settings

Now the UART is working fine I can use this library for receiving an access code by a Rfid module and arrange user control. I hope to get this working in a next blog. The UART is also used for the ESP8266 WiFi module to connect your home devices to the internet and have remote access. This also will be a future project. Thanks for reading!

Atmega328 with ADC

In this project I tested the analog to digital converter (ADC) from the Atmega328p. It read’s out an analog pin and shows the value on an i2c lcd. There are 6 analog ports and 2 are already used for the lcd. To change the voltage at the analog pin I used a 10k potmeter. The ADC can be set to free running mode or single conversion mode. Free running mode means the ADC samples continuously and single conversion mode means, only sample when you order to do so. I use the single conversion mode in this project.

 

Atmega328 with ADC and LCD schematic.

 

 

The working of the ADC

The first thing that’s needed to be done is initializing the ADC. I used a separate function called adc_init() in the file adc.c to get this done. For a proper initialization a few registers have to be set. For single conversion mode not all registers are used therefor I only describe the used registers in this project.

The ADMUX register is dealing with the voltage reference and the analog pin(s) that will be used.


Bit 6 and 7 tells the Atmega328p against what reference the voltage on the pin should be measured. This is important for the precision of the 10 bit read value. For example, it is possible to put 2V as a reference on the AREF pin. In that case the 10 bits (value up to 1023) will be divided over 2V. It’s more presice. It also means that every voltage above 2V on the analog pin will be presented as 1023. In this project I used Avcc (5V) as a reference therefor bit REFS0 is set.



The last register I use for initializing is the ADCSRA register.


Bit 7 is the ADC enable and needs to be set to activate the ADC. Bit 6 is the ADC start conversion, this is used in the application every time you need to read a value from the analog pin. Bit 5-3 is used for interrupts and triggering on certain conditions. Not used in this project. Bit 2 – 0 sets a prescaler. The 16MHz external clock gets divided by this prescaler to set the sampling rate. In this project all 3 bits are set, which gives a division factor of 128. The Atmega328p ADC circuit needs a clock below 200 KHz to get a 10 bit resolution and by using a higher frequentie the resolution will only be 8 bits, but then you will have a faster sampling rate. In this project I use 16 MHz / 128 to get 125 KHz. This is below 200 KHz so it will provide the full 10 bits resolution.


The initialization function uses the below code to initialize the ADC for Single conversion mode.

void adc_init(){
	
	ADMUX = (1<<REFS0);
	ADCSRA = (1<<ADEN)|(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0);
}

For reading a pin it’s necessary to set ADMUX again with the analog pin you want to use. After that the conversion gets started by setting the ADSC bit in the ADCSRA register. When the conversion is done this bit is automatically unset by the Atmega328p hardware so wait for this in a while loop. Last thing is returning the value to do something with it. Because it’s a 10 bit value the return value is 2 bytes placed in ADC.

uint16_t adc_read(int pin){
	if(pin < 0 || pin > 7){ //ADC0 - ADC7 is available
		return 1; //pin number is out of range
	}
	
	pin &= 0x7;
	ADMUX = (ADMUX & 0xF8)|pin;
	
	ADCSRA |= (1<<ADSC);
	
	while(ADCSRA & (1<<ADSC)); //as long as ADSC pin is 1 just wait.
	
	return (ADC);
}

 

The application1.c file contains the code that deals with reading the analog pin and writing the value to the lcd.

Application1.c

/*
 * application1.c
 *
 * Created: 20-5-2017 15:07:22
 * Author: HdH
 * Description: Application for multi threat, show lcd clock and adc
 */ 

#include <avr/io.h>
#include <stdlib.h>
#include "i2c.h"
#include "i2cdisplay.h"
#include "timer4clocklib.h"
#include "adc.h"

#define ADCPIN 2
#define DELAYMILIS 150 // Led blinking wait time in milliseconds
#define TIMER_IS_NOT_SET 0
#define TIMER_IS_SET 1

void showTime(); // showing the time
void showAdcValue(); // showing the ADC value

/****************************************************************************
	Public vars
****************************************************************************/

extern int alarmCode;
extern int timeSecApp1;
extern int timeMinApp1;
int oldTime;


int timerSet4Adc = TIMER_IS_NOT_SET;
unsigned long int endTimeAdc = 0;


void Application1(void){

	if(timerSet4Adc == TIMER_IS_NOT_SET){
		endTimeAdc = msCounter + DELAYMILIS;
		timerSet4Adc = TIMER_IS_SET;
		showAdcValue();
	}
	if(endTimeAdc < msCounter){ // Timer is expired
		timerSet4Adc = TIMER_IS_NOT_SET; // reset timer
		return;
	}

	
	showTime();
	
}

void showTime(){
	int seconds;
	int minutes;
	
	
	seconds = timeSecApp1;
	minutes = timeMinApp1;
	
	if (oldTime == timeSecApp1){
		return;
	}
	if (seconds >= 10){
		setCursor(11,2);
		writeI2cDisplay(seconds/10+48, DATAREGISTER);
		setCursor(12,2);
		writeI2cDisplay(seconds%10+48, DATAREGISTER);
		if (minutes>=10){
			setCursor(8,2);
			writeI2cDisplay(minutes/10+48, DATAREGISTER);
			setCursor(9,2);
			writeI2cDisplay(minutes%10+48, DATAREGISTER);
		}
		if (minutes<10){
			setCursor(8,2);
			writeI2cDisplay('0', DATAREGISTER);
			setCursor(9,2);
			writeI2cDisplay(minutes+48, DATAREGISTER);
		}
	}
	else if (seconds < 10){
		setCursor(12,2);
		writeI2cDisplay(seconds+48, DATAREGISTER);
		setCursor(11,2);
		writeI2cDisplay('0', DATAREGISTER);
		if (minutes>=10){
			setCursor(8,2);
			writeI2cDisplay(minutes/10+48, DATAREGISTER);
			setCursor(9,2);
			writeI2cDisplay(minutes%10+48, DATAREGISTER);
		}
		if (minutes<10){
			setCursor(8,2);
			writeI2cDisplay('0', DATAREGISTER);
			setCursor(9,2);
			writeI2cDisplay(minutes+48, DATAREGISTER);
		}
	}
	oldTime = timeSecApp1;
}

void showAdcValue(){
	
	uint16_t adcReadedValue;
	uint8_t hundreds;
	uint8_t tens;
	
	adcReadedValue = adc_read(ADCPIN);
	setCursor(8,3);
	
	if(adcReadedValue > 999){
		writeI2cDisplay('1', DATAREGISTER);
		adcReadedValue -= 1000;
	}
	else{
		writeI2cDisplay('0', DATAREGISTER);
	}
	hundreds = adcReadedValue / 100;
	adcReadedValue %= 100;
	writeI2cDisplay(hundreds + 48, DATAREGISTER);
	tens = adcReadedValue /10;
	adcReadedValue %= 10;
	writeI2cDisplay(tens + 48, DATAREGISTER);
	writeI2cDisplay(adcReadedValue + 48, DATAREGISTER);
	
}

initialize.c

/*
 * initialize.c
 *
 * Created: 20-5-2017 15:07:22
 * Author: HdH
 * Description: initialization off the project
 */ 

#define PIN_LED1 6
#define PIN_LED2 7
#define PIN_LED3 8
#define ADCPIN 0

#include <avr/interrupt.h>
#include "gpiolib.h"
#include "timer4clocklib.h"
#include "i2c.h"
#include "i2cdisplay.h"
#include "adc.h"


void initialize(){
	
	char openingText[] = "www.hdhprojects.nl";
	char clockText[] = "Clock";
	char adcText[] = "ADC";
	int i;

	cli();//interrupts disable
	tmr2_init(); // initialize timer2
	i2c_init(); // initialize i2c protocol
	i2cLcd_Init(); // initialize i2c lcd
	adc_init(); // initialize the ADC
	sei();//interrupts enable
	
	clear();
	setCursor(1,0);
	for(i=0; i<18; i++){		
		writeI2cDisplay(openingText[i], DATAREGISTER);
	}
	
	setCursor(1,2);
	for(i=0; i<5; i++){
		writeI2cDisplay(clockText[i], DATAREGISTER);
	}
	
	setCursor(10,2);
	writeI2cDisplay(':', DATAREGISTER);
	
	setCursor(1,3);
	for(i=0; i<3; i++){
		writeI2cDisplay(adcText[i], DATAREGISTER);
	}
	
	
	pinMode(PIN_LED1,PINMODEOUTPUT); //set pin to output
	pinMode(PIN_LED2,PINMODEOUTPUT); //set pin to output
	pinMode(PIN_LED3,PINMODEOUTPUT); //set pin to output	
}

adc.c

/*
 * adc.c
 *
 * Created: 4-12-2017 18:01:49
 *  Author: acer
 */ 

#include <avr/io.h>
#include "adc.h"

/****************************************************************************
	adc_init(), enables the ADC conversion and set's the prescaler
****************************************************************************/
void adc_init(){
	
	ADMUX = (1<<REFS0);
	ADCSRA = (1<<ADEN)|(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0);
}

/****************************************************************************
	adc_read(uint8_t pin), returns a value from 0 - 1023
****************************************************************************/
uint16_t adc_read(int pin){
	if(pin < 0 || pin > 7){ //ADC0 - ADC7 is available
		return 1; //pin number is out of range
	}
	
	pin &= 0x7;
	ADMUX = (ADMUX & 0xF8)|pin;
	
	ADCSRA |= (1<<ADSC);
	
	while(ADCSRA & (1<<ADSC)); //as long as ADSC pin is 1 just wait.
	
	return (ADC);
}

adc.h

/*
 * adc.h
 *
 * Created: 4-12-2017 18:02:01
 *  Author: acer
 */ 


#ifndef ADC_H_
#define ADC_H_

/****************************************************************************
	Bit and byte definitions
****************************************************************************/



/****************************************************************************
	Function definitions
****************************************************************************/
void adc_init();
uint16_t adc_read(int pin);




#endif /* ADC_H_ */

 

 

 

 

I2C lcd with clock

In this project I created a library for the i2c lcd. I used the Arduino library as a reference, but I found that library a bit to complicated to use it completely. The Arduino library also have a lot of functions that I’m not planning to use, like creating your own characters. The ASCII characters are enough for my projects for example. So I wrote a new library.

Communicating with the i2c lcd test build.

 

Again I used the same framework to get the lcd working simultanesly with other devices in the future and for now I only added 1 led to test the multi threating.

The lcd unit has a backpack attached used to communicate with the lcd via i2c. The original lcd uses more then 11 pin’s and that’s a lot for a microcontroller like the Atmega328p. The backpack uses the PCA8574, which is a 8 bit i2c pin expander to reduce the amount of pins to be used on a microcontroller. The lcd on it’s own has already 8 pins for writing to a register, but it is possible to set the lcd to use 4 bits instead. That means there are 4 pins left on the PCA8574 and these are used for selecting a register, read or write, enable puls and the backlight. See the below schematic how to connect.

I2c lcd project schematic.

 

Working of the display

Communicating with the display is a bit complicated because we have to use it in 4 bits  mode. If the backpack used a 16 bit expander instead of 8 bit then life whas much easier, but 8 bits expanders are cheaper… That means to write to an 8 bit register we have to send the high 4 bits of a byte first and then the last 4 bits. There are 2 registers to write to. That’s the data register DR and the instruction register IR. Text you want to get on the display have to be set in the data register. Command’s like, changing the cursor position or clearing the display, have to be set in the instruction register. The register select RS pin will let you choose between both the registers. Puttting the pin low (0V) selects the IR and high (5V) selects the DR.

It is possible to read an address location or to read the busy flag BF. The BF is used to check if the lcd is still working on an internal command. If so, it’s not possible to send a new instruction. I didn’t do anything with the BF yet or reading the address locations. For writing text on the lcd it’s not needed. I used delays according the datasheet to wait for the time the lcd needs.

There is a read write RW pin. To read from the lcd this pin should be high and for writing low. There is also a pin for the backlight BL, but my display requires this to be activated all the times else you won’t see anything on the display.

To set a byte into a register the enable EN pin have to be set high. This works like a latched flip flop, that means whatever voltage you put on the pins they will only be read by the lcd if the enable pin is high. Writing an instruction or a character are basically the same. The only difference is the register select pin. Basically to write to a register the following steps have to be done:

  1. Put the correct voltages on all the lcd pins for the first 4 bits of a byte you want to send.
  2. Set the enable pin high for more then 450 nano seconds and keep the rest of the pins the same.
  3. Set the enable pin low for more then 37 micro seconds and keep the rest of the pins the same.
  4. Do the last 3 steps again, but now for the last 4 bits of 8.

In code it looks like this:

i2c_start();
i2c_write((LCD_I2C_ADDR<<1)|0);
i2c_write(highnib | LCD_BACKLIGHT | registerselect);
i2c_stop();
i2c_start();
i2c_write((LCD_I2C_ADDR<<1)|0); i2c_write(highnib | LCD_BACKLIGHT | registerselect | ENABLE); i2c_stop(); _delay_us(1); // enable pulse must be >450ns
i2c_start();
i2c_write((LCD_I2C_ADDR<<1)|0); i2c_write((highnib | LCD_BACKLIGHT | registerselect ) & ~ENABLE); i2c_stop(); _delay_us(50); // commands need > 37us to settle
	
// same as above but now for last 4 bits
i2c_start();
i2c_write((LCD_I2C_ADDR<<1)|0);
i2c_write(lownib | LCD_BACKLIGHT | registerselect);
i2c_stop();
i2c_start();
i2c_write((LCD_I2C_ADDR<<1)|0); i2c_write(lownib | LCD_BACKLIGHT | registerselect | ENABLE); i2c_stop(); _delay_us(1); // enable pulse must be >450ns
i2c_start();
i2c_write((LCD_I2C_ADDR<<1)|0); i2c_write((lownib | LCD_BACKLIGHT | registerselect ) & ~ENABLE); i2c_stop(); _delay_us(50); // commands need > 37us to settle

 

Initializing the display

This subject needs a separate chapter, because its a bit complicated. The datasheet is not very clear about this or it is my understanding… Default the display is set to 8 bit mode therefor we have to put it on 4 bit mode first and this can’t be changed later on, only by rebooting the lcd. The Arduino library sends the same command 3 times with different times between the instructions. I send the instruction only once, but I use a delay before proceding. This first instruction have to be send in 1 time 4 bits and all other instructions have to be send in 2 times 4 bits. After that the lcd works in 4 bit mode and then the lcd lines and character dots configuration can be send in 2 times 4 bits.  These settings can be send only once and after that all other instructions can be send any time you need.

The next settings are the cursor on/off and blinking of the cursor. The last instruction set up the direction of the characters. For western countries, my case, that’s from left to right. After that the display initial settings are set and the lcd can be used. I made a seperate function called i2cLcd_init() to get this all done.

The application

For testing I wrote my name on the display and a clock. This clock only counts for 1 hour and then starts over again. Minor changes are needed to write a 24 hour clock, but for now I thought this is a good start. My clock is not so accurate at the moment. It’s about 2 seconds a minute to fast. To get the clock more accurate you can change the CMR_SECOND and the CMR_MINUT number in the timer4clocklib.h.

The source code

See the source code of the changed files here. All other files remain the same as before.

initialize.c

/*
 * initialize.c
 *
 * Created: 20-5-2017 15:07:22
 * Author: HdH
 * Description: initialization off the project
 */ 

#define PIN_LED1 6
#define PIN_LED2 7
#define PIN_LED3 8

#include <avr/interrupt.h>
#include "gpiolib.h"
#include "timer4clocklib.h"
#include "i2c.h"
#include "i2cdisplay.h"


void initialize(){
	
	char openingText[] = "www.hdhprojects.nl";
	char clockText[] = "Clock";
	int i;

	cli();//interrupts disable
	tmr2_init(); // initialize timer2
	i2c_init(); // initialize i2c protocol
	i2cLcd_Init(); // initialize i2c lcd
	sei();//interrupts enable
	
	clear();
	setCursor(1,0);
	for(i=0; i<18; i++){		
		writeI2cDisplay(openingText[i], DATAREGISTER);
	}
	setCursor(1,2);
	for(i=0; i<5; i++){
		writeI2cDisplay(clockText[i], DATAREGISTER);
	}
	setCursor(10,2);
	writeI2cDisplay(':', DATAREGISTER);
	
	
	pinMode(PIN_LED1,PINMODEOUTPUT); //set pin to output
	pinMode(PIN_LED2,PINMODEOUTPUT); //set pin to output
	pinMode(PIN_LED3,PINMODEOUTPUT); //set pin to output	
}

 

application1.c

/*
 * operational.c
 *
 * Created: 20-5-2017 15:07:22
 * Author: HdH
 * Description: Application for multi threat, show lcd clock
 */ 

#include <avr/io.h>
#include "i2c.h"
#include "i2cdisplay.h"

void showTime(); // showing the time

/****************************************************************************
	Public vars
****************************************************************************/

extern int alarmCode;
extern int timeSecApp1;
extern int timeMinApp1;
int oldTime;

void Application1(void){

	showTime();
	
}

void showTime(){
	int seconds;
	int minutes;
	
	
	seconds = timeSecApp1;
	minutes = timeMinApp1;
	
	if (oldTime == timeSecApp1){
		return;
	}
	if (seconds >= 10){
		setCursor(11,2);
		writeI2cDisplay(seconds/10+48, DATAREGISTER);
		setCursor(12,2);
		writeI2cDisplay(seconds%10+48, DATAREGISTER);
		if (minutes>=10){
			setCursor(8,2);
			writeI2cDisplay(minutes/10+48, DATAREGISTER);
			setCursor(9,2);
			writeI2cDisplay(minutes%10+48, DATAREGISTER);
		}
		if (minutes<10){
			setCursor(8,2);
			writeI2cDisplay('0', DATAREGISTER);
			setCursor(9,2);
			writeI2cDisplay(minutes+48, DATAREGISTER);
		}
	}
	else if (seconds < 10){ setCursor(12,2); writeI2cDisplay(seconds+48, DATAREGISTER); setCursor(11,2); writeI2cDisplay('0', DATAREGISTER); if (minutes>=10){
			setCursor(8,2);
			writeI2cDisplay(minutes/10+48, DATAREGISTER);
			setCursor(9,2);
			writeI2cDisplay(minutes%10+48, DATAREGISTER);
		}
		if (minutes<10){
			setCursor(8,2);
			writeI2cDisplay('0', DATAREGISTER);
			setCursor(9,2);
			writeI2cDisplay(minutes+48, DATAREGISTER);
		}
	}
	oldTime = timeSecApp1;
}


 

display_main.c

/*
* main.c
*
* Created: 20-5-2017
* Author: HdH
* Description: main, initializing en starting the app
* Global functions:
*
*
*/

#define WATCHDOG_ALARM 2
#define NO_ALARM 0

#include <avr/io.h>
#include "i2cdisplay.h"

void errorHandle(int receivedAlarm);
int operational(void);
void initialize(void);

/****************************************************************************
	Public vars
****************************************************************************/

int errorCode = NO_ALARM; //
char alarmText[] = "Alarm in app";

int main(void)
{
	initialize(); //start only once (setup with Arduino)
	
	errorCode = operational(); //the main loop
	if (errorCode == WATCHDOG_ALARM){ // you only end up here if there is an alarm.
		errorHandle(errorCode);
	}
	else{
		errorHandle(errorCode); //one of the apps is returning the error
	}
}

void errorHandle(int receivedAlarm){
	
	int i;
	
	while(1){ //blink some leds or show an error message on a display..
		
		clear();
		setCursor(1,0);
		for(i=0; i<12; i++){
			writeI2cDisplay(alarmText[i], DATAREGISTER);
		}
	}
}

 

i2cdiplay.h

/*
 * i2cdisplay.h
 *
 * Created: 30-11-2017 19:18:52
 *  Author: acer
 */ 


#ifndef I2CDISPLAY_H_
#define I2CDISPLAY_H_

/****************************************************************************
	Bit and byte definitions
****************************************************************************/

// Display function settings can only be changed once
#define LCD_I2C_ADDR 0x3f // i2c address of the lcd
#define LCD_FUNCTIONSET 0x20
#define LCD_BITMODE 0x00 //4bit is 0x0, 8bit is 0x10
#define LCD_LINE 0x08 // 2 line is 0x08, 1 line is 0x00
#define LCD_DOTS 0x00 // 5x8 dots is 0x00, 5x10 dots is 0x04
#define LCD_ROWS 4 // Number of LCD rows
#define LCD_COLUMNS 20 // Number of LCD columns
#define DATAREGISTER 1
#define INSTRUCTIONREGISTER 0
#define ENABLE 4
#define READ_BF 2

// Display control settings
#define LCD_DISPLAYCONTROL 0x08 // Use this cmd combined with the below optional settings
#define LCD_DISPLAYON 0x04
#define LCD_DISPLAYOFF 0x00
#define LCD_CURSORON 0x02
#define LCD_CURSOROFF 0x00
#define LCD_BLINKON 0x01
#define LCD_BLINKOFF 0x00
#define LCD_BACKLIGHT 0x08
#define LCD_NOBACKLIGHT 0x00

// Display entry mode settings
#define LCD_ENTRYMODESET 0x04 // Use this cmd combined with the below optional settings. 
#define LCD_ENTRYRIGHT 0x00
#define LCD_ENTRYLEFT 0x02
#define LCD_ENTRYSHIFTINCREMENT 0x01
#define LCD_ENTRYSHIFTDECREMENT 0x00

// Send below commands to the Instruction register IR.
#define LCD_CLEARDISPLAY 0x01
#define LCD_RETURNHOME 0x02
#define LCD_SETDDRAMADDR 0x80 // Used to point to a position on the display
#define LCD_CURSORSHIFT 0x10
#define LCD_DISPLAYMOVE 0x08
#define LCD_MOVERIGHT 0x04
#define LCD_MOVELEFT 0x00



/****************************************************************************
	Function definitions
****************************************************************************/

void i2cLcd_Init();
void writeI2cDisplay(uint8_t, uint8_t);
uint8_t checkBusyFlag(); // not used yet

void clear();
void home();
void setCursor(uint8_t, uint8_t);
void noDisplay();
void display();
void noCursor();
void cursor();
void noBlink();
void blink();
void scrollDisplayLeft();
void scrollDisplayRight();
void leftToRight();
void rightToLeft();
void autoscroll();
void noAutoscroll();

#endif /* I2CDISPLAY_H_ */

 

i2cdisplay.c

/*
* i2cDisplay.c
*
* Created: 14-6-2017
* Author: HdH
* Description: functions to control the I2C Dsiplay
* Global functions:
*
*/

#define F_CPU 16000000UL // 16 MHz clock speed

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


/****************************************************************************
	Public vars
****************************************************************************/

uint8_t displayMainSettings;
uint8_t displayControl;
uint8_t displayEntryMode;
uint8_t bfPin7;


void i2cLcd_Init(){
	
	_delay_ms(50); // to get the display powered up (pg. 46 datasheet)
	
	i2c_start(); // below is needed to change the lcd from 8 bit to 4 bit
	i2c_write((LCD_I2C_ADDR<<1)|0);
	i2c_write(LCD_FUNCTIONSET);
	i2c_stop();
	i2c_start();
	i2c_write((LCD_I2C_ADDR<<1)|0); i2c_write(LCD_FUNCTIONSET | ENABLE); i2c_stop(); _delay_us(1); // enable pulse must be >450ns
	i2c_start();
	i2c_write((LCD_I2C_ADDR<<1)|0);
	i2c_write(LCD_FUNCTIONSET & ~ENABLE);
	i2c_stop();
	_delay_ms(10); // pg. 46, instead of sending the functionseet 3 times
	
	displayMainSettings = LCD_FUNCTIONSET | LCD_BITMODE | LCD_LINE | LCD_DOTS;
	writeI2cDisplay(displayMainSettings, INSTRUCTIONREGISTER); // these display settings can't be changed after this.
		
	_delay_us(100); // pg. 46
	displayControl = LCD_DISPLAYON | LCD_CURSOROFF | LCD_BLINKOFF;
	writeI2cDisplay(displayControl | LCD_DISPLAYCONTROL, INSTRUCTIONREGISTER);
	
	displayEntryMode = LCD_ENTRYLEFT | LCD_ENTRYSHIFTDECREMENT;
	writeI2cDisplay(displayEntryMode | LCD_ENTRYMODESET, INSTRUCTIONREGISTER);
	home();
}

void writeI2cDisplay(uint8_t value, uint8_t registerselect){
	
	uint8_t highnib=value&0xf0;
	uint8_t lownib=(value<<4)&0xf0;
	
	// first byte send, set/prepare lcd pins
	// second byte send, same and turn enable on
	// third byte send, same and turn enable off
	i2c_start();
	i2c_write((LCD_I2C_ADDR<<1)|0);
	i2c_write(highnib | LCD_BACKLIGHT | registerselect);
	i2c_stop();
	i2c_start();
	i2c_write((LCD_I2C_ADDR<<1)|0); i2c_write(highnib | LCD_BACKLIGHT | registerselect | ENABLE); i2c_stop(); _delay_us(1); // enable pulse must be >450ns
	i2c_start();
	i2c_write((LCD_I2C_ADDR<<1)|0); i2c_write((highnib | LCD_BACKLIGHT | registerselect ) & ~ENABLE); i2c_stop(); _delay_us(50); // commands need > 37us to settle
	
	// same as above but now for last 4 bits
	i2c_start();
	i2c_write((LCD_I2C_ADDR<<1)|0);
	i2c_write(lownib | LCD_BACKLIGHT | registerselect);
	i2c_stop();
	i2c_start();
	i2c_write((LCD_I2C_ADDR<<1)|0); i2c_write(lownib | LCD_BACKLIGHT | registerselect | ENABLE); i2c_stop(); _delay_us(1); // enable pulse must be >450ns
	i2c_start();
	i2c_write((LCD_I2C_ADDR<<1)|0); i2c_write((lownib | LCD_BACKLIGHT | registerselect ) & ~ENABLE); i2c_stop(); _delay_us(50); // commands need > 37us to settle
	
}

uint8_t checkBusyFlag(){
	
	// Will be updated later
	return 0;
}

void clear(){
	writeI2cDisplay(LCD_CLEARDISPLAY, INSTRUCTIONREGISTER);// clear display
	_delay_us(2000);  // this command takes a long time!
}

void home(){
	writeI2cDisplay(LCD_RETURNHOME, INSTRUCTIONREGISTER);  // set cursor position to zero
	_delay_ms(2);  // this command takes a long time!
}

void setCursor(uint8_t col, uint8_t row){
	int row_offsets[] = { 0x00, 0x40, 0x14, 0x54 };
	if ( row > LCD_ROWS ) {
		row = LCD_ROWS-1;    // we count rows starting w/0
	}
	writeI2cDisplay(LCD_SETDDRAMADDR | (col + row_offsets[row]), INSTRUCTIONREGISTER);
}

void noDisplay() {
	displayControl &= ~LCD_DISPLAYON;
	writeI2cDisplay(LCD_DISPLAYCONTROL | displayControl, INSTRUCTIONREGISTER);
}

void display() {
	displayControl |= LCD_DISPLAYON;
	writeI2cDisplay(LCD_DISPLAYCONTROL | displayControl, INSTRUCTIONREGISTER);
}

void noCursor() {
	displayControl &= ~LCD_CURSORON;
	writeI2cDisplay(LCD_DISPLAYCONTROL | displayControl, INSTRUCTIONREGISTER);
}

void cursor() {
	displayControl |= LCD_CURSORON;
	writeI2cDisplay(LCD_DISPLAYCONTROL | displayControl, INSTRUCTIONREGISTER);
}

void noBlink() {
	displayControl &= ~LCD_BLINKON;
	writeI2cDisplay(LCD_DISPLAYCONTROL | displayControl, INSTRUCTIONREGISTER);
}

void blink() {
	displayControl |= LCD_BLINKON;
	writeI2cDisplay(LCD_DISPLAYCONTROL | displayControl, INSTRUCTIONREGISTER);
}

void scrollDisplayLeft(void) {
	writeI2cDisplay(LCD_CURSORSHIFT | LCD_DISPLAYMOVE | LCD_MOVELEFT, INSTRUCTIONREGISTER);
}

void scrollDisplayRight(void) {
	writeI2cDisplay(LCD_CURSORSHIFT | LCD_DISPLAYMOVE | LCD_MOVERIGHT, INSTRUCTIONREGISTER);
}

void leftToRight(void) {
	displayEntryMode |= LCD_ENTRYLEFT;
	writeI2cDisplay(LCD_ENTRYMODESET | displayEntryMode, INSTRUCTIONREGISTER);
}

void rightToLeft(void) {
	displayEntryMode &= ~LCD_ENTRYLEFT;
	writeI2cDisplay(LCD_ENTRYMODESET | displayEntryMode, INSTRUCTIONREGISTER);
}

void autoscroll(void) {
	displayEntryMode |= LCD_ENTRYSHIFTINCREMENT;
	writeI2cDisplay(LCD_ENTRYMODESET | displayEntryMode, INSTRUCTIONREGISTER);
}

void noAutoscroll(void) {
	displayEntryMode &= ~LCD_ENTRYSHIFTINCREMENT;
	writeI2cDisplay(LCD_ENTRYMODESET | displayEntryMode, INSTRUCTIONREGISTER);
}

 

The lcd comes in very handy as a debug tool. You can write everything to the display that might be important for trouble shooting. Besides that, there are many more reasons to use an lcd in your projects. I think my next blog will be an ADC library for the analog ports and I will write this on a display to understand the working of the analog pins.

I2C with the MCP23016 16 bit expander

In this test project I used the MCP23016 for building and testing an i2c library. The MCP23016 is controllable by the i2c protocol. It contains 16 digital input/output pins. This can be usefull if your microcontroller has not enough free digital pins left. Some i2c displays also use a simular IC to control the display. For easier testing I used this IC as a preparation to get a display working in a later blog. The complete source code can be downloaded here.

Test setup

The i2c protocol

The i2c protocol is meant for getting communication between microcontrollers, ic’s, sensors and actuators. The following things will summarize it a bit:

  • The distance you can use the protocol is only a few meters.
  • The communication speed goes up to 400KHz, but for testing I used a lower speed, around 20KHz.
  • It’s posible to connect up to 128 devices parallel. Each device got his own address and can be changed by connecting some pins to gnd or Vcc.
  • Only 1 device at a time can control the bus. The one who controls the bus is called the master and the other devices are the slaves. So in this case the Atmega328p will act as the master and the MCP23016 acts as a slave.
  • I2c only uses 2 wires. CLK is the clock and the data will be send over the SDA line. When the clock is high (5V) the SDA line can be read.

 

To send a message to a device a START condition have to be send first. The device that sends the START condition will remain the master untill it sends a STOP condition.

As you can see  a start condition is created by reading when the clock is high and the SDA line will go from high to low. A stop condition is simular only now the SDA line goes from low to high. Remember that the bits from a message send will also be read when the clock is high, but in that case the SDA line should be stable 0V or 5V. The good thing about this theory is that the microcontroller will take care of the voltage levels with respect to time, we only have to tell the microcontroller when to send a start or stop condition.

After sending a start condition we have to send the addres of the device we want to communcicate with. In case of the MCP23016 the default address is 0x20h. After this a message can be send or received. Therefor I wrote the following functions.

i2c_init(), Enables the Atmega328p to become i2c instead of the default ADC.
i2c_start(), Send the start condition
i2c_write(), send a byte/character
i2c_read(), Read a byte/character
i2c_stop(), send a stop condition.

 

The MCP23016

There are a few settings that can be set in the MCP23016. For example the pins you want to be inputs or outputs. That’s the first thing during initialize() that happens in the code.

The list with settings for the MCP23016.

 

The basics of controlling the MCP23016 are easy. The first byte send is a command. For example the byte 0x06h. This will select the IODIR0 register. This register controls how the first 8 pins should work (input or output). The next byte send will tell which of the 8 pins should be input or output. A ‘0’ is an output. That means sending 00000000b (0x0h) will turn all the pins to outputs. This only have to be done once and therefor it’s done in the initialize() function.

After setting the pins to become outputs we can use them in the application.c by sending the command byte 0x0h to select the first 8 pins. The next byte send will turn them on or off. For example if you want to have the first and last pins to become 5V and the other pins should be 0V send the following bits 10000001b ( in hex that’s 0x81h). In application1.c all the pins will be set high for 2 seconds and then low for 2 seconds just for testing.

Keep in mind I didn’t do any i2c error checking at all to keep the code clear for learning and easy testing. To get the code working I used a logic analyzer (link antatek) a lot before I finally managed to get it working. That doesn’t mean that this code doesn’t work! Instead it works fine, but could have a bit more fault checking. See some screendumps below to get an easier understanding of the bytes that have to be send to the MCP23016.

Screendump from the initialization of the MCP23016

 

Screendump when i2c controls the blinking.

 

Connecting the Atmega to the MCP23016

The schematic is straight forward. Keep in mind that the 4k7 pull-up resistors are very important. Without them things seems to work, but after a while the Atmega328p receives to many NACK’s from the MCP23016 and will freeze (at least in my case…).

Schematic of the project.

 

The source code

I used the same framework as in my last blog’s. The files that are changed are initialize.c and application.c. Besides that I kept application2.c and application3.c the same as in the “Multi thread 3 leds” blog.

initialize.c

/*
 * projectInit.c
 *
 * Created: 20-5-2017 15:07:22
 * Author: HdH
 * Description: initialization off the project
 */ 

#define PIN_LED1 6
#define PIN_LED2 7
#define PIN_LED3 8

#include <avr/interrupt.h>
#include "gpiolib.h"
#include "timer4clocklib.h"
#include "i2c.h"


void initialize(){

	cli();//interrupts disable
	tmr2_init(); // initialize timer2
	i2c_init(); // initialize i2c protocol
	i2c_start(); // send a i2c startbit
	i2c_write((0x20<<1)|0); //send address of the MCP230
	i2c_write(0x06); //send cmd to select IODIR0
	i2c_write(0x00); //set IODIR0 to 0 to make it all outputs
	i2c_write(0x00); //set IODIR1 to 0 to make it all outputs
	i2c_stop();
	sei();//interrupts enable
	
	pinMode(PIN_LED1,PINMODEOUTPUT); //set pin to output
	pinMode(PIN_LED2,PINMODEOUTPUT); //set pin to output
	pinMode(PIN_LED3,PINMODEOUTPUT); //set pin to output	
}

application.c

/*
 * operational.c
 *
 * Created: 20-5-2017 15:07:22
 * Author: HdH
 * Description: Application for multi threat
 */ 

#define PIN_LED1 6
#define DELAYMILIS 2000 // Led blinking wait time in milliseconds
#define TIMER_IS_NOT_SET 0
#define TIMER_IS_SET 1

#include <avr/io.h>
#include "gpiolib.h"
#include "timer4clocklib.h"
#include "i2c.h"

void toggleLed1();

extern int alarmCode;
int previsiousvalue = 0;
int timerSet = TIMER_IS_NOT_SET;
unsigned long int endTime = 0;

void Application1(void){
	
	if(timerSet == TIMER_IS_NOT_SET){
		endTime = msCounter + DELAYMILIS;
		timerSet = TIMER_IS_SET;
		toggleLed1();
	}
	if(endTime < msCounter){ // Timer is expired
		timerSet = TIMER_IS_NOT_SET; // reset timer
		return;
	}
}

void toggleLed1(){
	if (previsiousvalue == 0){
		i2c_start(); // send i2c start bit
		i2c_write((0x20<<1)|0); // send address of the MCP23016
		i2c_write(0x00); // send cmd to access GP0
		i2c_write(0xFF); // set all the pins of GP0 high
		i2c_write(0xFF); // set all the pins of GP1 high
		i2c_stop();
		previsiousvalue = 1;
	}
	else{
		i2c_start(); // send i2c start bit
		i2c_write((0x20<<1)|0); // send address of the MCP23016
		i2c_write(0x00); // send cmd to access GP0
		i2c_write(0x00); // set all the pins of GP0 low
		i2c_write(0x00); // set all the pins of GP1 low
		i2c_stop();
		previsiousvalue = 0;
	}
}


The i2c library only contains the necessary things needed for this moment. That means no fault checkings, or resends of i2c yet.

I2c.h

/*
* I2C.h
*
* Created: 27-5-2017
* Author: HdH
* Description: functions to control the I2C pins
* Global functions:
*
*/


#ifndef I2C_H_
#define I2C_H_

void i2c_init(); // Change ADC pin 4 and 5 to i2c
void i2c_start(); // Send a start condition
void i2c_write(char x); // Send a byte
void i2c_stop(); // Send a stop condition



#endif /* I2C_H_ */

I2c.c

/*
* I2C.c
*
* Created: 27-5-2017
* Author: HdH
* Description: functions to control the I2C pins
* Global functions:
* 
*/
#include <avr/io.h>
#include "i2c.h"

void i2c_init()
{
	TWBR = 0x08;        // SCL freq = CPU clock / (16+2*TWBR*prescaler), 16+2(TWBR)*1 = 16MHz / 100KHz. TWBR=72=48HEX
	TWCR = (1<<TWEN);   // Enable I2C, changes the pin's from ADC to i2c pin's
	TWSR = 0x00;        // Prescaler set to 1
}

 
void i2c_start()
{
    TWCR = (1<<TWINT) | (1<<TWEN) | (1<<TWSTA); // Start Condition 
    while (!(TWCR & (1<<TWINT))); // Check for Start condition successful
}
 
 
void i2c_write(char x)
{
    TWDR = x; // Move value to I2C register
    TWCR = (1<<TWINT) | (1<<TWEN); // Enable I2C and Clear Interrupt
    while (!(TWCR & (1<<TWINT))); // Write Successful with TWDR Empty
	
}
 

char i2c_read()
{
    TWCR = (1<<TWEN) | (1<<TWINT); // Enable I2C and Clear Interrupt
    while (!(TWCR & (1<<TWINT))); // Read successful with all data received in TWDR
    return TWDR;
} 


void i2c_stop()
{
	TWCR = (1<<TWINT) | (1<<TWEN) | (1<<TWSTO); // Stop Condition 
}


In the next blog I will use the i2c library to communicate with a display. So finaly something more visual and more interesting!

 

 

Multi thread 3 leds

In my last blog I created a default framework to get a multi threat solution . This time I created a test project to get 3 leds blinking simultanesly on different speeds. Every led gets it’s own thread. This time I didn’t use a delay function, because it freezes the microcontroller. I created a timer library that provides milliseconds, seconds and minutes. Download all the files here.

The multi thread example

 

The timer library files

The files timer4clocklib.h and timer4clocklib.c are the only 2 new files added to the library. The files containing only 2 functions. Function tmr2_init() enables the timer2. The atmega328p contains 3 different timers and they are all a little different. The only reason I used timer2 is because it’s not used by Arduino. I thought it would be handy in the future in case I needed some arduino code to test.

The working of the timer

How does the Atmega328p timer work? Timer2 is an 8 bit timer. That means it will count from 0 till 255 (8 bits) and after reaching 255 it will trigger a function. This is function is called the interrupt service routine (ISR). The name for this function is already defined by AS6 and should have the same name and that is ISR(TIMER2_COMPA_vect){}. For counting millisseconds I would like an interrupt every 1ms, because then I can increment every intterupt with 1 to my msCounter variable and then i can use this variable to read out the milliseconds that had passed since I started the microcontroller. To get the seconds I wait until msCounter becomes 1000 (1000 milliseconds is 1 second). Every 1000 milliseconds a variable, calleds seconds, increments with 1 untill its reaches 60 seconds. After that the minutes variable will increment with 1 too. The same principle counts for the hours as well. The msCounter, seconds and minutes are global variables so you can read out the time passed everwhere you want into the project.

Initializing the timer

To get the ISR starting every 1 millisecond a calculation have to be made to let the microcontroller know when a millisecond has passed. The formula is CMR = t * (clockfreq/prescaler+1). The CMR is a value that should be given in the OCR2A register during initialization. ‘t’ is the time and that’s 1ms in this project. The clockfrequentie is the frequency of the clock you use for the Atmega328p. I used a 16MHz Crystal. The higher number you choose for the prescaler the shorter you can make the intterupt time. I used 1024. The prescaler value is set in the TCCR2B register.

CMR = 1ms * (16MHz/1024-1) = ca. 15,6.

timer4clocklib.h

/*
 * timer4clocklib.h
 *
 * Created: 20-5-2017
 * Author: HdH
 * Description: This lib uses Timer2 to create ms, sec and minutes to use in your apps
 */ 

#ifndef TIMER4CLOCKLIB_H
#define TIMER4CLOCKLIB_H

/****************************************************************************
	Bit and byte definitions
****************************************************************************/

#define CMR_SECOND 100 //count till 100. (100*10ms=1sec)
#define CMR_MINUT 6001 //count till 6001 to get till 1 minute
#define CMR_MSCOUNTER 5184000000 //max 24 hours and then this milliseconds counter will reset back to 0

extern unsigned long int msCounter;//counts 24 hours used for timings in the apps
extern unsigned long int mseconden;//counts 24 hours used for timings in the apps
extern int seconds; // provides seconds
extern int minutes; // provides minutes

//timer2 is 8bits and after all bits are count the interrupt follow.
//CMR = t * (clockfreq/prescaler+1)
//CMR = 1ms * (16MHz/1024-1) = ca. 15,6. 
//CMR decides after which time the interrupt follow. so in this case 1ms

/****************************************************************************
	Function definitions
****************************************************************************/

void tmr2_init(void); //and the ISR

#endif

timer4clocklib.c

/*
 * timer2clockLib.h
 *
 * Created: 20-5-2017
 * Author: HdH
 * Description: This lib uses Timer2 create ms, sec and minutes to use in your app
 */ 

#include <avr/io.h>
#include <avr/interrupt.h>
#include "timer4clocklib.h"

unsigned long int _secondCounter = 0;
unsigned long int _minutCounter = 0;
int i = 0;

unsigned long int msCounter = 0;// counts 24 hours used for timings in the apps
int seconds = 0; // provides seconds
int minutes = 0; // provides minutes


/****************************************************************************
	tmr2_init, Timer0 and Timer2 are 8 bit. Timer0 is used for milis() and delay() in arduino. I use Timer2 
****************************************************************************/

void tmr2_init(void){
  TCCR2A = 0; // TCCR0A reset to 0
  TCCR2B = 0; // TCCR0B reset to 0
  TCNT2 = 0; // reset the counter to 0
  OCR2A = 16; // CMR = 15,6 about 16 for 1ms
  TCCR2A |= (1<<WGM21); // CTC mode
  TCCR2B |= ((1<<CS22) | (1<<CS21) | (1<<CS20)); // prescaler = 1024
  TIMSK2 |= (1<<OCIE2A); // activate interrupt timer 2
}

/****************************************************************************
	ISR, Interrupts every ms
****************************************************************************/

ISR(TIMER2_COMPA_vect){ // timer2 interrupt every 1 ms
    
  msCounter += 1;
  i += 1;
  if (i==10){
    _secondCounter += 1; // 1 count every 10 interrupts
    _minutCounter += 1; // 1 count every 10 interrupts
    i=0;
  }
  if (msCounter == CMR_MSCOUNTER){ // Counts ms for 24 hours
    msCounter = 0;
  }
  if (_secondCounter == CMR_SECOND){
    _secondCounter = 0;
    seconds = seconds+1;
	seconds = seconds % 60;
  }  
  if (_minutCounter == CMR_MINUT){
    _minutCounter = 0;
    minutes = (minutes+1)%60;
  }
}

The timer library is ready to be used and now I can remove the delay() functions and use the timer instead. The first function that needs to be adjusted is the initialize() function. Include the AS6 interrupt library and the timer4clocklib library. The function mr2_init() will activate the timer.

Initialize.c

/*
 * projectInit.c
 *
 * Created: 20-5-2017 15:07:22
 * Author: HdH
 * Description: initialization off the project
 */ 

#define PIN_LED1 6
#define PIN_LED2 7
#define PIN_LED3 8

#include <avr/interrupt.h>
#include "gpiolib.h"
#include "timer4clocklib.h"


void initialize(){

	cli();//interupts disable
	tmr2_init();
	sei();//interupts enable
	pinMode(PIN_LED1,PINMODEOUTPUT); //set pin to output
	pinMode(PIN_LED2,PINMODEOUTPUT); //set pin to output
	pinMode(PIN_LED3,PINMODEOUTPUT); //set pin to output	
}

For this test project I used 3 seperate applications. For each led an application to simulate 3 separate threats. Later on I can use 1 application for showing information on a display. An application for listening to the USART or I2C messages. Or an application to read incomming button presses etc. In this case all applications are using a function to toggle a led between on and off. The timer is used for the time between blinking on and off. First the application checks if the timer is already activated. If it is, nothing happens untill the timer expired and then the led will toggle. By checking the time every loop and not delaying the microcontroller all the applications will keep running simultaneously.

Application1.c

/*
 * operational.c
 *
 * Created: 20-5-2017 15:07:22
 * Author: HdH
 * Description: Application for multi threat
 */ 

#define PIN_LED1 6
#define DELAYMILIS 50 // Led blinking wait time in milliseconds
#define TIMER_IS_NOT_SET 0
#define TIMER_IS_SET 1

#include <avr/io.h>
#include "gpiolib.h"
#include "timer4clocklib.h"

void toggleLed1();

extern int alarmCode;
int previsiousvalue = 0;
int timerSet = TIMER_IS_NOT_SET;
unsigned long int endTime = 0;

void Application1(void){
	
	if(timerSet == TIMER_IS_NOT_SET){
		endTime = msCounter + DELAYMILIS;
		timerSet = TIMER_IS_SET;
		toggleLed1();
	}
	if(endTime < msCounter){ // Timer is expired
		timerSet = TIMER_IS_NOT_SET; // reset timer
		return;
	}
}

void toggleLed1(){
	if (previsiousvalue == 0){
		WritePin(PIN_LED1,HIGH);
		previsiousvalue = 1;
	}
	else{
		WritePin(PIN_LED1,LOW);
		previsiousvalue = 0;
	}
}

 

Application2.c

/*
 * inBedrijf.c
 *
 * Created: 20-5-2017 15:07:22
 * Author: HdH
 * Description: Application for multi threat
 */ 

#define PIN_LED2 7
#define DELAYMILIS 500 // Led blinking wait time in milliseconds
#define TIMER_IS_NOT_SET 0
#define TIMER_IS_SET 1

#include <avr/io.h>
#include "gpiolib.h"
#include "timer4clocklib.h"

void toggleLed2();

extern int alarm;
int previsiousvalueL2 = 0;
int timerSetL2 = TIMER_IS_NOT_SET;
unsigned long int endTimeL2 = 0;

void Application2(){
	
	if(timerSetL2 == TIMER_IS_NOT_SET){
		endTimeL2 = msCounter + DELAYMILIS;
		timerSetL2 = TIMER_IS_SET;
		toggleLed2();
	}
	if(endTimeL2 < msCounter){ // Timer is expired
		timerSetL2 = TIMER_IS_NOT_SET; // reset timer
		return;
	}
}

void toggleLed2(){
	if (previsiousvalueL2 == 0){
		WritePin(PIN_LED2,HIGH);
		previsiousvalueL2 = 1;
	}
	else{
		WritePin(PIN_LED2,LOW);
		previsiousvalueL2 = 0;
	}
}

 

Application3.c

/*
 * inBedrijf.c
 *
 * Created: 20-5-2017 15:07:22
 * Author: HdH
 * Description: Application for multi threat
 */ 

#define PIN_LED3 8
#define DELAYMILIS 2000 // Led blinking wait time in milliseconds
#define TIMER_IS_NOT_SET 0
#define TIMER_IS_SET 1

#include <avr/io.h>
#include "gpiolib.h"
#include "timer4clocklib.h"

void toggleLed3();

extern int alarm;
int previsiousvalueL3 = 0;
int timerSetL3 = TIMER_IS_NOT_SET;
unsigned long int endTimeL3 = 0;

void Application3(){
	if(timerSetL3 == TIMER_IS_NOT_SET){
		endTimeL3 = msCounter + DELAYMILIS;
		timerSetL3 = TIMER_IS_SET;
		toggleLed3();
	}
	if(endTimeL3 < msCounter){ // Timer is expired
		timerSetL3 = TIMER_IS_NOT_SET; // reset timer
		return;
	}
}

void toggleLed3(){
	if (previsiousvalueL3 == 0){
		WritePin(PIN_LED3,HIGH);
		previsiousvalueL3 = 1;
	}
	else{
		WritePin(PIN_LED3,LOW);
		previsiousvalueL3 = 0;
	}
}

The framework is working fine so now it’s time to use the framework do something more then just blinking leds. Stay tuned to next blog!

 

 

 

 

 

 

Blinking led with Atmel studio

The Arduino IDE is great, but with Atmel studio you can do more including easier debugging. Besides that, it’s the original IDE from AVR, the maker of the Atmega328p microcontroller (used by Arduino). With Atmel studio it is possible to program a wide range of AVR microcontrollers which will be usefull in the future. I tried to use the latest version 7 from Atmel, but i run into a lot of blue screens. I had to turn off my virusscanner to avoid blue screens so i decided to keep using the older version 6.2 (AS6) You can download it from http://www.atmel.com/tools/studioarchive.aspx

The AVR Dragon board

 

The cheapest board with some debugging options I could find is the AVR Dragon board (http://www.atmel.com/tools/avrdragon.aspx ). It is possible to use an Arduino board together with Atmel studio, but with this board you can also program different microcontrollers.

Connect the Atmega328p to the AVR Dragon board

Before you use the original Atmega328p microcontroller from your Arduino keep in mind that you will loose the bootloader inside the microcontroller. That means you can’t use the Atmega328p on your Arduino anymore ! So just buy a seperate microcontroller and keep your original Arduino working. You can directly put the Atmega328p on the Dragon board, but if you use an external bread board you can easily put more components on that board and do much more testing.

Build on a bread board

 

Connect the Dragon to the Atmega328p following this wiring. HV_PROG is the Dragon and DEVICE the Atmega328p.

 

After connecting everything you should end up with something like this.

 

 

For a quick reference see the Atmega328p pinout here. I used digital pin9 for the blinking led and that is the physical pin number 15.

 

Test the connectivity

After installling the software and AVR dragon USB drivers , start the AS6 software. First check if AS6 had found the AVR Dragon and the Atmega328p. Go in the top menu and click Tools -> Device programming. Select in tools your AVR Dragon. Select ISP for the interface and and press apply. When the connections are done correctly you should be able read out the Dragon. Click on read at “device signature” when you get some info the Atmega328p is also connected correctly.

If you receive something like these parameters everything should be connected correctly and ready for compiling.

The multi thread source code in a frame work

Why a framework? Thats to make your life easier in the future. By using a framework you will have always the basic setup for a design. For every new project you load the same framework and add your new project source code. This will safe time and bring some standardization into your projects. The main reason I used a framework is to be able to use multi threats in a clear way. There are often many things your microcontroller have to do at the same time and to keep track of your code this framework might help.

The framework

 

This framework is downloadable here. It contains of a few files linked together. main.c is the file that will start at first when you startup the Atmega328p. With Arduino you will have a setup part which is only executed 1 time during start up. For the setup procedures I use a seperate file called initialize.c and it does the same as setup() for Arduino. The first thing main.c does is starting the initialize() only once.

Try not to put your new project code in main.c like you do with Arduino. We use the file application1.c to put in the code that should run in a loop. By using an application file for each routine you can keep track of your source code and it will be easy in the future to troubleshoot. The only thing main.c does is starting the initialize.c (setup in Arduino) once during boot and then it will start the operational() function which is located in the file operational.c. This function runs all the application files you want to use after each other in a loop. The file also returns an alarm/error code if something goes wrong in any of the applications. If there is an alarm/error the framework will stop running all the applications and you can use the errorHandle() function in main.c to blink a led or show an error code on a display. Everything will become more clear when you start using it.

main.c

/*
* main.c
*
* Created: 20-5-2017
* Author: HdH
* Description: main, initializing en starting the app
* Global functions:
* 
*
*/

#define WATCHDOG_ALARM 2
#define NO_ALARM 0

#include <avr/io.h>

void errorHandle(int receivedAlarm);
int operational(void);
void initialize(void);

int errorCode = NO_ALARM; //

int main(void)
{
	initialize(); //start only once (setup with Arduino)
	
    errorCode = operational(); //the main loop
	if (errorCode == WATCHDOG_ALARM){ // you only end up here if there is an alarm.
		errorHandle(errorCode);
	}
	else{ 
		errorHandle(errorCode); //one of the apps is returning the error
	}	
}

void errorHandle(int receivedAlarm){
	
	while(1){ //blink some leds or show an error message on a display..
	}
}

 

initialize.c

/*
 * projectInit.c
 *
 * Created: 20-5-2017 15:07:22
 * Author: HdH
 * Description: initialization off the project
 */ 

#define PINNUMBER 9

#include "gpiolib.h"


void initialize(){


	pinMode(PINNUMBER,PINMODEOUTPUT); //set pin to output	
}

 

operational.c

/*
* inBedrijf.c
*
* Created: 20-5-2017 15:07:22
* Author: HdH
* Description: Main loop going trough all the apps
*/

#define NO_ALARM 0

#include <avr/io.h>

void Application1(void);
void Application2(void);
void Application3(void);
void Application4(void);

extern int errorCode;

int operational(void){
	
	int applicationCounter = 0;
	
	do
	{
		applicationCounter = (applicationCounter +1) % 5;
		
		if (applicationCounter == 1)
		{
			Application1();
		}
		if (applicationCounter == 2)
		{
			Application2();
		}
		if (applicationCounter == 3)
		{
			Application3();
		}
		if (applicationCounter == 4)
		{
			Application4();
		}
		
	} while (errorCode == NO_ALARM);
	
	return errorCode;
}



 

Application1.c

/*
 * operational.c
 *
 * Created: 20-5-2017 15:07:22
 * Author: HdH
 * Description: Application for multi threat
 */ 

#define F_CPU 16000000UL // 16 MHz clock speed needed for delay.h
#define PINNUMBER 9
#define DELAYMILIS 2000

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

extern int alarmCode;

void Application1(void){
	
	WritePin(PINNUMBER,HIGH);
	_delay_ms(DELAYMILIS);
	WritePin(PINNUMBER,LOW);
	_delay_ms(DELAYMILIS);
}

The only new code in the framework (the rest is the default framework) is setting up the pin as output. This line of code is added in initialize.c

 pinMode(PINNUMBER,PINMODEOUTPUT); //set pin to output 

And in apllication1.c the following lines to get the led blinking.

WritePin(PINNUMBER,HIGH);
_delay_ms(DELAYMILIS);
WritePin(PINNUMBER,LOW);
_delay_ms(DELAYMILIS);

 

The GPIO library

What id didn’t mentioned yet are the library files gpiolib.c and gpiolib.h. This library supports the functions pinMode() and WritePin(). Arduino already comes with simular libraries, but for AS6 we have to writer them ourselves. It is possible to import the Arduino libraries, but for learning purposes you will get a better understanding what the libraries actually are doing. For your AS6 to locate these files you have to add the directory where you put the files at the following places. For the compiler to find these files I added the directory at properties -> toolchain -> Directorys and General.

See here the source code for the 2 library files or download all the files at once.

gpiolib.h

/*
* gpgiolib.h
*
* Created: 20-5-2017
* Author: HdH
* Description: functions to control the GPIO
* Global functions:
* pinmode() used to set the pin as output or input
* WritePin(int pin, int value), write only 1 pin with a specific value
* int ReadPin(int pin), read a specified pin and returns the value
*
*/


#ifndef GPIOLIB_H
#define GPIOLIB_H

/****************************************************************************
	Bit and byte definitions
****************************************************************************/
#define PINMODEOUTPUT 1
#define PINMODEINPUT 0
#define HIGH 1
#define LOW 0
#define HIGH 1
#define LOW 0
#define WRONGPIN 2
#define WRONGMODE 3
#define SUCCCESFULL 0
#define WRONGPARAMETER 1

/****************************************************************************
	Function definitions
****************************************************************************/
int pinMode(int pin, int mode);
int WritePin(int pin, int value);
int ReadPin(int pin);


#endif

gpiolib.c

/*
* gpiolib.c
*
* Created: 20-5-2017
* Author: HdH
* Description: functions to control the GPIO
* Global functions:
* WritePin(int pin, int value), write only 1 pin with a specific value
* int ReadPin(int pin), read a specified pin and returns the value
*
*/

#include <avr/io.h> // needed to recognize the macros DDRD and DDRB etc.
#include "gpiolib.h"

int pinMode(int pin, int mode){
	
	if(pin < 0 || pin > 13){
		return WRONGPIN; //pin number is out of range
	}
	if ((mode != PINMODEOUTPUT) && (mode != PINMODEINPUT)){
		return WRONGMODE; //mode is out of range
	}
	
	if(mode == PINMODEOUTPUT){
		if ((pin >= 0) && (pin <= 7)){
			DDRD |= (1<<pin); //set pin as output
			return SUCCCESFULL; //successfully set pin as output
		}
		
		if ((pin >= 8) && (pin <= 13)){
			DDRB |= (1<<(pin-8)); //set pin as output
			return SUCCCESFULL; //successfully set pin as output
		}
	}
	if(mode == PINMODEINPUT){
		if ((pin >= 0) && (pin <= 7)){
			DDRD &= ~(1<<pin); // set pin as input
			return SUCCCESFULL; //successfully set pin as input
		}
		if ((pin >= 8) && (pin <= 13)){
			DDRB &= ~(1<<(pin-8)); // set pin as input
			return SUCCCESFULL; //successfully set pin as input
		}
	}
	return WRONGPARAMETER;
}


int WritePin(int pin, int value){ // function also meant for future apps on different MCU Instead of using the Digital.write().
	
	if ((pin >= 0) && (pin <= 7)){
		if (value == 1){
			PORTD |= (1<<pin); //set pin
		}
		else{
			PORTD &= ~(1<<pin); // reset pin
		}
		return SUCCCESFULL; //return ok
	}
	if ((pin >= 8) && (pin <= 13)){
		if (value == 1){
			PORTB |= (1<<(pin-8)); // set pin
		}
		else {
			PORTB &= ~(1<<(pin-8)); // reset pin
		}
		return SUCCCESFULL; // return ok
	}
	return WRONGPIN; // error
}

int ReadPin(int pin){ // function also meant for future apps on different MCU Instead of using the Digital.read().
	
	if ((pin >= 0) && (pin <= 7)){
		if (PIND & (1<<pin)){
			
			return HIGH;
		}
		else
		return LOW;
	}
	if ((pin >= 8) && (pin <= 13)){
		if (PINB & (1<<(pin-8))){
			return HIGH;
		}
		else{
			return LOW;
		}
		
	}
	return WRONGPIN;//wrong pin number given. Not handled yet
}


Start the blinking LED

All files are in place now and ready to compile, but first select the correct board and make sure you
don’t use the simulator.

Select the AVR Dragon board and NOT simulation.

 

Now it’s time to compile and load the program into the Atmega328p bij clicking the “Start without debugging” button and the led will start blinking in a multi thread !

The good thing is the framework works nice, but the bad thing is we don’t use the framework correct with the blinking led. The reason for this is I used the funtion _delay_ms(2000). What this function is doing is basically keep the microcontroller doing nothing for 2 seconds. That means that application2 till application4 will start running after the 2 times 2000 milliseconds delay and not at the same time as the blinking led application. To get this issue solved I will introduce some new functions to avoid the use of delays. I will do this in the next blog. So if you don’t see the advantages of this framework yet then it will come clear in the next blog where i will show you how to use 3 application files.