ソフトウェア処理の限界に挑む。AVR用高速シリアル・ライブラリを作ってみた。

Last Updated on 2022-10-08 by researcher

以前に、送信専用ではあるがソフトウェアだけで実現した高速シリアルライブラリを紹介した。

Hi-Speed Serial TX Library for AVR Series

送信ができると受信もやってみたくなる。ただ、ソフトウェアのみによる受信処理にはピン変化割り込みを使うのが一般的であるが高速化という観点ではその割り込みレイテンシーが最高速度を制限してしまう。仮にビット毎に割り込み処理した場合どんなに頑張っても200Kbps程度が限界だろう。より高速化するには割り込みはスタートビット検出時のみとしそのまま一気にビットを組み立てるしかない。ちなみに割り込みではなくポーリング方式ならさらなる高速化を期待できるが他のことが全くできなくなってしまうので現実的ではない。

データシートによるとINTピン変化ラッチに2.5-3.5サイクル(PCINTは+2サイクル?)、割り込み実行に6-8サイクル、ここまでで既に10サイクル前後も必要だ。10サイクルは16MHzクロックで0.625usなので単純に考えると1Mbps程度が受信処理の限界値。但し、適切なタイミングで受信処理が開始できさえすれば、その後はソフトウェア処理でもビット当たり2サイクルで処理できるので全然余裕はある。

残る問題は、割り込み開始処理のレジスタ退避や受信データの保存処理が間に合うかどうかだが、そこはアセンブラでカリカリにチューニングして限界までサイクルカットするしか方法はないがアセンブラだけで作ってしまうと柔軟性に欠けるため、今回も、テンプレートクラスとインライン・アセンブラとの組み合わせで実装してみた。テンプレート・クラスによりアセンブラ・コードを柔軟に生成するための手段であり、ある意味、最強とも言える。

ちなみに1Mbpsでの余裕は0.5usもないので他の割り込みとカチあったりすると簡単に受信データ喪失或いは化けてしまうことに注意してほしい。割り込みを多用する機能(USB/UART/I2C/SPI)と同時利用したりするとかなりの高確率で受信データ喪失或いは化けが発生してしまうはずだ。タイマー割り込みもエラーを引き起こす原因となる可能性があると考えるべきだろう。特にC言語のコールバック関数を呼び出すタイプの割り込み処理はかなり大きなオーバーヘッドがかかるため気を付けなければならない。

受信データ喪失或いは化けはソフトウェア受信処理型の欠点でもあり対策としては通信速度を落とすぐらいのことしかないが通信速度を落とすと今度は割り込み禁止期間が長くなってしまうという弊害がでる。それが他の問題を引き起こすようなら通信速度は落とさずアプリ側にてリトライなどエラー対策を別途検討するしかない。

今のところ、手元にあるATtiny85/ATMega32U4で動作確認しているがATmega328はなぜか持っていないので動作未確認。

【仕様】
・半二重通信(送受信同時処理は出来ない)
・送信は、16MHzクロックで38400-8Mbpsまで。
・受信は、16MHzクロックで38400-1Mbpsまで。
・通信形式は、data 8-bit/none-parity/1-stopbit固定。
・送信ピンは任意のピンに対応。
・受信ピンはMAXUART_RXPIN_XX型を指定。
・割り込みハンドラーは未実装なので受信ピンに対応したMAXUART_RXPIN_XX_ISRマクロによりアプリ側で実装する必要がある。
・INT0/INT1,…PCINT0/PCINT1,…などの割り込みソース毎に一つのピンだけが受信可能。同じ割り込みソースで複数の受信ピンを同時利用することは出来ない。

【921600bpsでの受信開始タイミングの確認】

【概要】

ライブラリを開始する。

ライブラリを終了する。

受信バッファをクリアする。

受信バッファ内の受信済みバイト数を取得する。

受信バッファから1バイト読み込む。受信バッファが空の場合は、-1を返す。

受信バッファから複数バイト読み込む。バッファ(buf)に格納したバイト数を返す。

受信割り込み処理。受信割り込みハンドラーから呼び出される。

1バイトを送信する。

複数バイトを送信する。

【サンプルスケッチ】
PB1を送信ピン、PB2を受信ピンとする例。※Arduinoのピン名とは違うことに注意。

