メモリ使用量を確認するプログラム VC++ / Ruby

#include <iostream>
#include <windows.h>

int main()
{
    std::locale::global(std::locale("japanese"));

    MEMORYSTATUSEX statex;
    statex.dwLength = sizeof(statex);
    if (GlobalMemoryStatusEx(&statex) == 0) //関数が失敗した時
    {
        return 0;
    }

    wprintf(L"物理メモリ(ullTotalPhys)    %15llu bytes\n", statex.ullTotalPhys);
    wprintf(L"物理メモリ(ullAvailPhys)    %15llu bytes\n", statex.ullAvailPhys);
    wprintf(L"仮想メモリ(ullTotalVirtual) %15llu bytes\n", statex.ullTotalVirtual);
    wprintf(L"仮想メモリ(ullAvailVirtual) %15llu bytes\n", statex.ullAvailVirtual);
}
require "fiddle/import"
require 'fiddle/types'

module WIN32API
  extend Fiddle::Importer
  dlload 'C:\\Windows\\System32\\kernel32.dll'
  include Fiddle::Win32Types

  extern 'BOOL GlobalMemoryStatusEx(void *)'

  MEMORYSTATUSEX = struct([
    "unsigned int  dwLength",
    "unsigned int  dwMemoryLoad",
    "unsigned long long ullTotalPhys",
    "unsigned long long ullAvailPhys",
    "unsigned long long ullTotalPageFile",
    "unsigned long long ullAvailPageFile",
    "unsigned long long ullTotalVirtual",
    "unsigned long long ullAvailVirtual",
    "unsigned long long ullAvailExtendedVirtual",
  ])
end

mem = WIN32API::MEMORYSTATUSEX.malloc
mem.dwLength = WIN32API::MEMORYSTATUSEX.size

WIN32API.GlobalMemoryStatusEx(mem.to_ptr)
puts "物理メモリ(ullTotalPhys)     #{"%15d" % mem.ullTotalPhys} byte"
puts "物理メモリ(ullAvailPhys)     #{"%15d" % mem.ullAvailPhys} byte"
puts "仮想メモリ(ullTotalVirtual)  #{"%15d" % mem.ullTotalVirtual} byte"
puts "仮想メモリ(ullAvailVirtual)  #{"%15d" % mem.ullAvailVirtual} byte"
puts "仮想メモリ(ullTotalPageFile) #{"%15d" % mem.ullTotalPageFile} byte"
puts "仮想メモリ(ullAvailPageFile) #{"%15d" % mem.ullAvailPageFile} byte"

メモリ使用量を確認する VC++ プログラム

#include <iostream>
#include <windows.h>

int main()
{
    std::locale::global(std::locale("japanese"));

    MEMORYSTATUSEX statex;
    statex.dwLength = sizeof(statex);
    if (GlobalMemoryStatusEx(&statex) == 0) //関数が失敗した時
    {
        return 0;
    }

    wprintf(L"物理メモリ(ullTotalPhys)    %15llu bytes\n", statex.ullTotalPhys);
    wprintf(L"物理メモリ(ullAvailPhys)    %15llu bytes\n", statex.ullAvailPhys);
    wprintf(L"仮想メモリ(ullTotalVirtual) %15llu bytes\n", statex.ullTotalVirtual);
    wprintf(L"仮想メモリ(ullAvailVirtual) %15llu bytes\n", statex.ullAvailVirtual);
}

ESP32-WROOM-32 で ILI9341 に SDから読みだした PNG画像を連続描画【マルチコア使用】

戦略

描画とSDからの読み出しを別々のコアでやることで並列に動作させてできるだけアイドルを減らす。

画像の前半を読んだらセマフォを開放。前半の描画を行っている間に次の後半の描画部分を読みだす。 描画のほうが早いので読み出しの速度分のFPSがでるはず。

コード

#include "FS.h"
#include "SD.h"
#include "SPI.h"
#include "TFT_eSPI.h"

TFT_eSPI tft = TFT_eSPI();       // Invoke custom library
SPIClass sd_HSPI(HSPI);
#define HSPI_SCK  14
#define HSPI_MISO 35
#define HSPI_MOSI 13
#define HSPI_SS   15
#define SDSPEED   16000000
boolean sdHasError;

