RSS

SPI and the STM32

March 17th, 2014 • Electronics, Software Development, STM322 Comments »

STM32 Discovery With Logic Probes

This post was supposed to be about controlling an LED panel using SPI on the STM32. It soon became apparent that SPI on the STM32 was a topic of it’s own and so the hardware component of the post, the LED panel, will have to wait for another day.

In this post we will aim to achieve the following:

  1. Use SysTick to trigger regular events
  2. Send data over SPI by polling
  3. Use interrupts to send data by SPI
  4. Combine SPI and DMA to send data over SPI

So let’s start with the trigger/heartbeat.

Heartbeat

The heartbeat will allow us to perform tasks at regular intervals, say 1ms, and by toggling a pin we can also determine if the processor is still “alive”.

For a simple heartbeat we can use the SysTick timer. This built in timer can be configured to generate an interrupt. The handler has it’s own entry in the interrupt vector table. It is theoretically possible to calibrate this timer to trigger at an accurate period. For the purpose of this problem we will not really need high accuracy and so will simply turn on the timer.

Setting this timer running requires only two lines of code, encapsulating this into an initialisation method gives us the following:

//
//    Initialise SysTick.
//
void InitialiseSysTick()
{
    RCC_ClocksTypeDef RCC_Clocks;
    //
    //    Setup the system tick for the heartbeat.
    //
    RCC_GetClocksFreq(&RCC_Clocks);
    SysTick_Config(RCC_Clocks.HCLK_Frequency / 1000);
}

The SysTick_Config method sets up the SysTick timer. The timer works by loading the counter with a Load value. The counter then starts to count down from the load value each tick of the clock. When the counter reaches zero it is reloaded and the whole process starts again. It is possible to generate an interrupt (SysTick_Handler) when the counter reaches zero.

The expression RCC_Clocks.HCLK_Frequency / 1000 is the counter reload value. It is important to note that the reload value is a 24-bit unsigned long and so this has a maximum value of 0xffffff. In this case the load value is 0×29040, well within the specified range. This value will give 1000 interrupts per second, i.e. an interrupt every 1ms.

Now we have a 1ms interrupt we need to determine if this interrupt is being triggered. The simplest way of doing this is to toggle one of the GPIO pins. First thing to do is to select a pin and then initialise the port. Selecting PA1 we can modify the InitialiseSysTick code above to the following:

//
//    Initialise SysTick.
//
void InitialiseSysTick()
{
    GPIO_InitTypeDef GPIO_InitStructure;
    RCC_ClocksTypeDef RCC_Clocks;
    //
    //  Initialise the peripheral clock.
    //
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
    //
    //  Initialise the heartbeat GPIO port.
    //
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
    GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    //
    //    Setup the system tick for the heartbeat.
    //
    RCC_GetClocksFreq(&RCC_Clocks);
    SysTick_Config(RCC_Clocks.HCLK_Frequency / 1000);
}

The next task is to add the SysTick interrupt handler. Opening the startup_stm32f4xx.c file you will find the following line:

#pragma weak SysTick_Handler = Default_Handler

This statement defines the SysTick_Handler and points the handler to the Default_Handler. The weak attribute allows the developer to override this handler and provide their own interrupt handler. In our case we want the handler to toggle PA1. This gives the following code:

//
//    System tick handler.
//
void SysTick_Handler(void)
{
    //
    //    Generate a heartbeat.
    //
    GPIOA->ODR ^= GPIO_Pin_1;
}

All of the above code is placed in a file SysTick.c and an appropriate header file created. Our main program file is:

#include "SysTick.h"

//
//    Initialise the system.
//
void Initialise()
{
    InitialiseSysTick();
}

//
//    Main program loop.
//
int main()
{
    Initialise();
    while (1);
}

Putting this into a project, compiling and running results in the following output on the oscilloscope:

500Hz Square Wave

500Hz Square Wave

As you can see, we have a 500Hz square wave indicating that PA1 is being toggled 1,000 times per second.

Polled SPI

Polled SPI will use the various registers associated with the SPI feature to determine if the SPI data transmission has completed. You can also use interrupts, a subject we will come to later.

The first things we need to do is to configure the pins required to support SPI. For conventional SPI we need four pins:

  1. MOSI (PA7)
  2. MISO (PA6)
  3. SCLK (PA5)
  4. Chip select (PE6)

One change we will make is to move the heartbeat from Port A to Port E (PE5) and keep Port A for the SPI function. Abstracting the GPIO initialisation out to it’s own files gives the following header file:

//
//    Include file for the GPIO methods.
//
//    Copyright 2014 Mark Stevens
//
#include "stm32f4xx_rcc.h"
#include <stm32f4xx_gpio.h>


#ifndef _SPI_H_
#define _SPI_H_

#ifdef __cplusplus
extern "C"
{
#endif

void InitialiseGPIO();

#ifdef __cplusplus
}
#endif

#endif

and the code file:

//
//    GPIO methods.
//
//    Copyright 2014 Mark Stevens
//
#include "GPIO.h"

//
//    Initialise the GPIO ports setting up the clocks.
//
void InitialiseGPIO()
{
    GPIO_InitTypeDef GPIO_InitStructure;
    //
    //  Initialise the peripheral clocks.
    //
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOE, ENABLE);
    //
    //    Configure pins used by SPI1:
    //        PA5 = SCLK
    //        PA6 = MISO
    //        PA7 = MOSI
    //
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7 | GPIO_Pin_6 | GPIO_Pin_5;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
    GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    //
    //    Configure the Port E:
    //        PE6 - SPI chip select pin.
    //        PE5 - 1ms heartbeat.
    //
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_5;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
    GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
    GPIO_Init(GPIOE, &GPIO_InitStructure);
}

Calling the InitialiseGPIO method sets up Port A for SPI and Port E for general IO.

The next step is to initialise the SPI port:

  • Clock is idle low
  • Data is sampled on the rising edge
  • SPI Master
  • 8 data bits
  • MSB transmitted first
  • Clock prescalar 256 (slowest clock possible)

Coding this into an initialisation method gives:

//
//    SPI methods.
//
//    Copyright 2014 Mark Stevens
//
#include "spi.h"

//
//    Initialise SPI
//
void InitialiseSPI(void)
{
    SPI_InitTypeDef SPI_InitStruct;
    //
    //     Connect SPI1 pins to SPI alternate function.
    //
    GPIO_PinAFConfig(GPIOA, GPIO_PinSource5, GPIO_AF_SPI1);
    GPIO_PinAFConfig(GPIOA, GPIO_PinSource6, GPIO_AF_SPI1);
    GPIO_PinAFConfig(GPIOA, GPIO_PinSource7, GPIO_AF_SPI1);
    //
    //    Set PE6 high as we will be using active low for the
    //    device select.
    //
    GPIOE->BSRRL |= GPIO_Pin_6;
    //
    //     Enable the SPI peripheral clock.
    //
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);

    //
    //    Configure SPI1 in Mode 0:
    //        CPOL = 0 --> clock is low when idle
    //        CPHA = 0 --> data is sampled at the first edge
    //
    //    SPI Master mode, 8 bits of data, clock prescalar is 256, MSB is
    //    transmitted first.
    //
    SPI_InitStruct.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
    SPI_InitStruct.SPI_Mode = SPI_Mode_Master;
    SPI_InitStruct.SPI_DataSize = SPI_DataSize_8b;
    SPI_InitStruct.SPI_CPOL = SPI_CPOL_Low;
    SPI_InitStruct.SPI_CPHA = SPI_CPHA_1Edge;
    SPI_InitStruct.SPI_NSS = SPI_NSS_Soft | SPI_NSSInternalSoft_Set;
    SPI_InitStruct.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256;
    SPI_InitStruct.SPI_FirstBit = SPI_FirstBit_MSB;
    SPI_Init(SPI1, &SPI_InitStruct);
    //
    //    Enable SPI.
    //
    SPI_Cmd(SPI1, ENABLE);
}

As already noted, this first version of the SPI method will poll the SPI registers in order to determine the state of the SPI bus. The general algorithm is:

  1. Set the data register to the byte to be transmitted
  2. Wait for data transmission on MOSI to complete
  3. Wait for data reception on MISO to complete
  4. Wait until SPI is no longer busy
  5. Transfer the received data from the data register

Translating to C gives the following method:

//
//    Transmit and receive a single byte of data.
//
uint8_t SPISend(uint8_t data)
{
    //
    //    Setting the Data Register (DR) transmits the byte of data on MOSI.
    //
    SPI1->DR = data;
    //
    //    Wait until the data has been transmitted.
    //
    while (!(SPI1->SR & SPI_I2S_FLAG_TXE));
    //
    //    Wait for any data on MISO pin to be received.
    //
    while (!(SPI1->SR & SPI_I2S_FLAG_RXNE));
    //
    //    All data transmitted/received but SPI may be busy so wait until done.
    //
    while (SPI1->SR & SPI_I2S_FLAG_BSY);
    //
    //    Return the data received on MISO pin.
    //
    return(SPI1->DR);
}

And a header file for these methods:

//
//    Include file for the SPI methods.
//
//    Copyright 2014 Mark Stevens
//
#include <stm32f4xx.h>
#include <stm32f4xx_spi.h>
#include <stm32f4xx_gpio.h>
#include <stm32f4xx_rcc.h>

#ifndef _SPI_H_
#define _SPI_H_

#ifdef __cplusplus
extern "C"
{
#endif

void InitialiseSPI();
uint8_t SPISend(uint8_t);

#ifdef __cplusplus
}
#endif

#endif

Before we move on to the main program we need to remember to change the SysTick_Handler and initialisation method to take into consideration the changes we have made to the initialisation of the GPIO ports and the movement of the heartbeat to Port E. The SysTick.c becomes:

//
//    SysTick methods.
//
//    Copyright 2014 Mark Stevens
//
#include "stm32f4xx_rcc.h"
#include "stm32f4xx_gpio.h"
#include "system_stm32f4xx.h"

//
//    Initialise SysTick.
//
void InitialiseSysTick()
{
    RCC_ClocksTypeDef RCC_Clocks;
    //
    //    Setup the system tick for the heartbeat.
    //
    RCC_GetClocksFreq(&RCC_Clocks);
    SysTick_Config(RCC_Clocks.HCLK_Frequency / 1000);
}

//
//    System tick handler.
//
void SysTick_Handler(void)
{
    //
    //    Generate a heartbeat.
    //
    GPIOE->ODR ^= GPIO_Pin_5;
}

The last thing to do is to modify the main program file to call the appropriate initialisation methods and then transmit the data.

//
//    LED Panel - Main program and associated methods.
//
//    Copyright 2014 Mark Stevens
//
#include "SPI.h"
#include "GPIO.h"
#include "SysTick.h"
#include <stm32f4xx_gpio.h>

//
//    Initialise the system.
//
void Initialise()
{
    InitialiseGPIO();
    InitialiseSPI();
    InitialiseSysTick();
}

//
//    Main program loop.
//
int main()
{
    Initialise();
    while (1)
    {
        GPIOE->BSRRH |= GPIO_Pin_6;        // Set PE6 (Chip Select) low
        SPISend(0xAA);                  // Transmit data
        SPISend(0x00);                     // Transmit dummy byte and receive data
        GPIOE->BSRRL |= GPIO_Pin_6;     // set PE6 (Chip Select) high
    }
}

Compiling the above code and deploying it to the STM32 Discovery board generated the following output on the logic analyser:

Polled SPI

Polled SPI

A low clock speed is chosen for the SPI bus as it helps to eliminate the impact of interference from stay signals, long leads etc. Once the system is working at a low clock speed, the prescalar can be changed and the speed increased gradually until we determine the maximum rate at which data can be transmitted reliably.

The main program loop above contains two calls to the SPISend method. The first transmits the data we want top send to the slave device, the second call sends dummy data. This would allow the slave module to send a single byte response.

Interrupt Driven SPI

The final aim of this project is to be able to send data to an LED Panel using SPI. The panel itself is not required to send data back to the application. The modifications made here will take that into consideration. The following changes will be made:

  • Transmit only, no data will be received
  • Interrupt driven
  • Heartbeat will kick off the transmission of the data
  • Transmit a buffer of data (more than a single byte)

The first modification to be made is to the SPI configuration. Change the SPI_InitStruct setup to use a single Tx line and add code to configure the SPI interrupt priority:

SPI_InitStruct.SPI_Direction = SPI_Direction_1Line_Tx;
SPI_InitStruct.SPI_Mode = SPI_Mode_Master;
SPI_InitStruct.SPI_DataSize = SPI_DataSize_8b;
SPI_InitStruct.SPI_CPOL = SPI_CPOL_Low;
SPI_InitStruct.SPI_CPHA = SPI_CPHA_1Edge;
SPI_InitStruct.SPI_NSS = SPI_NSS_Soft | SPI_NSSInternalSoft_Set;
SPI_InitStruct.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256;
SPI_InitStruct.SPI_FirstBit = SPI_FirstBit_MSB;
SPI_Init(SPI1, &SPI_InitStruct);
//
//    Configure the SPI interrupt priority
//
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitStructure.NVIC_IRQChannel = SPI1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);

This application will be sending a buffer of data rather than a repeated byte so we need to add somewhere to store the data. Adding the following to the SPI.c file gives us some storage:

//
//    Storage for the SPI data.
//
uint8_t buffer[SPI_BUFFER_LENGTH];
int bufferIndex = 0;

And adding the following to the SPI.h header file allows the storage to be accessible from other modules:

//
//    SPI related constants.
//
#define SPI_BUFFER_LENGTH        10

//
//    Data storage for the SPI methods.
//
uint8_t buffer[SPI_BUFFER_LENGTH];
int bufferIndex;

Ensure that the code is within the extern “C” statement.

The final bit of the puzzle is the addition of the interrupt capability. The method chosen is to configure the SPI bus and leave SPI turned on but initially have the SPI interrupts turned off. The SysTick_Handler will act as the trigger for the SPI communication starting the communication by setting up the initial conditions and turning on the SPI interrupt. The SPI interrupt handler will take over from there. Modifying the SysTick_Handler we get:

//
//    System tick handler.
//
void SysTick_Handler(void)
{
    //
    //    Generate a heartbeat.
    //
    GPIOE->ODR ^= GPIO_Pin_5;
    //
    //    If we are about to generate a rising edge on the heartbeat
    //    we are ready to start SPI data transmission.
    //
    if (GPIOE->ODR & GPIO_Pin_5)
    {
        GPIOE->BSRRH |= GPIO_Pin_6;
        bufferIndex = 0;
        SPI_I2S_ITConfig(SPI1, SPI_I2S_IT_TXE, ENABLE);
    }
}

This code starts the SPI transmission on the rising edge of the heartbeat pulse. Note also that this interrupt handler is responsible for setting the chip select line.

The final bit of the puzzle is the SPI interrupt handler itself.

//
//    Process the interrupts for SPI1.
//
void SPI1_IRQHandler()
{
    //
    //    If TX buffer is empty then transmit the next byte.
    //
    if (SPI1->SR & SPI_I2S_FLAG_TXE)
    {
        if (bufferIndex < SPI_BUFFER_LENGTH)
        {
            SPI1->DR = buffer[bufferIndex++];
        }
    }
    //
    //    If SPI is not busy then we have finished sending data
    //    so turn off this interrupt.
    //
    if (!(SPI1->SR & SPI_I2S_FLAG_BSY))
    {
        SPI_I2S_ITConfig(SPI1, SPI_I2S_IT_TXE, DISABLE);
        GPIOE->BSRRL |= GPIO_Pin_6;
    }
    //
    //    Clear the interrupt pending bit otherwise this interrupt
    //    will be regenerated.
    //
    SPI_I2S_ClearITPendingBit(SPI1, SPI_I2S_IT_TXE);
}

Two key points to notice in this interrupt handler when the end of data buffer has been reached and SPI is no longer busy:

  1. Chip select is set to high
  2. Interrupts are disabled

The last line of this interrupt handler clears the SPI interrupt pending bit to prevent this handler being called again as soon as it exits.

One final modification to be made is to the main program loop. This is no longer required to control the transmission of the data but we will need to setup the contents of the data buffer:

//
//    Main program loop.
//
int main()
{
    Initialise();
    //
    //    Fill the SPI buffer with data.
    //
    int index;
    for (index = 0; index < SPI_BUFFER_LENGTH; index++)
    {
        buffer[index] = index;
    }
    //
    //    Main program loop.
    //
    while (1);
}

Putting all this together, compiling, deploying gives the following output on the logic analyser:

Buffered SPI With Heartbeat

Buffered SPI With Heartbeat

SPI and DMA

The transition from polled SPI to interrupt driven SPI has so far reduced the load on the microcontroller but the STM32 has one final trick we can use, DMA (Direct Memory Access). DMA allows the various peripherals (of which SPI is one) to directly access the memory used for data storage/retrieval. By doing this the peripheral can operate autonomously until it has run out of data to process.

Remember the following in the SPI interrupt handler above:

if (bufferIndex < SPI_BUFFER_LENGTH)
{
    SPI1->DR = buffer[bufferIndex++];
}

This is required as the SPI peripheral generates an interrupt each time it has transmitted a byte of data and the buffer is empty. With DMA we can hand the SPI peripheral a block of data and tell it to transmit all of the data and simply tell us when the transmission has completed. This means we only receive one interrupt at the end of transmission rather than the 10 we receive for the above scenario.

First thing to do is to modify the InitialiseSPI method to configure the SPI peripheral to use DMA:

//
//    Initialise SPI
//
void InitialiseSPI()
{
    SPI_InitTypeDef SPI_InitStruct;
    NVIC_InitTypeDef NVIC_InitStructure;
    DMA_InitTypeDef DMA_InitStructure;
    //
    //     Connect SPI1 pins to SPI alternate function.
    //
    GPIO_PinAFConfig(GPIOA, GPIO_PinSource5, GPIO_AF_SPI1);
    GPIO_PinAFConfig(GPIOA, GPIO_PinSource6, GPIO_AF_SPI1);
    GPIO_PinAFConfig(GPIOA, GPIO_PinSource7, GPIO_AF_SPI1);
    //
    //    Set PE6 high as we will be using active low for the
    //    device select.
    //
    GPIOE->BSRRL |= GPIO_Pin_6;
    //
    //     Enable the SPI peripheral clock.
    //
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);
    //
    //    Configure SPI1 in Mode 0:
    //        CPOL = 0 --> clock is low when idle
    //        CPHA = 0 --> data is sampled at the first edge
    //
    //    SPI Master mode, 8 bits of data, clock prescalar is 128, MSB is
    //    transmitted first.
    //
    SPI_InitStruct.SPI_Direction = SPI_Direction_1Line_Tx;// SPI_Direction_2Lines_FullDuplex;
    SPI_InitStruct.SPI_Mode = SPI_Mode_Master;
    SPI_InitStruct.SPI_DataSize = SPI_DataSize_8b;
    SPI_InitStruct.SPI_CPOL = SPI_CPOL_Low;
    SPI_InitStruct.SPI_CPHA = SPI_CPHA_1Edge;
    SPI_InitStruct.SPI_NSS = SPI_NSS_Soft | SPI_NSSInternalSoft_Set;
    SPI_InitStruct.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256;
    SPI_InitStruct.SPI_FirstBit = SPI_FirstBit_MSB;
    SPI_Init(SPI1, &SPI_InitStruct);
    //
    //    Configure the DMA controller
    //
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2, ENABLE);
    DMA_StructInit(&DMA_InitStructure);
    DMA_InitStructure.DMA_Channel = DMA_Channel_3;
    DMA_InitStructure.DMA_PeripheralBaseAddr  = (uint32_t) &(SPI1->DR);
    DMA_InitStructure.DMA_Memory0BaseAddr  = (uint32_t) &buffer;
    DMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToPeripheral;
    DMA_InitStructure.DMA_BufferSize  = SPI_BUFFER_LENGTH;
    DMA_InitStructure.DMA_PeripheralInc  = DMA_PeripheralInc_Disable;
    DMA_InitStructure.DMA_MemoryInc  = DMA_MemoryInc_Enable;
    DMA_InitStructure.DMA_PeripheralDataSize  = DMA_PeripheralDataSize_Byte;
    DMA_InitStructure.DMA_MemoryDataSize  = DMA_MemoryDataSize_Byte;
    DMA_InitStructure.DMA_Mode  = DMA_Mode_Normal;
    DMA_InitStructure.DMA_Priority  = DMA_Priority_High;
    DMA_InitStructure.DMA_FIFOMode  = DMA_FIFOMode_Disable;
    DMA_Init(DMA2_Stream5, &DMA_InitStructure);
    DMA_ITConfig(DMA2_Stream5, DMA_IT_TC, ENABLE);

    NVIC_InitStructure.NVIC_IRQChannel = DMA2_Stream5_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
    //
    //    Enable SPI.
    //
    SPI_Cmd(SPI1, ENABLE);
}

The DMA controller is configured for memory to peripheral data transfer (from the buffer to the SPI->DR register). The pointer into memory is incremented after each transmission but the destination pointer (SPI->DR) remains fixed. The system will use DMA2, channel 3, stream 5. The choice of the DMA peripheral, stream and channel has some freedom but is constrained by the choice of peripheral. The list of allowed choices can be found in the STM32 Programmers Reference. As we are using SPI1 we are forced to use DMA2, channel 3 but we can choose between streams 3 and 5.

The next thing to do is to add a new interrupt handler for the DMA completion interrupt:

//
//    SPI DMA handler.
//
void DMA2_Stream5_IRQHandler()
{
    //
    //     Test if DMA Stream Transfer Complete interrupt
    //
    if (DMA_GetITStatus(DMA2_Stream5, DMA_IT_TCIF5) == SET)
    {
        DMA_ClearITPendingBit(DMA2_Stream5, DMA_IT_TCIF5);
        //
        //    The following is required to ensure that the chip select is not
        //    changed while data is still being transmitted.
        //
        while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_BSY) == SET);
        //
        //    Now set chip select to high.
        //
        GPIOE->BSRRL |= GPIO_Pin_6;
    }
}

Make a note of the while loop in the middle of the if statement. We will come back to this later.

The final piece of work is to modify the SysTick_Handler method:

//
//    System tick handler.
//
void SysTick_Handler(void)
{
    //
    //    Generate a heartbeat.
    //
    GPIOE->ODR ^= GPIO_Pin_5;
    //
    //    If we are about to generate a rising edge on the heartbeat
    //    we are ready to start SPI data transmission.
    //
    if (GPIOE->ODR & GPIO_Pin_5)
    {
        GPIOE->BSRRH |= GPIO_Pin_6;
        DMA2_Stream5->M0AR = (uint32_t) &buffer;
        DMA_Cmd(DMA2_Stream5, ENABLE);
        SPI_I2S_DMACmd(SPI1, SPI_I2S_DMAReq_Tx, ENABLE);
    }
}

As with the previous interrupt example, this method starts the transfer process.

Putting this together in a project, compiling and deploying gives the following output on the logic analyser:

Multiple DMA SPI Transfers

Multiple DMA SPI Transfers

Remember the while loop above. This is necessary as the DMA transfer complete interrupt is generated as soon as the data has been transferred from memory to the SPI peripheral. This does not necessarily mean that the data has been transferred to the slave device connected to the SPI bus. If we did not have the loop in the handler to check that the transfer had completed we could end up in a situation where the chip select line to taken high before data transfer has completed. This is verified by the following trace on the logic analyser:

Enable Asserting Before Transfer Complete

Enable Asserting Before Transfer Complete

Note how chip select goes high when we still have nearly a full byte of data to transmit.

Conclusion

What started out as an investigation into the control of an LED panel turned into a marathon investigation into SPI on the STM32. This post has presented three different methods for controlling data transfer over SPI each having its own merits. The source code for the CooCox projects can be downloaded here:

  1. Polled SPI
  2. Interrupt driven SPI
  3. SPI and DMA

Clocks, Reset Signals and Switch Debouncing

February 16th, 2014 • Electronics, Home Built CPU, MSP430Comments Off

Every computer needs a clock circuit to synchronise the operation of the various components. A CPU is no exception. In this post we will create a clock for the CPU. We will also add reset and single step circuits.

The brief for this post is as follows:

  • The reset circuit must be capable of generating both high and low reset signals.
  • A stop/run circuit should allow the CPU to be stopped by the user and the clock single stepped.
  • Internal clock source with a master clock of 4 MHz.
  • Selectable clock frequency which is a fraction (half, quarter etc.) of the master clock source.

Reset Signals

The requirement to have both high and low reset signals is not really too complex as a single inverter can provide this capability. The most complex part of this requirement is to remove the switch bounce from the signal.

Switch De-bouncing

