torstai 28. huhtikuuta 2016

STM32F4xx display code, refined version


Okay, ages ago I promised improved version of the original code for using STM32F429/STM32F439 LCD controller. Like previously mentioned same code works with newer STM32F469/STM32F479 too and most likely on almost all other chips on the family.

So here it goes. Sorry this isn't a drop-in project, taking all code out from my libraries (with all supporting code included) would take a bit too much of time, so you'll have to settle for  generalized version, you'll need to adapt it to your specific display; mostly with defines listed at the top. Note that I use the pin mappings for the 429 discovery board, but display size does not match it so this won't work there directly. I left also out the SPI initialization as most display modules don't either have it or use very different settings.

Main change here (compared to previous one) are the defines; now they are defined in same way they typically are in datasheets so adaptation for different displays should be easier.


#include "stm32f4_discovery.h"
#include "stm32f4xx_conf.h"

 // Display module timing configuration. These are different for 
 // each display module; see module's datasheet for details.
#define DISP_WIDTH              (320)     /* active area width */
#define DISP_HEIGHT             (240)     /* active area height */
#define DISP_HSYNC_W            (3)       /* HSYNC width, in clocks */
#define DISP_VSYNC_H            (3)       /* VSYNC height, in rows */
#define DISP_HORIZ_BACKPORCH    (65)      /* Horizontal backporch, excluding HSYNC. Data starts after backporch. */
#define DISP_VERT_BACKPORCH     (15)      /* Vertical backporch, excluding VSYNC */
#define DISP_HORIZ_FRONTPORCH   (22)      /* Horizontal frontporch (after data on scanline) */
#define DISP_VERT_FRONTPORCH    (42)      /* Vertical frontporch */ 
 
unsigned char dispFrameBuff[DISP_WIDTH * DISP_HEIGHT]; 
 
#define LTDC_CFG_PINCOUNT 22
const int ltdc_lcd_pins[] =
    // port AF pin. For some reason LCD has pins in both AF9 (alternate function 9) 
    // and AF14 slots.
  { PORTA | 3 | (14<<8), PORTA | 4 | (14<<8), PORTA | 6  | (14<<8), 
    PORTA | 11  | (14<<8), PORTA | 12  | (14<<8),
    PORTB | 0 | (9<<8),  PORTB | 1 | (9<<8),
    PORTB | 8 | (14<<8), PORTB | 9 | (14<<8), PORTB | 10 | (14<<8), 
    PORTB | 11 | (14<<8),PORTC | 6 | (14<<8), PORTC | 7 | (14<<8), 
    PORTC | 10 | (14<<8),PORTD | 3 | (14<<8), PORTD | 6 | (14<<8), 
    PORTF | 10 | (14<<8), PORTG | 6 | (14<<8), PORTG | 7 | (14<<8), 
    PORTG | 10 | (9<<8), PORTG | 11 | (14<<8), PORTG | 12 | (9<<8) };
   // some pins for easier probing;
   // PC2  = CS
   // PD13 = CMD/DATA
   // PF9  = SDA
   // PF7  = CLK
   // PA4  = vsync (active low)
   // PC6  = hsync (active low)
   // PF10 = DE (active high)
   // PG7  = dotclk


void dispInit()
{
   // Enable clocks for GPIOs (pins) and devices. Last is DMA2D (Chrom-Art)
  unsigned int i;
  RCC->AHB1ENR |= (RCC_AHB1ENR_GPIOAEN | RCC_AHB1ENR_GPIOBEN | RCC_AHB1ENR_GPIOCEN | 
     RCC_AHB1ENR_GPIODEN | RCC_AHB1ENR_GPIOFEN | RCC_AHB1ENR_GPIOGEN | (1<<23) ); 

  // Initialize LTDC pins in AF mode.
  for (i = 0; i < LTDC_CFG_PINCOUNT; ++i)
    { unsigned int af = (ltdc_lcd_pins[i] >> 8) & 15;
       // Set pin (3) in (PORTA) to (AF14) through GPIO controller
      ioPinSetAF(ltdc_lcd_pins[i] & (~0xff00), 0, af);
    }

  dispLTDCInit();

}


/* -----------------------------------------------------------------
 * Init internal LTDC controller.
 */
void dispLTDCInit()
{
  RCC->APB2ENR |= (1 << 26); // Enable LTDC clock.

  /*   Clock setup: LCD pixel clock = HSE / M * N / R / DIVR (see your 
   *   display module specs to see what it should be) Most discovery board 
   *   templates has HSE 8MHz and M at 8. This gives PLL input clock of 1 MHz.
   *
   *   Then we use values:
   *      N = 192,  R = 4,  DIVR = 8 so output freq is 6 MHz
   *
   * PLL has also Q divider, for SAI (audio), but we do not use it so it
   * it just set to something here.
   */

#define PLLSAI_N     192  /* Multiplier for input clock (which is HSE/M) */
#define PLLSAI_R     4    /* Divider 1; /4 */
#define PLLSAI_DIVR  2    /* Divider 2; 0=/2, 1=/4, 2=/8, 3=/16 */

  RCC->PLLSAICFGR = (PLLSAI_R << 28) | (4 << 24) | (PLLSAI_N << 6);
  RCC->DCKCFGR = (RCC->DCKCFGR & (~0x30000)) | (PLLSAI_DIVR << 16);    

  RCC->CR |= (1<<28); // enable PLL SAI and wait until it is ready
  unsigned int n = 0;
  while (!(RCC->CR & (1<<29)))
    { if (++n > 2000000)
        { serialWrite((unsigned char*)"pllsai fail\r"); // PLL clock did not start; failure somewhere.
          return;
        }
    }

  LTDC->GCR = 0; // disable LCD-TFT controller for re-initialisation

   // Set up LCD timing variables. At first these values may seem like black magic
   // but trust me, if you spend some time with display datasheet they become clear eventually.
   // Or just trust me on these :)
  LTDC->SSCR = ((DISP_HSYNC_W-1) << 16) | (DISP_VSYNC_H-1);
  LTDC->BPCR = ((DISP_HSYNC_W+DISP_HORIZ_BACKPORCH-1) << 16) | ((DISP_VSYNC_H+DISP_VERT_BACKPORCH-1));
  LTDC->AWCR = ((DISP_HSYNC_W+DISP_HORIZ_BACKPORCH+DISP_WIDTH) << 16) | (DISP_VSYNC_H+DISP_VERT_BACKPORCH+DISP_HEIGHT-1);
  LTDC->TWCR = ((DISP_HSYNC_W+DISP_HORIZ_BACKPORCH+DISP_WIDTH+DISP_HORIZ_FRONTPORCH-1) << 16) | (DISP_VSYNC_H+DISP_VERT_BACKPORCH+DISP_HEIGHT+DISP_VERT_FRONTPORCH-1);

   // background color (23:0 : R8G8B8). Purple-ish for testing.
  LTDC->BCCR = 0xff00ff;

   // Enable layer 1. We have 320x240 (76800) bytes in internal memory in L8 (with color LUT) mode
   // Discovery board has also external memory that could be used for bigger
   // display sizes, color depths or multiple display pages.

   // From the display controller's point of view the image is 240x320 pixels, portrait mode.
   // Entire area configured for layer 1.
  LTDC->L1CR = 0; // disable for reprogramming.
  LTDC->L1WHPCR = ((DISP_WIDTH+DISP_HSYNC_W+DISP_HORIZ_BACKPORCH+2) << 16) | (DISP_HSYNC_W+DISP_HORIZ_BACKPORCH);
  LTDC->L1WVPCR = ((DISP_HEIGHT+DISP_VSYNC_H+DISP_VERT_BACKPORCH) << 16) | (DISP_VSYNC_H+DISP_VERT_BACKPORCH);
  LTDC->L1PFCR = 5; // Pixel format: 5=L8. 2= RGB565
  LTDC->L1CFBAR = (unsigned int)dispFrameBuff;
  LTDC->L1CFBLR = ((DISP_WIDTH) << 16) | (DISP_WIDTH+6); // hi:line pitch in bytes; lo:(width_in_bytes+3). Note that L8 has just 1 byte per pixels!
   // docs say that CFBLR loword should be W+3 but that doesn't seem to work, at least in  L8 mode. So +6 instead.
  LTDC->L1CFBLNR = DISP_HEIGHT;
  LTDC->L1CKCR = 0x000; // Black data is transparent, allowing background to be shown.

    // Set color look-up tables. Here we fill only first 128 entries with black-to-full color values.
  unsigned int i;
  for (i = 0; i < 16; ++i) 
    LTDC->L1CLUTWR = ((i+0)  << 24) + (i*0x101010); //   0-15: 16 greyscales
  for (i = 0; i < 16; ++i) 
    LTDC->L1CLUTWR = ((i+16) << 24) + (i*0x100000); //  16-31: 16 reds
  for (i = 0; i < 16; ++i) 
    LTDC->L1CLUTWR = ((i+32) << 24) + (i*0x001000); //  32-47: 16 greens
  for (i = 0; i < 16; ++i) 
    LTDC->L1CLUTWR = ((i+48) << 24) + (i*0x000010); //  48-63: 16 blues
  for (i = 0; i < 16; ++i) 
    LTDC->L1CLUTWR = ((i+64) << 24) + (i*0x101000); //  64-79: 16 yellows
  for (i = 0; i < 16; ++i) 
    LTDC->L1CLUTWR = ((i+80) << 24) + (i*0x100010); //  80-95: 16 purples
  for (i = 0; i < 16; ++i) 
    LTDC->L1CLUTWR = ((i+96) << 24) + (i*0x001010); // 96-112: 16 cyans

  LTDC->L1CR = 0x13; // Enable layer1 (0x01), CLUT (0x10) and color key (0x02)

   // Fill display buffer with some data.
  for (i = 0; i < sizeof(dispFrameBuff); ++i)
    { dispFrameBuff[i] = 7;
    }

   // White borders
  for (i = 0; i < DISP_WIDTH; ++i)
    {
      dispFrameBuff[i] = 15;
      dispFrameBuff[i+DISP_WIDTH] = 15;
      dispFrameBuff[i+(DISP_HEIGHT-1)*DISP_WIDTH] = 15;
      dispFrameBuff[i+(DISP_HEIGHT-2)*DISP_WIDTH] = 15;
    }
  for (i = 0; i < DISP_HEIGHT; ++i)
    {
      dispFrameBuff[i*DISP_WIDTH] = 15;
      dispFrameBuff[i*DISP_WIDTH+1] = 15;
      dispFrameBuff[i*DISP_WIDTH+DISP_WIDTH-1] = 15;
      dispFrameBuff[i*DISP_WIDTH+DISP_WIDTH-2] = 15;
    }

   // Color square at upper left corner
  for (i = 0; i < 16; ++i)
    { unsigned int j;
      for (j = 0; j < 16; ++j)
        { dispFrameBuff[DISP_WIDTH*2+2+ i*DISP_WIDTH*2+j*2] = i+j*16;
          dispFrameBuff[DISP_WIDTH*2+2+ i*DISP_WIDTH*2+1+j*2] = i+j*16;
          dispFrameBuff[DISP_WIDTH*3+2+ i*DISP_WIDTH*2+j*2] = i+j*16;
          dispFrameBuff[DISP_WIDTH*3+2+ i*DISP_WIDTH*2+1+j*2] = i+j*16;
        }
    }


   // Reload shadow registers to active in SRCR
  LTDC->SRCR = 1; // 1=reload immeiately; 2=reload on vertical blank

   // Enable controller.
  LTDC->GCR = 1;

}


So there.

sunnuntai 24. huhtikuuta 2016

LCD controller of STM32F469


After some troubles of acquiring the chips, I've managed to play with the LCD controller of newer STM32F469 (in this regard STM32F479 is similar) and the routines I listed here before work equally well on this MCU too. This shouldn't be surprise, it's "just" newer model of the same MCU family. The main difference is that the new chip has full 128kB more internal memory, allowing larger (in pixels) displays to be used without resorting to external memory (and by extension, more complex board design.)

Of course, the display timing parameters - that is, front- and backporch, sync widths and so on - still feel like black magic to me, and getting them right (despite having display specifications) is mostly exercise of black magic. Or guesswork, take your pick. Especially for displays that don't use the DE line.

I haven't tried with the display included with the discovery kit though, so I won't publish any code here now; parameters aren't same for that display so my code (using very different display) wouldn't be of any use to anyone.

One register I haven't fully figured out is LxCFBLR (or L1CFBLR or just CFBLR) that should include line width and pitch. Documents say that pitch is length of line in bytes plus three, but for me that doesn't work; plus six does the trick.





torstai 21. huhtikuuta 2016

Prototyping with weapons


Commercial product development forces me occasionally to dig into information about topic I'd rather not touch, for a reason or another. Some years ago I had jolly good time (/s) learning about various aspects of EMI (immunity in this case) that are not commonly encountered on automotive field (read: there was essentially no direct information to be found so I had to read and adapt information from unrelated fields), and there is of course always fun ISO 9001 stuff (with new edition coming up, oh the joy...) and now I had to dig into world of weapons exports.

Okay, slight exaggeration there, neither exporting nor weapons were involved.

I  had finished design for a new board I wanted to test so I ordered components for it. Nothing special really; some connectors, a new model of MCU, some passives, few transistors and so on. Typically the place I use (I won't name it but I've read that they have history with this behavior) sends order confirmations immediately. This time though it took a week, and they told me they couldn't deliver the MCU I ordered - a pretty typical STM32F-series microcontroller. After some inquiries they finally admitted that they could not export them from UK due to export regulations.

This was very curious, as last I checked UK hasn't exited EU yet, which means that this trade would be completely within EU customs area and thus there is no exporting involved - and thus no export controls should be involved here. Let's just say that I wasn't very happy about this, since without this MCU my new shiny board design would be completely useless.

After some more reading I found out that there are actually are some export regulations involved when ordering stuff from UK, specifically concerning what some government page I can't find anymore (d'oh!) called sensitive technologies, specifically ones that are listed in EU's dual use listing, annex IV.

You can read the complete EU's dual use list here. Be warned though, it is 228 pages long, but like just about any EU directive it's relatively easy to read once you are familiar with their structure (unlike some FCC regulations -- I tried once and I think I broke my brain trying to decipher that horribly written legalese... But that's topic for another time). Dual Use list's annex IV is relatively short but doesn't make much sense unless you have some understanding of rest of the categories document.

I don't blame you if you don't want to read it, but to make it short, dual use contains anything (be it materials directly, raw materials or tools for development or processing materials) that could be used to construct nuclear weapons, missiles, advanced guidance systems, stealth technologies (air and underwater, and curiously enough underwater detection seems to be a very hot potato too - guess some countries don't want their shiny -- umm, no, wait -- matte-black rubbery (I'm tempted to add 'vibrating' here) toys to be found) and so on. So in general not things that you could just pick up from a corner shop.

I read the annex IV through, several times actually, and I really can't understand why this common MCU suddenly appears to be so dangerous. My best guess is that they expect me to use them like shown in this clip here, but since I only needed just few of them this time it wouldn't be very effective even then.

There is mention of cryptography in annex IV, but the specific MCU I ordered doesn't include any crypto parts in it (those come with different part number). Having cryptography regulated is kinda funny these days as anyone can download sources for AES, RSA or whatever from just about anywhere in the world so that part of export control is pretty much ineffective anyway (but not completely ineffective or silly, mind you. I let you to do the thinking of the logic here yourself.)

So I just ordered the chip from another shop which had no problem delivering it (directly from US actually, based on cargo manifest information, which pretty much clears this chip anyway - export controls from US are certainly more strict than from UK)

So, my best guess is that they (first shop) screwed up. The part I ordered is not actually under export control, but some idiot there only eyed through the product family datasheet, saw crypto being mentioned and flagged entire family of products as controlled although only one part in series has crypto parts in it. And they would not budge nor even explain their reasoning concerning this part.

So screw them. And like I already mentioned, based on some reading I've done this wasn't the first time they fucked up like this. Mostly I'm pissed off because they flatly refuse to admit being wrong here...


maanantai 18. huhtikuuta 2016

Lead-free or not?


European RoHS directive (Reduction of Hazardous Substances) was introduced in 2002, and has been in full effect for about 10 years. In electronics side this primarily affects solder; old tin/lead solder can no longer be used (although I think exceptions may still be in effect in military, aerospace and/or medical fields - I'm not exactly sure since I don't deal with those directly.)

When RoHS was introduces many veterans absolutely refused to even touch lead-free solder (when doing hand assembly/repair). Reasons were various; risk of tin whiskers, lead-free is harder to work with, solder joints are less pretty and more brittle and what not. Even now some refuse to use it.

Not me. I prefer to do most of the initial prototyping and most repairs with lead-free solder.

The reason is (and has always been) very simple: it is more difficult to work with. So when I do typical, relatively simple stuff (and some more difficult things too, like 0.5mm-pitch TQFPs) with lead-free solder, I have gotten used to it and it's relatively easy (for me) now.

Now, when I have to work with hard stuff - primarily repairs and rework with small-pitch packages and tight spacing - I can switch to leaded solder and it's almost like a cheat mode - so easy to work with that even difficult stuff is a breeze.

Really, what other reasons do you need?


perjantai 15. huhtikuuta 2016

"Smart" TV


Warning: Mild rant ahead.

My parents recently replaced the spare TV (old tube-based one which had been dying of old age) with a new LCD one. And if this is how "smart" TVs in general work, I most definitely will not want one. This wasn't some random no-name brand either, but one of the most widely known and reputable brands.

The worst part is user interface lag. You'd think that controls for typical TV usage - that is, changing channels and volume - would be well tuned these days but no. Well, when you start it the picture comes up almost immediately, as well as the sound, but TV will not respond to any commands for close to 30 seconds or so. I suspect this is because the user interface software (separate from the picture/sound decoding) is still booting up at that point. So if the volume was previously set way up (like it often is) and kid is sleeping in next room... Well, tough luck.

Not only that, but it seems to stop responding for a few seconds every now and then. So very annoying. I haven't tried the more advanced features, but even the main menu feels damn sluggish.

I was kinda thinking about upgrading our aging LCD TV at home, but if all new TVs are like this... Damn it, I'll just keep my old, dumb TV until it dies. At least it works, damn it.



maanantai 11. huhtikuuta 2016

New and fragile diesel engines


At the moment I'm driving Skoda, which is part of Volkswagen concern. Essentially it's a designed around common frame and engine designed by VW, but is "eastern" design and somewhat cheaper to buy. I drive about 20000km a year, which means that so far diesel (with average consumption about 5l/100km with this car, sometimes reaching as low as 4l/100km with longer travels at relaxed pace) has been a quite natural choice. And I happen to like the low-end torque diesels offer, gasoline engines just need to be revved lot higher for equivalent power.

It used to be that traditional fully mechanical diesel engines could go for half a million kilometres or so without major hassles. Unfortunately new diesel engines seem to be more fragile than the old ones - especially the emissions control parts.

Just a few days ago the car suddenly popped up "Engine fault - workshop" message and dropped into limp mode - it lost a lot of power, resulting me barely being able to drive uphill. The reason was EGR valve - emission control device that, based on what I've read is the most fragile part of this specific engine type. And it isn't cheap either, replacement cost me about 1100eur (!!!), half of which was the part itself. I was pretty far away from home at the moment so I didn't really have any options to look for cheaper alternatives.

Now, as bad is the situation was, I got a replacement car for the duration of repairs for essentially free. The replacement was equipped with a bit newer gasoline engine, which for my absolute surprise had used just 16,5 litres of gasoline after 340km (that is amount I pumped in it before returning it). That's about 4,85l/100km, or about 48.5mpg (US). Pretty damn impressive for gasoline engine, as I expected consumption closer to 7l/100km or so at best (this was mostly highway driving at 80-100km/h, slower city driving might have different results)

With that low consumption I really have to think about what kind of engine my next car will have. As much as I like (and am used to) the low-end torque diesels offer, this kind of figures make gasoline engines quite tempting again, especially with tax structure of cars and fuels around here.



perjantai 8. huhtikuuta 2016

Phone battery longevity


It seems that almost everyone just takes it as granted that you have to charge your phone daily. And here I am, happily using my phone for 4-5 (yes, four to five) days between recharges, and I charge phone typically when it is down to 20% charge or so.

My usage doesn't really strain battery that much either; occasional web browsing, some navigation/tracking, audiobooks, but mostly plain old phone calls (it's my work phone, after all). I don't like playing games with a phone which may be large part of power usage for some people. For long time I though this to be the reason for extended usage time.

I have one Android phone too (cheap-ish Galaxy Trend-series), used mainly for app development/testing, and it, too, seems to happily go for about same time without needing recharging. Initially I thought this to be because I don't really use it for anything - it just sits on my desk, powered up but doing nothing. This would seem to match with my guess above.

Not so.

Last year I had to send my Jolla to be repaired - speaker developed horrible metallic sound which rendered phone useless as a phone. So I took the Android phone and used it for about two weeks. Usage pattern was pretty much the same as with my Jolla. And the battery life plummeted - even with that low usage I had to charge damn Android phone daily! (and afterwards, when it just sits there, it's back to four-day charge cycles)

If battery life were the primary consideration, I now certainly know which OS I prefer (I don't have hands-on experience on the remaining two OSs but based on what I've heard they aren't any better than Android.)  Now, if Jolla were just to offer slightly smaller phone, as current model is a bit too large for my liking... (and really, damn paid app store - I think that their disregard of proper app store was their Single Biggest Mistake)

sunnuntai 3. huhtikuuta 2016

A fan to warm you up


I was in a hardware store recently and noticed this curious thing there.


This is a fan you can place on top of a wood-burning stove. If you are unfamiliar with them, this type of stove is essentially a metal can in which you can burn wood to warm a (typically remote and unpowered) cabin. Their thermal mass is very low, which in practice means that while fire burns they give out a lot of heat at one point, and after fire has died the heat stops very shortly too.

Enter this thing.

This is essentially nothing but a heat sink with fan attached to it, so that when it sits on top a hot stove the heat rises up to radiating fins and fan then circulates warm air better in the surroundings. But wait, it gets better yet.

See that red arrow I drew there? There is a peltier element between top and bottom parts which generates the electricity needed to run the fan when this is hot. No external power needed.

I have no idea how efficient this is (i.e. how much and well this will warm the air and circulate it), but knowing typical peltier elements I suspect it'll need to get very, very hot (well actually, bottom part must get very very hot, and top part should stay relatively cool to allow heat transfer through peltier to generate electricity) and even then the air flow may not be very strong.

But still, when you are trying to warm your freezing cabin on evening before crawling to a sleeping bag, every bit of spread heat certainly helps.