SemaphoreHandle_t xSemaphore1;
SemaphoreHandle_t xSemaphore2;
SemaphoreHandle_t xSemaphore3;

static uint8_t data1[120 * 320];
static uint8_t data2[120 * 320];

void testOriginalData() {
  xSemaphoreTake(xSemaphore1, portMAX_DELAY);
  tft.pushImage(0,   0, 320, 120, data1);
  xSemaphoreGive(xSemaphore1);
  
  xSemaphoreTake(xSemaphore2, portMAX_DELAY);
  tft.pushImage(0, 120, 320, 120, data2);
  xSemaphoreGive(xSemaphore2);
}

bool readFile(fs::FS &fs, const char * path){
  if(sdHasError) {
    return false;
  }

  File file = fs.open(path);
  if(!file){
      Serial.println("Failed to open file for reading");
      return false;
  }

  xSemaphoreTake(xSemaphore1, portMAX_DELAY);
  xSemaphoreGive(xSemaphore3);
  file.read(data1, 120 * 320);
  xSemaphoreGive(xSemaphore1);
  
  xSemaphoreTake(xSemaphore2, portMAX_DELAY);
  file.read(data2, 120 * 320);
  xSemaphoreTake(xSemaphore3, portMAX_DELAY);
  xSemaphoreGive(xSemaphore2);

  file.close();
  return true;
}

void task0(void* arg) {
  char buf[255];
  while(1) {
    for(int i = 1; i <= 100; i++) {
      sprintf(buf, "/output/%04d.dat", i);
      readFile(SD, buf);
      delay(1);
    }
  }
}

void setup()
{
// Setup the LCD
  tft.init();
  tft.setRotation(1);

  sd_HSPI.begin(HSPI_SCK, HSPI_MISO, HSPI_MOSI, HSPI_SS);

  Serial.begin(115200);
  delay(100);

  if(!SD.begin(HSPI_SS, sd_HSPI, 12000000L)){
      Serial.println("Card Mount Failed");
      sdHasError = true;
  } else {
    Serial.println("Card Mount Succeeded");
  }

  xSemaphore1 = xSemaphoreCreateMutex();
  xSemaphore2 = xSemaphoreCreateMutex();
  xSemaphore3 = xSemaphoreCreateBinary();

  xTaskCreatePinnedToCore(task0, "Task0", 4096, NULL, 1, NULL, 0);
  tft.fillScreen(TFT_BLACK);
}

void loop()
{
  xSemaphoreTake(xSemaphore3, portMAX_DELAY);
  xSemaphoreGive(xSemaphore3);

  if (sdHasError) {
    if(!SD.begin(HSPI_SS, sd_HSPI, SDSPEED)){
        Serial.println("Card Mount Failed");
        delay(1000);
        return;
    } else {
      Serial.println("Card Mount Succeeded");
      sdHasError = false;
    }
  }
  
  //randomSeed(1234); // This ensure test is repeatable with exact same draws each loop
  char buf[255];
  int x, x2;
  int y, y2;
  int r;
  
// Clear the screen and draw the frame
  static unsigned long old_time;
  old_time = millis();
  testOriginalData();
  Serial.print((float)1/(millis()-old_time)*1000);
  Serial.println(" fps");
}

結果

6.80 ~ 7.50 fps になった。

ESP32-WROOM-32 で ILI9341 に SDから読みだした PNG画像を連続描画【TFT eSPI使用】

TFT eSPI を使ってみた。

コード

#include "FS.h"
#include "SD.h"
#include "SPI.h"
#include "TFT_eSPI.h"

TFT_eSPI tft = TFT_eSPI();       // Invoke custom library
SPIClass sd_HSPI(HSPI);
#define HSPI_SCK  14
#define HSPI_MISO 35
#define HSPI_MOSI 13
#define HSPI_SS   15

static uint8_t data1[120 * 320];
static uint8_t data2[120 * 320];

void testOriginalData() {
  tft.pushImage(0,   0, 320, 120, data1);
  tft.pushImage(0, 120, 320, 120, data2);
}

bool readFile(fs::FS &fs, const char * path){
    File file = fs.open(path);
    if(!file){
        Serial.println("Failed to open file for reading");
        return false;
    }

    file.read(data1, 120 * 320);
    file.read(data2, 120 * 320);
    file.close();
    return true;
}

void setup()
{
// Setup the LCD
  tft.init();
  tft.setRotation(1);

  sd_HSPI.begin(HSPI_SCK, HSPI_MISO, HSPI_MOSI, HSPI_SS);

  Serial.begin(115200);
  delay(100);

  if(!SD.begin(HSPI_SS, sd_HSPI, 12000000L)){
      Serial.println("Card Mount Failed");
      return;
  }
}

void loop()
{
  //randomSeed(1234); // This ensure test is repeatable with exact same draws each loop
  char buf[255];
  int x, x2;
  int y, y2;
  int r;
  
// Clear the screen and draw the frame
  tft.fillScreen(TFT_BLACK);
  unsigned long old_time;
  for(int i = 1; i <= 100; i++) {
    sprintf(buf, "/output/%04d.dat", i);
    old_time = millis();
    Serial.println(buf);
    if(readFile(SD, buf)) {
      // put your main code here, to run repeatedly:
      testOriginalData();
    }
    Serial.print((float)1/(millis()-old_time)*1000);
    Serial.println(" fps");
  }
}

結果

約 5.80 fps になった。

SDの読み込みが遅いのかもしれない。

参考

www.youtube.com

ESP32-WROOM-32 で ILI9341 に SDから読みだした PNG画像を連続描画【遅い】

回路

f:id:bamch0h:20210107223349j:plain

ソース

#include "FS.h"
#include "SD.h"
#include "SPI.h"
#include "Adafruit_GFX.h"
#include "Adafruit_ILI9341.h"

// For the Adafruit shield, these are the default.
#define TFT_RST 16
#define TFT_DC 17
#define TFT_CS 5

// Use hardware SPI (on Uno, #13, #12, #11) and the above for CS/DC
Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC, TFT_RST);

static uint8_t data1[120 * 320];
static uint8_t data2[120 * 320];

void testOriginalData() {
  byte c;
  uint16_t g;

  tft.startWrite();
  tft.setAddrWindow(0, 0, 320, 240);
  for(int y = 0;y < 120; y++) {
    for(int x = 0;x < 320; x++) {
      c = data1[y * 320 + x];
      g = (c>>3)<<11 | (c>>2)<<5 | c>>3;
      tft.SPI_WRITE16(g);
    }
  }
  for(int y = 0;y < 120; y++) {
    for(int x = 0;x < 320; x++) {
      c = data2[y * 320 + x];
      g = (c>>3)<<11 | (c>>2)<<5 | c>>3;
      tft.SPI_WRITE16(g);
    }
  }
  tft.endWrite();  
}

bool readFile(fs::FS &fs, const char * path){
    File file = fs.open(path);
    if(!file){
        Serial.println("Failed to open file for reading");
        return false;
    }

    file.read(data1, 120 * 320);
    file.read(data2, 120 * 320);
    file.close();
    return true;
}

SPIClass SPI2(HSPI);
#define HSPI_SCK  14
#define HSPI_MISO 35
#define HSPI_MOSI 13
#define HSPI_SS   15

void setup() {
  tft.begin();
  tft.setRotation(1);
  
  SPI2.begin(HSPI_SCK, HSPI_MISO, HSPI_MOSI, HSPI_SS);

  Serial.begin(115200);
  delay(100);

  if(!SD.begin(HSPI_SS, SPI2, 12000000L)){
      Serial.println("Card Mount Failed");
      return;
  }
}

void loop() {
  tft.fillScreen(ILI9341_BLACK);
  char buf[255];
  unsigned long old_time;
  for(int i = 1; i <= 100; i++) {
    sprintf(buf, "/output/%04d.dat", i);
    old_time = millis();
    Serial.println(buf);
    if(readFile(SD, buf)) {
      // put your main code here, to run repeatedly:
      testOriginalData();
    }
    Serial.print((float)1/(millis()-old_time)*1000);
    Serial.println(" fps");
  }
}

課題

4.5 fps しか出てないのでどうにかしたい。

