Micronucleus(v-usb)というDigispark(ATtiny85)などで使われているソフトウェアでUSB通信を実現する有名なプログラムがある。今まで何度も利用してきたがどう実現しているのかについての興味や未だにUSBはよくわからんということもあったりするので理解を深めるために勉強がてら自作に挑戦してみた。
Micronucleus(v-usb)のようにクロック別にソースを分けずクロックの動的補正により15MHz以上の任意クロックでの動作や12MHzでも動作するという目標で考えてみた。
12MHzで動作するにはUSB-1bitあたり8サイクル以下で処理しなければならず同時にUSBの符号化処理(NRZI/BIT-STUFFING)も行う必要がある。当初はUSBの符号化処理を別にすれば楽勝じゃないかとも思ったが実際やってみると別にした符号化処理のオーバーヘッドによりUSBプロトコルのリトライに間に合わなくなり完全にボツ。理想と現実のギャップを感じてしまう結果となってしまった。
初めてMicronucleus(v-usb)のソースを見たとき複雑なコードだなって感じしかなかったけど自分でやって見て改めてMicronucleus(v-usb)のソースを見直してみるとMicronucleus(v-usb)の開発者達はとても素晴らしい仕事をしたんだなと感心するばかりだ。
まずはクロックについてであるがUSBのクロック誤差は+-0.25%以下となっているがAVRのRCクロックは+-10%も誤差がある。USB1.1のクロック(1.5MHz)に換算すると12MHzでは+-2.0%(0.25×8)以下に抑える必要があるがRCオシレータの校正ステップは1.5%程度と結構荒くて絶妙な調整が必要だ。
UARTであれば10bit程度なのでかなりの誤差を許容できるがUSB1.1の最大ビット数は96bit(12×8)であり最終誤差が96倍にもなってしまう。なのでクロック誤差を許容値内に収められるかどうかが極めて重要となってくる。
次はRCオシレータの校正処理であるが1ms毎に出力されるUSBのkeep alive signalにより校正している。余計に校正しすぎてから最終調整するところがミソかも。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
uint8_t osc = OSCCAL; int16_t pos, neg; pos = neg = 0; for (uint8_t i = 0; i < 8; ++i) { usb = usbMeasureFrameLength(); if (usb == 0) break; int16_t x = usb - (int16_t)(F_CPU * 1 / 1000 / 6 + 0.5); // 1ms if (x < 0) { if ((neg = -x) < pos) break; ++OSCCAL; } else { if ((pos = x) < neg) break; --OSCCAL; } } if ((pos == 0) || (neg == 0)) OSCCAL = osc; |
ちなみにDigisparkが16.5Mhzという中途半端?なクロックになっているのはCPUクロックをUSBクロックの整数倍にすることでクロック誤差がでないようにするための処置である。
今回は全てC++で記述している。なーんて言いつつinline-asmを多用してたりするが、そのinline-asmの記述の仕方が少しイレギュラーだ。でもこのほうがすっきり書けて見通しが良くなる。但し、試してみたら意図した結果になったというだけで公式な記述方法ではないことには注意したほうがいいかもしれない。
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 |
uint16_t SWUSB::usbMeasureFrameLength(void) { ASM(".equ INPORT, %0" :: "I" (_SFR_IO_ADDR(USB_INPORT))); ASM(".equ DM , %0" :: "M" (USB_LINE_DM)); ASM(".equ cntH, 25"); ASM(".equ cntL, 24"); ASM("clr cntL"); ASM("clr cntH"); ASM("1:"); ASM("adiw cntL, 1"); ASM("breq 9f"); ASM("sbic INPORT, DM"); ASM("rjmp 1b"); ASM("clr cntL"); ASM("clr cntH"); ASM("2:"); ASM("adiw cntL, 1"); ASM("breq 9f"); ASM("sbis INPORT, DM"); ASM("rjmp 2b"); ASM("3:"); ASM("adiw cntL, 1"); ASM("breq 9f"); ASM("sbic INPORT, DM"); ASM("rjmp 3b"); ASM("9:"); ASM("ret"); } |
クロックの動的補正処理は次のように行う。CPUクロック/USBクロックの整数部分をループとNOPの組み合わせにより処理し、小数部分は1バイトに収まるよう1/256(0.39%/F_CPU)の精度で処理している。レジスタに誤差を蓄積しオーバーフロー毎に1サイクル追加するという動作を繰り返すだけだ。誤差の初期値をバイトの中間値の128とすることで+-0.5サイクル以内、数十ナノ秒程度の誤差に収めることができる。
1 2 |
ASM("subi r18, %0" :: "M" (ERROR(f)) : "r18"); \ ASM("brcs .+0"); \ |
誤差補正処理はC/C++のコンパイル時評価+最適化機能により必要なinline-asmだけを組み合わせるコンパイル時コード生成(勝手に命名)という手法を使っている。コンパイラのデッドコード削除を積極的に利用することで必要なコードだけを残す方法である。下記の#define内のコードが全て展開されるわけではなく引数評価により到達可能なinline-asmのみが展開され実行する意味のないif文や変数は除去される。但し、定数評価できる引数を指定する必要がある。
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 |
※ f=1500000 #define CYCLE(f) ((float)F_CPU / (f)) #define ERROR(f) (int)((CYCLE(f) - (int)CYCLE(f)) * 256) #define SETUP() \ ASM("ldi r18, 128" ::: "r18") #define DELAY(f, c) \ do { \ int delay = CYCLE(f) - (c); \ if ((delay >= 2) && (CYCLE(f) >= 10)) { \ ASM("subi r18, %0" :: "M" (ERROR(f)) : "r18"); \ ASM("brcs .+0"); \ delay -= 2; \ } \ DELAY_LOOP(delay); \ } \ while (0) #define DELAY_CYCLE 3 // cycle of delay loop #define DELAY_LOOP(c) \ do { \ int count = c; \ if (count / DELAY_CYCLE) { \ ASM("ldi r19, %0" :: "M" (count / DELAY_CYCLE) : "r19"); \ ASM("dec r19"); \ ASM("brne .-4"); \ } \ if ((count % DELAY_CYCLE) > 0) \ ASM("nop"); \ if ((count % DELAY_CYCLE) > 1) \ ASM("nop"); \ } \ while (0) |
送信処理の基本ロジックはbit-stuffingありなしでクロックが乱れないよう工夫している。基本ロジックは6サイクルで処理が完了するが送信データをロードするだけでも2サイクル追加となるため余裕はない。他に必要な処理は各ビット処理の残りの2サイクルに分散配置している。
ちなみにcall/retではなくbrsh(2)/rjmp(2)を使っているのはcall(3)/ret(4)だとサイクル数が大きすぎて使えないから。
※ATtiny1614などのAVR1シリーズではシングルサイクルの出力反転機能が削除されてしまった(改悪だ!)ため1サイクル追加となり16.5MHz以上でないとクロック動的補正することができない。20MHzで実行できるから特に問題ではないんだけど...モヤッと感は残る...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
// bit-1 stuffing ASM("121:"); ASM("clr stuf"); DELAY(USB_SPEED, 5); ASM("out TGLPORT, line"); DELAY(USB_SPEED, 6); ASM("rjmp 221f"); ...(省略)... // bit-1 ASM("cpi stuf, 0xFC"); // BIT-STUFFING ASM("brsh 121b"); // DELAY(USB_SPEED, 6); ASM("221:"); ASM("lsr data"); ASM("ror stuf"); ASM("sbrs stuf, 7"); // NRZI ASM("out TGLPORT, line"); // ...(省略)... |
受信処理の基本ロジックはMicronucleus(v-usb)とほぼ同じコードになっている。送信処理と同じく基本ロジックは6サイクルで他に必要な処理は各ビット処理の残りの2サイクルに分散配置している。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
// bit-1 unstuffing ASM("121:"); ASM("ori data, 0x01"); ASM("andi stuf, ~0x01"); ASM("mov bus1, bus2"); DELAY(USB_SPEED, 8); ASM("in bus2, INPORT"); DELAY(USB_SPEED, 6); ASM("rjmp 221f"); ...(省略)... // bit-1 ASM("in bus2, INPORT"); ASM("bld data, 0"); // bit-0 code ASM("andi data, 0xF9"); // BIT-STUFFING ASM("breq 121b"); // DELAY(USB_SPEED, 7); ASM("221:"); ASM("eor bus1, bus2"); // NRZI ASM("bst bus1, DM"); // ASM("bld data, 1"); // ...(省略)... |
CRC計算はMicronucleus(v-usb)で使用されているものを有難く使わせてもらったがデータコピーとCRC計算を同時にできるように改良してある。この改良により送信パケット生成処理に必要なクロック数を半減させることができる。
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 |
/* * This implementation is faster, but has bigger code size * This version has been optimized in size compared to the original * fast CRC by Shay Green, January 2014. * * Thanks to Slawomir Fras (BoskiDialer) for the original contribution. * It implements the following C pseudo-code: * unsigned table(unsigned char x) * { * unsigned value; * * value = (unsigned)x << 6; * value ^= (unsigned)x << 7; * if(parity(x)) * value ^= 0xc001; * return value; * } * unsigned usbCrc16(unsigned char *src, unsigned char len) * { * unsigned crc = 0xffff; * * while(len--) * crc = table(lo8(crc) ^ *src++) ^ hi8(crc); * return ~crc; * } */ uint16_t SWUSB::crc16(uint8_t *dst, const uint8_t *src, uint8_t len, bool pgm) { (void)dst; // r24:r25 (void)src; // r22:r23 (void)len; // r20 (void)pgm; // r18 ASM(".equ src , 30"); // Z register ASM(".equ dst , 26"); // X register ASM(".equ resCrcH, 25"); ASM(".equ resCrcL, 24"); // return value ASM(".equ bitCnt , 23"); ASM(".equ scratch, 22"); ASM(".equ byte , 21"); ASM(".equ len , 20"); ASM(".equ copy , 19"); ASM(".equ pgm , 18"); ASM("movw src, r22"); ASM("movw dst, r24"); ASM("mov copy, r24"); ASM("or copy, r25"); ASM("ldi resCrcL, 0xFF"); ASM("ldi resCrcH, 0xFF"); ASM("clr bitCnt"); // zero ASM("rjmp 9f"); ASM("1:"); ASM("tst pgm"); ASM("brne 2f"); ASM("ld byte, z+"); ASM("rjmp 3f"); ASM("2:"); ASM("lpm byte, z+"); ASM("3:"); ASM("cpse copy, __zero_reg__"); ASM("st x+, byte"); ASM("eor byte, resCrcL"); // scratch is now 'x' in table() ASM("mov scratch, byte"); // compute parity of 'x' ASM("swap byte"); ASM("eor byte, scratch"); ASM("mov resCrcL, byte"); ASM("lsr byte"); ASM("lsr byte"); ASM("eor byte, resCrcL"); ASM("inc byte"); ASM("andi byte, 2"); // byte is now parity(x) << 1 ASM("cp bitCnt, byte"); // c = (byte != 0), then put in high bit ASM("ror scratch"); // so that after xoring, shifting, and xoring, it gives ASM("ror byte"); // the desired 0xC0 with resCrcH ASM("mov resCrcL, byte"); ASM("eor resCrcL, resCrcH"); ASM("mov resCrcH, scratch"); ASM("lsr scratch"); ASM("ror byte"); ASM("eor resCrcH, scratch"); ASM("eor resCrcL, byte"); ASM("9:"); ASM("subi len, 1"); ASM("brsh 1b"); ASM("com resCrcL"); ASM("com resCrcH"); ASM("ret"); } |
コーディングもほぼ完了したので試してみたら全く動かない。USBプロトコル処理のタイミング規定というものがあるのかどうかはわからないが送信タイミングが想定外でかなりハマってしまった。ホスト側のリトライ送信間隔が最短で20usと非常に短くてCRCを含む送信パケットを組み立ててる途中でホスト側がリトライ送信をかけるためデバイス側の送信とカチあったりしてしまうのだ。このへんは割込み処理で受信すれば必然的に避けられそうなのだが割込み対応すると何かと面倒くさいことになるためポーリングによるバス調停により回避してみた。ちなみにMicronucleus(v-usb)もポーリングによるバス調停をしている。
動き始めたときは2Kバイト程度に収まっていたのだが、その後にちょっとした対策などを入れてたら2.5Kくらいになってしまった。2Kバイトを切っているMicronucleus(v-usb)は凄いなと改めて思ったり...でも、クロック動的補正などを入れたからMicronucleus(v-usb)より大きくなるのは当然か...
【概要仕様】
Digispark(Micronucleus)ブートローダーと互換仕様。Digisparkを追加したArduino環境からDigisparkとして書き込みできる。
ブートローダー自体は、AVRシリーズ共通かつ下記クロックの範囲内で動作するようにしたつもり。
AVR 12.0MHz/13.5MHz and 15.0MHz-20.0MHz
AVR1 13.5MHz/15.0MHz and 16.5MHz-20.0MHz
※ATtiny85(16MHz)でのみ検証している。その他環境は未試験であることに注意すべし!
※USBがPCに接続されていない(給電のみ)場合は5秒待たずにアプリを即座に起動するように改良している。
【ブートローダー書き換え】
Digisparkを追加したArduinoから書き込むとブートローダーを書き換えてくれる。
bootcode.zip (Arduino Project)
【ブートローダー書き換えソースコード生成ツール】
ブートローダー書き換え(Arduino)のソースコードを生成するためのツール。ブートローダーのHEXファイル(swusb.hex)を読み込んでbootcode.c/bootcode.hを生成する。
bootcode-eclipse.zip (Eclipse-CDT Project)
【ブートローダー開発環境】
開発はAtme Studioで行った。ブートローダーのコードをフラッシュの後方アドレスに移動し正しいリセットベクターを設定するためにリンカーのMemory SettingsのFlash segmentの指定が必要。アドレスは64(128)バイト境界アドレスをwordアドレスに変換した値を設定すること。
なぜかはわからないがAtme Studio起動直後のコンパイルが失敗するが2回目以降は問題なくコンパイルできる。なんかおかしなことをしているのかな?
Atme Studio起動直後のコンパイルが失敗するのは、Memory SettingsのFlash segmentの設定順の問題だった。既存のセグメントを移動する前にリセットベクターアドレスを指定してしまうと領域が重なるためにエラーになるみたい...
swusb.zip (Atmel Studio Project)
【ブートローダーライブラリ】
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 |
/* main.cpp - Software USB1.1 Device Library (Bootloader for Micronucleus) Copyright (c) 2022 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 <avr/io.h> #include <avr/wdt.h> #include "swusb.h" static void reset(void) __attribute__((naked, section(".reset"))); static void reset(void) { #if FLASHEND < 0x2000 asm volatile ("rjmp __vectors"); #else asm volatile ("jmp __vectors"); #endif } uint8_t mcusr; int main(void) { #if defined(MCUSR) mcusr = MCUSR; MCUSR = 0; wdt_disable(); /* not hardware reset ? */ if (mcusr == 0) { wdt_enable(WDTO_15MS); while (1) ; } #else mcusr = RSTCTRL_RSTFR; RSTCTRL_RSTFR = 0xFF; wdt_disable(); /* not hardware reset ? */ if (mcusr == 0) RSTCTRL_SWRR = RSTCTRL_SWRE_bm; if ((CLKCTRL_MCLKCTRLA & CLKCTRL_CLKSEL_gm) == (CLKCTRL_CLKSEL_OSC20M_gc << CLKCTRL_CLKSEL_gp)) { CPU_CCP = CCP_IOREG_gc; CLKCTRL_MCLKCTRLB = 0; // no prescaler } #endif SWUSB::boot(); reset(); } |
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 |
/* swusb.h - Software USB1.1 Device Library (Bootloader for Micronucleus) Copyright (c) 2022 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 */ #pragma once #include <stdint.h> #include <stdbool.h> class SWUSB { public: static void connect(void); static void disconnect(void); static void boot(void); static bool connected(void); protected: typedef struct __attribute__((packed)) { uint8_t rcpt: 5; uint8_t type: 2; uint8_t dir : 1; } request_type_t; typedef struct __attribute__((packed)) { request_type_t bmRequestType; uint8_t bRequest; uint16_t wValue; uint16_t wIndex; uint16_t wLength; } request_t; static void reset(void); static void control(const uint8_t *buf, uint8_t len); static void setup(const request_t *req); static void command(const request_t *req); static void command(void); static void startApp(void); static void handshake(uint8_t pid); static void response(const uint8_t *buf, uint8_t len, bool pgm = false); static void buildpacket(void); static void transmit(void); static void poll(uint8_t *buf, uint8_t len) __attribute__((noinline, optimize(1))); static uint8_t read(uint8_t *buf, uint8_t len) __attribute__((noinline, optimize(1), naked)); static void write(const uint8_t *buf, uint8_t len) __attribute__((noinline, optimize(1), naked)); static uint16_t crc16(uint8_t *dst, const uint8_t *src, uint8_t len, bool pgm) __attribute__((noinline, optimize(1), naked)); static void usbBusArbitration(void) __attribute__((noinline, optimize(1), naked)); static uint16_t usbMeasureFrameLength(void) __attribute__((noinline, optimize(1), naked)); static bool calibration(void) __attribute__((noinline)); }; |
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 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 |
/* swusb.cpp - Software USB1.1 Device Library (Bootloader for Micronucleus) Requires CPU Clock AVR 12.0MHz/13.5MHz and 15.0MHz-20.0MHz AVR1 13.5MHz/15.0MHz and 16.5MHz-20.0MHz Copyright (c) 2022 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 <string.h> #include <avr/io.h> #include <avr/pgmspace.h> #include <avr/interrupt.h> #include <util/delay.h> #include "nvmctrl.h" #include "swusb.h" #define BOOTLOADER_ADDRESS ((uint16_t)&__vectors) #define FLASH_WRITE_SLEEP 5 #define DEVICE_SIGNATURE 0x1e930b #if SPM_PAGESIZE > 128 #error "Micronucleus only supports pagesizes up to 128 bytes" #endif /* * USB Setting's * * #define USB_IO_PORT B // USB Port * #define USB_LINE_DM 3 // USB Pin (D-) * #define USB_LINE_DP 4 // USB Pin (D+) * #define USB_LED_PORT B // LED Port * #define USB_LED_PIN 1 // LED Pin */ #if defined(ARDUINO_AVR_DIGISPARK) #define USB_IO_PORT B #define USB_LINE_DM 3 #define USB_LINE_DP 4 #define USB_LED_PORT B #define USB_LED_PIN 1 #endif #if !defined(USB_IO_PORT) || !defined(USB_LINE_DM) || !defined(USB_LINE_DP) #error "USB_IO_PORT, USB_LINE_DMM, USB_LINE_DP Symbol not defined." #endif #define AUTO_EXIT_MS 5000 #define LED_BLINK_MS 100 #define USB_SPEED 1500000UL #define USB_PACKET_SIZE 8 #define USB_BUFFER_SIZE (1 + USB_PACKET_SIZE + 2) #define USB_LINE_MASK (_BV(USB_LINE_DM) | _BV(USB_LINE_DP)) #define USB_CONCAT2(a, b) a ## b #define USB_CONCAT3(a, b, c) a ## b ## c #define USB_PORT2(a, b) USB_CONCAT2(a, b) #define USB_PORT3(a, b, c) USB_CONCAT3(a, b, c) #define ASM __asm__ volatile #if defined(VPORTA) #define LED_DDRPORT USB_PORT3(VPORT, USB_LED_PORT, _DIR) #define LED_OUTPORT USB_PORT3(VPORT, USB_LED_PORT, _OUT) #define LED_TGLPORT USB_PORT3(PORT , USB_LED_PORT, _OUTTGL) #define USB_DDRPORT USB_PORT3(VPORT, USB_IO_PORT, _DIR) #define USB_OUTPORT USB_PORT3(VPORT, USB_IO_PORT, _OUT) #define USB_TGLPORT USB_PORT3(VPORT, USB_IO_PORT, _IN ) /* not used. dummy!! */ #define USB_INPORT USB_PORT3(VPORT, USB_IO_PORT, _IN ) #define OUTTGL(bit) \ do { \ ASM("eor bus, " # bit); \ ASM("out OUTPORT, bus"); \ } while (0) #define OUTTGL_ADD_CYCLE 1 #else #define LED_DDRPORT USB_PORT2(DDR , USB_LED_PORT) #define LED_OUTPORT USB_PORT2(PORT, USB_LED_PORT) #define LED_TGLPORT USB_PORT2(PIN , USB_LED_PORT) #define USB_DDRPORT USB_PORT2(DDR , USB_IO_PORT) #define USB_OUTPORT USB_PORT2(PORT, USB_IO_PORT) #define USB_TGLPORT USB_PORT2(PIN , USB_IO_PORT) #define USB_INPORT USB_PORT2(PIN , USB_IO_PORT) #define OUTTGL(bit) \ do { \ ASM("out TGLPORT, " # bit); \ } while (0) #define OUTTGL_ADD_CYCLE 0 #endif #if defined(USB_LED_PORT) && defined(USB_LED_PIN) #define led_begin() \ do { \ LED_OUTPORT &= ~_BV(USB_LED_PIN); \ LED_DDRPORT |= _BV(USB_LED_PIN); \ } while (0) #define led_end() \ do { \ LED_OUTPORT &= ~_BV(USB_LED_PIN); \ LED_DDRPORT &= ~_BV(USB_LED_PIN); \ } while (0) #define led_on() \ do { \ LED_OUTPORT |= _BV(USB_LED_PIN); \ } while (0) #define led_off() \ do { \ LED_OUTPORT &= ~_BV(USB_LED_PIN); \ } while (0) #if defined(VPORTA) #define led_toggle() \ do { \ LED_TGLPORT = _BV(USB_LED_PIN); \ } while (0) #else #define led_toggle() \ do { \ LED_TGLPORT |= _BV(USB_LED_PIN); \ } while (0) #endif #else #define led_begin() #define led_end() #define led_on() #define led_off() #define led_toggle() #endif #define CYCLE(f) ((float)F_CPU / (f)) #define ERROR(f) (int)((CYCLE(f) - (int)CYCLE(f)) * 256) #define SETUP() \ ASM("ldi r18, 128" ::: "r18") #define DELAY(f, c) \ do { \ int delay = CYCLE(f) - (c); \ if ((delay >= 2) && (CYCLE(f) >= 10 + OUTTGL_ADD_CYCLE)) { \ ASM("subi r18, %0" :: "M" (ERROR(f)) : "r18"); \ ASM("brcs .+0"); \ delay -= 2; \ } \ DELAY_LOOP(delay); \ } \ while (0) #define DELAY_CYCLE 3 // cycle of delay loop #define DELAY_LOOP(c) \ do { \ int count = c; \ if (count / DELAY_CYCLE) { \ ASM("ldi r19, %0" :: "M" (count / DELAY_CYCLE) : "r19"); \ ASM("dec r19"); \ ASM("brne .-4"); \ } \ if ((count % DELAY_CYCLE) > 0) \ ASM("nop"); \ if ((count % DELAY_CYCLE) > 1) \ ASM("nop"); \ } \ while (0) #define SBIS_INPORT_DM(label) \ do { \ ASM("sbis INPORT, DM"); \ ASM("rjmp " # label); \ } \ while (0) #define SBIC_INPORT_DM(label) \ do { \ ASM("sbic INPORT, DM"); \ ASM("rjmp " # label); \ } \ while (0) #define REPEAT(us, code) \ do { \ int count = CYCLE(1000000) * us / 2; \ if (count > 0) { code; } \ if (count > 1) { code; } \ if (count > 2) { code; } \ if (count > 3) { code; } \ if (count > 4) { code; } \ if (count > 5) { code; } \ if (count > 6) { code; } \ if (count > 7) { code; } \ if (count > 8) { code; } \ if (count > 9) { code; } \ if (count > 10) { code; } \ if (count > 11) { code; } \ } \ while (0) #define USB_SYNC 0b10000000 typedef enum { PID_SOF = 0b10100101, PID_OUT = 0b11100001, PID_IN = 0b01101001, PID_SETUP = 0b00101101, PID_DATA0 = 0b11000011, PID_DATA1 = 0b01001011, PID_ACK = 0b11010010, PID_NACK = 0b01011010, PID_STALL = 0b00011110, } PID; #define PID_DATA_XOR (PID_DATA0 ^ PID_DATA1) typedef enum { REQ_GET_STATUS = 0x00, // common REQ_CLEAR_FEATURE = 0x01, REQ_SET_FEATURE = 0x03, REQ_SET_ADDRESS = 0x05, // Standard Device Request REQ_GET_DESCRIPTOR = 0x06, REQ_SET_DESCRIPTOR = 0x07, REQ_GET_CONFIG = 0x08, REQ_SET_CONFIG = 0x09, REQ_GET_INTERFACE = 0x0a, // Standard Interface Request REQ_SET_INTERFACE = 0x0b, } REQ; typedef enum { DES_DEVICE = 0x01, DES_CONFIG = 0x02, DES_STRING = 0x03, DES_INTERFACE = 0x04, DES_ENDPOINT = 0x05, } descriptor_type_t; typedef enum { RQT_RCPT_DEVICE = 0, RQT_RCPT_INTERFACE = 1, RQT_RCPT_ENDPOINT = 2, RQT_TYPE_STANDARD = 0, RQT_TYPE_CLASS = 1, RQT_TYPE_VENDOR = 2, RQT_DIR_HOST_TO_DEVICE = 0, RQT_DIR_DEVICE_TO_HOST = 1, } RQT; typedef enum { CMD_CONNECT = 0, CMD_PAGE_ADDR = 1, CMD_ERASE_ALL = 2, CMD_WRITE_DATA = 3, CMD_START_APP = 4, CMD_WRITE_PAGE = 5, } CMD; /* USB device descriptor */ static const uint8_t DEVICE[] PROGMEM = { 18, // length of descriptor in bytes DES_DEVICE, // descriptor type 0x10, 0x01, // USB version supported 0xFF, // device class (0xFF = vendor specific class) 0, // device sub class 0, // protocol USB_PACKET_SIZE, // max packet size 0xD0, 0x16, // vendor id (0x16D0 = digispark) 0x53, 0x07, // device id (0x0753 = digispark) 4, 2, // device version (nucleus version 2.x) 1, // manufacturer string index 2, // product string index 0, // serial number string index 1, // number of configurations }; /* USB configuration descriptor */ static const uint8_t CONFIG[] PROGMEM = { 9, // length of descriptor in bytes DES_CONFIG, // descriptor type 9 + 9, 0, // total length of data returned (including inlined descriptors) 1, // number of interfaces in this configuration 1, // index of this configuration 0, // configuration name string index 0x80, // attributes 100 / 2, // max USB current in 2mA units /* interface descriptor */ 9, // length of descriptor in bytes DES_INTERFACE, // descriptor type 0, // index of this interface 0, // alternate setting for this interface 0, // endpoints excl 0: number of endpoint descriptors to follow 0, // interface class 0, // interface sub class 0, // interface protocol 0, // string index for interface }; /* string descriptors */ static const uint8_t STRING_LANG[] PROGMEM = { 1 * sizeof(wchar_t) + 2, // length of descriptor in bytes DES_STRING, // descriptor type 0x09, 0x04, // language index (0x0409 = US-English) }; static const uint8_t STRING_VENDOR[] PROGMEM = { 13 * sizeof(wchar_t) + 2, // length of descriptor in bytes DES_STRING, // descriptor type 'S', 0, 'a', 0, 's', 0, 'a', 0, 'p', 0, 'e', 0, 'a', 0, '\'', 0, 's', 0, ' ', 0, 'L', 0, 'a', 0, 'b', 0, }; static const uint8_t STRING_PRODUCT[] PROGMEM = { 5 * sizeof(wchar_t) + 2, // length of descriptor in bytes DES_STRING, // descriptor type 's', 0, 'w', 0, 'u', 0, 's', 0, 'b', 0, }; extern const char *__vectors; // start address of this program static uint8_t _address; static uint8_t _datapid; static uint8_t _rxbuf[USB_BUFFER_SIZE]; static uint8_t _txbuf[USB_BUFFER_SIZE]; static const uint8_t *_txptr; static uint8_t _txcnt; static uint8_t _txlen; static bool _txpgm; static uint8_t _command; static uint8_t _ack; static uint16_t _pgaddr; static uint16_t _pgvect[2]; void SWUSB::boot(void) { _pgvect[0] = pgm_read_word(0); _pgvect[1] = FLASHEND < 0x2000 ? 0xFFFF : pgm_read_word(2); led_begin(); connect(); if (!calibration()) startApp(); while (1) { poll(_rxbuf, sizeof(_rxbuf)); startApp(); } } void SWUSB::reset(void) { _address = 0; _datapid = PID_DATA1; _txptr = _txbuf; _txcnt = 0; _txlen = 0; _txpgm = false; _pgaddr = 0; _command = 0; } void SWUSB::connect(void) { USB_DDRPORT &= ~USB_LINE_MASK; USB_OUTPORT &= ~USB_LINE_MASK; reset(); } void SWUSB::disconnect(void) { USB_OUTPORT &= ~USB_LINE_MASK; USB_DDRPORT |= USB_LINE_MASK; _delay_ms(10); USB_OUTPORT &= ~USB_LINE_MASK; } void SWUSB::control(const uint8_t *buf, uint8_t len) { request_t *req; switch (buf[0]) { case PID_DATA0: case PID_DATA1: _datapid = buf[0]; if (len == 3) handshake(PID_ACK); else if (len == USB_PACKET_SIZE + 3) { req = (request_t *)(buf + 1); if (req->bmRequestType.type == RQT_TYPE_VENDOR) command(req); else { switch (req->bRequest) { case REQ_GET_DESCRIPTOR: setup(req); break; case REQ_SET_ADDRESS: _address = req->wValue; response(0, 0); break; } } } break; case PID_IN: transmit(); break; case PID_ACK: command(); buildpacket(); break; } } void SWUSB::setup(const request_t *req) { const uint8_t *buf = 0; uint8_t len = 0; switch ((uint8_t)(req->wValue >> 8)) { case DES_DEVICE: buf = DEVICE; len = sizeof(DEVICE); break; case DES_CONFIG: buf = CONFIG; len = sizeof(CONFIG); break; case DES_STRING: switch ((uint8_t)(req->wValue >> 0)) { case 0: buf = STRING_LANG ; len = sizeof(STRING_LANG) ; break; case 1: buf = STRING_VENDOR ; len = sizeof(STRING_VENDOR) ; break; case 2: buf = STRING_PRODUCT; len = sizeof(STRING_PRODUCT); break; } break; } if (len) response(buf, len > req->wLength ? req->wLength : len, true); } void SWUSB::command(const request_t *req) { const uint8_t *buf = 0; uint8_t len = 0; switch (_command = req->bRequest) { case CMD_CONNECT: static uint8_t info[6]; // nucleus version 2.x info[0] = (uint8_t)(BOOTLOADER_ADDRESS >> 8); info[1] = (uint8_t)(BOOTLOADER_ADDRESS >> 0); info[2] = SPM_PAGESIZE; info[3] = FLASH_WRITE_SLEEP | ERASE_PAGE_OPT; info[4] = (uint8_t)(DEVICE_SIGNATURE >> 8); info[5] = (uint8_t)(DEVICE_SIGNATURE >> 0); buf = info; len = sizeof(info); break; case CMD_PAGE_ADDR: _pgaddr = req->wIndex; break; case CMD_WRITE_DATA: boot_page_fill_safe(_pgaddr + 0, req->wValue); boot_page_fill_safe(_pgaddr + 2, req->wIndex); if (((_pgaddr += 4) & (SPM_PAGESIZE - 1)) == 0) _command = CMD_WRITE_PAGE; break; } response(buf, len); } void SWUSB::command(void) { switch (_command) { case CMD_ERASE_ALL: _pgaddr = BOOTLOADER_ADDRESS; do { _pgaddr -= SPM_ERASE_PAGESIZE; boot_page_erase_safe(_pgaddr); } while (_pgaddr); boot_page_fill_safe(0, _pgvect[0]); boot_page_fill_safe(2, _pgvect[1]); boot_page_write_safe(0); break; case CMD_WRITE_PAGE: boot_page_write_safe(_pgaddr - SPM_PAGESIZE); break; case CMD_START_APP: startApp(); break; } _command = 0; } void SWUSB::startApp(void) { boot_rww_enable_safe(); uint16_t opcode = pgm_read_word(BOOTLOADER_ADDRESS - 4); if (opcode != 0xFFFF) { disconnect(); led_end(); cli(); asm volatile ("rjmp __vectors - 4"); // jump to application reset vector at end of flash } } bool SWUSB::connected(void) { return _address != 0; } void SWUSB::response(const uint8_t *buf, uint8_t len, bool pgm) { handshake(PID_ACK); _txptr = buf; _txlen = len; _txpgm = pgm; buildpacket(); } void SWUSB::handshake(uint8_t pid) { _ack = pid; write(&_ack, sizeof(_ack)); } void SWUSB::transmit(void) { write(_txbuf, _txcnt); } void SWUSB::buildpacket(void) { uint8_t len = _txlen > USB_PACKET_SIZE ? USB_PACKET_SIZE : _txlen; _txbuf[0] = (_datapid ^= PID_DATA_XOR); *(uint16_t *)(_txbuf + len + 1) = crc16(_txbuf + 1, _txptr, len, _txpgm); _txptr += len; _txlen -= len; _txcnt = len + 3; usbBusArbitration(); } void SWUSB::poll(uint8_t *buf, uint8_t len) { (void)buf; // r24:r25 (void)len; // r22 ASM(".equ INPORT, %0" :: "I" (_SFR_IO_ADDR(USB_INPORT))); ASM(".equ MASK , %0" :: "M" (USB_LINE_MASK)); ASM(".equ DM , %0" :: "M" (USB_LINE_DM)); ASM(".equ sreg , 13" ::: "r13"); ASM(".equ cntH , 14" ::: "r14"); ASM(".equ cntM , 15" ::: "r15"); ASM(".equ cntL , 16" ::: "r16"); ASM(".equ cnt0 , 17" ::: "r17"); ASM("in sreg, __SREG__"); ASM("1:"); ASM("ldi cnt0, %0" :: "M" ((AUTO_EXIT_MS / LED_BLINK_MS) & 0xFF)); ASM("2:"); led_toggle(); ASM("ldi cntL, %0" :: "M" (((F_CPU * LED_BLINK_MS / 1000 / 9) >> 16) & 0xFF)); ASM("mov cntH, cntL"); ASM("ldi cntL, %0" :: "M" (((F_CPU * LED_BLINK_MS / 1000 / 9) >> 8) & 0xFF)); ASM("mov cntM, cntL"); ASM("ldi cntL, %0" :: "M" (((F_CPU * LED_BLINK_MS / 1000 / 9) >> 0) & 0xFF)); ASM("rjmp 4f"); ASM("3:" ::: "r18", "r19", "r20", "r21", "r22", "r23", "r24", "r25", "r26", "r27", "r30", "r31"); // good luck charm !! uint8_t n = read(buf, len); ASM("tst %0" :: "r" (n)); ASM("breq 4f"); control(buf, n); ASM("4:"); ASM("cli"); ASM("sbis INPORT, DM"); ASM("rjmp 3b"); ASM("sei"); ASM("subi cntL, 1"); ASM("sbc cntM, __zero_reg__"); ASM("sbc cntH, __zero_reg__"); ASM("brne 4b"); ASM("lds r24, %0" :: "m" (_command)); ASM("tst r24"); ASM("brne 1b"); ASM("subi cnt0, 1"); ASM("brsh 2b"); ASM("out __SREG__, sreg"); } uint8_t SWUSB::read(uint8_t *buf, uint8_t len) { (void)buf; // r24:r25 (void)len; // r22 ASM(".equ INPORT , %0" :: "I" (_SFR_IO_ADDR(USB_INPORT))); ASM(".equ MASK , %0" :: "M" (USB_LINE_MASK)); ASM(".equ DM , %0" :: "M" (USB_LINE_DM)); ASM(".equ DP , %0" :: "M" (USB_LINE_DP)); ASM(".equ bus2, 31" ::: "r31"); ASM(".equ bus1, 30" ::: "r30"); ASM(".equ ptrH, 27" ::: "r27"); ASM(".equ ptrL, 26" ::: "r26"); // X register ASM(".equ cnt , 24" ::: "r24"); ASM(".equ stuf, 23" ::: "r23"); ASM(".equ len , 22" ::: "r22"); // return value ASM(".equ save, 21" ::: "r21"); ASM(".equ data, 20" ::: "r20"); // [SYNC] detect H level REPEAT(0.8, SBIC_INPORT_DM(101f)); // detect reset DELAY(USB_SPEED, 0); DELAY(USB_SPEED, 0); DELAY(USB_SPEED, 0); ASM("in bus1, INPORT"); ASM("andi bus1, MASK"); ASM("brne 100f"); reset(); ASM("100:"); ASM("rjmp 900f"); // -> timeouted. // [SYNC] detect L edge ASM("101:"); REPEAT(0.8, SBIS_INPORT_DM(102f)); ASM("rjmp 900f"); // -> timeouted. // [SYNC] check L level ASM("102:"); SETUP(); ASM("mov ptrL, r24"); ASM("mov ptrH, r25"); ASM("clr bus2"); ASM("clr save"); DELAY(USB_SPEED, 6); ASM("sbic INPORT, DM"); ASM("rjmp 101b"); // -> retry. ASM("clr cnt"); ASM("ldi data, ~0x80"); DELAY(USB_SPEED, 4); ASM("rjmp 210f"); // -> frame start // bit-7 unstuffing ASM("127:"); ASM("ori data, 0x40"); ASM("andi stuf, ~0x40"); ASM("mov bus1, bus2"); DELAY(USB_SPEED, 7); ASM("in bus2, INPORT"); DELAY(USB_SPEED, 8); ASM("rjmp 227f"); // bit-0 unstuffing ASM("120:"); ASM("ori data, 0x80"); ASM("mov bus2, bus1"); DELAY(USB_SPEED, 7); ASM("in bus1, INPORT"); DELAY(USB_SPEED, 7); ASM("rjmp 220f"); // bit-1 unstuffing ASM("121:"); ASM("ori data, 0x01"); ASM("andi stuf, ~0x01"); ASM("mov bus1, bus2"); DELAY(USB_SPEED, 8); ASM("in bus2, INPORT"); DELAY(USB_SPEED, 6); ASM("rjmp 221f"); // se0 ASM("190:"); ASM("rjmp 910f"); // bit-7 ASM("200:"); ASM("in bus2, INPORT"); ASM("cpi data, 0x02"); ASM("brlo 127b"); DELAY(USB_SPEED, 8); ASM("227:"); ASM("eor bus1, bus2"); ASM("bst bus1, DM"); ASM("bld data, 7"); ASM("eor stuf, data"); ASM("mov save, stuf"); // bit-0 ASM("210:"); ASM("in bus1, INPORT"); ASM("ldi stuf, 0xFF"); ASM("andi data, 0xFC"); ASM("breq 120b"); DELAY(USB_SPEED, 8); ASM("220:"); ASM("andi bus1, MASK"); ASM("breq 190b"); ASM("eor bus2, bus1"); ASM("bst bus2, DM"); // bit-1 ASM("in bus2, INPORT"); ASM("bld data, 0"); // bit-0 code ASM("andi data, 0xF9"); ASM("breq 121b"); DELAY(USB_SPEED, 7); ASM("221:"); ASM("eor bus1, bus2"); ASM("bst bus1, DM"); ASM("bld data, 1"); // bit-2 ASM("in bus1, INPORT"); ASM("andi data, 0xF3"); ASM("breq 822f"); DELAY(USB_SPEED, 8); ASM("222:"); ASM("eor bus2, bus1"); ASM("bst bus2, DM"); ASM("bld data, 2"); ASM("cp cnt, len"); ASM("brsh 190b"); // bit-3 ASM("in bus2, INPORT"); ASM("andi data, 0xE7"); ASM("breq 823f"); DELAY(USB_SPEED, 8); ASM("223:"); ASM("eor bus1, bus2"); ASM("bst bus1, DM"); ASM("bld data, 3"); ASM("st x+, save"); // bit-4 ASM("in bus1, INPORT"); ASM("andi data, 0xCF"); ASM("breq 824f"); DELAY(USB_SPEED, 8); ASM("224:"); ASM("eor bus2, bus1"); ASM("bst bus2, DM"); ASM("cpi cnt, 1"); ASM("sbc ptrL, __zero_reg__"); ASM("sbc ptrH, __zero_reg__"); // bit-5 ASM("in bus2, INPORT"); ASM("bld data, 4"); // bit-4 code ASM("andi data, 0x9F"); ASM("breq 825f"); DELAY(USB_SPEED, 8); ASM("225:"); ASM("eor bus1, bus2"); ASM("bst bus1, DM"); ASM("bld data, 5"); ASM("inc cnt"); // bit-6 ASM("in bus1, INPORT"); ASM("andi data, 0x3F"); ASM("breq 826f"); DELAY(USB_SPEED, 8); ASM("226:"); ASM("eor bus2, bus1"); ASM("bst bus2, DM"); ASM("bld data, 6"); ASM("rjmp 200b"); // bit-2 unstuffing ASM("822:"); ASM("ori data, 0x02"); ASM("andi stuf, ~0x02"); ASM("mov bus2, bus1"); DELAY(USB_SPEED, 7); ASM("in bus1, INPORT"); DELAY(USB_SPEED, 8); ASM("rjmp 222b"); // bit-3 unstuffing ASM("823:"); ASM("ori data, 0x04"); ASM("andi stuf, ~0x04"); ASM("mov bus1, bus2"); DELAY(USB_SPEED, 7); ASM("in bus2, INPORT"); DELAY(USB_SPEED, 8); ASM("rjmp 223b"); // bit-4 unstuffing ASM("824:"); ASM("ori data, 0x08"); ASM("andi stuf, ~0x08"); ASM("mov bus2, bus1"); DELAY(USB_SPEED, 7); ASM("in bus1, INPORT"); DELAY(USB_SPEED, 8); ASM("rjmp 224b"); // bit-5 unstuffing ASM("825:"); ASM("ori data, 0x10"); ASM("andi stuf, ~0x10"); ASM("mov bus1, bus2"); DELAY(USB_SPEED, 7); ASM("in bus2, INPORT"); DELAY(USB_SPEED, 7); ASM("rjmp 225b"); // bit-6 unstuffing ASM("826:"); ASM("ori data, 0x20"); ASM("andi stuf, ~0x20"); ASM("mov bus2, bus1"); DELAY(USB_SPEED, 7); ASM("in bus1, INPORT"); DELAY(USB_SPEED, 8); ASM("rjmp 226b"); ASM("900:"); ASM("clr cnt"); ASM("910:"); ASM("cpse cnt, __zero_reg__"); ASM("st x+, save"); ASM("920:"); ASM("sbis INPORT, DM"); ASM("rjmp 920b"); ASM("ret"); } void SWUSB::write(const uint8_t *buf, uint8_t len) { (void)buf; // r24:r25 (void)len; // r22 if (len == 0) return; SETUP(); ASM(".equ DDRPORT, %0" :: "I" (_SFR_IO_ADDR(USB_DDRPORT))); ASM(".equ OUTPORT, %0" :: "I" (_SFR_IO_ADDR(USB_OUTPORT))); ASM(".equ TGLPORT, %0" :: "I" (_SFR_IO_ADDR(USB_TGLPORT))); ASM(".equ INPORT , %0" :: "I" (_SFR_IO_ADDR(USB_INPORT))); ASM(".equ MASK , %0" :: "M" (USB_LINE_MASK)); ASM(".equ DM , %0" :: "M" (USB_LINE_DM)); ASM(".equ SYNC , %0" :: "M" (USB_SYNC)); ASM(".equ ptrH, 27" ::: "r27"); ASM(".equ ptrL, 26" ::: "r26"); // X register ASM(".equ bus , 25" ::: "r25"); ASM(".equ line, 24" ::: "r24"); ASM(".equ stuf, 23" ::: "r23"); ASM(".equ len , 22" ::: "r22"); ASM(".equ save, 21" ::: "r21"); ASM(".equ data, 20" ::: "r20"); ASM("mov ptrL, r24"); ASM("mov ptrH, r25"); ASM("ldi line, MASK"); ASM("in __tmp_reg__, __SREG__"); ASM("cli"); ASM("in bus, OUTPORT"); ASM("andi bus, ~MASK"); ASM("ori bus, 1 << DM"); ASM("out OUTPORT, bus"); ASM("in data, DDRPORT"); ASM("or data, line"); ASM("out DDRPORT, data"); ASM("ldi data, SYNC"); ASM("inc len"); ASM("clr stuf"); ASM("rjmp 210f"); // bit-7 stuffing ASM("127:"); ASM("clr stuf"); DELAY(USB_SPEED, 7 + OUTTGL_ADD_CYCLE); OUTTGL(line); DELAY(USB_SPEED, 6 + OUTTGL_ADD_CYCLE); ASM("rjmp 227f"); // bit-0 stuffing ASM("120:"); ASM("clr stuf"); DELAY(USB_SPEED, 7 + OUTTGL_ADD_CYCLE); OUTTGL(line); DELAY(USB_SPEED, 6 + OUTTGL_ADD_CYCLE); ASM("rjmp 220f"); // bit-1 stuffing ASM("121:"); ASM("clr stuf"); DELAY(USB_SPEED, 5 + OUTTGL_ADD_CYCLE); OUTTGL(line); DELAY(USB_SPEED, 6 + OUTTGL_ADD_CYCLE); ASM("rjmp 221f"); ASM("190:"); ASM("rjmp 900f"); // bit-7 ASM("200:"); ASM("cpi stuf, 0xFC"); ASM("brsh 127b"); DELAY(USB_SPEED, 8 + OUTTGL_ADD_CYCLE); ASM("227:"); ASM("lsr data"); ASM("ror stuf"); ASM("sbrs stuf, 7"); OUTTGL(line); ASM("brts 190b"); ASM("mov data, save"); // bit-0 ASM("210:"); ASM("cpi stuf, 0xFC"); ASM("brsh 120b"); DELAY(USB_SPEED, 8 + OUTTGL_ADD_CYCLE); ASM("220:"); ASM("lsr data"); ASM("ror stuf"); ASM("sbrs stuf, 7"); OUTTGL(line); // bit-1 ASM("cpi stuf, 0xFC"); ASM("brsh 121b"); DELAY(USB_SPEED, 6 + OUTTGL_ADD_CYCLE); ASM("221:"); ASM("lsr data"); ASM("ror stuf"); ASM("sbrs stuf, 7"); OUTTGL(line); // bit-2 ASM("cpi stuf, 0xFC"); ASM("brsh 822f"); DELAY(USB_SPEED, 6 + OUTTGL_ADD_CYCLE); ASM("222:"); ASM("lsr data"); ASM("ror stuf"); ASM("sbrs stuf, 7"); OUTTGL(line); ASM("dec len"); // bit-3 ASM("cpi stuf, 0xFC"); ASM("brsh 823f"); DELAY(USB_SPEED, 7 + OUTTGL_ADD_CYCLE); ASM("223:"); ASM("lsr data"); ASM("ror stuf"); ASM("sbrs stuf, 7"); OUTTGL(line); ASM("set"); // bit-4 ASM("cpi stuf, 0xFC"); ASM("brsh 824f"); DELAY(USB_SPEED, 7 + OUTTGL_ADD_CYCLE); ASM("224:"); ASM("lsr data"); ASM("ror stuf"); ASM("sbrs stuf, 7"); OUTTGL(line); ASM("cpse len, __zero_reg__"); ASM("clt"); // bit-5 ASM("cpi stuf, 0xFC"); ASM("brsh 825f"); DELAY(USB_SPEED, 8 + OUTTGL_ADD_CYCLE); ASM("225:"); ASM("lsr data"); ASM("ror stuf"); ASM("sbrs stuf, 7"); OUTTGL(line); ASM("ld save, x+"); // *** Note that it is over read by 1 byte. *** // bit-6 ASM("cpi stuf, 0xFC"); ASM("brsh 826f"); DELAY(USB_SPEED, 8 + OUTTGL_ADD_CYCLE); ASM("226:"); ASM("lsr data"); ASM("ror stuf"); ASM("sbrs stuf, 7"); OUTTGL(line); ASM("rjmp 200b"); // bit-2 stuffing ASM("822:"); ASM("clr stuf"); DELAY(USB_SPEED, 5 + OUTTGL_ADD_CYCLE); OUTTGL(line); DELAY(USB_SPEED, 6 + OUTTGL_ADD_CYCLE); ASM("rjmp 222b"); // bit-3 stuffing ASM("823:"); ASM("clr stuf"); DELAY(USB_SPEED, 6 + OUTTGL_ADD_CYCLE); OUTTGL(line); DELAY(USB_SPEED, 6 + OUTTGL_ADD_CYCLE); ASM("rjmp 223b"); // bit-4 stuffing ASM("824:"); ASM("clr stuf"); DELAY(USB_SPEED, 6 + OUTTGL_ADD_CYCLE); OUTTGL(line); DELAY(USB_SPEED, 6 + OUTTGL_ADD_CYCLE); ASM("rjmp 224b"); // bit-5 stuffing ASM("825:"); ASM("clr stuf"); DELAY(USB_SPEED, 7 + OUTTGL_ADD_CYCLE); OUTTGL(line); DELAY(USB_SPEED, 6 + OUTTGL_ADD_CYCLE); ASM("rjmp 225b"); // bit-6 stuffing ASM("826:"); ASM("clr stuf"); DELAY(USB_SPEED, 7 + OUTTGL_ADD_CYCLE); OUTTGL(line); DELAY(USB_SPEED, 6 + OUTTGL_ADD_CYCLE); ASM("rjmp 226b"); // last bit stuffing ASM("827:"); DELAY(USB_SPEED, 8 + OUTTGL_ADD_CYCLE); OUTTGL(line); DELAY(USB_SPEED, 4 + OUTTGL_ADD_CYCLE); ASM("rjmp 927f"); ASM("900:"); ASM("cpi stuf, 0xFC"); ASM("brsh 827b"); DELAY(USB_SPEED, 8); ASM("927:"); ASM("andi bus, ~MASK"); ASM("out OUTPORT, bus"); DELAY(USB_SPEED, 0); // SE0 (1) DELAY(USB_SPEED, 3); // SE0 (2) ASM("in save, DDRPORT"); ASM("eor save, line"); ASM("sbi OUTPORT, DM"); ASM("out DDRPORT, save"); ASM("cbi OUTPORT, DM"); ASM("out __SREG__, __tmp_reg__"); ASM("ret"); } /* * This implementation is faster, but has bigger code size * This version has been optimized in size compared to the original * fast CRC by Shay Green, January 2014. * * Thanks to Slawomir Fras (BoskiDialer) for the original contribution. * It implements the following C pseudo-code: * unsigned table(unsigned char x) * { * unsigned value; * * value = (unsigned)x << 6; * value ^= (unsigned)x << 7; * if(parity(x)) * value ^= 0xc001; * return value; * } * unsigned usbCrc16(unsigned char *src, unsigned char len) * { * unsigned crc = 0xffff; * * while(len--) * crc = table(lo8(crc) ^ *src++) ^ hi8(crc); * return ~crc; * } */ uint16_t SWUSB::crc16(uint8_t *dst, const uint8_t *src, uint8_t len, bool pgm) { (void)dst; // r24:r25 (void)src; // r22:r23 (void)len; // r20 (void)pgm; // r18 ASM(".equ src , 30" ::: "r30", "r31"); // Z register ASM(".equ dst , 26" ::: "r26", "r27"); // X register ASM(".equ resCrcH, 25" ::: "r25"); ASM(".equ resCrcL, 24" ::: "r24"); // return value ASM(".equ bitCnt , 23" ::: "r23"); ASM(".equ scratch, 22" ::: "r22"); ASM(".equ byte , 21" ::: "r21"); ASM(".equ len , 20" ::: "r20"); ASM(".equ copy , 19" ::: "r19"); ASM(".equ pgm , 18" ::: "r18"); ASM("movw src, r22"); ASM("movw dst, r24"); ASM("mov copy, r24"); ASM("or copy, r25"); ASM("ldi resCrcL, 0xFF"); ASM("ldi resCrcH, 0xFF"); ASM("clr bitCnt"); // zero ASM("rjmp 9f"); ASM("1:"); ASM("tst pgm"); ASM("brne 2f"); ASM("ld byte, z+"); ASM("rjmp 3f"); ASM("2:"); ASM("lpm byte, z+"); ASM("3:"); ASM("cpse copy, __zero_reg__"); ASM("st x+, byte"); ASM("eor byte, resCrcL"); // scratch is now 'x' in table() ASM("mov scratch, byte"); // compute parity of 'x' ASM("swap byte"); ASM("eor byte, scratch"); ASM("mov resCrcL, byte"); ASM("lsr byte"); ASM("lsr byte"); ASM("eor byte, resCrcL"); ASM("inc byte"); ASM("andi byte, 2"); // byte is now parity(x) << 1 ASM("cp bitCnt, byte"); // c = (byte != 0), then put in high bit ASM("ror scratch"); // so that after xoring, shifting, and xoring, it gives ASM("ror byte"); // the desired 0xC0 with resCrcH ASM("mov resCrcL, byte"); ASM("eor resCrcL, resCrcH"); ASM("mov resCrcH, scratch"); ASM("lsr scratch"); ASM("ror byte"); ASM("eor resCrcH, scratch"); ASM("eor resCrcL, byte"); ASM("9:"); ASM("subi len, 1"); ASM("brsh 1b"); ASM("com resCrcL"); ASM("com resCrcH"); ASM("ret"); } void SWUSB::usbBusArbitration(void) { // Bus arbitration is required because USB protocol retry enters during processing. ASM(".equ INPORT , %0" :: "I" (_SFR_IO_ADDR(USB_INPORT))); ASM(".equ MASK , %0" :: "M" (USB_LINE_MASK)); ASM(".equ STATE , %0" :: "M" (1 << USB_LINE_DM)); ASM(".equ TIMEOUT, %0" :: "M" (F_CPU * 6 / 1000000 / 7)); // 6us ASM(".equ loop, 25" ::: "r25"); ASM(".equ data, 24" ::: "r24"); ASM(".equ line, 23" ::: "r23"); ASM("ldi line, STATE"); ASM("rjmp 2f"); ASM("1:"); ASM("in data, INPORT"); ASM("andi data, MASK"); ASM("cpse data, line"); ASM("2:"); ASM("ldi loop, TIMEOUT"); ASM("subi loop, 1"); ASM("brsh 1b"); ASM("ret"); } uint16_t SWUSB::usbMeasureFrameLength(void) { ASM(".equ INPORT, %0" :: "I" (_SFR_IO_ADDR(USB_INPORT))); ASM(".equ DM , %0" :: "M" (USB_LINE_DM)); ASM(".equ cntH, 25" ::: "r25"); ASM(".equ cntL, 24" ::: "r24"); ASM("clr cntL"); ASM("clr cntH"); ASM("1:"); ASM("adiw cntL, 1"); ASM("breq 9f"); ASM("sbic INPORT, DM"); ASM("rjmp 1b"); ASM("clr cntL"); ASM("clr cntH"); ASM("2:"); ASM("adiw cntL, 1"); ASM("breq 9f"); ASM("sbis INPORT, DM"); ASM("rjmp 2b"); ASM("3:"); ASM("adiw cntL, 1"); ASM("breq 9f"); ASM("sbic INPORT, DM"); ASM("rjmp 3b"); ASM("9:"); ASM("ret"); } #if defined(CLKCTRL_OSC20MCALIBA) #define GET_OSCCAL() CLKCTRL_OSC20MCALIBA #define SET_OSCCAL(v) do { CPU_CCP = CCP_IOREG_gc; CLKCTRL_OSC20MCALIBA = (v); } while (0) #else #define GET_OSCCAL() OSCCAL #define SET_OSCCAL(v) OSCCAL = (v) #endif bool SWUSB::calibration(void) { // USB connected ? uint16_t usb; for (uint8_t i = 0; i < 10; ++i) { if ((usb = usbMeasureFrameLength())) break; } if (usb) { // Calibrate OSCCAL uint8_t osc = GET_OSCCAL(); uint8_t cal = osc; int16_t pos = 0; int16_t neg = 0; for (uint8_t i = 0; i < 8; ++i) { usb = usbMeasureFrameLength(); if (usb == 0) { pos = neg = 0; break; } int16_t x = usb - (int16_t)(F_CPU * 1 / 1000 / 6 + 0.5); // 1ms if (x < 0) { if ((neg = -x) < pos) break; ++cal; } else { if ((pos = x) < neg) break; --cal; } SET_OSCCAL(cal); } if ((pos == 0) || (neg == 0)) SET_OSCCAL(osc); } #if 0 // 500Hz clock for debug output DDRB |= _BV(PB0); while (1) { PINB |= _BV(PB0); ASM("nop"); ASM("nop"); ASM("nop"); _delay_loop_2(F_CPU / 1000 / 4 - 2); } #endif return 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 |
/* nvmctrl.h - NVMCTRL Library for AVR Series Copyright (c) 2022 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 */ #pragma once #if !defined(SPM_PAGESIZE) && defined(PROGMEM_PAGE_SIZE) #define SPM_PAGESIZE PROGMEM_PAGE_SIZE #endif #if defined(__AVR_ATtiny841__) || defined(__AVR_ATtiny441__) #define SPM_ERASE_PAGESIZE (SPM_PAGESIZE * 4) #define ERASE_PAGE_OPT 0x80 #else #define SPM_ERASE_PAGESIZE SPM_PAGESIZE #define ERASE_PAGE_OPT 0x00 #endif #if defined(NVMCTRL) #define boot_spm_busy() (NVMCTRL.STATUS & (NVMCTRL_EEBUSY_bm | NVMCTRL_FBUSY_bm)) #define boot_spm_busy_wait() do {} while(boot_spm_busy()) #define boot_page_bufclr() \ do { \ CCP = CCP_SPM_gc; \ NVMCTRL.CTRLA = NVMCTRL_CMD_PAGEBUFCLR_gc; \ } while (0) #define boot_page_fill(address, data) \ do { \ NVMCTRL.ADDRL = (char)((address) >> 0); \ NVMCTRL.ADDRH = (char)((address) >> 8); \ NVMCTRL.DATAL = (char)((data) >> 0); \ NVMCTRL.DATAH = (char)((data) >> 8); \ } while (0) #define boot_page_erase(address) \ do { \ boot_page_fill(address, 0xFFFF); \ CCP = CCP_SPM_gc; \ NVMCTRL.CTRLA = NVMCTRL_CMD_PAGEERASE_gc; \ } while (0) #define boot_page_write(address) \ do { \ NVMCTRL.ADDRL = (char)((address) >> 0); \ NVMCTRL.ADDRH = (char)((address) >> 8); \ CCP = CCP_SPM_gc; \ NVMCTRL.CTRLA = NVMCTRL_CMD_PAGEWRITE_gc; \ } while (0) #define boot_page_fill_safe(address, data) \ do { \ boot_spm_busy_wait(); \ boot_page_fill(address, data); \ } while (0) #define boot_page_erase_safe(address) \ do { \ boot_spm_busy_wait(); \ boot_page_erase (address); \ } while (0) #define boot_page_write_safe(address) \ do { \ boot_spm_busy_wait(); \ boot_page_write (address); \ } while (0) #define boot_rww_enable_safe() #else #include <avr/boot.h> #if defined(CTPB) #define boot_page_bufclr() \ do { \ __SPM_REG = _BV(__SPM_ENABLE) | _BV(CTPB); \ asm volatile ("spm"); \ } while (0) #elif defined(RWWSRE) #define boot_page_bufclr() \ do { \ __SPM_REG = _BV(__SPM_ENABLE) | _BV(RWWSRE); \ asm volatile ("spm"); \ } while (0) #else #define boot_page_bufclr() \ do { \ __SPM_REG = _BV(__SPM_ENABLE); \ asm volatile ("spm"); \ } while (0) #endif #if !defined(RWWSRE) && !defined(ASRE) #undef boot_rww_enable_safe #define boot_rww_enable_safe() #endif #endif #define boot_page_bufclr_safe() \ do { \ boot_spm_busy_wait(); \ boot_page_bufclr(); \ } while (0) |