RSS

Posts Tagged ‘LED’

LED Board Has Arrived

Sunday, November 1st, 2015

A few weeks ago I started to work with KiCAD and put together a small LED board as a project to become familiar with the application. The boards were sent to manufacture and the the postman delivered the manufactured PCBs yesterday.

LED Board Front

LED Board Front

LED Board Front

LED Board Front

Time to add the components and test the boards.

Populated Board Right Angled Headers

Populated Board Right Angled Headers

And one with straight headers:

Populated Board Straight Headers

Populated Board Straight Headers

Everthing looks good, the boards work OK so time to move on to the big board for the cube.

Teensy 3.1 and Visual Micro

Tuesday, October 14th, 2014

September saw the end of an agonising few months (nearly a year in truth) working with a 32 x 32 LED matrix and smart LEDs (WS2811, Neopixels etc.). I’ve been trying to reliably control these LEDs with the STM32 Discovery board. It’s not that I cracked the problem with the STM32 but that I came across a cheap ready made solution to the problem, namely the Teensy 3.1. It has proved to be a painful reminder of something a software engineer should know, use libraries if you can. In the case of hardware prototyping the use of libraries also extends in part to the choice of hardware.

One of the main reasons I have resisted going down the route of using boards like the Teensy 3.1 is the Arduino IDE. I think that I have been spoiled by the richness of the Visual Studio IDE. Even IDES should as the IAR IDE for the STM8S leave something to be desired when you compare it to Visual Studio. This is the story of how I got the LEDs working and solved the IDE issue by using Visual Micro.

Problem Hardware

There are two pieces of hardware causing me problems at the moment:

  1. 32 x 32 LED Matrix
  2. 5mm Digital Addressable LEDs

These were purchased in the UK from Cool Components.

32 x 32 LED Matrix/Panel

The 32 x 32 LED matrix is often a component in larger displays.

LED Matrix

LED Matrix

The displays are easily chained together although the power requirements quickly increase. Each panel can consume up to 2A depending upon the number of LEDs illuminated as any time.

These boards are designed to be controlled by FPGAs allowing for high frame rates. Sparkfun and Adafruit have tutorials on running these boards and they have found that you can run a single panel at 16MHz (i.e. you can run it on an Arduino but only just). Running two or more panels requires more power, both RAM and processor speed.

Digitally Addressable LEDs

The LEDs in question use the WS2811 constant current IC to drive RGB LEDs. These controllers use their own one-wire protocol (not to be confused with the Dallas one-wire protocol) to determine the PWM characteristics of the red, green and blue components of the light output. Over the years these controllers have shown up in LED strings, LED strips and more recently as the Adafruit Neopixel and now as individual through hole LEDs.

The main problem I have found with these LEDs, or more specifically the controllers, is the one-wire protocol. This requires very precise timing and this is often difficult to obtain on the hobbyist microcontrollers without a lot of very precise control.

Solving the problem

I decided to solve this problem using the Teensy 3.1. The thing which makes the Teensy 3.1 very attractive for this type of project is the add-on board, the SmartMatrix Shield. This board allows you to connect a Teensy directly on to the IDC headers of the LED Matrix.

In addition to the hardware, the Teensy 3.1 library contains ports of the equivalent Arduino libraries for the 32 x 32 LED matrix as well as LEDs controlled by the WS2811.

Teensy 3.1

The Teensy has been through several iterations, the Teensy 3.1 being the latest at the time of writing. The board is small, only 35mm x 18mm, and I was not really prepared for how small it actually is!

TeensyAndRuler

Small does not mean low powered though, the board packs a Cortex-M4 processor, 256K flash and 64K RAM. The 72MHz processor can also be overclocked to over 90MHz. Other features include:

  • 34 Digital I/O (3.3V but 5V tolerant)
  • 21 analog inputs
  • 12 PWM
  • SPI, I2C, CAN bus
  • ARM DSP extension

The foot print of the board allows it to be easily added to a breadboard.

The Teensy 3.1 board is also supplied with it’s own libraries in the form of Teensyduino. This is an add-on for the Arduino IDE and is compatible with many of the Arduino libraries. The Arduino compatible libraries provide the ability to work with the LED matrix and the addressable LEDs mentioned above as well as a wide variety of other devices.

Software

As I have stated earlier, I am not really a fan of the Arduino IDE. Do not misinterpret me, it represents a great piece of work, however the amount of time and money Microsoft have poured into Visual Studio over the years means that Visual Studio really does take some beating. This is where Visual Micro comes in.

Visual Micro is a Visual Studio 2008-2013 add-in which allows the development of Arduino sketches within the Visual Studio framework. This gives you all the power of Visual Studio (code refactoring etc.) while still allowing the development of Arduino sketches. The add-in is free until the end of 2014 with the option to purchase the debugger support for a nominal fee.

The add-in supports a good number of boards out of the box including the Teensy 3.1.

Setup is simple and the documentation is really good. There are a few additional steps for the Teensy 3.1 but this is cover in the Tips for Teensy page.

Once configured it was a simple task to create a new Teensy 3.1 project using the Neopixel sketch template. A little code reformatting gives the following code:

#include <Adafruit_NeoPixel.h>

//
//	Parameter 1 = number of pixels in strip
//	Parameter 2 = pin number (most are valid)
//	Parameter 3 = pixel type flags, add together as needed:
//		NEO_RGB     Pixels are wired for RGB bitstream
//		NEO_GRB     Pixels are wired for GRB bitstream
//		NEO_KHZ400  400 KHz bitstream (e.g. FLORA pixels)
//		NEO_KHZ800  800 KHz bitstream (e.g. High Density LED strip)
//
Adafruit_NeoPixel strip = Adafruit_NeoPixel(60, 6, NEO_RGB + NEO_KHZ800);

//
//	Input a value 0 to 255 to get a color value.
//	The colours are a transition r - g - b - back to r.
//
uint32_t Wheel(byte WheelPos)
{
	if (WheelPos < 85)
	{
		return strip.Color(WheelPos * 3, 255 - WheelPos * 3, 0);
	}
	else if (WheelPos < 170)
	{
		WheelPos -= 85;
		return strip.Color(255 - WheelPos * 3, 0, WheelPos * 3);
	}
	else
	{
		WheelPos -= 170;
		return strip.Color(0, WheelPos * 3, 255 - WheelPos * 3);
	}
}

//
//	Fill the dots one after the other with a colour
//
void colorWipe(uint32_t c, uint8_t wait)
{
	for (uint16_t i = 0; i < strip.numPixels(); i++)
	{
		strip.setPixelColor(i, c);
		strip.show();
		delay(wait);
	}
}

//
//  Set all LEDs to the same colour.
//
void rainbow(uint8_t wait)
{
	uint16_t i, j;

	for (j = 0; j < 256; j++)
	{
		for (i = 0; i < strip.numPixels(); i++)
		{
			strip.setPixelColor(i, Wheel((i + j) & 255));
		}
		strip.show();
		delay(wait);
	}
}

//
//	Slightly different, this makes the rainbow equally distributed throughout
//
void rainbowCycle(uint8_t wait)
{
	uint16_t i, j;

	for (j = 0; j < 256 * 5; j++)
	{ // 5 cycles of all colors on wheel
		for (i = 0; i < strip.numPixels(); i++)
		{
			strip.setPixelColor(i, Wheel(((i * 256 / strip.numPixels()) + j) & 255));
		}
		strip.show();
		delay(wait);
	}
}

//
//	Executed once at startup to set up the board.
//
void setup()
{
	strip.begin();
	strip.show(); // Initialize all pixels to 'off'
}

//
//	The actual main program loop.
//
void loop()
{
	rainbow(20);
	rainbowCycle(20);
}

This code creates an Adafruit_NeoPixel object with the pixels connected to pin 6 of the Teensy 3.1.

Connecting some LEDs top the Teensy and uploading the above code results in the following:

Modifying the code to add a counter will allow testing of the debugging features. The loop code is changed to have a counter variable to test the conditional breakpoint feature:

int count = 0;

//
//	The actual main program loop.
//
void loop()
{
	count++;
	digitalWrite(LED_PIN, HIGH);
	delay(500);
	digitalWrite(LED_PIN, LOW);
	delay(500);
	// Some example procedures showing how to display to the pixels:
	rainbow(20);
	rainbowCycle(20);
}

Adding a breakpoint after the counter increment and making the breakpoint conditional results in the following display:

Sample code

Sample Code With Breakpoint

Nothing too strange here, pretty much standard for Visual Studio. The breakpoint window shows the breakpoint and the condition:

Debugger Breakpoint Window

Debugger Breakpoint Window

Running the code brings up the debugger expression window:

Debugger Expression Window

Debugger Expression Window

The one thing which experienced users of Visual Studio will find unfamiliar is the way in which single stepping works. Unlike say Win32 development where single stepping takes you to the next line of code and then waits, with Visual Micro, single stepping runs the code to the next breakpoint.

Conclusion

Visual Studio is without doubt one of the best software development environments available. The addition of Visual Micro brings the power of Visual Studio to the Arduino world. Whilst the debugging features (single stepping) may a little unfamiliar to Visual Studio users they are not so strange as to be unusable.

Both the Teensy 3.1 and Visual Micro are a welcome addition to the development tools and it is highly likely that they will appear in future posts.

Why Do We Prototype?

Wednesday, January 1st, 2014

I’ve been playing with the TLC5940 for a few years now. I aim to eventually have it play a part in a larger project but I need to get a few things ironed out first. I’m currently on my second prototype board, well they are more proof of concept boards really.

This post is not really about the boards themselves but more about the mistakes I’ve made along the way. Hence the title of this post, Why do we prototype?

I think the simple answer is that we make mistakes.

Design Goals

The long term aim of this project is to use the TLC5940 to drive a grid of LEDs. The chip allows the connection of the LEDs directly to the chip but I want to use a transistor (or an equivalent) to turn the LEDs on and off and not the TLC5940 directly.

Breadboard

The breadboard circuit had all of the necessary components on the board and was pretty simple to get going. I started by connecting the LEDs directly to the TLC5940 and then built the software to run the circuit. The software runs on the STM8S103F3.

The next step is to connect one or more LEDs through a transistor. For this I used a PNP transistor and connected one of the LEDs using the transistor as a switch.

So far, so good. All is well with the world and I have some flashing LEDs.

The board (without the TLC5940s) looks like this:

OriginalBreadboardWithoutTLC5940

Next step, prototype.

TLC5940 – Rev A

At this point I had designed a few boards and thought I’d push my SMD skills a little. I decided to use iTeads 5cm x 5cm manufacturing service and with the exception of the external connectors I would use SMD components only.

To give you an idea of the scale of this, the circuit requires 2 ICs plus 6 supporting discrete components. Each LED (and there are 16 of them) requires three discrete components for the driver plus the LED itself.

That is a pretty dense board for a beginner. Here is the 3D render of the board:

TLC5940 Rev A  Prototype 3D Render

TLC5940 Rev A Prototype 3D Render

And the bare board itself:

TLC5940 Driver Board Rev A - Front