【修正履歴】
2022-10-08
1M未満の速度で受信が誤動作するのを修正。

2020-07-19
ストップビット送信処理を全AVRシリーズ共通コードに変更。

2020-07-10
ライブラリのクラス名とファイル名を変更。インラインアセンブラの入出力パラメタの使い方が正しくなかったので修正。

2020-06-29
受信バッファ・フル時の処理サイクル数の違いにより受信処理自体が誤動作する可能性があったためバッファ状態に関係なく同じサイクル数(厳密には1サイクル違う)で処理できるようデータ保存処理を改良してみた。

2020-06-28
1Mbpsの連続送信データでも安定して受信できるように改良。

2020-06-27
1Mbpsでの連続受信が微妙に間に合っていなかったので改良。割り込み処理からリターンすると間に合わなくなるのでリターンせずに割り込み処理を再実行するようにしてみたがもう少し調整必要な感じ。ちなみに、送信元の最大連続送信バイト数は、このライブラリの受信バッファサイズ(規定値は32バイト)に制限され送信と送信の間には受信処理が間に合うよう間隔をあける必要がある。
また、送信側が複数バイトを連続送信するとき、サンプルスケッチのようなプログラムは全二重通信となりライブラリの送信処理の割り込み禁止が受信処理を妨害してしまうため誤動作することにも注意。

【ライブラリ】

【参照ライブラリ】
AVR用高速GPIOライブラリ

