├── .gitignore ├── CasioSerial.cpp ├── CasioSerial.h ├── LICENSE.txt ├── README.md ├── examples ├── CasioMega │ ├── CasioMega.ino │ └── README.md └── CasioUno │ └── CasioUno.ino └── library.properties /.gitignore: -------------------------------------------------------------------------------- 1 | notes.txt 2 | -------------------------------------------------------------------------------- /CasioSerial.cpp: -------------------------------------------------------------------------------- 1 | // (C) 2018 by nsg 2 | #include "Arduino.h" 3 | // check communication for 0x15 4 | // when get it, immediately send back 0x13 5 | // and wait for further info 6 | // 7 | // If receive :REQ request, check if there is anything for that name and send it 8 | // Do not send back 0x06 until there is something (calc will wait indefinitely 9 | // at this point) 10 | // Once there is data for this name, send 0x06, and proceed with sending :VAL, 11 | // :0101 and :END 12 | // 13 | // If receive :VAL request, acknoledge it immediately and save it to the 14 | // mailbox for that name for other parts of the firmware to consume. 15 | // Alternative strategy, if it is considered an atomic command, hold on on 16 | // sending final 0x06 until the "command" is executed (e.g. rover has actually 17 | // driven specified distance or temperature reached specified value) 18 | 19 | /* 20 | inbox -- contains values for received names. "action" names require "fresh" flag to be cleared before 21 | outbox -- contains values for 22 | "new" flag: 23 | inbox 24 | do not acknoledge until "fresh" flag is clear 25 | outbox 26 | do not acknoledge 27 | 28 | Immediate box does not wait, nonimmediate waits forever. 29 | It may have sense to have something in between: timed-out box which emits a 30 | value after a timeout even if it is not fresh. 31 | */ 32 | 33 | #include "CasioSerial.h" 34 | 35 | HardwareSerial *casio_serial=NULL; 36 | CasioMailBox *casio_inboxes=NULL; 37 | CasioMailBox *casio_outboxes=NULL; 38 | 39 | #define CASIO_DEBUG 40 | 41 | void cccp_null_hook(char name) 42 | { 43 | } 44 | 45 | void (*casio_receive_hook)(char)=&cccp_null_hook; 46 | 47 | 48 | // find a mailbox for a name or create one 49 | 50 | #ifdef CASIO_STATIC_MAILBOX 51 | // run in setup() to populate links in mailboxes defined literally 52 | void fill_static_links(CasioMailBox *head, int count) 53 | { 54 | for(int i=1; iname == name ) return p; 70 | p=p->next; 71 | } 72 | #ifdef CASIO_DEBUG 73 | Serial.print("Not found mailbox for "); 74 | Serial.println(name); 75 | #endif 76 | #ifdef CASIO_STATIC_MAILBOX 77 | fakebox.name=name; 78 | fakebox.next=NULL; 79 | fakebox.fresh=true; 80 | fakebox.immediate=true; 81 | fakebox.value=12345.67; 82 | return &fakebox; 83 | #else 84 | if( !create_if_not_exists ) return NULL; 85 | p=(CasioMailBox*)malloc(sizeof(CasioMailBox)); 86 | if( NULL==p ) return NULL; // cannot allocate 87 | // create new and update head 88 | p->name=name; 89 | p->next=*head; 90 | *head=p; 91 | p->fresh=false; 92 | p->immediate=true; // signals are immediate by default to keep calc from waiting; 93 | #endif 94 | } 95 | 96 | /* 97 | * --Receive() --Send() 98 | * Casio MCU Casio MCU 99 | * $15 $13 $15 $13 100 | * :REQ $06 :VAL $06 101 | * $06 :VAL :0101 $06 102 | * $06 :0101 :END 103 | * $06 :END 104 | */ 105 | 106 | /* 107 | 108 | IDLE -> (get $15) -> ALERT -> (send $13) -> GETHEADER -> 109 | (:END) -> IDLE 110 | (:VAL) -> SEND_ACK1 -> (send $06) -> SEND_WAIT_DATA -> 111 | (get :0101 with data) -> SEND_EXECUTE_DATA -> (send $06) -> GETHEADER 112 | (:REQ) -> RECEIVE_WAIT_DATA-> -> RECEIVE_ACK1-> 113 | (send $06)->RECEIVE_CLIENT_WAIT1->(receive $06)->RECEIVE_VAL->(send :VAL)->RECEIVE_CLIENT_WAIT2->(receive $06)-> 114 | 115 | States are named after casio basic operators SEND and RECEIVE. 116 | So, SEND's states are about arduino receiving data and possibly acting on it 117 | and RECEIVE's states are about arduino obtaining sensor or process data and 118 | sending it to calc. 119 | 120 | */ 121 | 122 | 123 | enum CCCP_STATE { 124 | CCCP_ALERT, 125 | CCCP_NACK, 126 | CCCP_GETHEADER0, 127 | CCCP_GETHEADER, 128 | CCCP_SEND_ACK1, 129 | CCCP_SEND_WAITDATA0, 130 | CCCP_SEND_WAITDATA, 131 | CCCP_SEND_EXECUTEDATA, 132 | CCCP_RECEIVE_WAITDATA, 133 | CCCP_RECEIVE_ACK1, 134 | CCCP_RECEIVE_CLIENTWAIT1, 135 | CCCP_RECEIVE_VAL0, 136 | CCCP_RECEIVE_VAL, 137 | CCCP_RECEIVE_CLIENTWAIT2, 138 | CCCP_RECEIVE_0101_0, 139 | CCCP_RECEIVE_0101, 140 | CCCP_RECEIVE_CLIENTWAIT3, 141 | CCCP_RECEIVE_END0, 142 | CCCP_RECEIVE_END, 143 | CCCP_IDLE 144 | }; 145 | 146 | 147 | int cccp_state=CCCP_IDLE; 148 | 149 | /* Mailbox for currently progressing request */ 150 | CasioMailBox *cccp_actionbox; 151 | 152 | /* Protocol communication symbols */ 153 | #define CASIO_ATT 0x15 154 | #define CASIO_READY 0x13 // Code A "Ok" 155 | #define CASIO_ACK 0x06 // Code B "Ok" 156 | #define CASIO_RETRY 0x05 157 | #define CASIO_ERROR 0x22 158 | 159 | // offsets in a buffer 160 | #define CASIO_B_NAME 11 161 | #define CASIO_B_COMPLEX 27 // 'C' or 'R' 162 | #define CASIO_B_RANK 5 // VM/PC/LT/MT 163 | #define CASIO_B_VARTAG 19 // 'Variable' tag 164 | #define CASIO_B_USED1 8 165 | #define CASIO_B_USED2 10 166 | #define CASIO_B_CHECKSUM 49 167 | #define CASIO_B_SIZE 50 168 | 169 | 170 | #define CASIO_B_RE 5 // location of the real part 171 | #define CASIO_B_IM 15 // location of the complex part 172 | #define CASIO_R_SIZE 16 // real value buffer size 173 | #define CASIO_C_SIZE 26 // complex value buffer size 174 | 175 | // value to use for non-existent mailboxes 176 | // TODO: something better than this 177 | #define CASIO_DEFAULT_VALUE 987.654 178 | 179 | byte cccp_buffer[CASIO_B_SIZE]; 180 | int cccp_buffer_index; 181 | int cccp_buffer_size; 182 | char cccp_varname; // recently requested name; needed if there is no mailbox for it 183 | 184 | byte casio_checksum(byte *buffer, int size) 185 | { 186 | byte chk=0; 187 | int i; 188 | for(i=0; i>4); 212 | } 213 | 214 | byte bcd(int v){ 215 | if( v<0 ) return 0; 216 | if( v>99 ) return 99; 217 | return (v%10) | ((v/10)<<4); 218 | } 219 | 220 | // "sign" byte bitfields 221 | #define CASIO_EXPPOS 1 222 | #define CASIO_NEG 0x50 223 | 224 | #define CASIO_IM 0x80 225 | 226 | #ifdef CASIO_DEBUG 227 | void serial_dump(byte *buffer, size_t size){ 228 | char buf[10]; 229 | for(int i=0; i99) { 293 | // Cannot happen on Arduino, but might happen on platform with 8 byte 294 | // double. 295 | OVERFLOW: 296 | // CAUTION: overflow 297 | exp=99; 298 | memset(&buffer[1],0x99,7); 299 | buffer[0]=0x09; 300 | } 301 | } 302 | buffer[8]=sign; 303 | buffer[9]=bcd(exp); 304 | #ifdef CASIO_DEBUG_V 305 | Serial.print("Buffer="); 306 | serial_dump(buffer,10); 307 | Serial.print("\n"); 308 | #endif 309 | return buffer; 310 | } 311 | 312 | /* 313 | * --- Real: 314 | * :0101 315 | * dddd dddd s e 316 | * Σ 317 | * --- Complex: 318 | * :0101 319 | * dddd dddd s e 320 | * dddd dddd s e 321 | * Σ 322 | * 323 | * CASIO 10 byte buffer: 324 | * 0 1 2 3 4 5 6 7 8 9 325 | * 0d dd dd dd dd dd dd dd ss ee 326 | */ 327 | 328 | // Convert 10-byte buffer representing CASIO floating point number into Arduino 329 | // floating point number. 330 | double casio_number_parse(byte *buffer) 331 | { 332 | int i; 333 | double r=0.0; 334 | int exp,sign; 335 | #ifdef CASIO_DEBUG_V 336 | Serial.print("Parse"); 337 | serial_dump(buffer,10); 338 | #endif 339 | for( i=0; i<8; ++i ) r=r*100.0+fbcd(buffer[i]); 340 | r=r/1e14; 341 | sign=buffer[8]; 342 | exp=fbcd(buffer[9]); 343 | #ifdef CASIO_DEBUG_V 344 | Serial.print("mantissa="); 345 | Serial.println(r); 346 | Serial.print("exp="); 347 | Serial.println(exp); 348 | Serial.print("sign="); 349 | Serial.println(sign); 350 | #endif 351 | if( 0==(sign & CASIO_EXPPOS) ) exp=exp-100; 352 | if( 0!=(sign & CASIO_NEG ) ) r=-r; 353 | return r*pow(10.0,exp); 354 | } 355 | 356 | const char TAG_VM[] PROGMEM = {'V','M'}; // named variable 357 | const char TAG_PC[] PROGMEM = {'P','C'}; // picture 358 | const char TAG_LT[] PROGMEM = {'L','T'}; // list 359 | const char TAG_MT[] PROGMEM = {'M','T'}; // matrix 360 | 361 | const char TAG_Variable[] PROGMEM = {'V','a','r','i','a','b','l','e'}; 362 | /* Lists have "List n" or "List nn" 363 | * Matrices have "Mat N" at this offset 364 | */ 365 | 366 | 367 | int cccp_analyze_header(byte *buffer) 368 | { 369 | // :END -> idle 370 | // :REQ -> CCCP_RECEIVE_WAITDATA 371 | // :VAL -> CCCP_SEND_ACK1 372 | // setup cccp_actionbox 373 | #ifdef CASIO_DEBUG_V 374 | Serial.print("Received header "); 375 | serial_dump(buffer, CASIO_B_SIZE); 376 | #endif 377 | // TODO: request resend instead of NACK 378 | if( buffer[CASIO_B_CHECKSUM]!=casio_checksum(buffer, CASIO_B_SIZE-1) ) goto REJECT_HEADER; 379 | if( 0==memcmp_P(&buffer[0],HEADER_END,5) ) return CCCP_IDLE; 380 | if( 0==memcmp_P(&buffer[0],HEADER_VAL,5) ) { 381 | // :VAL, AKA SEND() request 382 | // :0101 packet with actual data possibly to follow 383 | if( 0!=memcmp_P(&buffer[CASIO_B_RANK],TAG_VM,2) ) goto REJECT_HEADER; 384 | cccp_actionbox=get_inbox(buffer[CASIO_B_NAME]); 385 | cccp_varname=buffer[CASIO_B_NAME]; 386 | if( buffer[CASIO_B_USED1]!=buffer[CASIO_B_USED2] ) goto REJECT_HEADER; 387 | switch(buffer[CASIO_B_USED1]) { 388 | case 0: 389 | // variable has not been assigned yet, no :0101 to follow 390 | cccp_buffer_size=0; 391 | break; 392 | case 1: 393 | // size of expected :0101 packet 394 | cccp_buffer_size=buffer[CASIO_B_COMPLEX]=='C'?26:16; 395 | break; 396 | default: 397 | goto REJECT_HEADER; 398 | } 399 | return CCCP_SEND_ACK1; 400 | } 401 | if( 0==memcmp_P(&buffer[0],HEADER_REQ,5) ) { 402 | // :REQ, AKA RECEIVE() request 403 | if( 0!=memcmp_P(&buffer[CASIO_B_RANK],TAG_VM,2) ) goto REJECT_HEADER; 404 | cccp_actionbox=get_outbox(buffer[CASIO_B_NAME]); 405 | cccp_varname=buffer[CASIO_B_NAME]; 406 | if( NULL!=casio_receive_hook ) (*casio_receive_hook)(cccp_varname); 407 | return CCCP_RECEIVE_WAITDATA; 408 | } 409 | REJECT_HEADER: 410 | #ifdef CASIO_DEBUG 411 | Serial.print("!Reject header "); 412 | serial_dump(buffer, CASIO_B_SIZE); 413 | #endif 414 | return CCCP_NACK; 415 | } 416 | 417 | int cccp_analyze_senddata(byte* buffer, size_t buffer_size) 418 | { 419 | // validate that data in buffer is :0101 of acceptable characteristics 420 | // decode value 421 | // populate actionbox 422 | // move on to CCCP_SEND_EXECUTEDATA 423 | #ifdef CASIO_DEBUG_V 424 | Serial.print("senddata: "); 425 | serial_dump(buffer,buffer_size); 426 | #endif 427 | 428 | if( buffer[buffer_size-1]!=casio_checksum(buffer, buffer_size-1) ) goto REJECT; 429 | if( 0!=memcmp_P(&buffer[0],HEADER_0101,5) ) goto REJECT; 430 | if( NULL!=cccp_actionbox ) { 431 | cccp_actionbox->value=casio_number_parse(&buffer[CASIO_B_RE]); 432 | // TODO? deal with imaginary part if present 433 | cccp_actionbox->fresh=true; 434 | } 435 | return CCCP_SEND_EXECUTEDATA; 436 | REJECT: 437 | #ifdef CASIO_DEBUG 438 | Serial.print("!Reject senddata "); 439 | serial_dump(buffer, buffer_size); 440 | #endif 441 | return CCCP_NACK; 442 | } 443 | 444 | 445 | long int last_change; 446 | int last_state; 447 | 448 | void casio_poll() 449 | { 450 | if( NULL==casio_serial ) return; 451 | int rd; 452 | if( cccp_state!=last_state ){ 453 | last_change=millis(); 454 | } else { 455 | if( cccp_state!=CCCP_IDLE && millis()>last_change+6000 ) { 456 | Serial.print("Stuck in state "); 457 | Serial.println(cccp_state); 458 | last_change=millis(); 459 | } 460 | } 461 | last_state=cccp_state; 462 | while(true) switch(cccp_state){ 463 | case CCCP_IDLE: 464 | if( 0==casio_serial->available() ) return; 465 | if( casio_serial->read()==CASIO_ATT ) cccp_state=CCCP_ALERT; 466 | break; 467 | case CCCP_ALERT: 468 | if( 0==casio_serial->availableForWrite() ) return; 469 | casio_serial->write(CASIO_READY); 470 | 471 | case CCCP_GETHEADER0: 472 | cccp_buffer_index=0; 473 | cccp_buffer_size=CASIO_B_SIZE; 474 | cccp_state=CCCP_GETHEADER; 475 | case CCCP_GETHEADER: 476 | // TODO: timeout 477 | if( 0==casio_serial->available() ) return; 478 | cccp_buffer[cccp_buffer_index++]=casio_serial->read(); 479 | if( cccp_buffer_index>=cccp_buffer_size ) 480 | cccp_state=cccp_analyze_header(cccp_buffer); 481 | break; 482 | 483 | case CCCP_SEND_ACK1: 484 | if( 0==casio_serial->availableForWrite() ) return; 485 | casio_serial->write(CASIO_ACK); 486 | if( cccp_buffer_size==0 ) { 487 | // not expecting :0101, jump right to :END 488 | cccp_state=CCCP_GETHEADER0; 489 | break; 490 | } 491 | 492 | case CCCP_SEND_WAITDATA0: 493 | cccp_buffer_index=0; 494 | // cccp_buffer_size should be set by cccp_analyze_header; 495 | cccp_state=CCCP_SEND_WAITDATA; 496 | case CCCP_SEND_WAITDATA: 497 | // expecting :0101 498 | if( 0==casio_serial->available() ) return; 499 | cccp_buffer[cccp_buffer_index++]=casio_serial->read(); 500 | if( cccp_buffer_index>=cccp_buffer_size ) 501 | cccp_state=cccp_analyze_senddata(cccp_buffer,cccp_buffer_size); 502 | break; 503 | 504 | case CCCP_SEND_EXECUTEDATA: 505 | // CAUTION: make sure that non-immediate mailboxes are properly acted 506 | // upon by firmware and the freshness bit is cleared when that happens. 507 | // TODO? timed-out actionboxes 508 | if( NULL!=cccp_actionbox 509 | && !cccp_actionbox->immediate 510 | && cccp_actionbox->fresh ) 511 | return; // keep calc on hold while data is executing 512 | // CAUTION: it is possible that once the freshness conditions are 513 | // satisfied, the out queue is full and while we are waiting for it to 514 | // clear, freshness conditions may become not satisfied. It should not 515 | // happen: immediate flag should not change and once fresness flag is 516 | // clear it should not be set until the next SEND packet arrives. But it 517 | // is logically possible. 518 | if( 0==casio_serial->availableForWrite() ) return; 519 | casio_serial->write(CASIO_ACK); 520 | cccp_state=CCCP_GETHEADER0; 521 | // Expect to get :END packet which leads to IDLE, but if there is some 522 | // other valid :VAL or :REQ packet, it might as well be acted upon. 523 | break; 524 | 525 | case CCCP_RECEIVE_WAITDATA: 526 | // wait for the data to become ready, client may be on hold 527 | if( NULL!=cccp_actionbox 528 | && !cccp_actionbox->immediate 529 | && !cccp_actionbox->fresh ) 530 | return; 531 | cccp_state=CCCP_RECEIVE_ACK1; 532 | case CCCP_RECEIVE_ACK1: 533 | if( 0==casio_serial->availableForWrite() ) return; 534 | casio_serial->write(CASIO_ACK); 535 | cccp_state=CCCP_RECEIVE_CLIENTWAIT1; 536 | case CCCP_RECEIVE_CLIENTWAIT1: 537 | if( 0==casio_serial->available() ) return; 538 | if( casio_serial->read()!=CASIO_ACK ) { 539 | cccp_state=CCCP_IDLE; 540 | break; 541 | } 542 | 543 | case CCCP_RECEIVE_VAL0: 544 | // populate :VAL buffer 545 | cccp_buffer_size=CASIO_B_SIZE; 546 | memcpy_P(cccp_buffer,PACKET_END,CASIO_B_SIZE); 547 | memcpy_P(cccp_buffer,HEADER_VAL,5); 548 | memcpy_P(cccp_buffer+CASIO_B_RANK,TAG_VM,2); 549 | cccp_buffer[7]=0; 550 | cccp_buffer[CASIO_B_USED1]=1; 551 | cccp_buffer[9]=0; 552 | cccp_buffer[CASIO_B_USED2]=1; 553 | cccp_buffer[CASIO_B_NAME]=cccp_varname; 554 | memcpy_P(cccp_buffer+CASIO_B_VARTAG,TAG_Variable,8); 555 | cccp_buffer[CASIO_B_COMPLEX]='R'; 556 | cccp_buffer[CASIO_B_COMPLEX+1]=0x0a; 557 | cccp_buffer[CASIO_B_CHECKSUM]=casio_checksum(cccp_buffer,cccp_buffer_size-1); 558 | 559 | cccp_state=CCCP_RECEIVE_VAL; 560 | cccp_buffer_index=0; 561 | #ifdef CASIO_DEBUG_V 562 | Serial.print("ready to transmit :VAL for "); 563 | Serial.println(cccp_varname); 564 | serial_dump(cccp_buffer,cccp_buffer_size); 565 | #endif 566 | case CCCP_RECEIVE_VAL: 567 | // transmit :VAL buffer 568 | if( 0==casio_serial->availableForWrite() ) return; 569 | if( cccp_buffer_indexwrite(cccp_buffer[cccp_buffer_index++]); 571 | break; 572 | } 573 | cccp_state=CCCP_RECEIVE_CLIENTWAIT2; 574 | case CCCP_RECEIVE_CLIENTWAIT2: 575 | if( 0==casio_serial->available() ) return; 576 | rd=casio_serial->read(); 577 | if( rd==CASIO_RETRY ) { 578 | // resend already populated buffer 579 | cccp_buffer_index=0; 580 | cccp_state=CCCP_RECEIVE_VAL; 581 | break; 582 | } else if( rd!=CASIO_ACK ) { 583 | cccp_state=CCCP_IDLE; 584 | break; 585 | } 586 | case CCCP_RECEIVE_0101_0: 587 | cccp_buffer_size=CASIO_R_SIZE; // TODO: ...or _C_SIZE 588 | memset(cccp_buffer,0,cccp_buffer_size); 589 | memcpy_P(cccp_buffer,HEADER_0101,5); 590 | // TODO? send back "unused" response instead of a default value 591 | casio_number_format(&cccp_buffer[CASIO_B_RE] 592 | ,cccp_actionbox==NULL?CASIO_DEFAULT_VALUE:cccp_actionbox->value); 593 | cccp_buffer[cccp_buffer_size-1]=casio_checksum(cccp_buffer,cccp_buffer_size-1); 594 | cccp_buffer_index=0; 595 | cccp_state=CCCP_RECEIVE_0101; 596 | case CCCP_RECEIVE_0101: 597 | // transmit 0101 buffer 598 | if( 0==casio_serial->availableForWrite() ) return; 599 | if( cccp_buffer_indexwrite(cccp_buffer[cccp_buffer_index++]); 601 | break; 602 | } 603 | cccp_state=CCCP_RECEIVE_CLIENTWAIT3; 604 | 605 | case CCCP_RECEIVE_CLIENTWAIT3: 606 | if( 0==casio_serial->available() ) return; 607 | rd=casio_serial->read(); 608 | if( rd==CASIO_RETRY ) { 609 | // TODO? repopulate buffer with fresher data 610 | cccp_buffer_index=0; 611 | cccp_state=CCCP_RECEIVE_0101; 612 | break; 613 | } else if( rd!=CASIO_ACK ) { 614 | cccp_state=CCCP_IDLE; 615 | break; 616 | } 617 | // only clear freshness if received confirmation 618 | if( cccp_actionbox!=NULL ) cccp_actionbox->fresh=false; 619 | 620 | case CCCP_RECEIVE_END0: 621 | // populate :END buffer 622 | cccp_state=CCCP_RECEIVE_END; 623 | cccp_buffer_size=CASIO_B_SIZE; 624 | memcpy_P(cccp_buffer, PACKET_END, CASIO_B_SIZE); 625 | cccp_buffer_index=0; 626 | case CCCP_RECEIVE_END: 627 | // transmit :END buffer 628 | if( 0==casio_serial->availableForWrite() ) return; 629 | if( cccp_buffer_indexwrite(cccp_buffer[cccp_buffer_index++]); 631 | break; 632 | } 633 | cccp_state=CCCP_IDLE; 634 | break; 635 | // send value requested value 636 | case CCCP_NACK: 637 | if( 0==casio_serial->availableForWrite() ) return; 638 | #ifdef CASIO_DEBUG 639 | Serial.println("Sending NACK"); 640 | #endif 641 | casio_serial->write(CASIO_ERROR); 642 | cccp_state=CCCP_IDLE; 643 | break; 644 | 645 | default: 646 | cccp_state=CCCP_IDLE; 647 | } 648 | } 649 | 650 | 651 | 652 | 653 | -------------------------------------------------------------------------------- /CasioSerial.h: -------------------------------------------------------------------------------- 1 | /* (C) 2018 by nsg 2 | * Host implementation for Casio Basic SEND/RECEIVE serial interface operators. 3 | * 4 | * Incoming variables (sent by SEND()) are delivered to inboxes, 5 | * Values for requested variables (requsted by RECEIVE()) are sought in 6 | * outboxes. 7 | * Mailboxes have freshness flags (.fresh). They have slightly different 8 | * meaning for inboxes and for outboxes. 9 | * 10 | * Whenever inbox receives a value (sent by a calculator with a SEND() 11 | * operator), the .fresh flag is set. Firmware should clear this flag after it 12 | * reads this value and acts on it. Normal inboxes will keep calculator on hold 13 | * until this flag is cleared. Inbox marked immediate (.immediate flag) do not 14 | * do this and proceed with the protocol immediately after the value is 15 | * received. 16 | * 17 | * If a calculator requests a value with a RECEIVE() operator, .immediate 18 | * outbox will provide a value right away, whether it is fresh or not. Outbox 19 | * without .immediate flag will keep calculator on hold until .fresh flag goes 20 | * on. After the value is sent to the calc, the .fresh flag is cleared again. 21 | * The poll function calls a hook (*casio_receive_hook) immediately after upon 22 | * request. The firmware may use it to initiate a process of obtaining a value 23 | * for that name. 24 | * 25 | */ 26 | 27 | /* Physical serial interface which is attached to CASIO communication 28 | * It can be &Serial on arduinos without extra serial interfaces, but extreme 29 | * care should be taken to ensure that nothing else, especially debug messages 30 | * are transmitted over it. 31 | */ 32 | extern HardwareSerial *casio_serial; 33 | 34 | // variable names are 35 | // 'A'..'Z' 36 | // 0xcd = r 37 | // 0xce = θ 38 | 39 | #define CASIO_LOWR 0xcd 40 | #define CASIO_THETA 0xce 41 | 42 | 43 | #define CASIO_STATIC_MAILBOX 44 | 45 | typedef struct casiomailbox { 46 | char name; 47 | // "freshness" indicator 48 | // inbox: indicates new data from calc; expected to be cleared after firmware 49 | // acts upon it 50 | // outbox: set by firmware whenever it updates it 51 | bool fresh; 52 | bool immediate; // ignore freshness, use the data as is and immediately 53 | double value; 54 | struct casiomailbox *next; // linked list 55 | #ifndef CASIO_STATIC_MAILBOX 56 | #endif 57 | } CasioMailBox; 58 | 59 | // Use these macros to allocate memory statically for mailboxes. 60 | #ifdef CASIO_STATIC_MAILBOX 61 | #define IMMEDIATE(n) {name:n, fresh:false, immediate:true, value:0.0} 62 | #define MAILBOX(n,imm) {name:n, fresh:false, immediate:imm, value:0.0} 63 | #endif 64 | 65 | // use the POST_TO_BOX macro to post directly or to define 66 | // post macros for specific mailboxes, e.g. 67 | // #define BOX_LEFT(v) POST_TO_BOX(my_outbox[0],v) 68 | 69 | #define POST_TO_BOX(BOX,V) do{BOX.value=V;BOX.fresh=true;}while(0) 70 | 71 | extern CasioMailBox *casio_inboxes; 72 | extern CasioMailBox *casio_outboxes; 73 | 74 | CasioMailBox *get_mailbox(CasioMailBox **head, char name, bool create_if_not_exists=false); 75 | 76 | inline CasioMailBox *get_outbox(char name) { return get_mailbox(&casio_outboxes, name); } 77 | inline CasioMailBox *get_inbox(char name) { return get_mailbox(&casio_inboxes, name); } 78 | 79 | // get_mailbox relies on linked list fields to find appropriate box. 80 | // If mailboxes are allocated in static arrays, their link fields need to be 81 | // initialized with fill_static_links(), e.g.: 82 | // fill_static_links(&my_inbox[0], sizeof(my_inbox)); 83 | // fill_static_links(&my_outbox[0], sizeof(my_outbox)); 84 | void fill_static_links(CasioMailBox *head, int count); 85 | 86 | // This procedure implements serial protocols for SEND() and RECEIVE() 87 | // operators. It populates inboxes with the incoming values and uses values in 88 | // outboxes to respond to variable requests. 89 | // It should be called periodically, e.g. in the loop() 90 | void casio_poll(); 91 | 92 | // This hook is called each time casio_poll gets a RECEIVE() request from a 93 | // calculator. It gets the name of the requested variable as its first 94 | // parameter. 95 | extern void (*casio_receive_hook)(char); 96 | 97 | /* 98 | * --Receive() --Send() 99 | * Casio MCU Casio MCU 100 | * $15 $13 $15 $13 101 | * :REQ $06 :VAL $06 102 | * $06 :VAL :0101 $06 <- only present when "in use" byte==1 103 | * $06 :0101 :END 104 | * $06 :END 105 | */ 106 | 107 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Host for Casio Basic SEND/RECEIVE operators. 2 | 3 | This library enables use of Casio graphing calculators with serial interface 4 | capabilities as a simple user interface/data storage device for Arduino 5 | controllers. It implements Casio Basic serial communication protocol which 6 | allows the use of Casio Basic to define logic of data presentation and storage. 7 | 8 | ## Why Casio 9 | 10 | Casio calculators use simple TTL UART serial for communication. Older models 11 | are 5V and can be connected directly to 5V Arduino Boards. Newer models (e.g. 12 | fx-CG20 Prizm) are 3.3 volts. They feature same exact connector and people 13 | would not think twice about connecting them with their earlier siblings. I hope 14 | Casio engineers did the right thing and made newer models 5V tolerant. Having 15 | said that, in my design I use logic level converter to connect 3.3V Prizm to 5V 16 | Mega, just to be on a safe side. 17 | 18 | Physical connector is a standard 2.5 mm ("microphone") jack. 19 | 20 | Casio graphing calculators are easy to come by and *pre-owned* older models are 21 | often very inexpensive. 22 | 23 | Casio Basic, while far from being state-of-the-art IDE, still provides enough 24 | flexibility to design simple interfaces. Minor changes and adjustments can be 25 | made on the spot and without PC. 26 | 27 | ## What is implemented 28 | 29 | Casio Basic features 2 statements, SEND(*var*) and RECEIVE(*var*). *Var* can be 30 | a named scalar variable, numbered list, named matrix, or numbered picture. 31 | 32 | This library implements operator variants that work with named scalar variables 33 | only. 34 | 35 | From the calculator's point of view, both `SEND()` and `RECEIVE()` are requests to 36 | a host (server) to save and retrieve given named value respectively. 37 | 38 | Arduino acts as such host. It waits for either type of request and when any of 39 | them arrives, it processes it accordingly. It takes a value sent by `SEND()` 40 | and acts on it, or generates a value for a name requested by `RECEIVE()` and 41 | sends it back to a calculator. 42 | 43 | Typically, `SEND()` can be used to activate actuators and `RECEIVE()` can be 44 | used to retrieve sensor values. 45 | 46 | ## Mailboxes 47 | 48 | The library interfaces with the rest of controller software via *mailboxes*. 49 | Mailboxes are data structures that hold a name (1 character "alpha-variable" of 50 | Casio Basic), a value and some flags. There are 2 flavors of mailboxes: inboxes 51 | and outboxes. Each of those can be *immediate* or not. 52 | 53 | Outboxes hold data ready to be sent in response to `RECEIVE()` request by a 54 | calculator. Inboxes is where the data sent by a calculator via `SEND()` go. 55 | 56 | Inboxes and Outboxes are organized in linked lists with a `.next` field. The 57 | pointer to the head of inboxes list is called `casio_inboxes`, the pointer to 58 | the head of outboxes is called `casio_outboxes` and must be assigned in the 59 | `setup()`. 60 | 61 | It is possible to have both inbox and outbox for the same name. They are 62 | completly independent. 63 | 64 | ### Inbox 65 | 66 | Incoming variables (sent by SEND()) are delivered to inboxes. 67 | 68 | When library gets a valid SEND() request, it looks for a mailbox associated 69 | with then name in the request. If there is such a mailbox, the library writes a 70 | value to its `.value` field and sets its `.fresh` flag. 71 | 72 | If the inbox is *immediate* (its `.immediate` flag is `true`), the library 73 | proceeds with the protocol, confirms the reception of the value to the 74 | calculator, the `SEND()` operator successfully completes and the Casio Basic 75 | program proceeds in normal fashion. It is up to the rest of the controller 76 | software to act upon or ignore the value in `.value` field of a mailbox. 77 | 78 | If the inbox is *not* *immediate* (`.immediate` flag is `false`), the library 79 | will **not** confirm the reception of the value until the rest of control 80 | software processes it and clears the `.fresh` flag of the mailbox. The 81 | calculator will wait for the confirmation indefinitely, so it is really 82 | important that control software properly communicates when it is finished 83 | processing that request. 84 | 85 | This flavor can be used to implement actions that take time to complete, like 86 | driving certain distance. For example if `25→D:SEND(D)` initiates a movement, 87 | the Basic program will be stuck on `SEND()` until control 88 | software indicates that it moved the target 25 units. It may take a few seconds 89 | or a few hours -- `SEND()` will wait patiently for confirmation. 90 | 91 | ### Outbox 92 | 93 | Values for variables requsted by `RECEIVE()` are sought in 94 | outboxes. 95 | 96 | Control software in its main loop may check sensors and populate appropriate mailboxes' `.value` field with the sensor value. 97 | 98 | When a calculator requests a value with a `RECEIVE()` operator, *immediate* 99 | outbox will provide a value right away, whether its `.fresh` flag set or not. 100 | Outbox without `.immediate` flag will keep calculator on hold until `.fresh` 101 | flag goes `true`. After that, the value is sent to the calculator, and the 102 | `.fresh` flag is cleared again. 103 | 104 | The poll function calls a hook (`*casio_receive_hook`) immediately after 105 | initial `RECEIVE()` handshake. 106 | The control software may use it to initiate a process of obtaining a value 107 | for that name. 108 | 109 | 110 | 111 | 112 | ## API 113 | 114 | ### `HardwareSerial *casio_serial` 115 | 116 | Physical serial interface which is attached to Casio communication. 117 | It can be `&Serial` on arduinos without extra serial interfaces, but extreme 118 | care should be taken to ensure that nothing else, especially debug messages 119 | are transmitted over it. 120 | 121 | For boards with multiple uarts, it can be `&Serial1` ... `&Serial3`, which is 122 | preferrable. 123 | 124 | This global must be assigned in `setup()` 125 | 126 | ### `CasioMailBox` 127 | 128 | Data structure that holds mailbox information 129 | 130 | ```c 131 | typedef struct casiomailbox { 132 | char name; /* 1-character name of Casio Basic variable */ 133 | bool immediate; /* immediate flag */ 134 | bool fresh; /* freshness indicator */ 135 | double value; 136 | struct casiomailbox *next; /* link field for linked list */ 137 | } CasioMailBox; 138 | ``` 139 | 140 | The easiest strategy is to have fixed number of inboxes and outboxes, 141 | permanently assigned to certain process variables and commands, periodically 142 | updated/checked by control sofware. 143 | 144 | ### `CasioMailBox *casio_inboxes` 145 | ### `CasioMailBox *casio_outboxes` 146 | 147 | Pointers to the heads of linked lists containing inboxes and outboxes 148 | respectively. Must be assigned in `setup()`. 149 | 150 | 151 | ### `fill_static_links(&my_inbox[0], my_inbox_count);` 152 | Internal function 153 | `get_mailbox()` relies on linked list fields to find appropriate box. 154 | 155 | If mailboxes are allocated in static arrays, their link fields need to be 156 | initialized with `fill_static_links(...)`, e.g.: 157 | ```c 158 | CasioMailBox my_inbox[]={ 159 | {name:'A',immediate:true}, 160 | ... 161 | }; 162 | ... 163 | fill_static_links(&my_inbox[0], sizeof(my_inbox)/sizeof(CasioMailBox)); 164 | fill_static_links(&my_outbox[0], sizeof(my_outbox)/sizeof(CasioMailBox)); 165 | ``` 166 | The function simply sets `.next` of each mailbox to the next element in the 167 | array, last one points to `NULL`. 168 | 169 | ### `void casio_poll(void);` 170 | 171 | This function implements serial protocols for `SEND()` and `RECEIVE()` 172 | operators. It listens for incoming bytes, interprets them, populates inboxes 173 | with the incoming values and uses values in 174 | outboxes to respond to variable requests. 175 | 176 | I took extra care to make it as non-blocking as possible. If it has to wait 177 | for the calculator's response, it returns. When called next time, it resumes 178 | protocol from the previous point once the bytes from the calculator have 179 | arrived. 180 | 181 | It should be called periodically, e.g. in the `loop()` with reasonable 182 | frequency. In the early phases of protocol calculator is sensitive to timeout, 183 | so it is important to send back prompt initial response. 184 | 185 | If there are parts of the control software that may block execution for a 186 | while, it is worth considering calling this function from serial interrupt. 187 | 188 | 189 | ### `void (*casio_receive_hook)(char);` 190 | 191 | This hook is called each time `casio_poll()` sees a `RECEIVE()` request from a 192 | calculator. It gets the name of the requested variable as its first 193 | parameter. 194 | 195 | ## Physical connection 196 | 197 | ### Using Standard Casio Crossover cable 198 | 199 | Attach *ring* terminal of a female 2.5 mm socket to RX pin (possibly through 200 | logic level voltage converter) and *tip* terminal to the TX pin. Attach 201 | *base* to ground. 202 | 203 | Connect this socket too your calculator using standard Casio serial cable 204 | ("crossover" cable). 205 | 206 | ### Custom built cable 207 | 208 | Make a cable with a male 2.5 mm TRS plug on one end and whatever is convenient 209 | for your design on the other end. Attach *ring* terminal of a male 2.5 mm plug 210 | to TX pin, *tip* terminal to RX pin and *base* to ground. Note that connections 211 | of *ring* and *tip* are switched compared to wiring of a female connector. 212 | 213 | ## Examples 214 | 215 | Please see the examples. 216 | 217 | ## Copyright 218 | 219 | Copyright (C) 2018 nsg21. All rights reserved. 220 | 221 | ## Acknoledgements 222 | 223 | I would like to thank Michael Fenton for describing Casio host protocol in 224 | "Connecting the PICAXE 08M and PICAXE 18X to the Casio 9750G Plus graphics 225 | calculator" 226 | 227 | ## License 228 | 229 | Apache-2.0 230 | see `LICENSE.txt` 231 | 232 | -------------------------------------------------------------------------------- /examples/CasioMega/CasioMega.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include "CasioSerial.h" 3 | 4 | CasioMailBox my_inbox[]={ 5 | MAILBOX('W',false) // request by a calc: number of milliseconds to delay 6 | #define BOX_WAIT my_inbox[0] 7 | ,MAILBOX('L',true) // brightness of built-in LED (0..255) 8 | #define BOX_LED my_inbox[1] // sent index for subsequent read 9 | ,MAILBOX('I',true) // select analog input to read with 'V' 10 | #define BOX_INDEX my_inbox[2] // sent index for subsequent read 11 | }; 12 | 13 | CasioMailBox my_outbox[]={ 14 | IMMEDIATE('T') // millisecond timer 15 | #define BOX_MILLIS(v) POST_TO_BOX(my_outbox[0],v) 16 | ,IMMEDIATE('U') // microsecond timer 17 | #define BOX_MICROS(v) POST_TO_BOX(my_outbox[1],v) 18 | ,IMMEDIATE('V') // sensor value read from analog input 'I' 19 | #define BOX_VALUE(v) POST_TO_BOX(my_outbox[2],v) 20 | }; 21 | 22 | void hook_example(char name) { 23 | Serial.print("[hook] :REQ for "); 24 | Serial.println(name); 25 | } 26 | 27 | void setup() { 28 | // debug and monitoring 29 | Serial.begin(9600); 30 | 31 | // Setup communication interface. 32 | // Arduino Mega's Serial3 is communicating over pins 14 and 15. 33 | casio_serial=&Serial3; 34 | casio_serial->begin(9600); 35 | Serial.println("Listening on Serial3" ); 36 | 37 | // Setup mailboxes 38 | fill_static_links(&my_inbox[0], sizeof(my_inbox)/sizeof(CasioMailBox)); 39 | fill_static_links(&my_outbox[0], sizeof(my_outbox)/sizeof(CasioMailBox)); 40 | casio_inboxes=&my_inbox[0]; 41 | casio_outboxes=&my_outbox[0]; 42 | casio_receive_hook=&hook_example; 43 | 44 | // Other initializations 45 | pinMode(LED_BUILTIN,OUTPUT); 46 | 47 | Serial.println("Finished setup" ); 48 | } 49 | 50 | long lastbeat=0; 51 | 52 | bool wait_in_progress=false; 53 | long wait_started=0; 54 | 55 | int analog_pin=0; 56 | 57 | void loop() { 58 | long timer=millis(); 59 | // Heartbeat indicator 60 | if(timer>lastbeat+10000) { 61 | lastbeat=timer; 62 | Serial.print("Timer="); 63 | Serial.println(timer); 64 | Serial.print("Mailbox Timer="); 65 | Serial.println(my_outbox[0].value); 66 | } 67 | 68 | casio_poll(); 69 | 70 | /* Try on your calculator: 71 | * RECEIVE(T) 72 | * then inspect T -- should show current timer. 73 | * Look for hook trace in serial monitor window. 74 | */ 75 | BOX_MILLIS(millis()); 76 | 77 | /* Try on your calculator: 78 | * RECEIVE(U) 79 | * then inspect U -- should show current timer in microseconds. 80 | */ 81 | BOX_MICROS(micros()); 82 | 83 | /* Try on your calculator: 84 | * 1->I:SEND(I) 85 | * This will setup Arduino to read analog value from analog pin 1. 86 | */ 87 | if( BOX_INDEX.fresh ) { 88 | Serial.print("From now on 'V' provides value of analog index "); 89 | Serial.println(BOX_INDEX.value); 90 | analog_pin=(int)BOX_INDEX.value; 91 | BOX_INDEX.fresh=false; 92 | } 93 | 94 | /* 95 | * Connect potentiometer to analog pin 1, 5 volts and ground. Set it to some 96 | * position. On your calculator execute 97 | * RECEIVE(V):V 98 | * and note the value. Then change the potentiometer position and execute 99 | * RECEIVE(V):V 100 | * once more. Note how the value has changed. 101 | */ 102 | if( analog_pin>0 ) { 103 | BOX_VALUE(analogRead(analog_pin)); 104 | } 105 | 106 | /* Try on your calculator: 107 | * 3000->W 108 | * SEND(W) 109 | * -- Calculator should remain on hold during SEND() for ~3000 ms before 110 | * continuing. Also watch serial monitor window for feedback. 111 | * -- Note that mailbox for W is marked as not .immediate. This enables 112 | * polling routine to keep calculator on hold while data is being 113 | * processed/executed. 114 | */ 115 | if( BOX_WAIT.fresh ) { 116 | if( !wait_in_progress ) { 117 | Serial.print("Received WAIT request for "); 118 | Serial.print(BOX_WAIT.value); 119 | wait_in_progress=true; 120 | wait_started=millis(); 121 | Serial.print(" at "); 122 | Serial.println(wait_started); 123 | Serial.print("Estimated completion at "); 124 | Serial.println(wait_started+(long)BOX_WAIT.value); 125 | } else { 126 | if( millis()>wait_started+(long)BOX_WAIT.value ) { 127 | wait_in_progress=false; 128 | BOX_WAIT.fresh=false; 129 | Serial.print("completed WAIT request at "); 130 | Serial.println(millis()); 131 | } 132 | } 133 | } 134 | 135 | /* Try on your calculator: 136 | * 0→L 137 | * SEND(L) 138 | * -- Built-in LED should go OFF 139 | * 100→L 140 | * SEND(L) 141 | * -- Built-in LED should go full ON 142 | * 5→L 143 | * SEND(L) 144 | * -- Built-in LED should go dim, but still visible. 145 | */ 146 | if( BOX_LED.fresh ) { 147 | analogWrite(LED_BUILTIN,constrain(map((int)BOX_LED.value,0,100,0,255),0,255)); 148 | /* 149 | * Note that this mailbox is marked as .immediate, so technically, there is 150 | * no need to clear .fresh bit upon completion of request, but we still do 151 | * it anyway. 152 | */ 153 | BOX_LED.fresh=false; 154 | } 155 | 156 | /* I have an irrational fear of busy waiting, so ... */ 157 | delay(1); 158 | 159 | } 160 | -------------------------------------------------------------------------------- /examples/CasioMega/README.md: -------------------------------------------------------------------------------- 1 | # Example of communication between Arduino Mega and Casio CFX 9850G programmable calculator 2 | 3 | Any older model programmable Casio calculator with serial port should work. 4 | 5 | **WARNING**: 6 | Make sure that the calculator's serial port is 5v, otherwise you may damage 7 | your calculator. 8 | 9 | ## Hardware setup 10 | 11 | Wire a male 2.5mm barrel plug to your Arduino Mega. 12 | 13 | - *ring* terminal to to pin 14 (Serial3 TX) 14 | - *tip* terminal to pin 15 (Serial3 RX) 15 | - *base* terminal of to pin GND (ground) 16 | 17 | Plug 2.5mm barrel plug in the calculator's communication port. 18 | 19 | Optionally, wire one or more potentiometers or sensors to analog pins. 20 | 21 | ## Calculator commands 22 | 23 | I assume you know your way around Casio operating system and Casio Basic and 24 | give only minimal details. 25 | 26 | In this example we issue commands in calculator's immediate execution mode 27 | (that is, normal "calculator" mode). Make sure you switch to "Linear" display 28 | -- in their infinite wisdom Casio made SEND and RECEIVE commands not available 29 | in "Math" mode. 30 | 31 | Check the Arduino IDE serial console if available, as it provides extra 32 | indication of what is going on. 33 | 34 | ### Timer 35 | 36 | ```Casio Basic 37 | RECEIVE(T):T 38 | ``` 39 | Shows time in milliseconds since Arduino is on. 40 | 41 | ### High resolution timer 42 | 43 | ```Casio Basic 44 | RECEIVE(U):U 45 | ``` 46 | Shows time in microseconds since Arduino is on. 47 | 48 | ### LED control 49 | 50 | ```Casio Basic 51 | 0→L 52 | SEND(L) 53 | ``` 54 | 55 | Built-in LED should goes OFF 56 | 57 | ```Casio Basic 58 | 100→L 59 | SEND(L) 60 | ``` 61 | 62 | Built-in LED should goes full ON 63 | 64 | ```Casio Basic 65 | 5→L 66 | SEND(L) 67 | ``` 68 | 69 | Built-in LED should goes dim, but still visible. 70 | 71 | ### Delay 72 | 73 | ```Casio Basic 74 | 6000→W:SEND(W) 75 | ``` 76 | 77 | Note that it takes approximately 6 second to execute this command. This is an 78 | example of *non-immediate* mailbox which keeps calculator on hold until 79 | commands completes. 80 | 81 | ### Analog read 82 | 83 | ```Casio Basic 84 | 1→I:SEND(I) 85 | ``` 86 | 87 | Select Analog 1 pin to read value next. 88 | 89 | ```Casio Basic 90 | RECEIVE(V):V 91 | ``` 92 | Request and display the value from analog pin (in this example, Analog 1). 93 | 94 | ## Copyright 95 | 96 | Copyright (C) 2018 nsg21. All rights reserved. 97 | 98 | ## License 99 | 100 | Apache-2.0 101 | see `LICENSE.txt` 102 | 103 | -------------------------------------------------------------------------------- /examples/CasioUno/CasioUno.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include "CasioSerial.h" 3 | 4 | CasioMailBox my_inbox[]={ 5 | MAILBOX('W',false) // request by a calc: number of milliseconds to delay 6 | #define BOX_WAIT my_inbox[0] 7 | ,MAILBOX('L',true) // brightness of built-in LED (0..255) 8 | #define BOX_LED my_inbox[1] // sent index for subsequent read 9 | ,MAILBOX('I',true) // select analog input to read with 'V' 10 | #define BOX_INDEX my_inbox[2] // sent index for subsequent read 11 | }; 12 | 13 | CasioMailBox my_outbox[]={ 14 | IMMEDIATE('T') // millisecond timer 15 | #define BOX_MILLIS(v) POST_TO_BOX(my_outbox[0],v) 16 | ,IMMEDIATE('U') // microsecond timer 17 | #define BOX_MICROS(v) POST_TO_BOX(my_outbox[1],v) 18 | ,IMMEDIATE('V') // sensor value read from analog input 'I' 19 | #define BOX_VALUE(v) POST_TO_BOX(my_outbox[2],v) 20 | }; 21 | 22 | void hook_example(char name) { 23 | } 24 | 25 | void setup() { 26 | // No debug or monitoring via serial port -- port is occupied by Casio 27 | 28 | // Setup communication interface. 29 | casio_serial=&Serial; 30 | casio_serial->begin(9600); 31 | 32 | // Setup mailboxes 33 | fill_static_links(&my_inbox[0], sizeof(my_inbox)/sizeof(CasioMailBox)); 34 | fill_static_links(&my_outbox[0], sizeof(my_outbox)/sizeof(CasioMailBox)); 35 | casio_inboxes=&my_inbox[0]; 36 | casio_outboxes=&my_outbox[0]; 37 | casio_receive_hook=&hook_example; 38 | 39 | // Other initializations 40 | pinMode(LED_BUILTIN,OUTPUT); 41 | 42 | } 43 | 44 | bool wait_in_progress=false; 45 | long wait_started=0; 46 | 47 | int analog_pin=0; 48 | 49 | void loop() { 50 | 51 | casio_poll(); 52 | 53 | /* Try on your calculator: 54 | * RECEIVE(T) 55 | * then inspect T -- should show current timer. 56 | * Look for hook trace in serial monitor window. 57 | */ 58 | BOX_MILLIS(millis()); 59 | 60 | /* Try on your calculator: 61 | * RECEIVE(U) 62 | * then inspect U -- should show current timer in microseconds. 63 | */ 64 | BOX_MICROS(micros()); 65 | 66 | /* Try on your calculator: 67 | * 1->I:SEND(I) 68 | * This will setup Arduino to read analog value from analog pin 1. 69 | */ 70 | if( BOX_INDEX.fresh ) { 71 | analog_pin=(int)BOX_INDEX.value; 72 | BOX_INDEX.fresh=false; 73 | } 74 | 75 | /* 76 | * Connect potentiometer to analog pin 1, 5 volts and ground. Set it to some 77 | * position. On your calculator execute 78 | * RECEIVE(V):V 79 | * and note the value. Then change the potentiometer position and execute 80 | * RECEIVE(V):V 81 | * once more. Note how the value has changed. 82 | */ 83 | if( analog_pin>0 ) { 84 | BOX_VALUE(analogRead(analog_pin)); 85 | } 86 | 87 | /* Try on your calculator: 88 | * 3000->W 89 | * SEND(W) 90 | * -- Calculator should remain on hold during SEND() for ~3000 ms before 91 | * continuing. Also watch serial monitor window for feedback. 92 | * -- Note that mailbox for W is marked as not .immediate. This enables 93 | * polling routine to keep calculator on hold while data is being 94 | * processed/executed. 95 | */ 96 | if( BOX_WAIT.fresh ) { 97 | if( !wait_in_progress ) { 98 | wait_in_progress=true; 99 | wait_started=millis(); 100 | } else { 101 | if( millis()>wait_started+(long)BOX_WAIT.value ) { 102 | wait_in_progress=false; 103 | BOX_WAIT.fresh=false; 104 | } 105 | } 106 | } 107 | 108 | /* Try on your calculator: 109 | * 0→L 110 | * SEND(L) 111 | * -- Built-in LED should go OFF 112 | * 100→L 113 | * SEND(L) 114 | * -- Built-in LED should go full ON 115 | * 5→L 116 | * SEND(L) 117 | * -- Built-in LED should go dim, but still visible. 118 | */ 119 | if( BOX_LED.fresh ) { 120 | analogWrite(LED_BUILTIN,constrain(map((int)BOX_LED.value,0,100,0,255),0,255)); 121 | /* 122 | * Note that this mailbox is marked as .immediate, so technically, there is 123 | * no need to clear .fresh bit upon completion of request, but we still do 124 | * it anyway. 125 | */ 126 | BOX_LED.fresh=false; 127 | } 128 | 129 | /* I have an irrational fear of busy waiting, so ... */ 130 | delay(1); 131 | 132 | } 133 | -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=CasioSerial 2 | version=1.0.0 3 | author=nsg21 4 | maintainer=nsg21 5 | sentence=Casio graphing calculators Basic serial protocol 6 | paragraph=Host implementation for Casio Basic SEND/RECEIVE serial interface operators. 7 | category=Communication 8 | url=https://github.com/nsg21/Arduino-Casio-Serial-library 9 | architectures=* 10 | 11 | --------------------------------------------------------------------------------