Tuesday 3 December 2013

Adding the Terasic USB Blaster to Ubuntu 13.10

When I tried to use my Terasic USB Blaster with Altera Quartus 13.0 tonight, the device was detecting but the JTAG programmer would report "fail" instantly upon attempting to program.

The problem was to do with the permissions device, and the following should fix that:

  • Create a new file called "terasic-usb-blaster.rules" in the "/etc/udev/rules.d/" folder
  • Simply add the following line to the otherwise empty file:
ATTR{idVendor}=="09fb", ATTR{idProduct}=="6001", MODE="666"
Thanks to http://www.eecg.toronto.edu/~laforest/USB-Blaster-Debian.html for explaining this solution!

Adding a Launcher to the Launch Bar in Ubuntu 13.10

So I installed Altera Quartus 13.0 (the final version to support the Cyclone II FPGA I'm using), and either I forgot to add a shortcut, or it didn't choose to add one (I know 13.1 did everything for me).

Anyway, to create one, here's what I did:

  • Locate the executable (in this case "/opt/altera/13.0/quartus/bin/quartus")
  • Locate an icon (optional, in this case "/opt/altera/13.0/quartus/adm/quartusii.png")
  • Create a file in "/usr/share/applications" called <filename>.desktop (in this case "quartus.desktop")
  • Open it in an editor and configure as shown below (adjusting the fields like Name, Exec (the executable), Icon, and if you like you can change the applications's Categories field to make searching and grouping of applications easier):

[Desktop Entry]
Version=1.0
Name=Altera Quartus
Exec=/opt/altera/13.0/quartus/bin/quartus
Comment=Altera Quartus 13.0
Terminal=false
Icon=/opt/altera/13.0/quartus/adm/quartusii.png
Type=Application
Categories=IDE;Development
X-Ayatana-Desktop-Shortcuts=NewWindow
[NewWindow Shortcut Group]
Name=New Window
Exec=/opt/altera/13.0/quartus/bin/quartus
TargetEnvironment=Unity

  • Now the link with selected icon. should appear in your applications menu when you click the icon at the top of your launch bar. Simply drag this into the desired position on your launch bar. Easy.

(On a side note, If you have problems creating and saving in the applications folder like I did, just move the file from another directory with root access.)

Monday 19 August 2013

Cleared by Reading

It's been a while since my last update, and while I wish I had more to show off, I've been busy with... life. Back in July I resigned from my previous job, took a two week scuba and free diving holiday in Bali, I've just completed my third week with my new employer.



So anyway, I've decided to start tinkering again, and this afternoon I encountered something I hadn't really dealt with before. This debugging problem is pretty obvious when looking back, but had me stumped for half an hour.

I was trying to setup the RS-232 interface on a new development board, and I had to change the code to utilise USART6 instead of USART3 which I'd used previously. A library I had previously written uses interrupts to automatically store received characters into a FIFO buffer. However when I first changed the code to use the new peripheral, the buffer system wasn't working.

I set a breakpoint at the start of the "Read data register not empty" interrupt service routine, but it was never reached. The USART6 registers appeared to be configured correctly, so what else could it be? A few minutes later I realised what the problem was - the NVIC was not configured properly for this interrupt - and so once I had fixed, compiled and re-flashed the micro, I debugged again and this time when it ran the break point was reached. However, when I stepped through the code after the break point, I noticed that the check to see that the interrupt trigger was as expected (i.e. triggered by the RXNE flag) was not succeeding.

But what else could have triggered the interrupt? The RXNE interrupt was the only one currently enabled. Something strange was going on.

Then a strange thought occurred to me. I had all the USART6 registers 'exposed' for viewing in the 'EmbSys Registers' viewer in Eclipse. This plug-in allows easy viewing of all the DSP's registers, and it automatically reads the requested registers and presents them to the user whenever the processor is halted. I remembered the datasheet saying that the RXNE flag is cleared by a read to the USART's data register, DR.

Bingo.

Removing the breakpoint and placing a new one within the IF statement fixed everything, as did removing the DR register from the EmbSys Registers viewer. This is the kind of problem that could have stumped me for quite a while, but luckily I must have been thinking logically.

Thursday 13 June 2013

Scale

It’s silly, especially since I an easily calculate the dimensions of the finished display by taking the dimensions from Altium, but I think tonight was the first time I really comprehended final size of the display I’m building.
I’ve combined 4 boards to create a 32x4 pixel array, which is 870mm tall.
20130613 32x4 RGB LED Element

I think once I’ve got a 32x16 array assembled, I’ll put a halt to the soldering and start with the VHLD; transferring the SPI and timing signals from the STM32F4 onto my DE0-Nano development board. This will be a bit of a challenge, but It needs to happen to free up my microprocessor and allow it to perform graphical processing tasks, along with some I/O work, especially once it's part of a 48 x 32 pixel array.

Friday 7 June 2013

Video Test Pattern

Before I get stuck into a LOT of soldering (960 LEDs to go!) I thought I'd upload a quick video of a test pattern running:
I've slowed down the footage and decreased the brightness (although I've lost 1-bit of resolution due to the way I did this), and the colours aren't the best on my ContourHD. Hopefully I'll have a 3x3 board array up and running sometime next week.

Wednesday 5 June 2013

STM32F4 SPI with DMA

A few people have requested code, so I thought I’d post the code showing how I’ve configured my GPIO, timer, SPI, DMA and NVIC modules, along with some explanation of how the system works.
Note that I’m using the STM32F4 Standard Peripheral Libraries.

The first step is to enable clock signals to the required modules via the RCC (Reset and Clock Control) module:

RCC


//Configure the clocks for the required modules
    //Enable GPIO peripheral clocks
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC, ENABLE);
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD, ENABLE);

    //Enable the Serial Peripheral Interface peripheral clocks
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE);

    //Enable the Direct Memory Access peripheral clocks
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1, ENABLE);

   //Enable the timer peripheral clocks
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);


