แนะนำโปรแกรมมิ่งภาษาซีบนเครื่องคิดเลข Casio fx-9860G II SD ด้วยเครื่องมือพัฒนา SDK ของ Casio

เคยเกริ่นมาก่อนว่าต้องการเขียนบทความนี้ขึ้นมาเพื่อวงการศึกษาบ้านเราที่สนใจเรื่องโปรแกรมมิ่งบนเครื่องคิดเลขสามารถจะพัฒนาโปรแกรมภาษาซีบน Casio fx-9860G II SD หรือรุ่นที่ใกล้เคียงนี้ได้ โดยที่มีไม่มีข้อจำกัดด้านภาษาโปรแกรมมิ่ง เหมือนกับภาษา casio basic อาจจะส่งผลให้ในอนาคต มีโปรแกรมที่พัฒนาโดยบุคคลากรท่านอื่นๆ เข้ามาสู่วงการนี้มากขึ้น และได้ตัวโปรแกรมงานสำรวจที่มีความหลากหลายและความสามารถมากขึ้นทั้งนี้เพื่อขยายขีดความสามารถโปรแกรมบนเครื่องคิดเลขให้สามารถคิดงานที่ยาก ซับซ้อนได้ บางครั้งเกือบจะเทียบเท่าโปรแกรมที่ใช้งานบนคอมพิวเตอร์

เครื่องมือพัฒนา Software Development Kit (SDK)

เครื่องมือตัวนี้เดิมทีสามารถดาวน์โหลดที่เว็บไซต์ตามลิ๊งค์นี้ได้ http://edu.casio.com/support/en/agreement.html#2 ขั้นตอนแรกยอมรับเงื่อนไขแล้วเลื่อนหน้าไปด้านล่างๆจะเห็นเครื่องคิดเลขรุ่น fx-9860 เมื่อคลิกลิ๊งค์ SDK เข้าไปจะเห็นว่าลิ๊งค์เครื่องมือพัฒนาโปรแกรมขาด แต่คู่มือยังสามารถดาวน์โหลดมาอ่านศึกษาได้ ผมอาศัยลงใต้ดินที่มีคนปล่อยให้ดาวน์โหลด (ถ้าใครอยากได้เครื่องมือตัวนี้ก็ขอมาหลังไมค์กันได้ครับ) และดาวน์โหลดโปรแกรม FA-124 มาด้วยอยู่ในหมวด Support Software/PC Link software

ติดตั้งเครื่องมือพัฒนาโปรแกรม

เปิดไฟล์ zip ของเครื่องมือพัฒนาจะเห็นไฟล์ข้างในดังนี้

จากนั้นทำการติดตั้งเครื่องมือลงคอมพิวเตอร์  ข้อสำคัญคือพาทของโฟลเดอร์หรือไดเรคทอรีที่ติดตั้งจะต้องไม่มีช่องว่าง ดังนั้นให้ติดตั้งไปที่รากของไดรว์ C: ตัวอย่างผมใช้ชื่อว่า fx-9860-sdk 

ถ้าพาทของโฟลเดอร์ที่ติดตั้งมีช่องว่างการ compile & build จะไม่ผ่านเลย เมื่อติดตั้งแล้วจะมีไอคอนที่หน้า desktop

เริ่มต้นใช้งาน

เมื่อเปิดโปรแกรมจากไอคอนที่ desktop จะเห็นหน้าตาเครื่องมือพัฒนาโปรแกรม ดังรูป อาจจะดูทื่อๆเพราะเครื่องมือตัวนี้ออกมานานแล้วตั้งแต่วินโดส์รุ่นก่อนหน้านี้ ที่เมนู “Project” คลิกเลือก “New” ผมสร้างโฟลเดอร์ชื่อ “FX9860GIISD” ไว้ที่ไดรว์ D: และตั้งชื่อโฟลเดอร์สำหรับทดสอบการเขียนโปรแกรมนี้ว่า “Test” และตั้งชื่อโปรแกรมว่า “Hello”

จะเห็นหน้าตาเครื่องมือพัฒนาประมาณรูปด้านล่าง และตัวอีมูเลเตอร์ “Display” และ “Keyboard” ของ fx-9860G

Project แรกเริ่ม

เมื่อเราสร้าง Project ใหม่จะเห็นโครงร่างที่เครื่องมือเขียนมาให้ดังนี้

/*****************************************************************/
/*                                                               */
/*   CASIO fx-9860G SDK Library                                  */
/*                                                               */
/*   File name : Hello.c                                 */
/*                                                               */
/*   Copyright (c) 2006 CASIO COMPUTER CO., LTD.                 */
/*                                                               */
/*****************************************************************/
#include "fxlib.h"

//****************************************************************************
//  AddIn_main (Sample program main function)
//
//  param   :   isAppli   : 1 = This application is launched by MAIN MENU.
//                        : 0 = This application is launched by a strip in eACT application.
//
//              OptionNum : Strip number (0~3)
//                         (This parameter is only used when isAppli parameter is 0.)
//
//  retval  :   1 = No error / 0 = Error
//
//****************************************************************************
int AddIn_main(int isAppli, unsigned short OptionNum)
{
    unsigned int key;

    Bdisp_AllClr_DDVRAM();

    locate(1,4);
    Print((unsigned char*)"This application is");
    locate(1,5);
    Print((unsigned char*)" sample Add-In.");

    while(1){
        GetKey(&key);
    }

    return 1;
}

//****************************************************************************
//**************                                              ****************
//**************                 Notice!                      ****************
//**************                                              ****************
//**************  Please do not change the following source.  ****************
//**************                                              ****************
//****************************************************************************

