RSS

Posts Tagged ‘Electronics’

STM8S Independent Watchdog

Saturday, June 21st, 2014

The Independent Watchdog (IWDG) allows the developer to detect software failures and reset the STM8S microcontroller restarting the application. This post demonstrates how this feature works and how the developer can change the time window for the watchdog.

Watchdogs offer the application developer a simple to use method of determining if an application has entered a state which means that it is locked or taking too long to perform an operation.

The first task is to define what we mean by too long. This will vary from one application to another but the key point is that this should be longer than the longest operation the application will perform plus a margin. Once we have this time period defined we know that the application must reset the IWDG before the time period has elapsed.

Hardware

Independent Watchdog Key Register – IWDG_KR

The Key Register controls the operation of the IWDG. The allowed values are as follows:

ValueDescription
0x55Enable writing to the IWDG_PR and IWDG_RLR registers. These registers are write protected by default.
0xAAReset the IWDG counter to the reload value. This value must be written to the key register periodically in order to prevent the watchdog from resetting the microcontroller.
0xCCEnable and start the IWDG.
0x00Disable writing to the IWDG_PR and IWDG_RLR registers.

The final value in the table is not present in revision 9 of RM0016 – Reference Manual but does appear in the header files for the STD Peripheral Library.

In researching this post a key point in the documentation was missed, namely IWDG_PR and IWDG_RLR can only be written when the IWDG has actually been enabled. This means that the IWDG needs to be running before the prescaler and reload registers can be loaded with the values required by the application. If the IWDG is not running then the system will resort to the default reset values for these registers. This gives a watchdog window of 16ms. It also means that the application has 16ms to set the desired time window through the IWDG_PR and IWDG_RLR registers before the microcontroller is reset.

Independent Watchdog Prescaler – IWDG_PR and Independent Watchdog Reload Register – IWDG_RLR

These two registers define the watchdog window and can. The default values are IWDG_PR = 0 and IWDG_RLR = 0xff. This gives a watchdog window of approximately 16ms. To change the size of the watchdog window use the table below to determine the values which are appropriate for your application:

Prescaler DividerPrescaler Value (IWDG_PR)Period when RL = 0Period when RL = 0xff
4062.5 us15.90 ms
81125 us31.90 ms
162250 us63.70 ms
323500 us127 ms
6441.00 ms255 ms
12852.00 ms510 ms
25664.00 ms1.02 s

The programming reference for the STM8S gives the following formula for determining the exact reset period:

T = 2 x TLSI x P x R

where:

TTimeout period
TLSI1 / fLSI
P2(IWDG_PR + 2)
RIWDG_RLR + 1

Additionally, the time between the last reset of the key register (i.e. writing 0xAA to IWDG_KR) is T + 6 x TLSI.

Software

So lets look at how we can implement this:

//
//  This program demonstrates how to use the Independent Watchdog timer
//  on the STM8S microcontroller in order to detect software failures and
//  reset the microcontroller.
//
//  This software is provided under the CC BY-SA 3.0 licence.  A
//  copy of this licence can be found at:
//
//  http://creativecommons.org/licenses/by-sa/3.0/legalcode
//
#include <iostm8S105c6.h>
#include <intrinsics.h>

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

//--------------------------------------------------------------------------------
//
//  Initialise the ports.
//
//  Configure all of Port D for output.
//
void InitialisePorts()
{
    PD_ODR = 0;             //  All pins are turned off.
    PD_DDR = 0xff;          //  All pins are outputs.
    PD_CR1 = 0xff;          //  Push-Pull outputs.
    PD_CR2 = 0xff;          //  Output speeds up to 10 MHz.
}

The code above should be pretty familiar to anyone who has been following The Way of the Register series of posts. This sets up the system clock to run at 16MHz and sets port D as an output port.