GPIO

Next, the required GPIO pins are configured:

     #define GPIO_SCAN_PORT            GPIOB
    #define GPIO_SCAN_PIN                GPIO_Pin_7
    #define GPIO_XLAT_PORT              GPIOA
    #define GPIO_XLAT_PIN                 GPIO_Pin_5
    #define GPIO_BLANK_PORT           GPIOB
    #define GPIO_BLANK_PIN              GPIO_Pin_6

//Configure the GPIO Pins
    GPIO_InitTypeDef GPIO_InitStructure;

    //Timer3&4 Outputs (TLC5940 GSCLK and BLANK)
    GPIO_InitStructure.GPIO_Pin = (GPIO_Pin_4 | GPIO_Pin_6);
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
    GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
    GPIO_Init(GPIOB, &GPIO_InitStructure);
    //Connect Timers to the GPIO Pins
    GPIO_PinAFConfig(GPIOB, GPIO_PinSource4, GPIO_AF_TIM3);            //Connect TIM3 OC1 output to PortB Pin4 (GSCLK)
    GPIO_PinAFConfig(GPIOB, GPIO_PinSource6, GPIO_AF_TIM4);            //Connect TIM4 OC1 output to PortB Pin6 (BLANK)

    //TLC5940 XLAT Pin
    GPIO_InitStructure.GPIO_Pin = GPIO_XLAT_PIN;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
    GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
    GPIO_Init(GPIO_XLAT_PORT, &GPIO_InitStructure);

    //Display SCAN Pin
    GPIO_InitStructure.GPIO_Pin = GPIO_SCAN_PIN;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
    GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
    GPIO_Init(GPIO_SCAN_PORT, &GPIO_InitStructure);

    //SPI2 Pins
    //    SCLK =    PB10
    //    NSS =     PB9
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9 | GPIO_Pin_10;
    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(GPIOB, &GPIO_InitStructure);
    GPIO_PinAFConfig(GPIOB, GPIO_PinSource9, GPIO_AF_SPI2);
    GPIO_PinAFConfig(GPIOB, GPIO_PinSource10, GPIO_AF_SPI2);

    //    MISO =    PC2
    //    MOSI =    PC3
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2 | GPIO_Pin_3;
    GPIO_Init(GPIOC, &GPIO_InitStructure);
    GPIO_PinAFConfig(GPIOC, GPIO_PinSource2, GPIO_AF_SPI2);
    GPIO_PinAFConfig(GPIOC, GPIO_PinSource3, GPIO_AF_SPI2);



The main points of interest here are that I’m connecting TIM3’s OC1 output directly to a GPIO pin for GSCLK, and TIM4’s OC1 output for the BLANK signal.

SPI

Now the SPI module can be initialised:

//Initialise the SPI module
    SPI_InitTypeDef SPI_InitStructure;

    SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;       //The SPI bus setup uses two lines, one for Rx and one for Tx
    SPI_InitStructure.SPI_Mode = SPI_Mode_Master;                                //STM32 is the master with the TLC5940s as slaves
    SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;                             //Use 8-bit data transfers
    SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;                                    //TLC5940 clock is low when idle
    SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;                                //TLC5940 uses first clock transition as the "capturing edge"
    SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;                                        //Software slave-select operation
    SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_8; //Set the prescaler
    SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;                             //TLC5940 data is transferred MSB first
    SPI_InitStructure.SPI_CRCPolynomial = 0;                                          //No CRC used

    SPI_Init(SPI2, &SPI_InitStructure);                                                    //Initialise the SPI2 peripheral
    SPI_SSOutputCmd(SPI2, ENABLE);                                                 //Set the SS Pin as an Output (master mode)
    SPI_Cmd(SPI2, ENABLE);


My choice of SPI clock prescaler is fairly arbitrary, but the key points here are that I’ve configured the clock phase and polarity as per the TLC5940 datasheet, and all transfers will be 8bits (more on this later).

DMA

DMA module for SPI transfers is as follows:


//Initialise the DMA1 Stream 4 Channel 0 for SPI2_TX DMA access

    #define DISP_SCAN_DATA_CNT     (24 * 3 * 2)                                                   //24 bytes per chip, one chip per colour (RGB), two boards
   
