Wednesday, July 9, 2014

Using the STM32 DCMI with JPEG Sensors

If you've been looking for this, chances are you saw Frank's post on the subject, while it contains very useful information and he mentions reading JPEG, he didn't explain his approach in details, so here's my soultion.

First, let me reiterate the issue with JPEG and the STM32, as you already know JPEG images are compressed, so you don't know the exact image size beforehand, you expect to read it after a complete transfer, however, the STM32 DMA data count register (NDTR) counts down, and therein lies the problem, the counter reaches zero after a complete transfer (or gets reloaded in case of a circular mode) either way, you can't find the transfer size after a DMA transfer is complete.

So here's my solution to this problem, first set the DMA stream to transfer the maximum frame size you expect or the absolute max for a DMA transfer (2^16 bits), afterwards, start the DCMI, it will generate a FRAME_IT when a whole frame has been read (VSYNC is asserted high/low depending on the polarity) however, the DMA will never stop, so at this point you should abort the DMA transfer, this will leave the remainder of the transfer size you requested earlier in the NDTR register (it's guaranteed to be there because the transfer was interrupted) now subtract that from the initial transfer size and you have the frame size.

Here's an example code, which uses the STM32Cube (HAL):

uint32_t addr;
uint16_t length;

addr = (uint32_t) image->pixels;
length = MAX_XFER_SIZE;

/* Start the DCMI */
HAL_DCMI_Start_DMA(&DCMIHandle,
        DCMI_MODE_SNAPSHOT, addr, length);

/* Wait for frame */
while ((DCMI->CR & DCMI_CR_CAPTURE) != 0) {

}

/* The frame is finished, but DMA still waiting
   for data because we set max frame size
   so we need to abort the DMA transfer here */
HAL_DMA_Abort(&DMAHandle);

/* Read the number of data items transferred */
image->size = (MAX_XFER_SIZE - DMAHandle.Instance->NDTR)*4;
One issue remains, what if it overflows ? well that would mean you're really cutting it close, but anyway (I haven't tested this) but you should be able read the overflow flag in the DCMI port, or check the JPEG markers.
Read more ...

Tuesday, June 3, 2014

OV9650 Breakout

I made this breakout for the OV9650 a while ago, and I've been waiting to test it before sharing, I finally had the chance to do so, I used an FPGA this time for testing, and here's the result:


The breakout has two SOT23 LDOs for the sensor's core, digital and analog supplies and requires a single external 3.3v supply, note that most sensors have internal regulators for the core supply, so technically you only need one, anyway, those LDOs should be fairly easy to source, their exact voltages depend on the sensor used. The board is compatible with a few other Omnivision sensors, such as the OV9655, OV7660, OV2640 etc... however, make sure it has the same pinout...

Here's the breakout and Eagle library:
https://github.com/iabdalkader/OV965x

Alternatively, you could order the PCB directly from OSHPark:
http://oshpark.com/shared_projects/FQwlpPpi
Read more ...

Saturday, April 26, 2014

Running ZPU Softcore on Lattice ICE40

So I got my hands on a new FPGA development board, a Lattice ICE40HX8K eval kit, this is a really basic low-cost board/breakout with a few LEDS, EEPROM and a dual FTDI2232H UART/FIFO, that you can use for programming the EEPROM/FPGA...What I really love about this board, other than the low cost and many I/Os, is that the second FTDI port is configured as a serial port and connected to the FPGA, so basically you get a free USB<->USART bridge which you can use to talk to the FPGA with the same USB cable used for programming (see Linux notes) that's basically all I need as I'm already familiar with FPGAs...
I wanted to evaluate those FPGAs for a new project I'm working on, which will most likely require a minimal SoC with a wishbone bus... My first thoughts was to use my uMIPS core, but it doesn't support the wishbone bus (I might fix that later) so I decided to give the ZPU core a shot, which was definitely worth the time it took to get it working on the Lattice FPGA.

In an effort to save someone else's time, I created this github repo for the project, it has the sw, hdl, project files and a tested bitmap file, all you need to do is to program the bitmap (or synthesis the project with iCEcube2 if you want), open /dev/ttyUSB1, set it to 8N1, 9600, parity=none and reset the core (you will need and external switch for that, connect it between H16 and GND), it should print out "Hello World!"...

ZPU Core:
The ZPU is small 32-bit stack-based CPU, it comes with a gcc-toolchain, lots of variants and examples... For this project I used the zealot/medium variant which comes with a physical I/O layer (provides a USART, GPIO and a timer) and I'm using a single port RAM, no PLLs (to keep things simple) and all the RAM blocks on the ICE40HX8K (16KBs total) for the program image. There's no wishbone yet, I will work on that next.

Compiling Stuff:
First It's worth mentioning here that I modified the I/O memory map (crt_io.c) since there's only 16KBytes of on-chip ram, I modified it to use bit 15 for the I/O space, the modified crt_io.c is included in the repo and the Makefile will compile and link this modified map for you.

If you want to compile a new program, you'll need to download the ZPU toolchain first and use the Makefile in zpu/sw it will generate a bunch of files, the one we're interested in is hello.bram, this contains the instructions/data used to initialize the embedded RAM, copy the contents of this file and replace the one in bram.vhdl.

Linux notes:
I didn't have much trouble getting the software running on Linux, but one thing you might want to do to make things nicer, is to use this udev rule to unbind ftdio-sio from the first FTDI port when the board is connected, this will allow the programmer to use the first port as JTAG with the second (USART) port bound to ftdi-sio at the same time, so you can use it as to talk to the FPGA after programming:

BUS=="usb",ACTION=="add",SYSFS{idVendor}=="0403",SYSFS{idProduct}=="6010",MODE=="0660"
,GROUP=="plugdev",SYMLINK+="ftdi-0"
SUBSYSTEM=="usb",DRIVER=="ftdi_sio",ATTRS{idVendor}=="0403",SYSFS{idProduct}=="6010"
,ATTR{bInterfaceNumber}=="00",RUN+="/bin/sh -c 'echo `basename %p` >/sys/bus/usb/drivers/ftdi_sio/unbind'"

The following is the iCEcube report for this design:

Total Logic Cells: 3099/7680
Combinational Logic Cells: 2471     out of   7680      32.1745%
Sequential Logic Cells:    628      out of   7680      8.17708%
Logic Tiles:               473      out of   960       49.2708%
Registers: 
Logic Registers:           628      out of   7680      8.17708%
IO Registers:              0        out of   1280      0
Block RAMs:                32       out of   32        100%
Pins:
Input Pins:                3        out of   206       1.45631%
Output Pins:               9        out of   206       4.36893%
InOut Pins:                0        out of   206       0%
Global Buffers:            6        out of   8         75%
PLLs:                      0        out of   2         0%

Read more ...

Saturday, February 8, 2014

OpenMV Update: MicroPython, More I/O, uSD and Lots of Other Things!

Time for another update, sorry this took me so long, I've been very busy working on OpenMV, the good news is I have lots of new features implemented! There's a new (smaller :D) hardware revision with more I/O (USART/I2C and SPI) and a uSD socket, MicroPython support, an IDE for the camera, and for those of you who have been wondering, I'm working with Michael Shimniok from Bot-Thoughts on doing a Kickstarter campaign for OpenMV, soon, hopefully, you will be able to get one for a very reasonable price :) so stay tuned!

Okay, so on the software side, you've probably heard of the MicroPython project, if not make sure to check it out, basically MicroPython is very efficient, lightweight Python VM for microcontrollers, the plan was to script the camera with Lua/eLua but MP has some really neat features already implemented, so long story short, I've decided to script the camera with MP... after lots of work, I managed to get MP running on OpenMV, and wrote some MP bindings to export the subsystems of OpenMV to Python, eventually it will be completely controlled with Python.

So how this works so far, basically, on reset OpenMV runs a default Python script with the old serial camera interface (receive commands from the serial port, process and return result) but it also shows up as a small USB storage device where you can copy your own Python script(s), reset and it runs that instead of the default script.. In addition to that, you can also "talk" to the camera directly using a Python shell over the com port while watching the framebuffer in realtime :)

I've also combined all those nice features into a single "IDE" for convenience, written with Python, PyGTK and PyUSB. The IDE has a Python shell, a framebuffer viewer, and it can run scripts or save them to flash:


Moving on to the hardware, the new revision is 1.0x1.30 inches, it has a tiny uSD socket (which will be available to Python user code) USART, SPI and I2C broken out on the main 2.54mm header and a separate 2mm SWD debugging header.. There's also a switch, which will be used for boot or reset.
Here are some pics of the 3rd (2nd?) revision:



Compared to the old one:



That's it for now, please let me know if you have any comments :) thanks!
Read more ...

Saturday, January 18, 2014

Overclocking the STM32F4

I've been doing some tests with the STM32F407 to see how fast it can go, STMicro has released an almost identical one that runs at 180MHz, is it a marketing thing ? will they release a 200MHz version in a few months? who cares, anyway, I was able to run the STM32F407 at 240MHz without any "obvious" problems, in addition to overclocking, the code listed below lets you set some different frequencies, which could be useful for frequency scaling.

