Main Site Documentation

OutOfMemory exception on N18 SimpleGraphics.DisplayImage ONLY when debugging


#1

On a FEZ Cerberus with the latest loader and firmware (4.3), I am using the N18 display’s SimpleGraphics.DisplayImage to display a small (72x12) bitmap in the upper corner of the display.

If I just deploy and run the program (without debugging), it works fine, making this DisplayImage call hundreds of times without any problems. But when I run the program through the debugger (no breakpoints, no changes to the code, no conditional code), the very first call to DisplayImage gets an OutOfMemory exception.

The same code worked fine with or without the debugger, but suddenly started throwing this exception on the very first DisplayImage call whenever the debugger is attached. Running the deployed code without the debugger works fine, and runs for hours without a problem.

I’m pretty new to Gadgeteer, so still trying to figure out the nuances. But this one has my scratching my head a bit.

Thanks.


#2

The debugger uses some memory so this is normal. What is in your code? Let’s try to optimize it.


#3

@ Gus -

Thanks for your reply, and it’s good to know that this is expected potential behavior when the debugger is attached. At the moment, the code does very little. It is a small demo I was creating for an upcoming presentation.

It gets the current time (actually, the elapsed time since reset) using DateTime.Now.TimeOfDay.ToString(), draws the string text into a 75x12 bitmap (which is allocated only once at initialization and is reused thereafter), and uses the N18 SimpleGraphics.DisplayImage method to display it. The new time means a new string every second, so there’s some garbage being generated, but as I said, the exception always occurs on the very first DisplayImage call, so there’s only been one string object generated at that point.

I use a GT.Timer to wake up every second (1000ms) and update the display. The timer handler also implements a 10-second countdown. When the countdown hits zero, it causes the display backlight to turn off, causes all updates to the screen to stop (and thus no time strings are created), and causes the LED on a button module to flash (toggle) once a second while the display backlight is off. The button pressed handler just turns the backlight on again, resets the 10-second countdown to 10 again, and enables the once-per-second display update to resume. It’s all driven by the timer handler and the button pressed handler.

There is a Tunes module attached as well, and the code tells it to beep just once at the very beginning, but that isn’t functioning (see my other post on the Tunes module issue), so I removed that module from the diagram and the socket, and removed that code for now.

It’s very minimal code, so the exception surprised me.


#4

Can you share the code so we can try? This is on 4.3 right?


#5

@ Gus -

Yes, I’m using 4.3. The components are FEZ Cerberus, USB Client SP (socket 8), Button (socket 6), and N18 display (socket 5). Here is the code:

using System;
using System.Collections;
using System.Threading;
using Microsoft.SPOT;
using Microsoft.SPOT.Presentation;
using Microsoft.SPOT.Presentation.Controls;
using Microsoft.SPOT.Presentation.Media;
using Microsoft.SPOT.Presentation.Shapes;
using Microsoft.SPOT.Touch;

using Gadgeteer.Networking;
using GT = Gadgeteer;
using GTM = Gadgeteer.Modules;
using Gadgeteer.Modules.GHIElectronics;

namespace GadgeteerApp2
{
    public partial class Program
    {
        void ProgramStarted()
        {
            Debug.Print("Program Started");

            DisplayVisible = false;
            button.TurnLedOff();
            displayN18.Clear();
            textBmp = new Bitmap(75, 12);
            button.Mode = Button.LedMode.Off;
            timer = new GT.Timer(1000);
            timer.Tick += Tick;
            UpdateDisplay();
            DisplayVisible = true;

            /* 
            // Tunes module not functioning for some reason.  
            tunes.AddNote(new Tunes.MusicNote(Tunes.Tone.B4, 50));
            tunes.Play();
            */

            button.ButtonPressed += button_ButtonPressed;
            timer.Start();
        }

        private GT.Timer timer;
        private const int TimeOut = 10;
        private int CountDown = TimeOut;
        private bool displayVisible;
        Bitmap textBmp;
        Color foreground = Color.White;
        Color background = Color.Black;

        private bool DisplayVisible
        {
            get
            {
                return displayVisible;
            }

            set
            {
                displayVisible = value;
                displayN18.BacklightEnabled = displayVisible;

                if (!displayVisible)
                {
                    button.TurnLedOn();
                }
                else
                {
                    button.TurnLedOff();
                }
            }
        }

        private void Tick(object objectState)
        {
            if (DisplayVisible)
            {
                UpdateDisplay();

                if (CountDown <= 0)
                {
                    DisplayVisible = false;
                }

                if (CountDown > 0)
                {
                    --CountDown;
                }
            }
            else
            {
                button.ToggleLED();
            }
        }

        private void button_ButtonPressed(Button sender, Button.ButtonState state)
        {
            if (!DisplayVisible)
            {
                UpdateDisplay();
                DisplayVisible = true;
                CountDown = TimeOut;
            }
        }

        private void UpdateDisplay()
        {
            string text = DateTime.Now.TimeOfDay.ToString();
            textBmp.DrawRectangle(background, 1, 0, 0, 75, 12, 0, 0, background, 0, 0, background, 75, 12, 0xFF);
            textBmp.DrawText(text, Resources.GetFont(Resources.FontResources.small), foreground, 0, 0);
            displayN18.SimpleGraphics.DisplayImage(textBmp, 0, 0);
        }
    }
}