For new readers, switch bounce occurs due to the imperfect nature of mechanical switch. For a brief period of time when a switch is depressed or released there will be a period where the electrical signal is not stable. If you connect an oscilloscope to a switch in an electrical circuit and press the switch you are likely to see traces such as the following:

Switch Bounce

Switch Bounce

The obvious outcome of this is that the circuit can act as though the switch has been pressed multiple times in a short period of time. The answer to the problem is to de-bounce the switch. This removes the additional signals which can be generated. For microcontroller developers there are two possible solutions to this problem, software or hardware de-bouncing. As we have no conventional software environment available to us we must achieve this by hardware de-bouncing.

There are many possible solutions to this problem and you can find some discussions in the following links:

In this circuit there are two switches to debounce, the reset switch and the clock selection switch. The reset switch is a momentary switch and the clock selection switch is a rotary switch. The rotary switch should be viewed as a momentary switch for the purposes of this exercise.

The problem of debouncing the switch can be broken down into two distinct components, namely removing the jitter by rounding off the signal and the squaring off the rounded signal. The jitter can be rounded off by the use of a RC circuit. The above articles suggest that the rounded signal can be squared using an inverter with a Schmitt trigger.

Time to break out the breadboard, oscilloscope and the calculator.

The first thing to examine is the amount of bounce for the switches being used. Hooking up the momentary switch and a resistor resulted in the following traces for the depression of the switch:

Switch Bounce Depression

Switch Bounce Depression

And for the release of the switch:

Switch Bounce Release

Switch Bounce Release

Taking some measurements we see that the maximum bounce appears to be about 5ms for the momentary switch.

Repeating the exercise for the rotary switch reveals that this switch was a lot dirtier. The bouncing continued for about 10ms in the worst case.

Taking the worst case scenario we should assume that the switch is unstable for 10ms and 20ms would allow for an extreme case.

There are two possibilities for debouncing, hardware and software. For a large commercial run where a microcontroller is in use then software debouncing would be used as it requires no additional components. As this is a small run, hardware debouncing is a possibility.

Hardware Debouncing (Circuit 1)

This first hardware debouncer will use an RC network connected to a Schmitt triggered inverter. The basic circuit is:

2RC Debounce

2RC Debounce

The capacitor C4 charges through R4 and R5. When the capacitor is charged the input to the inverter is high and the output will be low.

Depressing the switch starts the discharge cycle for the capacitor. The capacitor slowly discharges through R4 until it is below the level associated with logic 0 on the inverter. The output of the inverter then goes to logic level 1. The hysteresis of the inverter makes the gate resistant to the time the voltage applied to the input of the inverter is between logic 1 and logic 0. So pressing the switch takes the output high and releasing the switch takes the output from the circuit low.

The trick with the circuit above is to select values for R4, R5 and C which eliminate the bounce at both the charge and discharge points in the circuits operation. This circuit gives an output something similar to the following:

RC Added to Switch

RC Added to Switch

The yellow trace shows the output of the circuit when the oscilloscope probe is connected to the input of the inverter. As you can see, the noisy signal has been replaced by a rapid decay at the start (when the switch is depressed) and a longer rise at the end when the switch is released.

Hardware Debouncing (Circuit 2)

In the last post I was looking at the MSP430. The intention is to look at how it can be used for software debouncing (we’ll come on to that later). The MSP430 Launchpad has a reset button connected to Port 1, pin 3 (P1.3) and this switch is also debounced (the schematics are freely available from TI’s web site). The debounce circuit on the MSP430 looks something like this:

MSP430 Launchpad Debounce Circuit

MSP430 Launchpad Debounce Circuit

Modifying this to add an inverter we get the following:

Modified MSP430 Debounce Circuit

Modified MSP430 Debounce Circuit

The discharge when the button is pressed is a lot quicker but the charging when the button is released show the same characteristics as Hardware Debouncing (Circuit 1). Examining the trace we see that the output from the inverter is good enough for the job at hand.

Software Debouncing

The algorithm for software debouncing is relatively trivial. The microcontroller waits and detects the changing input on of the the pins (say going low to high). Once detected, start a timer and wait for the worst case scenario (we set this to 20ms for the switches being used in this circuit). If at the end of the time period the signal is still high then set the output high, otherwise start the whole process again.

Now this algorithm works well for a single input to a microcontroller but what if you have multiple inputs and outputs all of which need debouncing. This is the scenario with this circuit as the rotary switch allows one selection to be made from a multiple of inputs. In this case the rotary switch acts like multiple switches. Following the above algorithm would require multiple interrupts and timers, many more than found on low end microcontrollers.

Modifying the algorithm to use timers and counters can solve this problem.

  1. Initialise an array of counters (one for each input) to 0
  2. Set the outputs to the same level as the inputs
  3. Setup a timer to trigger on a regular period (say 8ms)
  4. For each input, if the input is different to the output increment the counter for that input, otherwise set the counter to 0
  5. For each counter, if the value has exceeded the threshold count then set the output to the same level as the input

Although this is a little more convoluted it does allow switch debouncing for multiple switches with a simple and cheap microcontroller.

Software Debouncing on the MSP430

The software debouncer will be implemented in MSP430 assembler code developed using IAR Embedded Workbench.

The first thing we need is somewhere to store the counters and some workspace for the application. This application is small and so the registers on the chip should suffice.

;--------------------------------------------------------------------------------------------------
;
;   Switch debouncer for the home made CPU.  This will debounce five switches
;   four through Port 1 and 1 through port 2.
;
;   Copyright (c) Mark Stevens 2014
;
;--------------------------------------------------------------------------------------------------
#include <msp430.h>

;--------------------------------------------------------------------------------------------------
;
;   Global definitions
;
;--------------------------------------------------------------------------------------------------
;
;   Define some symbolic names for the registers holding the counts of the number of interrupts
;   for which the value on the input differs from the output.
;
#define  port0Count r4
#define  port1Count r5
#define  port2Count r6
#define  port3Count r7
;
;   Now some temporary workspace for the Watchdog ISR.
;
#define inputRegister r9
#define temporaryRegister r10
;
INTERRUPT_BASE      equ     0xffe0                  ; Base address of the interrupt vector table.
MAX_COUNTS          equ     4                       ; 4 counts equates to 24ms.

The above defines some symbolic names for the counters and maps these names onto four registers. These are also two registers reserved for workspace for the application.

;--------------------------------------------------------------------------------------------------
;
;   Main program
;
;--------------------------------------------------------------------------------------------------
                    org     0xf800                          ; Program Reset
main                mov     #0x280, SP                      ; Initialize stackpointer
StopWDT             mov.w   #WDT_MDLY_8, &WDTCTL            ; WDT ~8ms interval timer
                    bis.b   #WDTIE, &IE1                    ; Enable WDT interrupt
                    ;
                    ;   Setup the counters.
                    ;
                    mov.w   #0, port0Count
                    mov.w   #0, port1Count
                    mov.w   #0, port2Count
                    mov.w   #0, port3Count
                    ;
                    ;   P1.0, 2, 4, 6 are inputs, P1.1, 3, 5, 7 are outputs.
                    ;
                    bis.b   #0xaa, &P1DIR
                    mov.b   &P1IN, &P1OUT                   ; Start with the outputs equal to the inputs.
                    ;
                    ;   Put the chip to sleep waiting for interupts.  Put it back to sleep
                    ;   if it ever wakes up.
                    ;
Mainloop            bis.w   #CPUOFF + GIE, SR               ; CPU off, enable interrupts
                    jmp     Mainloop

The main program initialises the counters and the ports and turns on the watchdog interrupt (triggered every 8ms – approx). The main program then puts the chip to sleep until the Watchdog interrupt fires.

;--------------------------------------------------------------------------------------------------
;
;   Watchdog interrupt
;
;   Check the input pins (0, 2, 4, 6) and if they are different to the output pins then increment
;   the counter for that pin.
;
;   If the counter reaches MAX_COUNT then transfer the input to the output.
;
;   If the input equals the output then set the counter to 0.
;
;--------------------------------------------------------------------------------------------------
WatchdogISR         mov.b   &P1IN, inputRegister
                    mov.b   &P1OUT, temporaryRegister
                    clrc
                    rrc     temporaryRegister
                    xor.b   temporaryRegister, inputRegister
                    and     #BIT0, inputRegister
                    jz      resetPort0Counter               ; Input bit and output bit are the same.
                    ;
                    ;   Now check the port counters
                    ;
                    inc     port0Count
                    cmp     #MAX_COUNTS, port0Count
                    jne     checkPort2                      ; Not enough differences.
                    ;
                    ;   Transfer the input to the output and reset the counter.
                    ;
                    bit.b   #BIT0, &P1IN
                    jz      resetPort0
                    bis.b   #BIT1, &P1OUT
                    jmp     resetPort0Counter
resetPort0          bic.b   #BIT1, &P1OUT
resetPort0Counter   mov.w   #0, port0Count
checkPort2          ;
                    ;   Checking the remaining ports is left as an exercise for later.
                    ;
                    reti

This is where calculations are performed. Note that the above code only works for one input pin but it can easily be extended to work with four pins. The first thing this routine does is to extract the input and output states of the port. It then works out if there is a difference between the input on P1.0 and the output on P1.1.

The middle block of code increments the counter and checks to see if the counter has reached the threshold. If it hasn’t then we move on to the next port.

The final block of code is executed if the port input/output are different. It transfers the input to the output and then resets the counter.

Deploying this code to the MSP430 and connecting a switch gave the following trace on the oscilloscope:

Switch With Delay

Switch With Delay

As you can see, there is a delay between the switch being depressed and the output being set. The blue trace is wired to the microcontroller input and the yellow trace is the output.

Selecting a debouncer

After many hours working on this circuit I have elected to go with Hardware Debouncing (Circuit 2) with the addition of the Schmitt inverter. This circuit is relatively simple and also meets my long term design goals for this project, namely to use only 1970/80′s technology and using logic gates where possible. It also removes the issue that the MSP430 has a maximum supply voltage of 3.6V and the remainder of this project will be working at 5V.

Selecting the Inverter

Throughout this design process I hit an issue with the inverters I have. I was trying to use the 74LS14 hex Schmitt inverter. For some reason this chip was outputting 1V on the input pin of the logic gate. I could not get the circuits above to work and in the end I ordered some 74HCT14 Schmitt Inverters. Voila, I now have a working debounce circuit.

Something to investigate later…

Reset Circuit

One possibility would be to use the output from the debounced reset switch as a reset signal. This would lead to a variable length reset pulse. Whilst an overly long reset pulse would not be an issue there may be problems if the pulse is too small. This can be solved by using an NE555 timer in monostable mode. The reset signal will still be variable but it will never be shorter than the defined by the components used to control the NE555.

The schematic for the basic monostable NE555 circuit looks as follows:

NE555 Reset Circuit

NE555 Reset Circuit

The length of the pulse is determined by the following formula:

t ~= 1.1RC

In our case R = R3 and C = C2. So this gives:

t ~= 1.1 * R3 * C2>
=> t ~= 1.1 * 45,000 * 100 * 10-9
=> t ~= 4.95ms

At 4.95ms this might be a little close to the 5ms bounce seen by some of the switches in use. Increasing the values of R3 and C = C2 will increase the duration of the reset pulse. Making R3 = 180K we see the following at the end of the signal:

NE555 End of  Reset Signal

NE555 End of Reset Signal

The full reset pulse looks like this:

NE555 Full Reset Pulse

NE555 Full Reset Pulse

Clock Circuit