TLC5940 Driver Board Rev A – Front

and the back:

TLC5940 Driver Board Rev A - Back

TLC5940 Driver Board Rev A – Back

Lesson 1 – Track Routing

I’ve been using DesignSpark PCB for all of my designs and it’s a pretty good piece of software and I am very impressed. One feature I have only recently used in anger is the ability to turn off some of the layers. Have a look at the traces to the left of the board below:

TLC5940 Prototype Rev A Routed Traces

TLC5940 Prototype Rev A Routed Traces

This did not really cause an issue as there was no routing error but I could have routed these tracks more elegantly. The problem was caused by me having both the top and bottom traces visible at the same time. In my mind I had to route these tracks around the traces on the top layer as well as the artefacts on the bottom layer. Hence I took the traces around the via when I could have taken them directly from the via on the right (as viewed from below).

I would have spotted this more logical routing had I turned off the top copper view as soon as the trace passed through to the bottom copper layer.

Not really an error, more of a cosmetic nicety.

Lesson 2 – Use the Same Parts

Between the breadboard stage and the PCB stage I changed the part used in the driver. Not really too much of an issue except…

I did not get an equivalent PTH part and test it on the breadboard first.

As it stands the transistor driver circuit does not function and needs some more attention.

To enable testing of the remainder of the circuit you can use the following work around. Abandon the driver and change the value of the IREF resistor to allow a small enough current through the LED.

Lesson 3 – 0603 Parts Are Small Enough For the Hobbyist

Some of the parts I used in the design are 0402. These are small parts and really difficult to solder. It is especially difficult to see markings on the components.

In future I think 0603 is as small as I’ll go.

TLC5940 – Rev B

The Rev A board was a bit of a disaster. I never really managed to get the board working so it was time to go back to first principles and build a simpler board. Enter Revision B.

Revision B of the board will have a reduced brief. This board will use a mixture of SMD and PTH components. The STM8S and supporting components will be surface mount but the TLC5940’s will be PTH. I’m getting reasonably confident that I can get the STM8S on a board and working even in surface mount format.

Boards ordered, arrived and assembled. Here’s is a photo of it working:

Lesson 3 – Vcc and Ground Are Not Interchangeable

In redesigning the circuit I had to replace the TLC5940 component and so added the new one and changed the resistor value for the LEDs I’d be working with. Except I connected the IREF resistor to Vcc instead of ground.

TLC5940 Prototype Rev B Working

TLC5940 Prototype Rev B Working

Notice that the places for R1 and R2 are empty. That’s because they are on the bottom of the board:

TLC5940 Prototype Rev B Resistors

TLC5940 Prototype Rev B Resistors

Lesson 4 – Plastic Melts At Low Temperatures

For this circuit I use a single AND gate to square off the CCO output from the STM8S. This small IC was placed onto the board after the connector for the ST-Link programmer. The only problem was that I was using a hot air rework station to fix this part to the board. The hot air from the rework station caused some bubbling on the plastic of the connector.

ST-Link Connector

ST-Link Connector

I suppose this brings me to the next lesson.

Lesson 5 – Use the Board Luke

OK, so there’s been too much Star Wars on TV over Christmas.

What I really mean is, when prototyping, use all of the board available to you. The manufacturing process I used allowed for a 10cm x 5cm board (or anything up to that size) for a fixed price. For this board there is a lot of spare real estate and I could, with ease, have put that connector and the IC where they would not have interfered with each other.

The final board may have to be compact and fit into a location determined by the rest of the project but when testing you might as well use all of the space to your advantage.

Conclusion

I have a working Revision B board which mean I can free up the breadboard for other work but there is still some way to go before I have the final project completed. As with all things in life, this is a learning experience.

Hope you found this useful.

Nikon D70 remote Control PCB Assembly

Sunday, November 10th, 2013

A few days ago the bare PCBs arrived from China:

Nikon D70 Bare Boards

Nikon D70 Bare Boards

Work commitments meant that assembly has had to wait a while. I finally managed to get to put the boards together today. There is certainly a difference in size between the proto-board and the final (well nearly) PCB.

Proto-board and Assembled Board With Ruler

Proto-board and Assembled Board With Ruler

I’ve documented the assembly process of SMD boards in the past so in this post I’ll just be documenting the lessons learned from this assembly

Measure Twice, Cut Once

A carpenter friend of mine passed on this advice and it certainly rings true on this build. If you look at the back of the board there are a couple of options for connecting power to the board. The intention was to allow the board to be powered by 2 x AA batteries or a CR2032 coin cell. The connection points were supposed to be placed to allow the use of two battery holders I had purchased from Bitsbox. The measurements are a classic "off by one" case. The connections are both 2.54mm off.

Next time I’ll be double checking the footprint of the components.

Solder Paste is Opaque

I originally tried to apply solder paste to the pads for the STM8S and then position the chip on the pads. The theory is great but in practice the positioning is difficult as you cannot see the pads through the paste. Instead I found it easier to apply flux and then tack down one pin on he STM8S. This allowed the positioning of the pads and legs of the STM8S to be checked. Once I was happy with the alignment of the two I applied solder paste to the top of the legs on the chip and then heated the pins.

0402 Components are Small

A few of the components are small, very small. Most of these do not require any orientation but the LED indicating that the power is applied is small and does require a particular orientation. I found a cheap USB microscope useful to help ensure the orientation was correct.

Conclusion

I always forget how small some of these components are but with a little practice you can work with surface mount components. The current board looks like this:

Assembled Board With Ruler

Assembled Board With Ruler

The Nikon D70 infra-red remote control prototype still triggers the camera using the trigger button. The next stage is to work on the software allowing the trigger of the remote control using the UART either by the FTDI and the RedBearBLE mini.

Netduino, iPhone and Low Energy Bluetooth

Saturday, August 24th, 2013

A while ago I wrote a small library for the RN-42 Bluetooth module. This small module acted as a serial port when connected to the Netduino. Times have moved on with Bluetooth 4.0 being available on the iPhone I thought I would take another look at Bluetooth.

Bluetooth Module

The Bluetooth module we will be using is the RedBear Bluetooth Mini. This little module is a Bluetooth 4.0 Low Energy (BLE) module. In it’s simplest form it offers the ability to act as a serial port for your project. This is how we will be using this module for this project.

Bluetooth Device

To test this a BLE compatible device is required. Enter the iPhone 5 and the BLE Arduino application which RedBear have thoughtfully provided. The application may be written for Arduino but it is sending simple serial commands to the Arduino. Let’s see what it is doing and if we can use this application.

Serial Port Settings

First job is to figure out the serial port settings. Scanning the source code for the Arduino I came across the following line in the setup method:

BleFirmata.begin(57600);

My guess is that this is setting up the Arduino serial port to run at 57,600 baud using standard data bits, parity and top bit settings. Time to break out the logic analyser.

Connections between the Netduino and the RedBear module is simple:

Netduino PinRedbear BLE Module Pin
D0 (Rx)Tx
D1 (Tx)Rx
3.3VVin
GNDGND

Additionally, the logic analyser is connected to the Rx and Tx pins of the Netduino. An Async Serial analyser was added to the two pins through the Saleae software. This analyser has an Autobaud feature. Turn this on just in case we get the baud rate incorrect.

Next step is to install the BLE Arduino application on the iPhone 5. Start the application:

ReadBear BLE Software Opening Screen

ReadBear BLE Software Opening Screen

Press the Click button to connect to the ReadBear module.

Select Arduino Board

Select Arduino Board

Select the Arduino to connect to. The Arduino Uno has a similar pin out so select the Uno option.

Change a Pin Output Using BLE Software

Change a Pin Output Using BLE Software

So far there has been no output on the logic analyser. Change the state of an output pin (pressing H or L). This generates serial data:

Serial Data On the Logic Analyser

Serial Data On the Logic Analyser

A quick check in the Async Serial analysers properties shows that the data logic analyser thinks that the data is being sent at 60,000 baud, pretty close to the 57,600 which was found in the code earlier.

Netduino Code

The next step is to try to connect some code on the Netduino to the iPhone application. The simplest application we can use simply captures the incoming data on the serial port and displays the bytes received in the debug window of Visual Studio. The following should do the trick:

public class Program
{
    const int BUFFER_SIZE = 1024;

    public static void Main()
    {
        SerialPort sp = new SerialPort("COM1", 57600, Parity.None, 8, StopBits.One);
        sp.DataReceived += sp_DataReceived;
        sp.Open();
        Thread.Sleep(Timeout.Infinite);
    }

    static void sp_DataReceived(object sender, SerialDataReceivedEventArgs e)
    {
        string str;
        str = "";
        if (e.EventType == SerialData.Chars)
        {
            int amount;
            byte[] buffer;

            buffer = new byte[BUFFER_SIZE];
            amount = ((SerialPort) sender).Read(buffer, 0, BUFFER_SIZE);
            if (amount > 0)
            {
                for (int index = 0; index < amount; index++)
                {
                    str += buffer[index].ToString();
                    str += " ";
                }
                Debug.Print("Data: " + str);
            }
        }
    }
}

Deploying the above code to my Netduino Plus 2 and running the application gives the following output:

Data: 144 4 0
Data: 144
Data: 12 0
Data: 144
Data: 4 0

Pressing the H at the side of Pin 2 in the application generates the sequence 144 4 0. Following this up by pressing the H at the side of Pin 3 results in the sequence 144 12 0. Checking the Arduino code shows that the digital commands are prefixed by 0x90, i.e. 144.

The iPhone Application

Now that we know the iPhone and the Netduino can communicate (at least from the phone to the Netduino) let’s have a look at creating our own iPhone application. The simplest application would entail toggling a digital pin, so let’s do that. We’ll turn an LED on and off.

Starting XCode we will create a new project (Single View Application) for the iPhone and set the application be targeted at the iPhone only and to use Automatic Reference Counting (ARC):

New iPhone Project Options

New iPhone Project Options

Next we need to add the CoreBluetooth.Framework references to the application. So scroll down the frameworks and click on the + button under the Linked Frameworks section. Type bluetooth in order to search the installed frameworks.

Next, we need to add the RedBear BLE framework. I downloaded this and put it in my stored libraries. This was then added by right clicking on the Frameworks folder of the project, selecting Add Files to… and then browsing to the folder which contained the files.

Next we head over to the Storyboard and add a few controls to our interface:

iPhone Application in XCode

iPhone Application in XCode

ControlDescription
btnConnectToNetduinoButton to connect/disconnect to/from the Netduino.
lblStatusLabel to indicate the status of the application.
lblLEDStatusLabel containing the text LED Status
swLEDSwitch which is used to turn the LED on/off.
aiBusyIndicator which shows when the application is trying to detect the Bluetooth module.

First thing to do is to connect the controls to the code in the header file for the view controller:

#import <UIKit/UIKit.h>
#import "BLE.h"

//
//  Define the various modes for the state machine.
//
#define MODE_DISCONNECTED       0
#define MODE_CONNECTING         1
#define MODE_CONNECTED          2
#define MODE_DISCONNECTING      3