volatile uint8_t dispData0[DISP_SCAN_DATA_CNT];
    volatile uint8_t dispData1[DISP_SCAN_DATA_CNT];

    DMA_InitTypeDef DMA_InitStructure;

    DMA_InitStructure.DMA_Channel = DMA_Channel_0;                                          //SPI2 Tx DMA is DMA1/Stream4/Channel0
    DMA_InitStructure.DMA_PeripheralBaseAddr  = (uint32_t)&(SPI2->DR);                //Set the SPI2 Tx
    DMA_InitStructure.DMA_Memory0BaseAddr  = (uint32_t)&dispData0;                   //Set the memory location
    DMA_InitStructure.DMA_DIR  = DMA_DIR_MemoryToPeripheral;                          //Sending data from memory to the peripheral's Tx register
    DMA_InitStructure.DMA_BufferSize  = DISP_SCAN_DATA_CNT;                          //Define the number of bytes to send
    DMA_InitStructure.DMA_PeripheralInc  = DMA_PeripheralInc_Disable;                  //Don't increment the peripheral 'memory'
    DMA_InitStructure.DMA_MemoryInc  = DMA_MemoryInc_Enable;                        //Increment the memory location
    DMA_InitStructure.DMA_PeripheralDataSize  = DMA_PeripheralDataSize_Byte;    //Byte size memory transfers
    DMA_InitStructure.DMA_MemoryDataSize  = DMA_MemoryDataSize_Byte;         //Byte size memory transfers
    DMA_InitStructure.DMA_Mode  = DMA_Mode_Normal;                                       //Normal mode (not circular)
    DMA_InitStructure.DMA_Priority  = DMA_Priority_High;                                      //Priority is high to avoid saturating the FIFO since we are in direct mode
    DMA_InitStructure.DMA_FIFOMode  = DMA_FIFOMode_Disable;                         //Operate in 'direct mode' without FIFO
    DMA_Init(DMA1_Stream4, &DMA_InitStructure);

    //Enable the transfer complete interrupt for DMA1 Stream4
    DMA_ITConfig(DMA1_Stream4, DMA_IT_TC, ENABLE);                                       //Enable the Transfer Complete interrupt


This is a memory to peripheral (SPI module) transfer, sending DISP_SCAN_DATA_CNT (24 * 3 * 2 = 144) bytes per transfer.
The memory address is incremented after every byte, and the Transfer Complete flag generates an interrupt.

NVIC

Next, I’ve configured the NVIC (Nested Vectored Interrupt Controller) for two interrupt service routine triggers:


//Initialise the Nested Vectored Interrupt Controller
    NVIC_InitTypeDef NVIC_InitStructure;

    //Enable the TIM4 (BLANK) Interrupt
    NVIC_InitStructure.NVIC_IRQChannel = TIM4_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);

    //Enable the DMA1 Stream4 (SPI2_TX) Interrupt
    NVIC_InitStructure.NVIC_IRQChannel = DMA1_Stream4_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);


The BLANK interrupt is used for generating the BLANK pulse, initialising DMA transfers, and latching previously transferred data after each SCAN cycle.

Timers

Finally, the timer modules are configured:


     #define TLC5940_GSCLK_COUNTS    256                   //GSCLK Counts between BLANK Pulses
    #define TLC5940_GSCLK_FREQ        1000000            //GSCLK Frequency
    #define TLC5940_BLANK_COUNT      50                     //Padding to allow previous SCAN column’s positive supply rail to turn off before switching to the next column
    #define TIM_APB1_FREQ                  84000000          //Internal TIMx Clock frequency (CK_INT)

//Initalise the Timer Modules
    TIM_TimeBaseInitTypeDef TIM_BaseInitStructure;
    TIM_OCInitTypeDef TIM_OCInitStructure;

    //Deinitialise timer modules and the initialisation structures
    TIM_DeInit(TIM3);
    TIM_DeInit(TIM4);
    TIM_TimeBaseStructInit(&TIM_BaseInitStructure);
    TIM_OCStructInit(&TIM_OCInitStructure);

    //Setup the TIM3 to generate the 'master clock'
    TIM_BaseInitStructure.TIM_Period = 1;
    TIM_BaseInitStructure.TIM_Prescaler = (uint16_t) (((TIM_APB1_FREQ / TLC5940_GSCLK_FREQ)/4) - 1);    //Note that the division factor of 4 is due to the OC1 freq vs CK_INT freq
    TIM_BaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
    TIM_BaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseInit(TIM3, &TIM_BaseInitStructure);
    //Configure Channel 1 Output Compare as the Trigger Output (used to generate the 'GSCLK' signal)
    TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_Toggle;
    TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
    TIM_OCInitStructure.TIM_Pulse = 1;
    TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
    TIM_OC1Init(TIM3, &TIM_OCInitStructure);
    TIM_OC1PreloadConfig(TIM3, TIM_OCPreload_Enable);

   //Setup the TIM4 base for a symmetrical counter with a maximum count specified as the 'GSCLK count' (effectively the TLC5940's greyscale resolution)
    TIM_BaseInitStructure.TIM_Period = TLC5940_GSCLK_COUNTS + TLC5940_BLANK_COUNT;                   //GSCLK overflow count (with 1 extra for the BLANK signal to 'block')
    TIM_BaseInitStructure.TIM_Prescaler = 0;
    TIM_BaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
    TIM_BaseInitStructure.TIM_CounterMode = TIM_CounterMode_CenterAligned1;
    TIM_TimeBaseInit(TIM4, &TIM_BaseInitStructure);
    //Configure Channel 1 Output Compare as the Trigger Output (used as the clock signal by TIM4 to generate 'BLANK' pulses)
    TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
    TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
    TIM_OCInitStructure.TIM_Pulse = TLC5940_BLANK_COUNT;
    TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
    TIM_OC1Init(TIM4, &TIM_OCInitStructure);
    TIM_OC1PreloadConfig(TIM4, TIM_OCPreload_Enable);

    //Configure TIM3 as a master timer
    TIM_SelectOutputTrigger(TIM3, TIM_TRGOSource_Update);                 //TRGO is tied to the update of TIM3
    TIM_SelectMasterSlaveMode(TIM3, TIM_MasterSlaveMode_Enable);    //TIM3 enabled as a master

   //Configure TIM4 as a slave
    TIM_SelectInputTrigger(TIM4, TIM_TS_ITR2);                                     //Set TIM4 (slave) to trigger off TIM3 (master)
    TIM_SelectSlaveMode(TIM4, TIM_SlaveMode_External1);                   //Use the master signal input as an 'external clock'

    //Configure the TIM4 module to interrupt at Capture/Compare 1 events (match on both up and down-counting)
    TIM_ITConfig(TIM4, TIM_IT_CC1, ENABLE);

    //Enable Timers 3 and 4
    TIM_Cmd(TIM4, ENABLE);
    TIM_Cmd(TIM3, ENABLE);