#pragma section _BR_Size
unsigned long BR_Size;
#pragma section

#pragma section _TOP

//****************************************************************************
//  InitializeSystem
//
//  param   :   isAppli   : 1 = Application / 0 = eActivity
//              OptionNum : Option Number (only eActivity)
//
//  retval  :   1 = No error / 0 = Error
//
//****************************************************************************
int InitializeSystem(int isAppli, unsigned short OptionNum)
{
    return INIT_ADDIN_APPLICATION(isAppli, OptionNum);
}
#pragma section

จากโค้ดด้านบนจะเห็นว่าไม่เห็นจุดเริ่มต้นเข้าโปรแกรม (entry point) ฟังก์ชัน main() เช่นภาษาซีทั่วๆไป แต่จะมี AddIn_main() มาแทนให้รู้ว่าเป็นจุดเริ่มต้นของโปรแกรม AddIn สำหรับเครื่องคิดเลขนี้

มาดูโค้ดกันสักนิด ที่ #inlucde “fxlib.h” จะเป็น header ของ Casio สำหรับการแสดงผลเช่นฟังก์ชัน Print เพื่อแสดงบนจอภาพเครื่องคิดเลข Bdisp_AllClr_DDVRAM(); จะเป็นฟังก์ชันเคลียร์หน้าจอภาพให้ว่างเปล่า  locate(1,4); เลื่อนเคอเซอร์มาที่คอลัมน์ 1 และบรรทัดที่ 4 จากนั้นพิมพ์ด้วยฟังก์ชัน Print((unsigned char*)”This application is”); สุดท้ายปิดด้วย loop ไม่รู้จบ while (1) แล้วรอผู้ใช้กดคีย์ GetKey(&key); ตอนรันโปรแกรมถ้าผู้ใช้กดคีย์ “MENU” บนเครื่องคิดเลขก็จะเข้าสู่โหมด “MAIN MENU” แต่โปรแกรมก็ยังรันค้างอยู่ในสถานะเดิม

Compile & Build

มาลองคอมไพล์และบิวด์ดู ที่เมนู “Project” คลิก “Rebuild all” ถ้าไม่มีอะไรผิดพลาดที่กรอบ “Builds” จะแสดงผลดังนี้

Executing Hitachi SH C/C++ Compiler/Assembler phase

set SHC_INC=C:\fx-9860G-SDK\OS\SH\include
set PATH=C:\fx-9860G-SDK\OS\SH\bin
set SHC_LIB=C:\fx-9860G-SDK\OS\SH\bin
set SHC_TMP=D:\FX9860GIISD\Test\Debug
"C:\fx-9860G-SDK\OS\SH\bin\shc.exe" -subcommand=C:\Users\priabroy\AppData\Local\Temp\hmk9A12.tmp

Executing Hitachi OptLinker04 phase

"C:\fx-9860G-SDK\OS\SH\bin\Optlnk.exe" -subcommand=C:\Users\priabroy\AppData\Local\Temp\hmk9D5F.tmp

Optimizing Linkage Editor Completed

HMAKE MAKE UTILITY Ver. 1.1
Copyright (C) Hitachi Micro Systems Europe Ltd. 1998
Copyright (C) Hitachi Ltd. 1998 


	Make proccess completed

"D:\FX9860GIISD\Test\HELLO.G1A" was created.

Build has completed.

ถ้าสำเร็จจะเห็น HELLO.G1A ถูกสร้างขึ้นมา จากนั้นที่เมนู “Run” คลิกที่เมนูย่อย “Run” อีมูเลเตอร์จะเริ่มทำงาน

ที่ “Keyboard” กดปุ่มเลื่อนลูกศรลงที่ไอคอน “Debug” กดคีย์ “EXE” จะเห็นหน้าตาโปรแกรมดังนี้

ดาวน์โหลดซอร์สโค้ด (Source code) โปรแกรมแปลงพิกัดภูมิศาสตร์ (Geographic Calc)

เพื่อการลดระยะเวลาการเรียนรู้สำหรับคนที่เพิ่งจะมาศึกษาการใช้งานเครื่องมือ SDK ผมจะขอแสดงโครงการที่ผมทำไว้แล้ว ไปที่หน้าดาวน์โหลด (Download) มองหาซอร์สโค้ด (Source code) โปรแกรมสำหรับเครื่องคิดเลข Casio fx-9860G II SD จะได้ไฟล์ zip ชื่อ “UTM-Geo.zip” ทำการแตกไฟล์ zip จะเห็นไฟล์ดังนี้

ที่ผมวงสีแดงไว้คือโค้ดที่ได้จาก ไลบรารีที่ผมดาวน์โหลดมาใช้จาก githubพัฒนาโดย Howard Butler ส่วนที่ผมวงสีฟ้าไว้คือโค๊ดของผมเอง แตกไฟล์ zip ไปไว้ที่โฟลเดอร์สำหรับผมเองอยู่ที่ไดรว์ D:\FX9860GIISD

เปิดโครงการโปรแกรมแปลงพิกัดภูมิศาสตร์

ใช้เมนู “Project” > “Open…” เปิดไฟล์ชื่อ “UTMGeo.g1w”  ที่ช่อง panel  ด้านซ้ายสุดของเครื่องมือพัฒนาจะเห็นเป็นชื่อไฟล์ที่ผมได้ add มาไว้ จะสังเกตเห็นชื่อไฟล์บางตัวมี นามสกุล extension *.h, *.c ที่เป็นปกติของไฟล header และซอร์สโค้ดของภาษาซีและ *.hpp, *.cpp ของภาษาซีพลัสพลัส ซึ่งไฟล์ extension ที่เราตั้งชื่อไว้ถ้ามีโค้ดภาษาซีพลัสพลัส จะต้องใช้ extension เป็น hpp และ cpp