Edit: The line that gets the OutOfMemory exception is the last line in the last method (UpdateDisplay) above. It occurs on the very first call, and only occurs if the debugger is attached. If the debugger is not attached, the program runs fine and the DisplayImage method doesn’t throw.


#6

Avoid SimpleGraphics. Taylorza helped me out in this thread
https://www.ghielectronics.com/community/forum/topic?id=14026&page=1 you want post #7.


#7

@ kgcode - Before your very first call to UpdateDisplay in ProgramStarted, can you add a call to Debug.GC(true) and see if the exception goes away?


#8

@ John -

As you suggested, I added Debug.GC(true) call just after the initial Debug.Print in ProgramStarted, and it still gets the OutOfMemory exception on the first DisplayImage when the debugger is running (and no exception with the debugger is not running).

Update: I also noticed that I’m now getting two messages in the output window that I wasn’t getting before, after the “Program Started” string and before the exception is thrown:

Failed allocation for 3419 blocks, 41028 bytes.

Failed allocation for 3419 blocks, 41028 bytes.

The call stack shows that the exception was thrown at DisplayModule.RecreateDrawingContext at line 153, called by SimpleGraphics.get (line 386), called by my UpdateDisplay method’s call to DisplayImage…

Does this additional info provide any leads?


#9

Debug uses extra resources. So does SimpleGraphics. Avoid using the 2 together and your problem should go away. As it stands OOM is OOM.


#10

@ kgcode - Skewworks is correct in that debugging and SimpleGraphics both use a lot of memory. The 41028 byte allocation you see is for the internal bitmap SimpleGraphics is using. Cerberus doesn’t have much RAM more than that available for use in Gadgeteer, so it is very easy to run out.


#11

@ kgcode - If you do find that using SimpleGraphics takes too much memory or you are unable to use it, you can create smaller NETMF bitmaps, draw to those, and then send them to the screen using the Draw function in the N18 driver.


#12

@ John -

Thanks. I had actually been using Draw before, when I was writing an earlier version under 4.2, and it worked. But when I made the transition to 4.3, I ran into some odd behavior (don’t recall exactly what happened), so I switched to SimpleGraphics. I will try going back to Draw and see if I can get it to work or narrow down what the issue was.


#13

@ John -

I changed that SimpleGraphics.DisplayImage line to use Draw (of the 75x12 bitmap):



and deployed and ran without the debugger, and I got nothing on the display and apparently no firing of the timer.  So, I brought it up in the debugger, and that line of code (the very first time it was hit) is throwing a NotSupportedException.

I think this is what I encountered when I moved the Draw-based code to 4.3, so I had assumed that Draw was no longer implemented in 4.3.  That's what drew me (pun intended) to move to SimpleGraphics.

Is this what you would expect to see, or is something else going on?

Thanks.

#14

@ kgcode - It looks like there is a bug in our code that converts the bitmap to the correct format for the display. It has been fixed, but, unfortunately, it is in the firmware so it will have to wait until the next SDK release.


#15

@ John -

Thanks for looking into this.

Is there some other bitmap format I can use, to work around the bug?


#16

@ kgcode - It will not be very fast at all, but you could use the DrawRaw function. It bypasses the firmware code that fails. It takes a byte array that represents the raw pixel data in 16 bits per pixel, big endian, 565 BGR format. The GetBitmap() function on a bitmap returns the byte array representing the image data that you can manually convert.


#17

@ kgcode - The below code should work for you. You won’t be able to make a bitmap to represent the entire screen, however, as memory is limited.


using Microsoft.SPOT;
using Microsoft.SPOT.Presentation.Media;

namespace N18BitmapConveter
{
    public partial class Program
    {
        void ProgramStarted()
        {
            var bmp = new Bitmap(20, 20);
            bmp.DrawEllipse(Colors.Red, 5, 5, 5, 5);
            bmp.DrawEllipse(Colors.Green, 5, 15, 5, 5);
            bmp.DrawEllipse(Colors.Blue, 15, 5, 5, 5);
            bmp.DrawEllipse(Colors.White, 15, 15, 5, 5);

            this.DrawBitmap(bmp, 0, 0);
        }

        private void DrawBitmap(Bitmap bmp, int x, int y)
        {
            var buffer1 = bmp.GetBitmap();
            var buffer2 = new byte[bmp.Width * bmp.Height * 2];

            for (int i = 0, j = 0; i < buffer1.Length; i += 4, j += 2)
            {
                var temp = ((ushort)(buffer1[i] & 0xF8) << 8) | ((ushort)(buffer1[i + 1] & 0xFC) << 3) | (buffer1[i + 2] >> 3);
                buffer2[j] = (byte)(temp >> 8);
                buffer2[j + 1] = (byte)(temp);
            }

            this.displayN18.DrawRaw(buffer2, x, y, bmp.Width, bmp.Height);
        }
    }
}


#18

@ John -

Thanks.