//--------------------------------------------------------------------------------
//
//  Initialise the Independent Watchdog (IWDG)
//
void InitialiseIWDG()
{
    IWDG_KR = 0xcc;         //  Start the independent watchdog.
    IWDG_KR = 0x55;         //  Allow the IWDG registers to be programmed.
    IWDG_PR = 0x02;         //  Prescaler is 2 => each count is 250uS
    IWDG_RLR = 0x04;        //  Reload counter.
    IWDG_KR = 0xaa;         //  Reset the counter.
}

We follow this up by setting up the IWDG. The prescaler and counter values should give a 1ms window.

//--------------------------------------------------------------------------------
//
//  Main program loop.
//
int main()
{
    //
    //  Initialise the system.
    //
    __disable_interrupt();
    InitialiseSystemClock();
    InitialisePorts();
    InitialiseIWDG();
    __enable_interrupt();
    __halt();
}

The main program configures the system and then halts the microcontroller. Without the watchdog this would the end of the application. With the watchdog we should see the microcontroller continuously resetting. This can be verified by connecting an oscilloscope to Port D. If we do this we see the output on the port rise as the microcontroller is reset and the InitialisePorts method is called.

Now replace the __halt(); statement with the following code:

while (1)
{
    IWDG_KR = 0xaa;
}

This code continuously writes the I am alive message to the IWDG key register. Doing this forces the IWDG to reset the counters with the reload values and start again. Running this application we see a flat line following the initial reset of the microcontroller. The flat line indicates that the microcontroller is not being reset.

Conclusion

The Independent Watchdog provides a simple method for detecting software failures merely writing a reset value into the key register.

Earlier the point was made that it appears that the IWDG must be enabled (and hence running) before the prescaler and reload registers can be modified. This point is critical as the microcontroller will resort to the reset values otherwise.

Although not demonstrated here it is possible for the application to determine if the application has been started through the application of power to the microcontroller or because of a reset by the IWDG. This can be determined by checking the value in RST_SR_IWDGF. This will be set to 1 if the IWDG has caused the microcontroller to be reset or 0 otherwise. This is left as an exercise for the reader.

Download

The source code for this post is available for download.

Auto-Wakeup on the STM8S

Friday, June 20th, 2014

A few days ago I was asked for advice about pausing the STM8S for a long time period (in this case 30 seconds). I had to admit that I was not sure how to achieve this without using a timer and counting interrupts until the 30 time period had expired. A quick examination of the STM8S Programming Reference reveals that there is a simpler way of doing this. This post examines the Auto-Wakeup (AWU) feature of the STM8S and shows how this feature can be used to pause for a time period which can range from 15.625uS to 30.720s, assuming the clock source is accurate.

Auto-Wakeup (AWU) Feature

The AWU feature wakes the STM8S after a predefined time period following the microcontroller going into the active halt state. This feature can only be used when the microcontroller is halted and the accuracy is dependent upon the clock source.

The clock source is fed into a prescalar. The output from the prescalar is then used as a clock for a counter. The counter will cause an interrupt to be generated when the preset counter value has been reached.

It is also possible to determine the accuracy of the clock source by using the capture compare feature of TIM1 or TIM3 to measure the clock frequency.

Auto-Wakeup Registers

The function of the AWU is determined by the values in the AWU control registers.

Auto-Wakeup Enable – AWU_CSR1_AWUEN

Setting this bit to 1 enables the AWU function. Setting this to 0 disables the function.

Auto-Wakeup Asynchronous Prescalar Divider – AWU_APR_APR and Auto-Wakeup Timebase Selection Register – AWU_TBR_AWUTB

These two registers together control the duration of the AWU. The exact duration of the wakeup period is determined by the AWU_APR_APR and AWU_TBR_AWUTB values from the following table.