แก้ไขโครงการ

ใช้เมนู “Project” > “Edit…” เพื่อแก้ไขชื่อโครงการ หรือเพิ่มลดไฟล์ header และ source file

แก้ไขไอคอนสำหรับปรากฎที่หน้า “MAIN MENU” ของเครื่องคิดเลข ไอคอนขนาดกว้าง 39 pixel และสูงขนาด 19 pixel ฟอร์แม็ตเป็น bitmap แบบขาวดำ 1 bit ผมออกแบบในโปรแกรม Paint.net จัดเก็บเป็นไฟล์ bitmap (bmp) แต่ไม่สามารถจัดเก็บเป็นไฟล์ขาวดำแบบ 1 bit ได้ต้องไปเปิดต่อใน Gimp แล้วเลือกเมนู Image>Mode>Indexed ตรง Color map เลือก Use black and white (1 bit) palette จากนั้น save จึงจะได้ไฟล์รูปที่มีฟอร์แม็ต bitmap แบบ 1 bit ได้ ทั้งสองโปรแกรมนี้ฟรี

ตรง Edit icon เวลาจะคลิกต้องระวังเพราะถ้าเราออกแบบไอคอนมาแล้ว จะโดนทับด้วยรูปไอคอนดีฟอลท์ทันที ผมไม่ใช้เลยเพราะพลาดหลายทีแล้ว

การจัดการโค้ด C++

ถ้ามีโค้ดซีพลัสพลัสมาผสมด้วย ที่ด้านบนสุดไฟล์จะต้องกำหนดดังนี้

#ifdef __cplusplus
  extern "C" {
#endif

ด้านล่างสุดปิดท้ายด้วย

  #ifdef __cplusplus
}
  #endif

โค้ดจัดการการป้อนข้อมูล

การป้อนข้อมูลของงานสำรวจในเครื่องคิดเลขส่วนใหญ่จะเป็นเลขทศนิยมเช่นระยะทางหรือค่าพิกัด แต่ถ้าเป็นมุมทีแยกองศา ลิปดาและฟิลิปดา ปกติในเครื่องคิดเลขเช่น  fx-4500, fx-5800P จะมีคีย์ให้กดสะดวก แต่เครื่อง  fx-9860G II SD กลับเอาไปไว้ลึกมากต้องกดหลายครั้งจากคีย์ “OPTN” แต่ที่ผมช็อคคือใน SDK กลับไม่มีฟังก์ชันให้เรียกใช้งานได้เลย จะต้องเขียนฟังก์ชันขึ้นมาเองทั้งหมด ผมอาศัยไปอ่านตามฟอรั่มที่มีคนแฮ็คไว้ พบว่าสามารถเรียกใช้ฟังก์ชัน EditExpress ที่ทาง Casio ไม่ได้เปิดเผยเอกสารไว้ (สังเกตว่าใช้เป็น function pointer อ่านรายละเอียดการใช้งานได้ที่ลิ๊งค์นี้)

#define SCA 0xD201D002
#define SCB 0x422B0009
#define SCE 0x80010070

const unsigned int sc08DB[] = {SCA, SCB, SCE, 0x08DB};
typedef int(*sc_EE)(int, short, int, char*, char*, short, char*, int );
#define EditExpression (*(sc_EE)sc08DB)

วิธีใช้งานก็ประมาณนี้

#define MAXEDITBUFFER 21
//
void AddIn_main(int isAppli, unsigned short OptionNum){
int key;
char vBCD[24];
unsigned char sBCD[MAXEDITBUFFER];

memset( vBCD, 0, sizeof(vBCD));
memset( sBCD, 0, sizeof(sBCD));
key = EditExpression(0, KEY_CTRL_RIGHT, 1, vBCD, (char*)sBCD, MAXEDITBUFFER - 1, "Input:", 0x04);

locate(1, 4);
PrintLine(sBCD, 21);

GetKey(&key);
return 1;
}

ข้อมูลที่ผู้ใช้ป้อนจะกลับมาที่ข้อมูลสตริง sBCD ผมพบว่าเรียกใช้ EditExpression มี  mode ให้ป้อนเลือกว่าจะป้อนข้อมูลเป็น double ไหม ซึ่งตอนป้อนข้อมูลจะรับแค่ตัวเลข แต่ในโหมดตามตัวอย่างด้านบน (พารามิเตอร์สุดท้าย 0x04) นั้นจะรับค่าทั่วๆไปทั้งตัวอักษรผสมกับตัวเลข แต่ผมสังเกตว่าเวลาเราป้อนข้อมูลแล้วกดคีย์ “EXE” จะมีการประมวลผล expression ด้วย ปัญหาที่ผมพบคือถ้าป้อนสตริงค่าพิกัดแบบ MGRS เช่น “18SVK8588822509” กลับ error ผมเลยต้องเขียนฟังก์ชันขึ้นมาเองคือ inputMGRSString() โดยเฉพาะ

การป้อนข้อมูลมุม

