ぷるぷるの雑記

低レイヤーがんばるぞいなブログ. 記事のご利用は自己責任で.

<algorithm>でポインタを使うときは型に注意

c++の<algorithm>中にはイテレータを引数に渡すことでコンテナの全要素に対して探索をする関数があります. 探索する対象が配列の場合はポインタを引数に渡すことが可能です.

例えばstd::any_of()は次のように使用することができます.

#include<iostream>
#include<algorithm>
#include<vector>

using namespace std;

int main()
{
    vector<int> vec_int    = {0x00ffffff};
    int         arr_int[1] = {0x00ffffff};
    cout << boolalpha;

    bool retvec = any_of( v.begin(),   v.end(), [](int x){return x<0x100;});
    bool retarr = any_of(   arr_int, arr_int+1, [](int x){return x<0x100;});

    cout << retvec << endl; /* false */
    cout << retarr << endl; /* false */

    return 0;    
}

このプログラムは配列やコンテナに0x100未満の要素があればtrue、そうでなければfalseを表示するプログラムです. コンテナとポインタでどちらもfalseが表示され、正しく評価されていることがわかります.

しかし、ポインタを引数に使用する場合 引数に渡したポインタの型によって評価する型が変動します. 例えば次のようにポインタをchar型へのポインタへキャストしてプログラムを実行すると、0x00ffffffという数値ではなく、(リトルエンディアンの場合)0xff, 0xff, 0xff, 0x00 という数値が評価され、trueが表示されます.

#include<iostream>
#include<algorithm>
#include<vector>

using namespace std;

int main()
{
    vector<int> vec_int    = {0x00ffffff};
    int         arr_int[1] = {0x00ffffff};
    cout << boolalpha;

    bool retvec = any_of( v.begin(),   v.end(), [](int x){return x<0x100;});
    bool retarr = any_of(   (char*)arr_int, (char*)(arr_int+1), [](int x){return x<0x100;});

    cout << retvec << endl; /* false */
    cout << retarr << endl; /* true */

    return 0;    
}

ラムダ式の引数の型ではなく、ポインタの型に依存するので注意しましょう.

参考

cpprefjp.github.io

不揮発メモリICの型番まとめ

不揮発性メモリICの型番とその種類についてざっくりまとめました.

自分用のメモなので間違ってたり一般性がないものが含まれてるでしょうが、あしからず.

24xx

I2C対応不揮発性メモリ.

調べた限りではEEPROMしかなさそう. 表面実装が多いがDIPも普通に手に入る.

名前 メーカー 説明
M24Cxx STマイクロ EEPROM
AT24Cxx Microchip Technology(Atmelを買収) EEPROM
S-24Cxx エイブリック(元エスアイアイ・セミコンダクタ) EEPROM

25xx

SPI対応不揮発性メモリ.

NORフラッシュとNANDフラッシュが殆どだが、EEPROMもある.

名前 メーカー 説明
W25Qxx Winbond NOR型フラッシュ
W25Nxx Winbond NAND型フラッシュ
BR25xx ROHM EEPROM

27xx

EPROM. 紫外線で消去するやつ.

シリアルI/Fを持ったものはなく、パラレルI/Fしかなさそう.

名前 メーカー 説明
AM27Cxx AMD パラレル
TMS27Cxx 東芝 パラレル
M27Cxx STマイクロ パラレル
AT27Cxx Microchip Technology(Atmelを買収) パラレル

MT28xx、MT29xx

MicronはパラレルNOR型フラッシュをMT28xx、パラレルNAND型フラッシュをMT29xxと区別して製品展開をしている模様.

名前 メーカー 説明
MT28xx Micron Technology パラレルNOR型フラッシュ
MT29xx Micron Technology ONFI(Open NAND Flash Interface)NAND型フラッシュ

29xx

パラレルI/Fフラッシュ. Micron以外のメーカーはNOR型、NAND型問わずパラレルI/Fのフラッシュメモリを29xxとして製品展開していることが多い. シリアル(SPI)の方が転送速度が速いためか、ラインナップは限定的に思える.

