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.