2024年12月28日土曜日

XIAO nRF52840で加速度センサデータを飛ばす(2)

 


加速度センサの計測結果をBLEで飛ばすっていう試み。XIAO nRF52840と秋月AE-ADXL367を使って。
 
ではnRF52840でデータを飛ばしてみる。
基本はXIAO nRF5240のexampleのperipheral bleuartをそのまんま使う。で
ADXL367ctrl1.ino
  1. /*********************************************************************
  2.  This is an example for our nRF52 based Bluefruit LE modules
  3.  
  4.  Pick one up today in the adafruit shop!
  5.  
  6.  Adafruit invests time and resources providing this open source code,
  7.  please support Adafruit and open-source hardware by purchasing
  8.  products from Adafruit!
  9.  
  10.  MIT license, check LICENSE for more information
  11.  All text above, and the splash screen below must be included in
  12.  any redistribution
  13. *********************************************************************/
  14. #include <bluefruit.h>
  15. #include <Adafruit_LittleFS.h>
  16. #include <InternalFileSystem.h>
  17.  
  18. #include "ADXL367API.h" //2024.12.28 Sunday Engineer
  19.  
  20. // BLE Service
  21. BLEDfu bledfu; // OTA DFU service
  22. BLEDis bledis; // device information
  23. BLEUart bleuart; // uart over ble
  24. BLEBas blebas; // battery
  25.  
  26. void setup(){
  27.     Serial.begin(115200);
  28.  
  29. #if CFG_DEBUG
  30.     // Blocking wait for connection when debug mode is enabled via IDE
  31.     while (!Serial)
  32.         yield();
  33. #endif
  34.  
  35.     Serial.println("Bluefruit52 BLEUART Example");
  36.     Serial.println("---------------------------\n");
  37.  
  38.     // Setup the BLE LED to be enabled on CONNECT
  39.     // Note: This is actually the default behavior, but provided
  40.     // here in case you want to control this LED manually via PIN 19
  41.     Bluefruit.autoConnLed(true);
  42.  
  43.     // Config the peripheral connection with maximum bandwidth
  44.     // more SRAM required by SoftDevice
  45.     // Note: All config***() function must be called before begin()
  46.     Bluefruit.configPrphBandwidth(BANDWIDTH_MAX);
  47.  
  48.     Bluefruit.begin();
  49.     Bluefruit.setTxPower(4); // Check bluefruit.h for supported values
  50.     // Bluefruit.setName(getMcuUniqueID()); // useful testing with multiple central connections
  51.     Bluefruit.Periph.setConnectCallback(connect_callback);
  52.     Bluefruit.Periph.setDisconnectCallback(disconnect_callback);
  53.  
  54.     // To be consistent OTA DFU should be added first if it exists
  55.     bledfu.begin();
  56.  
  57.     // Configure and Start Device Information Service
  58.     bledis.setManufacturer("Adafruit Industries");
  59.     bledis.setModel("Bluefruit Feather52");
  60.     bledis.begin();
  61.  
  62.     // Configure and Start BLE Uart Service
  63.     bleuart.begin();
  64.  
  65.     // Start BLE Battery Service
  66.     blebas.begin();
  67.     blebas.write(100);
  68.  
  69.     // Set up and start advertising
  70.     startAdv();
  71.  
  72.     ADXL367setup();//2024.12.28 Sunday Engineer
  73.  
  74.     Serial.println("Please use Adafruit's Bluefruit LE app to connect in UART mode");
  75.     Serial.println("Once connected, enter character(s) that you wish to send");
  76. }
  77.  
  78. void startAdv(void){
  79.     // Advertising packet
  80.     Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE);
  81.     Bluefruit.Advertising.addTxPower();
  82.  
  83.     // Include bleuart 128-bit uuid
  84.     Bluefruit.Advertising.addService(bleuart);
  85.  
  86.     // Secondary Scan Response packet (optional)
  87.     // Since there is no room for 'Name' in Advertising packet
  88.     Bluefruit.ScanResponse.addName();
  89.  
  90.     /* Start Advertising
  91.      * - Enable auto advertising if disconnected
  92.      * - Interval: fast mode = 20 ms, slow mode = 152.5 ms
  93.      * - Timeout for fast mode is 30 seconds
  94.      * - Start(timeout) with timeout = 0 will advertise forever (until connected)
  95.      *
  96.      * For recommended advertising interval
  97.      * https://developer.apple.com/library/content/qa/qa1931/_index.html
  98.      */
  99.     Bluefruit.Advertising.restartOnDisconnect(true);
  100.     Bluefruit.Advertising.setInterval(32, 244); // in unit of 0.625 ms
  101.     Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast mode
  102.     Bluefruit.Advertising.start(0); // 0 = Don't stop advertising after n seconds
  103. }
  104.  
  105. void loop(){
  106.     // Forward data from HW Serial to BLEUART
  107.     while (Serial.available()){
  108.         // Delay to wait for enough input, since we have a limited transmission buffer
  109.         delay(2);
  110.  
  111.         uint8_t buf[64];
  112.         int count = Serial.readBytes(buf, sizeof(buf));
  113.         bleuart.write(buf, count);
  114.     }
  115.  
  116.     // Forward from BLEUART to HW Serial
  117.     while (bleuart.available()){
  118.         uint8_t ch;
  119.         ch = (uint8_t)bleuart.read();
  120.         Serial.write(ch);
  121.     }
  122.     if(ADXL367task()){bleuart.write(ADXL367sendbuf, 15);}//2024.12.28 Sunday Engineer
  123. }
  124. // callback invoked when central connects
  125. void connect_callback(uint16_t conn_handle){
  126.     // Get the reference to current connection
  127.     BLEConnection *connection = Bluefruit.Connection(conn_handle);
  128.  
  129.     char central_name[32] = {0};
  130.     connection->getPeerName(central_name, sizeof(central_name));
  131.  
  132.     Serial.print("Connected to ");
  133.     Serial.println(central_name);
  134. }
  135.  
  136. /**
  137.  * Callback invoked when a connection is dropped
  138.  * @param conn_handle connection where this event happens
  139.  * @param reason is a BLE_HCI_STATUS_CODE which can be found in ble_hci.h
  140.  */
  141. void disconnect_callback(uint16_t conn_handle, uint8_t reason){
  142.     (void)conn_handle;
  143.     (void)reason;
  144.  
  145.     Serial.println();
  146.     Serial.print("Disconnected, reason = 0x");
  147.     Serial.println(reason, HEX);
  148. }