fLS = ffLS = 128 kHzAWU_TBR_AWUTBAPRDIV FormulaAWU_APR_APR Range
2/f – 64/f0.015625 ms – 0.5 ms0001APRDIV/fLS2 to 64
2×32/f – 2x2x32/f0.5 ms – 1.0 ms00102 x APRDIV/fLS32 to 64
2×64/f – 2x2x64/f1 ms – 2 ms001122 x APRDIV/fLS32 to 64
22×64/f – 22×128/f2 ms – 4ms010023 x APRDIV/fLS32 to 64
23×64/f – 23×128/f4 ms – 8 ms010124 x APRDIV/fLS32 to 64
24×64/f – 24×128/f8 ms – 16 ms011025 x APRDIV/fLS32 to 64
25×64/f – 25×128/f16 ms – 32 ms011126 x APRDIV/fLS32 to 64
26×64/f – 26×128/f32 ms – 64 ms100027 x APRDIV/fLS32 to 64
27×64/f – 27×128/f64 ms – 128 ms100128 x APRDIV/fLS32 to 64
28×64/f – 28×128/f128 ms – 256 ms101029 x APRDIV/fLS32 to 64
29×64/f – 29×128/f256 ms – 512 ms1011210 x APRDIV/fLS32 to 64
210×64/f – 210×128/f512 ms – 1.024 s1100211 x APRDIV/fLS32 to 64
211×64/f – 211×128/f1.024 s – 2.048 s1101212 x APRDIV/fLS32 to 64
211×130/f – 211×320/f2.080 s – 5.120 s11105 x 211 x APRDIV/fLS26 to 64
211×330/f – 212×960/f5.280 s – 30.720s111130 x 211 x APRDIV/fLS11 to 64

Where fLS = f is the formula which should be used when the inbuilt LSI is not being used.

It may not be possible to obtain an exact time period and the application may have to use the values which give the closest period.

When not in use, AWU_TBR_AWUB should be set to 0 in order to reduce power consumption.

Auto-Wakeup Flag – AWU_CSR1_AWUF

This flag is set when the AWU interrupt has been generated. The flag is cleared by reading the AWU control/status register (AWU_CSR1).

Auto-Wakeup Measurement Enable – AWU_CSR1_MSR

Setting this flag to 1 connects the output from the prescalar to one of the internal timers. This allows the application to accurately determine the clock frequency connected to the AWU prescalar. By measuring the clock frequency the application can adjust the values used for the prescalar divider and the timebase.

Software

Now we have the theory it is time to break out the STM8S Discovery board and the IAR compiler. The application starts pretty much the same as previous examples in this series, by setting the system clock and configuring a port for output:

//
//  This program demonstrates how to use the Auto-Wakeup feature of the STM8S
//  microcontroller.
//
//  This software is provided under the CC BY-SA 3.0 licence.  A
//  copy of this licence can be found at:
//
//  http://creativecommons.org/licenses/by-sa/3.0/legalcode
//
#include <iostm8S105c6.h>
#include <intrinsics.h>

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

//--------------------------------------------------------------------------------
//
//  Initialise the ports.
//
//  Configure all of Port D for output.
//
void InitialisePorts()
{
    PD_ODR = 0;             //  All pins are turned off.
    PD_DDR = 0xff;          //  All pins are outputs.
    PD_CR1 = 0xff;          //  Push-Pull outputs.
    PD_CR2 = 0xff;          //  Output speeds up to 10 MHz.
}

The next step is to provide a method to initialise the AWU function:

//--------------------------------------------------------------------------------
//
//  Initialise the Auto Wake-up feature.
//
//
//
void InitialiseAWU()
{
    AWU_CSR1_AWUEN = 0;     // Disable the Auto-wakeup feature.
    AWU_APR_APR = 38;
    AWU_TBR_AWUTB = 1;
    AWU_CSR1_AWUEN = 1;     // Enable the Auto-wakeup feature.
}

Firstly, the AWU function is disabled. The prescalar is set followed by the timebase. The final step is to re-enable the AWU.