C言語ファイルをNMAKEする為の最小Makefile

タイトルはちょっと盛った。

C言語Hello WorldのファイルをVisual Studioコンパイラでmakeする

#include "stdio.h"

void main(void) {
    printf("Hello World\n");
}

参考にしたサイト

makefile と nmake ~ makefile を読み解く - C/C++ による Windows プログラミング入門講座 - C/C++ 入門

TARGETNAME=test_make
OUTDIR=obj
CC=cl.exe
LINK=link.exe

ALL: $(OUTDIR)\$(TARGETNAME).exe

$(OUTDIR) :
  @if not exist $(OUTDIR) mkdir $(OUTDIR)

CPPFLAGS=\
  /nologo\
  /W3\
  /Fo"$(OUTDIR)\\"\
  /Fd"$(OUTDIR)\\"\
  /c\
  /Zi\
  /D_WIN32_WINNT=0x0600\
  /DUNICODE\
  /D_UNICODE

LINK32_FLAGS=\
  /nologo\
  /subsystem:console\
  /pdb:"$(OUTDIR)\$(TARGETNAME).pdb"\
  /out:"$(OUTDIR)\$(TARGETNAME).exe"\
  /DEBUG

LINK32_OBJS=\
  $(OUTDIR)\$(TARGETNAME).obj

$(OUTDIR)\$(TARGETNAME).exe : $(OUTDIR) $(LINK32_OBJS)
  (LINK) $(LINK32_FLAGS) $(LINK32_OBJS)

.c{$(OUTDIR)}.obj:
  $(CC) $(CPPFLAGS) $<

clean:
  -@erase /Q $(OUTDIR)\*

上記のファイルで obj フォルダ下に test_make.exe というファイルが作成される。

nmake を実行する場合は以下のバッチをたたく

@echo off

call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Auxiliary\Build\vcvars64.bat"

10進数、16進数、8進数をコンボボックスで切り替えつつ入力チェックもしてくれる実装サンプル

あらすじ

こんな感じのものを実装しました。

f:id:bamch0h:20200720021840g:plain

XAML

<Window x:Class="WpfApp15.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApp15"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Window.Resources>
        <ControlTemplate x:Key="ValidationTemplate">
            <StackPanel>
                <TextBlock Foreground="Red" Text="{Binding AdornedElement.(Validation.Errors)[0].ErrorContent, ElementName=adornedelem}" />
                <AdornedElementPlaceholder x:Name="adornedelem" />
            </StackPanel>
        </ControlTemplate>
        <local:HexConverter x:Key="ByteHexConverter" />
    </Window.Resources>
    <StackPanel>
        <DataGrid ItemsSource="{Binding Items, Mode=OneWay}" AutoGenerateColumns="False">
            <DataGrid.Columns>
                <DataGridTextColumn Header="Index" Binding="{Binding SelectedTypesIndex}" />
                <DataGridTemplateColumn IsReadOnly="True" Header="Types">
                    <DataGridTemplateColumn.CellTemplate>
                        <DataTemplate>
                            <ComboBox ItemsSource="{Binding Types}" SelectedIndex="{Binding SelectedTypesIndex, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
                        </DataTemplate>
                    </DataGridTemplateColumn.CellTemplate>
                </DataGridTemplateColumn>
                <DataGridTemplateColumn Header="Number">
                    <DataGridTemplateColumn.CellTemplate>
                        <DataTemplate>
                            <StackPanel>
                                <TextBlock Margin="20" Width="100">
                                    <TextBlock.Text>
                                        <Binding Path="Number" Converter="{StaticResource ByteHexConverter}" />
                                    </TextBlock.Text>
                                </TextBlock>
                            </StackPanel>
                        </DataTemplate>
                    </DataGridTemplateColumn.CellTemplate>
                    <DataGridTemplateColumn.CellEditingTemplate>
                        <DataTemplate>
                            <TextBox Margin="20" Width="100" Validation.ErrorTemplate="{StaticResource ValidationTemplate}">
                                <TextBox.Text>
                                    <Binding Path="Number" Converter="{StaticResource ByteHexConverter}" />
                                </TextBox.Text>
                            </TextBox>
                        </DataTemplate>
                    </DataGridTemplateColumn.CellEditingTemplate>
                </DataGridTemplateColumn>
            </DataGrid.Columns>
        </DataGrid>
    </StackPanel>
