Seeduino XIAO の予約販売をポチッたのは確か1月だったような...でも届いたのは3月に入ってから。ウイルス騒ぎのせいで出荷がかなり遅れたようだ。
Seeeduino XIAO – Arduino 互換ボード – SAMD21 Cortex M0+
16MHz動作のATtiny85はソフトでポートたたくと確か8MHz出たような気がするがATSAMD21G18はどうなんだろうという素朴な疑問があるのでまずはLチカかねてGPIOの速度試験をしてみた。
armのドキュメントによるとレジスタアクセスには2サイクルかかるとあるので48MHz動作のATSAMD21G18なら12MHz出るのかなと思っていたらなぜか6MHzしか出ない。シングルサイクルI/Oポートなら1サイクルとも記載されているが1命令に4サイクルかかっているのはなぜ?誰か知ってる人いる?
ちなみに下記写真の波形がナマってるのは上記写真のようないい加減な配線するとそうなりますよというお手本なのでマネしないように。(笑)
【サンプル・コード】
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
#include "Arduino.h" void setup() { pinMode(0, OUTPUT); } void loop() { PortGroup *pa = &PORT->Group[PORTA]; while (1) { pa->OUTCLR.reg = (1 << 2); pa->OUTSET.reg = (1 << 2); pa->OUTCLR.reg = (1 << 2); pa->OUTSET.reg = (1 << 2); pa->OUTCLR.reg = (1 << 2); pa->OUTSET.reg = (1 << 2); pa->OUTCLR.reg = (1 << 2); pa->OUTSET.reg = (1 << 2); } } |
【サンプル・コードを逆アセンブル】
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
00002198 <loop>: 2198: 4b05 ldr r3, [pc, #20] ; (21b0 <loop+0x18>) 219a: 2204 movs r2, #4 219c: 615a str r2, [r3, #20] 219e: 619a str r2, [r3, #24] 21a0: 615a str r2, [r3, #20] 21a2: 619a str r2, [r3, #24] 21a4: 615a str r2, [r3, #20] 21a6: 619a str r2, [r3, #24] 21a8: 615a str r2, [r3, #20] 21aa: 619a str r2, [r3, #24] 21ac: e7f4 b.n 2198 <loop> 21ae: 46c0 nop ; (mov r8, r8) 21b0: 41004400 .word 0x41004400 |
その後、下記ページの内容からシングルサイクルI/Oポートが0x60000000からマップされてることがわかった。
再度試してみたらな~んと本当に1サイクルで実行できてしまった。波形は綺麗ではないが確かに24MHz出てる。ということは通常のIOBUSは24MHzで動作してるということなのかな?
【シングルサイクルI/Oポートの例】
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
#include "Arduino.h" void setup() { pinMode(0, OUTPUT); } void loop() { PortGroup *pa = &PORT_IOBUS->Group[PORTA]; while (1) { pa->OUTSET.reg = (1 << 2); pa->OUTCLR.reg = (1 << 2); pa->OUTSET.reg = (1 << 2); pa->OUTCLR.reg = (1 << 2); pa->OUTSET.reg = (1 << 2); pa->OUTCLR.reg = (1 << 2); pa->OUTSET.reg = (1 << 2); pa->OUTCLR.reg = (1 << 2); } } |
ついでにチョー高速なIOBUSライブラリも作ってみた。引数を定数指定することでコンパイラの最適化により高速にアクセス可能となる。
おまけの機能としてArduinoではサポートされていないトグル操作、I/Oピンのディセーブル、出力ドライブ強度の設定も可能だ。出力ドライブ強度は規定だと数mA程度しか流せないので以外に便利かもしれない。
ちなみにピン番号はArduinoのものとは違うので注意してほしい。
【修正履歴】
2020-06-20
内部マクロ定義の変更とそれに伴う修正。
2020-06-03
非ARDUINO環境への対応
2020-05-13
XIAOポート定義にDACポートとコメントを追加
2020-05-12
multiplexing()の仕様変更と、このライブラリで指定するピン番号がわからない人が多そうなのでSeeeduino-XIAO限定でポート定義を追加してみた。
2020-05-09
multiplexing()を追加。
2020-04-30
各レジスタ及びポートのビット位置/値を取得するマクロの追加とそれに伴うコード変更。
2020-04-27
Single Cycle I/O Port からの入力は、古いデータを読まないよう連続サンプリングを有効にする必要があると書かれていたのでその対応をしてみた。
2020-04-25
ライブラリにピン番号を計算してくれるIOBUS_PIN(port,pos)マクロを追加。
【サンプル・スケッチ】
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
#include "IOBUS.h" #define LED_PORT 17 // A17 void setup(void) { // example(1): disable i/o pin. IOBUS::pinMode(LED_PORT, DISABLE); // example(2): normal drive strength. IOBUS::pinMode(LED_PORT, OUTPUT); // example(3): stronger drive strength. IOBUS::pinMode(LED_PORT, OUTPUT, true); } void loop(void) { IOBUS::toggleOutput(LED_PORT); delay(1000); } |
【チョー高速IOBUSライブラリ】
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 |
/* IOBUS.h - Single Cycle IOBUS Library for Microchip ATSAMD21 (Cortex®-M0+) Copyright (c) 2020 Sasapea's Lab. All right reserved. [i/o pin number mapping] PA00 - PA31 ... 0 - 31 or IOBUS_PIN('A', 0) - IOBUS_PIN('A', 31) PB00 - PB31 ... 32 - 63 or IOBUS_PIN('B', 0) - IOBUS_PIN('B', 31) 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef __IOBUS_H #define __IOBUS_H #ifdef ARDUINO #include "Arduino.h" #else #include <stdint.h> #include <stdbool.h> #include <sam.h> #define LOW 0 #define HIGH 1 #define INPUT 0 #define OUTPUT 1 #define INPUT_PULLUP 2 #define INPUT_PULLDOWN 3 #endif #define DISABLE -1 // GPIO disable mode #define IOBUS_PID(port) ((port) - 'A') #define IOBUS_PIN(port, pos) ((IOBUS_PID(port) << 5) | (pos & 31)) #define IOBUS_PORTID(pin) ((pin) >> 5) #define IOBUS_PINPOS(pin) ((pin) & 31) #define IOBUS_PINVAL(pin) (1 << IOBUS_PINPOS(pin)) // // Port Define for Seeeduino XIAO // #ifdef SEEED_XIAO_M0 #define IOBUS_PIN_13_LED IOBUS_PIN('A', 17) // YELLOW LED #define IOBUS_PIN_RX_LED IOBUS_PIN('A', 18) // BLUE LED #define IOBUS_PIN_TX_LED IOBUS_PIN('A', 19) // BLUE LED #define IOBUS_PIN_D0 IOBUS_PIN('A', 2) // INT/DAC #define IOBUS_PIN_D1 IOBUS_PIN('A', 4) // INT/AC0 #define IOBUS_PIN_D2 IOBUS_PIN('A', 10) // INT/GCLK #define IOBUS_PIN_D3 IOBUS_PIN('A', 11) // INT/GCLK #define IOBUS_PIN_D4 IOBUS_PIN('A', 8) // NMI/SDA #define IOBUS_PIN_D5 IOBUS_PIN('A', 9) // INT/SCL #define IOBUS_PIN_D6 IOBUS_PIN('B', 8) // INT/TX #define IOBUS_PIN_D7 IOBUS_PIN('B', 9) // INT/RX #define IOBUS_PIN_D8 IOBUS_PIN('A', 7) // INT/AC3/SCK #define IOBUS_PIN_D9 IOBUS_PIN('A', 5) // INT/AC1/MISO #define IOBUS_PIN_D10 IOBUS_PIN('A', 6) // INT/AC2/MOSI #define IOBUS_PIN_A0 IOBUS_PIN_D0 #define IOBUS_PIN_A1 IOBUS_PIN_D1 #define IOBUS_PIN_A2 IOBUS_PIN_D2 #define IOBUS_PIN_A3 IOBUS_PIN_D3 #define IOBUS_PIN_A4 IOBUS_PIN_D4 #define IOBUS_PIN_A5 IOBUS_PIN_D5 #define IOBUS_PIN_A6 IOBUS_PIN_D6 #define IOBUS_PIN_A7 IOBUS_PIN_D7 #define IOBUS_PIN_A8 IOBUS_PIN_D8 #define IOBUS_PIN_A9 IOBUS_PIN_D9 #define IOBUS_PIN_A10 IOBUS_PIN_D10 #define IOBUS_PIN_DAC IOBUS_PIN_D0 #define IOBUS_PIN_SDA IOBUS_PIN_D4 #define IOBUS_PIN_SCL IOBUS_PIN_D5 #define IOBUS_PIN_TX IOBUS_PIN_D6 #define IOBUS_PIN_RX IOBUS_PIN_D7 #define IOBUS_PIN_SCK IOBUS_PIN_D8 #define IOBUS_PIN_MISO IOBUS_PIN_D9 #define IOBUS_PIN_MOSI IOBUS_PIN_D10 #endif typedef enum { IOBUS_PMUX_A = PORT_PMUX_PMUXE_A_Val, IOBUS_PMUX_B = PORT_PMUX_PMUXE_B_Val, IOBUS_PMUX_C = PORT_PMUX_PMUXE_C_Val, IOBUS_PMUX_D = PORT_PMUX_PMUXE_D_Val, IOBUS_PMUX_E = PORT_PMUX_PMUXE_E_Val, IOBUS_PMUX_F = PORT_PMUX_PMUXE_F_Val, IOBUS_PMUX_G = PORT_PMUX_PMUXE_G_Val, IOBUS_PMUX_H = PORT_PMUX_PMUXE_H_Val, IOBUS_PMUX_DISABLE = PORT_PMUX_PMUXE_H_Val + 1, } IOBUS_PMUX; #define IOBUS_PGROUP(pin) PORT_IOBUS->Group[(pin) >> 5] #define IOBUS_DIRSET(pin) IOBUS_PGROUP(pin).DIRSET.reg #define IOBUS_DIRCLR(pin) IOBUS_PGROUP(pin).DIRCLR.reg #define IOBUS_DIRTGL(pin) IOBUS_PGROUP(pin).DIRTGL.reg #define IOBUS_OUTSET(pin) IOBUS_PGROUP(pin).OUTSET.reg #define IOBUS_OUTCLR(pin) IOBUS_PGROUP(pin).OUTCLR.reg #define IOBUS_OUTTGL(pin) IOBUS_PGROUP(pin).OUTTGL.reg #define IOBUS_IN(pin) IOBUS_PGROUP(pin).IN.reg class IOBUS { public: static void pinMode(uint32_t pin, uint32_t mode, bool drvstr = false) { PortGroup *PG = &IOBUS_PGROUP(pin); uint32_t cfg = IOBUS_PINPOS(pin); pin = IOBUS_PINVAL(pin); switch(mode) { case INPUT: PG->PINCFG[cfg].reg = (uint8_t)(PORT_PINCFG_INEN); PG->DIRCLR.reg = pin; PG->CTRL.reg |= pin; break ; case INPUT_PULLUP: PG->PINCFG[cfg].reg = (uint8_t)(PORT_PINCFG_INEN | PORT_PINCFG_PULLEN); PG->DIRCLR.reg = pin; PG->OUTSET.reg = pin; PG->CTRL.reg |= pin; break ; case INPUT_PULLDOWN: PG->PINCFG[cfg].reg = (uint8_t)(PORT_PINCFG_INEN | PORT_PINCFG_PULLEN); PG->DIRCLR.reg = pin; PG->OUTCLR.reg = pin; PG->CTRL.reg |= pin; break ; case OUTPUT: PG->PINCFG[cfg].reg = (uint8_t)(drvstr ? PORT_PINCFG_DRVSTR | PORT_PINCFG_INEN : PORT_PINCFG_INEN); PG->DIRSET.reg = pin; PG->CTRL.reg |= pin; break ; default: PG->PINCFG[cfg].reg = 0; PG->DIRCLR.reg = pin; PG->CTRL.reg &= ~pin; break ; } } static void digitalWrite(uint32_t pin, uint32_t val) { if (val) IOBUS_OUTSET(pin) = IOBUS_PINVAL(pin); else IOBUS_OUTCLR(pin) = IOBUS_PINVAL(pin); } static int digitalRead(uint32_t pin) { return (IOBUS_IN(pin) >> IOBUS_PINPOS(pin)) & 1; } static void toggleOutput(uint32_t pin) { IOBUS_OUTTGL(pin) = IOBUS_PINVAL(pin); } static void toggleDirection(uint32_t pin) { IOBUS_DIRTGL(pin) = IOBUS_PINVAL(pin); } static void multiplexing(uint32_t pin, IOBUS_PMUX mux) { PortGroup *PG = &IOBUS_PGROUP(pin); uint32_t cfg = IOBUS_PINPOS(pin); if (mux == IOBUS_PMUX_DISABLE) PG->PINCFG[cfg].bit.PMUXEN = 0; else { PG->PMUX[cfg >> 1].reg = (PG->PMUX[cfg >> 1].reg & (pin & 1 ? 0x0F : 0xF0)) | (pin & 1 ? mux << 4 : mux); PG->PINCFG[cfg].bit.PMUXEN = 1; } } }; #endif |