Skip to main content

Taking your idea from concept to prototype - a stress ball demo: Part 1

This article is the first part in the series of articles focusing on the Microchip's "Stress Ball" demo. We will look into the basic operation of GPIO peripheral and how to implement it with software examples.

Parts list

Qty Product Part number
1 Microchip AVR128DA48 Curiosity Nano Evaluation Kit GPIO Evaluation Board DM164151 204-2690
1 Microchip AC164162 for use with Mikrobus Click Modules, Xplained Pro Extension Boards 193-6490
1 MikroElektronika MIKROE-1881, 4x4 RGB LED Matrix Display mikroBus Click Board 136-0725
1 MikroElektronika Force Click mikroBus Click Board for Implement Force Pressure Measurement MIKROE-2065 136-0788
1 MikroElektronika RN4871 Click Bluetooth Development Kit MIKROE-2544 168-3003

The Curiosity Nano development platform from Microchip is a small-sized, easily customizable rapid prototyping solution that provides developers with everything they might need to bring their project ideas to life. This article introduces the main features of the platform focusing specifically on a target microcontroller from AVR DA family. Together with our DesignSpark members, we will investigate one of the example codes from Microchip’s GitHub repository, while also discussing the basic operation of peripherals that were used in the demo, such as GPIOs, ADC and USART.

We all have that crazy idea for a project that we think probably going to be impossible to pull off whether because of its cost or lack of time. But what if I told you that there is a platform that will take your worries away and you will end up ENJOYING learning and building new things.

Enter Microchip’s Curiosity Nano development platform. The process of setting up the hardware could not be simpler: take a Curiosity Nano board with a target microcontroller of your choice, choose any of the MikroElectronika Click boards for added functionality, including various sensors, interfaces and connectivity to your project, and finally plug the boards into the base board.

Curiosity Nano Development Platform

Curiosity Nano Development Platform (microchip.com)

Sounds simple, right? Well, we still need to write a code that will bring life to your project. Luckily, Microchip provides a plethora of code examples both for AVR and PIC microcontroller-based Curiosity Nano boards. In this article, I will cover the build of one of the example projects – measuring and visualizing a force applied to a ball, so called “stress ball”.

Stress Ball Demo setup

Stress Ball Demo setup

As shown in the block diagram below, the demo utilizes the AVR128DA48 Curiosity Nano board as well as three add-on boards: Force, 4x4 RBG and RN4871 Bluetooth Low Energy Click boards. The force resistive sensor on the Force Click board is reading the force applied to the ball. The analog value is then read by the microcontroller’s built-in ADC. The applied force strength readings are visualised through either RGB LEDs, on the Android app via Bluetooth communication or can be sent to the PC’s serial terminal.

Block diagram for the demo

Block diagram for the demo

From the block diagram, we see that there are three distinct peripherals that enable this demo: GPIO, USART, ADC. We will go through each peripheral later in the article.

Hardware used for the demo

AVR128DA48 Curiosity Nano

Let’s have a closer look at the AVR128DA48 Curiosity Nano board (DM164151) (204-2690) and its features.

  • The board is based on AVR128DA48 8-bit microcontroller with operating speed up to 24 MHz, 128 KB of Flash, 16 KB of SRAM, and 512B of EEPROM in 48-pin package.
  • It features an on-board debugger for programming and debugging with either Microchip MPLAB® X IDE or Atmel Studio 7
  • There is one yellow user LED as well as a mechanical user switch
  • A 32.768 kHz crystal oscillator is mounted on the board to be used as an external clock source
  • The board is USB powered and features an adjustable target voltage with MIC5353 LDO regulator controlled by the on-board debugger

Looking at the pinout diagram of AVR128DA48 Curiosity Nano board with USB port at the top and a user button at the bottom, you might notice that the top section includes pins for debugging. This section is common for all Curiosity Nano boards. The middle section accommodates analog peripherals (ADC, PWM) on the right and pins for digital communication interfaces (UART, I2C and SPI) are located on the left. Finally, the bottom section pins will vary depending on the package of the target MCU. If you are interested in in-depth learning about the Curiosity Nano platform, there is a free course from Microchip that can be a very good place to start.

AVR128DA48 Pinout diagram

AVR128DA48 Pinout diagram

Peripheral Configuration - GPIO

The RGB LEDs communicate with the target microcontroller through General Purpose Input Output (GPIO) pins. The PORT peripheral registers are used to control these pins by following a certain configuration sequence. The PORTs are named PORTA, PORTB, PORTC, etc., with each instance having up to 8 I/O pins.

To understand how everything works under the hood, let’s have a look at the simplest task of turning a single LED on. I have simplified the PORT block diagram on page 161 of the AVR DA family datasheet to solely focus on our example.

Simplified block diagram for PORT pin used as an output

Simplified block diagram for PORT pin used as an output

Each port includes Data Direction (DIR) and Data Output Value (OUT) registers to define (or configure) its direction as output and to define its state High or Low. If a bit in the DIR register is set to “1”, the respective pin is enabled to be used as an output pin. The PORTx.OUT register, on the other hand, is responsible for setting the output of the pin to HIGH or LOW level.

One thing to note is that I am ignoring the Peripheral Override function by setting the second input of the 2-input multiplexer (MUX) to “0”. The same principle applies to the Invert Enable function.

Pin configuration for LED on/off

Pin configuration for LED on/off

If you look at the schematics of the Curiosity Nano board, you will notice that the LED (D) is connected to the pin PC6 through the current limiting resistor (R). Setting the output pin of the microcontroller to high does not allow any current pass as there is no potential difference across the components R and D. Conversely, the current will flow when the output of pin PC6 is set to a logic low.

But how do we set values high and low at the pin to turn the LED on and off? To answer that question, let’s review the D type flip-flops like the ones that are used for implementing PORTx.DIR and PORTx.OUT registers. The D flip-flop circuit can store one bit of information and its output (Q and Q’) changes state by signals applied to inputs - D (data), CLK (Clock), S (Set) and R (Clear/Reset).

D flip-flop with Set and Clear control inputs

D flip-flop with Set and Clear control inputs

For a basic D flip-flop without Set and Reset inputs, the data bit at the D input is transferred to the output on the low-high (rising edge) or high-low (falling edge) transition of the clock input. The timing diagram below demonstrates the former case.

Timing diagram for rising edge D flip-flop

Timing diagram for rising edge D flip-flop

Optionally, asynchronous inputs S and R can be used to override the clock/data inputs and force the output to a predefined state as can be seen in the last three rows of the truth table.

Truth table for rising edge D flip-flop with set and reset

Truth table for rising edge D flip-flop with set and reset

Using this knowledge of D flip-flips and going back to our example, you might notice that we have several options for configuring the output pin. As it was discussed earlier, PORTx.DIR is set to “1” (Enable) or “0” (disable) and PORTx.OUT is set to “1” (high) or “0” (low). Alternatively, the output can be enabled or disabled by respectively writing “1” to PORTx.DIRSET or PORTx.DIRCLR registers, whereas HIGH or LOW level can be set by writing ‘1’ to bit n in the PORTx.OUTSET or PORTx.OUTCLR register. The table below summarizes different registers available within each port.

PORTx Register Summary

PORTx Register Summary

Code implementation - LED toggle

The following code combines everything we have learned so far about the configuring GPIO pins. We want to toggle the output value of pin 6 at PORTC between high and low. First, we set PORTC.DIRSET to “1” by applying a bit mask - PIN6_bm. Each port is 8-bit in size and byte addressable, which means register value can only be configured as a block. To change the bit at position 6, we need to apply the value “0b01000000” or “0x40” to the register. If you have a look at the definition of PIN6_bm within “avr/io.h” header files, you will see that its value corresponds to the “0x40”. For more guidance on programming AVR® microcontrollers in C, you can refer to “AVR1000b: Getting Started with Writing C-Code for AVR® MCUs” technical brief from Microchip. Then, we alternate the values in PORTC.OUTSET and PORTC.OUTCLR with a certain delay in between so that we can differentiate the LED toggling between on and off states.

#include <avr/io.h>
#include <util/delay.h>
#define F_CPU 4000000UL
#define DELAY_TIME 1000

