ESP8266には、SoftwareSerialが同梱されているのでそれを使えば良い話ではあるのだが、なんでこんなに複雑になるんだろうと思うくらいのコードになっていて性能的にも悪そうだし気分的にあまり使いたくないと思ってしまったので自分で作ってみることに...
SoftwareSerialは115200bpsだと処理が間に合わなくてスタートビットで割り込み処理に入った後、受信完了まで割り込み処理内でループする苦肉の策をとっているようだが、その方法だと他の割り込みを約100us遅延させてしまう。
このライブラリは、PinChange割り込みのみで実装しているが可能な限り単純化した結果、230400bps(ESP8266:80MHz)まで対応可能だ。対応フレームは8N1形式のみ。
但し、なんらかの割り込み処理時間或いは割り込み禁止時間が長いコードが存在する場合、そのコードの実行タイミングによってはボーレートに関わずデータ化けが発生してしまうことがある。ライブラリ側では解決不可能な問題であり、解決策としてはアプリ側のプロトコル処理でエラーリカバリーしてもらう以外に方法はない。
ちなみにESP32(240MHz)よりもESP8266(80MHz)のほうが性能が良いようだ。というかESP32が遅すぎる。ArduinoOTAの更新進捗速度を見ててもわかるくらい遅い。Pingのレスに至っては10倍以上も遅い。もしかして失敗作というか性能でなかったからクロックだけ上げてゴマかしちゃったとかではないよね?...(-_-;)
それはともかく、このライブラリのどこにも受信ピンをリードするコードがない...って、それで受信できるのか?(笑)
【ESP32-C3では使えない】
2023-03-19
正確に言うと無線接続前なら受信できるが無線接続後に受信できなくなる。詳しくは調べてないが無線処理の割込み処理時間が長い、割込み禁止時間が長い、或いは、FreeRTOSのタスク切り替えのオーバーヘッドが大きい(というかCPUが遅い?)のどれかだろうと思っている。原因はなんにせよこのCPUでは処理能力的に無理っぽそうなので、もう一つのシリアル(Serial1)を使うこと。ちなみにSerial1の規定のピンはUSB用に割り当てられているため他のピンに変更したほうが良いだろう。
例: Serial1.begin(115200, SERIAL_8N1, RXPIN, TXPIN);
【概要】
1 |
void begin(uint32_t baudrate, uint8_t rxpin, uint8_t txpin) |
初期処理を行う。rxpin/txpinを省略する場合は、SWUART_NOPINを指定する。
1 |
void end(void) |
終了処理を行う。
1 |
size_t available(void) |
受信済みのデータバイト数を取得する。
1 |
int read(void) |
1バイト受信する。データがないときは-1が返される。
1 |
size_t read(uint8_t *buf, size_t len) |
複数バイト受信する。bufに格納したデータバイト数を返す。
1 |
void write(uint8_t b) |
1バイト送信する。送信は割り込み禁止で行われることに注意。
1 |
void write(uint8_t *buf, size_t len) |
複数バイト送信する。
※通信は半二重方式。同時に送受信することは出来ないので注意!
【サンプル・スケッチ】
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
#include "swuart.h" SWUART swuart; void setup() { swuart.begin(115200, 2, 15); } void loop() { int c = swuart.read(); if (c >= 0) swuart.write(c); } |
【ライブラリ】
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 |
/* swuart.h - Software UART Library for ESP8266/ESP32 Baudrate: ESP8266( 80MHz): Max 230400 bps: (Caution: irregular error occurs) ESP32 (240MHz): Max 57600 bps: (Caution: irregular error occurs) 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 __SWUART_H #define __SWUART_H #include <stdint.h> #include <stdbool.h> #include "Arduino.h" #define SWUART_NOPIN 0xFF #define SWUART_CLOCK_WIDTH(b) (F_CPU / (b)) #define SWUART_FRAME_NBITS 9 // start(1) + data(8) #if defined(ESP32) #define _DI() portDISABLE_INTERRUPTS() #define _EI() portENABLE_INTERRUPTS() #else #define _DI() noInterrupts() #define _EI() interrupts() #endif class SWUART { private: uint32_t _bitwidth; uint32_t _frmwidth; uint32_t _endclock; uint32_t _bitclock; uint32_t _bitcount; uint32_t _bitlevel; uint32_t _bitshift; uint8_t _rxputpos; uint8_t _rxgetpos; uint8_t _rxbuffer[256]; // must be 256. uint8_t _rxpin; uint8_t _txpin; static void IRAM_ATTR onRxPinChange0(SWUART *instance) { instance->onRxPinChange(ESP.getCycleCount()); } void IRAM_ATTR onRxPinChange(uint32_t clock) { if (_bitcount == 0) { _bitclock = clock; _endclock = clock + _frmwidth; _bitcount = 1; _bitlevel = 0; } else { int32_t t = clock - _bitclock + (_bitwidth >> 1); while ((t -= _bitwidth) > 0) { _bitshift = (_bitshift >> 1) | _bitlevel; if (_bitcount++ == SWUART_FRAME_NBITS) { if ((uint8_t)(_rxputpos + 1) != _rxgetpos) _rxbuffer[_rxputpos++] = _bitshift; _bitcount = 0; // start next frame if ((int32_t)(clock - _endclock) >= 0) onRxPinChange(clock); return; } } _bitclock = clock; _bitlevel ^= 0x80; } } void finish(void) { // disable interrupt _DI(); // finish to receiving if (_bitcount && ((int32_t)(ESP.getCycleCount() - _endclock) >= 0)) onRxPinChange(_endclock - 1); // enable interrupt _EI(); } public: SWUART(void) : _bitcount(0) , _rxputpos(0) , _rxgetpos(0) , _rxpin(SWUART_NOPIN) , _txpin(SWUART_NOPIN) { } virtual ~SWUART(void) { } void begin(uint32_t baudrate, uint8_t rxpin, uint8_t txpin) { _bitwidth = SWUART_CLOCK_WIDTH(baudrate); _frmwidth = _bitwidth * (SWUART_FRAME_NBITS + 1); _bitcount = 0; _rxputpos = 0; _rxgetpos = 0; _rxpin = rxpin; _txpin = txpin; if (rxpin != SWUART_NOPIN) { pinMode(rxpin, INPUT); attachInterruptArg(rxpin, reinterpret_cast<void (*)(void*)>(onRxPinChange0), this, CHANGE); } if (txpin != SWUART_NOPIN) { digitalWrite(txpin, HIGH); pinMode(txpin, OUTPUT); } } void end(void) { if (_rxpin != SWUART_NOPIN) { detachInterrupt(_rxpin); _rxpin = SWUART_NOPIN; } if (_txpin != SWUART_NOPIN) { pinMode(_txpin, INPUT); digitalWrite(_txpin, LOW); _txpin = SWUART_NOPIN; } } size_t available(void) { finish(); int remain = _rxputpos - _rxgetpos; return remain >= 0 ? remain : remain + sizeof(_rxbuffer); } int read(void) { return (available() ? _rxbuffer[_rxgetpos++] : -1); } size_t read(uint8_t *buf, size_t len) { size_t cnt = available(); if (cnt < len) len = cnt; else cnt = len; while (len--) *buf++ = _rxbuffer[_rxgetpos++]; return cnt; } void write(uint8_t b) { if (_txpin != SWUART_NOPIN) { // disable interrupt _DI(); // start bit uint32_t clock = ESP.getCycleCount(); digitalWrite(_txpin, LOW); while (ESP.getCycleCount() - clock < _bitwidth) continue; // data bit for (uint8_t i = 1; i; i <<= 1) { digitalWrite(_txpin, b & i); clock += _bitwidth; while (ESP.getCycleCount() - clock < _bitwidth) continue; } // stop bit digitalWrite(_txpin, HIGH); clock += _bitwidth; while (ESP.getCycleCount() - clock < _bitwidth) continue; // enable interrupt _EI(); // spacing to next frame clock += _bitwidth; while (ESP.getCycleCount() - clock < _bitwidth) continue; } } void write(const uint8_t *buf, size_t len) { while (len--) write(*buf++); } }; #endif |