//
//  Timeout values.
//
#define TIMEOUT_BLE_TIMEOUT         2
#define TIMEOUT_BUSY_CONNECTING     3.0

@interface SNCViewController : UIViewController <BLEDelegate>
{
    IBOutlet UILabel *lblStatusMessage;
    IBOutlet UILabel *lblLEDStatus;
    IBOutlet UISwitch *swLED;
}

- (IBAction) btnConnect_TouchUpInside:(UIButton *) sender;
- (IBAction) swLED_Changed:(UISwitch *)sender;

@property (strong, nonatomic) BLE *ble;
@property (strong, nonatomic) UIActivityIndicatorView *aiBusy;
@property (strong, nonatomic) IBOutlet UIButton *btnConnectToNetduino;

@end

Now we have the controls defined in the header file we can start to work on the methods.

Definitions

We need some simple definitions at the head of our implementation file:

#import "SNCViewController.h"
#import "BLE.h"

@interface SNCViewController ()

@end

@implementation SNCViewController

@synthesize ble;
@synthesize btnConnectToNetduino;

//
//  Private local variables.
//
int mode = MODE_DISCONNECTED;

These definitions create the Bluetooth object and he variables required for state control.

Supporting Methods

The Connected and Disconnected methods change the interface and state variables when we have successfully connected or disconnected to/from the Bluetooth module:

//
//  Set up the interface to show that the Bluetooth module is connected
//
- (void) Connected
{
    [lblStatusMessage setText:@"Connected"];
    lblLEDStatus.hidden = false;
    swLED.hidden = false;
    [btnConnectToNetduino setTitle:@"Disconnect from Netduino" forState:UIControlStateNormal];
    btnConnectToNetduino.enabled = true;
    [self.aiBusy stopAnimating];
    mode = MODE_CONNECTED;
}

//
//  Set up the interface to show that the Bluetooth module is disconnected
//
- (void) Disconnected
{
    [lblStatusMessage setText:@"Disconnected"];
    lblLEDStatus.hidden = true;
    swLED.hidden = true;
    [btnConnectToNetduino setTitle:@"Connect to Netduino" forState:UIControlStateNormal];
    btnConnectToNetduino.enabled = true;
    [self.aiBusy stopAnimating];
    mode = MODE_DISCONNECTED;
}

Standard Methods (viewDidLoad and didReceiveMemoryWarning)

These methods are generated by default when the application is first created. For viewDidLoad we will set up the application for initial use. We will not be modifying the didReceiveMemoryWarning method in this application.

//
//  Setup the view.
//
- (void) viewDidLoad
{
    [super viewDidLoad];
    //
    //  Setup the display.
    //
    lblLEDStatus.hidden = true;
    [swLED setOn:NO];
    swLED.hidden = true;
    [lblStatusMessage setText:@"Ready"];
    self.aiBusy.hidesWhenStopped = true;
    //
    //  Create the Bluetooth objects.
    //
    ble = [[BLE alloc] init];
    [ble controlSetup:1];
    ble.delegate = self;
    //
    //  Setp the simple properties.
    //
    mode = MODE_DISCONNECTED;
}

//
//  Process the memory warning event.
//
- (void) didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
}

The main point to note in the viewDidLoad method is that the Bluetooth object is initialised.

Button Press Event

The btnConnect_TouchUpInside event initiates the connection/disconnection of the application to/from the Bluetooth module.

//
//  User has pressed the Connect button so try to connect/disconnect from the Netduino.
//
- (IBAction) btnConnect_TouchUpInside:(UIButton *) sender
{
    if (ble.activePeripheral)
    {
        if (ble.activePeripheral.isConnected)
        {
            [[ble CM] cancelPeripheralConnection:[ble activePeripheral]];
        }
    }
    ble.peripherals = nil;
    switch (mode)
    {
        case MODE_DISCONNECTED:
            [lblStatusMessage setText:@"Connecting..."];
            [NSTimer scheduledTimerWithTimeInterval:(float) TIMEOUT_BUSY_CONNECTING target:self selector:@selector(connectionTimer:) userInfo:nil repeats:NO];
            [ble findBLEPeripherals:TIMEOUT_BLE_TIMEOUT];
            [self.aiBusy startAnimating];
            mode = MODE_CONNECTING;
            break;
        case MODE_CONNECTED:
            [lblStatusMessage setText:@"Disconnecting..."];
            [self.aiBusy startAnimating];
            mode = MODE_DISCONNECTING;
            break;
        default:
            break;
    }
    btnConnectToNetduino.enabled = false;
}

Timer

The btnConnect_TouchUpInside event starts a timer when it is trying to connect to a Bluetooth module. This timer prevents the application from locking when searching for a Bluetooth module which does not exist. The timer callback stops the search process after 3 seconds:

//
//  The timer started by the Connect button has triggered.  See if we have any
//  Bluetooth modules nearby.  If we have then connect to the first one in the
//  list.
//
-(void) connectionTimer:(NSTimer *) timer
{
    if (ble.peripherals.count > 0)
    {
        [ble connectPeripheral:[ble.peripherals objectAtIndex:0]];
    }
    else
    {
        [lblStatusMessage setText:@"Cannot find Netduino"];
        mode = MODE_DISCONNECTED;
    }
    [self.aiBusy stopAnimating];
}

Switching the LED on/off

The application assumes only one LED is connected to the Netduino. The data packet sent to the Netduino therefore contains a 1 or 0 to indicate the status of the LED:

//
//  User has changed the value of the LED.
//
- (IBAction) swLED_Changed:(UISwitch *) sender
{
    UInt8 buffer[1];
    buffer[0] = sender.isOn == YES ? 1 : 0;
    NSData *data = [[NSData alloc] initWithBytes:buffer length:1];
    [ble write:data];
}

Bluetooth Module Events

The final two events are generated by the Bluetooth library. These are fired when the module connects/disconnects to/from the module:

//
//  Connected to a Bluetooth module successfully.
//
-(void) bleDidConnect
{
    [self Connected];
}

//
//  Application has disconnected from a Bluetooth module.
//
- (void) bleDidDisconnect
{
    [self Disconnected];
}

//
//  RSSI has been updated.
//
- (void) bleDidDisconnect:(NSNumber *) rssi
{
}

Netduino Application

The final task is to modify the Netduino Application to process the data. An OutputPort is added to the application and this is connected to a digital IO pin. This pin is then turned on/off when serial data is received from the Bluetooth module:

public class Program
{
    const int BUFFER_SIZE = 1024;
    private static OutputPort led = new OutputPort(Pins.GPIO_PIN_D8, false);

    public static void Main()
    {
        SerialPort sp = new SerialPort("COM1", 57600, Parity.None, 8, StopBits.One);
        sp.DataReceived += sp_DataReceived;
        sp.Open();
        Thread.Sleep(Timeout.Infinite);
    }

    static void sp_DataReceived(object sender, SerialDataReceivedEventArgs e)
    {
        string str;
        str = "";
        if (e.EventType == SerialData.Chars)
        {
            int amount;
            byte[] buffer;

            buffer = new byte[BUFFER_SIZE];
            amount = ((SerialPort) sender).Read(buffer, 0, BUFFER_SIZE);
            if (amount > 0)
            {
                for (int index = 0; index < amount; index++)
                {
                    str += buffer[index].ToString();
                    str += "";
                }
                if (buffer[0] == 1)
                {
                    led.Write(true);
                }
                else
                {
                    led.Write(false);
                }
                Debug.Print("Data: " + str);
            }
        }
    }
}

Does it Work?

Well of course it does and here’s the video to prove it:

First we connect to the module. Once connected we can start to turn the LED on and off using the iPhone.

Conclusion

The library provided by RedBear is a great starting point for using the iPhone to communicate with a BLE module. It is simple and easy to use for this type of application. This whole experiment only took a few hours to put together an execute.

Let’s see where this takes us 🙂

Infra-red Transmitter and Receiver

Sunday, August 18th, 2013

In this post we will use Mr Maxwell’s new fangled electrons to generate invisible light and transmit a signal through the luminiferous aether.

Or to put it another way, I’m learning how to use IR remote controls in microcontroller circuits. The problem with using infra-red LEDs is that you cannot really see if they are on or off. You can see if a signal is applied to the LED but this does not mean it’s generating an infra-red signal. Let’s admit it, we’ve all burnt out an LED or two in our time. So in this post I’m going to put together a couple of circuits to generate and detect infra-red signals.

Detecting IR Signals

For this circuit I am going to be using the L-7113P3C phototransistor. This component looks like an LED but is in fact a transistor:

Photo Transistor

Photo Transistor

The phototransistor has two conventional connections which allow the collector and emitter to be connected to the electrical circuit but the third connector (the base) has been made sensitive to infra-red light and then exposed via a lens. The transistor becomes conductive when infra-red light hits the photosensitive base of the transistor.

The schematic for the basic detector circuit is as follows:

IR Receiver Schematic

IR Receiver Schematic

Should be simple enough. Out comes the breadboard, oscilloscope and a couple of remote controls I have to hand.

Simple IR Receiver

Simple IR Receiver

Connecting the Vout to the oscilloscope and pointing a WD TV remote control at the phototransistor I saw the following trace:

WD TV Remote Signal

WD TV Remote Signal

So, it looks like the detector circuit is detecting the infra-red signal from these remote controls.

Infra-red Transmitter

The transmitter circuit is simply an infra-red LED with visible LED replaced by an infra-red LED. For this circuit I used the TSUS5400 as this is matched with the L-7113P3C phototransistor I used in the detector circuit. Dropping this into a simple LED/transistor circuit gives the following schematic:

IR Transmitter Schematic

IR Transmitter Schematic


R2 was selected to allow 20mA of current through the infra-red LED (IRLED). The resistor on the base of the transistor should allow the transistor to become saturated. Dropping this on breadboard gives a circuit which looks like this:

IR Transmitter and Receiver

IR Transmitter and Receiver

The top part of the breadboard contains the transmitter circuit (Blue LED) and the lower part of the breadboard contains the receiver.

Testing

To test the circuit a 1KHz signal generator was connected to the base of the transmitter circuit. Channel 1 (yellow) of the oscilloscope is connected to Vout of the receiver with channel 2 (blue) connected to the signal generator. Turning on the signal generator produced the following output on the oscilloscope:

1 KHz Signal

1 KHz Signal

Looking good. The signals show a good match.

Conclusion

The main aim of this exercise was to create an infra-red receiver circuit which can be used to verify that an equivalent transmitter circuit is working. The receiver circuit gives a value for Vout of 3.12V. This is an acceptable value for logic 1 for the microcontrollers which I am using. Their nominal input value for logic 1 is the supply voltage +/- 0.3V.

The transmitter circuit should allow the use of the same microcontroller to generate a signal which can be used to control a device such as a TV etc.

Transmitting Data Using the STM8S SPI Master Mode

Sunday, June 23rd, 2013

So far in The Way of the Register series we have only looked at SPI from a slave device point of view as we have been working towards creating a Netduino GO! module. For every slave device there must be a master, here we will look at configuring the STM8S to operate in SPI master mode.