int main(void)
{
   PORTC.DIRSET = PIN6_bm;
   while (1)
   {
      PORTC.OUTSET = PIN6_bm;
      _delay_ms(DELAY_TIME);
 
      PORTC.OUTCLR = PIN6_bm;
      _delay_ms(DELAY_TIME);
    }
}

PIN6_bm definition in header files

PIN6_bm definition in header files

Code implementation - LED Toggle with a button

Let’s now modify the code above to only turn off the LED when the user presses the button. This time around we need to consider the case when there is an input signal coming into the pin. I went back to the PORT block diagram, stripped all the fancy functions until I was left with only the ones that are necessary for a basic operation.

Simplified block diagram for PORT pin used as an output/input

Simplified block diagram for PORT pin used as an output/input

The first change you might notice is that we now use Pull-up Enable to let the current flow through the transistor. The user button is connected to the pin PC7. When the button is pressed, this current can happily travel to the ground and the pin’s input value will be a logical “0”. Conversely, if there is an open circuit due to the button not being pressed, Vcc value will be read as “1” at the pin.

User button schematic

User button

We first initialize PC6 as an output and PC7 as an input. The pull-up configuration for PC7 can be enabled by setting bit position 3 to “1” within the PIN7CTRL register by applying the bit mask value that corresponds to “0b00001000”. Inside the while loop, we check whether the value of a respective bit in the PORTC.IN register is high or low. This can be achieved by performing a logical AND operation between the value that is detected and a target value (0b00001000). If the result of bitwise AND operation changes the state of the pin to high, the LED is turned off and vice versa for the case when the value detected is low.

#include <avr/io.h>
#include <util/delay.h>
#define F_CPU 4000000UL

PORTC.DIRSET = PIN6_bm; 
PORTC.DIRCLR = PIN7_bm; 
PORTC.PIN7CTRL |= PORT_PULLUPEN_bm; 

while (1)
{
   /* Check the button */
   if(PORTC.IN & PIN7_bm) 
   {
      /* The button is released - LED off */ 
      PORTC.OUTSET = PIN6_bm; 
   }
   else
   {
      /* The button is pressed - LED on */
      PORTC.OUTCLR = PIN6_bm; 
   }
}

>Code implementation – 4x4 RGB Click

The 4x4 RGB Click board features 16 LED elements (WS2812) from Worldsemi Corporation arranged in 4 by 4 matrix. In the demo, the applied force on FSR is proportional to the number and their brightness of LED elements that are illuminated.

4x4 RGB Click board

4x4 RGB Click board

Each element or “pixel” features red, green and blue individual LEDs along with an integrated control circuit. From the pinout diagram below, you can see that there are power supply and ground as well as data in and data out pins. By cascading these pixels with data output connected to the data input pin, we can control all 16 of them through a single pin on the AVR128DA48.

Pin out diagram for WS2812

Pin out diagram for WS2812

Cascade configuration with three WS2812 devices

Cascade configuration with three WS2812 devices

The three LEDs inside the WS2812 can achieve 256 brightness levels with 8-bits of data per channel, forming 24-bit color palette. This results in a total of 16777216 (23*8) possible colour combinations that can be displayed on each pixel. The control signal consisting of 24 bits is shown in the diagram below.

24-bit data sequence

24-bit data sequence

The timing diagram for a data signal with corresponding values for logical “1” and “0” is shown below. The values for T0H, T0L, T1H and T1L can be found in the device datasheet. You might notice one caveat with setting these values in the code implementation. The exact duration of T0H, T0L, T1H and T1L will have to be adjusted depending on how many cycles Clear Bit and Set Bit instructions will take. For example, it takes 1 cycle to execute rgb_led_set_high(), thus _delay_one_high() will need to take that into account and only delay for “T1H – 1 cycle (ns)”. Refer to the macros in RGBClick_4x4.c file for more detailed explanation.

Timing diagram for WS2812

Timing diagram for WS2812

The RGB Click board is connected to the Curiosity Nano board through pin PE3 as you might notice in the “RGBClick_4x4.h” header file.

Port definition in header files

Port definition in header files