Here, Timer 3 is used as the master clock, generating the GSCLK signal on its Output Compare 1 line, and driving Timer 4 which is configured as a centre-aligned PWM output on OC1.
Blank count is effectively a padded pulse, allowing for:

  1. Minimum BLANK pulse time
  2. XLAT and DMA transfer triggering
  3. The MOSFET output on the previous SCAN column to fully discharge (I’ve tuned this by viewing discharge time on my oscilloscope)

The GSCLK frequency is set, and the number of GSCLK pulses between falling and rising BLANK signal edges is set to 256 since I’m using 8-bit colour rather than the full capability of the TLC5940 chip (12-bit). This means that there will be 256 GSCLK cycles between BLANK pulses.

The peripherals are now fully configured, so the last thing to do is look at the interrupt service routines, and investigate the results:

ISRs


     #define DISP_SCAN_FREQ                 200                                                                                                                                                                           //Frequency of the SCAN signal
    #define DISP_BLANK_CYCLE_LIMIT    ((((TLC5940_GSCLK_FREQ / (TLC5940_GSCLK_COUNTS + TLC5940_BLANK_COUNT)) / DISP_SCAN_FREQ) / 2) - 1)   //Number of BLANK cycles to count before SCANing

    void TIM4_IRQHandler(void)
    {
        //TIM4 IRQ Handler has several tasks:
        //    - Toggles the SCAN signal
       //    - Latches the previously transmitted data for the newly selected ('scanned') column
        //    - Sets up and starts the SPI2 DMA Stream to transmit the next column's data
        //All this should be performed within the window of the BLANK signal (TIM4 OC1) being high (not the full SPI transmission)

        //Check if the interrupt generated is an OC1 Update
        if(TIM_GetFlagStatus(TIM4,TIM_IT_CC1))
        {
            //Clear the TIM4 CC1 interrupt bit
            TIM_ClearITPendingBit(TIM4, TIM_IT_CC1);

            //Only perform event when down-counting (this ensures the XLAT pulse, SCAN update, and SPI transfers are triggered within the BLANK pulse)
            if(TIM4->CR1 & TIM_CR1_DIR)
            {
                //Check if we require a 'SCAN' update (XLAT pulse, SCAN toggle, and next transfer triggered)
                if(dispBlankCycleCnt++ >= DISP_BLANK_CYCLE_LIMIT)
                {
                    GPIO_SetBits(GPIO_XLAT_PORT, GPIO_XLAT_PIN);                  //Set the XLAT pin
                    dispBlankCycleCnt = 0;                                                             //Reset the counter

                    //Determine the current column, and shift accordingly
                    if(dispCurrentCol)
                    {
                        dispCurrentCol = 0;                                                              //Change to column '0'
                       GPIO_SetBits(GPIO_SCAN_PORT, GPIO_SCAN_PIN);          //Set the SCAN pin (note that column 0 is a logic high, column 1 is a logic low)
                        DMA1_Stream4->M0AR = (uint32_t)&dispData1;                    //Send *next* column's data (dispData1 is sent (for the next cycle) since the current column is now '0')
                   }
                   else
                    {
                        dispCurrentCol = 1;                                                              //Change to column '1'
                        GPIO_ResetBits(GPIO_SCAN_PORT, GPIO_SCAN_PIN);      //Reset the SCAN pin (note that column 0 is a logic high, column 1 is a logic low)
                        DMA1_Stream4->M0AR = (uint32_t)&dispData0;                   //Send *next* column's data (dispData0 is sent (for the next cycle) since the current column is now '1')
                    }

                    GPIO_ResetBits(GPIO_XLAT_PORT, GPIO_XLAT_PIN);             //Clear the XLAT pin

                    //Trigger the next transfer
                    SPI_I2S_DMACmd(SPI2, SPI_I2S_DMAReq_Tx, ENABLE);        //Enable the DMA Transmit Request
                    DMA_Cmd(DMA1_Stream4, ENABLE);                                      //Enable the DMA stream assigned to SPI2
                }
            }
        }
    }

    void DMA1_Stream4_IRQHandler(void)
    {
        //Check if the transfer complete interrupt flag has been set
        if(DMA_GetITStatus(DMA1_Stream4, DMA_IT_TCIF4) == SET)
        {
            //Clear the DMA1 Stream4 Transfer Complete flag
            DMA_ClearITPendingBit(DMA1_Stream4, DMA_IT_TCIF4);
        }
    }