名前 メーカー 説明
M29xx STマイクロ パラレルNOR型フラッシュ
W29xx Winbond パラレルNAND型フラッシュ
S29xx Infineon(Cypress) ONFI(Open NAND Flash Interface)NAND型フラッシュ

C++でbind()が使えない

以下のコードはVisual StudioでWinsock2を使おうとしたときのものです. Microsoftのサンプルコードをコピペしただけなのでビルドできると思いきやコンパイルエラーになります.

#include <iostream>
#include<WinSock2.h>
#include<ws2tcpip.h>
#include<functional>

/* これがないとリンク時にエラーになる */
#pragma comment( lib, "ws2_32.lib" )

using namespace std;

int main()
{
    WSADATA wsaData;
    struct addrinfo *result;

    struct addrinfo hints;
    hints.ai_family   = AF_INET;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_protocol = IPPROTO_TCP;
    hints.ai_flags    = AI_PASSIVE;

    // Resolve the local address and port to be used by the server
    int iResult = getaddrinfo(NULL, "12345", &hints, &result);
    if (iResult != 0) {
        printf("getaddrinfo failed: %d\n", iResult);
        WSACleanup();
        return 1;
    }

    SOCKET ListenSocket;

    ListenSocket = socket(result->ai_family, result->ai_socktype, result->ai_protocol);
    
    if (ListenSocket == INVALID_SOCKET) {
        printf("Error at socket(): %ld\n", WSAGetLastError());
        freeaddrinfo(result);
        WSACleanup();
        return 1;
    }

    iResult = bind(ListenSocket, result->ai_addr, (int)result->ai_addrlen);

        ....

}

エラー内容は以下になります.

「'=': 'std::Binder<std::Unforced,SOCKET &,sockaddr *&,int>' から 'int' に変換できません。」

エラーの原因

このエラーは WinAPIのbind()と、std::bind()が衝突しているのが原因です . つまり、functionalがインクルードされ、using namespace stdしているのが原因です. 解決策としては以下の2つが考えられます.

グローバルスコープを指定してbind()を利用する

Winsock2のbind()はグローバルスコープに定義されているので、スコープ解決演算子::を利用して明示的にグローバルスコープのbind()を呼び出せばコンパイルエラーは出なくなります.

/* グローバルスコープのbind()を明示的にコール */
iResult = ::bind(ListenSocket, result->ai_addr, (int)result->ai_addrlen);

using namespace stdをしない

std名前空間を使わなければbind()はグローバルスコープのbind()と認識されます. functionalの方のbindが使いたくなったらstd::bind()と呼び出せばよいです.

まとめ

WinAPIやWinsockなどの関数はグローバルスコープで定義されている. 名前が衝突したときは明示的にグローバルスコープを指定するか名前空間のusing namespaceを避ければよい.

参考

learn.microsoft.com

DLLで共有したデータの寿命

Windowsの動的ライブラリ(DLL)中のグローバル変数を複数のプロセスで利用する方法とそのグローバル変数の寿命についてまとめました.

DLLでデータを共有する方法

DLLのデータセグメントに置かれた変数は複数のプロセスで共有可能です. なので data_seg pragma を使用して明示的にデータセグメントに変数を配置することでプロセスをまたいだ変数の参照をすることが出来ます.

ただし初期値を設定しないとBSS領域に置かれてしまうので、必ず初期化もするようにしましょう.

また、セクション名は任意につけることが出来ます.

DLLで共有したデータの寿命

検証用に以下のDLLとexeを作りました.

exeはwhileループで終了しないようにしています.

// 検証用DLL

#include<stdio.h>

#pragma comment( linker, "/SECTION:.shared,RWS" )
#pragma data_seg( ".shared" )    /* セクション名は任意でok */
int g_var = 0;                   /* 初期化しないとBSS領域に置かれてしまうので注意 */
#pragma data_seg()

BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
                     )
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}

extern "C" __declspec(dllexport) void print() {
    printf("g_var:%d", ++g_var);
}
// 検証用exe

#include <iostream>
#include<Windows.h>