The project will look at controlling a TLC5940 in order to emulate the work described in the post TLC5940 16 Channel PWM Driver. We could simply bit-bang the data out to the chip but instead we will use the SPI interface to achieve this.

The project breaks down into the following steps:

  1. Generate the grey scale clock and blank signals
  2. Bit-Bang data out over GPIO pins to create an operational circuit
  3. Convert the data transmission to SPI

See the quoted post for a description of how this chip works and for an explanation of the terminology used.

Generating the Grey Scale Clock and Blank Signals

The TLC5940 generated 4,096 grey scale values by using a PWM counter. Once the counter reaches 4096 pulses it stops until it is told to restart. The Blank pulse acts as a restart signal. This project will be controlling LEDs and so will want to continuously keep the counter running. If we did not keep the counter in the TLC5940 running then the LEDs would light for a short while and then simply turn off and remain off.

The greyscale clock is generated by using the Configurable Clock Output (CCO) pin on the STM8S. This pin simply outputs the clock pulses used to drive the STM8S. Reviewing the data sheet we find that the maximum value for the grey scale clock is 30MHz. Using out standard clock initialisation generates a clock with a frequency of 16MHz (approximately). This is well within the tolerances of the TLC5940. To output this we need to make a simple modification to our standard code, name change the line:

CLK_CCOR = 0;   //  Turn off CCO.

to:

CLK_CCOR = 1;   //  Turn on CCO.

The starting point for our application becomes:

#if defined DISCOVERY
    #include <iostm8S105c6.h>
#elif defined PROTOMODULE
    #include <iostm8s103k3.h>
#else
    #include <iostm8s103f3.h>
#endif
#include <intrinsics.h>

//
//  Setup the system clock to run at 16MHz using the internal oscillator.
//
void InitialiseSystemClock()
{
    CLK_ICKR = 0;                       //  Reset the Internal Clock Register.
    CLK_ICKR_HSIEN = 1;                 //  Enable the HSI.
    CLK_ECKR = 0;                       //  Disable the external clock.
    while (CLK_ICKR_HSIRDY == 0);       //  Wait for the HSI to be ready for use.
    CLK_CKDIVR = 0;                     //  Ensure the clocks are running at full speed.
    CLK_PCKENR1 = 0xff;                 //  Enable all peripheral clocks.
    CLK_PCKENR2 = 0xff;                 //  Ditto.
    CLK_CCOR = 1;                       //  Turn on CCO.
    CLK_HSITRIMR = 0;                   //  Turn off any HSIU trimming.
    CLK_SWIMCCR = 0;                    //  Set SWIM to run at clock / 2.
    CLK_SWR = 0xe1;                     //  Use HSI as the clock source.
    CLK_SWCR = 0;                       //  Reset the clock switch control register.
    CLK_SWCR_SWEN = 1;                  //  Enable switching.
    while (CLK_SWCR_SWBSY != 0);        //  Pause while the clock switch is busy.
}

//
//  Main program loop.
//
void main()
{
    //
    //  Initialise the system.
    //
    __disable_interrupt();
    InitialiseSystemClock();
    __enable_interrupt();
    while (1)
    {
        __wait_for_interrupt();
    }
}

Wiring up the STM8S and connecting the scope to PC4 (CCO output pin) gives the following trace on the scope:

CCO On Scope

CCO On Scope

The trace on the scope has a minimum value of around 680mV and a maximum of 2.48V. In an ideal world this signal should range from 0 to 3.3V (based upon a 3.3V supply). Adding an inverter from a 74HC04 and feeding the signal through one of the gates gives the following trace:

Invertor output on the scope

Inverter output on the scope

This is starting to look a lot better. The next task is to create the Blank signal. There are several ways of doing this. The most automatic way of doing this is to generate a very short PWM pulse using one of the timers in the STM8S. One drawback of this method is that it is more difficult to generate a Blank pulse on demand. Instead we will use the interrupt method described in the same article. Whilst not automatic it is still a trivial task to complete. We simply modify the code from the method to load the counters with 4,096. The code for the GPIO port, timer and interrupt becomes:

//
//  Timer 2 Overflow handler.
//
#pragma vector = TIM2_OVR_UIF_vector
__interrupt void TIM2_UPD_OVF_IRQHandler(void)
{
    PD_ODR_ODR4 = 1;
    PD_ODR_ODR4 = 0;
    TIM2_SR1_UIF = 0;       //  Reset the interrupt otherwise it will fire again straight away.
}

//
//  Setup Timer 2 to generate an interrupt every 4096 clock ticks.
//
void SetupTimer2()
{
    TIM2_PSCR = 0x00;       //  Prescaler = 1.
    TIM2_ARRH = 0x10;       //  High byte of 4096.
    TIM2_ARRL = 0x00;       //  Low byte of 4096.
    TIM2_IER_UIE = 1;       //  Turn on the interrupts.
    TIM2_CR1_CEN = 1;       //  Finally enable the timer.
}

//
//  Setup the output ports used to control the TLC5940.
//
void SetupOutputPorts()
{
    PD_ODR = 0;             //  All pins are turned off.
    PD_DDR_DDR4 = 1;        //  Port D, pin 4 is used for the Blank signal.
    PD_CR1_C14 = 1;         //  Port D, pin 4 is Push-Pull
    PD_CR2_C24 = 1;         //  Port D, Pin 4 is generating a pulse under 2 MHz.
}

Hooking up the scope to PD4 gives the following trace:

Blanking pulses

Blanking pulses

The single pulses are being generated at a frequency of approximately 3.9kHz. A little mental arithmetic dividing the 16MHz clock by 4,096 comes out to about 3,900.

Zooming in on the signal we see:

Single Blanking Pulse

Single Blanking Pulse

This shows that the signal is 125nS wide. This is acceptable as the minimum pulse width given in the data sheet is 20nS.

So at this point we have the 16MHz grey scale clock signal and the Blank pulse being generated every 4,096 clock pulses.

Connecting the TLC5940

The next task is to connect the STM8S to the TLC5940. You should refer to the article TLC5940 16 Channel PWM Driver for more information on the pins and their meaning. For this exercise we will use the following mapping:

STM8S PinTLC5940 Pin
PD4Blank (pin 23)
PD3XLAT (pin 24)
PD2VPRG (pin 27)
PD5Serial data (pin 26)
PD6Serial clock (pin 25)
PC4 (via inverter)GSCLK (pin 18)

You will note that the serial data and clock are currently connected to PD5 and PD6 respectively. Whilst the eventual aim is to communicate with the TLC5940 via SPI, the initial communication will be using Bit-Banging. We will move on to using SPI once the operation of the circuit has been proven using tested technology.

The first changes we will have to make create some #define statements to make the code a little more readable. We also add some storage space for the grey scale and dot correction data.

//
//  Define which pins on Port D will be used as control signals for the TLC5940.
//
//  BLANK - A pulse from low to high causes the TLC5940 to restart the counter
//  XLAT - A high pulse causes the data to be transferred into the DC or GS registers.
//  VPRG - Determines which registers are being programmed, High = DC, Low = GS.
//
#define PIN_BLANK                   PD_ODR_ODR4
#define PIN_XLAT                    PD_ODR_ODR3
#define PIN_VPRG                    PD_ODR_ODR2
//
//  Bit bang pins.
//
#define PIN_BB_DATA                 PD_ODR_ODR5
#define PIN_BB_CLK                  PD_ODR_ODR6
//
//  Values representing the modes for the VPRG pin.
//
#define PROGRAMME_DC                1
#define PROGRAMME_GS                0

//
//  TLC5940 related definitions.
//
#define TLC_NUMBER                  1
#define TLC_DC_BYTES_PER_CHIP       12
#define TLC_DC_BYTES                TLC_NUMBER * TLC_DC_BYTES_PER_CHIP
#define TLC_GS_BYTES_PER_CHIP       24
#define TLC_GS_BYTES                TLC_NUMBER * TLC_GS_BYTES_PER_CHIP

//
//  Next we need somewhere to hold the data.
//
unsigned char _greyScaleData[TLC_GS_BYTES];
unsigned char _dotCorrectionData[TLC_DC_BYTES];

The Bit-Banging methods should look familiar to anyone who has been reading any of the posts in The Way of the Register series.

//--------------------------------------------------------------------------------
//
//  Bit bang data.
//
//  TLC5940 expects the data to be shifted MSB first.  The data
//  is shifted in on the rising edge of the clock.
//
void BitBang(unsigned char byte)
{
    for (short bit = 7; bit >= 0; bit--)
    {
        if (byte &amp; (1 << bit))
        {
            PIN_BB_DATA = 1;
        }
        else
        {
            PIN_BB_DATA = 0;
        }
        PIN_BB_CLK = 1;
        PIN_BB_CLK = 0;
    }
    PIN_BB_DATA = 0;
}

//--------------------------------------------------------------------------------
//
//  Bit bang a buffer of data.
//
void BitBangBuffer(unsigned char *buffer, int size)
{
    for (int index = 0; index < size; index++)
    {
        BitBang(buffer[index]);
    }
}

Related to the Bit-Banging methods are the two methods which will send the grey scale and dot correction data:

//--------------------------------------------------------------------------------
//
//  Send the grey scale data to the TLC5940.
//
void SendGreyScaleData(unsigned char *buffer, int length)
{
    PIN_VPRG = PROGRAMME_GS;
    BitBangBuffer(buffer, length);
    PulseXLAT();
    PulseBlank();
}

//--------------------------------------------------------------------------------
//
//  Send the dot correction buffer to the TLC5940.
//
void SendDotCorrectionData(unsigned char *buffer, int length)
{
    PIN_VPRG = PROGRAMME_DC;
    BitBangBuffer(buffer, length);
    PulseXLAT();
    PulseBlank();
}

We also need a few methods to make the TLC5940 latch the data and also restart the counters:

//--------------------------------------------------------------------------------
//
//  Pulse the Blank pin in order to make the TLC5940 reload the counters and
//  restart timer.
//
void PulseBlank()
{
    PIN_BLANK = 1;
    PIN_BLANK = 0;
}

//--------------------------------------------------------------------------------
//
//  Pulse the XLAT pin in order to make the TLC5940 transfer the
//  data from the latches into the appropriate registers.
//
void PulseXLAT()
{
    PIN_XLAT = 1;
    PIN_XLAT = 0;
}

Next we need to set the initial condition. For this we set the TLC dot correction off and also turn all of the LEDs off:

//--------------------------------------------------------------------------------
//
//  Initialise the TLC5940.
//
void InitialiseTLC5940()
{
    for (int index = 0; index < TLC_DC_BYTES; index++)
    {
        _dotCorrectionData[index] = 0xff;
    }
    for (int index = 0; index < TLC_GS_BYTES; index++)
    {
        _greyScaleData[index] = 0;
    }
    SendDotCorrectionData(_dotCorrectionData, TLC_DC_BYTES);
    SendGreyScaleData(_greyScaleData, TLC_GS_BYTES);
}