こちらには3箇所追記しただけ、で、
 
ADXL367API.h
  1. #ifndef ADXL367API_H_
  2. extern char ADXL367sendbuf[16];
  3. extern void ADXL367setup(void);
  4. extern uint8_t ADXL367task(void);
  5. #define ADXL367API_H_
  6. #endif
 
ADXL367API.cpp
  1. #include <SPI.h>
  2. #define pinMOSI (10)
  3. #define pinMISO (9)
  4. #define pinSCK (8)
  5. #define pinCS (7)
  6. #define pinDRDY (6)
  7. #define ADDRESS_INTMAP1_LOWER (0x2A)
  8. #define ADDRESS_FILTER_CTL (0x2C)
  9. #define ADDRESS_POWER_CTL (0x2D)
  10. #define ADDRESS_STATUS (0x0B)
  11. #define ADDRESS_XDATA_H (0x0E)
  12. #define ADDRESS_XDATA_L (0x0F)
  13. #define ADDRESS_YDATA_H (0x10)
  14. #define ADDRESS_YDATA_L (0x11)
  15. #define ADDRESS_ZDATA_H (0x12)
  16. #define ADDRESS_ZDATA_L (0x13)
  17. #define ADDRESS_SOFT_RESET (0x1F)
  18. #define VALUE_INTMAP1_LOWER (0x81)
  19. #define VALUE_FILTER_CTL (0x40)
  20. #define VALUE_POWER_CTL (0x02)
  21. #define VALUE_SOFT_RESET (0x52)
  22. SPISettings mySPISettings=SPISettings(1000000,MSBFIRST,SPI_MODE0);
  23. void SPI_writeRegister(uint8_t adr,uint8_t data){
  24.     uint8_t txbuf[3];
  25.     uint8_t rxbuf[3];
  26.     txbuf[0]=0x0A;
  27.     txbuf[1]=adr;
  28.     txbuf[2]=data;
  29.     digitalWrite(pinCS,LOW);
  30.     delayMicroseconds(1);
  31.     SPI.transfer(txbuf,rxbuf,3);
  32.     delayMicroseconds(1000);
  33.     digitalWrite(pinCS,HIGH);
  34. }
  35. uint8_t SPI_readRegister(uint8_t adr){
  36.     uint8_t txbuf[3];
  37.     uint8_t rxbuf[3];
  38.     txbuf[0]=0x0B;
  39.     txbuf[1]=adr;
  40.     txbuf[2]=0x00;
  41.     digitalWrite(pinCS,LOW);
  42.     delayMicroseconds(1);
  43.     SPI.transfer(txbuf,rxbuf,3);
  44.     delayMicroseconds(1000);
  45.     digitalWrite(pinCS,HIGH);
  46.     return rxbuf[2];
  47. }
  48. volatile uint8_t drdy_detected;
  49. void interruptHandler_pinDRDY(){
  50.     drdy_detected=1;
  51. }
  52. void readStatusAndData(uint8_t* buf){
  53.     buf[0]=SPI_readRegister(ADDRESS_STATUS);
  54.     buf[1]=SPI_readRegister(ADDRESS_XDATA_H);
  55.     buf[2]=SPI_readRegister(ADDRESS_XDATA_L);
  56.     buf[3]=SPI_readRegister(ADDRESS_YDATA_H);
  57.     buf[4]=SPI_readRegister(ADDRESS_YDATA_L);
  58.     buf[5]=SPI_readRegister(ADDRESS_ZDATA_H);
  59.     buf[6]=SPI_readRegister(ADDRESS_ZDATA_L);
  60. }
  61. const char char_array[]="0123456789ABCDEF";
  62. void hex2chars(uint8_t src,char* tgt){
  63.   tgt[0]=char_array[(src>>4)&0x0F];
  64.   tgt[1]=char_array[(src>>0)&0x0F];
  65. }
  66. void bytes2chars(uint8_t n,char* strbuf,uint8_t* bytedata){
  67.     uint8_t i;
  68.     for(i=0;i<n;i++){
  69.         hex2chars(bytedata[i],&strbuf[i*2]);
  70.     }
  71.     strbuf[n*2]='\n';
  72. }
  73.  
  74. uint8_t buf[7];
  75. char ADXL367sendbuf[16];
  76.  
  77. void ADXL367setup(){
  78.     uint8_t temp;
  79.     drdy_detected=0;
  80.     pinMode(pinCS,OUTPUT);
  81.     pinMode(pinMOSI,OUTPUT);
  82.     pinMode(pinSCK,OUTPUT);
  83.     pinMode(pinMISO,INPUT);
  84.     digitalWrite(pinCS,HIGH);
  85.     digitalWrite(pinMOSI,LOW);
  86.     digitalWrite(pinSCK,LOW);
  87.     pinMode(pinDRDY,INPUT_PULLUP);
  88.     SPI.begin();
  89.     SPI.beginTransaction(mySPISettings);
  90.     SPI_writeRegister(ADDRESS_SOFT_RESET,VALUE_SOFT_RESET);
  91.     temp=SPI_readRegister(ADDRESS_INTMAP1_LOWER);
  92.     while(temp!=VALUE_INTMAP1_LOWER){
  93.         SPI_writeRegister(ADDRESS_INTMAP1_LOWER,VALUE_INTMAP1_LOWER);
  94.         temp=SPI_readRegister(ADDRESS_INTMAP1_LOWER);
  95.     }
  96.     temp=SPI_readRegister(ADDRESS_FILTER_CTL);
  97.     while(temp!=VALUE_FILTER_CTL){
  98.         SPI_writeRegister(ADDRESS_FILTER_CTL,VALUE_FILTER_CTL);
  99.         temp=SPI_readRegister(ADDRESS_FILTER_CTL);
  100.     }
  101.     temp=SPI_readRegister(ADDRESS_POWER_CTL);
  102.     while(temp!=VALUE_POWER_CTL){
  103.         SPI_writeRegister(ADDRESS_POWER_CTL,VALUE_POWER_CTL);
  104.         temp=SPI_readRegister(ADDRESS_POWER_CTL);
  105.     }
  106.  
  107.     delay(100);
  108.     readStatusAndData(buf);
  109.     attachInterrupt(digitalPinToInterrupt(pinDRDY),interruptHandler_pinDRDY,FALLING);
  110. }
  111.  
  112. uint8_t ADXL367task(){
  113.     uint8_t ret=0;
  114.     if(drdy_detected){
  115.         drdy_detected=0;
  116.         readStatusAndData(buf);
  117.         bytes2chars(7,ADXL367sendbuf,buf);
  118.         ret=1;
  119.     }else{
  120.         ret=0;
  121.     }
  122.     return ret;
  123. }