The DMA ISR is currently not used (I do intend to use it for something unrelated), but the TIM4 ISR essentially controls the whole of the display.
The rising edge of the BLANK pulse (effectively) triggers the interrupt. After determining that the correct ISR triggered the event, the TIM_CR1_DIR bit is used to check if the counter is down-counting. This ensures that we only perform the following tasks at the rising edge of the BLANK pulse.
Every time the ISR is run, we increment a counter and if this counter exceeds the number required to SCAN the display, we latch the previous data using the XLAT signal, toggle the SCAN signal, and transfer the next data (found in the dispData0[ ] or dispData1[ ] arrays.
The number of BLANK cycles to wait before SCANning is calculated in DISP_BLANK_CYCLE_LIMIT, which takes into account:

  1. TLC5940_GSCLK_FREQ – Greyscale clock frequency
  2. TLC5940_GSCLK_COUNTS + TLC5940_BLANK_COUNT – The number of GSCLK pulses between rising BLANK edges
  3. DISP_SCAN_FREQ – The frequency we would like to SCAN the array at (set to 200Hz here)

Updating the data in the dispDatax[ ] arrays will now change what is displayed on the LEDs.
With a GSCLK frequency of 1MHz and a SCAN frequency of 200Hz, I have no noticeable LED flicker even though I’ve heard people talk about using >5MHz to avoid it with their setups.

Logic Analysis


I’ve attached a logic analyser between the STM32F407 outputs and the TLC5940 display-board input and this is what appears:

XLAT and GSCLK

On the small scale, we can see that the GSCLK period is 1080ns – 80ns = 1us = 1MHz.
Also, the XLAT pulse is 210ns; well above the minimum 20ns listed in the TLC5940 datasheet.

GSCLK Count

Now that we know the GSCLK period is as expected, we can investigate the BLANK time to determine the greyscale data is being clocked for 8-bit resolution.
The time between the falling and rising BLANK edges is 305.6 – 49.6 = 256us which is as expected. I’ve also investigated on a closer level to check the phase of the signals are correct for 2^8 counts.

SCAN Cycle

Finally, checking the scan width, we can see that one column is enabled for 2.445ms. This means that the SCAN rate is 409Hz; fairly good considering 2.5us =  is not evenly divisible by 256us.
The capture above also shows that when the BLANK count reaches the limit, the associated ISR latches the previous data, toggles the SCAN line, and then triggers the SPI transfer. It then counts the required number of BLANK cycles before latching this data (XLAT signal barely visible to the right of the blue arrow where the 2,445ms cursor is).

Feel free to comment on the above, and let me know if anything is unclear.

Tuesday 28 May 2013

The boards are back!

I received my boards back from the manufacturer last week, and I've fully populated a few with components for software testing. So far everything has gone without a problem, aside from a few niggling software problems that have since been rectified.

I’ll make another update once I've got a small array of these boards connected and running, but for now here are some pretty pictures (note that I decided to change the silkscreen and solder-mask colours to black and white respectively, but otherwise the design is unchanged from previous blog updates).

 

20130528_Board 001 20130528_Board 002 20130528_Board 003

Fig.1: The ICs

Fig.2: The Full Board (Back)

Fig.3: The Logo!

When I showed a colleague the board today, he asked if I had had a problem with solder getting into the thread of the surface-mount mounting nuts, but when I said that they were hand-soldered, he told me that it was probably only an issue for the reflow-soldered process. Because of this, I decided to run a quick test. I made a crude stencil using an overhead-transparency sheet, applied solder to the mounting hole - screen-printing style – and then use my IR soldering station to reflow the solder.

From the images below, the only problem I could see was that perhaps I had applied too little solder since there appears to be a gap when viewing the nut from below. However, no solder entered the nut and blocked the thread on any of the four attempts.

 

20130528_Board 004 20130528_Board 005

Fig.4: Stencil and Spatula

Fig.5: Pasted

20130528_Board 006 20130528_Board 007

Fig.6: Inserted

Fig.7: Reflowing under IR

20130528_Board 009 20130528_Board 010

Fig.9: Reflowed Version

Fig.10: Hand Soldered Version

Tuesday 30 April 2013

The HTSSOP and Altium

After reviewing several land-pattern options for my HTSSOP-28 'PowerPAD' Plastic Small Outline package TLC5940, I've decided to use the one from the chip's datasheet. The datasheet made many references to the SLMA002 and SLMA004 application notes, but the key points I got from both of these were to:
  • use as much copper and as many vias as possible to facilitate heat transfer, and
  • tent the vias to prevent solder wicking away from the chip
To the first point, the SLMA002 version has ten vias, while the example footprint in the chip's datasheet uses twenty-one. Secondly, tenting directly below the chip's thermal pad would be a nightmare as I'm trying to get as large of a soldering contact area as possible. So with those observations, I've decided to use the land-pattern shown in the datasheet (Fig.2 below, purple being the inverse of the solder mask, red the masked copper).
00 01 AM
Fig.1: SLMA002 Recommended
Fig.2: TLC5940 Datasheet
Fig.3: A Colleague’s footprint
Figure 3 shows a footprint for a different TI chip with the same package (SN65HVS880), but with:
  • different land-pattern than the example recommended in the datasheet, and
  • a strange, asymmetrical via pattern
The first point may be the result of several things, such as an out-dated datasheet, but regardless, as I said, I’ve made my decision to go with Figure 2.
Thanks go to Scott for demonstrating how to specify the polygon-connect style for individual pads on a specific footprint:
  1. Add a new design rule to ‘Design -> Design Rules -> Plane -> Polygon Connect Style’
  2. Specify the first ‘object’ to be a solution to the query: ‘IsPad AND Name = 'U1-29'‘; where ‘U1’ is the name of the chip, and ‘29’ is the specific pad number we would like to pour across.
  3. Watch Altium correctly pour the polygon across specified pad and be happy.
relief direct
Fig.4: Before Scott
Fig.5: After Scott
Notice the thermal relief in Figure 4 and the solid connection in Figure 5. I've added four extra vias to the PCB (and probably hinder hand-soldering... eeep) to promote vertical heat transfer due to the abundance of physical space. Anyway, let’s get this baby off to the manufacturer!

Saturday 27 April 2013

3D Component Models for PCAs

I've been messing around with embedded software for the last few weeks, but lately my time's been dominated by linking some proper 3D models to my PCA for accurate component clearance checking.

The key areas I was unsure about with my current design were:

  • Through-hole LEDs
  • Through-hole connectors
  • Surface-mount capacitors
  • Surface-mount fasteners

The LED-to-fastener conflict had a chance to be a game-changer, so I decided to 'do things right' by using the tools available to me; a combination of Altium and SolidWorks.
A lot of the models I used were readily available for free on various manufacturer websites and forums, which made short work of a lot of it. Some that I've used are:

  • Texas Instruments - Their data appears very accurate, although a couple of their web-links are broken and I've asked for them to be fixed.
  • TE Connectivity - Excellent 3D models for connectors, but without colour in the one I used.
  • PEM Fasteners - Again, great models, but with some colour 'issues'.
  • http://www.3dcontentcentral.com - The Dassault Systèmes (makers of SolidWorks) website for anyone to upload free components.

I downloaded all my passive 'chip' components (resistors, capacitors, inductors) from 3D Content Central, and a couple of ICs too, but most of the ICs I got from Texas Instruments, although as I said, one of their links for the HTSSOP28 chip was broken, so I've substituted a TSSOP28 (no thermal pad... not that it matters from a purely visual point of view) for the TLC5940s (I can't remember if I noted previously that I've switched from the through-hole to one of the surface-mount variants for reasons of cost).
The model for the PEM Fasteners SMD threaded-nut I've purchased came with an orange colour on the threading which may be to highlight that a) the threading wasn't modelled, or b) that it is a contact surface, but I didn't want it to look unrealistic, so I changed it to silver. The Tyco barrier-terminal model came without any colour information, which I suspected may have been due to them using an older .STEP file format, but it appears to be a STEP AP214 format which should have colour options. This became a simple matter of using SolidWorks to import the file, add the colour to the appropriate surfaces, and save as an updated .STEP model, as shown below in the before and after images:

tyco001 tyco002

Figure 1: Connector before adding colour

Figure 2: Connector with colour

The first component I had to make from scratch was the tri-colour LED that is placed in an array across the entire top-side of the board. I didn't have great technical documentation for the 'internals' of the component, so my digital callipers came in handy. Previously I had modelled the component as a single flat-block, but this caused clashes with one bottom-layer component (the Tyco barrier-connector) and one top-layer component (the SMD fastener). While I knew I could probably get away with an educated-guess as a valid position where the components wouldn't collide, it's always better to be sure.

RoyalFourWay001 RoyalIso002 RoyalIso001

Figure 3: LED Front/Side/Bottom/Top

Figure 4: LED Iso Above

Figure 5: LED Iso Below

 

The second component was the KEMET EDK-Series SMD 16V, 470uF electrolytic capacitor. This provided some more good design 'revision' with SolidWorks (which I haven't touched in over a year). It was a relatively simple component to model, and I found that when I needed to revise one of the dimensions I'd set early on, the method I'd used to link my 'Smart Dimensions' - as they're called - meant that the model was automatically realigned and adjusted for the change. Handy!

KemetFourWay001 KemetIso001 KemetIso002

Figure 6: Kemet from all angles

Figure 7: Kemet Iso Above

Figure 8: Kemet Iso Below

 

And here are those two components in real life in their natural environment:

realLEDCap001 realLEDCap002

Figure 9: Capacitor right, LED Left

Figure 10: Capacitor right, LED Left

 

So with all the components modelled, I was able change the layout from this:

PCBold001 PCBold002

Figure 11: Leadless-components

Figure 12: Missing Connectors


...to this:

PCB003 PCB000

Figure 13: Front

Figure 14: Back

PCB005 PCB006

Figure 15: ICs and colourful 'jellybeans'

Figure 16: Barrier Terminal, header pins, capacitor

 

Now that all the components are spatially accurate, I can see that there is no longer a problem with clashes between components:

PCBold003 Edit PCB004

Figure 17: Fastener has tight fit

Figure 18: Spacious fastener

One thing to note is that I have the footprint for the electrolytic capacitor slightly larger than necessary so that I can temporarily use a few larger 35V 470uF capacitors that I acquired cheaply.
Hopefully I'll get the board off to the manufacturer tomorrow. It's always best to sleep on a design and re-check for design faults in the morning; a 'cooling-off period'. :)

Monday 8 April 2013

LEDs Received?!