int main()
{
    HINSTANCE hApp;
    void(*func)();
    hApp = LoadLibrary(L"dll1.dll");
    func = (void(*)())GetProcAddress(hApp, "print");
    func();
    while (1) {
        Sleep(1000);
    }
}


検証用のexeを起動すると、以下のような結果が得られました(2024/3/6更新)

# exe1つ目起動
g_var:1
# exe2つ目起動(1つ目はwhileループ中)
g_var:2
# exe3つ目起動(1つ目をkill後)
g_var:3
# 検証用exeをすべてkillしたのちに3つ目起動
g_var:1


以上から、DLLで共有したデータの寿命は DLLをロードしたプロセスがすべて終了するまで のようです.

また、 バイナリ一致するDLLもパスが違えば別物扱いされます . つまり変数を共有できるのはパスも含めて同じDLLを利用しているプロセスに限ります.

参考

nazochu.blogspot.com

USBシリアル変換ICを使う

USBシリアル変換ICを使ってマイコンとシリアル通信をした時のメモです.

準備するもの

開発用PC

今回はWindowsを利用します. 環境を整えられるのであればOSは特に問いません.

STM32NUCLEO-F072RB

STM32F072RBT6(LQFP64)を搭載した評価用ボードです. このボード自体にST-LINK/V2-1という回路が組み込まれており、本来はUSBシリアル変換ICを別途用意する必要はありません. ただし、STM32F072RBT6はCH1からCH4のUSARTを持っていて、そのうちST-LINK/V2-1につながっているのはUSART2です. なので今回はUSART1を使って開発用PCと通信してみます.

USBシリアル変換IC

SWITCH SCIENCEから「Pololu USB AVRプログラマ v2.1」を購入しました.

この記事を書いている時点で売り切れてますが、他のUSBシリアル変換ICでも代替可能なはずです.

Pololu USB AVRプログラマ v2.1www.switch-science.com

USBシリアル変換IC用ドライバ

購入したUSBシリアル変換ICに対応したドライバをインストールします.

「Pololu USB AVRプログラマ v2.1」のドライバは以下のサイトからインストールできます. Windowsの場合msi形式で配布されています.

www.pololu.com

その他

シリアル通信用にTera Termと、STM32の開発環境であるSTM32CubeIDEと、STM32CubeProgrammerをインストールしておきます.

STM32CubeIDEでの作業

プロジェクトの作成

STM32CubeIDEにてSTM32 Projectを新規作成します. 作成時にCommercial Part No のところでSTM32F072RBT6を選びます.

プロジェクト名はUsartとしました.

コンフィギュレーション

Usart.iocを開き、Connectivity->USART1を選択します.

ModeをAsynchronousにし、USART1を有効にします. ボーレートやパリティビットの設定ははいじる必要ないと思いますが、念のためボーレートは9600 Bits/sにしておきましょう.

USART1を有効にするとPinout viewのPA9とPA10が緑色になります. また、PA10がUSART1_RXでPA9がUSART1_TXであることが分かります. Usart.iocを編集後は忘れずにコード生成まで行いましょう.

USART1を有効にした時のPinout View

printfの記述

以下の記事と同じ要領でwriteシステムコールを実装しprintfをできる状態にします. Core/Src/main.c を以下のように編集します.

prupru-prune.hatenablog.com

// Core/Src/main.c

~略~
/* Includes ------------------------------------------------------------------*/
#include "main.h"

~略~
int main(){
~略~
  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */
    /* USER CODE BEGIN 3 */
      printf("Hello World\r\n");
      HAL_Delay(1000);
  }
  /* USER CODE END 3 */
~略~
/* USER CODE BEGIN 4 */
int _write(int file, char *ptr, int len)
{
    HAL_UART_Transmit(&huart1,(uint8_t *)ptr, len, 10);
    return len;
}
/* USER CODE END 4 */

コードを編集したらトンカチマークをクリックしてプロジェクトをビルドします.

フラッシュへの書き込み

STM32CubeProgrammerを利用してビルドしてできたelfファイルをフラッシュへ書き込みます. フラッシュへの書き込みはmini usb-Bを通して行います.

実行

配線