</Window>

MainWindow.xaml.cs

using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Data;
using Microsoft.Practices.Prism.Mvvm;
using System.Globalization;

namespace WpfApp15
{
    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            this.DataContext = new MainWindowVM();
        }
    }

    public class MainWindowVM : BindableBase
    {
        public List<DataGridItem> Items { get; set; }

        public MainWindowVM()
        {
            Items = new List<DataGridItem>()
            {
                new DataGridItem(123),
                new DataGridItem(234),
                new DataGridItem(0),
            };
        }
    }

    public class Number<T> : BindableBase
    {
        public Number(T initValue, string initFormat = "Decimal")
        {
            Value = initValue;
            Format = initFormat;
        }

        private T _value;
        public T Value
        {
            get
            {
                return _value;
            }

            set
            {
                this.SetProperty(ref this._value, value);
            }
        }

        private string format;
        public string Format
        {
            get
            {
                return format;
            }

            set
            {
                this.SetProperty(ref this.format, value);
            }
        }
    }

    public class DataGridItem : BindableBase
    {
        private Number<byte> number;
        public Number<byte> Number
        {
            get
            {
                return number;
            }

            set
            {
                this.SetProperty(ref this.number, value);
            }
        }

        public List<string> Types { get; set; }

        public int selectedTypesIndex;
        public int SelectedTypesIndex
        {
            get
            {
                return selectedTypesIndex;
            }

            set
            {
                this.SetProperty(ref this.selectedTypesIndex, value);
                if (this.Number != null)
                    Number = new Number<byte>(number.Value, Types[selectedTypesIndex]);
            }
        }

        public DataGridItem(byte initValue)
        {
            this.Types = new List<string>() { "Decimal", "Hexadecimal", "Octal" };

            this.Number = new Number<byte>(initValue, Types[SelectedTypesIndex]);

            this.SelectedTypesIndex = 0;
        }
    }

    public class HexConverter : IValueConverter
    {
        private Number<byte> byteNumber;
            
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            byteNumber = (Number<byte>)value;

            switch (byteNumber.Format)
            {
                case "Decimal":
                    return System.Convert.ToString(byteNumber.Value, 10);
                case "Hexadecimal":
                    return byteNumber.Value.ToString("X2");
                case "Octal":
                    return System.Convert.ToString(byteNumber.Value, 8);
                default:
                    return byteNumber.ToString();
            }
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            string strValue = value as string;
            byte resultValue;

            try
            {
                switch (byteNumber.Format)
                {
                    case "Hexadecimal":
                        resultValue = System.Convert.ToByte(strValue, 16);
                        return new Number<byte>(resultValue, byteNumber.Format);
                    case "Octal":
                        resultValue = System.Convert.ToByte(strValue, 8);
                        return new Number<byte>(resultValue, byteNumber.Format);
                    default:
                        resultValue = System.Convert.ToByte(strValue);
                        return new Number<byte>(resultValue, byteNumber.Format);
                }
            }
            catch
            {
                return DependencyProperty.UnsetValue;
            }
        }
    }
}

まとめ

VM側でConvertとValidationをするようにした場合、フォーカスが外れた後にValidationが走るのでエラーが見えないという欠点があってView側でやりたいという気持ちがあった。

View側でConvertを行うとformat単位でCoverterを切り替えたりできないので、Convert()メソッドでNumberクラスを受け取ってクラス内に定義してあるFormatを使って場合分けしている。CovertBackの場合は引数からNumberクラスが受け取れないので、Convert()メソッド時点のNumberクラスを保存しておいて、ConvertBackメソッドで使いまわしている。VM側で変更される場合は注意が必要かもしれないけど、編集中にVM上で変更されることはないはずなので問題ないはず。

ただ、今回はNumberというジェネリクスクラスを作成したけど、そこまでする必要はなかったかも。CoverterBackでフォーマット情報が取れないので、その保存方法としてNumberというクラスを作ったけど、formatをIMultiValueConverterで渡してあげて保存しておくだけでも今回の件は実現できたかもしれない。