The final support method we need to add is the method which sets the brightness of an LED. The brightness is a 12-bit value (0-4095). This means each LED uses 1.5 bytes for the brightness value. The following methods breaks down the value and ensures that the correct bits are set in the grey scale buffer depending upon which LED is being changed:

//--------------------------------------------------------------------------------
//
//  Set the brightness of an LED.
//
void SetLEDBrightness(int ledNumber, unsigned short brightness)
{
    int offset = (ledNumber >> 1) * 3;
    if (ledNumber &amp; 0x01)
    {
        _greyScaleData[offset + 1] = (unsigned char) (_greyScaleData[offset + 1] &amp; 0xf0) | ((brightness & 0x0f00) >> 8);
        _greyScaleData[offset + 2] = (unsigned char) (brightness & 0xff);
    }
    else
    {
        _greyScaleData[offset] = (unsigned char) ((brightness &amp; 0x0ff0) >> 4) &amp; 0xff;
        _greyScaleData[offset + 1] = (unsigned char) ((brightness & 0x0f) >> 4) | (_greyScaleData[offset + 1] & 0x0f);
    }
}

We should also create a similar method for changing the dot correction value for an LED. This is left as an exercise for the reader as we will not be changing this value in this code.

Proving the concept

If we have connected the TLC5940 correctly and our code works we should be able to connect up some LEDs (common anode) to the TLC5940 and change the brightness under program control.

This main program loop slowly increases the brightness of the LEDs. When they are at full brightness they are turned off and the process starts again:

//--------------------------------------------------------------------------------
//
//  Main program loop.
//
void main()
{
    //
    //  Initialise the system.
    //
    __disable_interrupt();
    InitialiseSystemClock();
    SetupOutputPorts();
    SetupTimer2();
    
    InitialiseTLC5940();
    __enable_interrupt();
    //
    //  Main program loop.
    //
    int brightness = 0;
    int counter = 0;
    while (1)
    {
        __wait_for_interrupt();
        counter++;
        if (counter == 20)
        {
            TIM2_CR1_CEN = 0;
            counter = 0;
            for (int index = 0; index < 16; index++)
            {
                SetLEDBrightness(index, brightness);
            }
            SendGreyScaleData(_greyScaleData, TLC_GS_BYTES);
            brightness++;
            if (brightness == 4096)
            {
                brightness = 0;
            }
            TIM2_CR1_CEN = 1;       //  Finally re-enable the timer.
        }
    }
}

If you connect a scope to the cathode of one of the LEDs you will see that the wave form slowly changes over time. At the start, the LED is fully on and the trace on the scope shows a horizontal line, i.e. a constant voltage. As time moves on and the value in the dot correction buffer changes you start to see a PWM signal similar to the following:

PWM Output On Scope 1

PWM Output On Scope 1

This trace shows the signal when the LEDs are a little brighter:

PWM Output On Scope 2

PWM Output On Scope 2

Having arrived here we now know that the circuit has been connected correctly and that the control logic in the main method works. We can now move on to considering what we need to do in order to use SPI in master mode. The aim will be to simply remove the Bit-Banging methods and replace these with an interrupt driven SPI master algorithm.

SPI Master

So now we have a working circuit we need to look at SPI on the STM8S. Firstly let’s remind ourselves of the serial communication parameters for the TLC5940. This chip reads the data on the leading clock edge (CPHA = 1). We have also set the clock idle state to low (CPOL = 0).

It is also advisable to start off using the lowest clock speed for SPI in order to confirm correct operation of the software and circuit. Lower speed are less likely to be subject to interference.

SPI Registers

You should review the previous articles on SPI communication if you are not already familiar with the SPI registers we have used so far. In this post we will only discuss the new settings required to switch from being a SPI slave device to a SPI master device.

SPI_CR1_BR – Baud Rate Control

The SPI baud rate is determined by the master clock frequency and the value in this register. The divisor used to set the baud rate according to the following table:

SPI_CR1_BRDivisor
0002
0014
0108
01116
10032
10164
110128
111256

The SPI baud rate is calculated as fmaster / divisor. So for our master clock speed of 16MHz we get the lowest clock speed of 16,000,000 / 256, or 62,500Hz.

SPI_CR1_MSTR – Master Selection

Setting this bit switches SPI into master mode (see also SPI_CR1_SPE).

Note that the reference for the STM8S also states that this bit (and SPI_CR1_SPE) will only remain set whilst NSS is high. It this therefore essential to connect NSS to Vcc if this device is not being used as a slave device.

Implementing SPI

Using SPI presents us with a small problem, namely the program will have to start to operate in a more asynchronous way. The code presented so far has only one interrupt to be concerned with, namely the timer used to control the Blank signal. Adding SPI to the mix means that we will have to also consider the SPI interrupt as well. It also adds the complication of sending dot correction data followed by grey scale data. This last problem will not be covered here and is left as an exercise for the reader.

The initialisation code merely sets things up for us:

//--------------------------------------------------------------------------------
//
//  Initialise SPI to be SPI master.
//
void SetupSPIAsMaster()
{
    SPI_CR1_SPE = 0;                    //  Disable SPI.
    SPI_CR1_CPOL = 0;                   //  Clock is low when idle.
    SPI_CR1_CPHA = 0;                   //  Capture MSB on first edge.
    SPI_ICR_TXIE = 1;                   //  Enable the SPI TXE interrupt.
    SPI_CR1_BR = 7;                     //  fmaster / 256 (62,500 baud).
    SPI_CR1_MSTR = 1;                   //  Master device.
}

Much of the code should be familiar as it has been used in previous posts discussing SPI slave devices. Not however that we do not enable SPI at this point. We simply set the scene for us to use SPI later.

The SPI data transfers will be controlled by using an interrupt service routine:

//--------------------------------------------------------------------------------
//
//  SPI Interrupt service routine.
//
#pragma vector = SPI_TXE_vector
__interrupt void SPI_IRQHandler(void)
{
    //
    //  Check for an overflow error.
    //
    if (SPI_SR_OVR)
    {
        (void) SPI_DR;                      // These two reads clear the overflow
        (void) SPI_SR;                      // error.
        return;
    }
    if (SPI_SR_TXE)
    {
        //
        //  Check if we have more data to send.
        //
        if (_txBufferIndex == _txBufferSize)
        {
            while (SPI_SR_BSY);
            SPI_CR1_SPE = 0;
            _txBuffer = 0;
            PulseXLAT();
            PulseBlank();
            TIM2_CR1_CEN = 1;
        }
        else
        {
            SPI_DR = _txBuffer[_txBufferIndex++];
        }
    }
}

The main works starts when we have established that the transmit buffer is empty (SPI_SR_TXE is set). If we have more data then we put the byte into the data register (SPI_DR). If we have transmitted all the data we have then we wait for the last byte to complete transmission (SPI_SR_BSY becomes false) before we start to terminate the end of the SPI communication.

In order to send some data we really just need to setup the pointers and counters correctly and then enable SPI. So the SendGreyScaleData method becomes:

//--------------------------------------------------------------------------------
//
//  Send the grey scale data to the TLC5940.
//
void SendGreyScaleData(unsigned char *buffer, int length)
{
    PIN_VPRG = PROGRAMME_GS;
    _txBuffer = buffer;
    _txBufferIndex = 0;
    _txBufferSize = length;
    TIM2_CR1_CEN = 0;
    SPI_CR1_SPE = 1;
}

We also need to have a look at the main program loop as we use the __wait_for_interrupt() method in order to determine when we should start to process the next LED brightness value. We now need to ignore the interrupts when SPI is enabled otherwise the brightness will increase each time the transmit buffer is empty. A crude implementation eliminating the SPI interrupts is:

int brightness = 0;
int counter = 0;
while (1)
{
    __wait_for_interrupt();
    if (!SPI_CR1_SPE)
    {
        counter++;
        if (counter == 20)
        {
            TIM2_CR1_CEN = 0;
            counter = 0;
            for (int index = 0; index < 16; index++)
            {
                SetLEDBrightness(index, brightness);
            }
            SendGreyScaleData(_greyScaleData, TLC_GS_BYTES);
            brightness++;
            if (brightness == 4096)
            {
                brightness = 0;
            }
            TIM2_CR1_CEN = 1;       //  Finally re-enable the timer.
        }
    }
}

Making these changes and running the code shows that the system operated as before.

Increasing the Baud Rate

As noted earlier, the baud rate has been set low in order to reduce the chance of any problems being experienced due to interference. Now we have established that using SPI communication is possible and the circuit works as before we can start to increase the baud rate. Using our 16MHz clock we find we have the following baud rates which are theoretically possible:

SPI_CR1_BRDivisorSPI Frequency
00028 MHz
00144 MHz
01082 MHz
011161 MHz
10032500 KHz
10164250 KHz
110128125 KHz
11125662.5 KHz

A little experimentation is called for. Being ambitious I started with a clock frequency of 1MHz. This resulted in a flickering effect on the LED display. So, 1MHz is too ambitious, let’s start to reduce the frequency. I finally settled on 250 KHz as this allowed the circuit to function as before.

Conclusion

Using SPI master for data transmission was not as difficult as I originally thought. To make this application complete there are a few tasks to follow up on, namely:

  1. Receiving data over SPI
  2. Create the method to allow setting the dot correction values
  3. Transmitting buffers from a queue
  4. Minor tidying up of the timer control

The use of SPI here actually increased the time taken (597uS Bit-Banging c.f. 795uS for 250 KHz SPI) to reliably send the grey scale data to the TLC5940. I suspect that the time can be decreased if the circuit was taken from breadboard and put onto a PCB manufactured for the purpose. The breadboard for this circuit currently looks like this:

Bread Board And Flying Leads

Bread Board And Flying Leads

As you can see, there is a lot of opportunity for interference with all those flying leads.

While the time taken might have increased, the load on the microcontroller will have decreased as the SPI method is interrupt driven. The actual transmission is off-loaded to the microcontrollers dedicated circuitry.

As usual, the source code for this project is available to download.

Making a Netduino GO! Module – Stage 6 – Assembling the Prototype

Monday, May 6th, 2013

A few days ago I received a package from China, namely my Output Expander prototype boards:

Bubble Wrapped Boards From China

Bubble Wrapped Boards From China

Could not wait to unwrap them:

OutputExpander Bare Boards

OutputExpander Bare Boards