“ソフトウェア処理の限界に挑む。AVR用高速シリアル・ライブラリを作ってみた。” への8件の返信

  1. ATmega328では未検証との事でしたが、ひとまずarduino unoで試させてもらいました。

    当初57600bpsや115200bpsで運用しようとしていました。
    その際に送信は想定通りうまく行き、受信は1バイト毎に余計なデータが挿入されたものが取得できました。
    おかしいと思いソースを解読しようと思ったのですが、一度1Mbpsでも試しておこうと設定したところ、送受信ともに成功することが分かりました。
    1Mbps以上ではいずれもうまく行くことから、何かしらの調整が必要なのか、はたまたATmega328P依存なのかといった所を少しアシストしていただけると嬉しいです。
    ちなみに半二重の環境です。
    maxuart.hはATmega328上での動作確認がなされていないとの事でしたが、Arduino UNOに適用してみました。
    115200bpsにおいて送信は問題なく、受信は1バイト毎に無用なデータが挿入されることを確認。
    115200bps以下でも同様のためソースを追ってみようと思ったのですが、ふと高速ではどうだろうと1Mbpsで試したところ送受信とも問題なく。1Mbps以上では問題ない事を確認。

    低速時における挙動について、何かヒントをいただければと。

    1. コメントありがとうございます。

      コメントがスパムと判断されていて気づくのに時間がかかってしまいました。また、正しい記事のほうにコメントとして記載させていただきました。

      挙動から推測すると受信後に無用な受信割込みが発生しているものと思われたのでコードを見直してみたら...あきらかに間違ってるところがありました。タイプ・ミスみたいですね...(-_-;)

      706行目
      誤 if (MAXUART_SHIFT_CYCLE >= 32)
      正 if (MAXUART_CLOCK_CYCLE >= 32)

      投稿内のコードは修正しておきました。

      これが原因だとは思いますが、こちらでは試験できないので再度試してみていただけないでしょうか?

  2. こちらこそレスいただいているのに気付かず放置してしまいました。

    早速57600bpsで試しましたところ、所望の挙動に至りました。
    全域においてなかなかヨサゲです。

    1. テスト&コメント頂きありがとうございます。
      感謝!<(_ _)>

  3. 引用できるか分かりませんが、最近のasの厳格さと最適化に負けないよう書き直したものを貼り付けさせてもらいました。このように使えるようにしてあります。

    #include “SoftUart.h”

    // TX = PB1, RX = PB2
    using TxPin = avr_uart::Pin;
    using RxPin = avr_uart::Pin;

    // テンプレート引数でボーレートとピンを指定してインスタンス化
    using SerialPort = avr_uart::SoftUart;

    // 割り込みベクタは責任を持って定義(マクロの隠蔽を廃止し必ずISR_NAKEDに)
    ISR(PCINT0_vect, ISR_NAKED) {
    SerialPort::receive();
    reti();
    }

    int main(void) {
    PCICR |= (1 << PCIE0);
    PCMSK0 |= (1 << PCINT2); // PB2のピン変化割り込み許可
    sei();

    SerialPort::begin();

    SoftUart.hpp

    // SPDX-License-Identifier: GPL-3.0-or-later

    /**
    * @file SoftUart.hpp
    * @brief High-speed Software UART Library for AVR Microcontrollers
    *
    * A highly optimized, bare-metal C++11 software UART implementation for AVR.
    * Capable of achieving up to 8 Mbps TX and 1 Mbps RX speeds on a 16MHz clock.
    *
    * @note This library explicitly excludes Arduino environment dependencies to
    * allow for strict zero-overhead compilation and cycle-accurate inline assembly.
    */

    /*
    * Copyright (c) 2020 Sasapea's Lab. All right reserved.
    * Copyright (C) 2026 Mukyokyo
    */

    #pragma once
    #include
    #include
    #include

    namespace avr_uart {

    // ==========================================
    // Traits class for pin definitions
    // ==========================================

    /**
    * @brief Represents a specific I/O pin on an AVR microcontroller.
    *
    * Provides compile-time resolution of I/O register addresses and bitmasks.
    *
    * @tparam Port The port letter (e.g., 'B', 'C', 'D').
    * @tparam Bit The pin number (0 to 7).
    */
    template
    struct Pin {
    static_assert(Port >= 'A' && Port <= 'L', "Port must be between 'A' and 'L'");
    static_assert(Bit <= 7, "Bit must be between 0 and 7");

    static constexpr uint8_t bit = Bit;
    static constexpr uint8_t mask = 1 << Bit;

    /// Base memory address of the PIN register (assumes standard AVR layout)
    static constexpr uintptr_t mem_in = 0x20 + (Port - 'A') * 3;

    /// I/O space addresses for inline assembly constraints
    static constexpr uint8_t io_in = mem_in - __SFR_OFFSET;
    static constexpr uint8_t io_out = io_in + 2;
    static constexpr uint8_t io_tgl = io_in; // Writing to PIN register toggles output

    /** @brief Configures the pin as an output. */
    static inline void mode_output() { *(volatile uint8_t*)(mem_in + 1) |= mask; }

    /** @brief Configures the pin as an input with the internal pull-up resistor enabled. */
    static inline void mode_input_pullup() {
    *(volatile uint8_t*)(mem_in + 1) &= ~mask;
    *(volatile uint8_t*)(mem_in + 2) |= mask;
    }

    /** @brief Configures the pin as a high-impedance input. */
    static inline void mode_input() {
    *(volatile uint8_t*)(mem_in + 1) &= ~mask;
    *(volatile uint8_t*)(mem_in + 2) &= ~mask;
    }

    /** @brief Sets the pin output to logic HIGH. */
    static inline void set_high() { *(volatile uint8_t*)(mem_in + 2) |= mask; }
    };

    /**
    * @brief A dummy pin representation for disabled TX or RX channels.
    */
    struct PinDisabled {
    static constexpr uint8_t bit = 0xFF;
    static inline void mode_output() {}
    static inline void mode_input_pullup() {}
    static inline void mode_input() {}
    static inline void set_high() {}
    };

    // ==========================================
    // Software serial class
    // ==========================================

    /**
    * @brief Zero-overhead, cycle-accurate Software UART class.
    *
    * Utilizes template metaprogramming and optimized inline assembly to achieve
    * extreme baud rates. Timing constraints are resolved at compile-time.
    *
    * @tparam BaudRate The desired baud rate (e.g., 115200, 1000000).
    * @tparam TxPin The pin type used for transmission (e.g., Pin). Use PinDisabled to disable TX.
    * @tparam RxPin The pin type used for reception (e.g., Pin). Use PinDisabled to disable RX.
    * @tparam RxBufSizeBit Defines the ring buffer size as 2^RxBufSizeBit (default is 5, meaning 32 bytes).
    */
    template
    class SoftUart {
    private:
    static constexpr uint8_t RXBUF_SIZE = 1 << RxBufSizeBit;
    static constexpr uint8_t RXBUF_MSK = RXBUF_SIZE - 1;

    static uint8_t _rx_buf[RXBUF_SIZE];
    static uint8_t _rx_get;
    static volatile uint8_t _rx_put;

    static constexpr int START_CYCLE = 18; // LATCH(3-5) + INTERRUPT(5-7) + Overhead(10)
    static constexpr int DELAY_CYCLE = 3; // cycle of delay loop
    static constexpr int SHIFT_CYCLE = 2; // cycle of bit assembly
    static constexpr float CLOCK_CYCLE = ((float)F_CPU / BaudRate);
    static constexpr int SWCLK_CYCLE = (int)(CLOCK_CYCLE + 0.5f);

    // ---------------------------------------------------------
    // Delay Macro (Placed within the class for member function access)
    // ---------------------------------------------------------
    #define UART_DELAY(n, m) \
    do { \
    int cycle = 0; \
    float error = CLOCK_CYCLE - SWCLK_CYCLE; \
    float round = (error < 0 ? -0.5f : 0.5f); \
    error -= (int)error; \
    if (((int)(error * (n) + round) ^ (int)(error * ((n) + 1) + round)) & 1) cycle += (error 0) { \
    if (cycle / DELAY_CYCLE) { \
    __asm__ volatile( \
    "ldi r27, %[_COUNT_]" \
    "\n\t" \
    "1: dec r27" \
    "\n\t" \
    "brne 1b" \
    "\n\t" \
    : \
    : [_COUNT_] "M"(cycle / DELAY_CYCLE) \
    : "r27"); \
    } \
    if ((cycle % DELAY_CYCLE) > 0) __asm__ volatile("nop"); \
    if ((cycle % DELAY_CYCLE) > 1) __asm__ volatile("nop"); \
    } \
    } while (0)

    public:
    /**
    * @brief Initializes the UART hardware pins and clears the buffer.
    * @note Hardware interrupt enabling (e.g., setting PCICR/PCMSK) must be handled by the user in the main code.
    */
    static void begin() {
    clear();
    if (TxPin::bit != 0xFF) {
    TxPin::set_high();
    TxPin::mode_output();
    }
    if (RxPin::bit != 0xFF) {
    RxPin::mode_input_pullup();
    }
    }

    /**
    * @brief Disables the UART pins, reverting them to high-impedance inputs.
    */
    static void end() {
    RxPin::mode_input();
    TxPin::mode_input();
    }

    /**
    * @brief Clears the receive ring buffer.
    */
    static void clear() {
    _rx_put = 0;
    _rx_get = 0;
    }

    /**
    * @brief Checks the number of bytes available in the receive buffer.
    * @return The number of bytes available to read.
    */
    static uint8_t available() { return (_rx_put - _rx_get) & RXBUF_MSK; }

    /**
    * @brief Reads a single byte from the receive buffer.
    * @return The next available byte, or -1 if the buffer is empty.
    */
    static int read() { return available() ? _rx_buf[(_rx_get++) & RXBUF_MSK] : -1; }

    /**
    * @brief Transmits a single byte via software UART.
    *
    * This method uses precise cycle-counted inline assembly. Interrupts are globally
    * disabled (cli) during transmission to prevent jitter.
    *
    * @param data The byte to transmit.
    */
    static void write(uint8_t data) {
    if (SWCLK_CYCLE >= 256 * DELAY_CYCLE + SHIFT_CYCLE - 1) return;

    // GCC optimization countermeasure:
    // Explicitly bind variables to specific registers to prevent the compiler from
    // reallocating them between fragmented __asm__ blocks.
    register uint8_t data_reg asm("r24") = data;
    register uint8_t tmp_mask asm("r25");
    register uint8_t sreg_save asm("r23");

    __asm__ volatile(
    "mov %[_TMP_], %[_DATA_]"
    "\n\t"
    "lsl %[_TMP_]"
    "\n\t"
    "eor %[_DATA_], %[_TMP_]"
    "\n\t"
    "ldi %[_TMP_], %[_MASK_]"
    "\n\t"
    "in %[_SREG_], __SREG__"
    "\n\t"
    "cli"
    "\n\t"
    "out %[_PORT_], %[_TMP_]"
    "\n\t"
    : [_DATA_] "+r"(data_reg), [_TMP_] "=&d"(tmp_mask), [_SREG_] "=&r"(sreg_save)
    : [_PORT_] "I"(TxPin::io_tgl), [_MASK_] "M"(TxPin::mask));
    UART_DELAY(0, 0);

    __asm__ volatile("sbrc %[_DATA_], 0 \n\t out %[_PORT_], %[_TMP_]" : : [_DATA_] "r"(data_reg), [_TMP_] "r"(tmp_mask), [_PORT_] "I"(TxPin::io_tgl));
    UART_DELAY(1, 0);
    __asm__ volatile("sbrc %[_DATA_], 1 \n\t out %[_PORT_], %[_TMP_]" : : [_DATA_] "r"(data_reg), [_TMP_] "r"(tmp_mask), [_PORT_] "I"(TxPin::io_tgl));
    UART_DELAY(2, 0);
    __asm__ volatile("sbrc %[_DATA_], 2 \n\t out %[_PORT_], %[_TMP_]" : : [_DATA_] "r"(data_reg), [_TMP_] "r"(tmp_mask), [_PORT_] "I"(TxPin::io_tgl));
    UART_DELAY(3, 0);
    __asm__ volatile("sbrc %[_DATA_], 3 \n\t out %[_PORT_], %[_TMP_]" : : [_DATA_] "r"(data_reg), [_TMP_] "r"(tmp_mask), [_PORT_] "I"(TxPin::io_tgl));
    UART_DELAY(4, 0);
    __asm__ volatile("sbrc %[_DATA_], 4 \n\t out %[_PORT_], %[_TMP_]" : : [_DATA_] "r"(data_reg), [_TMP_] "r"(tmp_mask), [_PORT_] "I"(TxPin::io_tgl));
    UART_DELAY(5, 0);
    __asm__ volatile("sbrc %[_DATA_], 5 \n\t out %[_PORT_], %[_TMP_]" : : [_DATA_] "r"(data_reg), [_TMP_] "r"(tmp_mask), [_PORT_] "I"(TxPin::io_tgl));
    UART_DELAY(6, 0);
    __asm__ volatile("sbrc %[_DATA_], 6 \n\t out %[_PORT_], %[_TMP_]" : : [_DATA_] "r"(data_reg), [_TMP_] "r"(tmp_mask), [_PORT_] "I"(TxPin::io_tgl));
    UART_DELAY(7, 0);
    __asm__ volatile("sbrc %[_DATA_], 7 \n\t out %[_PORT_], %[_TMP_]" : : [_DATA_] "r"(data_reg), [_TMP_] "r"(tmp_mask), [_PORT_] "I"(TxPin::io_tgl));
    UART_DELAY(8, 0);

    __asm__ volatile(
    "sbis %[_POUT_], %[_POS_]"
    "\n\t"
    "out %[_PORT_], %[_TMP_]"
    "\n\t"
    "out __SREG__, %[_SREG_]"
    "\n\t"
    :
    : [_TMP_] "r"(tmp_mask), [_SREG_] "r"(sreg_save), [_POUT_] "I"(TxPin::io_out), [_PORT_] "I"(TxPin::io_tgl), [_POS_] "I"(TxPin::bit));
    UART_DELAY(9, 0);
    }

    /**
    * @brief Core reception routine. Must be called from a NAKED Interrupt Service Routine (ISR).
    *
    * @warning This method bypasses compiler prologues/epilogues for extreme speed.
    * The calling ISR must be defined with ISR_NAKED and MUST NOT perform any register
    * push/pop operations prior to calling this method.
    */
    static void receive() __attribute__((always_inline)) {
    __asm__ volatile(
    "sbic %[_PORT_], %[_POS_]"
    "\n\t"
    "rjmp 9f"
    "\n\t"
    "push r27"
    "\n\t"
    "push r24"
    "\n\t"
    "in r24, __SREG__"
    "\n\t"
    "push r24"
    "\n\t"
    :
    : [_PORT_] "I"(RxPin::io_in), [_POS_] "I"(RxPin::bit));
    UART_DELAY(0, SWCLK_CYCLE / 2 - START_CYCLE);

    __asm__ volatile(
    "0: clr r24"
    "\n\t");
    __asm__ volatile("sbic %[_PORT_], %[_POS_] \n\t ori r24, %[_BIT_]" : : [_PORT_] "I"(RxPin::io_in), [_POS_] "I"(RxPin::bit), [_BIT_] "M"(0x01));
    UART_DELAY(1, 0);
    __asm__ volatile("sbic %[_PORT_], %[_POS_] \n\t ori r24, %[_BIT_]" : : [_PORT_] "I"(RxPin::io_in), [_POS_] "I"(RxPin::bit), [_BIT_] "M"(0x02));
    UART_DELAY(2, 0);
    __asm__ volatile("sbic %[_PORT_], %[_POS_] \n\t ori r24, %[_BIT_]" : : [_PORT_] "I"(RxPin::io_in), [_POS_] "I"(RxPin::bit), [_BIT_] "M"(0x04));
    UART_DELAY(3, 0);
    __asm__ volatile("sbic %[_PORT_], %[_POS_] \n\t ori r24, %[_BIT_]" : : [_PORT_] "I"(RxPin::io_in), [_POS_] "I"(RxPin::bit), [_BIT_] "M"(0x08));
    UART_DELAY(4, 0);
    __asm__ volatile("sbic %[_PORT_], %[_POS_] \n\t ori r24, %[_BIT_]" : : [_PORT_] "I"(RxPin::io_in), [_POS_] "I"(RxPin::bit), [_BIT_] "M"(0x10));
    UART_DELAY(5, 0);
    __asm__ volatile("sbic %[_PORT_], %[_POS_] \n\t ori r24, %[_BIT_]" : : [_PORT_] "I"(RxPin::io_in), [_POS_] "I"(RxPin::bit), [_BIT_] "M"(0x20));
    UART_DELAY(6, 0);
    __asm__ volatile("sbic %[_PORT_], %[_POS_] \n\t ori r24, %[_BIT_]" : : [_PORT_] "I"(RxPin::io_in), [_POS_] "I"(RxPin::bit), [_BIT_] "M"(0x40));
    UART_DELAY(7, 0);
    __asm__ volatile("sbic %[_PORT_], %[_POS_] \n\t ori r24, %[_BIT_]" : : [_PORT_] "I"(RxPin::io_in), [_POS_] "I"(RxPin::bit), [_BIT_] "M"(0x80));

    __asm__ volatile(
    "push r26"
    "\n\t"
    "lds r26, %[_PUT_]"
    "\n\t"
    "lds r27, %[_GET_]"
    "\n\t"
    "inc r26"
    "\n\t"
    "andi r26, %[_MSK_]"
    "\n\t"
    "andi r27, %[_MSK_]"
    "\n\t"
    "cpse r26, r27"
    "\n\t"
    "sts %[_PUT_], r26"
    "\n\t"
    "dec r26"
    "\n\t"
    "andi r26, %[_MSK_]"
    "\n\t"
    "clr r27"
    "\n\t"
    "subi r26, lo8(-(%[_BUF_]))"
    "\n\t"
    "sbci r27, hi8(-(%[_BUF_]))"
    "\n\t"
    "st x, r24"
    "\n\t"
    "pop r26"
    "\n\t"
    :
    : [_GET_] "m"(_rx_get), [_PUT_] "m"(_rx_put), [_BUF_] "m"(_rx_buf), [_MSK_] "M"(RXBUF_MSK));

    if (SHIFT_CYCLE >= 32) {
    __asm__ volatile("7: sbis %[_PORT_], %[_POS_] \n\t rjmp 7b" : : [_PORT_] "I"(RxPin::io_in), [_POS_] "I"(RxPin::bit));
    } else {
    __asm__ volatile(
    "sbis %[_PORT_], %[_POS_]"
    "\n\t"
    "rjmp 7f"
    "\n\t"
    "sbis %[_PORT_], %[_POS_]"
    "\n\t"
    "rjmp 7f"
    "\n\t"
    "sbis %[_PORT_], %[_POS_]"
    "\n\t"
    "rjmp 7f"
    "\n\t"
    "sbis %[_PORT_], %[_POS_]"
    "\n\t"
    "rjmp 7f"
    "\n\t"
    "sbis %[_PORT_], %[_POS_]"
    "\n\t"
    "rjmp 7f"
    "\n\t"
    "sbic %[_PORT_], %[_POS_]"
    "\n\t"
    "rjmp 8f"
    "\n\t"
    "7:"
    :
    : [_PORT_] "I"(RxPin::io_in), [_POS_] "I"(RxPin::bit));
    UART_DELAY(0, SWCLK_CYCLE / 2 - 8);
    __asm__ volatile("rjmp 0b \n\t 8:");
    }

    // Flag clearing and register restoration
    __asm__ volatile(
    "ldi r24, 1 << %[_POS_]"
    "\n\t"
    "out %[_IFR_], r24"
    "\n\t" // Note: Adjust the interrupt flag register (e.g., PCIFR) according to your specific hardware setup
    "pop r24"
    "\n\t"
    "out __SREG__, r24"
    "\n\t"
    "pop r24"
    "\n\t"
    "pop r27"
    "\n\t"
    "9:"
    "\n\t"
    :
    : [_IFR_] "I"(_SFR_IO_ADDR(PCIFR)), // <-- This assumes PCINT. Modify if using INT0/INT1, etc.
    [_POS_] "I"(0));
    }
    #undef UART_DELAY
    };

    // ---------------------------------------------------------
    // Static Member Definitions
    // ---------------------------------------------------------
    template
    uint8_t SoftUart::_rx_buf[SoftUart::RXBUF_SIZE];
    template
    uint8_t SoftUart
    ::_rx_get = 0;
    template
    volatile uint8_t SoftUart
    ::_rx_put = 0;

    } // namespace avr_uart

    1. お久しぶりです&コメントありがとうございます。
      これを作った当時は、AVRのインライン・アセンブラを覚えたての頃で今見るとコンパイラの最適化に絡む問題が出そうな書き方があったりしてお恥ずかしいかぎりですが、私のコメントのないコードが読める(読みたくない?)人ってあまりいないと思っていたのにそれを理解して自分なりに改良できるなんて...素晴らしいです!

  4. こちらこそモチベーションネタをいただきました。
    実は1Mbpsでしかテストしていなかった事で間抜けな事をしでかしていました。
    19200bps以上という制限がありますが、こちらの方が良いかと思います。


    #pragma once
    #include
    #include
    #include

    namespace avr_uart {

    // ==========================================
    // Traits class for pin definitions
    // ==========================================
    template
    struct Pin {
    static_assert(Port >= 'A' && Port <= 'L', "Port must be between 'A' and 'L'");
    static_assert(Bit <= 7, "Bit must be between 0 and 7");

    static constexpr uint8_t bit = Bit;
    static constexpr uint8_t mask = 1 << Bit;

    static constexpr uintptr_t mem_in = 0x20 + (Port - 'A') * 3;
    static constexpr uint8_t io_in = mem_in - __SFR_OFFSET;
    static constexpr uint8_t io_out = io_in + 2;
    static constexpr uint8_t io_tgl = io_in;

    static inline void mode_output() { *(volatile uint8_t*)(mem_in + 1) |= mask; }
    static inline void mode_input_pullup() {
    *(volatile uint8_t*)(mem_in + 1) &= ~mask;
    *(volatile uint8_t*)(mem_in + 2) |= mask;
    }
    static inline void mode_input() {
    *(volatile uint8_t*)(mem_in + 1) &= ~mask;
    *(volatile uint8_t*)(mem_in + 2) &= ~mask;
    }
    static inline void set_high() { *(volatile uint8_t*)(mem_in + 2) |= mask; }
    };

    struct PinDisabled {
    static constexpr uint8_t bit = 0xFF;
    static inline void mode_output() {}
    static inline void mode_input_pullup() {}
    static inline void mode_input() {}
    static inline void set_high() {}
    };

    // ==========================================
    // Software Serial Class
    // ==========================================
    template
    class SoftUart {
    private:
    static constexpr uint8_t RXBUF_SIZE = 1 < 0) { \
    if (cycle / DELAY_CYCLE) { \
    __asm__ volatile( \
    "ldi r27, %[_COUNT_]" "\n\t" \
    "1: rjmp .+0" "\n\t" \
    "rjmp .+0" "\n\t" \
    "dec r27" "\n\t" \
    "brne 1b" "\n\t" \
    : \
    : [_COUNT_] "M"(cycle / DELAY_CYCLE) \
    : "r27"); \
    } \
    if ((cycle % DELAY_CYCLE) > 0) __asm__ volatile("nop"); \
    if ((cycle % DELAY_CYCLE) > 1) __asm__ volatile("nop"); \
    if ((cycle % DELAY_CYCLE) > 2) __asm__ volatile("nop"); \
    if ((cycle % DELAY_CYCLE) > 3) __asm__ volatile("nop"); \
    if ((cycle % DELAY_CYCLE) > 4) __asm__ volatile("nop"); \
    if ((cycle % DELAY_CYCLE) > 5) __asm__ volatile("nop"); \
    } \
    } while (0)

    public:
    static void begin() {
    clear();
    if (TxPin::bit != 0xFF) {
    TxPin::set_high();
    TxPin::mode_output();
    }
    if (RxPin::bit != 0xFF) {
    RxPin::mode_input_pullup();
    }
    }

    static void end() {
    RxPin::mode_input();
    TxPin::mode_input();
    }

    static void clear() {
    _rx_put = 0;
    _rx_get = 0;
    }

    static uint8_t available() { return (_rx_put - _rx_get) & RXBUF_MSK; }

    static int read() {
    return available() ? _rx_buf[(_rx_get++) & RXBUF_MSK] : -1;
    }

    static void write(uint8_t data) {
    // Specification definition to reject values less than 19200 bps as a compile error
    static_assert(SWCLK_CYCLE = 32) {
    __asm__ volatile("7: sbis %[_PORT_], %[_POS_] \n\t rjmp 7b" : : [_PORT_] "I"(RxPin::io_in), [_POS_] "I"(RxPin::bit));
    } else {
    __asm__ volatile(
    "sbis %[_PORT_], %[_POS_]" "\n\t" "rjmp 7f" "\n\t"
    "sbis %[_PORT_], %[_POS_]" "\n\t" "rjmp 7f" "\n\t"
    "sbis %[_PORT_], %[_POS_]" "\n\t" "rjmp 7f" "\n\t"
    "sbis %[_PORT_], %[_POS_]" "\n\t" "rjmp 7f" "\n\t"
    "sbis %[_PORT_], %[_POS_]" "\n\t" "rjmp 7f" "\n\t"
    "sbic %[_PORT_], %[_POS_]" "\n\t" "rjmp 8f" "\n\t"
    "7:"
    : : [_PORT_] "I"(RxPin::io_in), [_POS_] "I"(RxPin::bit));
    UART_DELAY(0, SWCLK_CYCLE / 2 - 8);
    __asm__ volatile("rjmp 0b \n\t 8:");
    }

    __asm__ volatile(
    "ldi r24, 1 << %[_POS_]" "\n\t"
    "out %[_IFR_], r24" "\n\t" // <-- IMPORTANT: Assumes PCIFR. Adjust if using INTx.
    "pop r24" "\n\t"
    "out __SREG__, r24" "\n\t"
    "pop r24" "\n\t"
    "pop r27" "\n\t"
    "9:" "\n\t"
    : : [_IFR_] "I"(_SFR_IO_ADDR(PCIFR)), [_POS_] "I"(0)
    );
    }
    #undef UART_DELAY
    };

    template uint8_t SoftUart::_rx_buf[SoftUart::RXBUF_SIZE];
    template uint8_t SoftUart
    ::_rx_get = 0;
    template volatile uint8_t SoftUart
    ::_rx_put = 0;

    } // namespace avr_uart

    1. takaboo様
      コメントありがとうございます。
      ソフトウェアシリアルは送受信とも割込み禁止状態で実行することにご注意ください。
      例えば、19200bpsでは、1000000 / 19200 * 10 = 520us もの間、割込み禁止となるため他の割込みを使う処理を誤動作させる可能性があります。
      でも、自分が使いやすいように改良していくのって楽しいですよね?

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

CAPTCHA


このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください