Fez Cerberus & Display N18

Hello,

I started yesterday playing with my Fez Cerberus v1.2 mainboard and my Display N18 v1.1 module in Visual Studio Express 2012 and wanted to do simple things like rotating the screen or displaying an image …

These tasks proved to be not at all easy …

“Cannot set LCD rotation on displays that are not LCD controller based” is the error I get when I try to rotate the display in Landscape.

display_N18.SetLCDRotation(GTM.Module.DisplayModule.LCDRotation.Clockwise90Degrees);

Any idea why? Does this functionality work at all or just on more powerful mainboards? How can I know what’s supported and what is not?

About images, how can I display an image that I have in my project’s Resources directory? I have dragged in there a 128 x 160 pixels image that I would like displayed on screen as a basic exercise and go from there …

Also, it seems that many features are out of reach for this mainboard as I get out of memory exceptions every time I try to use examples found online.

Thanks,

Andrei-Virgil

The N18 is a SPI based display, so LCD functions such as SetLCDRotation will not work with it. On the Cerberus, in the library GHI.OSHW.Hardware, we provide the function Util.SetSpecialDisplayConfig that takes a rotation value as its last parameter. It will only display bitmaps with rotated full screen dimensions when bitmap.flush() is called. Be sure to pass in the SPI configuration as well otherwise your display will stop working since it defaults to null. We have created an issue internally to better document that function and its use.

As for the 128x160 image, that is too large to load as a resource on the Cerberus. You can slice it into smaller images and load and draw those one at a time making sure to dispose of one before loading the other. You can experiment to see what sizes you are able to load based on the memory use of the rest of your program.

Hi,

I already started working on the slicing images approach as I’ve seen it’s the recommended way of using images on Cerberus.

Will all the canvas be rotated using your suggestion ? Drawing lines and text will use the new orientation also ?

Thanks,

Andrei-Virgil

Say you rotate the screen 90 degrees, you’d create a bitmap with width of 160 and height of 128 instead of 160x120. You’ll then draw directly on that bitmap using what coordinates you need. Once you call flush, it’ll be sent out to the screen and will appear rotated by 90 degrees. The rotation only occurs when you flush a bitmap that is the width and height of the entire screen, but yes, the entire screen/canvas will be rotated provided the bitmap is the right size.

ok, that sounds nice, but will it work whit the puzzle solution of displaying full screen images on cerberus ? or this is a conflicting situation ?

It should work, you will just have to create smaller images. The full screen bitmap will take up a large portion of the memory. You will draw the smaller resource images onto that bitmap, then flush. Depending on what exactly you are drawing, it might not be very fast.

…or use a device with more memory, like 16MB on spider, vs few KBs on Cerberus :slight_smile:

Yeah, it sounds like the stuff you are trying to do is much simpler if you select a device with slightly more power. You end up spending too much time fiddling with memory instead of finishing your prototype.

Spend $60 on a CobraII board and you will be better equipped to work with displays!

I’m trying to keep this project as low cost as possible, that’s the main reason I’m using the Cerberus mainboard.

i have news, good ones and less than good ones :slight_smile:

i’ve managed to fill the screen with content, but it’s not the right content :))

i’ve split my source image into 64 smaller tiles and displayed one of the images 64 times on the screen.

problem no 1: the image looks weird, as if it is missing some info … instead of some solid shape on the screen i see some blue and red dots, like Braille alphabet :slight_smile:

problem no 2 : i need a way to get the images from the resources dynamically. what i mean by this is that i know the name of the resource and i have a formula to compose it but how do i ask C# to give me the file based on a computed identifier ?

here is the code i’m using:


        public static uint DISPLAY_WIDTH = 128;
        public static uint DISPLAY_HEIGHT = 160;

        public static uint DISPLAY_COLUMNS_NO = 8;
        public static uint DISPLAY_ROWS_NO    = 8;

        public static uint DISPLAY_TILE_WIDTH = DISPLAY_WIDTH / DISPLAY_COLUMNS_NO;
        public static uint DISPLAY_TILE_HIGHT = DISPLAY_HEIGHT / DISPLAY_ROWS_NO;
....
        void ProgramStarted()
        {
            for (uint c = 0; c < DISPLAY_COLUMNS_NO; c++)
            {
                for (uint r = 0; r < DISPLAY_ROWS_NO; r++)
                {
                    byte[] tile = Resources.GetBytes(Resources.BinaryResources.Tile_SGV_C8_R3);
                    Debug.Print(tile.Length.ToString());
                    display_N18.DrawRaw(tile, DISPLAY_TILE_WIDTH, DISPLAY_TILE_HIGHT, c * DISPLAY_TILE_WIDTH, r * DISPLAY_TILE_HIGHT);
                }
            }
        }

images can be seen here, i use visual studio express 2012:

http://sdrv.ms/1bbSnuN
http://sdrv.ms/18PjICL

you can see a preview of the image that was loaded into resources and what i see on the display …

please share some knowledge :slight_smile:

Are these bitmaps? These are not raw images. use bitmap instead in your code.

yes, i loaded them as bitmaps.

can you please explain some more? what should i change ?

thank you

You call display_N18.DrawRaw. That functions expects the data to be converted into the format the device expects. The bytes you load as a resource are not in that format. You want something along the lines of:

byte[] vram = new byte[DISPLAY_TILE_WIDTH * DISPLAY_TILE_HEIGHT * 2];
Mainboard.NativeBitmapConverter(tile, vram, Mainboard.BPP.BPP16_BGR_BE);
display_N18.DrawRaw(vram, DISPLAY_TILE_WIDTH, DISPLAY_TILE_HEIGHT, c * DISPLAY_TILE_WIDTH, r * DISPLAY_TILE_HIGHT);

The function NativeBitmapConverter converts the bytes you get from the resource into the format that the device expects.

You could also create a bitmap:

var bitmap = new Bitmap(tile, Bitmap.BitmapImageType.Bmp);
display_N18.Draw(bitmap, c * DISPLAY_TILE_WIDTH, r * DISPLAY_TILE_HIGHT);

@ thisway.ro -

[quote]
problem no 2 : i need a way to get the images from the resources dynamically. what i mean by this is that i know the name of the resource and i have a formula to compose it but how do i ask C# to give me the file based on a computed identifier ?[/quote]

Short answer: You can’t. See http://blogs.msdn.com/b/aldenl/archive/2007/03/14/using-fonts-in-the-microsoft-net-micro-framework.aspx for a decent description on Resources.
Then look at Resources.Designer.cs in your project directory to get examples.

Long answer: I haven’t thought this through, but off the cuff, you could modify Resources.Designer.cs to create some type of mapped list – maybe an array of a class with two members: string, int. Then search the array based on the generated filename strings to find the corresponding int for the getResources… calls. Problem with this solution is you have to redo your work every time you change your resources.

there must be a more elegant way of iterating over the resource image tiles and displaying them. it would be insane to write 64 times the same lines of code just to display an image !

@ thisway.ro - You could use a T4 template to generate code. The following post has an example.

https://www.ghielectronics.com/community/forum/topic?id=10182

Of course you would need to tweak it for your requirements, but it should give you a good starting point for processing the resource file.

I ended up with this class partial:


    internal partial class Resources
    {
        internal static byte[] GetTile(String name)
        {
            switch (name)
            {
                case "Tile_SGV_C1_R1": return GetBytes(BinaryResources.Tile_SGV_C1_R1);
                case "Tile_SGV_C1_R2": return GetBytes(BinaryResources.Tile_SGV_C1_R2);
                case "Tile_SGV_C1_R3": return GetBytes(BinaryResources.Tile_SGV_C1_R3);
                case "Tile_SGV_C1_R4": return GetBytes(BinaryResources.Tile_SGV_C1_R4);
                case "Tile_SGV_C1_R5": return GetBytes(BinaryResources.Tile_SGV_C1_R5);
                case "Tile_SGV_C1_R6": return GetBytes(BinaryResources.Tile_SGV_C1_R6);
                case "Tile_SGV_C1_R7": return GetBytes(BinaryResources.Tile_SGV_C1_R7);
                case "Tile_SGV_C1_R8": return GetBytes(BinaryResources.Tile_SGV_C1_R8);

                case "Tile_SGV_C2_R1": return GetBytes(BinaryResources.Tile_SGV_C2_R1);
                case "Tile_SGV_C2_R2": return GetBytes(BinaryResources.Tile_SGV_C2_R2);
                case "Tile_SGV_C2_R3": return GetBytes(BinaryResources.Tile_SGV_C2_R3);
                case "Tile_SGV_C2_R4": return GetBytes(BinaryResources.Tile_SGV_C2_R4);
                case "Tile_SGV_C2_R5": return GetBytes(BinaryResources.Tile_SGV_C2_R5);
                case "Tile_SGV_C2_R6": return GetBytes(BinaryResources.Tile_SGV_C2_R6);
                case "Tile_SGV_C2_R7": return GetBytes(BinaryResources.Tile_SGV_C2_R7);
                case "Tile_SGV_C2_R8": return GetBytes(BinaryResources.Tile_SGV_C2_R8);

                case "Tile_SGV_C3_R1": return GetBytes(BinaryResources.Tile_SGV_C3_R1);
                case "Tile_SGV_C3_R2": return GetBytes(BinaryResources.Tile_SGV_C3_R2);
                case "Tile_SGV_C3_R3": return GetBytes(BinaryResources.Tile_SGV_C3_R3);
                case "Tile_SGV_C3_R4": return GetBytes(BinaryResources.Tile_SGV_C3_R4);
                case "Tile_SGV_C3_R5": return GetBytes(BinaryResources.Tile_SGV_C3_R5);
                case "Tile_SGV_C3_R6": return GetBytes(BinaryResources.Tile_SGV_C3_R6);
                case "Tile_SGV_C3_R7": return GetBytes(BinaryResources.Tile_SGV_C3_R7);
                case "Tile_SGV_C3_R8": return GetBytes(BinaryResources.Tile_SGV_C3_R8);

                case "Tile_SGV_C4_R1": return GetBytes(BinaryResources.Tile_SGV_C4_R1);
                case "Tile_SGV_C4_R2": return GetBytes(BinaryResources.Tile_SGV_C4_R2);
                case "Tile_SGV_C4_R3": return GetBytes(BinaryResources.Tile_SGV_C4_R3);
                case "Tile_SGV_C4_R4": return GetBytes(BinaryResources.Tile_SGV_C4_R4);
                case "Tile_SGV_C4_R5": return GetBytes(BinaryResources.Tile_SGV_C4_R5);
                case "Tile_SGV_C4_R6": return GetBytes(BinaryResources.Tile_SGV_C4_R6);
                case "Tile_SGV_C4_R7": return GetBytes(BinaryResources.Tile_SGV_C4_R7);
                case "Tile_SGV_C4_R8": return GetBytes(BinaryResources.Tile_SGV_C4_R8);

                case "Tile_SGV_C5_R1": return GetBytes(BinaryResources.Tile_SGV_C5_R1);
                case "Tile_SGV_C5_R2": return GetBytes(BinaryResources.Tile_SGV_C5_R2);
                case "Tile_SGV_C5_R3": return GetBytes(BinaryResources.Tile_SGV_C5_R3);
                case "Tile_SGV_C5_R4": return GetBytes(BinaryResources.Tile_SGV_C5_R4);
                case "Tile_SGV_C5_R5": return GetBytes(BinaryResources.Tile_SGV_C5_R5);
                case "Tile_SGV_C5_R6": return GetBytes(BinaryResources.Tile_SGV_C5_R6);
                case "Tile_SGV_C5_R7": return GetBytes(BinaryResources.Tile_SGV_C5_R7);
                case "Tile_SGV_C5_R8": return GetBytes(BinaryResources.Tile_SGV_C5_R8);

                case "Tile_SGV_C6_R1": return GetBytes(BinaryResources.Tile_SGV_C6_R1);
                case "Tile_SGV_C6_R2": return GetBytes(BinaryResources.Tile_SGV_C6_R2);
                case "Tile_SGV_C6_R3": return GetBytes(BinaryResources.Tile_SGV_C6_R3);
                case "Tile_SGV_C6_R4": return GetBytes(BinaryResources.Tile_SGV_C6_R4);
                case "Tile_SGV_C6_R5": return GetBytes(BinaryResources.Tile_SGV_C6_R5);
                case "Tile_SGV_C6_R6": return GetBytes(BinaryResources.Tile_SGV_C6_R6);
                case "Tile_SGV_C6_R7": return GetBytes(BinaryResources.Tile_SGV_C6_R7);
                case "Tile_SGV_C6_R8": return GetBytes(BinaryResources.Tile_SGV_C6_R8);

                case "Tile_SGV_C7_R1": return GetBytes(BinaryResources.Tile_SGV_C7_R1);
                case "Tile_SGV_C7_R2": return GetBytes(BinaryResources.Tile_SGV_C7_R2);
                case "Tile_SGV_C7_R3": return GetBytes(BinaryResources.Tile_SGV_C7_R3);
                case "Tile_SGV_C7_R4": return GetBytes(BinaryResources.Tile_SGV_C7_R4);
                case "Tile_SGV_C7_R5": return GetBytes(BinaryResources.Tile_SGV_C7_R5);
                case "Tile_SGV_C7_R6": return GetBytes(BinaryResources.Tile_SGV_C7_R6);
                case "Tile_SGV_C7_R7": return GetBytes(BinaryResources.Tile_SGV_C7_R7);
                case "Tile_SGV_C7_R8": return GetBytes(BinaryResources.Tile_SGV_C7_R8);

                case "Tile_SGV_C8_R1": return GetBytes(BinaryResources.Tile_SGV_C8_R1);
                case "Tile_SGV_C8_R2": return GetBytes(BinaryResources.Tile_SGV_C8_R2);
                case "Tile_SGV_C8_R3": return GetBytes(BinaryResources.Tile_SGV_C8_R3);
                case "Tile_SGV_C8_R4": return GetBytes(BinaryResources.Tile_SGV_C8_R4);
                case "Tile_SGV_C8_R5": return GetBytes(BinaryResources.Tile_SGV_C8_R5);
                case "Tile_SGV_C8_R6": return GetBytes(BinaryResources.Tile_SGV_C8_R6);
                case "Tile_SGV_C8_R7": return GetBytes(BinaryResources.Tile_SGV_C8_R7);
                case "Tile_SGV_C8_R8": return GetBytes(BinaryResources.Tile_SGV_C8_R8);
            }

            return null ;
        }
    }

given .netmf c# limitations, this is the cleanest solution i found…

I tried


using GHI.OSHW.Hardware;

but it’s missing …

@ thisway.ro - Before adding a “using” statement you need to add the reference for the assembly. In this case assembly name is the same - GHI.OSHW.Hardware.dll.

thanks for pointing that out :slight_smile:

Util.SetSpecialDisplayConfig expects 3 parameters.

I don’t know where to get the first one from, the SPI Config …