//--------------------------------------------------------------------------------
//
//  Auto Wakeup Interrupt Service Routine (ISR).
//
//  Pulse PD0 for a short time.  The NOP instructions generate a more stable
//  pulse on the oscilloscope.
//
#pragma vector = AWU_vector
__interrupt void AWU_IRQHandler(void)
{
    volatile unsigned char reg;

    PD_ODR = 1;
    asm("nop;");
    asm("nop;");
    PD_ODR = 0;
    reg = AWU_CSR1;     // Reading AWU_CSR1 register clears the interrupt flag.
}

This interrupt handler is triggered at the end of the AWU period. This handler simply pulses pin 0 on port D.

//--------------------------------------------------------------------------------
//
//  Main program loop.
//
int main(void)
{
    //
    //  Initialise the system.
    //
    __disable_interrupt();
    InitialiseSystemClock();
    InitialisePorts();
    InitialiseAWU();
    __enable_interrupt();
    //
    //  Main program loop.
    //
    while (1)
    {
        __halt();
    }
}

The AWU function is turned on when the microcontroller enters the active halt state. A halt instruction is generated by the __halt() method.

Running the above code on the STM8S Discovery board results in the following trace on the oscilloscope:

AWU 3208Hz Signal

AWU 3208Hz Signal

and:

AWU Expanded Pulse

AWU Expanded Pulse

Examining the table above and the values used for the precalar and timebase we should be seeing a pulse with a frequency of approximately 3,368Hz. The actual value measured on the oscilloscope is 3,208Hz. The measurement on the oscilloscope will alway be slightly out due to the time it takes for the ISR to be setup and called. An ISR on the STM8S takes 9 clock cycles to be established, this equates to about 560nS on a system running at 16MHz. The actual difference is 6.25mS. The remainder of the difference comes from the fact that the LSI has an accuracy of +/-12.5%. A quick calculation shows the the LSI was running at about 121KHz at the time this post was written.

Conclusion

AWU offers a simple way of triggering processing at predetermined time periods. The active halt state puts the microcontroller into a low power mode whilst the microcontroller is waiting for the time period to elapse.

It should also be noted that using the LSI results in a slightly variable time period.

Bare Metal GPIO on the Raspberry Pi

Monday, June 16th, 2014

The Raspberry Pi is classically used as a single board computer running Linux but it also offers the possibility of using the board without Linux (known as bare metal development). Add to this the availability of free ARM development tools and we have the opportunity to develop on a high speed ARM processor at a reasonable cost.

This post explores the steps necessary to toggle a GPIO pin on the Raspberry Pi by directly accessing the registers without the presence of an operating system.

Project Description

For this simple project the software will need to perform three operations:

  • Configure the GPIO port
  • Set the GPIO pins for the selected port
  • Reset the GPIO pins for the selected port

The application here will toggle two GPIO pins, one connected to one of the LEDs on the board (GPIO16 connected to the OK/ACT LED) and the second connected to a pin on the header (GPIO18 connected to Pin 12 on header P1).

Resources

There are already a number of resources out on the internet which cover this topic including a large number references in the Bare Metal forum on the Raspberry Pi web site.

As well as the Raspberry Pi web site there are a number of other sites discussing OS development for the Raspberry Pi. This post was going to be a comprehensive write up of the tools and various scripts required to put together a bare metal example but I have since discovered an excellent tutorial on this subject on the OSDev web site. I would heartily recommend that you visit this web site and review the material on the Raspberry Pi C Tutorial page.

Hardware

Before we look at the registers we need to take a look at the first chapter of the Broadcom BCM2835 ARM Peripheral document. Particularly the following two paragraphs:

Physical addresses range from 0x20000000 to 0x20FFFFFF for peripherals. The bus addresses for peripherals are set up to map onto the peripheral bus address range starting at 0x7E000000. Thus a peripheral advertised here at bus address 0x7Ennnnnn is available at physical address 0x20nnnnnn.

and