เนื่องจากเครื่องมือพัฒนา SDK ไม่สนับสนุนการป้อนมุมแบบใช้งานปกติ ผมเลยกำหนดว่ามุมองศา ลิปดาและฟิลิปดาให้คั่นด้วยเครื่องหมายลบ (-) เช่นแลตติจูด (Latitude) จะใช้ตัวอักษร “N” หรือ “S” มากำกับว่าอยู่ในซึกโลกเหนือหรือซีกโลกใต้ คั่นด้วยเส้นศูนย์สูตร ส่วนมุมลองจิจูด (Longitude) แบ่งเป็นซีกโลกตะวันออก (“E” ปิดท้าย) และซีกโลกตะวันตก (“W” ปิดท้าย

ตัวอย่าง แลตติจูด 45-5-32.525N ลองจิจูด 98-45-38.587W

โค้ดอ่านเขียนข้อมูลเข้าตัวแปร  Alpha

ส่วนใหญ่เวลาเราป้อนข้อมูลเข้าไปในการคำนวณงาน เราต้องการให้โปรแกรมจดจำค่านั้นไว้ ดังนั้นโปรแกรมต้องมีการจัดเก็บเอาไว้แล้วเรียกมาใช้ทีหลังเมื่อเรียกโปรแกรมมาใช้งานอีกที เพราะไม่ต้องป้อนบ่อย ถ้ายังใช้ค่าเดิมแค่กดคีย์ “EXE” ผ่านได้เลย และก็เหมือนเดิมครับ SDK ไม่ได้เปิดเผยฟังก์ชันนี้ไว้ทั้งที่สำคัญมาก ผมไปค้นหาตามฟอรั่มพบว่าการจัดเก็บข้อมูลเข้าตัวแปรตัวอักษร A-Z ใช้โครงสร้างข้อมูลเฉพาะ อ่านได้ตามลิ๊งค์นี้ การแปลงข้อมูลสามารถใช้ class TBCD (โค๊ดเดิมมีบั๊กผมแก้ไปนิดหน่อย)

#define SCA 0xD201D002
#define SCB 0x422B0009
#define SCE 0x80010070

const unsigned int sc04E0[] = {SCA, SCB, SCE, 0x04E0};
const unsigned int sc04DF[] = {SCA, SCB, SCE, 0x04DF};

typedef void(*sc_agd)(char, TBCDvalue*);
typedef void(*sc_asd)(char, TBCDvalue*);

#define Alpha_GetData (*(sc_agd)sc04DF)
#define Alpha_SetData (*(sc_asd)sc04E0)

void GetAlphaDoubleData(char alpha, double *dval);
void SetAlphaDoubleData(char alpha, double val);

typedef struct{
  unsigned char hnibble:4;
  unsigned char lnibble:4;
} TBCDbyte;

typedef struct{
  unsigned short exponent:12;
  unsigned short mantissa0:4;
  TBCDbyte mantissa[7];
  char flags;
  short info;
} TBCDvalue;

typedef struct{
  int exponent;
  int sign;
  int unknown; 
  char mantissa[15];
} TBCDInternal;

//Implement of class TBCD please see utilities.cpp
class TBCD{
  public:
        TBCDvalue*PValue();
        int Get( TBCDvalue&value );
        int Set( TBCDvalue&value );
        int Set( double&value );
        int Get( double&value );
        int SetError( int error );
        int GetError();
        void Swap();
  protected:
  private:
        TBCDvalue FValue[2];
};

void GetAlphaDoubleData(char alpha, double *dval){
  TBCD *bcd;
  TBCDvalue *bval;
  int i, ii;

  bval = (TBCDvalue *)malloc(sizeof(TBCDvalue));
  Alpha_GetData(alpha, bval);
  bcd = new TBCD;
  i = bcd->Set(*bval);
  ii = bcd->Get(*dval);
  delete bcd;
  free(bval);
}

void SetAlphaDoubleData(char alpha, double dval){
  TBCD *bcd;
  TBCDvalue bval;
  int i, ii;

  bcd = new TBCD;
  i = bcd->Set(dval);
  ii = bcd->Get(bval);
  Alpha_SetData(alpha, &bval);
  delete bcd;
}
วิธีการใช้งานก็ง่ายๆ

      SetAlphaDoubleData('B', 123.456); //เอาค่า 123.456 เข้าเก็บที่ตัวแปรอักษร "B"
      GetAlphaDoubleData('B', &x); //ดึงค่าที่เก็บในตัวอักษร "B" ออกมาเข้าตัวแปร x
      Locate(1, 4);
      sprintf((char*) str, (char*) "Value = %.3lf", x); 
      Print((unsigned char*)str); //Value = 123.456 

วิธีการใช้งานก็ง่ายๆ

      SetAlphaDoubleData('B', 123.456); //เอาค่า 123.456 เข้าเก็บที่ตัวแปรอักษร "B"
      GetAlphaDoubleData('B', &x); //ดึงค่าที่เก็บในตัวอักษร "B" ออกมาเข้าตัวแปร x
      Locate(1, 4);
      sprintf((char*) str, (char*) "Value = %.3lf", x); 
      Print((unsigned char*)str); //Value = 123.456  

โค้ดสำหรับเมนูหลัก

เป็นการเพิ่มทางเลือกให้ผู้ใช้ ข้อดีการสร้างเมนูคือสามารถรวมงานคำนวณที่มีลักษณะคล้ายๆกันมาอยู่โปรแกรมเดียวกันได้ อาจบางทีอาจจะใช้ไลบรารีร่วมกันดูตัวอย่างด้านล่าง

โค้ดก็ง่ายๆดังนี้

      memset(s, '-', 21);
      Bdisp_AllClr_DDVRAM(); 
      locate(0, 1);
      Print((unsigned char *)"Geographic Calc");
      locate(0, 2);
      PrintLine((unsigned char*)s, 21);
      locate(0, 3);
      PrintLine("[1]:UTM to Geo", 21);
      locate(0, 4);
      PrintLine("[2]:Geo to UTM", 21);
      locate(1, 5);
      PrintLine("[3]:MGRS to Geo", 21);
      locate(0, 6);
      PrintLine("[4]:Geo to MGRS", 21);
      locate(1, 8);
      PrintLine("Select 1,2,3 or 4", 21);
      while (!((key1 >= 0x31) && (key1 <= 0x34)){ 
        GetKey(&key1);

จะมีลูป while ดักการกดคีย์อีก loop ถ้าพบว่ากดคีย์เลข “1” (character code = 0x31) ถึงเลข “4” (Character code = 0x34) เงื่อนไขจริงจะออกจาก loop เข้าเงื่อนไข if (มีหลายชั้นใช้ case แทนได้)

โค้ดงานคำนวณหลัก

ถ้าผู้ใช้กดคีย์ “1” จะเป็นการคำนวณค่าพิกัดจาก UTM ไปยัง Geographic

    if (key1 == 0x31) { //UTM to Geo
      Bdisp_AllClr_DDVRAM(); 
      locate(0, 1);
      Print((unsigned char *)"UTM to Geo");
      locate(0, 2);
      PrintLine((unsigned char*)s, 21);

      GetAlphaDoubleData('A', A);//ดึงค่าข้อมูลเดิมจากหน่วยความจำตัวอักษร "A" 
      sprintf((char*)sBCD, (char*) "%.3lf", *A); //เตรียมรูปแบบข้อมูลทศนิยมสามตำแหน่งไว้ใน sBCD
      //เรียกฟังก์ชันป้อนข้อมูล โดยส่ง sBCD ไปให้
      key2 = EditExpression(0, KEY_CTRL_RIGHT, 3, vBCD, (char*)sBCD, MAXEDITBUFFER - 1, "N? : ", 0x04);
      y = atof((char*)sBCD); //แปลงข้อมูลที่ป้อนมาเป็นตัวเลขทศนิยม
      SetAlphaDoubleData('A', y);//เก็บค่าที่ป้อนไว้ในหน่วยความจำตัวอักษร "A"

      GetAlphaDoubleData('B', B);
      sprintf((char*)sBCD, (char*) "%.3lf", *B); 
      key2 = EditExpression(0, KEY_CTRL_RIGHT, 4, vBCD, (char*)sBCD, MAXEDITBUFFER - 1, "E? : ", 0x04);
      x = atof((char*)sBCD);
      SetAlphaDoubleData('B', x);
     
      GetAlphaDoubleData('Z', Z);
      sprintf((char*)sBCD, (char*) "%.3lf", *Z); 
      key2 = EditExpression(0, KEY_CTRL_RIGHT, 5, vBCD, (char*)sBCD, MAXEDITBUFFER - 1, "UTM Zone No.? : ", 0x04);
      if (strchr((char*)sBCD, 0x87) != NULL) {
        removechar((char*)sBCD, 0x87);
        hemi = 'S';
      } else if(strchr((char*)sBCD, 0x2D) != NULL) {
        removechar((char*)sBCD, 0x2D);
        hemi = 'S';
      } else
        hemi = 'N';

      zn = atoi((char*)sBCD);
      SetAlphaDoubleData('Z', zn);
      //เรียกไลบรารีแปลงพิกัดที่ประกาศใน utm.c
      err = Convert_UTM_To_Geodetic(zn, hemi, x, y, &lat, &lng);
      if (!err){
	      Lat = lat * RAD2DEG; 
	      Lng = lng * RAD2DEG;
              //แยกทศนิยมจัดรูปแบบที่ที่องศา ลิปดาและฟิลิปดาคั่นด้วยเครื่องหมายลบ
	      slat = degreetodms(fabs(Lat), NUMDECIMAL, 0x2D);
	      if(Lat >= 0)
	        sprintf(str, "Lat= %s N", slat);
	      else
	        sprintf(str, "Lat= %s S", slat);
	      locate(1, 6);
	      Print((unsigned char*)str); 
	      locate(1, 7);
	      slong = degreetodms(fabs(Lng), NUMDECIMAL, 0x2D);
	      if (Lng >= 0)
	        sprintf(str, "Lon= %s E", slong);
	      else
	        sprintf(str, "Lon= %s W", slong);
	      Print((unsigned char*)str);      
	      free(slat);
	      free(slong);
       }

ลองดูโค้ดแปลงพิกัดจากค่าพิกัดฉากยูทีเอ็มไปค่าพิกัดภูมิศาสตร์ โดยใช้ไลบรารี

err = Convert_UTM_To_Geodetic(zn, hemi, x, y, &lat, &lng);

zn คือโซนยูทีเอ็ม hemi คือซีกโลก x และ y คือค่าพิกัดฉากยูทีเอ็ม ส่วนค่าที่คำนวณแล้วจะส่งกลับมาที่ตัวแปร lat, lng ส่วนการแปลงพิกัดอย่างอื่นก็ลองดูได้ตามโค้ด

คอมไพล์และบิวด์ (compile & build)

ทดสอบโดยเมนู “Project” > “Rebuild all” ถ้าเมนูนี้ไม่ขึ้นให้คลิก “Project” > “Reload” ก่อน จากทำการรันโปรแกรมโดย “Run” > “Run” จะเห็นอีมูเลเตอร์ “Display” เลื่อนกดคีย์ ไปที่ไอคอนโปรแกรม จากอีมูเลเตอร์ “Keyboard” แล้วกดคีย์ “EXE”

จะเห็นโปรแกรม “Geographic Cacl” ขึ้นเมนูมา

การใช้งานโปรแกรมนี้พร้อมตัวอย่างไปดูที่ลิ๊งค์นี้ได้

ซอร์สโค้ดหลัก

ท้ายสุดผมเอาซอร์สโค้ดของไฟล์ “main.cpp” มาลงให้ดูเต็มๆ จะเห็นว่าไม่มีอะไรสลับซับซ้อนมาก ง่ายๆครับ

#ifdef __cplusplus
  extern "C" {
#endif

#include "fxlib.h"
#include "string.h"
#include "stdlib.h"
#include "stdio.h"
#include "math.h"
#include "mgrs.h"
#include "tranmerc.h"
#include "utm.h"
#include "utilities.hpp"


#define MAXEDITBUFFER 21
#define NUMDECIMAL 5


void removechar(char* s, const char toremove)
{
  while(s=strchr(s, toremove))   
    memmove(s, s+1, 1+strlen(s+1));
}

int InputMGRSString(int x, int y, unsigned char*prompt, unsigned char*buffer, int bufferSize ){
   unsigned int key, edit_key, return_key = 0;
   int pos, len, len2;
   Cursor_SetFlashMode(1);   // set cursor visibility on
   pos = strlen((char*)buffer);// initially set the cursor to the end of the string

   locate(x, y);
   Print(prompt);
   len2 = strlen((char*)prompt);
   while (!return_key){
      locate(x + len2, y);
      PrintLine(buffer, 22-x);
      locate (x + pos + len2, y);
      GetKey( &key );
      edit_key = 0;
      if ((key >= 0x30 ) && (key <= 0x39)){
         edit_key = key;
      } else if ((key >= 0x41) && (key <= 0x5A)){
         edit_key = key;
      } else{
         switch (key){
            case KEY_CTRL_DEL :
               if ( pos > 0 ) pos--;
               len = strlen( (char*)buffer );   // get the current length of the string
               memmove( buffer+pos, buffer+pos+1, len-pos);   // shift the memory: XXYDYYY -> XXXYYY
               break;
            case KEY_CTRL_RIGHT :
               len = strlen( (char*)buffer );   // get the current length of the string
               if ( pos < len ) pos++;
               break;
            case KEY_CTRL_LEFT :
               if ( pos > 0 ) pos--;
               break;
            case KEY_CTRL_UP :
            case KEY_CTRL_DOWN :
               return_key = key;
               break;
            case KEY_CTRL_EXE :
            case KEY_CTRL_EXIT :
               return_key = key;
               break;
            default :
               break;
         };
      }
      if ( edit_key ){
         if ( pos < bufferSize-1 ){
            buffer[ pos ] = edit_key;
            pos++;
         }
      }
   }
   Cursor_SetFlashMode( 0 );   // set cursor visibility off
   return ( return_key );
}

int AddIn_main(int isAppli, unsigned short OptionNum)
{
  unsigned char buffer[21];
  char str[21], s[21];
  int editresult;
  unsigned int key1, key2;
  double x, y, lat, lng, Lat, Lng;
  char *slat, *slong, *sangle;
  long zn; 
  char hemi;//North hemi is 'N', South hemi is 'S'
  char vBCD[24];
  unsigned char sBCD[MAXEDITBUFFER] = ""; 
  int err;
  unsigned char mgrs[15];
  long precision;
  double *A, *B, *C, *D, *E, *F, *H, *I, *Z;

  A = (double *)malloc(sizeof(double));
  B = (double *)malloc(sizeof(double));
  C = (double *)malloc(sizeof(double));
  D = (double *)malloc(sizeof(double));
  E = (double *)malloc(sizeof(double));
  F = (double *)malloc(sizeof(double));
  H = (double *)malloc(sizeof(double));
  I = (double *)malloc(sizeof(double));
  Z = (double *)malloc(sizeof(double));
  memset(s, '-', 21);
  while(1){
	Bdisp_AllClr_DDVRAM(); 
	locate(0, 1);
	Print((unsigned char *)"Geographic Calc");
	locate(0, 2);
	PrintLine((unsigned char*)s, 21);
	locate(0, 3);
	PrintLine("[1]:UTM to Geo", 21);
	locate(0, 4);
	PrintLine("[2]:Geo to UTM", 21);
	locate(1, 5);
	PrintLine("[3]:MGRS to Geo", 21);
	locate(0, 6);
	PrintLine("[4]:Geo to MGRS", 21);
	locate(1, 8);
	PrintLine("Select 1,2,3 or 4", 21);
    while (!(key1 >= 0x31) && (key1 <= 0x34)){ 
      GetKey(&key1);
    }
    if (key1 == 0x31) { //UTM to Geo
      Bdisp_AllClr_DDVRAM(); 
      locate(0, 1);
      Print((unsigned char *)"UTM to Geo");
      locate(0, 2);
      PrintLine((unsigned char*)s, 21);

      GetAlphaDoubleData('A', A);
      sprintf((char*)sBCD, (char*) "%.3lf", *A); 
      key2 = EditExpression(0, KEY_CTRL_RIGHT, 3, vBCD, (char*)sBCD, MAXEDITBUFFER - 1, "N? : ", 0x04);
      y = atof((char*)sBCD);
      SetAlphaDoubleData('A', y);

      GetAlphaDoubleData('B', B);
      sprintf((char*)sBCD, (char*) "%.3lf", *B); 
      key2 = EditExpression(0, KEY_CTRL_RIGHT, 4, vBCD, (char*)sBCD, MAXEDITBUFFER - 1, "E? : ", 0x04);
      x = atof((char*)sBCD);
      SetAlphaDoubleData('B', x);
     
      GetAlphaDoubleData('Z', Z);
      sprintf((char*)sBCD, (char*) "%.3lf", *Z); 
      key2 = EditExpression(0, KEY_CTRL_RIGHT, 5, vBCD, (char*)sBCD, MAXEDITBUFFER - 1, "UTM Zone No.? : ", 0x04);
      if (strchr((char*)sBCD, 0x87) != NULL) {
        removechar((char*)sBCD, 0x87);
        hemi = 'S';
      } else if(strchr((char*)sBCD, 0x2D) != NULL) {
        removechar((char*)sBCD, 0x2D);
        hemi = 'S';
      } else
        hemi = 'N';

      zn = atoi((char*)sBCD);
      SetAlphaDoubleData('Z', zn);
      //Call function declared in utm.c
      err = Convert_UTM_To_Geodetic(zn, hemi, x, y, &lat, &lng);
      if (!err){
	      Lat = lat * RAD2DEG; 
	      Lng = lng * RAD2DEG;
	      slat = degreetodms(fabs(Lat), NUMDECIMAL, 0x2D);
	      if(Lat >= 0)
	        sprintf(str, "Lat= %s N", slat);
	      else
	        sprintf(str, "Lat= %s S", slat);
	      locate(1, 6);
	      Print((unsigned char*)str); 
	      locate(1, 7);
	      slong = degreetodms(fabs(Lng), NUMDECIMAL, 0x2D);
	      if (Lng >= 0)
	        sprintf(str, "Lon= %s E", slong);
	      else
	        sprintf(str, "Lon= %s W", slong);
	      Print((unsigned char*)str);      
	      free(slat);
	      free(slong);
       }
    } else if (key1 == 0x32) { //Geographic to UTM
      Bdisp_AllClr_DDVRAM(); 
      locate(0, 1);
      Print((unsigned char *)"Geo To UTM");
      locate(0, 2);
      PrintLine((unsigned char*)s, 21);

      GetAlphaDoubleData('H', H);
      sangle = degreetodms(*H, NUMDECIMAL, 0x99); 
      if(*H >= 0)
        sprintf(str, "%sN", sangle);
      else
        sprintf(str, "%sS", sangle);
      key2 = EditExpression(0, KEY_CTRL_RIGHT, 3, vBCD, (char*)str, MAXEDITBUFFER - 1, "Lat?: ", 0x04);
      lat = parsedms((char*)str);
      SetAlphaDoubleData('H', lat);
      free(sangle);

      GetAlphaDoubleData('I', I);
      sangle = degreetodms(*I, NUMDECIMAL, 0x99); 
      if(*I >= 0)
        sprintf(str, "%sE", sangle);
      else
        sprintf(str, "%sW", sangle);
      key2 = EditExpression(0, KEY_CTRL_RIGHT, 4, vBCD, (char*)str, MAXEDITBUFFER - 1, "Lon?: ", 0x04);
      lng = parsedms((char*)str);
      SetAlphaDoubleData('I', lng);
      free(sangle);

      Lat = lat * DEG2RAD;
      slat = degreetodms(fabs(Lat), NUMDECIMAL, 0x2D);
      Lng = lng * DEG2RAD;
      slong = degreetodms(fabs(Lng), NUMDECIMAL, 0x2D);
      //Call function declared in utm.c
      err = Convert_Geodetic_To_UTM(Lat, Lng, &zn, &hemi, &x, &y);
      if (!err) {
	      sprintf(str, "North= %11.3lf", y);
	      locate(1, 5);
	      Print((unsigned char*)str);      
	      sprintf(str, "East= %10.3lf", x);
	      locate(1, 6);
	      Print((unsigned char*)str);      
	      sprintf(str, "UTM Zone No= %d%c", zn, hemi);
	      locate(1, 7);
	      Print((unsigned char*)str); 
      }
      free(slat);
      free(slong);      
    } else if (key1 == 0x33) { //MGRS to Geo
      Bdisp_AllClr_DDVRAM(); 
      locate(0, 1);
      Print((unsigned char *)"MGRS to Geo");
      locate(0, 2);
      PrintLine((unsigned char*)s, 21);

      memset(mgrs, 0, 16);
      editresult = InputMGRSString( 1, 3, "MGRS?", buffer, sizeof(buffer) );
      if (editresult) {
        memcpy(mgrs, buffer, 15);
        //Call function that declared in mgrs.c
        err = Convert_MGRS_To_Geodetic((char*)mgrs, &lat, &lng);
        if (!err){
          Lat = lat * 180.0 / PI;
          slat = degreetodms(fabs(Lat), NUMDECIMAL, 0x2D);
          Lng = lng * 180.0 / PI;
          slong = degreetodms(fabs(Lng), NUMDECIMAL, 0x2D);
          if(Lat >= 0)
            sprintf(str, "Lat= %s N", slat);
          else
            sprintf(str, "Lat= %s S", slat);
          locate(1, 4);
          Print((unsigned char*)str);      
          if(Lng >= 0)
            sprintf(str, "Lon= %s E", slong);
          else
            sprintf(str, "Lon= %s W", slong);
          locate(1, 5);
          Print((unsigned char*)str);        
          free(slat);
          free(slong);
        } 
      }
    } else if (key1 == 0x34) { //Geographic to MGRS
      Bdisp_AllClr_DDVRAM(); 
      locate(0, 1);
      Print((unsigned char *)"MGRS to Geo");
      locate(0, 2);
      PrintLine((unsigned char *)s, 21);

      memset(sBCD, 0, MAXEDITBUFFER);
      key2 = EditExpression(0, KEY_CTRL_RIGHT, 3, vBCD, (char*)sBCD, MAXEDITBUFFER - 1, "Lat?:", 0x04);
      lat = parsedms((char*)sBCD);
      lat = lat * DEG2RAD;
      memset(sBCD, 0, MAXEDITBUFFER);
      key2 = EditExpression(0, KEY_CTRL_RIGHT, 4, vBCD, (char*)sBCD, MAXEDITBUFFER - 1, "Lon?:", 0x04);
      lng = parsedms((char*)sBCD);
      lng = lng * DEG2RAD;

      //Call function that declared in mgrs.c
      err = Convert_Geodetic_To_MGRS(lat, lng, 5, (char*)mgrs);
      if (!err){
        sprintf(str, "MGRS= %s", mgrs);
        locate(1, 5);
        Print((unsigned char*)str);        
      }     
    }
    GetKey(&key1);
  } //while (1)
  free(A);
  free(B);
  free(C);
  free(D);
  free(E);
  free(F);
  free(H);
  free(I);
  free(Z);
  return 1;
}

#pragma section _BR_Size
unsigned long BR_Size;
#pragma section

#pragma section _TOP

int InitializeSystem(int isAppli, unsigned short OptionNum)
{
    return INIT_ADDIN_APPLICATION(isAppli, OptionNum);
}

#pragma section

#ifdef __cplusplus
}
#endif

การนำโปรแกรมไปติดตั้งบนเครื่องคิดเลข

เมื่อคอมไพล์และบิวด์แล้วจะได้ไฟล์ G1a ก็สามารถนำไปใช้บนเครื่องคิดเลขได้ วิธีการเอาโปรแกรมไปใส่เครื่องคิดเลขสามารถทำได้หลายวิธี ดูโพสต์เก่าแสดงวิธีการได้ตามลิ๊งค์นี้

สรุป

การพัฒนาโปรแกรมสำหรับเครื่องคิดเลขไม่มีอะไรยากเย็นมากนัก มีความรู้ภาษาซีขั้นพื้นฐานแบบผมก็ทำได้ เพียงแต่ถ้าต้องการคำนวณอะไรซับซ้อนอาจจะต้องหาไลบรารีที่ท่านอื่นได้พัฒนาเขียนไว้ แต่เครื่องมือพัฒนานี้มีข้อจำกัดอยู่บ้างเช่นคอมไพเลอร์นี้สนับสนุน c standard library ได้ไม่ครบทุกอย่าง ตัวอย่างเช่นฟังก์ชั่น strtok() ที่ตัดสตริงออกตามตัวคั่นก็ไม่สนับสนุน ดังนั้นการเลือกไลบรารีที่คนอื่นได้ทำไว้ก็ต้องพิจารณาส่วนนี้ด้วย ส่วนการรับข้อมูลจากผู้ใช้ สำหรับผมแล้วที่มีอยู่ตอนนี้เกือบจะเพียงพอ เพราะงานสำรวจก็จะมีแค่ป้อนมุม ระยะทาง เป็นหลัก ติดตามกันต่อไปครับ

10 thoughts on “แนะนำโปรแกรมมิ่งภาษาซีบนเครื่องคิดเลข Casio fx-9860G II SD ด้วยเครื่องมือพัฒนา SDK ของ Casio”

  1. อยากได้ เครื่องมือพัฒนา Software Development Kit (SDK) ครับ

    1. สวัสดีครับ ผมจะส่งลิ๊งค์ให้ทางอีเมล์ครับ

  2. ขอบคุณสำหรับการส่งต่อ ความรู้ ผมว่า เครื่อง HP Prime น่าใช้ พัฒนาง่ายกว่ามากครับ พึ่งได้เครื่องมาจึงลองพัฒนาโปรแกรมดู เริ่มจากโปรแกรมออกแบบแผ่นพื้นคอนกรึตเสริมเหล็ก เป็นภาษาไทยในเครื่อง HP Prime.

    1. สวัสดีครับ ไม่ทราบว่าใช้ภาษาอะไรครับ ภาษาซีไหม หรือคล้ายๆกับ Casio Basic (ในตระกูลเครื่องคาสิโอ้) ที่ผมโฟกัสไปภาษาซี อาจจะดูยากแต่เขียนโปรแกรมยากๆได้เทียบกับคอมพิวเตอร์ได้ แต่ยังไงก็ไม่เป็นไรถ้าเขียนโปรแกรมแล้วได้ผลลัพธ์ตามที่ต้องการก็ถือว่าบรรลุจุดประสงค์ และใช้ภาษาไทยได้ใน HP Prime ถือว่าไม่ธรรมดาครับ

  3. สวัสดีครับ ผมขอสอบถามวิธีเอาข้อมูลต่างๆจากคอมลงเครื่องคิดเลขหน่อยครับ รุ่น casio algebra fx 2.0 plus พอมีวิธีไหมครับ

    1. สวัสดีครับ ลองไปอ่านๆดูแล้ว ในลิ๊งค์ทางการของคาสิโอ้ระบุว่า มีสายขายเป็น optional แต่ไปดูตามฟอรั่มต่างๆเห็นคนทำสายเคเบิ้ลขึ้นมาใช้เอง ไม่ทราบเพราะอะไรหรือสายที่ขายโดยคาสิโอ้นั้นแพงหรือหายากแล้ว การทำสายเองก็ไม่ใช่เรื่องง่ายถ้าไม่มีความรู้ด้านอิเลคโทรนิคส์ พวกทำสายขาย ถ้าสนใจลองตามลิ๊งค์นี้ไปดูครับ

  4. สนใจเครื่องมือครับ รบกวนส่งให้ผมหน่อยนะครับ ขอบคุณครับ

    1. ไปที่หน้า ลิ๊งค์ นี้ แล้วดาวน์โหลดตามรูปที่ผมแนบมา ต้องลงทะเบียน register ก่อนนะครับ
      ดาวน์โหลดตามนี้

Leave a Reply

Your email address will not be published. Required fields are marked *