Only one thing left to do, start assembling them. As with all projects this will be broken down into steps:

  • Add the STM8S microcontroller and test
  • Add one 74HC595 shift register and test
  • Complete the board and add connectors and of course, test
  • By using a modular approach it should be easy to detect a problem with the design or assembly.

    Component List

    The board requires the following components:

    ComponentValueQuantity
    STM8S103F3NA1
    IDC socket1.27″ pitch1
    Sr1, SR2, Sr3, SR4SOL / SOP164
    C21uF0403
    C1, C3, C4, C5, C6, C7100nF – 04036
    Connectors0.1″Misc

    When these arrive be prepared, they are small!

    Adding the Microcontroller

    The board will need a microcontroller and some way of programming it. The logical first task is to add the controller, socket and the supporting passive components. Doing this will allow us to programme the controller with the firmware. As a test we can connect the programmed board to the Netduino Go!. If the connections between the board and the Netduino GO! are correct then the blue LED on the socket on the Netduino Go! should light.

    If you are attempting to follow this series and you are making your own board then I recommend you browse the net and have a look for videos on soldering SMD components. I found the tutorials on drag soldering really useful.

    Out with the soldering iron, a magnifier (it was needed). One thing I noticed was the difference between the 74HC595 pads and the pads for the STM8S. The 74HC595 component used was a built in component whilst the STM8S was a component I had created. The most noticeable difference between the two parts was the size of the pads on the PCB compared to the size of the component. The 74HC595 pads were elongated. These make soldering easier.

    STM8SPadsShiftRegisterPads
    STM8S75HC595

    Although the pins on the STM8S are only 0.65mm pitch, soldering is not as difficult as it first appears. A quick first attempt gave the following:

    STM8S Mounted On Board

    STM8S Mounted On Board

    There is only one item of concern and that is the whisker of solder between the fourth and fifth pins down on the right hand side of the image. This was quickly tidied up by dragging the soldering iron between the two pins.

    Next task was to add the passives which supported the STM8S leaving the passives for the shift registers for later. This is where you get some idea of the difference between the size of the components vs the size of the tools you are using:

    Capacitor and Tools

    Capacitor and Tools

    At this point I realised that an 0403 (metric sizing) component is 0.4mm x 0.3mm and the smallest soldering iron bit I has was about 1.5mm. Not to worry, the pads on the board are a reasonable size, simply tin the pads and then slide the capacitor into the molten solder.

    The next job was to add the socket for the GoBus. The sockets are surface mounted 1.27″ pitch IDC sockets. I found the easiest way to add these was to tin one pad and then slide the socket into place. The remaining pads could be soldered by placing the solder at the end of the connector and then applying heat and letting the solder run under the socket. It’s not as difficult as it sounds.

    At this point, the microcontroller should be in place with enough supporting hardware to allow it to be programmed. This was achieved by connecting the ST-Link/V2 programmer to the prototype board using the Komodex Breakout Board. The firmware developed in the previous posts was loaded into the development environment and deployed top the microcontroller.

    Programming the STM8S

    Programming the STM8S

    No deployment errors!

    A good indication that the microcontroller and the supporting hardware are functioning correctly.

    Add a Shift Register

    Next step is to add a single shift register and see if we get some output. Soldering the shift registers was a lot simpler than the STM8S as the pin pitch was greater. These could be soldered more conventionally although the pitch was finer than you may be used to if you have only worked with PTH components.

    Connecting the module to the Netduino GO! acts as a quick check:

    Netduino Go! Connected to OutputExpander

    Netduino Go! Connected to OutputExpander

    The blue LED lights up – the Netduino GO! recognises the OutputExpander as a valid module.

    Adding the single register worked and so the next task is to add the remaining registers and connectors.

    But All Was Not Well…

    During the assembly and testing process I had managed to accidentally short a few pins on the shift registers. This resulted in no output from the OutputExpander module. Breaking out the scope and the logic analyser proved that something was very wrong. The following trace shows the problem:

    Original Output From the OutputExpander

    Original Output From the OutputExpander

    It appears that the latch and clear lines were being triggered at the same time. I was able to establish by disconnecting the module from the circuit that there was not short between the two lines. Something else must be going on. Some further digging into the output from the logic analyser showed that the clear signal was being triggered slightly before the latch signal and that the latch was being released slightly after the clear signal. As a result I would expect no output from the shift registers – this is what I was seeing.

    Not wanting to waste money on components I continued to check the circuit but could not find anything else obviously wrong with the soldering or the software.

    Only one remaining option. Try a putting together a new board. Back to step one.

    A New Board

    Building the new board was a lot quicker than the first. Following the same procedure (one step at a time and test all the way) produced a new board:

    Before testing the board there was a final modification to make. This time to the software. The board has the outputs labelled from left to right with the lower bits being to the right of the board. The prototype module had the shift registers ordered from right to left. A quick change to the C code on the STM8S soon resolved this problem:

    //--------------------------------------------------------------------------------
    //
    //  GO! function 2 - Output the specified bytes to the shift registers.
    //  Tx buffer.
    //
    void SetShiftRegisters()
    {
        for (int index = 0; index < _numberOfShiftRegisters; index++)
        {
            _registers[index] = _rxBuffer[5 - index];
        }
        OutputData();
        NotifyGOBoard();
    }
    

    A small bug had also been noticed in the clock configuration method. The code stated that CCO was turned off but the code actually turned it on. The code should read:

    //--------------------------------------------------------------------------------
    //
    //  Setup the system clock to run at 16MHz using the internal oscillator.
    //
    void InitialiseSystemClock()
    {
        CLK_ICKR = 0;                       //  Reset the Internal Clock Register.
        CLK_ICKR_HSIEN = 1;                 //  Enable the HSI.
        CLK_ECKR = 0;                       //  Disable the external clock.
        while (CLK_ICKR_HSIRDY == 0);       //  Wait for the HSI to be ready for use.
        CLK_CKDIVR = 0;                     //  Ensure the clocks are running at full speed.
        CLK_PCKENR1 = 0xff;                 //  Enable all peripheral clocks.
        CLK_PCKENR2 = 0xff;                 //  Ditto.
        CLK_CCOR = 0;                       //  Turn off CCO.
        CLK_HSITRIMR = 0;                   //  Turn off any HSIU trimming.
        CLK_SWIMCCR = 0;                    //  Set SWIM to run at clock / 2.
        CLK_SWR = 0xe1;                     //  Use HSI as the clock source.
        CLK_SWCR = 0;                       //  Reset the clock switch control register.
        CLK_SWCR_SWEN = 1;                  //  Enable switching.
        while (CLK_SWCR_SWBSY != 0);        //  Pause while the clock switch is busy.
    }
    

    Testing

    The step by step testing process had shown that a single shift register worked, now to prove that four worked. Now it was time to add some more and connect some LEDs:

    Netduino Go OutputExpander and Some LEDs

    Netduino Go OutputExpander and Some LEDs

    And here’s a video of it working:

    Conclusion

    Assembly was not as difficult as it first appears even considering the small size of the components. In fact the STM8S was programmed first time.

    One piece of equipment I did find invaluable was a cheap USB microscope. These don’t give a high resolution image but they do allow you to zoom in on the board and check for problems.

    One final post left – time to reflect on the process.

Making a Netduino GO! Module – Stage 2 – Connect the GO! to the Breadboard

Friday, April 12th, 2013

In the previous post a prototype for the multiple digital outputs was developed and implemented on breadboard. The STM8S code was independent and demonstrated that two chained 74HC595 shift registers could be controlled by bit-banging data through GPIO pins on the STM8S.

In this post we will continue the development of the module by merging the code from the previous post with the STM8S module code from the post STM8S SPI Slave (Part 3) – Making a Go Module. We will develop both the STM8S code and the NETMF driver to implement the following functionality:

  • Clear the shift registers
  • Set the outputs of the two shift registers

These two functions are already in the standalone version of the STM8S code and it should be a relatively simple exercise to merge this functionality with the Netduino Go Bus communication protocol.

STM8S Application

The first task to be completed for the STM8S code is to create a new project and add the code from the post STM8S SPI Slave (Part 3) – Making a Go Module. The two functions should then be removed and the two new ones we are implementing should be added.

Start a New Project

Start a new STM8S project and save the project into a new directory. Now take the source code in main.c from the project STM8S SPI Slave (Part 3) – Making a Go Module and paste over the code in you new project. Ensure that the project is targeting the correct microcontroller and save the project.

Alternatively, copy the project code from STM8S SPI Slave (Part 3) – Making a Go Module into a new directory.

Remove Dummy Functionality

The project code in the STM8S module post contained a couple of dummy functions illustrating the concept of module functionality and communications. This code should be removed and the application stripped down to the basics required for SPI communication with the Netduino Go!.

Add New Functionality

The final task is to add the code which implements the new functionality as defined for the basic module (i.e. clear the shift registers and set the outputs of the shift registers).

The first thing which is required is to add the #define statements to support the bit-banging:

//--------------------------------------------------------------------------------
//
//  Pins which we will be using for output the data to the shift registers.
//
#define SR_CLOCK            PD_ODR_ODR3
#define SR_DATA             PD_ODR_ODR2
#define SR_CLEAR            PC_ODR_ODR3
#define SR_OUTPUT_ENABLE    PC_ODR_ODR4
#define SR_LATCH            PD_ODR_ODR4

The InitialisePorts method will also need to be modified in order to ensure that the ports above are all setup correctly. We need these to be output ports capable of running at up to 10MHz. The code becomes:

//--------------------------------------------------------------------------------
//
//  Initialise the ports.
//
void InitialisePorts()
{
    //
    //  Initialise Port D.
    //
    PD_ODR = 0;             //  All pins are turned off.
    PD_DDR = 0xff;          //  All pins are outputs.
    PD_CR1 = 0xff;          //  Push-Pull outputs.
    PD_CR2 = 0xff;          //  Output speeds up to 10 MHz.
    //
    //  Initialise Port C.
    //
    PC_ODR = 0;             //  Turn port C outputs off.
    PC_DDR = 0x18;          //  PC3 &amp; PC4 initialised for output.
    PC_CR1 = 0x18;          //  PC3 &amp; PC4 Push-Pull outputs
    PC_CR2 = 0x18;          //  PC3 &amp; PC4 can run up to 10MHz.
    //
    //  Initialise the CS port for input and set up the interrupt behaviour.
    //
#if defined(DISCOVERY)
    PB_ODR = 0;             //  Turn the outputs off.
    PB_DDR = 0;             //  All pins are inputs.
    PB_CR1 = 0xff;          //  All inputs have pull-ups enabled.
    PB_CR2 = 0xff;          //  Interrupts enabled on all pins.
    EXTI_CR1_PBIS = 2;      //  Port B interrupt on falling edge (initially).
#else
    PA_ODR = 0;             //  Turn the outputs off.
    PA_DDR = 0;             //  All pins are inputs.
    PA_CR1 = 0xff;          //  All inputs have pull-ups enabled.
    PA_CR2 = 0xff;          //  Interrupts enabled on all pins.
    EXTI_CR1_PAIS = 2;      //  Port A interrupt on falling edge (initially).
#endif
}

The application will also need some storage space for the data in the shift registers:

//--------------------------------------------------------------------------------
//
//  Number of registers in the chain.
//
#define NUMBER_OF_REGISTERS     2
//
//  Data area holding the values in the register.
//
U8 _registers[NUMBER_OF_REGISTERS];             //  Data in the shift registers.

Note that the variable used to store the data in the registers has been converted into a static array instead of a variable sized array using malloc.

The function table needs to be modified to hold the references to the methods which will implement the module functionality:

//
//  Forward function declarations for the function table.
//
void SetShiftRegisters();
void ClearShiftRegisters();
//
//  Table of pointers to functions which implement the specified commands.
//
FunctionTableEntry _functionTable[] = { { 0x01, ClearShiftRegisters }, { 0x02, SetShiftRegisters } };