フラッシュへの書き込みに使った経路に加え、PC - USBシリアル変換IC - USART1 の経路を接続します. Pinout view からPA9がUSART1_TXであることが分かっているので、まずはNUCLEOのどの穴がPA9なのかを調べます. NUCLEOのユーザーマニュアル(UM)を見るとPA9はCH10の21番ピンであることが分かります. このCH10の21番ピンをPololu USB AVRプログラマのRXピンに接続します. 今回はNUCLEOからPCにprintfするだけなので、最低限このラインさえつながっていれば事足ります.

NUCLEO-F072RBのピン配置. 赤丸がUSART1_TX.
参照:https://www.st.com/ja/evaluation-tools/nucleo-f072rb.html#documentation

NUCLEOとUSBシリアル変換ICの接続

配線の全体像

Tera Termと接続

接続が出来たらTera Termを開きCOMポートの「Pololu USB AVR Programmer v2.1 TTL Serial Port」を選択します. 今回はたまたまCOM6でした.

COMを指定する

また、端末から受信の改行コードをCR+LFにし、シリアルポートからボーレートを9600にします.

受信の改行コードの設定

ボーレートの設定

ここまで来たら準備完了です. NUCLEOの黒いリセットボタンを押して次のようにHello Worldが表示されたら成功です.

Hello World

C/C++でコマンドライン引数の文字数が取得できない

結論から言うとsizeof()じゃなくてstrlen()を使いましょうという話です.

x86(32bit)環境で以下のようなプログラムをビルドして実行すると、いかなるコマンドライン引数であっても「size:4」と表示されます.

#include<stdio.h>

int main(int argc, char** argv){
    if(argc==1){
         return 1;
    }
    printf("size:%d\n",sizeof(argv[1]));
    return 0;
}



これは コマンドライン引数は配列ではなくchar型へのポインタのポインタ として渡されるので sizeof(argv[1])はポインタのサイズを返す ためです. 文字列の長さを取得したい場合はstrlen()を使用しましょう. strlen()はナル文字を除いた文字数を返してくれます. したがってコマンドライン引数の文字列を取得したければ以下のようなコードにしましょう.

#include<stdio.h>
#include<string.h>

int main(int argc, char** argv){
    if(argc==1){
        return 1;
    }
    printf("size:%d\n", strlen(argv[1]));
    return 0;
}



strlen()を使用するときの注意点として、(検索対象の文字列内にナル文字がないと)ナル文字を見つけるまで永遠に検索してしまうことがあります. 以下のようなコードを実行すると文字列の外まで検索が行われていることが分かります. まあこれはstrlen()に限らずC/C++の文字列全般に言えることですが.

#include<stdio.h>
#include<string.h>

int main(int argc, char** argv){
    char buff1[]={'A','B','A','B','A','B','A','B'};    // 8文字
    char buff2[]={'A','B','A','B','A','B','A','B'};    // 8文字
    printf("strlen of buff1:%d\n",strlen(buff));      // strlen of buff1:12
    printf("strlen of buff2:%d\n",strlen(buff2));     // strlen of buff2:20
    return 0;
}

参考

programming-place.net

javascriptで足し算ができない

HTMLの<area>タグのcoordsに設定された属性値にjavascriptからオフセットを与えようとしたときに引き算はうまくいくのに足し算はうまくいかないという事態に遭遇しました. 以下のコードを実行して加減算をした時の各座標の値を表示してみると原因が分かります.

<map name="example">
    <area shape="rect" coords="50,50,150,150">
</map>
<script>
    const area     = document.querySelector('area')
    let [x1, y1, x2, y2] = area.coords.split(',')
    x1 -= 50
    y1 += 50
    x2 += 50
    y2 += 50
    console.log([x1, y1, x2, y2])   // [0, 0, '15050', '15050']
</script>

つまり引き算の時は数値型として演算が行われているのに対し、足し算の時は文字列型として演算が行われてしまっているようです. 数値型として演算を行いたければ以下の方法があります.

  1. 明示的に数値型に変換してから演算を行う
  2. 加算も減算として定義する

どう考えても1.の方法の方が良さそうですね.