最近使うことがあまり無くなってきたATtiny85だが使い始めたときはまだ何も知らなくてシリアル通信にはSoftwareSerialを使ってきた。それなりに使えていたと思うのだが割り込み禁止期間が長くて割り込みを使う他のライブラリとの相性が悪いという問題があったという記憶がある。
今までUSIでI2C/SPIなどのライブラリーを作ったりしてきたがUSIでシリアル通信も出来るんだってのを久しぶりに思い出し作ってみたのだが結論から言うとこれは使えるぞって感じ。
タイミング的には厳しいはずなのに割り込みを邪魔するコードさえなければ300 – 230400bpsまで当たり前のように動作してしまう8ピンしかないATtiny85ってなんなんだって感動を覚えるレベルだ。但し、他の割り込みを利用するライブラリと併用した場合はせいぜい57600bps以下程度が限界とも思う。
実装は下記AVR307を参考にTIMER0を利用している。なのでTIMER0を利用するライブラリや機能との併用はできないことに注意してほしい。
半二重という点が気になる人もいると思われるが、一般的には何らかのプロトコルによるピンポン方式での通信となるだろうからあまり問題にはならないとは思う。たぶん...
受信はstartbitの1.5bit時間後からデータ8bitを連続取り込みしstop-bitは無視。送信は[startbit+data-low4bit]と[data-high4bit+stopbit]の2度に分けて5bitずつ送信する仕様だ。
受信は割り込みによるバッファリング(32バイト)を行い、送信は1バイトずつ処理している。割り込みに時間がかかるようだと使い物にならないので割り込み処理が可能な限りシンプルになるように工夫している。
【サンプル・スケッチ(エコーバック)】
1 2 3 4 5 6 7 8 9 10 11 12 |
#include "USISerial.h" void setup() { USISerial::begin(115200); } void loop() { if (USISerial::available()) USISerial::write(USISerial::read()); } |
【修正履歴】
2020-06-23
ピン変化から割り込み処理開始まで約2usほどかかっていてなんでこんなに遅いんだろう?と不思議に思っていたが、同一製品の他のストックで試してみたらデートシート通り約0.6us前後ほどで開始できることが確認できた。どうやら個体差というよりも壊れかけのMPUで試験していたみたい...ということで再度タイミング調整し直してみた。(-_-;)
2020-04-13
デバックコードが入ったままだったので除去。
2020-04-12
送信有効かつ最下位ビットがゼロのデータを受信すると受信完了時に送信ピンにグリッチが発生する点を修正。
2020-04-08
確実な初期設定のための変更、送信だけのときの不具合修正、受信時のスタートビット検出遅延設定値を割り込み遅延に対する余裕度を高める方向で微調整してみた。
【ライブラリ】
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 |
/* USISerial.h - USI Serial Library for ATtiny85 Baudrate: 300 - 230400 bps. Copyright (c) 2020 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 __USISERIAL_H #define __USISERIAL_H #include <stdint.h> #define USISerial_ERROR_OVERFLOW 1 #define USISerial_ERROR_BAUDRATE 2 typedef enum { USISerial_MODE_READWRITE, USISerial_MODE_RDONLY, USISerial_MODE_WRONLY, } USISerial_MODE; class USISerial { public: static uint8_t begin(uint32_t baudrate, USISerial_MODE mode = USISerial_MODE_READWRITE); static uint8_t available(void); static int read(void); static uint8_t read(uint8_t *buf, uint8_t len); static void write(uint8_t b); static void write(uint8_t *buf, uint8_t len); static uint8_t error(void); }; #endif |
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 |
/* USISerial.h - USI Serial Library for ATtiny85 Baudrate: 300 - 230400 bps. Code size: 594 - 708 bytes. Copyright (c) 2020 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 "USISerial.h" #include <avr/io.h> #include <avr/interrupt.h> #define DO_PORT B #define DO_POS 1 #define DI_PORT B #define DI_POS 0 #define CAT(a, b) a##b #define REG(r, p) CAT(r, p) #define DI_REG(r) REG(r, DI_PORT) #define DO_REG(r) REG(r, DO_PORT) #define RCVBUF_SIZE (1 << 5) #define RCVBUF_OFFS(p) ((p) & (RCVBUF_SIZE - 1)) #define INITIAL_DELAY (48) // initial delay of start-bit (cpu cycle) static uint8_t _rcvbuf[RCVBUF_SIZE]; static uint8_t _rcvput; static uint8_t _rcvget; static uint8_t _usirx0; static uint8_t _usirxf; static volatile uint8_t _usitxf; static uint8_t _usidat; static uint8_t _usierr; static uint8_t _clksta; static uint8_t _clkcnt; static uint8_t _clkdiv; #define init() \ do \ { \ GTCCR |= _BV(TSM) | _BV(PSR0); \ TCNT0 = 0; \ OCR0A = _clksta; \ USIDR = 0xFF; \ USISR = _BV(USIOIF) | (16 - 8); \ USICR = _BV(USIOIE) | _BV(USIWM0) | _BV(USICS0); \ if (_usirxf) \ { \ TIMSK |= _BV(OCIE0A); \ TIFR |= _BV(OCF0A); \ PCMSK |= _BV(PCINT0); \ GIFR |= _BV(PCIF); \ } \ _usitxf &= 1; \ } \ while (0) ISR(PCINT0_vect) { if ((DI_REG(PIN) & _BV(DI_POS)) == 0) { GTCCR &= ~_BV(TSM); PCMSK &= ~_BV(PCINT0); } } ISR(TIMER0_COMPA_vect) { OCR0A = _clkcnt; TIMSK &= ~_BV(OCIE0A); _usirx0 = USIDR; USIDR |= 1; } ISR(USI_OVF_vect) { if (_usitxf == 2) { USIDR = _usidat; USISR = _BV(USIOIF) | (16 - 5); _usitxf = 3; } else { USICR = 0; if (_usitxf != 3) { if (RCVBUF_OFFS((uint8_t)(_rcvput + 1) ^ _rcvget)) _rcvbuf[RCVBUF_OFFS(_rcvput++)] = (uint8_t)(_usirx0 & 1 ? USIBR : USIBR & 0x7F); else _usierr = USISerial_ERROR_OVERFLOW; } init(); } } static uint8_t reverseBit(uint8_t b) { static const uint8_t REVBITS[] = {0x00, 0x08, 0x04, 0x0C, 0x02, 0x0A, 0x06, 0x0E, 0x01, 0x09, 0x05, 0x0D, 0x03, 0x0B, 0x07, 0x0F}; return (__builtin_avr_swap(REVBITS[b & 0x0F]) & 0xF0) | (REVBITS[__builtin_avr_swap(b) & 0x0F]); } #define MAX_COUNT (uint8_t)(256 / 1.5) uint8_t USISerial::begin(uint32_t baudrate, USISerial_MODE mode) { uint16_t count = (F_CPU + (baudrate / 2)) / baudrate; uint8_t delay = 0; if (count <= MAX_COUNT) { _clkdiv = _BV(CS00); delay = INITIAL_DELAY; } else if ((count >>= 3) <= MAX_COUNT) { _clkdiv = _BV(CS01); delay = INITIAL_DELAY >> 3; } else if ((count >>= 3) <= MAX_COUNT) { _clkdiv = _BV(CS01) | _BV(CS00); delay = INITIAL_DELAY >> 6; } else if ((count >>= 2) <= MAX_COUNT) _clkdiv = _BV(CS02); else if ((count >>= 2) <= MAX_COUNT) _clkdiv = _BV(CS02) | _BV(CS00); else return _usierr = USISerial_ERROR_BAUDRATE; _clkcnt = (uint8_t)count - 1; _clksta = (uint8_t)count + ((uint8_t)count / 2) - delay - 1; _rcvput = 0; _rcvget = 0; _usierr = 0; _usirxf = (mode != USISerial_MODE_WRONLY); _usitxf = (mode != USISerial_MODE_RDONLY); if (_usirxf) { DI_REG(PORT) |= _BV(DI_POS); DI_REG(DDR) &= ~_BV(DI_POS); } if (_usitxf) { DO_REG(PORT) |= _BV(DO_POS); DO_REG(DDR) |= _BV(DO_POS); } TCCR0B = 0; TCCR0A = _BV(WGM01); init(); TCCR0B = _clkdiv; if (_usirxf) GIMSK |= _BV(PCIE); } uint8_t USISerial::available(void) { return RCVBUF_OFFS(_rcvput - _rcvget); } int USISerial::read(void) { return available() ? reverseBit(_rcvbuf[RCVBUF_OFFS(_rcvget++)]) : -1; } uint8_t USISerial::read(uint8_t *buf, uint8_t len) { uint8_t cnt; for (cnt = 0; cnt < len; ++cnt) { int c = read(); if (c < 0) break; *buf++ = (uint8_t)c; } return cnt; } void USISerial::write(uint8_t b) { if (!_usitxf) return; while (_usirxf || (_usitxf > 1)) { cli(); if (PCMSK & _BV(PCINT0)) break; sei(); } PCMSK &= ~_BV(PCINT0); TIMSK &= ~_BV(OCIE0A); sei(); b = reverseBit(b); _usidat = __builtin_avr_swap(b) | 0x0F; _usitxf = 2; OCR0A = _clkcnt; USISR = _BV(USIOIF) | (16 - 5); USIDR = (uint8_t)(b / 2); GTCCR &= ~_BV(TSM); } void USISerial::write(uint8_t *buf, uint8_t len) { while (len--) write(*buf++); } uint8_t USISerial::error(void) { uint8_t rv = _usierr; _usierr = 0; return rv; } |