PIC16F145xはUSBガジェットを作るには最高だ。価格は安く入手もしやすい。でも問題はファームウェアの書き込みをどうするかだ。ICSPは接続が面倒くさいし、かといってマイクロチップが公開しているUSBブートローダーはプログラム領域の半分も使ってしまうため同じくUSBを使うプログラムなどには対応できない。なんか意味ねーじゃんということでUSBブートローダーを使うのは諦めていたのだが世の中には凄いことをやってのける人がいるものでUSBブートローダーを512ワード以下で作ってしまった猛者を発見してしまった。
74hc595/PIC16F1-USB-Bootloader
6年ほど前に作ったものらしいが本人曰くPICアセンブラもMPLABXも大嫌いということで個人的にはとても好感が持てそうな人のようだ。お友達になってもらいたいかも。(笑)
windowsで試したところ全く動作しなかったが、そもそも当時のmac-osで試験しただけらしいので動かなくても当たり前。さらにDFU対応したのも発見したがドライバーの登録や事前にファイル形式変換が必要だったりして何かと面倒臭いのに加え完全に動作することはなく結局使いものにはならなかった。
majbthrd/PIC16F1-USB-DFU-Bootloader
でも、512ワードのブートローダーがあるとなんとなく幸せになれそうな気がしたのでwindows/linuxでも使えるように改良してみることにした。見たくもない理解したくもなかったPICアセンブラ命令+USBハードウェアを相手に戦ってみたが全く埒が明かない状況が暫し続いた。が、分からないながらもプログラムを見続けているうちに少しずつ光が見えてくるようになるものだ。幾度となく諦めかけたがとうとう動作させることができてしまったのだ。これは奇跡的というかもう嬉しい限り。(/・ω・)/
元のプログラムの大きな問題点と思うのは、次の通り。
1.GNU-gpasmでしかコンパイルできない。mpasmではエラーになる。
2.USBのGET-CONFIG-DESCRIPTOR要求時に不正なデータを返すためOSがUSB認識に失敗する。(ことがある)
3.フラッシュ書き込み時のベリファイに失敗する。(ことがある)
4.アドレス0x0800以上のコードを実行中にHW割り込みが発生すると誤動作する。
5.アプリを書き込んでも実行できない可能性がある。(恐らく問題ないとは思うが)
6.アプリ書き込み後にブートローダー・モード(書き込みモード)に入るにはGPIO(RA3)をGNDに落とす外部スイッチが必要。(仕様)
これらを修正した上で、外部スイッチなしで再プログラムできるように特別なソフトウェア・リセット、或いは、ウォッチドック・リセットでブートローダーモードに入れるようにしてみた。
但し、ウォッチドック・リセットを使う方法については開発段階ではいいのかもしれないが、本番運用中に突然ブートローダー・モードに入られて、だんまりになるのはいかがなものか?と言う気もするのでソース・コード上で設定(WDT_BOOTLOADER_MODE=1)するようにしてみた。開発環境専用のブートローダーとしては便利かも。
細かいところまでは見てないしこれ以上やる気もないのでバグってる可能性は否定できないが、今のところ私の環境では問題なく動作はしている。
ソフトウェアで、ブートローダー・モードに入るためには、割り込みを禁止してからFSR1L=0xFD,FSR0L=0xBEを設定後にreset命令を実行すれば良い。これはソフトウェアリセット時にFSR1L/FSR0Lの値が変更されないことを利用している。ソフトウェアリセット時に値が変更されない他のレジスタを使っても良いだろう。このへんはデータシートを真剣に読まないとわからないことではあるが、安直に人に聞く前に可能な限り自分で精いっぱい調べてみる努力をしてみよう。それができるかどうかがご自身の将来に大きな与える影響を与えることになると思うので...
ちなみに、元ソースではUSB電源設定(セルフパワー/バスパワー&消費電流等)をアプリから変更できるようになってはいたが、512ワードでは厳しいことに加え今後のメンテナンスのことも考えた結果、バスパワー(100mA)固定としコード領域を削減している。
なお、CDC-ACMデバイスなので古いOSでないかぎりOS側に特別なドライバー等の組み込みは必要ない。
【アブリをブートローダー対応にする方法】
1.リンカー設定が必要。
[XC8 Linker commandline option] –codeoffset=0x200
or
[MPLABX] XC8-linker->Additinal options->Codeoffset 0x200
2.ウォッチドッグを使用する場合はソフトウェア制御が必要。
ウォッチドッグは次のコードで制御可能。スリープ中にウォッチドッグを停止したい場合にも制御が必要となることに注意しよう。
WDTCONbits.SWDTEN = 1 or 0; // 1=enable, 0=disable
3.アプリ側のCONFIG設定は無視され、常にブートローダーのCONFIG設定で動作することに注意。
不都合がある場合は、設定を書き換えたブートローダを使うしか方法がない。
【USBブートローダー】
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 |
; vim:noet:sw=8:ts=8:ai:syn=pic ; ; USB 512-Word CDC Bootloader for PIC16(L)F1454/5/9 ; Copyright (c) 2021, Sasapea's Lab. () ; derived from ; USB 512-Word CDC Bootloader for PIC16(L)F1454/5/9 ; Copyright (c) 2015, Matt Sarnoff (msarnoff.org) ; v1.0, February 12, 2015 ; Released under a 3-clause BSD license: see the accompanying LICENSE file. ; ; Bootloader is entered if the MCLR/RA3 pin is grounded at power-up or reset, ; or if there is no application programmed. (The internal pull-up is used, ; no external resistor is necessary.) ; ; To be detected as a valid application, the lower 8 bytes of the first ; instruction word must NOT be 0xFF. ; ; At application start, the device is configured with a 48MHz CPU clock, ; using the internal oscillator and 3x PLL. If a different oscillator ; configuration is required, it must be set by the application. ; ; A serial number between 0 and 65535 should be specified during the build ; by using the gpasm -D argument to set the SERIAL_NUMBER symbol, e.g. ; gpasm -D SERIAL_NUMBER=12345 ; If not specified, it will default to zero. ; A host may not behave correctly if multiple PICs with the same serial number ; are connected simultaneously. ; ; Code notes: ; - Labels that do not begin with an underscore can be called as functions. ; Labels that begin with an underscore are not safe to call, they should only ; be reached via goto. ; ; - FSR0L, FSR0H, FSR1L, and FSR1H are used as temporary registers in several ; places, e.g. as loop counters. They're accessible regardless of the current ; bank, and automatically saved/restored on interrupt. Neato! ; ; - As much stuff as possible is packed into bank 0 of RAM. This includes the ; buffer descriptors, bootloader state, endpoint 0 OUT and IN buffers, ; the endpoint 1 IN buffer (only a single byte is used), and the beginning of ; the 64-byte endpoint 1 OUT buffer. ; ; - Notification endpoint 2 is enabled, but never used. The endpoint 2 IN ; buffer descriptor is left uninitialized. The endpoint 2 OUT buffer ; descriptor is used as 4 bytes of RAM. ; ; - The programming protocol is described in the 'usb16f1prog' script. It is ; very minimal, but does provide checksum verification. Writing the ID words ; (0x8000-8003) is not supported at this time, and writing the configuration ; words is not possible via self-programming. ; With logging enabled, the bootloader will not fit in 512 words. ; Use this only for debugging! ; For more info, see log_macros.inc and log.asm. LOGGING_ENABLED equ 0 WDT_BOOTLOADER_MODE equ 0 radix dec list n=0,st=off include "p16f1454.inc" nolist include "macros.inc" include "bdt.inc" include "usb.inc" include "protocol_constants.inc" include "log_macros.inc" list errorlevel -302 ;;; Configuration if LOGGING_ENABLED WRT_CONFIG equ _WRT_HALF else WRT_CONFIG equ _WRT_BOOT endif __config _CONFIG1, _FOSC_INTOSC & _WDTE_SWDTEN & _PWRTE_ON & _MCLRE_OFF & _CP_OFF & _BOREN_ON & _IESO_OFF & _FCMEN_OFF __config _CONFIG2, WRT_CONFIG & _CPUDIV_NOCLKDIV & _USBLSCLK_48MHz & _PLLMULT_3x & _PLLEN_ENABLED & _STVREN_ON & _BORV_LO & _LVP_OFF ;;; Constants and varaiable addresses SERIAL_NUMBER_DIGIT_CNT equ 4 ifndef SERIAL_NUMBER_DIGIT SERIAL_NUMBER_DIGIT equ 0x0001 ; Why doesnt 'equ' work here? Go figure endif ; I plan to apply for an Openmoko Product ID: the current product ID is temporary. ; If your organization has its own vendor ID/product ID, substitute it here. ; The Openmoko vendor/product ID cannot be used in closed-source/non-open-hardware ; projects: see http://wiki.openmoko.org/wiki/USB_Product_IDs USB_VENDOR_ID equ 0x1D50 USB_PRODUCT_ID equ 0xEEEE ; to be filled in once I obtain a product ID DEVICE_DESC_LEN equ 18 ; device descriptor length CONFIG_DESC_TOTAL_LEN equ 67 ; total length of configuration descriptor and sub-descriptors SERIAL_NUM_DESC_LEN equ 2+(SERIAL_NUMBER_DIGIT_CNT*2) ALL_DESCS_TOTAL_LEN equ DEVICE_DESC_LEN+CONFIG_DESC_TOTAL_LEN+SERIAL_NUM_DESC_LEN EP0_BUF_SIZE equ 8 ; endpoint 0 buffer size EP1_OUT_BUF_SIZE equ 64 ; endpoint 1 OUT (CDC data) buffer size EP1_IN_BUF_SIZE equ 1 ; endpoint 1 IN (CDC data) buffer size (only need 1 byte to return status codes) ; Since we're only using 5 endpoints, use the BDT area for buffers, ; and use the 4 bytes normally occupied by the EP2 OUT buffer descriptor for variables. USB_STATE equ BANKED_EP2OUT+0 EP0_DATA_IN_PTR equ BANKED_EP2OUT+1 ; pointer to descriptor to be sent (low byte only) EP0_DATA_IN_COUNT equ BANKED_EP2OUT+2 ; remaining bytes to be sent APP_POWER_CONFIG equ BANKED_EP2OUT+3 ; application power config byte EP0OUT_BUF equ EP3OUT BANKED_EP0OUT_BUF equ BANKED_EP3OUT ; buffers go immediately after EP2 IN's buffer descriptor EP0IN_BUF equ EP0OUT_BUF+EP0_BUF_SIZE BANKED_EP0IN_BUF equ BANKED_EP0OUT_BUF+EP0_BUF_SIZE ; Use another byte to store the checksum we use to verify writes EXTRA_VARS_LEN equ 1 EXPECTED_CHECKSUM equ BANKED_EP0IN_BUF+EP0_BUF_SIZE ; for saving expected checksum EP1IN_BUF equ EP0IN_BUF+EP0_BUF_SIZE+EXTRA_VARS_LEN BANKED_EP1IN_BUF equ BANKED_EP0IN_BUF+EP0_BUF_SIZE+EXTRA_VARS_LEN EP1OUT_BUF equ EP1IN_BUF+EP1_IN_BUF_SIZE ; only use 1 byte for EP1 IN BANKED_EP1OUT_BUF equ BANKED_EP1IN_BUF+EP1_IN_BUF_SIZE ; High byte of all endpoint buffers. EPBUF_ADRH equ (EP0OUT_BUF>>8) if ((EP0IN_BUF>>8) != (EP0OUT_BUF>>8)) || ((EP1OUT_BUF>>8) != (EP0OUT_BUF>>8)) || ((EP1IN_BUF>>8) != (EP0OUT_BUF>>8)) error "Endpoint buffers must be in the same 256-word region" endif ; Total length of all RAM (variables, buffers, BDT entries) used by the bootloader, USED_RAM_LEN equ EP1OUT_BUF+EP1_OUT_BUF_SIZE-BDT_START if LOGGING_ENABLED BOOTLOADER_SIZE equ 0x1000 else BOOTLOADER_SIZE equ 0x200 endif RESET_VECT equ 0x0000 INTERRUPT_VECT equ 0x0004 DEVICE_DESCRIPTOR equ BOOTLOADER_SIZE-ALL_DESCS_TOTAL_LEN ; Application code locations APP_ENTRY_POINT equ BOOTLOADER_SIZE ;APP_CONFIG equ BOOTLOADER_SIZE+2 APP_INTERRUPT equ BOOTLOADER_SIZE+4 ; USB_STATE bit flags IS_CONTROL_WRITE equ 0 ; current endpoint 0 transaction is a control write ADDRESS_PENDING equ 1 ; need to set address in next IN transaction DEVICE_CONFIGURED equ 2 ; the device is configured ;;; Vectors org RESET_VECT ; Enable weak pull-ups banksel OPTION_REG bcf OPTION_REG,NOT_WPUEN banksel OSCCON goto bootloader_start ; to be continued further down in the file org INTERRUPT_VECT ; check the high byte of the return address (at the top of the stack) ; banksel TOSH ; if LOGGING_ENABLED ; for 4k-word mode: if TOSH < 0x10, we're in the bootloader ; if TOSH >= 0x10, jump to the application interrupt handler ; movlw high BOOTLOADER_SIZE ; subwf TOSH,w ; bnc _bootloader_interrupt ; else ; for 512-word mode: if TOSH == 0, we're in the bootloader ; if TOSH != 0, jump to the application interrupt handler ; tstf TOSH ; bz _bootloader_interrupt ; endif pagesel APP_INTERRUPT goto APP_INTERRUPT ; executing from the bootloader? it's a USB interrupt _bootloader_interrupt banksel UIR ; reset? btfss UIR,URSTIF goto _utrans ; not a reset? just start servicing transactions call usb_init ; if so, reset the USB interface (clears interrupts) ; banksel PIE2 ; bsf PIE2,USBIE ; reenable USB interrupts banksel UIR bcf UIR,URSTIF ; clear the flag ; service transactions _utrans banksel UIR btfss UIR,TRNIF ; goto _usdone return movfw USTAT ; stash the status in a temp register movwf FSR1H bcf UIR,TRNIF ; clear flag and advance USTAT fifo banksel BANKED_EP0OUT_STAT andlw b'01111000' ; check endpoint number bnz _ucdc ; if not endpoint 0, it's a CDC message call usb_service_ep0 ; handle the control message goto _utrans _ucdc call usb_service_cdc ; USTAT value is still in FSR1H goto _utrans ; clear USB interrupt ;_usdone ; banksel PIR2 ; bcf PIR2,USBIF ; retfie ;;; Idle loop. In bootloader mode, the MCU just spins here, and all USB ;;; communication is interrupt-driven. ;;; This snippet is deliberately located within the first 256 words of program ;;; memory, so we can easily check in the interrupt handler if the interrupt ;;; occurred while executing application code or bootloader code. ;;; (TOSH will be 0x00 when executing bootloader code, i.e. this snippet) bootloader_main_loop ; bsf INTCON,GIE ; enable interrupts _loop if LOGGING_ENABLED ; Print any pending characters in the log call log_service endif banksel PIR2 btfss PIR2,USBIF goto _loop call ret ; simulate interrupt call overhead call _bootloader_interrupt banksel PIR2 bcf PIR2,USBIF goto _loop ;;; Handles a control transfer on endpoint 0. ;;; arguments: expects USTAT value in FSR1H ;;; BSR=0 ;;; returns: none ;;; clobbers: W, FSR1H usb_service_ep0 btfsc FSR1H,DIR ; is it an IN transfer or an OUT/SETUP? goto _usb_ctrl_in ; it's an OUT or SETUP transfer movfw BANKED_EP0OUT_STAT andlw b'00111100' ; isolate PID bits sublw PID_SETUP ; is it a SETUP packet? bnz arm_ep0_out ; if not, it's a regular OUT, just rearm the buffer ; it's a SETUP packet--fall through ; Handles a SETUP control transfer on endpoint 0. ; BSR=0 _usb_ctrl_setup bcf USB_STATE,IS_CONTROL_WRITE ; get bmRequestType, but don't bother checking whether it's standard/class/vendor... ; the CDC and standard requests we'll receive have distinct bRequest numbers movfw BANKED_EP0OUT_BUF+bmRequestType btfss BANKED_EP0OUT_BUF+bmRequestType,7 ; is this host->device? bsf USB_STATE,IS_CONTROL_WRITE ; if so, this is a control write ; check request number: is it Get Descriptor? movlw GET_DESCRIPTOR subwf BANKED_EP0OUT_BUF+bRequest,w bz _usb_get_descriptor ; is it Set Address? movlw SET_ADDRESS subwf BANKED_EP0OUT_BUF+bRequest,w bz _usb_set_address ; is it Set_Configuration? movlw SET_CONFIG subwf BANKED_EP0OUT_BUF+bRequest,w bz _usb_set_configuration ; is it Get Configuration? movlw GET_CONFIG subwf BANKED_EP0OUT_BUF+bRequest,w bz _usb_get_configuration ; unhandled request? fall through to _usb_ctrl_invalid ; Finishes a rejected SETUP transaction: the endpoints are stalled _usb_ctrl_invalid banksel UCON bcf UCON,PKTDIS ; reenable packet processing banksel BANKED_EP0IN_STAT movlw _DAT0|_DTSEN|_BSTALL call arm_ep0_in_with_flags arm_ep0_out movlw _DAT0|_DTSEN|_BSTALL arm_ep0_out_with_flags ; W specifies STAT flags movwf BANKED_EP0OUT_STAT movlw EP0_BUF_SIZE ; reset the buffer count movwf BANKED_EP0OUT_CNT bsf BANKED_EP0OUT_STAT,UOWN ; arm the OUT endpoint return ; Finishes a successful SETUP transaction. _usb_ctrl_complete banksel UCON bcf UCON,PKTDIS ; reenable packet processing banksel USB_STATE btfsc USB_STATE,IS_CONTROL_WRITE goto _cwrite ; this is a control read; prepare the IN endpoint for the data stage ; and the OUT endpoint for the status stage _cread call ep0_read_in ; read data into IN buffer movlw _DAT1|_DTSEN ; OUT buffer will be ready for status stage ; value in W is used to specify the EP0 OUT flags _armbfs call arm_ep0_out_with_flags movlw _DAT1|_DTSEN ; arm IN buffer arm_ep0_in_with_flags ; W specifies STAT flags movwf BANKED_EP0IN_STAT bsf BANKED_EP0IN_STAT,UOWN return ; this is a control write: prepare the IN endpoint for the status stage ; and the OUT endpoint for the next SETUP transaction _cwrite bcf BANKED_EP0IN_STAT,UOWN ; ensure we have ownership of the buffer clrf BANKED_EP0IN_CNT ; we'll be sending a zero-length packet movlw _DAT0|_DTSEN|_BSTALL ; make OUT buffer ready for next SETUP packet goto _armbfs ; arm OUT and IN buffers ; Handles a Get Descriptor request. ; BSR=0 _usb_get_descriptor ; check descriptor type movlw DESC_CONFIG subwf BANKED_EP0OUT_BUF+wValueH,w bz _config_descriptor movlw DESC_STRING subwf BANKED_EP0OUT_BUF+wValueH,w bz _string_descriptor movlw DESC_DEVICE subwf BANKED_EP0OUT_BUF+wValueH,w bnz _usb_ctrl_invalid _device_descriptor movlw low DEVICE_DESCRIPTOR movwf EP0_DATA_IN_PTR movlw DEVICE_DESC_LEN goto _set_data_in_count_from_w _config_descriptor movlw low CONFIGURATION_DESCRIPTOR movwf EP0_DATA_IN_PTR movlw CONFIG_DESC_TOTAL_LEN ; length includes all subordinate descriptors goto _set_data_in_count_from_w _string_descriptor ; only one string descriptor (serial number) is supported, ; so don't bother checking wValueL movlw low SERIAL_NUMBER_STRING_DESCRIPTOR movwf EP0_DATA_IN_PTR movlw SERIAL_NUM_DESC_LEN _set_data_in_count_from_w movwf EP0_DATA_IN_COUNT ; the count needs to be set to the minimum of the descriptor's length (in W) ; and the requested length tstf BANKED_EP0OUT_BUF+wLengthH ; just ignore high byte... bnz _usb_ctrl_complete ; if W <= f, no need to adjust subwf BANKED_EP0OUT_BUF+wLengthL,w ; just ignore high byte... bc _usb_ctrl_complete ; if W <= f, no need to adjust movfw BANKED_EP0OUT_BUF+wLengthL movwf EP0_DATA_IN_COUNT goto _usb_ctrl_complete ; Handles a Set Address request. ; The address is actually set in the IN status stage. _usb_set_address bsf USB_STATE,ADDRESS_PENDING ; address will be assigned in the status stage goto _usb_ctrl_complete ; Handles a Set Configuration request. ; For now just accept any nonzero configuration. ; BSR=0 _usb_set_configuration bcf USB_STATE,DEVICE_CONFIGURED ; temporarily clear flag tstf BANKED_EP0OUT_BUF+wValueL ; anything other than 0 is valid skpz bsf USB_STATE,DEVICE_CONFIGURED call cdc_init goto _usb_ctrl_complete ; Handles a Get Configuration request. ; BSR=0 _usb_get_configuration ; load a pointer to either a 0 or a 1 in ROM ; the 0 and 1 have been chosen so that they are adjacent movlw low CONFIGURATION_0_CONSTANT btfsc USB_STATE,DEVICE_CONFIGURED incw movwf EP0_DATA_IN_PTR movlw 1 movwf EP0_DATA_IN_COUNT goto _usb_ctrl_complete ; Handles an IN control transfer on endpoint 0. ; BSR=0 _usb_ctrl_in btfsc USB_STATE,IS_CONTROL_WRITE ; is this a control read or write? goto _check_for_pending_address ; fetch more data and re-arm the IN endpoint call ep0_read_in movlw _DTSEN btfss BANKED_EP0IN_STAT,DTS ; toggle DTS bsf WREG,DTS goto arm_ep0_in_with_flags ; arm the IN buffer ; if this is the status stage of a Set Address request, assign the address here. ; The OUT buffer has already been armed for the next SETUP. _check_for_pending_address btfss USB_STATE,ADDRESS_PENDING return ; read the address out of the setup packed in the OUT buffer bcf USB_STATE,ADDRESS_PENDING movfw BANKED_EP0OUT_BUF+wValueL banksel UADDR movwf UADDR return ;;; Reads descriptor data from EP0_DATA_IN_PTR, copies it to the EP0 IN buffer, ;;; and decrements EP0_DATA_IN_COUNT. ;;; arguments: BSR=0 ;;; returns: EP0_DATA_IN_PTRL advanced ;;; EP0_DATA_IN_COUNT decremented ;;; clobbers: W, FSR0, FSR1 ep0_read_in bcf BANKED_EP0IN_STAT,UOWN ; make sure we have ownership of the buffer clrf BANKED_EP0IN_CNT ; initialize buffer size to 0 tstf EP0_DATA_IN_COUNT ; do nothing if there are 0 bytes to send retz movfw EP0_DATA_IN_PTR ; set up source pointer movwf FSR0L movlw high DEVICE_DESCRIPTOR|0x80 movwf FSR0H ldfsr1d EP0IN_BUF ; set up destination pointer clrw ; byte copy loop _bcopy sublw EP0_BUF_SIZE ; have we filled the buffer? bz _bcdone moviw FSR0++ movwi FSR1++ incf BANKED_EP0IN_CNT,f ; increase number of bytes copied movfw BANKED_EP0IN_CNT ; save to test on the next iteration decfsz EP0_DATA_IN_COUNT,f ; decrement number of bytes remaining goto _bcopy ; write back the updated source pointer _bcdone movfw FSR0L movwf EP0_DATA_IN_PTR ; if we're sending the configuration descriptor, we need to inject the app's ; values for bus power/self power and max current consumption ;_check_for_config_bmattributes ; movlw (low CONFIGURATION_DESCRIPTOR)+EP0_BUF_SIZE ; subwf FSR0L,w ; bnz _check_for_config_bmaxpower ; if we're sending the first 8 bytes of the configuration descriptor, ; set bit 6 of bmAttributes if the application is self-powered ; btfsc APP_POWER_CONFIG,0 ; bsf BANKED_EP0IN_BUF+7,6 ; return ;_check_for_config_bmaxpower ; movlw (low CONFIGURATION_DESCRIPTOR)+(EP0_BUF_SIZE*2) ; subwf FSR0L,w ; retnz ; if we're sending the second 8 bytes of the configuration descriptor, ; replace bMaxPower with the app's value ; movfw APP_POWER_CONFIG ; bcf WREG,0 ; value is in the upper 7 bits ; movwf BANKED_EP0IN_BUF+0 return ;;; Initializes the buffers for the CDC endpoints (1 OUT, 1 IN, and 2 IN). ;;; arguments: none ;;; returns: none ;;; clobbers: W, BSR=0 cdc_init banksel BANKED_EP1OUT_STAT call arm_ep1_out ; arm EP1 IN buffer, clearing data toggle bit clrw ; arms endpoint 1 IN, toggling DTS if W=(1<<DTS) arm_ep1_in clrf BANKED_EP1IN_CNT ; next packet will have 0 length (unless another OUT is received) andwf BANKED_EP1IN_STAT,f ; clear all bits (except DTS if bit is set in W) xorwf BANKED_EP1IN_STAT,f ; update data toggle (if bit is set in W) bsf BANKED_EP1IN_STAT,UOWN return ;;; Services a transaction on one of the CDC endpoints. ;;; arguments: USTAT value in FSR1H ;;; BSR=0 ;;; returns: none ;;; clobbers: W, FSR0, FSR1 usb_service_cdc movlw (1<<DTS) retbfs FSR1H,ENDP1 ; ignore endpoint 2 bbfs FSR1H,DIR,arm_ep1_in ; if endpoint 1 IN, rearm buffer movf BANKED_EP1OUT_CNT,f ; test for a zero-length packet bz arm_ep1_out ; (just ignore them and rearm the OUT buffer) bcf BANKED_EP1IN_STAT,UOWN call bootloader_exec_cmd ; execute command; status returned in W banksel BANKED_EP1IN_BUF movwf BANKED_EP1IN_BUF ; copy status to IN buffer movlw 1 movwf BANKED_EP1IN_CNT ; output byte count is 1 bsf BANKED_EP1IN_STAT,UOWN ; fall through to arm_ep1_out arm_ep1_out movlw EP1_OUT_BUF_SIZE ; set CNT movwf BANKED_EP1OUT_CNT clrf BANKED_EP1OUT_STAT ; ignore data toggle bsf BANKED_EP1OUT_STAT,UOWN ; rearm OUT buffer return ;;; Executes a bootloader command. ;;; arguments: command payload in EP1 OUT buffer ;;; BSR=0 ;;; returns: status code in W ;;; clobbers: W, BSR, FSR0, FSR1 bootloader_exec_cmd ; check length of data packet movlw BCMD_SET_PARAMS_LEN subwf BANKED_EP1OUT_CNT,w bz _bootloader_set_params movlw BCMD_WRITE_LEN subwf BANKED_EP1OUT_CNT,w bz _bootloader_write movlw BCMD_RESET_LEN subwf BANKED_EP1OUT_CNT,w bz _bootloader_reset retlw BSTAT_INVALID_COMMAND ; Resets the device if the received byte matches the reset character. _bootloader_reset movlw BCMD_RESET_CHAR subwf BANKED_EP1OUT_BUF,w ; check received character skpz retlw BSTAT_INVALID_COMMAND ; command is valid, reset the device reset ; Sets the write address, expected checksum of the next 32 words, ; and erases the row at that address if the last byte of the command matches ; the "erase" character. ; BSR=0 _bootloader_set_params movfw BANKED_EP1OUT_BUF+BCMD_SET_PARAMS_CKSUM ; expected checksum movwf EXPECTED_CHECKSUM ; save for verification during write command movfw BANKED_EP1OUT_BUF+BCMD_SET_PARAMS_ERASE movwf FSR1L ; temp movfw BANKED_EP1OUT_BUF+BCMD_SET_PARAMS_ADRL ; address lower bits movwf FSR1H ; temp movfw BANKED_EP1OUT_BUF+BCMD_SET_PARAMS_ADRH ; address upper bits banksel PMADRH movwf PMADRH movfw FSR1H ; bring lower bits out of temp movwf PMADRL ; do we need to erase? movlw BCMD_ERASE_CHAR subwf FSR1L,w skpz retlw BSTAT_OK ; if no reset command is given, return OK ; Erases the row of flash in PMADRH:PMADRL. ; BSR=3 _bootloader_erase movlw (1<<FREE)|(1<<WREN) ; enable write and erase to program memory movwf PMCON1 call flash_unlock ; stalls until erase finishes _wdone bcf PMCON1,WREN ; clear write enable flag retlw BSTAT_OK ; Verifies that the checksum of the 32 words (64 bytes) in the EP1 OUT buffer ; matches the previously sent value. If so, the 32 bytes are then written to ; flash memory at the address in PMADRH:PMADRL. (set by a prior command) ; BSR=0 _bootloader_write ; The expected checksum is the two's complement of the sum of the bytes. ; If the data is valid, we can add the checksum to the sum of the bytes and ; the result will be 0. We initialize a temporary register with the expected ; checksum, and then add each byte to it as it's processed. ; If the value in the temp register is 0 after all 64 bytes have been copied ; to the write latches, proceed with the write. movfw EXPECTED_CHECKSUM movwf FSR1L ; use a temp for the running checksum ldfsr0d EP1OUT_BUF ; set up read pointer ; simultaneously compute the checksum of the 32 words and copy them to the ; write latches movlw 32 ; number of words to write minus 1 movwf FSR1H ; used for loop count ; write to latches only movlw (1<<LWLO)|(1<<WREN) banksel PMCON1 movwf PMCON1 _wloop moviw FSR0++ ; load lower byte addwf FSR1L,f ; add lower byte to checksum movwf PMDATL ; copy to write latch moviw FSR0++ ; load upper byte addwf FSR1L,f ; add upper byte to checksum movwf PMDATH ; copy to write latch ; after writing the 32nd word to PMDATH:PMDATL, don't execute the unlock sequence ; or advance the address pointer! decf FSR1H,f ; decrement loop count bz _wcksum ; if 0, we're done writing to the latches ; write data to latch call flash_unlock ; execute unlock sequence ; still have more words to go incf PMADRL,f ; increment write address goto _wloop ; verify the checksum _wcksum tstf FSR1L bz _wfinish clrf PMCON1 retlw BSTAT_INVALID_CHECKSUM ; if there's a mismatch, abort the write ; write data to flush _wfinish bcf PMCON1,LWLO call flash_unlock ; stalls until write finishes clrf PMCON1 ; verify the write: compare each byte in the buffer to its counterpart that ; was just written to flash. ; we do this backwards so we don't waste instructions resetting the pointers. ; (note: PMADRH:PMADRL is already pointing at the last written word, but FSR0 ; is pointing to one byte past the end of the buffer) bsf FSR1H,5 ; set loop count to 32 (just need to set one bit because it's already 0) _vloop bsf PMCON1,RD ; read word from flash nop ; 2 required nops nop moviw --FSR0 ; get high byte of expected word subwf PMDATH,w ; compare with high byte written to flash andlw 0x3F skpz retlw BSTAT_VERIFY_FAILED_H moviw --FSR0 ; get low byte of expected word subwf PMDATL,w ; compare with low byte written to flash skpz retlw BSTAT_VERIFY_FAILED_L decf PMADRL,f ; decrement read address decfsz FSR1H,f ; decrement loop count goto _vloop retlw BSTAT_OK ;;; Executes the flash unlock sequence, performing an erase or write. ;;; arguments: PMCON1 bits CFGS, LWLO, FREE and WREN set appropriately ;;; BSR=3 ;;; returns: none ;;; clobbers: W flash_unlock movlw 0x55 movwf PMCON2 movlw 0xAA movwf PMCON2 bsf PMCON1,WR nop nop ret return ;;; Main function ;;; BSR=1 (OSCCON bank) bootloader_start ; Configure the oscillator (48MHz from INTOSC using 3x PLL) movlw (1<<SPLLEN)|(1<<SPLLMULT)|(1<<IRCF3)|(1<<IRCF2)|(1<<IRCF1)|(1<<IRCF0) movwf OSCCON ; Wait for the oscillator and PLL to stabilize _wosc movlw (1<<PLLRDY)|(1<<HFIOFR)|(1<<HFIOFS) andwf OSCSTAT,w sublw (1<<PLLRDY)|(1<<HFIOFR)|(1<<HFIOFS) bnz _wosc ; We have a valid application? Check if the entry pin is grounded banksel PORTA btfss PORTA,RA3 goto _bootloader_main ; enter bootloader mode if input is low ; $add$ do not run application if the watchdog timed out (providing a mechanism for the app to trigger a firmware update) if WDT_BOOTLOADER_MODE ; $add$ btfss STATUS,NOT_TO ; $add$ goto _bootloader_main ; $add$ endif ; $add$ ; $add$ Check for Bootloader Mode (Software Reset with FSR1L=0xDF and FSR0L=0xBE) movfw FSR1L ; $add$ sublw 0xDF ; $add$ bnz _check_app_entry ; $add$ movfw FSR0L ; $add$ sublw 0xBE ; $add$ bz _bootloader_main ; $add$ _check_app_entry ; $add$ ; Check for valid application code: the lower 8 bits of the first word cannot be 0xFF call app_is_present bz _bootloader_main ; if we have no application, enter bootloader mode ; We have a valid application and the entry pin is high. Start the application. banksel OPTION_REG bsf OPTION_REG,NOT_WPUEN ; but first, disable weak pullups if APP_ENTRY_POINT>=2048 pagesel APP_ENTRY_POINT endif goto APP_ENTRY_POINT ; Not entering application code: initialize the USB interface and wait for commands. _bootloader_main ; Enable active clock tuning banksel ACTCON movlw (1<<ACTSRC)|(1<<ACTEN) movwf ACTCON ; source = USB if LOGGING_ENABLED call uart_init ; Print a power-on character call log_init logch '^',LOG_NEWLINE endif ; Initialize USB call usb_init ; Attach to the bus (could be a subroutine, but inlining it saves 2 instructions) _usb_attach logch 'A',0 banksel UCON ; reset UCON clrf UCON ; banksel PIE2 ; bsf PIE2,USBIE ; enable USB interrupts ; bsf INTCON,PEIE ; banksel UCON _usben bsf UCON,USBEN ; enable USB module and wait until ready btfss UCON,USBEN goto _usben logch '!',LOG_NEWLINE ; Enable interrupts and enter an idle loop ; (Loop code is located at the top of the file, in the first 256 words of ; program memory) goto bootloader_main_loop ;;; Determines if application code is present in flash memory. ;;; arguments: none ;;; returns: Z flag cleared if application code is present ;;; clobbers: W, FSR0 app_is_present clrf FSR0L movlw (high APP_ENTRY_POINT)|0x80 ; need to set high bit to indicate program memory movwf FSR0H moviw FSR0++ incw ; if W was 0xFF, it'll be 0 now ; return ; Z flag will be unset if app code is present skpz ; $add$ return ; $add$ Z flag will be unset if app code is present moviw FSR0++ ; $add$ iorlw 0xC0 ; $add$ incw ; $add$ if W was 0xFF, it'll be 0 now return ; $add$ Z flag will be unset if app code is present ;;; Gets the application's power config byte and stores it in APP_POWER_CONFIG. ;;; arguments: none ;;; returns: none ;;; clobbers: W, BSR, FSR0 ;get_app_power_config ; banksel APP_POWER_CONFIG ; movlw 0x33 ; default value: bus-powered, max current 100 mA ; movwf APP_POWER_CONFIG ; call app_is_present ; retz ; if Z flag is set, we have no application, just return ; if LOGGING_ENABLED ; pagesel APP_CONFIG ; endif ; call APP_CONFIG ; config value returned in W ; pagesel get_app_power_config ; banksel APP_POWER_CONFIG ; movwf APP_POWER_CONFIG ; return ;;; Initializes the USB system and resets all associated registers. ;;; arguments: none ;;; returns: none ;;; clobbers: W, BSR, FSR0, FSR1H usb_init logch 'R',LOG_NEWLINE ; disable USB interrupts ; banksel PIE2 ; bcf PIE2,USBIE ; clear USB registers banksel UEIR clrf UEIR clrf UIR ; disable endpoints we won't use ; clrf UEP3 ; clrf UEP4 ; clrf UEP5 ; clrf UEP6 ; clrf UEP7 ; set configuration ; clrf UEIE ; don't need any error interrupts movlw (1<<UPUEN)|(1<<FSEN) movwf UCFG ; enable pullups, full speed, no ping-pong buffering movlw (1<<TRNIE)|(1<<URSTIE) movwf UIE ; only need interrupts for transaction complete and reset ; clear all BDT entries, variables, and buffers clrf FSR0L movlw high BDT_START ; BDT starts at 0x2000 movwf FSR0H movlw USED_RAM_LEN movwf FSR1H ; loop count movlw 0 _ramclr movwi FSR0++ decfsz FSR1H,f goto _ramclr ; get the app's power configuration (if it's present) ; call get_app_power_config ; reset ping-pong buffers and address banksel UCON bsf UCON,PPBRST clrf UADDR bcf UCON,PKTDIS ; enable packet processing bcf UCON,PPBRST ; clear ping-pong buffer reset flag ; flush pending transactions _tflush btfss UIR,TRNIF goto _initep bcf UIR,TRNIF call ret ; need at least 6 cycles before checking TRNIF again goto _tflush ; initialize endpoints: ; 0 for control ; 1 for CDC bulk data ; 2 for CDC notifications (though it's never actually used) ; my intuition was that I should wait until a SET_CONFIGURATION is received ; before setting up endpoints 1 and 2... but there seemed to be a timing issue ; when doing so, so I moved them here _initep movlw (1<<EPHSHK)|(1<<EPOUTEN)|(1<<EPINEN) movwf UEP0 movlw (1<<EPHSHK)|(1<<EPCONDIS)|(1<<EPOUTEN)|(1<<EPINEN) movwf UEP1 movlw (1<<EPHSHK)|(1<<EPCONDIS)|(1<<EPINEN) movwf UEP2 ; initialize endpoint buffers and counts banksel BANKED_EP0OUT_ADRL movlw low EP0OUT_BUF ; set endpoint 0 OUT address low movwf BANKED_EP0OUT_ADRL movlw low EP0IN_BUF ; set endpoint 0 IN address low movwf BANKED_EP0IN_ADRL movlw low EP1OUT_BUF ; set endpoint 1 OUT address low movwf BANKED_EP1OUT_ADRL movlw low EP1IN_BUF ; set endpoint 1 IN address low movwf BANKED_EP1IN_ADRL movlw EPBUF_ADRH ; set all ADRH values movwf BANKED_EP0OUT_ADRH movwf BANKED_EP0IN_ADRH movwf BANKED_EP1OUT_ADRH movwf BANKED_EP1IN_ADRH goto arm_ep0_out ;;; Includes if LOGGING_ENABLED include "log.asm" endif ;;; Descriptors ; Place all the descriptors at the end of the bootloader region. ; This serves 2 purposes: 1) as long as the total length of all descriptors is ; less than 256, we can address them with an 8-bit pointer, ; and 2) the assembler will raise an error if space is exhausted. org DEVICE_DESCRIPTOR dt DEVICE_DESC_LEN ; bLength dt 0x01 ; bDescriptorType dt 0x00, 0x02 ; bcdUSB (USB 2.0) dt 0x02 ; bDeviceClass (communication device) dt 0x00 ; bDeviceSubclass dt 0x00 ; bDeviceProtocol dt 0x08 ; bMaxPacketSize0 (8 bytes) dt low USB_VENDOR_ID, high USB_VENDOR_ID ; idVendor dt low USB_PRODUCT_ID, high USB_PRODUCT_ID ; idProduct dt 0x01, 0x00 ; bcdDevice (1) dt 0x00 ; iManufacturer dt 0x00 ; iProduct dt 0x01 ; iSerialNumber dt 0x01 ; bNumConfigurations CONFIGURATION_DESCRIPTOR dt 0x09 ; bLength dt 0x02 ; bDescriptorType dt CONFIG_DESC_TOTAL_LEN, 0x00 ; wTotalLength dt 0x02 ; bNumInterfaces dt 0x01 ; bConfigurationValue dt 0x00 ; iConfiguration dt 0x80 ; bmAttributes dt (100 / 2) ; bMaxPower ;INTERFACE_DESCRIPTOR_0 dt 0x09 ; bLength dt 0x04 ; bDescriptorType (INTERFACE) dt 0x00 ; bInterfaceNumber CONFIGURATION_0_CONSTANT dt 0x00 ; bAlternateSetting ;CONFIGURATION_1_CONSTANT dt 0x01 ; bNumEndpoints dt 0x02 ; bInterfaceClass (communication) dt 0x02 ; bInterfaceSubclass (abstract control model) dt 0x01 ; bInterfaceProtocol (V.25ter, common AT commands) dt 0x00 ; iInterface ; if (CONFIGURATION_0_CONSTANT>>8) != (CONFIGURATION_1_CONSTANT>>8) ; error "CONSTANT_0 and CONSTANT_1 must be in the same 256-word region" ; endif ;HEADER_FUNCTIONAL_DESCRIPTOR dt 0x05 ; bFunctionLength dt 0x24 ; bDescriptorType (CS_INTERFACE) dt 0x00 ; bDescriptorSubtype (header functional descriptor) dt 0x10,0x01 ; bcdCDC (specification version, 1.1) ;ABSTRACT_CONTROL_MANAGEMENT_FUNCTIONAL_DESCRIPTOR dt 0x04 ; bFunctionLength dt 0x24 ; bDescriptorType (CS_INTERFACE) dt 0x02 ; bDescriptorSubtype (abstract control management functional descriptor) dt 0x02 ; bmCapabilities ;UNION_FUNCTIONAL_DESCRIPTOR dt 0x05 ; bFunctionLength dt 0x24 ; bDescriptorType (CS_INTERFACE) dt 0x06 ; bDescriptorSubtype (union functional descriptor) dt 0x00 ; bMasterInterface dt 0x01 ; bSlaveInterface0 ;CALL_MANAGEMENT_FUNCTIONAL_DESCRIPTOR dt 0x05 ; bFunctionLength dt 0x24 ; bDescriptorType (CS_INTERFACE) dt 0x01 ; bDescriptorSubtype (call management functional descriptor) dt 0x01 ; bmCapabilities (doesn't handle call management) dt 0x01 ; dDataInterface ;ENDPOINT_DESCRIPTOR_2_IN dt 0x07 ; bLength dt 0x05 ; bDescriptorType (ENDPOINT) dt 0x82 ; bEndpointAddress (2 IN) dt 0x03 ; bmAttributes (transfer type: interrupt) dt 0x08, 0x00 ; wMaxPacketSize (8) dt 0x7f ; bInterval ;INTERFACE_DESCRIPTOR_1 dt 0x09 ; bLength dt 0x04 ; bDescriptorType (INTERFACE) dt 0x01 ; bInterfaceNumber dt 0x00 ; bAlternateSetting dt 0x02 ; bNumEndpoints dt 0x0a ; bInterfaceClass (data) dt 0x00 ; bInterfaceSubclass dt 0x00 ; bInterfaceProtocol dt 0x00 ; iInterface ;ENDPOINT_DESCRIPTOR_1_IN dt 0x07 ; bLength dt 0x05 ; bDescriptorType (ENDPOINT) dt 0x81 ; bEndpointAddress (1 IN) dt 0x02 ; bmAttributes (transfer type: bulk) dt 0x40, 0x00 ; wMaxPacketSize (64) dt 0x00 ; bInterval ;ENDPOINT_DESCRIPTOR_1_OUT dt 0x07 ; bLength dt 0x05 ; bDescriptorType (ENDPOINT) dt 0x01 ; bEndpointAddress (1 OUT) dt 0x02 ; bmAttributes (transfer type: bulk) dt 0x40, 0x00 ; wMaxPacketSize (64) dt 0x00 ; bInterval ; extract nibbles from serial number SN1 equ (SERIAL_NUMBER_DIGIT>>12) & 0xF SN2 equ (SERIAL_NUMBER_DIGIT>>8) & 0xF SN3 equ (SERIAL_NUMBER_DIGIT>>4) & 0xF SN4 equ SERIAL_NUMBER_DIGIT & 0xF SERIAL_NUMBER_STRING_DESCRIPTOR dt SERIAL_NUM_DESC_LEN ; bLength dt 0x03 ; bDescriptorType (STRING) dt '0'+SN1+((SN1>9)*7), 0x00 ; convert hex digits to ASCII dt '0'+SN2+((SN2>9)*7), 0x00 dt '0'+SN3+((SN3>9)*7), 0x00 dt '0'+SN4+((SN4>9)*7), 0x00 ; Raise an error if the descriptors aren't properly aligned. (This means you ; changed the descriptors withouth updating the definition of ALL_DESCS_TOTAL_LEN.) ; if $!=BOOTLOADER_SIZE ; error "Descriptors must be aligned with the end of the bootloader region" ; endif end |
【PC側の書き込みユーティリティ】
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 |
/* main.cpp - Device Firmware Update Utility Copyright (c) 2021 Sasapea's Lab. All right reserved. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include <stdlib.h> #include <stdbool.h> #include <stdint.h> #include <errno.h> #include "firmware.h" #include "i16hex.h" void usage(void) { printf("\n"); printf("Device Firmeware Update Utility, Version 1.0\n"); printf("Copyright(c) 2021 Sasapea's Lab. All right reserved.\n"); printf("\n"); printf("Usage: firmware port-name [options] intelhex-file\n"); printf("\n"); printf(" options: -reset ... reset bootloader\n"); printf(" -erase ... erase program\n"); printf("\n"); exit(EXIT_FAILURE); } int main(int argc, char **argv) { bool success = false; if (argc >= 3) { I16Hex ih; Firmware fw; const char *dev = argv[1]; const char *src = argv[2]; if (fw.open(dev) < 0) fw.error("Firmware.open(\"%s\"): %s\n", dev, strerror(errno)); else { if (*src == '-') { if (strcmp(src, "-reset") == 0) success = fw.reset(); else if (strcmp(src, "-erase") == 0) success = fw.erase(); } else { if (!ih.load(src)) fw.error("I16Hex.load(\"%s\"): %s\n", src, strerror(errno)); else success = fw.upload(ih.bytes(), ih.start(), ih.end()); } fw.close(); } } if (!success) usage(); return EXIT_SUCCESS; } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 |
/* firmware.h - Device Firmware Update Library Copyright (c) 2021 Sasapea's Lab. All right reserved. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef _FIRMWARE_H_ #define _FIRMWARE_H_ #include <stdarg.h> #include <stdio.h> #include <stdint.h> #include <stdbool.h> #include <string.h> #include <error.h> #include "serial.h" // // Firmware Address Range // #define FIRMWARE_ADDR_START 0x0400 #define FIRMWARE_ADDR_END 0x4000 // // Bootloader Row Size // #define FIRMWARE_SIZE_ROW 64 // // Bootloader Commands // #define FIRMWARE_CMD_ERASE 0x45 #define FIRMWARE_CMD_RESET 0x52 // // Bootloader Status codes // #define FIRMWARE_STATUS_OK 1 #define FIRMWARE_STATUS_INVALID_COMMAND 2 #define FIRMWARE_STATUS_INVALID_CHECKSUM 3 #define FIRMWARE_STATUS_VERIFY_FAILED_L 4 #define FIRMWARE_STATUS_VERIFY_FAILED_H 5 class Firmware : public Serial { private: ssize_t check(ssize_t status) { if (status < 0) error(strerror(status)); return status; } // Hide method ssize_t read(void *buf, size_t len) override { ssize_t rv = check(Serial::read(buf, len)); if ((rv >= 0) && (rv != (ssize_t)len)) error("READ-TIMEOUT\n"); return rv; } // Hide method ssize_t write(const void *buf, size_t len) override { ssize_t rv = check(Serial::write(buf, len)); if ((rv >= 0) && (rv != (ssize_t)len)) error("WRITE-IOERROR\n"); return rv; } bool status(void) { uint8_t status; if (read(&status, sizeof(status)) == (ssize_t)sizeof(status)) { switch (status) { case FIRMWARE_STATUS_OK: return true; case FIRMWARE_STATUS_INVALID_COMMAND: error("INVALID_COMMAND\n"); break; case FIRMWARE_STATUS_INVALID_CHECKSUM: error("INVALID_CHECKSUM\n"); break; case FIRMWARE_STATUS_VERIFY_FAILED_L: error("VERIFY_FAILED_L\n"); break; case FIRMWARE_STATUS_VERIFY_FAILED_H: error("VERIFY_FAILED_H\n"); break; default: error("UNKOWN ERROR\n"); break; } } return false; } bool invoke(const uint8_t *buf, size_t len) { return write(buf, len) == (ssize_t)len ? status() : false; } bool program(const uint8_t *code, uint32_t addr, uint32_t size) { struct __attribute__((packed)) { uint8_t low; uint8_t high; uint8_t csum; uint8_t mode; } request = {(uint8_t)(addr >> 1), (uint8_t)(addr >> 9), 0, FIRMWARE_CMD_ERASE}; if (code) { // calculate checksum for (uint32_t i = 0; i < size; ++i) request.csum -= code[i]; } // set write params if (!invoke((uint8_t *)&request, sizeof(request))) return false; // write code return code ? invoke(code, size) : true; } public: Firmware(void) { } virtual ~Firmware(void) { } bool reset(void) { uint8_t cmd = FIRMWARE_CMD_RESET; return write(&cmd, sizeof(cmd)) == (ssize_t)sizeof(cmd); } bool erase(void) { return program(NULL, FIRMWARE_ADDR_START, FIRMWARE_SIZE_ROW); } bool upload(const uint8_t *code, uint32_t start, uint32_t end) { if ((start < FIRMWARE_ADDR_START) || (end > FIRMWARE_ADDR_END)) { error("invalid code address range\n"); return false; } start &= ~(FIRMWARE_SIZE_ROW - 1); printf("upload address range (0x%04X - 0x%04X)\n", start, end - 1); while (start < end) { printf("."); if (!program(code + start, start, FIRMWARE_SIZE_ROW)) { printf("\n"); error("stopped address is 0x%04X\n", start); return false; } start += FIRMWARE_SIZE_ROW; } printf("\nupload success and reset\n"); return reset(); } void error(const char *format,...) { va_list ap; va_start(ap, format); fprintf(stderr, "[ERROR] "); vfprintf(stderr, format, ap); va_end(ap); } }; #endif /* _FIRMWARE_H_ */ |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 |
/* i16hex.h - Intel 16bit Hex File Library Copyright (c) 2021 Sasapea's Lab. All right reserved. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef _IHEX16_H_ #define _IHEX16_H_ #include <stdio.h> #include <stdint.h> #include <stdbool.h> #include <string.h> #include <ctype.h> #ifndef __linux__ #if SIZE_MAX == UINT64_MAX #define ssize_t long long #else #define ssize_t int #endif #endif enum { I16HEX_TYPE_DATA = 0, I16HEX_TYPE_END = 1, I16HEX_TYPE_IH32 = 4, }; class I16Hex { private: uint32_t _start; uint32_t _end; uint8_t _bytes[65536]; template<typename T> const uint8_t *toxdigit(const uint8_t *p, T &val) { val = 0; for (uint8_t i = 0; i < sizeof(T) * 2; ++i) { uint8_t c = *p++; if (!isxdigit(c)) return NULL; val = (val << 4) | (c <= '9' ? c - '0' : (c & ~0x20) - ('A' - 10)); } return p; } public: I16Hex(void) { clear(); } virtual ~I16Hex(void) { } void clear(void) { _start = _end = 0; memset(_bytes, 0xFF, sizeof(_bytes)); } bool load(const char *path) { bool eor = false; FILE *fp = fopen(path, "r"); if (fp) { uint32_t base = 0; uint16_t addr; uint8_t nlen, type, csum, sum, cnt; uint8_t line[(sizeof(nlen) + sizeof(addr) + sizeof(type) + UINT8_MAX + sizeof(csum)) * 2 + 4]; clear(); while (!eor && fgets((char *)line, sizeof(line), fp)) { const uint8_t *p = line; if (*p++ == ':') { if ((p = toxdigit<__typeof__(nlen)>(p, nlen)) == NULL) break; // invalid format. if ((p = toxdigit<__typeof__(addr)>(p, addr)) == NULL) break; // invalid format. if ((p = toxdigit<__typeof__(type)>(p, type)) == NULL) break; // invalid format. for (cnt = sum = 0; cnt < nlen; ++cnt) { __typeof__(line[0]) b; if ((p = toxdigit<__typeof__(b)>(p, b)) == NULL) break; // invalid format. sum += line[cnt] = b; } if (cnt < nlen) break; // invalid format. if ((p = toxdigit<__typeof__(csum)>(p, csum)) == NULL) break; // invalid format. if ((uint8_t)(nlen + (addr >> 8) + (addr >> 0) + type + csum + sum)) break; // invalid check sum. switch (type) { case I16HEX_TYPE_DATA: if (base + addr + nlen <= sizeof(_bytes)) memcpy(_bytes + addr, line, nlen); break; case I16HEX_TYPE_END: for (_end = sizeof(_bytes); (_end > 0) && (_bytes[_end - 1] == 0xFF); --_end) continue; for (_start = 0; (_end > _start) && (_bytes[_start] == 0xFF); ++_start) continue; eor = true; break; case I16HEX_TYPE_IH32: base = ((uint32_t)line[0] << 24) | ((uint32_t)line[1] << 16); break; default: break; // ignored. } } } fclose(fp); } return eor; } uint32_t start(void) { return _start; } uint32_t end(void) { return _end; } const uint8_t * bytes(void) { return _bytes; } }; #endif /* _IHEX16_H_ */ |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 |
/* serial.h - Serial I/O Library Copyright (c) 2021 Sasapea's Lab. All right reserved. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef _SERIAL_H #define _SERIAL_H #include <stdio.h> #include <stdint.h> #include <string.h> #include <fcntl.h> #ifdef __linux__ #include <unistd.h> #include <termios.h> #include <sys/time.h> #include <sys/types.h> #else #include <windows.h> #if SIZE_MAX == UINT64_MAX #define ssize_t long long #else #define ssize_t int #endif #endif typedef enum { SERIAL_FRAME_5N1, SERIAL_FRAME_6N1, SERIAL_FRAME_7N1, SERIAL_FRAME_8N1, SERIAL_FRAME_5N2, SERIAL_FRAME_6N2, SERIAL_FRAME_7N2, SERIAL_FRAME_8N2, SERIAL_FRAME_5O1, SERIAL_FRAME_6O1, SERIAL_FRAME_7O1, SERIAL_FRAME_8O1, SERIAL_FRAME_5O2, SERIAL_FRAME_6O2, SERIAL_FRAME_7O2, SERIAL_FRAME_8O2, SERIAL_FRAME_5E1, SERIAL_FRAME_6E1, SERIAL_FRAME_7E1, SERIAL_FRAME_8E1, SERIAL_FRAME_5E2, SERIAL_FRAME_6E2, SERIAL_FRAME_7E2, SERIAL_FRAME_8E2, } SERIAL_FRAME; typedef enum { SERIAL_FLOW_NONE, SERIAL_FLOW_RTSCTS, SERIAL_FLOW_XONOFF, } SERIAL_FLOW; #define SERIAL_FLOW_XONOFF_START 0x11 // DC1 #define SERIAL_FLOW_XONOFF_STOP 0x13 // DC3 #define SERIAL_FLOW_XONOFF_LIMIT 256 class Serial { private: int _fd; #ifdef __linux__ struct termios _tcattr; struct timeval _timeout; #endif void config(unsigned long speed, SERIAL_FRAME frame, SERIAL_FLOW flow, unsigned long timeout) { #ifdef __linux__ struct termios tcattr; tcgetattr(_fd, &tcattr); _tcattr= tcattr; cfmakeraw(&tcattr); speed = cfgetospeed(&tcattr); int cflags = tcattr.c_cflag & (CSIZE | CSTOPB | PARENB | PARODD); tcattr.c_cflag &= ~(CSIZE | CSTOPB | PARENB | PARODD); switch (speed) { case 50: speed = B50; break; case 75: speed = B75; break; case 110: speed = B110; break; case 134: speed = B134; break; case 150: speed = B150; break; case 200: speed = B200; break; case 300: speed = B300; break; case 600: speed = B600; break; case 1200: speed = B1200; break; case 1800: speed = B1800; break; case 2400: speed = B2400; break; case 4800: speed = B4800; break; case 9600: speed = B9600; break; case 19200: speed = B19200; break; case 38400: speed = B38400; break; case 57600: speed = B57600; break; case 115200: speed = B115200; break; case 230400: speed = B230400; break; case 460800: speed = B460800; break; case 500000: speed = B500000; break; case 576000: speed = B576000; break; case 921600: speed = B921600; break; case 1000000: speed = B1000000; break; case 1152000: speed = B1152000; break; case 1500000: speed = B1500000; break; case 2000000: speed = B2000000; break; case 2500000: speed = B2500000; break; case 3000000: speed = B3000000; break; case 3500000: speed = B3500000; break; case 4000000: speed = B4000000; break; } switch (frame) { case SERIAL_FRAME_5N1: cflags = CS5; break; case SERIAL_FRAME_6N1: cflags = CS6; break; case SERIAL_FRAME_7N1: cflags = CS7; break; case SERIAL_FRAME_8N1: cflags = CS8; break; case SERIAL_FRAME_5N2: cflags = CS5 | CSTOPB; break; case SERIAL_FRAME_6N2: cflags = CS6 | CSTOPB; break; case SERIAL_FRAME_7N2: cflags = CS7 | CSTOPB; break; case SERIAL_FRAME_8N2: cflags = CS8 | CSTOPB; break; case SERIAL_FRAME_5O1: cflags = CS5 | PARENB | PARODD; break; case SERIAL_FRAME_6O1: cflags = CS6 | PARENB | PARODD; break; case SERIAL_FRAME_7O1: cflags = CS7 | PARENB | PARODD; break; case SERIAL_FRAME_8O1: cflags = CS8 | PARENB | PARODD; break; case SERIAL_FRAME_5O2: cflags = CS5 | CSTOPB | PARENB | PARODD; break; case SERIAL_FRAME_6O2: cflags = CS6 | CSTOPB | PARENB | PARODD; break; case SERIAL_FRAME_7O2: cflags = CS7 | CSTOPB | PARENB | PARODD; break; case SERIAL_FRAME_8O2: cflags = CS8 | CSTOPB | PARENB | PARODD; break; case SERIAL_FRAME_5E1: cflags = CS5 | PARENB; break; case SERIAL_FRAME_6E1: cflags = CS6 | PARENB; break; case SERIAL_FRAME_7E1: cflags = CS7 | PARENB; break; case SERIAL_FRAME_8E1: cflags = CS8 | PARENB; break; case SERIAL_FRAME_5E2: cflags = CS5 | CSTOPB | PARENB; break; case SERIAL_FRAME_6E2: cflags = CS6 | CSTOPB | PARENB; break; case SERIAL_FRAME_7E2: cflags = CS7 | CSTOPB | PARENB; break; case SERIAL_FRAME_8E2: cflags = CS8 | CSTOPB | PARENB; break; } cfsetspeed(&tcattr, speed); tcattr.c_cflag = (tcattr.c_cflag & ~(CSIZE | CSTOPB | PARENB | PARODD)) | cflags; #ifdef CRTSCTS if (flow == SERIAL_FLOW_RTSCTS) tcattr.c_iflag |= CRTSCTS; else tcattr.c_iflag &= ~CRTSCTS; #endif if (flow == SERIAL_FLOW_XONOFF) tcattr.c_iflag |= IXON; else tcattr.c_iflag &= ~IXON; tcattr.c_cc[VSTART] = SERIAL_FLOW_XONOFF_START; tcattr.c_cc[VSTOP] = SERIAL_FLOW_XONOFF_STOP; tcattr.c_oflag &= ~OPOST; tcflush(_fd, TCIOFLUSH); tcsetattr(_fd, TCSANOW, &tcattr); _timeout.tv_sec = (timeout / 1000); _timeout.tv_usec = (timeout % 1000) * 1000; #else DCB dcb; COMMTIMEOUTS tov; HANDLE hSerial = (HANDLE) _get_osfhandle(_fd); memset(&dcb, 0, sizeof(DCB)); dcb.DCBlength = sizeof(DCB); GetCommState(hSerial, &dcb); dcb.BaudRate = speed; dcb.ByteSize = (frame & 3) + 5; dcb.StopBits = (frame >> 1) & 2; dcb.Parity = (frame >> 3); dcb.fDtrControl = (flow == SERIAL_FLOW_RTSCTS ? DTR_CONTROL_HANDSHAKE : DTR_CONTROL_DISABLE); dcb.fRtsControl = (flow == SERIAL_FLOW_RTSCTS ? RTS_CONTROL_HANDSHAKE : DTR_CONTROL_DISABLE); dcb.fOutxCtsFlow = (flow == SERIAL_FLOW_RTSCTS); dcb.fOutxDsrFlow = (flow == SERIAL_FLOW_RTSCTS); dcb.fOutX = (flow == SERIAL_FLOW_XONOFF); dcb.fInX = (flow == SERIAL_FLOW_XONOFF); dcb.XonLim = SERIAL_FLOW_XONOFF_LIMIT; dcb.XoffLim = SERIAL_FLOW_XONOFF_LIMIT; dcb.XonChar = SERIAL_FLOW_XONOFF_START; dcb.XoffChar = SERIAL_FLOW_XONOFF_STOP; dcb.fTXContinueOnXoff = TRUE; dcb.fDsrSensitivity = FALSE; dcb.fErrorChar = FALSE; dcb.fNull = FALSE; dcb.fAbortOnError = FALSE; dcb.fBinary = TRUE; SetCommState(hSerial, &dcb); PurgeComm(hSerial, PURGE_TXABORT | PURGE_RXABORT | PURGE_TXCLEAR | PURGE_RXCLEAR); memset(&tov, 0, sizeof(tov)); tov.ReadTotalTimeoutConstant = timeout; tov.WriteTotalTimeoutConstant = timeout; SetCommTimeouts(hSerial, &tov); #endif } public: Serial(void) : _fd(-1) { } virtual ~Serial(void) { close(); } virtual int open(const char *device, unsigned long speed = 115200, SERIAL_FRAME frame = SERIAL_FRAME_8N1, SERIAL_FLOW flow = SERIAL_FLOW_NONE, unsigned long timeout = 1000) { char path[32]; #ifdef __linux__ snprintf(path, sizeof(path), "/dev/%s", device); if ((_fd = ::open(path, O_RDWR | O_NOCTTY)) != -1) #else snprintf (path, sizeof(path), "\\\\.\\%s", device); if ((_fd = ::open(path, O_RDWR | _O_BINARY)) != -1) #endif config(speed, frame, flow, timeout); return _fd < 0 ? _fd : 0; } virtual int close(void) { int rv = 0; if (_fd >= 0) { #ifdef __linux__ tcsetattr(_fd, TCSANOW, &_tcattr); #endif rv = ::close(_fd); _fd = -1; } return rv; } virtual ssize_t read(void *buf, size_t len) { #ifdef __linux__ fd_set rdfds; FD_ZERO(&rdfds); FD_SET(_fd, &rdfds); int rv = select(_fd + 1, &rdfds, NULL, NULL, &_timeout); return (rv > 0 ? ::read(_fd, buf, len) : rv); #else return ::read(_fd, buf, len); #endif } virtual ssize_t write(const void *buf, size_t len) { #ifdef __linux__ fd_set wrfds; FD_ZERO(&wrfds); FD_SET(_fd, &wrfds); int rv = select(_fd + 1, NULL, &wrfds, NULL, &_timeout); return (rv > 0 ? ::write(_fd, buf, len) : rv); #else return ::write(_fd, buf, len); #endif } }; #endif |
【ダウンロード】
boot loader: MPLABX-project
pc-utility: eclipse-cdt(mingw)-project