Let’s briefly go through the functions implemented with the 4x4 RGB Click board:

  • Initialize – rgb_init() – set bit position 3 in the PORTE.DIR register to “1” to enable the pin PE3 as an output
void rgb_init(void) {
	RGB_LED_VPORT.DIR |= RGB_LED_bm;
}
  • Reset – reset() – reset bit position 3 of PORTE.OUT register to “0”
static void reset(void)
{
	rgb_led_set_low()
	_delay_us(50);
}
  • Update single LED - rgb_update_single() – send 24 pulses with specified timings by alternating the state of PORTE.OUT between HIGH and LOW.
static void rgb_update_single(rgb_led_t led) {
	for (uint8_t j=0; j < NEOPIXEL_CHANNELS; j++)
	{
		for (uint8_t k=0; k < 8; k++)
		{
			if (led.channel[j] & 0x80)
			{
				rgb_led_set_high()
				_delay_one_high();
				rgb_led_set_low()
				_delay_one_low();
			} else {
				rgb_led_set_high()
				_delay_zero_high();
				rgb_led_set_low()
				_delay_zero_low();
			}
			led.channel[j] <<= 1;
		}
	}
}

For example, if we want the LED to illuminate green colour with 50% intensity, the input of the function - rgb_update_single() is going to be {0x0F,0x00,0x00}. The function has a nested loop with three channels in the outer loop and 8-bit intensity values per channel in the inner loop. By performing bitwise AND operation with the value 0x80 (or 0b10000000) and left shifting the bit value of our target colour intensity on each iteration, the function generates 8-bit control signal per channel for the LED elements on the RGB Click as demonstrated below.

Pixel intensity values per channel stored in array

Pixel intensity values per channel stored in array

  • RGB Patterns:
    • Single colour - rgb_update_single_color() - use rgb_update_single() to set a number of LEDs to the same colour