The clock for the project can be broken down into two parts:

  • Oscillator
  • Clock Divider
  • Oscillator Circuit

    The oscillator circuit will provide the circuit with the “master clock”. These have been used previously on this blog in the TLC5940 circuit developed a few years ago. In the spirit of reusing old work where possible we will use the basic oscillator circuit from that post here. By changing the 8 MHz resonator for a 4 MHz part we should be able to achieve a 4 MHz master clock signal.

    The schematic for the oscillator can be found below.

    Dividing the Clock

    Dividing the clock circuit by two is easy enough to achieve using a flip-flop. The basic logic diagram is as follows:

    Flip-Flop Divider

    Flip-Flop Divider

    Circuit courtesy of Electronics Tutorials.

    This is easily implemented using the 7474 Dual D-Type Flip-Flops with Preset and Clear.

    The selection of the clock should also be easy if we make the decision that it is invalid to change the clock frequency whilst the system is running. We will rely upon the user to have the discipline not to change the clock in run mode. Since this is an initial design we can look at changing it later.

    This decision means that initially we do not have to worry about glitches due to the clock frequency being changed.

    The selection of the clock frequency can be achieved by feeding all of the clocks into one input of a dual input AND gate. The second input to the gate can then be connected to a rotary switch. The switch will connect the second input to high for the selected frequency. All of the inputs to the other gates will then be grounded. This means that only one gate allows a clock signal through to the system clock signal output.

    We can also use the debounce circuit described above to ensure a smooth transition from one clock to another.

    The schematic for this looks like this:

    Oscillator and Clock Dividers

    Oscillator and Clock Dividers

    By hooking up the logic analyser to the system clock output and the various outputs from the clock divider circuit we see the following:

    Clock and Flip Flop Output

    Clock and Flip Flop Output

    Hooking up the rotary switch to the debounce circuit and the AND gates will allow the selection of the master clock output.

    Conclusion

    The home built CPU is on it’s way. We now have a heart beat (clock) for the circuit and a method for issuing a reset request to the circuit.

    One final refinement would be the addition of a buffer driver chip to the circuit to allow for the fact that the clock and reset signals will be being fed into a larger circuit.

    MSP430 Launchpad

    February 9th, 2014 • Electronics, MSP4303 Comments »

    Shortly after it’s launch, I purchased a Texas Instruments MSP430 Launchpad board. It was about the time I had started to move away from the Netduino family of microcontrollers and started to look at alternatives to stretch myself a little more. I never managed to get the board talking to my laptop at the time and eventually gave up in frustration.

    The last week I found the board and in possession of a new laptop and new OS I decided to give it one final go. A few downloads later (about 500MB to be precise) and I have it working!

    MSP430 Launchpad

    The MSP430 Launchpad (MSP430-EXP430G2) is a small development board for the MSP430 value line microcontrollers. The board is supplied with two variants of the MSP43 microcontroller, namely the MSP430G2231 and MSP430G2211 (both in DIP form).

    I was attracted to this board for a few reasons:

    • DIP packaging means that they can be used on breadboard (also available as SMD components)
    • Cost. The microcontrollers themselves are available for as little as 50p in the UK
    • Available in a wide variety of options (memory etc.)
    • Free development tools

    The board is not a large board and it contains very little circuitry apart from the integrated debugger.

    MSP430 Launchpad

    MSP430 Launchpad

    Only thing which remained was to see if I could get it working on my PC.

    Development Tools

    When it comes to developing for the MSP430 there are two options which I considered, the Texas Instruments Code Composer Studio and IAR systems Embedded Workbench.

    Code Composer Studio is based on the popular Eclipse platform. The installation was quick and simple. The installer even asked if the add-ons for the MSP430 should be added into an existing Eclipse installation. This software does not appear to have any code restrictions.

    The IAR installation is the same IAR Embedded Workbench which I have used in the past with the STM8S. The only difference is the addition of MSP430 support. The free Kickstarter edition of this software is limited to 4Kbytes of code for traditional MSP430 devices (8KBytes for MSP430X) and you are not allowed to use the system to develop commercial applications.

    I have installed both of these tools as they both have their merits. I like the IAR compiler suite as I have one tool which I can use for the development of both MSP430 and STM8S applications. The code size limitation is unlikely to be an issue at the moment and as a hobbyist I am not intending to use the tool commercially.

    Code Composer has the advantage that it has a little integration with Texas instruments web site and examples. For instance, the Resource explorer will allow you to easily access the MSP430 Wiki pages. These are really minor advantages as the examples are freely available and the Wiki web site is easily found. The main advantage with Code Composer is that there does not appear to be any restriction on use.

    Other tools are also available and more information can be found on the Texas Instruments web site.

    Hardware

    The MSP430 Launchpad board is split into two areas. The upper quarter of the board contains the circuitry necessary to support programming chip and debugging the application deployed to the chip. The lower three quarters of the board are home to the 20 pin DIP socket for the MSP430, some headers, switches and LEDs. The headers will need to be added when you receive the board.

    The switches and two LEDs are connected to the MSP430 by default and are great for the beginner as you can start to interact with the microcontroller immediately.

    There is also space for an external oscillator should one be required.

    Connecting the Board

    This is where I ran into problems last time and sure enough it is where I ran into problems this time.

    Having installed Code Composer I expected to have everything installed which was required to start using the MSP430 Launchpad board. So I fired up Code Composer, set up a new project using one of the Blinky LED examples, plugged in the board and prepared to hit the Debug button.

    Plugging in the board gave me the usual message from Windows about a new USB device and the system set off looking for drivers. A minute or so later and I received the wonderful news that the driver could not be found.

    Great! Say hello to Mr Google.

    After a little searching I found some articles which indicated that the drivers needed updating (I’d already guessed that but always good to have it confirmed). A little more searching led me to the driver download page.

    A quick installation later and the board was still not recognised. Try another USB port (getting desperate now). Driver installation window pops up and this time no error. Hitting the debug button in Code Composer deployed my Blinky sample application.

    Interestingly, when I plugged the board into the original USB port it was recognised this time and I could deploy and debug from that port as well.

    Generating a Pulse

    Now I have a working environment it is time to write my own application. The first application I am going to attempt will generate a pulse using the internal watchdog. This is not a million miles away from the Blinky application in the samples. The main difference is that rather than use a loop to determine when the pulses are generated we will be using the in built watchdog.

    I have chose to use IAR for this project as I am already familiar with the tool. Setting up a new project works the same way as using this platform for the STM8S. The only real difference is the need to change the device and debugger types.

    First Step is to create and empty project:

    • Create a new workspace (File->New Workspace)
    • Create a new project (Project-%gt;Create New Project)
    • Select the MSP430 tool chain and a C project with a main application
    • Decide where you are going to put the application and give the project a name

    You should now have a project with one file (main.c) with the following code:

    #include "io430.h"
    
    int main( void )
    {
      // Stop watchdog timer to prevent time out reset
      WDTCTL = WDTPW + WDTHOLD;
    
      return 0;
    }
    

    Next step is to compile the code. This will force the environment to save the project workspace (only the project was saved in the above steps). So press F7 to make the project and enter a name for the workspace in the dialog which is presented. I used the same name for the workspace as I did for the project.

    Next we need to set up the device and debugger types. The device I am using is the MSP430G2231. This is the default chip installed on the board when I purchased the Launchpad. Right click on the project name in the Workspace section of the Embedded Workbench IDE and select Options…

    MSP430 Project Options

    MSP430 Project Options

    In the General Options of the dialog select the device type from the list:

    Set Device Type

    Set Device Type

    Now change the debugger type (driver) from Simulator to FET Debugger:

    Set Debugger Type

    Set Debugger Type

    Next, ensure that the connection is set to Texas Instruments USB-IF:

    Set Debugger Connection Type

    Set Debugger Connection Type

    We are now ready to type in some code and test the connection. Press Ctrl-D in the IDE and the system should connect to the launchpad and deploy the application to the chip. The IDE will highlight the first line of code to be executed:

    MSP430 Application Deployment Successful

    MSP430 Application Deployment Successful

    We are now ready to write our first application.

    #include "io430.h"
    
    //
    //  Watchdog timer ISR.
    //
    #pragma vector = WDT_VECTOR
    __interrupt void WDT_ISR(void)
    {
        //
        //  Clear the interrupt flag for watchdog timer.
        //
        IFG1 &= ~WDTIFG;
        //
        //  Toggle the output pin.
        //
        P1OUT = P1OUT ^ BIT7;
        //
        //  Go back to sleep.
        //
        __bis_SR_register_on_exit(LPM0_bits);
    }
    
    //
    //  Main program loop.
    //
    int main( void )
    {
        //
        //  Set the watchdog to 0.5ms trigger and enable the interrupt.
        //
        WDTCTL = WDT_MDLY_0_5;
        IE1 |= WDTIE;
        //
        //  Configure pin 7 on port 1 as an output pin.
        //
        P1DIR = BIT7;
        //
        //  Set pin 7 high.
        //
        P1OUT = BIT7;
        while   (1)
        {
            __bis_SR_register(LPM0_bits + GIE);
        }
    }
    

    Deploying this application to the chip and connecting up an oscilloscope to Port 1, Pin 7 gives the following trace:

    1.14KHz Pulse From MSP430

    1.14KHz Pulse From MSP430

    Success !

    Conclusion

    The availability of the MSP430 in DIP format makes it an interesting option for prototyping as you can easily breadboard your circuit. The STM8S which I have been using for a couple of years now can be breadboarded but only with the use of additional components.

    The example above should have generated a 1KHz signal as the watchdog was configured to generate an interrupt every 0.5ms. In fact the frequency was a little higher as the interrupt was being generated every 440uS instead. This is not uncommon when using the internal oscillator in a microcontroller. The frequency could be made more accurate by using an external crystal but this would complicate the circuit. For most hobbyist purposes this is accurate enough.

    Simulating the 74HC373

    January 2nd, 2014 • Electronics, FPGAComments Off

    In my last post on VHDL, I looked at simulating the 74HC373 using the Free Model Foundry std373 transparent latch component. This component simulated a single transparent latch and allowed the addition of timing information to give a more realistic simulation.

    The 74HC373 contains eight of these transparent latches in a single DIP package with a single latch enable and single output enable line. This post builds on the earlier work and simulates the DIP package rather than the single latch.

    Test Case

    Before we progress we should remind ourselves of the test bench created for the single latch. This changed the data inputs (D, le and oe) to the single latched and verified the output traces against the output expected. The simulated output looked like this:

    Simulation With Added Timing Data

    Simulation With Added Timing Data

    For this example we will use exactly the same sequence of events but use eight data bits rather than just a single bit. The sequence of events should look the same but the data (when present) will reflect the fact that more than one data line is being manipulated.

    Implementing the 74HC373

    Being new to VHDL and the design process using this language, the final solution took an hour or two to realise. I considered various options but all tended to be code related which is probably natural giving my software development background.

    Then inspiration struck and I came up with the following code:

    library IEEE;
    use IEEE.STD_LOGIC_1164.ALL;
    
    entity HC74373 is
        port ( dataIn : in  STD_LOGIC_VECTOR (7 downto 0);
               dataOut : out  STD_LOGIC_VECTOR (7 downto 0);
               le : in  STD_LOGIC;
               oe : in  STD_LOGIC);
    end HC74373;
    
    architecture Behavioral of HC74373 is
        component std373 is 
            port
            (
                Q : out std_logic;
                D, LE, OENeg : in std_logic
            );
        end component;
    begin
        d0: std373 port map (LE => le, OENeg => oe, D => dataIn(0), Q => dataOut(0));
        d1: std373 port map (LE => le, OENeg => oe, D => dataIn(1), Q => dataOut(1));
        d2: std373 port map (LE => le, OENeg => oe, D => dataIn(2), Q => dataOut(2));
        d3: std373 port map (LE => le, OENeg => oe, D => dataIn(3), Q => dataOut(3));
        d4: std373 port map (LE => le, OENeg => oe, D => dataIn(4), Q => dataOut(4));
        d5: std373 port map (LE => le, OENeg => oe, D => dataIn(5), Q => dataOut(5));
        d6: std373 port map (LE => le, OENeg => oe, D => dataIn(6), Q => dataOut(6));
        d7: std373 port map (LE => le, OENeg => oe, D => dataIn(7), Q => dataOut(7));
    end Behavioral;
    

    No coding (as in conventional software development). All of the work is performed by the mapping of the ports from the std_logic_vector to the bits std_logic inputs and outputs to the std373 component.

    Testing

    As already mentioned, the sequence of events we will use as a test case will match tests for a single latch. The test bench becomes:

    LIBRARY ieee;
    USE ieee.std_logic_1164.ALL;
     
    -- Uncomment the following library declaration if using
    -- arithmetic functions with Signed or Unsigned values
    --USE ieee.numeric_std.ALL;
     
    ENTITY HC74373TestBench IS
    END HC74373TestBench;
     
    ARCHITECTURE behavior OF HC74373TestBench IS 
     
        -- Component Declaration for the Unit Under Test (UUT)
     
        COMPONENT HC74373
        PORT(
             dataIn : IN  std_logic_vector(7 downto 0);
             dataOut : OUT  std_logic_vector(7 downto 0);
             le : IN  std_logic;
             oe : IN  std_logic
            );
        END COMPONENT;
        
    
       --Inputs
        signal dataIn : std_logic_vector(7 downto 0) := (others => '0');
        signal le : std_logic := '1';
        signal oe : std_logic := '1';
    
     	--Outputs
        signal dataOut : std_logic_vector(7 downto 0);
        signal clock : std_logic := '0';
    BEGIN
     
    	-- Instantiate the Unit Under Test (UUT)
        uut: HC74373 PORT MAP
        (
            dataIn => dataIn,
            dataOut => dataOut,
            le => le,
            oe => oe
        );
    
        -- Clock process definitions
        clockProcess: process
        begin
    		clock <= '0';
    		wait for 125 ns;
    		clock <= '1';
    		wait for 125 ns;
        end process;
     
    
        -- Stimulus process
        stim_proc: process
        begin		
            --
            --  Initial condition, latch should have stabilised and be high impedance.
            --
            wait for 125 ns;
            --
            --  Set data in to all zeroes, output should still be high impedance.
            --
            le <= '0';
            wait for 125 ns;
            --
            --  Now enable the output.
            --
            oe <= '0';
            wait for 125 ns;
            --
            --  Changing the data whilst LE is low does not change the output.
            --
            dataIn <= "11110000";
            wait for 125 ns;
            --
            --  Now set the data in whilst latch and output enable are both allowed.
            --  Data output should become 11001100.
            --
            le <= '1';
            wait for 125 ns;
            --
            --  Now set the data in whilst latch and output enable are both allowed.
            --  Data output should become 11110000.
            --
            dataIn <= "10101010";
            wait for 125 ns;
            --
            --  Turn off the latch enable and set the data in bits.  The data output 
            --  should not change.
            --
            le <= '1';
            dataIn <= "00110011";
            wait for 125 ns;
            --
            --  Re-enable the output, should become 10101010 as 00110011 has not
            --  been latched yet.
            --
            oe <= '1';
            wait for 125 ns;
            --
            --  Output is turned off, latch the data and enable the output.
            --
            le <= '0';
            wait for 125 ns;
            le <= '1';
            oe <= '0';
            wait for 125 ns;
            --
            --  End of test.
            --
            wait;
        end process;
    END;
    

    The output from the simulator is:

    74HC373 Simulation Output

    74HC373 Simulation Output

    As you can see, the sequence of events for the single latch matches those for the 74HC373 component.

    Conclusion

    Now I look at this solution it seems obvious. Something to bear in mind as I start to use more and more components from the Free Model Foundry library.

    Next stop a buffer driver.

    Why Do We Prototype?

    January 1st, 2014 • Electronics, STM8Comments Off

    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 PMP 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 botht he 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.

    Using the Free Model Foundry 373 Latch Model

    December 28th, 2013 • Electronics, FPGA1 Comment »

    The last post on VHDL/FPGA presented a naive implementation of a 74*373 latch and went as far as adding a little timing information to make the behavioural simulation a little more realistic. The post also hinted at the fact that there are some free VHDL libraries out on the net. This post uses a part from one of these libraries and shows how to add more accurate timing information to the behavioural simulation.

    Free Model Foundry

    The Free Model Foundry is an open source repository of VHDL and Verilog models. The models are freely available and the majority of the models are provided with timing information.

    The 74xxxx series of chips can be located in the STD series of models. A quick check reveals that the library contains a 74*373 behavioural model in the file std373.vhd.

    So let’s create a project and check out this model.

    Creating a Project

    First thing to do after starting Xilinx Webpack ISE is to create a new project to hold the circuit. From the startup screen select New Project:

    ISE Project Navigator Start Up

    ISE Project Navigator Start Up

    Now give the project a name. I selected FMF373Example, gave the project a short description and set the Top Level Source TYpe to HDL.

    New Project Wizard Screen 01

    New Project Wizard Screen 01

    Next set the device information:

    New Project Wizard Screen 02

    New Project Wizard Screen 02

    The final window displays a summary of the project information:

    New Project Wizard Screen 03

    New Project Wizard Screen 03

    The next step is to add the model to the empty project we have just created. Right click on the project and select Add Copy of Source…. Now navigate to the directory containing the VHDL source files for the FMF models and select the file std373.vhd. This will copy the file from the directory containing the original model files and place a new copy in the project directory.

    Opening the source file for this model shows that the Free Model Foundry implementation is vastly more complex than the model presented in the previous post.

    One of the first things to notice at the top of the file is that this model uses a number of libraries not seen in the previous posts, namely:

    • IEEE.VITAL_timing
    • IEEE.VITAL_primitives
    • FMF.gen_utils
    • FMF.ff_pacakge

    The FMF libraries are supplied by the Free Model Foundry we simply need to add them to the project. Select the Libraries tab:

    Libraries Tab

    Libraries Tab

    and add a New VHDL Library:

    Add New Library

    Add New Library

    Name the library FMF and set the directory for the files to the location where the generic files are located on your machine:

    FMF Library Properties

    FMF Library Properties

    Next, add the files:

    FMF Library Files

    FMF Library Files

    Next step, check that we can simulate the model we have so far. This will verify that all of the libraries are in place and the model compiles. Select Simulation and then open the simulator by right clicking on Simlate Behavioural Model and select run all. You should see something like the following:

    Simulation Run Without Test Bench

    Simulation Run Without Test Bench

    Close the simulator and return to the ISE Project Navigator.

    Next step is to add a test bench. Follow the instructions in the previous post and add a new test bench. The system fails at this point with the message:

    ERROR:HDLParsers:3014 – “E:/MarksDocs/Circuits/Xilinx FPGA/Library/Free Model Foundary/Generic Packages/gen_utils.vhd” Line 23. Library unit VITAL_primitives is not available in library IEEE.

    To date I have still not been able to find a satisfactory answer to why this happens. Even Google is being of little help at the moment. To work around this I modified the code in the new test bench code window which was opened and created the following test bench:

    LIBRARY ieee;
    USE ieee.std_logic_1164.ALL;
     
    -- Uncomment the following library declaration if using
    -- arithmetic functions with Signed or Unsigned values
    --USE ieee.numeric_std.ALL;
     
    ENTITY LatchTest IS
    END LatchTest;
     
    ARCHITECTURE behavior OF LatchTest IS 
     
        -- Component Declaration for the Unit Under Test (UUT)
        component std373
        port
        (
             LE : in  std_logic;
             OENeg : in  std_logic;
             D : in  std_logic;
             Q : out  std_logic
        );
        end component;
        
        --
        --  Inputs
        --
        signal le : std_logic := '1';
        signal oe : std_logic := '1';
        signal dataIn : std_logic := '0';
        --
     	--  Outputs
        --
        signal dataOut : std_logic;
        --
        --  Clock signal.
        --
        signal clock : std_logic := '0';
    begin
     
    	-- Instantiate the Unit Under Test (UUT)
        uut: std373 PORT MAP
        (
            LE => le,
            OENeg => oe,
            D => dataIn,
            Q => dataOut
        );
        
        --
        --  Provide an 8 MHz reference clock signal.
        --
        clockProcess: process
        begin
            clock <= '1';
            wait for 125 ns;
            clock <= '0';
            wait for 125 ns;
        end process;
        
        process
        begin		
            --
            --  Initial condition, latch should have stabilised and be high impedance.
            --
            wait for 125 ns;
            
            le <= '0';
            wait for 125 ns;
            
            oe <= '0';
            wait for 125 ns;
            
            dataIn <= '1';
            wait for 125 ns;
            
            le <= '1';
            dataIn <= '1';
            wait for 125 ns;
            
            dataIn <= '0';
            wait for 125 ns;
    
            le <= '1';
            dataIn <= '1';
            wait for 125 ns;
    
            oe <= '1';
            wait for 125 ns;
    
            le <= '0';
            wait for 125 ns;
    
            le <= '1';
            oe <= '0';
            wait for 125 ns;
            --
            --  End of test.
            --
            wait;
        end process;
    end;
    

    Once entered, go back to the process window in the ISE Project Explorer, select Simulate Behavioural Model, right click and select ReRun All. The simulator windows should not open and show the following view when the global (full run) view has been selected:

    Zoomed In Starting Simulation

    Zoomed In Starting Simulation

    Now if we zoom in on the trace to the 250ns section of the simulation you can see that there is a 1ns gap between the OE line dropping from 1 to 0 and the data on the dataOut line changing from high impedance to 0:

    ZoomedIn At 250ns

    ZoomedIn At 250ns

    We will now have a closer look at the code and examine why this happens.

    STD373 – Transparent Latch

    One of the first things to note about the STD373 implementation is that this file only contains a behavioural model for a single latch.

    The 74*373 is actually an octal transparent latch where each latch has it’s own distinct data input and out put lines but all share a common latch enable and output enable line. eight of these latches are required in order to simulate a single 74*373 chip.

    The second pint is that this file contains the abstracted behavioural model for a latch only. This does not include any additional implementation code for a synthesis-able model.

    STD373 Entity Declaration

    At the top of the std373.vhd file is the following code:

    --------------------------------------------------------------------------------
    -- ENTITY DECLARATION
    --------------------------------------------------------------------------------
    ENTITY std373 IS
        GENERIC (
            -- tipd delays: interconnect path delays
            tipd_D              : VitalDelayType01 := VitalZeroDelay01;
            tipd_LE             : VitalDelayType01 := VitalZeroDelay01;
            tipd_OENeg          : VitalDelayType01 := VitalZeroDelay01;
            -- tpd delays
            tpd_D_Q             : VitalDelayType01 := UnitDelay01;
            tpd_LE_Q            : VitalDelayType01 := UnitDelay01;
            tpd_OENeg_Q         : VitalDelayType01Z := UnitDelay01Z;
            -- tsetup values: setup times
            tsetup_D_LE         : VitalDelayType := UnitDelay;
            -- thold values: hold times
            thold_D_LE          : VitalDelayType := UnitDelay;
            -- tpw values: pulse widths
            tpw_LE_posedge      : VitalDelayType := UnitDelay;
            tpw_LE_negedge      : VitalDelayType := UnitDelay;
            -- tperiod_min: minimum clock period = 1/max freq
            tperiod_LE_posedge  : VitalDelayType := UnitDelay;
            -- generic control parameters
            TimingChecksOn      : Boolean  := DefaultTimingChecks;
            MsgOn               : BOOLEAN := DefaultMsgOn;
            XOn                 : Boolean  := DefaultXOn;
            InstancePath        : STRING   := DefaultInstancePath;
            -- For FMF SDF technology file usage
            TimingModel         : STRING   := DefaultTimingModel
        );
        PORT (
            Q       : OUT   std_logic := 'U';
            D       : IN    std_logic := 'X';
            LE      : IN    std_logic := 'X';
            OENeg   : IN    std_logic := 'X'
        );
    
        ATTRIBUTE VITAL_LEVEL0 of std373 : ENTITY IS TRUE;
    END std373;
    

    This declaration details the port specifications for the model along with the timing information.

    Port Specification

    The port specification tells us that this model has only three inputs and one output and that these are of type std_logic. This tells us that this is a single latch as all of the ports are single signals.

    Timing Information

    The upper section of the declaration contains the timing information. This information is used throughout the model and these values influence the timing of the signals in the simulation. Remember the 1 ns delay in the above trace. This comes from the UnitDelay01 delays set in this timing block.

    A little digging around the internet lead me to the file timing_p.vhd (on my machine this was installed in the directory C:\Xilinx\14.3\ISE_DS\PlanAhead\scripts\rt\data\vhdl\pkgs). This file contains the types, attributes, constants and functions/procedures for the timing models. Opening this file gives the definitions for the types and constants used in the generic code block above. The top of the file looks like this:

    TYPE VitalTransitionType IS ( tr01, tr10, tr0z, trz1, tr1z, trz0,
                                  tr0X, trx1, tr1x, trx0, trxz, trzx);
    
    SUBTYPE VitalDelayType     IS TIME;
    TYPE VitalDelayType01   IS ARRAY (VitalTransitionType   RANGE tr01 to tr10)
         OF TIME;
    TYPE VitalDelayType01Z  IS ARRAY (VitalTransitionType   RANGE tr01 to trz0)
         OF TIME;
    TYPE VitalDelayType01ZX IS ARRAY (VitalTransitionType   RANGE tr01 to trzx)
         OF TIME;
    
    TYPE VitalDelayArrayType     IS ARRAY (NATURAL RANGE <>) OF VitalDelayType;
    TYPE VitalDelayArrayType01   IS ARRAY (NATURAL RANGE <>) OF VitalDelayType01;
    TYPE VitalDelayArrayType01Z  IS ARRAY (NATURAL RANGE <>) OF VitalDelayType01Z;
    TYPE VitalDelayArrayType01ZX IS ARRAY (NATURAL RANGE <>) OF VitalDelayType01ZX;
    -- ----------------------------------------------------------------------
    -- **********************************************************************
    -- ----------------------------------------------------------------------
    
    CONSTANT VitalZeroDelay     : VitalDelayType     :=   0 ns;
    CONSTANT VitalZeroDelay01   : VitalDelayType01   := ( 0 ns, 0 ns );
    CONSTANT VitalZeroDelay01Z  : VitalDelayType01Z  := ( OTHERS => 0 ns );
    CONSTANT VitalZeroDelay01ZX : VitalDelayType01ZX := ( OTHERS => 0 ns );
    
    ---------------------------------------------------------------------------
    -- examples of usage:
    ---------------------------------------------------------------------------
    -- tpd_CLK_Q : VitalDelayType  := 5 ns;
    -- tpd_CLK_Q : VitalDelayType01  := (tr01 => 2 ns, tr10 => 3 ns);
    -- tpd_CLK_Q : VitalDelayType01Z := ( 1 ns, 2 ns, 3 ns, 4 ns, 5 ns, 6 ns );
    -- tpd_CLK_Q : VitalDelayArrayType(0 to 1)
    --                          := (0 => 5 ns, 1 => 6 ns);
    -- tpd_CLK_Q : VitalDelayArrayType01(0 to 1)
    --                          := (0 => (tr01 => 2 ns, tr10 => 3 ns),
    --                              1 => (tr01 => 2 ns, tr10 => 3 ns));
    -- tpd_CLK_Q : VitalDelayArrayType01Z(0 to 1)
    --                        := (0 => ( 1 ns, 2 ns, 3 ns, 4 ns, 5 ns, 6 ns ),
    --                            1 => ( 1 ns, 2 ns, 3 ns, 4 ns, 5 ns, 6 ns ));
    ---------------------------------------------------------------------------
    

    The key to the problem for me was the comment block at the bottom of the block as this gives examples of how to use the types.

    Setting The Timing Information

    Some of the Free Model Foundry libraries contain timing information along with some tools detailing how the timing information can be used. At the moment I have been unable to work out how to use these tools and the timing information.

    What I believe I have been able to do is to take the timing information in the data sheet for the 74HC373 and combine this with the model to adjust the behaviour of the code above. Here’s how I did it.

    Looking at the VitalTransitionType there are eight types defined:

    TYPE VitalTransitionType IS ( tr01, tr10, tr0z, trz1, tr1z, trz0,
                                  tr0X, trx1, tr1x, trx0, trxz, trzx);
    

    A little thought lead me to believe that the types are symbolic names for the transitions of the digital signals. So tr01 represents the transition from 0 to 1, tr0z represents a transition from 0 to high impedance etc.

    Next we have the following code:

    TYPE VitalDelayType01 IS ARRAY (VitalTransitionType RANGE tr01 to tr10) OF TIME;
    

    This line of code defines a type containing two time elements. I believe that these represent the time delays for signals changing from 01 to 1 and also from 1 to 0.

    If we combine this information with the following code from the entity definition of the std373 component:

    -- tpd delays
    tpd_D_Q             : VitalDelayType01 := UnitDelay01;
    

    Here tpd_D_Q represents the time it takes for the input data (D) to be reflected on the output (Q). From the data sheet for the 74HC373 this is 18ns. Changing the above line to:

    tpd_D_Q             : VitalDelayType01 := (tr01 => 18 ns, tr10 => 18 ns);
    

    Making a few more changes using the information in the data sheet gives the following timing code:

    -- tipd delays: interconnect path delays
    tipd_D              : VitalDelayType01 := VitalZeroDelay01;
    tipd_LE             : VitalDelayType01 := VitalZeroDelay01;
    tipd_OENeg          : VitalDelayType01 := VitalZeroDelay01;
    -- tpd delays
    tpd_D_Q             : VitalDelayType01 := (tr01 => 18 ns, tr10 => 18 ns);
    tpd_LE_Q            : VitalDelayType01 := (tr01 => 30 ns, tr10 => 30 ns);
    tpd_OENeg_Q         : VitalDelayType01Z := (tr01 => 12 ns, tr10 => 12 ns, tr0z => 12 ns, trz1 => 15 ns, tr1z => 9 ns, trz0 => 18 ns);
    -- tsetup values: setup times
    tsetup_D_LE         : VitalDelayType := 10 ns;
    -- thold values: hold times
    thold_D_LE          : VitalDelayType := 10 ns;
    -- tpw values: pulse widths
    tpw_LE_posedge      : VitalDelayType := 6 ns;
    tpw_LE_negedge      : VitalDelayType := 7.3 ns;
    -- tperiod_min: minimum clock period = 1/max freq
    tperiod_LE_posedge  : VitalDelayType := 10 ns;
    

    Running this through the simulator using the test bench gives the following trace:

    Simulation With Added Timing Data

    Simulation With Added Timing Data

    The trace shows that there is a delay between the data appearing on the data input and the same data appearing on the output pin. This appears to simulate the expected behaviour of a single latch using the timing parameters from the data sheet.

    Conclusion

    I am sure that there is a much better way to use timing information and further research is required into the simulation tools and the data from the models. The techniques presented here allow the use of the timing information from the data sheet in order to simulate the behaviour of the desired component.

    This post was challenging to write as there is very little information easily available on the IEEE VITAL library. Even Google was of little help in this area. I have presented my understanding of the topic from the little information I was able to obtain. Looking at the results in the simulator it does appear that this technique is valid even if it is a little long winded. Hopefully I will find a way of using this information in a more efficient method once I am more familiar with the tools.

    Next steps, combine eight of these to form a 74*373 latch and more research into the tools.

    Simulating A 74HC373 Latch

    December 25th, 2013 • Electronics, FPGAComments Off

    Some of my current projects are starting to require the use of 7400 series logic chips. Prototyping circuits with these chips gives me two obvious options:

    1. Breadboarding
    2. FPGA, simulation and implementation

    Both of these will allow a prototype to be build but the only way to ensure the FPGA circuit works will be to implement the circuit on breadboard. The simulation option does offer two advantages over breadboarding, namely the ability to simulate and most importantly the chance to work out how to use FPGAs and the associated software.

    Given the choice there is really only one option as I need to gain experience of VHDL, bring on the FPGA.

    74*373 – Octal Transparent Latch with Three State Output

    This is an interesting choice for simulation as the Xilinx software issues a warning about the creation of a latch when clocks are not used for synchronisation of signals. The reason for choosing this chip is simply the fact that this is one of the first chips I will be using in a larger project.

    First Implementation

    This first implementation concentrated on the functional definition of the latch. Examination of the data sheet for the chip describes the function as follows:

    OE (active low) LE (active low) Data Output
    L H H H
    L H L L
    L L l L
    L L h H
    H X X Z

    Where L = Low, H = High, Z = High impedance, X = Don’t care, l = low (h = high) as the signal appeared one setup period ago.

    A quick implementation of this could be something like the following:

    library ieee;
    use ieee.std_logic_1164.all;
    
    --
    --  Define the interface for this module.
    --
    entity HC74373 is
        port
        (
            signal le : in  std_logic;
            signal oe : in  std_logic;
            signal dataIn : in  std_logic_vector (7 downto 0);
            signal dataOut : out  std_logic_vector (7 downto 0)
        );
    end HC74373;
    
    --
    --  Behaviour of the model.
    --
    architecture Behavioral of HC74373 is
        --
        --  Store the data in the latch for output.
        --
        signal data : std_logic_vector (7 downto 0) := "00000000";
        --
        --  Temporary store for this model.  This will eventually be
        --  the output.
        --
        signal output : std_logic_vector (7 downto 0) := "ZZZZZZZZ";
    begin
        process (le, oe, dataIn, data) is
        begin
            --
            --  When LE is low, the data on the input is stored in the latch.
            --
            if (le = '0') then
                data <= dataIn;
            end if;
            --
            --  When OE is low, the data in the latch is output, 
            --  otherwise the output is high impedance.
            --
            if (oe = '0') then
                if (le = '1') then
                    output <= dataIn;
                  else
                    output <= data;
                end if;
              else
                output <= "ZZZZZZZZ";
            end if;
        end process;
        --
        --  Set the output to either the data in the latch or high impedance.
        --
        dataOut <= output;
    end Behavioral;
    

    A test bench is needed to simulate the behaviour of the latch.

    library ieee;
    use ieee.std_logic_1164.ALL;
     
    -- Uncomment the following library declaration if using
    -- arithmetic functions with Signed or Unsigned values
    --USE ieee.numeric_std.ALL;
     
    entity HC74373TestBench is
    end HC74373TestBench;
     
    architecture behavior OF HC74373TestBench is 
        component HC74373
        port
        (
             le : in  std_logic;
             oe : in  std_logic;
             dataIn : in  std_logic_vector(7 downto 0);
             dataOut : out  std_logic_vector(7 downto 0)
        );
        end component;
        
       --Inputs
       signal le : std_logic := '1';
       signal oe : std_logic := '1';
       signal dataIn : std_logic_vector(7 downto 0) := (others => '0');
    
     	--Outputs
       signal dataOut : std_logic_vector(7 downto 0);
    begin
     
    	-- Instantiate the Unit Under Test (UUT)
        uut: HC74373 PORT MAP
        (
            le => le,
            oe => oe,
            dataIn => dataIn,
            dataOut => dataOut
        );
    
        process
        begin		
            --
            --  Initial condition, latch should have stabilised and be high impedance.
            --
            wait for 100 ns;
            --
            --  Set data in to all zeroes, output should still be high impedance.
            --
            le <= '0';
            wait for 20 ns;
            --
            --  Now enable the output.
            --
            oe <= '0';
            wait for 20 ns;
            --
            --  Now set the data in whilst latch and output enable are both allowed.
            --  Data output should become 11110000.
            --
            dataIn <= "11110000";
            wait for 20 ns;
            --
            --  Turn off the latch enable and set the data in bits.  The data output 
            --  should not change.
            --
            le <= '1';
            dataIn <= "11001100";
            wait for 20 ns;
            --
            --  Re-enable the output, should become 11110000 as 11001100 has not
            --  been latched yet.
            --
            oe <= '1';
            wait for 20 ns;
            --
            --  Output is turned off, latch the data and enable the output.
            --
            le <= '0';
            wait for 20 ns;
            le <= '1';
            oe <= '0';
            wait for 20 ns;
            --
            --  End of test.
            --
            wait;
        end process;
    end;
    

    If we use the above test bench to simulate this then we get the following output:

    74HC373 Simulated Output

    74HC373 Simulated Output

    The simulation above shows the circuit running at an equivalent 50 MHz (test bench delays of 20ns). I will be working at a lot slower speed. I anticipate the maximum clock speed to be around 8 MHz (125ns clock) and so the delays in the test bench will be adjusted accordingly.

    Propagation Delay

    From the simulation we can see that we have emulated the function of the latch with one minor exception, namely timing. In the above zoomed in trace we can see that the input of the latch is immediately reflected on the output pins. In reality this is not what happens as the circuit takes a small amount of time to respond to the input changes. This time is the propagation delay of the chip.

    The propagation delay depends upon the change being made and the operating conditions of the circuit. The data sheets often quote several values for the delay. These will vary depending upon factors such as supply voltage and temperature. For the purpose of this post we will assume that the circuit is operating with a supply voltage of 4.5V and at room temperature. The values for the delays are also quoted as minimum, typical and maximum values.

    The delays for the 74HC373 are:

    Value Delay (ns)
    Data to Output 30
    LE to Output 35
    OE to Data 30
    tsetup 12

    If we are going to add these to the model then it may be better to consider breaking up the process into two or more processes each dealing with only a few signals.

    The first process deals with latching the data:

    process (le, dataIn) is
    begin
        --
        --  When LE is low, the data on the input is stored in the latch.
        --
        if (falling_edge(le) or (le = '0')) then
            data <= dataIn after 12 ns;
        end if;
    end process;
    

    The above code will store the data in the internal buffer (the latch) if the latch enable pin changes or the data changes and the latch enable pin is low. The data is stored 12ns after the change (as defined in the data sheet).

    The second process deals with the output from the latch:

    process (oe, dataIn, data) is
    begin
        --
        --  When OE is low, the data in the latch is output, 
        --  otherwise the output is high impedance.
        --
        if (oe = '0') then
            if (le = '1') then
                output <= dataIn after 30 ns;
              else
                output <= data after 30 ns;
            end if;
          else
            output <= "ZZZZZZZZ" after 30 ns;
        end if;
    end process;
    

    This code uses a default 30ns propagation delay for all signals. This is not exactly right but should be good enough for our purposes.

    Running the simulation generates the following output (Remember, the delays in the test bench have been adjusted to 125ns):

    74HC373 Simulated Output With Propagation Delays

    74HC373 Simulated Output With Propagation Delays

    We should perhaps look at the delay times in the above process as when that data is latched and also then output through dataOut the code will actually delay the output of the data for 42ns (12 from the latch process and 30 from the output process) when we are actually aiming for a 30ns delay. An exercise for another time.

    Conclusion

    I now have a latch which will operate at speeds of 8 MHz and I can now start to use this as a component in larger circuits. All that is needed now is a few more components and I can start to simulate larger circuits.

    Along the way I have also found a few component libraries at least one of which contained a simulation of this component. Creating this component rather than use a standard library has allowed me to explore and understand more about the Xilinx software. Next step is to look at these libraries and compare this component.

    1-Bit Full Adder With Carry

    November 16th, 2013 • Electronics, FPGAComments Off

    Last year I was given the Elektor FPGA board as a present. I had been wanting to work with FPGA as it presented a quick way of prototyping digital circuits and exploring the world of digital electronics. The board has been sitting in my parts collection for about 10 months. I have followed the tutorials in the magazine and decided that VHDL is probably the best way for me to start and use this board.

    This post looks at my first attempt to simulate a 1-bit full adder with carry using binary logic implemented in VHDL.

    I should also say that this is an early (i.e. inexperienced) project for me.

    Software

    Elektor recommended the Xilinx WebPack ISE software package for use with their board. It is free to use and all of their tutorials use this software. The version 14.7 download comes in at a heavy 6.17GB so make sure you have a good connection to the web or plenty of time to spare.

    Project Specification

    For the purpose of this exercise we will be implementing a 1-bit full adder with carry. For those not familiar with this circuit the schematic is as follows:

    1-Bit Full Adder Schematic

    1-Bit Full Adder Schematic

    The circuit takes three bits, A, B and a carry bit. The three bits are added and two bits are output from the adder, the result bit and a carry bit. The logic table for the circuit is:

    A B Carry In Result Carry Out
    0 0 0 0 0
    0 1 0 1 0
    1 0 0 1 0
    1 1 0 0 1
    0 0 1 1 0
    0 1 1 0 1
    1 0 1 0 1
    1 1 1 1 1

    Time for start Webpack ISE.

    ISE Project

    First thing to do after starting Webpack ISE is to create a new project to hold the circuit. From the startup screen select New Project

    ISE Project Navigator Start Up

    ISE Project Navigator Start Up

    Now give the project a name. I selected BitFullAdderWithCarry. I actually wanted 1-Bit but ISE does not seem to like projects containing non-alpha characters. I provided a short description of the project in the Descriptionbox. Finally ensure that the project source type is set to HDL:

    New Project Name And Properties

    New Project Name And Properties

    The next step is hardware specific. Here we define the target hardware characteristics. For the Elektor board we need to change the following parameters:

    • Device: XC3S250E
    • Package: VQ100
    • Speed: -4
    Device Properties

    Device Properties

    The next form simply shows the project details for review before the project is finally created.

    New Project Summary

    New Project Summary

    The project will now be created and is ready for the addition of files.

    Empty Hierarchy

    Empty Hierarchy

    Right clicking on the device in the Hierarchy brings up the context menu which will allow the addition of new or existing files:

    New File Menu

    New File Menu

    Select New Source option and a wizard will appear. In the New Source Wizard select VHDL Module and add a file name which will hold the source code for the module:

    Module Type

    Module Type

    The next step is to define the ports for the module. The adder has three inputs (a, b and carry in) and two output (result and carry out). This dialog gives the option of defining the ports and their direction:

    Specify Ports

    Specify Ports

    Finally a summary dialog is displayed where you can confirm the module and port information.

    New Source Summary

    New Source Summary

    After clicking on the Finish button the new file will be added to the project along with a template for the module. The new template contains the port definitions entered in the previous dialogs, some comments and space to start entering the code to implement the module.

    New Project Source Code

    New Project Source Code

    I must admit I find it slightly annoying that although VHDL is case insensitive, the system uses mixed and it not consistent in it’s use. You will see what I mean when we get to testing the code.

    A little editing is required to get the code into a form I prefer. It is also time to add the code which implements the hardware for the full adder.

    Adder Source Code

    Adder Source Code

    Once the code has been entered we need to perform a syntax check. Change the view in the top panel from Implementation to Simulation. Now turn your attention to the lower panel. Expand the ISIM Simulator, select Behavioral Check Syntax and click on the green triangle to start the syntax check. After a short while a green tick will appear to indicate that the the check was successful.

    Simulate Selected

    Simulate Selected

    If you right click on the Simulate Behavioral Model entry the system will prepare the implementation for simulation and then launch the simulator.

    Simulated Adder

    Simulated Adder

    At this point there is little to see as we have not defined any simulation parameters. Close the simulator. You will need to do this as there does not appear to be any bidirectional communication between the simulator and the designer. If you simply switch tasks and go back to the designer, make a change and then launch the simulator again you will receive an error message.

    Once back in the Designer, add a new source module as before but this time select VHDL Test Bench for the source type.

    New Test Module

    New Test Module

    Next select the source file test test code will be associated with. We only have one file in our project so you must select that.

    Associate With Source File

    Associate With Source File

    As with the previous cases, the next dialog in the wizard presents a summary for us to confirm the options we have selected.

    New Test Module Summary

    New Test Module Summary

    The Hierarchy will now show a new source file inserted between the device name and the source module we had previously created. This new file contains the test code for out module. The original code is also prefixed with uut which stands for Unit Under Test.

    Project After Adding Test Module

    Project After Adding Test Module

    Select the file containing the source code for the test module. This will have the default code for a test module.

    --------------------------------------------------------------------------------
    -- Company: 
    -- Engineer:
    --
    -- Create Date:   13:01:13 11/16/2013
    -- Design Name:   
    -- Module Name:   BitFullAdderWithCarry/FullAdderTextModule.vhd
    -- Project Name:  BitFullAdderWithCarry
    -- Target Device:  
    -- Tool versions:  
    -- Description:   
    -- 
    -- VHDL Test Bench Created by ISE for module: BitFullAdderWithCarry
    -- 
    -- Dependencies:
    -- 
    -- Revision:
    -- Revision 0.01 - File Created
    -- Additional Comments:
    --
    -- Notes: 
    -- This testbench has been automatically generated using types std_logic and
    -- std_logic_vector for the ports of the unit under test.  Xilinx recommends
    -- that these types always be used for the top-level I/O of a design in order
    -- to guarantee that the testbench will bind correctly to the post-implementation 
    -- simulation model.
    --------------------------------------------------------------------------------
    LIBRARY ieee;
    USE ieee.std_logic_1164.ALL;
     
    -- Uncomment the following library declaration if using
    -- arithmetic functions with Signed or Unsigned values
    --USE ieee.numeric_std.ALL;
     
    ENTITY FullAdderTextModule IS
    END FullAdderTextModule;
     
    ARCHITECTURE behavior OF FullAdderTextModule IS 
     
        -- Component Declaration for the Unit Under Test (UUT)
     
        COMPONENT BitFullAdderWithCarry
        PORT(
             a : IN  std_logic;
             b : IN  std_logic;
             cin : IN  std_logic;
             result : OUT  std_logic;
             cout : OUT  std_logic
            );
        END COMPONENT;
        
    
       --Inputs
       signal a : std_logic := '0';
       signal b : std_logic := '0';
       signal cin : std_logic := '0';
    
     	--Outputs
       signal result : std_logic;
       signal cout : std_logic;
       -- No clocks detected in port list. Replace <clock> below with 
       -- appropriate port name 
     
       constant <clock>_period : time := 10 ns;
     
    BEGIN
     
    	-- Instantiate the Unit Under Test (UUT)
       uut: BitFullAdderWithCarry PORT MAP (
              a => a,
              b => b,
              cin => cin,
              result => result,
              cout => cout
            );
    
       -- Clock process definitions
       <clock>_process :process
       begin
    		<clock> <= '0';
    		wait for <clock>_period/2;
    		<clock> <= '1';
    		wait for <clock>_period/2;
       end process;
     
    
       -- Stimulus process
       stim_proc: process
       begin		
          -- hold reset state for 100 ns.
          wait for 100 ns;	
    
          wait for <clock>_period*10;
    
          -- insert stimulus here 
    
          wait;
       end process;
    
    END;
    

    Remember I mentioned an issue I had with the consistency of the source code generated by the Designer. Check out the keywords in the test module and compare to the template source code for a module. Take the library keyword. In the template module code the keyword is in lower case. In the test module template it is upper case.

    The test module contains the component definitions we require to test our module. Note that the module also contains a default clock for the circuits which need it. Editing the file above to remove unused items (and also correct the case to make it consistent with the source module).

    --------------------------------------------------------------------------------
    -- Company: 
    -- Engineer:
    --
    -- Create Date:   13:01:13 11/16/2013
    -- Design Name:   
    -- Module Name:   BitFullAdderWithCarry/FullAdderTextModule.vhd
    -- Project Name:  BitFullAdderWithCarry
    -- Target Device:  
    -- Tool versions:  
    -- Description:   
    -- 
    -- VHDL Test Bench Created by ISE for module: BitFullAdderWithCarry
    -- 
    -- Dependencies:
    -- 
    -- Revision:
    -- Revision 0.01 - File Created
    -- Additional Comments:
    --
    -- Notes: 
    -- This testbench has been automatically generated using types std_logic and
    -- std_logic_vector for the ports of the unit under test.  Xilinx recommends
    -- that these types always be used for the top-level I/O of a design in order
    -- to guarantee that the testbench will bind correctly to the post-implementation 
    -- simulation model.
    --------------------------------------------------------------------------------
    library ieee;
    use ieee.std_logic_1164.ALL;
     
    entity FullAdderTextModule is
    end FullAdderTextModule;
    
    architecture behavior of FullAdderTextModule is
        --
        --  Component Declaration for the Unit Under Test (UUT)
        --
        component BitFullAdderWithCarry
        port (
             a : in  std_logic;
             b : in  std_logic;
             cin : in  std_logic;
             result : out  std_logic;
             cout : out  std_logic
            );
        end component;
        --
        --  Inputs
        --
        signal a : std_logic := '0';
        signal b : std_logic := '0';
        signal cin : std_logic := '0';
        --
     	--  Outputs
        --
        signal result : std_logic;
        signal cout : std_logic;
    begin
        --
    	-- Instantiate the Unit Under Test (UUT)
        --
        uut: BitFullAdderWithCarry port map
            (
                a => a,
                b => b,
                cin => cin,
                result => result,
                cout => cout
            );
        --
        --  Stimulus process
        --
        stim_proc: process
        begin		
            -- hold reset state for 100 ns.
            wait for 100 ns;	
            --
            --  0 + 1, carry in = 0
            --  Result should be 1 with no carry out.
            --
            a <= '0';
            b <= '1';
            cin <= '0';
            wait for 5 ns;
            --
            --  1 + 0, carry in = 0
            --  Result should be 1 with no carry out.
            --
            a <= '1';
            b <= '0';
            cin <= '0';
            wait for 5 ns;
            --
            --  1 + 1, carry in = 0
            --  Result should be 0 with carry out set to 1.
            --
            a <= '1';
            b <= '1';
            cin <= '0';
            wait for 5 ns;
            --
            --  0 + 1, carry in = 1
            --  Result should be 0 with carry out set to 1.
            --
            a <= '0';
            b <= '1';
            cin <= '1';
            wait for 5 ns;
            --
            --  1 + 1, carry in = 1
            --  Result should be 1 with carry out set to 1.
            --
            a <= '1';
            b <= '1';
            cin <= '1';
            wait;
       end process;
    end;
    

    The main work in the above code is performed in the stim_proc process. The first thing that happens is to pause. This 100ns pause is recommended by Xilinx to allow the system to stabilise after a reset.

    The next code blocks set the input signals to known levels and then pause for 5ns. The pause allows us to examine the outputs in the simulator.

    Now we have a test module we should re-run the simulator as before (right click on the Simulate Behavioral Module and select ReRun All). The simulator will launch again.

    Simulated Full Adder Zoomed Out

    Simulated Full Adder Zoomed Out

    Notice that this time the traces to the right showing the signals are green and not brown. This is because the various ports have values assigned to them by the default test template. Previously there were no values assigned to the pins and they were undefined.

    The default setting for the simulator is to run the simulation for 1us (1,000,000ps). The trace shows the final part of the simulation. Click on the Zoom to Full icon in the toolbar

    Zoom To Full View Icon

    to see the results of the fill run.

    Full View of Trace

    Full View of Trace

    Looking at the far left of the trace you can see that all of the inputs are set to zero and the corresponding output levels are also zero. At 100ns the traces start to change as the test code changes the input values to the adder circuit.

    Click on the zoom icon to zoom in on the traces. You can also use the scrollbar at the bottom of the trace to move around the output.

    Zooming in until you can clearly see the signals between 100ns and 130ns shows the output from the test circuit generated by the code entered in the test module.

    Zoomed In View Of Trace

    Zoomed In View Of Trace

    To the far left of the output window is a yellow cursor line. Grabbing this and dragging the cursor over the traces changes the values displayed for the various pins in the model. So just before 100ns into the simulation the output shows that all of the inputs are zero and so are the corresponding outputs:

    97ns Into Simulation

    97ns Into Simulation

    Moving the cursor through the simulation changes the values. So 102.081ns into the simulation we should have just completed execution of the following code:

    --
    --  0 + 1, carry in = 0
    --  Result should be 1 with no carry out.
    --
    a <= '0';
    b <= '1';
    cin <= '0';
    wait for 5 ns;
    

    From the truth table presented at the start of this post we should be expecting result to be 1 and carry out to be 0. This is verified by the following output:

    10ns Into Simulation

    10ns Into Simulation

    Similarly, at 107.081ns into the simulation we are executing the following code:

    --
    --  1 + 0, carry in = 0
    --  Result should be 1 with no carry out.
    --
    a <= '1';
    b <= '0';
    cin <= '0';
    wait for 5 ns;
    

    Again, double checking with the truth table we see that we are expecting result to be 1 and carry out to be 0.

    25-107nsIntoSimulation

    Once again the trace verifies this.

    Deploying to the Board

    This article was supposed to end with the deployment of the design to the Elektor FPGA board. The board has two LEDs which can be used for output and we can use jumper leads for the three inputs to the board.

    Xilinx Software Problems

    All was going well until I started to map the signals to the pins on the board using the Xilinx PlanAhead tool. At this point PlanAhead displayed the banner and then simply quit. The output in the console reported the fact that a log file had been generated. Opening the log file I found this:

    ****** PlanAhead v14.7 (64-bit)
      **** Build 321239 by xbuild on Fri Sep 27 19:29:51 MDT 2013
        ** Copyright 1986-1999, 2001-2013 Xilinx, Inc. All Rights Reserved.
    
    INFO: [Common 17-78] Attempting to get a license: PlanAhead
    INFO: [Common 17-290] Got license for PlanAhead
    INFO: [Device 21-36] Loading parts and site information from C:/Xilinx/14.7/ISE_DS/PlanAhead/data/parts/arch.xml
    Parsing RTL primitives file [C:/Xilinx/14.7/ISE_DS/PlanAhead/data/parts/xilinx/rtl/prims/rtl_prims.xml]
    Finished parsing RTL primitives file [C:/Xilinx/14.7/ISE_DS/PlanAhead/data/parts/xilinx/rtl/prims/rtl_prims.xml]
    start_gui
    #
    # A fatal error has been detected by the Java Runtime Environment:
    #
    #  EXCEPTION_ACCESS_VIOLATION (0xc0000005) at pc=0x000007fa87e21355, pid=64, tid=8124
    #
    # JRE version: 7.0_17-b02
    # Java VM: Java HotSpot(TM) 64-Bit Server VM (23.7-b01 mixed mode windows-amd64 compressed oops)
    # Problematic frame:
    # C  [msvcrt.dll+0x1355]  free_dbg+0x5
    #
    # Failed to write core dump. Minidumps are not enabled by default on client versions of Windows
    #
    # An error report file with more information is saved as:
    # E:\MarksDocs\Circuits\Xilinx FPGA\BitFullAdderWithCarry\hs_err_pid64.log
    #
    # If you would like to submit a bug report, please visit:
    #   http://bugreport.sun.com/bugreport/crash.jsp
    # The crash happened outside the Java Virtual Machine in native code.
    # See problematic frame for where to report the bug.
    #
    

    Problems Solved

    I have had the PlanAhead tool working in the past as I had completed the exercises in the Elektor magazines in the past. Time for some investigation. After several hours, and I do mean several hours, I uninstalled the 14.7 version of the tools and downloaded the 14.3 version from October 2012. Interestingly, this fixed the problem and PlanAhead launched as expected. So, on with the exercise.

    On With the Exercise

    The next step is to deploy the full-adder to the Elektor board connecting the ports to the pins on the board. We should be able to use jumper cables to represent the inputs to the full adder. The board contains two LEDs which we can use to show the output from the adder.

    Firstly, we switch back to the Implementation view, expand Synthesise – XST and click on the green triangle to start the synthesis. This took a minute or so on my machine so expect to wait for the process to complete.

    Getting Ready For Implementation

    Getting Ready For Implementation

    From the green tick in the processes panel above you can see that the synthesis completed with no errors.

    The next step is to assign pins on the board to the input and output signals.

    Double click on I/O Planning (PlanAhead) Post-Synthesis item in the User Constraints treeview. You will be presented with the following window:

    User Constraints File Question

    User Constraints File Question

    Answer Yes to this question. After a while the PlanAhead tool should appear.

    Plan Ahead Start Up

    Plan Ahead Start Up

    We now need to start assigning the pins on the board to the inputs/outputs in the design. So click on the Scalar Ports treeview item in the lower lefthand corner of the PlanAhead tool. PlanAhead will now show the inputs and outputs from the model.

    Plan Ahead Left Hand Panel

    Plan Ahead Left Hand Panel

    We can not start to assign the ports used in the model to the physical pins on the chip (and hence the board). The Elektor board has two LEDs which are connected to pins 90 and 91 of the FPGA. So for the two output rows set the entry in the Site column to 90 and 91. The entry in the I/O Std column should be set to LVCMOS33 for all of the pins.

    IO Ports Expanded

    IO Ports Expanded

    Once the pins have been selected and assigned, save the mapping in the PlanAhead tool and return to the Designer. Select Implement Design and either right click and select Run or click on the green triangle. After a short time the system should indicate that the implementation has completed without error:

    Implement Design

    Implement Design

    The final step is to generate the file which will be used by the FPGA. This is achieved by selecting Generate Programming File in the Designer, right clicking and selecting Process properties

    Process Properties

    Process Properties

    This will present the Process Properties dialog.

    Process Properties Dialog

    Process Properties Dialog

    Ensure that the following options are checked in the Process Properties dialog:

    Category Option Value
    General Options Create Binary Configuration File Tick this option
    Configuration Options Configuration Pin Program Float
    Configuration Options Unused IOB Pins Float
    Startup Options Drive Done Pin High Tick this option

    Select OK and return to the Designer. You can now right click on the Generate Programming File entry in the Designer and select Run.

    At the end of the process you will find a new file has been placed in the project directory, in this case bitfulladderwithcarry.bin.

    We are now ready to transfer the file to the FPGA board. According to the Elektor specs this is a simple process of connecting the board to the PC via USB and a new drive should appear in the file explorer. I must admit I have had intermittent success with this. I have found the most reliable way of transferring the file to the FPGA board is to insert the SD card into the PC and copy the bin file to the SD card. Once the file has been copied it must be renamed to config.bin. Next plug the SD card into the FPGA board and power the board (I used USB for this example).

    Testing

    In order to test the application you will need to power the board and then connect a,b and carry in to the appropriate signal levels and observe the LED output.

    Grounding pins 7, 8 and 9 on the board should turn off both of the LEDs. In fact it turns both of the LEDs on.

    a=0,b=0,cin=0

    a=0,b=0,cin=0

    Connecting all of the pins to a high signal should turn both LEDs on.

    a=1,b=1,cin=1

    a=1,b=1,cin=1

    As you can see, it turns the LEDs off. I looks like the signals are being interpreted in reverse (negated). Testing with a=0, b=0, cin=1 gives the following:

    a=0,b=0,cin=1

    a=0,b=0,cin=1

    The basic logic appears to be working although it is negated. A little more investigation is required.

    Conclusion

    It would have been nice to have completed the post with by deploying this to the FPGA board. Unfortunately to tools have let me down. Watch this space for future posts which will hopefully see this module deployed to the FPGA board.

    The software swings between being amazing and buggy.

    • The simulator is quick and easy to use although this comment comes from my novice perspective.
    • The 64-bit editor quits every now and then when you try to copy text to the clip board. The 32-bit editor does not suffer from this problem.
    • I have had a problem every now and then adding a new file to a project using the 64-bit Designer. Again, the 32-bit version of the tools do not have this problem.
    • You cannot launch the simulator from the 32-bit tools. This works fine with the 64-bit tools.

    This is the first step on the journey, a journey which I feel will take a while.

    Update 17 Nov 2013

    Version 14.7 of the software demonstrated the fact that new versions are not always better. In fact 14.7 proved to be a huge waste of time for me. I spent several hours trying to work out why PlanAhead didi not work only to revert back to version 14.3 of the software which looks to be working well.

    Some future work:

    • Create a more convenient pin diagram for the Elektor board.
    • Follow up on the negated signals.

    Work for another day I feel.

    Nikon D70 remote Control PCB Assembly

    November 10th, 2013 • Electronics, STM8Comments Off

    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.

    Nikon D70 Remote Control PCB Layout

    October 13th, 2013 • ElectronicsComments Off

    Today, the Nikon D70 Remote control has passed another milestone in it’s evolution. The schematic has been polished and revision A of the PCB designed ready to send to manufacture.

    Schematic Changes

    The main changes to the schematic are as follows:

    • Addition of connection points for power (Bench power supply, Coin Cell, 2xAA battery)
    • Power LED to indicate if the board is powered or not
    • Added a new part for the SOT353 single AND gate component
    • Grounded unused pins

    And here is the final version of the revision A schematic:

    Nikon D70 Remote Schematic (Rev A)

    Nikon D70 Remote Schematic (Rev A)

    The main reason for this taking a while to complete is that I have had to create new components for the single AND gate. I have also take the time to tidy up my parts library in Designspark, something which is overdue.

    PCB Layout

    The number of components is small and so I will be using the iTead Studio 5cm x 5cm PCB production service. At the time of writing this is coming in at $9.99 for 10 boards. And here is the board:

    Nikon D70 Remote Board Layout

    Nikon D70 Remote Board Layout

    And in 3D:

    Nikon D70 Remote 3D Impression

    Nikon D70 Remote 3D Impression

    Bill of Materials

    For this board I am intending to use two suppliers:

    Bitsbox are a fantastic small business and are fantastic value for discrete parts but I have elected to use some surface mount components and for these I will have to use RS Components. The bill of materials (excluding the board) is:

    Part Value Size Quantity Supplier Part Number Unit Price Board Price
    STM8S TSSOP20 1 RS Components 724-9895 £0.91 £0.91
    C1 1uF 1206 1 RS Components 264-4179 £0.05 £0.05
    C2 100nF 1206 1 RS Components 766-1126 £0.01 £0.01
    Header 4 x 1 1 Bitsbox CN202 £0.06 £0.06
    Header 6 x 1 – Male 1 Bitsbox CN203 £0.21 £0.21
    Header 6 x 1 – Female 1 Bitsbox CN259 £0.25 £0.25
    R1 10K 1206 1 RS Components 223-2394 £0.02 £0.02
    R2 1K 0805 1 RS Components 223-0427 £0.01 £0.01
    R3 2K2 0805 1 RS Components 721-8508 £0.08 £0.08
    R4 100 0402 1 RS Components 667-8602 £0.01 £0.01
    LED 1 RS Components 654-5767 £0.05 £0.05
    2N7002 SOT23 1 RS Components 780-0478 £0.02 £0.02
    Single AND Gate SOT353 1 RS Components 751-2806 £0.04 £0.04
    Power Switch 1 Bitsbox SW109 £0.88 £0.88
    Reset Switch 1 Bitsbox SW08 £0.38 £0.38
    IR LED 1 Bitsbox TOP022 £0.20 £0.20
    Total £3.18

    Conclusion

    Now we sit back and wait for two weeks whilst the boards are manufactured.