The next step is to add the methods which will implement the functionality:

//--------------------------------------------------------------------------------
//
//  Clear the shift registers.
//
void ClearRegisters()
{
    for (U8 index = 0; index < NUMBER_OF_REGISTERS; index++)
    {
        _registers[index] = 0;
    }
}

//--------------------------------------------------------------------------------
//
//  GO! Function 1 - Clear the shift registers.
//
void ClearShiftRegisters()
{
    ClearRegisters();
    OutputData();
    NotifyGOBoard();
}

Note that the functionality has been implemented using two methods, the first ClearRegisters is the internal implementation. This is independent of the Netduino Go! communications and allows the functionality to be called in say the initialisation code of the module. The second method, ClearShiftRegisters is the method which is called by the code as determined by the Netduino Go! driver code. This has the additional output and notification methods which actually sets the data in the shift registers and sends a signal back to the Netduino Go! to let the board know that the request has been received and processed. Note that this split is not necessary but is a design decision taken for this particular module.

The code should be ready to compile and deploy to the STM8S.

Netduino Go! Driver

At this point we should have one half of the communications channel ready to test. The second stage is to implement the driver on the Netduino Go!. As our starting point, we will copy the code from the BasicGoModule class in the post STM8S SPI Slave (Part 3) – Making a Go Module into a new class OutputExpander. We will modify this to add create new functionality to clear and set the shift registers.

Command Constants

The first this we need to do is to remove the old command constants and add two new ones:

/// <summary>
/// Command number for the ClearShiftRegister command.
/// </summary>
private const byte CMD_CLEAR_REGISTERS = 1;

/// <summary
/// Command number for the SetRegister command.
/// </summary>
private const byte CMD_SET_REGISTERS = 2;

Modify the Supporting Methods

The existing code in the module can be optimised for this module. To do this rename the WriteDataToModule method to WriteDataToSPIBus. Now add the following code:

/// <summary>
/// Write the data in the _writeFrameBuffer to the module.  Make several
/// attempts to write the data before throwing an exception.
/// </summary>
/// <param name="exceptionMessage">Exception message to the used in the constructor if the write attempt fails.</param>
private void WriteDataToModule(string exceptionMessage)
{
    int retriesLeft = 10;
    bool responseReceived = false;

    WriteDataToSPIBus();
    while (!responseReceived && (retriesLeft > 0))
    {
        //
        //  We have written the data to the module so wait for a maximum 
        //  of 5 milliseconds to see if the module responds with some 
        //  data for us.
        //
        responseReceived = _irqPortInterruptEvent.WaitOne(5, false);
        if ((responseReceived) && (_readFrameBuffer[1] == GO_BUS10_COMMAND_RESPONSE))
        {
            //
            //  Assume good result, it is up to the calling method to determine if
            //  the command has been executed correctly.
            //
            return;
        }
        else
        {
            //
            //  No response within the 5ms so lets make another attempt.
            //
            retriesLeft--;
            if (retriesLeft > 0)
            {
                WriteDataToSPIBus();
            }
        }
    }
    throw new Exception(exceptionMessage);
}

By making this change we are also making the assumption that the signal back from the module (via the interrupt) is always indicating success. This is a decision which is appropriate for this module but may not be appropriate for other applications/modules.

Add New Functionality

Before adding new functionality it is necessary to remove the existing AddFive functionality from the BasicGoModule.

Out OutputExpander module will provide the application with two basic functions:

  • Clear – clear the shift registers setting the output to 0
  • Set – Set the values in the shift registers to those specified by the parameters

This functionality is provided by the following code:

/// <summary>
/// This method calls the ClearRegister method on the GO! module and then waits for the
/// module to indicate that it has received and executed the command.
/// </summary>
public void Clear()
{
    _writeFrameBuffer[0] = GO_BUS10_COMMAND_RESPONSE;
    _writeFrameBuffer[1] = CMD_CLEAR_REGISTERS;
    WriteDataToModule("Clear cannot communicate with the Output Expander module");
}

/// <summary>
/// Set the shift registers using the values in the byte array.
/// </summary>
/// <param name="registers">Bytes containing the shift register values.</param>
public void Set(byte[] registers)
{
    if (registers.Length != 2)
    {
        throw new ArgumentException("registers: length should be 2 bytes");
    }
    _writeFrameBuffer[0] = GO_BUS10_COMMAND_RESPONSE;
    _writeFrameBuffer[1] = CMD_SET_REGISTERS;
    for (int index = 0; index < registers.Length; index++)
    {
        _writeFrameBuffer[2 + index] = registers[index];
    }
    WriteDataToModule("Clear cannot communicate with the Output Expander module");
}

Testing

At this point we should be able to add some code the Main method of the application to test the module. We will perform something similar to the code in the previous post, we will move a single LED but this time we will illuminate the LED starting a position 15 and working down to 0. This should ensure that we have deployed new code to both the module and the Netduino Go! board.

public static void Main()
{
    OutputExpander output = new OutputExpander();
    int cycleCount = 0;
    byte[] registers = new byte[2];
    while (true)
    {
        Debug.Print("Cycle: " + ++cycleCount);
        for (int index = 15; index >= 0; index--)
        {
            output.Clear();
            if (index < 8)
            {
                registers[0] = (byte) (1 << index);
                registers[1] = 0;
            }
            else
            {
                registers[0] = 0;
                registers[1] = (byte) (1 << (index - 8));
            }
            output.Set(registers);
            Thread.Sleep(200);
        }
    }
}

The test breadboard and Netduino Go! were connected through the Komodex GoBus Breakout Module. This allows the Netduino Go! and the ST-Link/2 programmer to be connected to the breadboard:

Output Expander Connected to Netdunio GO!

Output Expander Connected to Netdunio GO!

And here is a video of this working:

You can just see the Netduino Go! in the bottom left corner of the video.

Conclusion

At this point the basic concept has be proven and the application can perform the following:

  1. STM8S code can control one or more LEDs
  2. STM8S can be controlled using SPI
  3. Netduino Go! can pass instructions to the STM8S

One thing that did become apparent during this part of the process was that both the STM8S code and the Netduino code can be improved by improved grouping the functionality. For instance, in order to add the methods to the function table in the STM8S code I had to jump around the source code a little.

The next step in the process is to look at the hardware and start to prepare for prototype PCB manufacture.

STM32 – Simple GPIO and Some Flashing LEDs

Saturday, March 23rd, 2013

When starting with a new development board I always revisit the fundamentals like GPIO. Doing this helps establish that the development tools are working and gives a basic library for future development. So with this post we will be looking at GPIO on the STM32 Discovery board. When Rediscovering the STM32 Discovery Board I mentioned that I would need to look at using either the standard peripheral library provided by ST or to use a lower level method of controlling the board. After due consideration I have decided to continue with the lower level access. Whilst this may be more effort it does lead to a better understanding of what is actually happening at the low level.

Our objective today will be to toggle a GPIO pin (Port D, pin 0) generating a square wave. The result will be something similar to the Simple GPIO post for the STM8S.

STM32F4 Discovery Board

The STM32F4 Discovery board is a low cost development board for hobbyists, beginners and experienced users. The board uses the STM32F407VGT6 and hosts a number of sensors including digital accelerometer, push buttons, microphone and DAC. It also contains four built in LEDs which can be used to give feedback to the developer and breakout pins for the GPIO ports.

STM32F4 Discovery Board

STM32F4 Discovery Board

Each of the GPIO pins can be used for simple input/output of digital signals. The majority of the pins are also mapped to one or more alternative functions. For instance, Port C, pin 3 is also mapped to the following alternative functions:

  • SPI2 MOSI
  • I2S2 SD
  • OTG HS ULPI NXT
  • ETH MI TX Clk
  • EVENTOUT

For the remainder of this post we will restrict ourselves to the GPIO functionality of the chip and demonstrate the following:

  • Output a digital signal on a single GPIO pin
  • Flash one of the LEDs on the board

The alternative functionality will be considered in future posts.

The functionality of the 16 pins on each of the GPIO ports is controlled by a number of registers. The description of each of the registers can be found in RM0090 Reference Manual for the STM32 chip. This manual can be downloaded from STs web site.

GPIO Registers

Each STM32 chip has a number of ports each of which has 16 pins. As already noted, each of these pins has a number of functions, the most basic of which is digital input/output. The functionality and state of each port is controlled by a number of GPIO registers:

  • Four configuration registers
  • Two data registers
  • One set/reset register
  • One locking register
  • Two alternative function registers

An overview of the registers follows; a fuller description of the registers can be found in section 7 of RM0090 Reference Manual (Revision 4).

When referring to the registers, the reference manual uses the following notation:

GPIOx_Function

Where x is replaced by the port to be used. For instance, GPIOx_MODER refers to the generic mode register for a GPIO port. A specific instance of this would be GPIOD_MODER. This refers to the mode register for port D. Normally, x is in the range A to I and is defined by the specific version of the STM32 being used in the project.

Configuration Registers

The STM32 has four configuration registers for each of the ports.

  • Port mode register – GPIOx_MODER
  • Output type register – GPIOx_OTYPER
  • Speed register – GPIOx_OSPEEDR
  • Pull-up/Pull-down register – GPIOx_PUPDR

Each of these registers is 32-bits wide although not all of the bits are used in all of the registers.

Port Mode Register – GPIOx_MODER

This 32 bit register holds 2 bit values which defines the operation mode. The modes allowed are:

Bit ValuesDescription
00Input
01General purpose output
10Alternate function
11Analog

In general, the default reset value for this register is 0. Some of the ports are reset to non-zero values as the pins in the port have special meanings (debugging pins etc). The rest values are:

PortReset Value
A0xA800 0000
B0x0000 0280
All others0x0000 0000

The bits in the register are read/write and are in consecutive pairs. So bits 0 & 1 define the mode for pin 0, bits 2 & 3 define the mode for pin 1 etc.

Output Type Register – GPIOx_OTYPER

This is a 32-bit register but only the lower 16 bits are used. The top 16 bits are reserved and should not be used. This register uses a single bit for each pin to define the output type of the pins in the port. So, bit 0 defines the output type for pin 0, bit 1 for pin 1 etc.

Bit ValueDescription
0Push/pull output
1Open drain output

The default reset value for all of the GPIOx_TYPER registers is 0x0000 0000 meaning that all ports are configured for push/pull output.

Speed Register – GPIOx_OSPEEDR

This 32-bit register uses all of the bits in the register in pairs (similar to the port mode register). The bits define the output speed of the port. The bit pairs have the following meaning:

Bit ValuesDescription
002 MHz Low speed
0125 MHz Medium speed
1050 MHz Fast speed
11100 MHz High speed on 30pF
80 MHz High speed on 15 pF

The reset value for these registers is 0x0000 00C0 for port B and 0x0000 0000 for all other registers.

Pull-up/Pull-down register – GPIOx_PUPDR