void rgb_update_single_color(rgb_led_t led_string, uint16_t len) {
	for (uint16_t i=0; i < len; i++)
	{
		rgb_update_single(led_string);
	}
	reset();

To update a set of pixels with the same colour, we just need to pass the RGB values for a single and number of pixels we want to illuminate to the function rgb_update_single_color().

  • Mixed colour - rgb_pattern_MixColor() - use rgb_update () to set LEDs to different colours by filling the input array rgb_array_output[0-15].
void rgb_pattern_MixColor()
{
	uint8_t i,j=0;
	for (i = 0;i <= RGB_CLICK_NUM_LEDS - 1 ; i++)
	{
		rgb_array_output[i].green = led_color_array[j][0];
		rgb_array_output[i].red = led_color_array[j][1];
		rgb_array_output[i].blue = led_color_array[j][2];
		j++;
		if (j >= MAX_COLORS)
		{
			j = 0;
		}
	}
	rgb_update(rgb_array_output,RGB_CLICK_NUM_LEDS);
	_delay_ms(1000);
}

In the demo, a mix of BLUE, RED, GREEN, YELLOW, MAGENTA, CYAN and WHITE is displayed by passing RGB values of these colours to the function rgb_pattern_MixColor(). This pattern can be observed when you first power up the demo after flashing the code onto the target board.

  • RED, GREEN, WHITE - rgb_pattern_Red_Green_White() - the three colours are displayed with 500ms delay in between.
void rgb_pattern_Red_Green_White()
{
	    //RED
		rgb_array_output[0].green =0x00;
		rgb_array_output[0].red = 0x0F;
		rgb_array_output[0].blue = 0x00;
		rgb_update_single_color(rgb_array_output[0], RGB_CLICK_NUM_LEDS); 
		_delay_ms(500);
		//GREEN
		rgb_array_output[0].green =0x0F;
		rgb_array_output[0].red = 0x00;
		rgb_array_output[0].blue = 0x00;
		rgb_update_single_color(rgb_array_output[0], RGB_CLICK_NUM_LEDS);
		_delay_ms(500);
		//WHITE
		rgb_array_output[0].green =0x0F;
		rgb_array_output[0].red = 0x0F;
		rgb_array_output[0].blue = 0x0F;
		rgb_update_single_color(rgb_array_output[0], RGB_CLICK_NUM_LEDS);

}

Similar to the previous pattern, the RGB values of RED, GREEN and WHITE are used to fill in rgb_array_output array, which is then passed to the rgb_update() function.

  • Clear all LEDs - rgb_clear_all_leds() – RGB values for all LEDs are set to 0.
void rgb_clear_all_leds()
{
	uint8_t i;
	
	for (i = 0 ; i < RGB_CLICK_NUM_LEDS ; i++)
	{
		rgb_array_output[i].green = 0;
		rgb_array_output[i].blue = 0;
		rgb_array_output[i].red = 0;
	}
}
  • Display pattern per force - rgb_display_pattern_per_force() – fill LEDs with colour and brightness as per applied force by reading the value in ADC.
void rgb_display_pattern_per_force()
{
	uint8_t i;
	brightness = (uint8_t) (adc_t.adc_average_result >> 4);  /* Calculate brightness as per ADC result. For RGB LEDs, brightness is 8 bit so shift right the 12bit ADC result by 4 */
    leds_to_glow = 	(adc_t.adc_average_result * (RGB_CLICK_NUM_LEDS-1)) / ADC_MAX_VALUE_for_FORCE_CLICK;	/* As per ADC result. Maximum LEDS to glow are 16 */ 
	                                                                                                        /* When leds_to_glow = 15, All 16 LEDs will be lit */   																											
	rgb_clear_all_leds();
	if(leds_to_glow != RGB_CLICK_NUM_LEDS - 1) /* Force is not 100% so number of LEDs to glow are less than 16 */
	{
			for (i=0; i <= leds_to_glow ; i++)		/* Fill needed LEDs with color and brightness as per force */
			{
				rgb_array_output[led_seq_to_glow[i]].green = 0x00;
				rgb_array_output[led_seq_to_glow[i]].red =  0x00;
				rgb_array_output[led_seq_to_glow[i]].blue = brightness;
			}
		
		
	}
	else /* When 100% strength, fill LEDs with different colors and turn ON all 16 LEDS */
	{
		counter++;
		if (counter >= COUNTER_to_CHNAGE_COLOR)
		{
			counter=0;
			change_color_index++;
			if (change_color_index >= MAX_COLORS)
			{
				change_color_index = 0;
			}
		}
		for (i=0; i<=leds_to_glow ;i++)
		{
			rgb_array_output[led_seq_to_glow[i]].green = led_color_array[change_color_index][0] ;
			rgb_array_output[led_seq_to_glow[i]].red  =  led_color_array[change_color_index][1] ;
			rgb_array_output[led_seq_to_glow[i]].blue =  led_color_array[change_color_index][2] ;
		}
			
	}
	rgb_update(rgb_array_output,RGB_CLICK_NUM_LEDS);
}

This function gets hold of the applied force value from ADC given as variable adc_t.adc_average_result. Since ADC result takes up 12 bits, whereas the parameter it needs to be mapped to, pixel intensity or brightness, is 8 bits, it is necessary to perform the right shift operation by 4 bits. To find out how many LEDs need to be turned on, the ADC result is also scaled to the value between 1 and 16 (leds_to_glow). If this value is less than 16, we fill the rgb_output_array with colour and brightness per force. In our demo, that colour is blue. On the other hand, if the maximum force has been applied to the force sensor (100% strength), all LEDs will be turned on and filled with different colours.

More on the operation of ADC and how the force measurements are taken in the later sections.

Peripheral Configuration - USART (Part 2)

Peripheral Configuration - ADC (Part 3)

Resources

Rapid Prototyping with the Curiosity Nano Platform – Microchip University

AVR1000b: Getting Started with Writing C-Code for AVR® - TB3262

How to Use Force Sensitive Resistor with 12-bit ADC - AN3408

AVR® DA Family Datasheet - Microchip

AVR128DA48 USART Hello World Example – GitHub

USART Serial Terminal – Microchip Developer

WS2812 Tutorial – Friendly Wire

I am an electronics engineer turned data engineer who likes creating content around IoT, machine learning, computer vision and everything in between.
DesignSpark Electrical Logolinkedin