まぁ、こんな感じで。
で、スマホでSerial Bluetooth Terminalってアプリで、見ることができるわけだが、パソコンで見たいってときは、こちら(https://coskxlabsite.stars.ne.jp/html/for_students/iot/XIAOESP32S3/ESP32S3BLEServer_WinPC.html)がイケてる。ページ下の方のBLE_serial_terminalをダウンロードする。windows ble serial terminalとかで検索すると、BLEをCOMに割り当てる方法とか書いてるんだけど、どうもXIAO nRF5240のexampleのperipheral bleuartはその使い方は想定してないらしくて、すぐ切断されるので、COMへの割り当てすらできない。で、上記アプリでやってみると、
すごくいい。ちゃんとBLEでデータが飛んでるってのが簡単にわかるので助かるー。
 
2024年もあと数日、感謝の気持ちとともに、できるだけ、がんばる。、、、できるだけよ。

2024年12月27日金曜日

XIAO nRF52840で加速度センサデータを飛ばす(1)

 

加速度センサの計測結果をBLEで飛ばすっていう試み。XIAO nRF52840と秋月AE-ADXL367を使って。


XIAO nRF52840でなんかできんかなって雑な発想で秋月AE-ADXL367(https://akizukidenshi.com/catalog/g/g129428/)を購入。温度とか湿度とかをBLEで飛ばすのよりは加速度のほうがちょっと面白いかもなって。pinoutと結線はこのようにする。
データシートによるとINT2は出力だからn.c.でよくて、ADC_INはn.c.でいいぜって書いてあるのでn.c.で、VREG_OUTはパスコンつなげろって書いてあるけど、秋月のボード上に実装してあるようなのでさらにってことはないと思う。
では、ソフトを作っていくんだけど、ADXL367を制御するArduinoライブラリはなさそうなので、自力でデータシートと格闘しないといけない。まぁたったの全64ページ。格闘というほどではない。で、連続データ取得、INT1ピンにDataReadyをアクティブLowで出したい、ODRは12.5Hz(とりあえず)、測定レンジはフルスケール±4gってことにして、この辺をいじっていけばいいってのがわかる。
で、BLEほうは、Arduinoのexampleに入っているperipheral bleuartを使うことにする。っていうか完全にパクる。<--もうこのへんってデバイスが勝手にやっちゃうのが多すぎ。、、、全部知ろうとすることが無茶なくらい複雑な世の中になってきたのでしょうがないんだけど、複雑な心境ではある。とはいえまずは普通にADXL367を動かしてみる。
  1. #include <SPI.h>
  2. #define pinMOSI (10)
  3. #define pinMISO (9)
  4. #define pinSCK (8)
  5. #define pinCS (7)
  6. #define pinDRDY (6)
  7. #define ADDRESS_INTMAP1_LOWER (0x2A)
  8. #define ADDRESS_FILTER_CTL (0x2C)
  9. #define ADDRESS_POWER_CTL (0x2D)
  10. #define ADDRESS_STATUS (0x0B)
  11. #define ADDRESS_XDATA_H (0x0E)
  12. #define ADDRESS_XDATA_L (0x0F)
  13. #define ADDRESS_YDATA_H (0x10)
  14. #define ADDRESS_YDATA_L (0x11)
  15. #define ADDRESS_ZDATA_H (0x12)
  16. #define ADDRESS_ZDATA_L (0x13)
  17. #define ADDRESS_SOFT_RESET (0x1F)
  18. #define VALUE_INTMAP1_LOWER (0x81)
  19. #define VALUE_FILTER_CTL (0x40)
  20. #define VALUE_POWER_CTL (0x02)
  21. #define VALUE_SOFT_RESET (0x52)
  22. SPISettings mySPISettings=SPISettings(1000000,MSBFIRST,SPI_MODE0);
  23. void SPI_writeRegister(uint8_t adr,uint8_t data){
  24.     uint8_t txbuf[3];
  25.     uint8_t rxbuf[3];
  26.     txbuf[0]=0x0A;
  27.     txbuf[1]=adr;
  28.     txbuf[2]=data;
  29.     digitalWrite(pinCS,LOW);
  30.     delayMicroseconds(1);
  31.     SPI.transfer(txbuf,rxbuf,3);
  32.     delayMicroseconds(1000);
  33.     digitalWrite(pinCS,HIGH);
  34. }
  35. uint8_t SPI_readRegister(uint8_t adr){
  36.     uint8_t txbuf[3];
  37.     uint8_t rxbuf[3];
  38.     txbuf[0]=0x0B;
  39.     txbuf[1]=adr;
  40.     txbuf[2]=0x00;
  41.     digitalWrite(pinCS,LOW);
  42.     delayMicroseconds(1);
  43.     SPI.transfer(txbuf,rxbuf,3);
  44.     delayMicroseconds(1000);
  45.     digitalWrite(pinCS,HIGH);
  46.     return rxbuf[2];
  47. }
  48. volatile uint8_t drdy_detected;
  49. void interruptHandler_pinDRDY(){
  50.     drdy_detected=1;
  51. }
  52. void readStatusAndData(uint8_t* buf){
  53.     buf[0]=SPI_readRegister(ADDRESS_STATUS);
  54.     buf[1]=SPI_readRegister(ADDRESS_XDATA_H);
  55.     buf[2]=SPI_readRegister(ADDRESS_XDATA_L);
  56.     buf[3]=SPI_readRegister(ADDRESS_YDATA_H);
  57.     buf[4]=SPI_readRegister(ADDRESS_YDATA_L);
  58.     buf[5]=SPI_readRegister(ADDRESS_ZDATA_H);
  59.     buf[6]=SPI_readRegister(ADDRESS_ZDATA_L);
  60. }
  61. const char char_array[]="0123456789ABCDEF";
  62. void hex2chars(uint8_t src,char* tgt){
  63.   tgt[0]=char_array[(src>>4)&0x0F];
  64.   tgt[1]=char_array[(src>>0)&0x0F];
  65. }
  66. void bytes2chars(uint8_t n,char* strbuf,uint8_t* bytedata){
  67.     uint8_t i;
  68.     for(i=0;i<n;i++){
  69.         hex2chars(bytedata[i],&strbuf[i*2]);
  70.     }
  71.     strbuf[n*2]='\0';
  72. }
  73.  
  74. uint8_t buf[7];
  75. char sendbuf[16];
  76.  
  77. void setup(){
  78.     uint8_t temp;
  79.     drdy_detected=0;
  80.     pinMode(pinCS,OUTPUT);
  81.     pinMode(pinMOSI,OUTPUT);
  82.     pinMode(pinSCK,OUTPUT);
  83.     pinMode(pinMISO,INPUT);
  84.     digitalWrite(pinCS,HIGH);
  85.     digitalWrite(pinMOSI,LOW);
  86.     digitalWrite(pinSCK,LOW);
  87.     pinMode(pinDRDY,INPUT_PULLUP);
  88.     SPI.begin();
  89.     SPI.beginTransaction(mySPISettings);
  90.     delay(1);
  91.     SPI_writeRegister(ADDRESS_SOFT_RESET,VALUE_SOFT_RESET);
  92.     delay(1);
  93.     temp=SPI_readRegister(ADDRESS_INTMAP1_LOWER);
  94.     while(temp!=VALUE_INTMAP1_LOWER){
  95.         SPI_writeRegister(ADDRESS_INTMAP1_LOWER,VALUE_INTMAP1_LOWER);
  96.         delay(1);
  97.         temp=SPI_readRegister(ADDRESS_INTMAP1_LOWER);
  98.     }
  99.     temp=SPI_readRegister(ADDRESS_FILTER_CTL);
  100.     while(temp!=VALUE_FILTER_CTL){
  101.         SPI_writeRegister(ADDRESS_FILTER_CTL,VALUE_FILTER_CTL);
  102.         delay(1);
  103.         temp=SPI_readRegister(ADDRESS_FILTER_CTL);
  104.     }
  105.     temp=SPI_readRegister(ADDRESS_POWER_CTL);
  106.     while(temp!=VALUE_POWER_CTL){
  107.         SPI_writeRegister(ADDRESS_POWER_CTL,VALUE_POWER_CTL);
  108.         delay(1);
  109.         temp=SPI_readRegister(ADDRESS_POWER_CTL);
  110.     }
  111.  
  112.     delay(100);
  113.     Serial.begin(115200);
  114.     while(!Serial){delay(10);}
  115.     Serial.println("=== AE-ADXL367 control example using XIAO nRF52840 ===");Serial.flush();
  116.     buf[0]=SPI_readRegister(ADDRESS_INTMAP1_LOWER);
  117.     buf[1]=SPI_readRegister(ADDRESS_FILTER_CTL);
  118.     buf[2]=SPI_readRegister(ADDRESS_POWER_CTL);
  119.     bytes2chars(3,sendbuf,buf);
  120.     Serial.println(sendbuf);Serial.flush();
  121.     readStatusAndData(buf);
  122.     bytes2chars(7,sendbuf,buf);
  123.     Serial.println(sendbuf);Serial.flush();
  124.     readStatusAndData(buf);
  125.     bytes2chars(7,sendbuf,buf);
  126.     Serial.println(sendbuf);Serial.flush();
  127.     attachInterrupt(digitalPinToInterrupt(pinDRDY),interruptHandler_pinDRDY,FALLING);
  128. }
  129.  
  130. void loop(){
  131.     if(drdy_detected){
  132.         drdy_detected=0;
  133.         readStatusAndData(buf);
  134.         bytes2chars(7,sendbuf,buf);
  135.         Serial.println(sendbuf);Serial.flush();
  136.     }
  137. }
まぁいつも通りいきなりな感じで、こう。
書き込むとUARTに文字列が出力される。で、平置きでは
4101A4FE741F8C
ってなる。で、計算してみると、

STATUS XDATA YDATA ZDATA
4101A4FE741F8C 41 01A4 FE74 1F8C


420 65140 8076 unsigned


420 -396 8076 signed


105 -99 2019 2bits shift /4


0.05126953125 -0.04833984375 0.98583984375 Scaled *(8/(2^14))
てな感じ。うまくいっていると思う。
USBコネクタ周りに90度回すと
X=0.03857421875
Y=0.931640625
Z=0.0048828125
USBコネクタ部が上空に来るように90度回すと
X=0.9970703125
Y=0.048828125
Z=-0.0390625
いいぞ!では次はBLEで飛ばしてみよう!、、、後で。
 
ところで、年の瀬だねー。2024年、支えてくれたいろんな人への感謝とともに、あと数日、できるだけ、がんばる。、、、できるだけよ。

2024年12月26日木曜日

PDFsharpを使って複数のpdfを結合する

 

複数のpdfを結合して1つのpdfにしたいっていうこと、たまにあるよね。PDFsharpっていうdotnet libraryで実現できるらしいので軽い気持ちでやってみる。


"pdfsharp 結合 dotnet"でググるとまぁ結構出てくる。とりあえず、この辺(https://qiita.com/benjamin1gou/items/718e6dbbe8e831529737)を参考にする。
で、コマンドラインで入力ファイルと出力ファイルを引数にとって実行するってしたいんだけど、コマンドライン引数の解析ってめんどいんよね。で、System.CommandLine パッケージってのがあるらしいんだけど、、、prereleaseってなっていて、、、あまり参考情報がないので、ギブアップ。拙い感じで自力で実装することにする。
では、
mkdir pdf_connector
cd pdf_connector
dotnet new console
dotnet add package pdfsharp
ってやって、あとは好きな感じでProgram.csをいじる。VSCode使うもよし、別のエディタでもよし。
で、こういうコード
  1. using PdfSharp.Pdf;
  2. using PdfSharp.Pdf.IO;
  3. public class pdf_connector{
  4.     static void Main(string[] args)
  5.     {
  6.         int opos=0;
  7.         int skip=0;
  8.         int n=0;
  9.         var list_ifiles=new List<string>();
  10.         string ofile="";
  11.         //Console.WriteLine(args.Length);
  12.         foreach(string sarg in args){
  13.             if(opos==1){
  14.                 ofile=sarg;
  15.                 skip=1;
  16.             }
  17.             if(sarg=="-o"){
  18.                 opos=1;
  19.             }else{
  20.                 opos=0;
  21.                 if(skip==0){
  22.                     list_ifiles.Add(sarg);
  23.                     n=n+1;
  24.                 }
  25.             }
  26.             //Console.WriteLine(sarg);
  27.         }
  28.         string[] ifiles=list_ifiles.ToArray();
  29.         Console.WriteLine("Input file(s):");
  30.         foreach(string ifile in ifiles){
  31.             Console.WriteLine(ifile);
  32.         }
  33.         Console.WriteLine("Output file:");
  34.         Console.WriteLine(ofile);
  35.         MergePdfFiles(ifiles,ofile);
  36.     }
  37.     private static void MergePdfFiles(string[] sourceFilenames, string destinationFilename)
  38.     {
  39.         using (PdfDocument document = new PdfDocument()){
  40.             foreach (string filename in sourceFilenames){
  41.                 using (PdfDocument inputDocument = PdfReader.Open(filename, PdfDocumentOpenMode.Import)){
  42.                     foreach (PdfPage page in inputDocument.Pages){
  43.                         document.AddPage(page);
  44.                     }
  45.                 }
  46.             }
  47.             document.Save(destinationFilename);
  48.         }
  49.     }
  50. }
で、適当にpdfを作って、プロジェクトのルートフォルダに置いておく。で、
dotnet run -- aa.pdf bb.pdf cc.pdf -o kk.pdf
("--"以降の記述が実行ファイルの引数になるんだよー)
ってすると、kk.pdfが出来上がっていて、ちゃんと結合されているってのがわかる(<--すまんエビデンスは省略)。用紙サイズ違いもお構いなしにつなげてくれるのでなかなかよい。たったこれだけの作業で、たった50行のプログラムで、できちゃうんだから、みんなコンピューター言語やればいいのに、、、ってのと、やっぱり「コンソールアプリ」だよなー。大したことしないアプリだとGUIはムダムダムダ。

「これがUNIXの哲学である。
一つのことを行い、またそれをうまくやるプログラムを書け。
協調して動くプログラムを書け。
標準入出力(テキスト・ストリーム)を扱うプログラムを書け。標準入出力は普遍的インターフェースなのだ。」
「Worse is better」

あ、実行ファイルは、
dotnet build --configuration Release
ってすると、プロジェクトフォルダの下のどこかにできる。exeだけで使えるのか、dllも要るのかはまぁ試してみりゃわかるっしょ、、、
あ、PDFsharp以外にもpdfを扱えるdotnet libraryがあるっぽいけど、PDFsharpはMITライセンスなのが素晴らしい。

2024年12月17日火曜日

openEMSを再び(5)

 

以前ギブアップしていたopenEMSにもう一度チャレンジする。


実は前回のやつはAddLumpedElementってやつでキャパシタを入れていたので、キャパシタがない状態でシミュレーションしてみて、後からSパラに対してキャパシタを追加した場合と比較してみたい。
しばらく無言で絵を貼り続ける
で、Octaveのコードが、こう。
  1. % tutorial for hyp2mat - capacitor in a microstrip.
  2. %
  3. % run from openems matlab command prompt
  4. % See hyp2mat(1) - convert hyperlynx files to matlab scripts.
  5. % (C) 2011,2012 Thorsten Liebig <thorsten.liebig@gmx.de>
  6. % Copyright 2012 Koen De Vleeschauwer.
  7. %
  8. % This file is part of hyp2mat.
  9. %
  10. % This program is free software: you can redistribute it and/or modify
  11. % it under the terms of the GNU General Public License as published by
  12. % the Free Software Foundation, either version 3 of the License, or
  13. % (at your option) any later version.
  14. %
  15. % This program is distributed in the hope that it will be useful,
  16. % but WITHOUT ANY WARRANTY; without even the implied warranty of
  17. % MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  18. % GNU General Public License for more details.
  19. %
  20. % You should have received a copy of the GNU General Public License
  21. % along with this program. If not, see <http://www.gnu.org/licenses/>.
  22. close all
  23. clear
  24. clc
  25. function port_return=Execute_main(Sim_Path,f_max,show_structure,port_active)
  26.   % initialize
  27.   physical_constants;
  28.   %f_max = 1e9;
  29.   % Importing printed circuit board
  30.   CSX = InitCSX();
  31.   CSX = ImportHyperLynx(CSX, 'small_loop_antenna2.hyp');
  32.   % Adding a component
  33. ## [pad1_material, pad1_start, pad1_stop] = GetHyperLynxPort(CSX, 'C1.1');
  34. ## [pad2_material, pad2_start, pad2_stop] = GetHyperLynxPort(CSX, 'C1.2');
  35. ##
  36. ## c1_height = pad1_stop - pad1_start;
  37. ## c1_start = [(pad1_start(1)+pad1_stop(1))/2, pad1_start(2), pad1_start(3)];
  38. ## c1_stop = [(pad2_start(1)+pad2_stop(1))/2, pad2_stop(2), pad2_stop(3)+c1_height];
  39. ##
  40. ## CSX = AddLumpedElement( CSX, 'Capacitor', 0, 'Caps', 1, 'C', 1e-12);
  41. ## CSX = AddBox( CSX, 'Capacitor', 0, c1_start, c1_stop );
  42.   % Adding excitation and load
  43.   [port1_material, port1_start, port1_stop] = GetHyperLynxPort(CSX, 'TP1.1');
  44.   [gnd1_material, gnd1_start, gnd1_stop] = GetHyperLynxPort(CSX, 'TP3.1');
  45.   [port2_material, port2_start, port2_stop] = GetHyperLynxPort(CSX, 'TP2.1');
  46.   [gnd2_material, gnd2_start, gnd2_stop] = GetHyperLynxPort(CSX, 'TP4.1');
  47.   if(port_active==1)
  48.     [CSX, port{1}] = AddLumpedPort( CSX, 999, 1, 50, gnd1_start, port1_stop, [0 0 -1], true);
  49.     [CSX, port{2}] = AddLumpedPort( CSX, 999, 2, 50, gnd2_start, port2_stop, [0 0 -1]);
  50.   else
  51.     [CSX, port{1}] = AddLumpedPort( CSX, 999, 1, 50, gnd1_start, port1_stop, [0 0 -1]);
  52.     [CSX, port{2}] = AddLumpedPort( CSX, 999, 2, 50, gnd2_start, port2_stop, [0 0 -1], true);
  53.   endif
  54.   % Setting up a mesh
  55.   unit = GetUnits(CSX);
  56.   substrate_epr = GetEpsilon(CSX);
  57.   resolution = c0 / f_max / sqrt(substrate_epr) / unit / 25;
  58.   AirBox = c0 / f_max / unit / 25;
  59.   z_top = port1_start(3);
  60.   z_bottom = gnd1_start(3);
  61.   z_middle = (z_top+z_bottom)/2;
  62.   mesh.x = [];
  63.   mesh.y = [];
  64.   mesh.z = [ z_middle ];
  65.   mesh = DetectEdges(CSX, mesh);
  66.   mesh.x = [min(mesh.x)-AirBox max(mesh.x)+AirBox mesh.x];
  67.   mesh.y = [min(mesh.y)-AirBox max(mesh.y)+AirBox mesh.y];
  68.   mesh.z = [min(mesh.z)-AirBox max(mesh.z)+2*AirBox mesh.z];
  69.   %mesh = SmoothMesh(mesh, resolution);
  70.   mesh = SmoothMesh(mesh, resolution, 'algorithm', [ 1 ]);
  71.   % Setting boundary conditions
  72.   FDTD = InitFDTD();
  73.   FDTD = SetGaussExcite(FDTD, f_max/2, f_max/2);
  74.   BC = {'PML_8' 'PML_8' 'PML_8' 'PML_8' 'PML_8' 'PML_8'};
  75.   FDTD = SetBoundaryCond(FDTD, BC );
  76.   mesh = AddPML(mesh, 8);
  77.   CSX = DefineRectGrid(CSX, unit, mesh);
  78.   % Simulation
  79.   %Sim_Path = 'tmp';
  80.   Sim_CSX = 'msl.xml';
  81.   disp([ 'Estimated simulation runtime: 6500 timesteps' ]); % inform user this may take a while...
  82.   WriteOpenEMS([Sim_Path '/' Sim_CSX], FDTD, CSX);
  83.   if(show_structure>0)
  84.     CSXGeomPlot([Sim_Path '/' Sim_CSX]);
  85.   endif
  86.     RunOpenEMS(Sim_Path, Sim_CSX);
  87.   port_return=port;
  88. endfunction
  89. f_max = 1e9;
  90. Sim_Path1='tmp1'
  91. [status, message, messageid] = rmdir(Sim_Path1, 's'); % clear previous directory
  92. [status, message, messageid] = mkdir(Sim_Path1 ); % create empty simulation folder
  93. Sim_Path2='tmp2'
  94. [status, message, messageid] = rmdir(Sim_Path2, 's'); % clear previous directory
  95. [status, message, messageid] = mkdir(Sim_Path2 ); % create empty simulation folder
  96. port=Execute_main(Sim_Path1,f_max,1,1)
  97. % Calculating the s-parameters
  98. f = linspace( 100e6, f_max, 1601 );
  99. port = calcPort( port, Sim_Path1, f, 'RefImpedance', 50);
  100. s11 = port{1}.uf.ref./ port{1}.uf.inc;
  101. s21 = port{2}.uf.ref./ port{1}.uf.inc;
  102. port=Execute_main(Sim_Path2,f_max,0,2)
  103. % Calculating the s-parameters
  104. port = calcPort( port, Sim_Path2, f, 'RefImpedance', 50);
  105. s12 = port{1}.uf.ref./ port{2}.uf.inc;
  106. s22 = port{2}.uf.ref./ port{2}.uf.inc;
  107. s_param=[];
  108. s_param(1,1,:) = s11;
  109. s_param(1,2,:) = s12;
  110. s_param(2,1,:) = s21;
  111. s_param(2,2,:) = s22;
  112. write_touchstone('s',f,s_param,'out.s2p');
  113. close all
  114. semilogx(f/1e6,20*log10(abs(s11)),'k-','LineWidth',2);
  115. hold on;
  116. grid on;
  117. semilogx(f/1e6,20*log10(abs(s21)),'r--','LineWidth',2);
  118. legend('S_{11}','S_{21}');
  119. ylabel('S-Parameter (dB)','FontSize',12);
  120. xlabel('frequency (GHz) \rightarrow','FontSize',12);
  121. ylim([-80 10]);
  122. print('sparam.png', '-dpng');
  123. % not truncated
Hyperlynxファイルがこう。
  1. {VERSION=2.14}
  2. {UNITS=ENGLISH LENGTH}
  3. {BOARD "small_loop_antenna2.kicad_pcb"
  4.   (PERIMETER_SEGMENT X1=6.100000000 Y1=4.050000000 X2=4.900000000 Y2=4.050000000)
  5.   (PERIMETER_SEGMENT X1=4.900000000 Y1=4.050000000 X2=4.900000000 Y2=2.850000000)
  6.   (PERIMETER_SEGMENT X1=4.900000000 Y1=2.850000000 X2=6.100000000 Y2=2.850000000)
  7.   (PERIMETER_SEGMENT X1=6.100000000 Y1=2.850000000 X2=6.100000000 Y2=4.050000000)
  8. }
  9. {STACKUP
  10.   (SIGNAL T=0.00137795 P=0 C=1.724e-08 L="F.Cu" M=COPPER)
  11.   (DIELECTRIC T=0.03937 C=4.4 L="DE_F.Cu" M="FR4")
  12.   (SIGNAL T=0.00137795 P=0 C=1.724e-08 L="B.Cu" M=COPPER)
  13. }
  14. {DEVICES
  15.   (? REF="TP1" L="F.Cu")
  16.   (? REF="TP2" L="F.Cu")
  17.   (? REF="TP4" L="B.Cu")
  18.   (? REF="TP3" L="B.Cu")
  19. }
  20. {PADSTACK=0, 0.000000000
  21.   ("F.Cu", 1, 0.039370079, 0.039370079, 180.0, M)
  22. }
  23. {PADSTACK=1, 0.000000000
  24.   ("B.Cu", 1, 0.039370079, 0.039370079, 0.0, M)
  25. }
  26. {NET="GND"
  27.   (PIN X=5.4500000000 Y=4.0000000000 R="TP4.1" P=1)
  28.   (PIN X=5.5500000000 Y=4.0000000000 R="TP3.1" P=1)
  29.   (SEG X1=5.4500000000 Y1=4.0000000000 X2=5.5500000000 Y2=4.0000000000 W=0.0393700787 L="B.Cu")
  30. }
  31. {NET="Net-(TP1-Pad1)"
  32.   (PIN X=5.5500000000 Y=4.0000000000 R="TP1.1" P=0)
  33.   (PIN X=5.4500000000 Y=4.0000000000 R="TP2.1" P=0)
  34.   (SEG X1=5.4500000000 Y1=3.9000000000 X2=5.4000000000 Y2=3.8500000000 W=0.0393700787 L="F.Cu")
  35.   (SEG X1=5.4500000000 Y1=4.0000000000 X2=5.4500000000 Y2=3.9000000000 W=0.0393700787 L="F.Cu")
  36.   (SEG X1=5.1000000000 Y1=3.0000000000 X2=5.0000000000 Y2=3.1000000000 W=0.0393700787 L="F.Cu")
  37.   (SEG X1=5.9000000000 Y1=3.0000000000 X2=5.1000000000 Y2=3.0000000000 W=0.0393700787 L="F.Cu")
  38.   (SEG X1=6.0000000000 Y1=3.1000000000 X2=5.9000000000 Y2=3.0000000000 W=0.0393700787 L="F.Cu")
  39.   (SEG X1=6.0000000000 Y1=3.1000000000 X2=6.0000000000 Y2=3.6500000000 W=0.0393700787 L="F.Cu")
  40.   (SEG X1=5.4000000000 Y1=3.8000000000 X2=5.4000000000 Y2=3.8500000000 W=0.0393700787 L="F.Cu")
  41.   (SEG X1=5.6000000000 Y1=3.7500000000 X2=5.5500000000 Y2=3.8000000000 W=0.0393700787 L="F.Cu")
  42.   (SEG X1=5.0000000000 Y1=3.1000000000 X2=5.0000000000 Y2=3.6500000000 W=0.0393700787 L="F.Cu")
  43.   (SEG X1=5.0000000000 Y1=3.6500000000 X2=5.1000000000 Y2=3.7500000000 W=0.0393700787 L="F.Cu")
  44.   (SEG X1=5.9000000000 Y1=3.7500000000 X2=5.6000000000 Y2=3.7500000000 W=0.0393700787 L="F.Cu")
  45.   (SEG X1=5.3500000000 Y1=3.7500000000 X2=5.4000000000 Y2=3.8000000000 W=0.0393700787 L="F.Cu")
  46.   (SEG X1=5.1000000000 Y1=3.7500000000 X2=5.3500000000 Y2=3.7500000000 W=0.0393700787 L="F.Cu")
  47.   (SEG X1=6.0000000000 Y1=3.6500000000 X2=5.9000000000 Y2=3.7500000000 W=0.0393700787 L="F.Cu")
  48.   (SEG X1=5.5500000000 Y1=3.8000000000 X2=5.5500000000 Y2=4.0000000000 W=0.0393700787 L="F.Cu")
  49. }
ボードサイズと層構成はこんな感じで認識されている。
こんなパタンとして読み込まれている。
そしてしばらく待つ、、、意外と早かった、、、6+6分。で、こうなる。
で、Pythonで見てみる。前回と同じコード。
  1. import numpy as np
  2. import skrf as rf
  3. import matplotlib.pyplot as plt
  4. import scipy
  5.  
  6. def main():
  7.         
  8.     ANT1 = rf.Network('out.s2p')
  9.     ANT1.name='ANT1'
  10.     freq=ANT1.frequency
  11.     tl_media = rf.DefinedGammaZ0(freq, z0=50)
  12.     gnd = rf.Circuit.Ground(freq, name='gnd')
  13.     port1 = rf.Circuit.Port(freq, name='port1', z0=50)
  14.     port2 = rf.Circuit.Port(freq, name='port2', z0=50)
  15.     cnx=[[(port1,0),(ANT1,0)],
  16.          [(ANT1,1),(port2,0)],
  17.          [(gnd,0)]]
  18.     cir=rf.Circuit(cnx)
  19.     ntw=cir.network
  20.     ntw.frequency.unit='MHz'
  21.     fig=plt.figure()
  22.     ax1=fig.add_subplot(2,2,1)
  23.     ntw.plot_s_smith(ax=ax1,m=0, n=0, lw=2)
  24.     ax2_1=fig.add_subplot(2,2,2)
  25.     ax2_2=ax2_1.twinx()
  26.     ntw.plot_s_db(ax=ax2_1,m=1, n=0, lw=2, show_legend=False)
  27.     ntw.plot_s_vswr(ax=ax2_2,m=0, n=0, lw=2, show_legend=False, color='orangered')
  28.     ax2_2.set_ylim(1,6)
  29.     handler1, label1 = ax2_1.get_legend_handles_labels()
  30.     handler2, label2 = ax2_2.get_legend_handles_labels()
  31.     ax2_1.legend(handler1 + handler2, label1 + label2, loc=2, borderaxespad=0.)
  32.     ax3=fig.add_subplot(2,2,3)
  33.     ntw.plot_s_db(ax=ax3,m=0, n=1, lw=2)
  34.     ax4=fig.add_subplot(2,2,4)
  35.     ntw.plot_s_smith(ax=ax4,m=1, n=1, lw=2)
  36.     fig.tight_layout()
  37.     plt.show()
  38. if __name__ == "__main__":
  39.     main()
で、こうなる。
で、キャパシタを入れてみる。こんなかんじで
  1. import numpy as np
  2. import skrf as rf
  3. import matplotlib.pyplot as plt
  4. import scipy
  5.  
  6. def main():
  7.         
  8.     ANT1 = rf.Network('out.s2p')
  9.     ANT1.name='ANT1'
  10.     freq=ANT1.frequency
  11.     tl_media = rf.DefinedGammaZ0(freq, z0=50)
  12.     C11 = tl_media.capacitor(1e-12, name='C11')
  13.     gnd = rf.Circuit.Ground(freq, name='gnd')
  14.     port1 = rf.Circuit.Port(freq, name='port1', z0=50)
  15.     port2 = rf.Circuit.Port(freq, name='port2', z0=50)
  16.     cnx=[[(port1,0),(ANT1,0)],
  17.          [(ANT1,1),(C11,0)],
  18.          [(C11,1),(port2,0)],
  19.          [(gnd,0)]]
  20.     cir=rf.Circuit(cnx)
  21.     ntw=cir.network
  22.     ntw.frequency.unit='MHz'
  23.     fig=plt.figure()
  24.     ax1=fig.add_subplot(2,2,1)
  25.     ntw.plot_s_smith(ax=ax1,m=0, n=0, lw=2)
  26.     ax2_1=fig.add_subplot(2,2,2)
  27.     ax2_2=ax2_1.twinx()
  28.     ntw.plot_s_db(ax=ax2_1,m=1, n=0, lw=2, show_legend=False)
  29.     ntw.plot_s_vswr(ax=ax2_2,m=0, n=0, lw=2, show_legend=False, color='orangered')
  30.     ax2_2.set_ylim(1,6)
  31.     handler1, label1 = ax2_1.get_legend_handles_labels()
  32.     handler2, label2 = ax2_2.get_legend_handles_labels()
  33.     ax2_1.legend(handler1 + handler2, label1 + label2, loc=2, borderaxespad=0.)
  34.     ax3=fig.add_subplot(2,2,3)
  35.     ntw.plot_s_db(ax=ax3,m=0, n=1, lw=2)
  36.     ax4=fig.add_subplot(2,2,4)
  37.     ntw.plot_s_smith(ax=ax4,m=1, n=1, lw=2)
  38.     fig.tight_layout()
  39.     plt.show()
  40. if __name__ == "__main__":
  41.     main()
実行すると、
キャパシタまで一緒にOpenEMSで計算したのとほんのちょっとだけ違うけど、だいたいあってるよね。OpenEMSのAddLumpedElementがそれなりにちゃんと計算しているってことがわかる。ナイス!が、AddLumpedElementするために構造が複雑になってシミュレーションにめちゃ時間かかるので、使いどころはびみょー。実際のことろ実物とどのくらい一致するのかわからんけどOpenEMSなかなかおもしろい。