The peripheral addresses specified in this document are bus addresses. Software directly accessing peripherals must translate these addresses into physical or virtual addresses, as described above. Software accessing peripherals using the DMA engines must use bus addresses.

The remainder of the document discusses the registers and uses addresses in the 0x7Ennnnnn-0x7EFFFFFF memory range. This means that any software written should translate the 0x7Ennnnnn range down to the 0x20nnnnnn range.

GPIO Configuration Register

The software will need to set the function for GPIO pins 16 and 18 using the function select registers. The description of these registers and the GPIO pins which they relate to can be found on page 92 of the Broadcom BCM2835 ARM Peripheral manual. The port is configured using three bits in the register. The three bits have the following meaning:

Bit FieldDescription
000GPIO Pin is an input
001GPIO Pin is an output
100GPIO Pin configured for alternate function 0
101GPIO Pin configured for alternate function 1
110GPIO Pin configured for alternate function 2
111GPIO Pin configured for alternate function 3
011GPIO Pin configured for alternate function 4
010GPIO Pin configured for alternate function 5

The two pins the software will be accessing are pins 16 and 18. These pins are configured using bits 24-26 for GPIO 18 and 18-20 for GPIO 16 of the GPFSEL1 register at 0x7E200004. This maps to the address 0x20200004.

Set GPIO Pin High

The GPIO pins are set by setting a bit in the GPSETn registers. Both GPIO16 and GPIO18 are set through the GPSET0 register (see page 95 of the Broadcom BCM2835 ARM Peripheral manual). The address of this register is 0x7E20001C (0x2020001C).

Set GPIO Pin Low (Clear the GPIO Pin)

The GPIO pins are reset by setting a bit in the GPCLRn registers. Both GPIO16 and GPIO18 are set through the GPCLR0 register (see page 95 of the Broadcom BCM2835 ARM Peripheral manual). The address of this register is 0x7E200028 (0x20200028).

Software

The application is a relatively simple one, toggling a GPIO pin continuously. The following should suffice:

//
//  Flags used to configure the GPIO port.
//
#define FLAG_GPIO_RESET     ((7 << 18) | (7 << 24))
#define FLAG_GPIO_CONFIG    ((1 << 18) | (1 << 24))
//
//  Bits which will allow control of GPIO0 pins for the status
//  LED and the header on the Pi.
//
#define GPIO_PINS           ((1 << 16) | (1 << 18))
//
//  The following register definitions are used to access the GPIO
//  pins on the BCM2835.  The register definitions are taken from
//  section 6 of the BCM manual and the mapping is described in
//  section 1 of the same.
//
//  Address of the register which will configure the GPIO pins.
//
volatile unsigned int *gpsel1 = (unsigned int *) 0x20200004;
//
//  Address of the register which will set the GPIO pins.
//
volatile unsigned int *gpset0 = (unsigned int *) 0x2020001C;
//
//  Address of the register which will clear the GPIO pins.
//
volatile unsigned int *gpclr0 = (unsigned int *) 0x20200028;
//
//  Contents of the GPIO configuration register.
//
unsigned int gpioConfig;
//
//  Main program loop.
//
int main()
{
    //
    //  Reconfigure GPIO 16 and 18 to be outputs.  Clear any
    //  previous GPIO configuration for these pins.
    //
    gpioConfig = *gpsel1;
    //
    //  Firstly, remove any configuration for GPIO 16 & 18.
    //
    gpioConfig &= ~FLAG_GPIO_RESET;
    //
    //  Now configure GPIO 16 & 18 to be plain GPIO outputs.
    //
    gpioConfig |= FLAG_GPIO_CONFIG;
    *gpsel1 = gpioConfig;
    //
    //  Toggle GPIO 16 and 18 forever.
    //
    while (1)
    {
        *gpclr0 = GPIO_PINS;
        *gpset0 = GPIO_PINS;
    }
    return(0);
}

The makefile I used to build the application looks as follows:

#
#   Root directory/name of the ARM tools used to build the application.
#
ARMTOOLSROOT = e:\utils\gcc-arm\bin\arm-none-eabi

#
#   C Compiler options.
#
OPTIONS = -nostdlib -ffreestanding -O3

#
#   What do we need to make to build everything?
#
all: kernel.img
    copy /y /d kernel.img j:\

HelloWorldBlinky.o : HelloWorldBlinky.c
	$(ARMTOOLSROOT)-gcc $(OPTIONS) -c HelloWorldBlinky.c -o HelloWorldBlinky.o

HelloWorldBlinky.elf : memmap HelloWorldBlinky.o 
	$(ARMTOOLSROOT)-ld HelloWorldBlinky.o -T memmap -o HelloWorldBlinky.elf
	$(ARMTOOLSROOT)-objdump -D HelloWorldBlinky.elf > HelloWorldBlinky.list

kernel.img : HelloWorldBlinky.elf
	$(ARMTOOLSROOT)-objcopy HelloWorldBlinky.elf -O binary kernel.img

#
#   Delete any previously built files.
#
clean :
	del -f HelloWorldBlinky.o
	del -f HelloWorldBlinky.elf
	del -f HelloWorldBlinky.list
	del -f kernel.img

Tools

The software present here was compiled using the version 4.8.3 of the ARM eabi tools.

Conclusion

Compiling the above code and executing the application results in the following output when the oscilloscope is connection to GPIO18:

Raspberry Pi GPIO Output

Raspberry Pi GPIO Output

It is necessary to review the documents in the resources section of this post in order to fully understand the theory behind this application.

And now it is time to take on some more complex topics.

SPI and the STM32

Monday, March 17th, 2014

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 0x29040, 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

Sunday, February 16th, 2014

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

    Sunday, February 9th, 2014

    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!

    MSP540 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

    Thursday, January 2nd, 2014

    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; [/code]

    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?

    Wednesday, January 1st, 2014

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

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

    I think the simple answer is that we make mistakes.

    Design Goals

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

    Breadboard

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

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

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

    The board (without the TLC5940s) looks like this:

    OriginalBreadboardWithoutTLC5940

    Next step, prototype.

    TLC5940 – Rev A

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

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

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

    TLC5940 Rev A  Prototype 3D Render

    TLC5940 Rev A Prototype 3D Render

    And the bare board itself:

    TLC5940 Driver Board Rev A - Front

    TLC5940 Driver Board Rev A – Front

    and the back:

    TLC5940 Driver Board Rev A - Back

    TLC5940 Driver Board Rev A – Back

    Lesson 1 – Track Routing

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

    TLC5940 Prototype Rev A Routed Traces

    TLC5940 Prototype Rev A Routed Traces

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

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

    Not really an error, more of a cosmetic nicety.

    Lesson 2 – Use the Same Parts

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

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

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

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

    Lesson 3 – 0603 Parts Are Small Enough For the Hobbyist

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

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

    TLC5940 – Rev B

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

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

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

    Lesson 3 – Vcc and Ground Are Not Interchangeable

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

    TLC5940 Prototype Rev B Working

    TLC5940 Prototype Rev B Working

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

    TLC5940 Prototype Rev B Resistors

    TLC5940 Prototype Rev B Resistors

    Lesson 4 – Plastic Melts At Low Temperatures

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

    ST-Link Connector

    ST-Link Connector

    I suppose this brings me to the next lesson.

    Lesson 5 – Use the Board Luke

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

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

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

    Conclusion

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

    Hope you found this useful.

    Using the Free Model Foundry 373 Latch Model

    Saturday, December 28th, 2013

    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

    Wednesday, December 25th, 2013

    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)DataOutput
    LHHH
    LHLL
    LLlL
    LLhH
    HXXZ

    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:

    ValueDelay (ns)
    Data to Output30
    LE to Output35
    OE to Data30
    tsetup12

    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:

    [code=’c’] 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.