On the STM32F4 the clocks are controlled via the RCC (Reset and Clock Control) block, it's easy enough to change the frequency, the tricky part however, is understanding all the different dividers and getting them right. According to the datasheet, the following are the maximum clock frequencies for the core and peripheral buses:
SYSCLK: 168MHz
PLLC48: 48MHz (feeds the USB OTG FS and RNG)
APB1 clock: 42MHz
APB2 clock: 84MHz
Based on that, I used multiples of 42MHz to get the maximum possible frequencies for the peripheral buses (APB1 and APB2) for frequencies lower than 168MHz (this doesn't always result in the maximum USB frequency, which is required to get the full 12Mbps), for frequencies higher than 168MHz, I used 200MHz and 240MHz mainly because they are convenient to my application, you might want to use different frequencies based on yours:
enum sysclk_freq {
    SYSCLK_42_MHZ=0,
    SYSCLK_84_MHZ,
    SYSCLK_168_MHZ,
    SYSCLK_200_MHZ,
    SYSCLK_240_MHZ,
};

void rcc_set_frequency(enum sysclk_freq freq)
{
    int freqs[]   = {42, 84, 168, 200, 240};

    /* USB freqs: 42MHz, 42Mhz, 48MHz, 50MHz, 48MHz */
    int pll_div[] = {2, 4, 7, 10, 10}; 

    /* PLL_VCO = (HSE_VALUE / PLL_M) * PLL_N */
    /* SYSCLK = PLL_VCO / PLL_P */
    /* USB OTG FS, SDIO and RNG Clock =  PLL_VCO / PLLQ */ 
    uint32_t PLL_P = 2;
    uint32_t PLL_N = freqs[freq] * 2;
    uint32_t PLL_M = (HSE_VALUE/1000000);
    uint32_t PLL_Q = pll_div[freq];

    RCC_DeInit();

    /* Enable HSE osscilator */
    RCC_HSEConfig(RCC_HSE_ON);

    if (RCC_WaitForHSEStartUp() == ERROR) {
        return;
    }

    /* Configure PLL clock M, N, P, and Q dividers */
    RCC_PLLConfig(RCC_PLLSource_HSE, PLL_M, PLL_N, PLL_P, PLL_Q);

    /* Enable PLL clock */
    RCC_PLLCmd(ENABLE);

    /* Wait until PLL clock is stable */
    while ((RCC->CR & RCC_CR_PLLRDY) == 0);

    /* Set PLL_CLK as system clock source SYSCLK */
    RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);

    /* Set AHB clock divider */
    RCC_HCLKConfig(RCC_SYSCLK_Div1);

    /* Set APBx clock dividers */
    switch (freq) {
        /* Max freq APB1: 42MHz APB2: 84MHz */
        case SYSCLK_42_MHZ:
            RCC_PCLK1Config(RCC_HCLK_Div1); /* 42MHz */
            RCC_PCLK2Config(RCC_HCLK_Div1); /* 42MHz */
            break;
        case SYSCLK_84_MHZ:
            RCC_PCLK1Config(RCC_HCLK_Div2); /* 42MHz */
            RCC_PCLK2Config(RCC_HCLK_Div1); /* 84MHz */
            break;
        case SYSCLK_168_MHZ:
            RCC_PCLK1Config(RCC_HCLK_Div4); /* 42MHz */
            RCC_PCLK2Config(RCC_HCLK_Div2); /* 84MHz */
            break;
        case SYSCLK_200_MHZ:
            RCC_PCLK1Config(RCC_HCLK_Div4); /* 50MHz */
            RCC_PCLK2Config(RCC_HCLK_Div2); /* 100MHz */
            break;
        case SYSCLK_240_MHZ:
            RCC_PCLK1Config(RCC_HCLK_Div4); /* 60MHz */
            RCC_PCLK2Config(RCC_HCLK_Div2); /* 120MHz */
            break;
    }

    /* Update SystemCoreClock variable */ 
    SystemCoreClockUpdate();
}

Note: after calling this function, you will probably need to re-enable all the clocks ...

Note on Overclocking:
There's no telling if overclocking will always work, it might fail at some temperature, critical path or just randomly, but it is nice to know that you have the option to run (burn?) the micro at higher speeds if needed... One thing I can confirm though, is that it doesn't overheat too much, obviously touching the micro with your finger tip is not an accurate way to determine that, so I took this a step further and made a series of tests using the internal core temperature sensor.

I ran the micro at different frequencies while collecting samples from the internal temperature sensor, which is connected to one of the ADC's channels, for each frequency I collected a number of samples and then I plotted the average vs the frequencies and here's the result:
One important thing to note here, the internal temperature sensor readings varies from chip to chip up to 45 degrees, which means those are NOT absolute temperature values but should only be used to detect temperature variations. Now, the conclusion, it looks like overclocking the STM32F4 from 168MHz to 240MHz increases the core temperature by ~4 degrees.
Read more ...