This 32-bit register uses all of the bits in the register in pairs (similar to the port mode register). The bits define pull-up / pull-down resistor mode for the pins on a port. The bit pairs have the following meaning:

Bit ValuesDescription
00No pull-up or pull-down resistor
01Pull-up resistor
10Pull-down resistor
11Reserved

The reset values for the registers are:

PortReset Value
A0x6400 0000
B0x0000 0100
All others0x0000 0000

Data Registers

The two data registers for each port indicate the input/output state of the port. Each of the registers are 32-bit registers but only the lower 16 bits are used. The top 16 bits are reserved and should not be used.

  • Input data register – GPIOx_IDR
  • Output data register – GPIOx_ODR

Input data register – GPIOx_IDR

The input data register indicates if a high or low signal has been applied to the pin within a port. The register is read-only and the whole word should be read.

Bit ValueDescription
0High logic value
1Low logic value

As with the output type register – GPIOx_OTYPER, the bits in the register map onto the pins in the port (bit 0 maps to pin 0, bit 1 to pin 1 etc.).

The reset value for all of these registers is 0x0000 XXXX where X indicates that the bits are unknown as they will be defined by the input signals applied to the pins are reset.

Output data register – GPIOx_ODR

The output data register allows the program to change the output state of a pin within the port. This is a 32-bit register and only the lower 16 bits are used. The top 16 bits are reserved and should not be used. The output states are defined as:

Bit ValueDescription
0High logic value
1Low logic value

As with the output type register – GPIOx_OTYPER, the bits in the register map onto the pins in the port (bit 0 maps to pin 0, bit 1 to pin 1 etc.). The bits in the lower 16 bits of the register are all read/write bits.

The reset value for all of these registers is 0x0000 0000.

A more effective way of setting/resetting a bit in the output register is to use the bit set/reset register.

Set/reset register

The set/reset register sets or resets a single bit in the output data register.

Bit Set/Reset Register – GPIOx_BSSR

This is a 32-bit register and it is split into two halves, the lower 16 bits are used for setting a bit and the upper 16 bits are used for resetting a bit. This is a write only register; reading this register will return 0x0000.

Setting a bit in the lower 16-bits of this register will cause the corresponding bit in the output data register to be set to 1.

Similarly, setting a bit in the upper 16-bits of this register will cause the corresponding bit in the output data register to be set to 0.

For the lower 16-bits the mapping is simple, bit 0 in GPIOx_BSRR maps to bit 0 in GPIOx_ODR etc. For the upper 16-bits, bit 16 of the GPIOx_BSRR maps to bit 0 of the GPIOx_ODR; bit 17 of GPIOx_BSRR maps to bit 1 of GPIOx_ODR etc.

So why provide the set/reset functionality when you have the output data registers? The set/reset register provide an atomic method of setting or resetting an output pin on a port. When using the output data registers, the compiler will have to generate a number of statements in order to set or reset a pin. This can allow an interrupt to be processed when part way through the set/reset operation. By using this register it is possible to set or reset a pin using a single instruction. This means that the operation will complete before the interrupt can be processed.

Configuration Lock Register

This register is used to lock the configuration of the port until the next reset of the chip.

Configuration Lock Register – GPIOx_LCKR

The register is a 32-bit register but only 17 bits are actually used. The lower 16 bits indicate which bit of the port configuration is locked. So setting bit 0 of the register will lock the configuration of pin 0 etc.

The final bit used is bit 16 in the register (known as the Lock Key – LCKK); this bit determines the lock status of the port; a zero indicates that no lock is applied whilst a 1 indicates that the port configuration is locked.

In order to lock the configuration of a port the configuration lock write sequence must be followed. This will prevent accidental locking of the port configuration. The algorithm for locking the configuration is as follows:

  • Set bits 0 through 15 of the register indicating which pins should have their configuration locked
  • Set LCKK to 1 and write the value into the register
  • Set LCKK to 0 and write the value into the register
  • Set LCKK to 1 and write the value into the register
  • Read the lock bit of the port

It is important to note that the values in bits 0 through 15 should not change whilst following the above sequence. Should they change or any error occur then the lock will be aborted.

The final step, reading the lock bit, confirms the success or failure of the operation.

Alternative function registers

The alternative function registers allow the configuration of the port to change from simple GPIO to an alternative function such as SPI MOSI. These registers are noted here but will not be discussed further as they will be covered in subsequent posts.

C Definitions and Some Typedefs

The register in the STM32 microcontroller have been mapped onto specific memory locations within the valid address space of the STM32. These memory locations are defined in the various header files for the microcontroller. For instance, the following type definition has been taken from the header file stm32f4xx.h:

typedef struct
{
  __IO uint32_t MODER;    /*!> GPIO port mode register,               Address offset: 0x00      */
  __IO uint32_t OTYPER;   /*!> GPIO port output type register,        Address offset: 0x04      */
  __IO uint32_t OSPEEDR;  /*!> GPIO port output speed register,       Address offset: 0x08      */
  __IO uint32_t PUPDR;    /*!> GPIO port pull-up/pull-down register,  Address offset: 0x0C      */
  __IO uint32_t IDR;      /*!> GPIO port input data register,         Address offset: 0x10      */
  __IO uint32_t ODR;      /*!> GPIO port output data register,        Address offset: 0x14      */
  __IO uint16_t BSRRL;    /*!> GPIO port bit set/reset low register,  Address offset: 0x18      */
  __IO uint16_t BSRRH;    /*!> GPIO port bit set/reset high register, Address offset: 0x1A      */
  __IO uint32_t LCKR;     /*!> GPIO port configuration lock register, Address offset: 0x1C      */
  __IO uint32_t AFR[2];   /*!> GPIO alternate function registers,     Address offset: 0x20-0x24 */
} GPIO_TypeDef;

This header file can be found in the Standard Peripheral Library as supplied by ST Microelectronics.

A careful examination of the members of the struct within the typedef reveals that with one exception, the members of the struct all map directly onto the registers we have been discussing above. The one exception is the BSSR register. This has been split into two components BSSRL and BSSRH. This split nicely maps to the difference in functionality of the two halves of the register, namely Setting and resetting of individual bits in the output data register.

Locating A GPIO in Memory

Once we have the layout of the registers (as defined in the struct above), we simply need to map the structure onto the right location in memory. A little further digging in the header file leads us to the following #define statements:

#define GPIOD             ((GPIO_TypeDef *) GPIOD_BASE)

#define GPIOD_BASE        AHB1PERIPH_BASE + 0x0C00)

#define APB1PERIPH_BASE   PERIPH_BASE

#define PERIPH_BASE       ((uint32_t) 0x40000000) /*!< Peripheral base address in the alias region                                */

Following the chain of #define statements leads us to the the conclusion that the registers for GPIOD are located at memory location 0x40000C000. These definitions mean that we should be able to access individual registers with statements such as:

GPIOD->MODER = 0x01;    // Set Pin 0 of Port D to input.

A Practical Demonstration

After all this theory we must be ready for some coding. In this section we will look at creating two small applications:

  • GPIO Toggle – toggling a single pin on a GPIO port
  • Blinky – Flashing a single LED

The first of the two applications will simply toggle a single GPIO pin as fast as possible using the default reset state of the microcontroller. Note that an oscilloscope will be required in order to prove that the application is working correctly

The second application will make use of the on board LEDs. This will require a little more coding in order to provide a delay between turning the LED on and off in order to make the flashing visible to the naked eye.

Application 1 – Toggling A GPIO Pin

This application is simple and consists of two parts:

  • Initialisation
  • A loop which toggles the GPIO pin

The full application looks like this:

#include "stm32f4xx_rcc.h"
#include "stm32f4xx_gpio.h"

int main()
{
    //
    //  Initialise the peripheral clock.
    //
    RCC->AHB1ENR |= RCC_AHB1Periph_GPIOD;
    //
    //  Initilaise the GPIO port.
    //
    GPIOD->MODER |= GPIO_Mode_OUT;
    GPIOD->OSPEEDR |= GPIO_Speed_25MHz;
    GPIOD->OTYPER |= GPIO_OType_PP;
    GPIOD->PUPDR |= GPIO_PuPd_NOPULL;
    //
    //  Toggle Port D, pin 0 indefinitely.
    //
    while (1)
    {
        GPIOD->BSRRL = GPIO_Pin_0;
        GPIOD->BSRRH = GPIO_Pin_0;
    }
}

One thing to note from the above code is that we need to initialise the peripheral clocks (this should be taken as read for the moment and will be covered in future posts). When initialising the GPIO port we should take into consideration the default clock speed of the Discovery board after reset. By default this is 8 MHz and so we should set the speed of the output port accordingly. This leads to a configuration for pin 0 of output with a maximum speed of 25 MHz, push pull with not pull-up or pull-down resistors.

Once initialised we enter the main program loop. Here the application uses the set/rest registers to toggle pin 0. This shows how the 32-bit register is split into two, one for the set and one for the reset operation.

If we create a simple STM32 project using IAR, compile and then deploy this application we get the following output on the oscilloscope:

STM32 Scope Output

STM32 Scope Output

And there we have it, our first application deployed to the STM32 Discovery board.

Blinky – Flashing A Single LED

This second example is really aimed at those who do not have access to an oscilloscope. Here we will slow down the output of the GPIO port and flash one of the built in LEDs.

The STM32F4 Discovery board has four built in LEDs which can be accessed by setting/resetting pin 12 through 15 on port D. The LED colours are:

Pin NumberColour
12Green
13Orange
14Red
5Blue

Using the code in the previous example as a starting point we find ourselves with an application which looks something like this:

#include "stm32f4xx.h"
#include "stm32f4xx_gpio.h"
#include "stm32f4xx_rcc.h"

#define LED_GREEN      12
#define LED_ORANGE     13
#define LED_RED        14
#define LED_BLUE       15

int main()
{
    int pin = LED_RED;
    uint32_t mode = GPIO_Mode_OUT << (pin * 2);
    uint32_t speed = GPIO_Speed_100MHz << (pin * 2);
    uint32_t type = GPIO_OType_PP << pin;
    uint32_t pullup = GPIO_PuPd_NOPULL << (pin * 2);
    //
    //  Initialise the peripheral clock.
    //
    RCC->AHB1ENR |= RCC_AHB1Periph_GPIOD;
    //
    //  Initilaise the GPIO port.
    //
    GPIOD->MODER |= mode;
    GPIOD->OSPEEDR |= speed;
    GPIOD->OTYPER |= type;
    GPIOD->PUPDR |= pullup;
    //
    //  Toggle the selected LED indefinitely.
    //
    int index;
    while (1)
    {
        GPIOD->BSRRL = (1 << pin);
        for (index = 0; index < 500000; index++);
        GPIOD->BSRRH = (1 << pin);
        for (index = 0; index < 500000; index++);
    }
}

Deploying this application should flash the selected LED just below the STM32 microcontroller on the STM32F4 Discovery board.

Conclusion

This post presented two simple applications which toggle GPIO lines on Port D. Using these techniques and the additional information in the register descriptions you should be able to extend the application above to control digital devices such as shift registers etc.