メモリ使用量を確認するプログラム 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"
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の読み込みが遅いのかもしれない。
参考
ESP32-WROOM-32 で ILI9341 に SDから読みだした PNG画像を連続描画【遅い】
回路
ソース
#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進数をコンボボックスで切り替えつつ入力チェックもしてくれる実装サンプル
あらすじ
こんな感じのものを実装しました。
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で渡してあげて保存しておくだけでも今回の件は実現できたかもしれない。
UDPでNAT超え
あらすじ
NAT超えという技術を知り、いったいどういうものなのか?を調べて、Goで簡単に実装してみました。
NAT
NATというのはグローバルIPとローカルIPを変換する機構です。グローバルIPの枯渇を防ぐため、ローカルIP以下のデバイスに対して同じグローバルIPを割り振るための仕組み提供します。最近はIPだけではなくポートも変換するNAPTというタイプが主流のようです。
例えば、2台のパソコンがNAT下にぶら下がっていてそれぞれがサーバーにアクセスするような場合は以下のような構成になります。
PC1がWEBサーバーにアクセスする時、アクセス要求はNATを経由してインターネットを通じてサーバーに到達します。サーバーは要求を処理し応答を返します。応答はインターネットを経由してNATまで届きます。NATはその応答をPC1に送り、無事に通信ができます。
NATがどのように応答を振り分けるというと、NAT内部にグローバルIPとローカルIPの変換表を持っていてそれをもとに応答を振り分けています。TCP/IPやUDP/IPの場合、変換表には以下の情報が記録されます。
PC1がWEBサーバーにアクセスした場合は以下のような情報が変換表に記録されます。
プロトコル | 送信元IP | 送信元ポート | グローバルIP | グローバルポート | 相手先IP | 相手先ポート |
---|---|---|---|---|---|---|
TCP | 192.168.0.2 | 49152 | 1.2.3.4 | 49152 | 2.3.4.5 | 80 |
サーバー側からの応答は送信先IPと相手先IPが変換表とは逆になって返信されます。NATはそれを考慮して変換表に当てはまるものがあるかを調べ、当てはまるものがあったなら、そのデバイスへ応答を渡します。なかった場合はその時点で破棄されます。
送信元ポート
と グローバルポート
は基本的に同じになります。別々のローカルIPで同じポート番号から通信が来た場合にのみ変換されます。例えば、PC1とPC2が同じ送信元ポートで通信をした場合、ポートを変換せずに変換表に記録した場合、以下のようになります。
プロトコル | 送信元IP | 送信元ポート | グローバルIP | グローバルポート | 相手先IP | 相手先ポート |
---|---|---|---|---|---|---|
TCP | 192.168.0.2 | 49152 | 1.2.3.4 | 49152 | 2.3.4.5 | 80 |
TCP | 192.168.0.3 | 49152 | 1.2.3.4 | 49152 | 2.3.4.5 | 80 |
サーバーからの応答はどちらの要求に対しても 1.2.3.4:49152
に返信されるので、NATは変換表をチェックしたときに1番目にも2番目にも対象のIPがヒットするので応答を振り分けることができません。そこで、以下のようにグローバル側に見せるポートを変換することで、この問題を解決します。
プロトコル | 送信元IP | 送信元ポート | グローバルIP | グローバルポート | 相手先IP | 相手先ポート |
---|---|---|---|---|---|---|
TCP | 192.168.0.2 | 49152 | 1.2.3.4 | 49152 | 2.3.4.5 | 80 |
TCP | 192.168.0.3 | 49152 | 1.2.3.4 | 49153 | 2.3.4.5 | 80 |
こうすることで、PC1の応答は 1.2.3.4:49152
に、PC2の応答は 1.2.3.4:49153
に返されるため、NATは問題なく応答を振り分けることができるというわけです。
ポートマッピング
NATは基本的にグローバル側からアクセスは受け付けません。というのも、グローバル側から唐突に通信が来ても変換表から対応するローカルIPを割り出せないため、すべて破棄されるためです。これだと、ローカルにWEBサーバーを立てても外部に公開できなくて不便ですよね。なので、NATにはポートマッピングという機能があるものがあります。これは、特定のポートにやってきた通信を対応するIPのデバイスに転送する機能です。例えば、以下のようなマッピングテーブルがあったとします。
プロトコル | ポート | 転送IP |
---|---|---|
TCP | 80 | 192.168.0.2 |
この場合、NATにやってきた要求のうち TCPポート80番に来た要求は全て 192.168.0.2 に転送されます。192.168.0.2 にWEBサーバーが起動していれば要求は処理され応答が返ります。
P2P
P2Pとは、サーバーを持たず、クライアント同士が通信する方法のことを指します。サーバー運営コストが少ない、サーバーの処理性能に左右されない等のメリットがあります。ただ、そのままではクライアント間で通信はできないため、古き良きインターネッツ時代にあったP2P型ファイル共有ソフトではポート開放(ポートマッピング)を行うことで各ノード間の通信を可能にしていました。一方で、ポート開放はセキュリティリスクが高く、悪意のあるユーザーがそのポートに対してアクセスして悪意のある操作を可能にする可能性があるため、ユーザーとしてはあまりやりたいことではないと思います。そこで出てくるのがNAT超えです。
NAT超え
NAT超えとは、ポート開放せずにP2Pを可能にする技術のことです。クライアントだけではNAT越えは難しいので、STUNサーバーやTURNサーバーを使ってNAT超えを行うのがセオリーのようです。STUNサーバーやTURNサーバーのRFCがあるようです。私はまだ見れていません。。。
UDPでのNAT超え
UDPはコネクションレスなので内側からデータを送信するだけでNATはポートを開放します。相手側にはデータは届きませんが応答を待っている状態ですので、相手側からも同じようにデータを送信することで通信が可能となります。ただ、内から外への通信によってポートが自動で開放されるとしても、相手のIPアドレスとポートを知る方法が必要です。
IP取得用サーバー
相手のIPアドレスとポートを知るためにサーバーを一つ用意します。そのサーバーに自分のIPアドレスとポートを登録しておき、相手から自由に取得できるようにしておきます。いかに例を示します。
- ① サーバーにPC1のIPを登録
- ② サーバーにPC2のIPを登録
- ⓷ PC2のIPを取得
- ⓸ NATのポート開放のためにPC2に通信する
- ⑤ PC2に通信したことをサーバーに通知
- ⑥ サーバーがPC2にPC1が通信したことを通知(合わせてPC1のIPとポートを伝える)
- ⓻ PC2からPC1に伝えられたポートで通信
このような感じでPC1とPC2の疎通が可能となります。
Goのサンプル
上記を実装したのが以下になります。
クライアント
サーバー
まとめ
- UDPでNAT越えをするための理屈を説明しました。
- Goでのサンプルを実装しました。