I'm not sure why I received this package today, but it seems to contain 1,540 RBG LEDs (420 shown, pen and tip-of-toe shown for scale). Did anyone mistakenly ship these to me?


I guess I'll just put them into storage until the owner comes forward.

Sunday 7 April 2013

STM32 System and Timer Clock Configurations - Pt2

Now that I've verified my system's core clock is functioning as expected, I can move onto configuring the clock modules to produce the desired BLANK and GSCLK signals to control the TLC5940 chips.

Using the SYSCLK signal, the 'Advanced High-performance Bus' (AHB) clock frequency is set via a prescaler. In this case I am using the maximum frequency of the AHB by setting the prescaler to divide by 1. The two 'Advanced Peripheral Bus' clocks (APB1 and APB2) are generated from the AHB via their associated prescalers. They have maximum frequencies of 42 and 84MHz respectively, so my APB1 prescaler is set to '4' and APB2 prescaler is set to '2'. The internal signal that can be selected as the timer peripherals' clock is the APB1 clock.

HOWEVER... and this is an important part to take note of: While the APB1 signal provides the clock for numerous peripherals including the timer modules, the timers can receive a faster clock if the APB1 prescaler is set to anything other than '1'. The manual states:
  1. If the APB prescaler is 1, the timer clock frequencies are set to the same frequency as that of the APB domain to which the timers are connected.
  2. Otherwise, they are set to twice (×2) the frequency of the APB domain to which the timers are connected.
Since the APB1 prescaler I am using is '4' and the AHB clock is 168MHz, the internal clock presented to the timer modules is actually (168/4) * 2 = 84MHz. Inside the TIMx modules there are three main clock signals:
  • CK_INT - The internal clock before prescaling
  • CK_PSC - The clock signal after being divided by the prescaler
  • CK_CNT - The counter clock, which generates pulses every time the prescaler counter overflows
Note that while there are different clock signals (internal clock, external clock, internal triggers, etc) that can be used as the main input to each timer module, since I don't need to synchronise to external signals, I will use the default internal clock, APB1. The internal clock signal APB1 is fed into my main timer module TIM3. This module will be used to provide both the GSCLK signal on an external GPIO pin, and also to trigger/clock the slave TIM4 module which will provide the BLANK pulses. Initially I intend to have the TIM3 setup for its fastest possible rate, and then configure the output for the desired GSCLK frequency once both signals are synchronised correctly. I am using a prescaler divide factor of 1 (TIM3_PSC = 0), so CK_PSC = CK_INT = APB1 * 2 = 84MHz.

Each TIM module from TIM2 to TIM5 (and some others) can be used as a 'master' or a 'slave' to generate signals or react to them in various ways. For instance, I can use TIM3 as a master that generates a trigger pulse every time the counter reaches a specific value. I could then use that trigger pulse as the main clock signal in another timer module.

The way I intend to use TIM3 is to use its 'Update Event' as its external trigger signal (TRGO) which generates a single pulse every time its counter 'overflows', or reaches the upper limit which is set in the 'Auto Reload Register' (ARR). The TIM3 counter is configured in 'Upcounter Mode' which means that every rising edge of the counter clock causes the counter to increment until it reaches the ARR value. When it reaches this upper limit, it generates an 'Update Event', UEV, which causes the counter to reset to '0'. Each UEV pulses the trigger signal, so if ARR is set to '1', the counter moves from 0 to 1, generates a trigger pulse, resets the counter to 0, and the process repeats forever.

This two-count overflow acts as a divide-by-two on the CK_CNT signal. Therefore, our TIM3 master trigger signal is 84MHz / 2 = 42MHz.

This trigger is passed into TIM4 on the Internal Trigger network, which, when these two specific timers are used, is the Internal Trigger 2 signal (ITR2).

Returning to the original task of providing grayscale clock and blanking pulse signals for the TLC5940, the two timer modules now need to be configured to generate these signals on their Output Compare pins.

For the GSCLK signal, the TIM3 Output Compare Channel 1 (OC1) signal is configured to toggle its output every time the counter matches the desired value; in this case '1'. This essentially means that it is synchronised with the TRG0 signal at half the frequency.

For the BLANK signal, we need to be able to synchronise the signals with a phase shift so that the desired number of GSCLK pulses are generated before a blanking pulse is generated. To do this, the TIM4 counter is configured in 'Centre Aligned' mode, so that it counts up to the ARR value and then counts down to zero and repeats. The TIM4 OC1 signal is configured as a PWM signal that is set when the counter is less than OC1's  Capture/Compare Register (CCR1) value, where:
  • CCR1 = GSCLK_COUNT + 1
Due to internal timing, the TIM3 OC1 signal is given a slight phase shift relative to the TIM4 OC1, and the result is that the number of rising edges between consecutive blanking pulses is the desired number (GSCLK_COUNT). I'm still not exactly sure why, so I'll need to investigate the origin of this phase shift, but for now I am happy that it's there.

The timing for each timer module, and the resulting signals seen by the TLC5940 chip are shown below in Fig.1. Here GSCLK_COUNT is set to '2'. Note that for TIM4, the CK_INT is TIM3's TRG0.

Figure 1: TIM3, TIM4, and the TLC5940 Signal Configuration

I've experimented with changing the TIM3 prescaler to adjust the GSCLK frequency, different numbers of GSCLK_PULSES. Below Fig. 2 and Fig. 3 show scope captures of two different configurations:

Figure 2: GSCLK = 21MHz, GSCLK_COUNT = 2
Figure 3: GSCLK = 82kHz, GSCLK_COUNT = 4

Now that the timing of the TLC5940 GSCLK and BLANK signals have been configured and are easily adjustable for the desired frequency and intensity resolution, it's time to move onto the TLC5940's serial data interface.

Wednesday 3 April 2013

STM32 System and Timer Clock Configurations - Pt1

I've started writing some software to drive a series of TLC5940 16 Channel LED Driver chips on an ST STM32F407 Microcontroller. I previously had code working on an Atmel microcontroller, but obviously with the change of hardware comes the need for re-writing some of the lower peripheral configuration and application code.

The two main requirements for driving the TLC5940 chip from a microcontroller are:
  1. The grayscale clock
  2. A serial data bus
The microcontroller's SPI port is well suited to delivering data to the TLC5940 even though this is not officially an 'SPI' device. For the grayscale clock I have decided to use the STM32's timer modules to automatically provide the BLANK and GSCLK signals.

Basically, the blank signal is used to reset the grayscale counter inside the TLC5940, and depending on the desired LED brightness resolution, we wait a certain number of GSCLK cycles before generating a BLANK pulse to reset the TLC5940's internal counter.

Both these signals have timing requirements placed on them, so we need to understand exactly how the STM32's timer modules function. And before we can do this, we also need to understand the clock source that drives the timers (in my case, the internal system clock).

My board uses an external 8MHz crystal oscillator as its clock source, and although I believed I had it configured correctly, bit without measuring it, I couldn't be sure. Well... I could have flashed an LED at a human-measurable speed, like 1Hz, but that's not as fun as delving right into the core and working out to the timer modules. :p

The maximum frequency specified by ST for this device is 168MHz, so this is the frequency I will configure the device for. To begin with, the 8MHz clock is the 'High Speed External' clock signal, or HSE as it is known, enters the microcontroller through its OSC_IN/OSC_OUT pins, before being sent to the 'Phase Lock Loop' (PLL) module to be divided and multiplied by several pre-scalers to provide the 'System Clock' (SYSCLK), the 'Advanced High-performance Bus' (AHB) clock, and the two 'Advanced Peripheral Bus' clocks (APB1 and APB2). There are several other clocks configured by the 'Reset and Clock Control' (RCC) peripheral, but these aren't directly related to the Timer modules I am using.

The C source files in the STM32F4xxStdPeriph Library where the system clock is configured include:
  • system_stm32f4xx.c - the PLL register value #DEFINEs and initialisation code is found here
  • stm32f4xx.h - the HSE frequency is defined at the start
The SystemCoreClock variable in the first of the two files above is calculated using the following two equations found in the RM0090 Reference Manual:
  • f(VCO clock) = f(PLL clock input) × (PLLN / PLLM)
  • f(PLL general clock output) = f(VCO clock) / PLLP
These equations describe what is happening in the hardware. First, the HSE clock signal is divided by the PLLM division factor. The result from this must be between 1 - 2MHz, but a value of 2MHz is recommended to limit PLL jitter.
This pre-scaled signal then enters the Main PLL module where it is multiplied by PLLN, the VCO multiplication factor, to provide a signal between 64 to 432MHz, and then divided back down by the PLLP factor to produce the SystemCoreClock with a frequency no greater than 168MHz.

My initial setup used the following configuration:

stm32f4xx.h:
  • #define HSE_VALUE    ((uint32_t)8000000)
system_stm32f4xx.c
  • #define PLL_M      8
  • #define PLL_N      336
  • #define PLL_P      2
However after noticing the mention of increased clock jitter, I decided to change PLL_M to '4', PLL_N to '168'. All the PLL parameters above are placed into the 'RCC PLL Configuration Register', RCC_PLLCFGR.

Now, with the settings for the core clock determined and initialised into code, I needed a way to view these signals, and luckily for me the nice people at ST have provided hardware that can select from various internal clock signals, pre-scale them, and present them for viewing on two external pins, MCO1 and MCO2 which are the 'Microcontroller Clock Output' pins. These are configured in the 'RCC Clock Configuration Register' RCC_CFGR using the MCOx bits for selecting the desired signal, and the MCOxPRE bits to scale the signal by a factor of between 1 and 5.

Using the peripheral library commands below, I've selected the HSE for MCO1, and SYSCLK divided by 5 for MCO2. This should provide signals of 8MHz and 33.6MHz if my configuration is correct.
  • RCC_MCO1Config(RCC_MCO1Source_HSE, RCC_MCO1Div_1);
  • RCC_MCO2Config(RCC_MCO2Source_SYSCLK, RCC_MCO2Div_5);
And viewing these signals in the images below we can see that this is roughly the case. The 0.8% error in the SYSCLK (blue Ch2) signal is either due to the actual signal being slightly out due to the PLL scaling system, or possibly the limitations of my cheap 100MHz 'scope (I will test this tomorrow on some old (but high-end) Le Croy and Tektronix 500MHz 'scopes at work tomorrow.


Next I'm moving on to confirming my GSCLK and BLANK timer configuration is as I need it to be.

UPDATE:

I've taken some measurements with the scopes at work and they appear to agree with the 8MHz result, but definitely show the 33.6MHz SYSCLK (within the margin of error):