├── README.md ├── SWOArduinoSam.h ├── SWOStream.cpp ├── SWOStream.h ├── examples └── HelloWorld │ └── HelloWorld.ino └── library.properties /README.md: -------------------------------------------------------------------------------- 1 | # SerialWireOutput 2 | Serial Wire Output library for STM32duino ARM Arduinos 3 | 4 | With Serial Wire Output (SWO) an stm32duino can write tracing and logging to a pc at high speed. For SWO to work, you need a debugger probe to connect the program on your arduino to the debugger on your pc. SWO only runs on systems with arm processors, sorry. 5 | 6 | # Installation 7 | * install the [arduino IDE](https://www.arduino.cc/) 8 | * install [STM32duino library](https://github.com/stm32duino/Arduino_Core_STM32) 9 | * install [SerialWireOutput](https://github.com/koendv/SerialWireOutput) (this library) 10 | * connect a debugger probe with SWO support, e.g. a [segger](https://www.segger.com/products/debug-probes/j-link/) or a [black magic probe](https://github.com/blacksphere/blackmagic/wiki). Connect the SWO output of the target with the SWO input of the probe. 11 | * start tool to view the SWO stream. 12 | * run example File→Examples→SerialWireOutput→HelloWorld 13 | 14 | # Connection Diagram 15 | ![Connection Diagram](https://raw.githubusercontent.com/koendv/Connecting-Black-Magic-Probe-and-Blue-Pill/master/bmp_bp.svg "Connecting a Blue Pill to a Black Magic Probe") 16 | 17 | This is one way to connect a STM32F103 Blue Pill with a Black Magic Probe. This allows downloading firmware and debugging using SWD. At the same time, SWO provides high speed debugging output. 18 | 19 | Take care to connect only one power source to a Blue Pill board; feeding power through usb and a power pin at the same time may cause short circuit. 20 | 21 | # Arduino Use 22 | Use SWOStream where you would use Serial: 23 | ``` 24 | #include 25 | SWOStream s; 26 | void setup() { 27 | } 28 | void loop() { 29 | s.print("hello world! "); 30 | } 31 | ``` 32 | 33 | In the Arduino IDE, choose *Tools→Upload Method→BMP (Black Magic Probe)* and *Tools→Optimize→Debug (-g)*. 34 | 35 | When using e.g. Black Magic Probe you typically would use commands like: 36 | ``` 37 | koen@raspberrypi:~ $ gdb -q 38 | (gdb) target extended-remote /dev/ttyBmpGdb 39 | (gdb) monitor swdp_scan 40 | Available Targets: 41 | No. Att Driver 42 | 1 STM32F1 medium density M3/M4 43 | (gdb) attach 1 44 | (gdb) file /tmp/arduino_build_195867/HelloWorld.ino.elf 45 | (gdb) load 46 | (gdb) monitor traceswo 2250000 47 | (gdb) set mem inaccessible-by-default off 48 | (gdb) run 49 | ``` 50 | # Capturing the SWO stream 51 | 52 | Your typical debugger comes with tool a tool to capture the SWO stream. 53 | 54 | The commercial Jlink comes with [SWO viewer](https://www.segger.com/products/debug-probes/j-link/tools/j-link-swo-viewer/). 55 | 56 | The open source Black Magic Probe has the ``bmtrace`` SWO viewer: 57 | 58 | ![bmtrace](https://github.com/compuphase/Black-Magic-Probe-Book/raw/master/doc/bmtrace.png "bmtrace SWO viewer for Black Magic Probe") 59 | 60 | Companion utilities are the ``bmdebug`` debugger and the ``bmflash`` flashing tool. These tools are open source, like the Black Magic Probe itself. Download the [tools](https://github.com/compuphase/Black-Magic-Probe-Book/releases) and the [manual](https://github.com/compuphase/Black-Magic-Probe-Book/raw/master/BlackMagicProbe.pdf). 61 | 62 | Instead of using an external viewer to do the decoding, Black Magic Probe can also output a decoded SWO stream. In the device manager, Black Magic Probe shows up as two COM ports. On Windows , connect [PuTTY](https://www.chiark.greenend.org.uk/~sgtatham/putty/latest.html) to the second of the two COM ports, labeled "Black Magic UART Port". On linux, type ``cat /dev/ttyBmpTarg``. In another window, at the gdb prompt, type 63 | ``` 64 | (gdb) monitor traceswo 2250000 decode 65 | ``` 66 | to decode a 2250000 baud SWO stream. The decoded SWO stream is sent to the serial port and displayed by PuTTY. 67 | 68 | ## SWOStream 69 | The SWOStream Arduino class allows you to write tracing and logging to the PC where the debugger is running. Setup parameters are: 70 | 71 | **baudRate** Set baud rate. 72 | 73 | If the baud rate is zero, the SWOStream class will not set up the SWO pin. This can be used if the debugger, SWO viewer, or another SWOStream has already set up SWO. 74 | 75 | On a STM32F103-based Black Magic Probe, baud rate has to be 4500000 divided by an integer: 4500000, 2250000, 1500000, 1125000, 900000, 750000, ... . On a bmp clone, 2250000 is a safe choice. 76 | 77 | There is no default value. Check what baud rates your debugger supports. 78 | 79 | **swoProtocol** Protocol is *SWO_Manchester* or *SWO_Async* (asynchronous). Default value is *SWO_Async* . 80 | 81 | **swoChannel** a number between 0 and 31, used to distinguish different SWO streams. The default is 0, a safe choice. 82 | 83 | **swoEnable** set this to *false* to instantiate with SWO disabled. Default value is *true*. 84 | 85 | **cpuClockFrequency** Set this if your board has a non-standard crystal. 86 | 87 | ## SWO without debugger 88 | Some processors hang at power-up if you print to SWO and there is no debugger attached. As a workaround, boot with SWO disabled, and enable SWO from the debugger. To disable SWO at boot, instantiate with the fourth argument of SWOStream *false*, like this: 89 | ``` 90 | #include 91 | SWOStream s(2250000, SWO_Async, 0, false); // swoEnable=false 92 | int n=0; 93 | void setup() { 94 | pinMode(LED_BUILTIN, OUTPUT); 95 | } 96 | 97 | void loop() { 98 | digitalWrite(LED_BUILTIN, HIGH); 99 | delay(100); 100 | digitalWrite(LED_BUILTIN, LOW); 101 | delay(100); 102 | s.print(n++); 103 | s.print(" hello, world! \n"); 104 | s.flush(); 105 | } 106 | ``` 107 | The program will start up with SWO disabled. To enable SWO from gdb, type: 108 | ``` 109 | (gdb) target extended-remote /dev/ttyBmpGdb 110 | (gdb) monitor swd 111 | (gdb) attach 1 112 | (gdb) monitor traceswo decode 113 | (gdb) set mem inaccessible-by-default off 114 | (gdb) run 115 | ``` 116 | Interrupt with ctrl-C: 117 | ``` 118 | ^C 119 | Program received signal SIGINT, Interrupt. 120 | ``` 121 | Set Trace Enable Register all ones and continue execution: 122 | ``` 123 | (gdb) set {int}0xE0000E00=-1 124 | (gdb) continue 125 | ``` 126 | To disable SWO from the gdb command prompt, interrupt with ctrl-C and type: 127 | ``` 128 | ^C 129 | Program received signal SIGINT, Interrupt. 130 | (gdb) set {int}0xE0000E00=0 131 | (gdb) continue 132 | ``` 133 | # Acks 134 | Thanks to [orbuculum](https://github.com/orbcode/orbuculum), [Black Magic Probe book](https://github.com/compuphase/Black-Magic-Probe-Book), and Stephen P. WIlliams. 135 | 136 | If you find errors, omissions, or have additional data, feel free to open an issue. 137 | -------------------------------------------------------------------------------- /SWOArduinoSam.h: -------------------------------------------------------------------------------- 1 | #ifndef SWOARDUINOSAM_H 2 | #define SWOARDUINOSAM_H 3 | 4 | #include 5 | 6 | /* For some reason, these definitions are missing from the Arduino SAM core header files. */ 7 | /* The SAM3X8 is a Cortex-M3 core. */ 8 | 9 | /* 10 | * sources: 11 | * https://developer.arm.com/documentation/ddi0337/h/trace-port-interface-unit/tpiu-programmers-model?lang=en 12 | * ARMv7-M Architecture Reference Manual 13 | */ 14 | /** \brief Structure type to access the Trace Port Interface Unit (TPIU). */ 15 | typedef struct { 16 | __I uint32_t SSPSR; /*!< Offset: 0x000 (R/ ) Supported Parallel Port Sizes Register */ 17 | __IO uint32_t CSPSR; /*!< Offset: 0x004 (R/W) Current Parallel Port Size Register */ 18 | uint32_t RESERVED0[2]; /* Offsets: 0x008, 0x00C */ 19 | __IO uint32_t ACPR; /*!< Offset: 0x010 (R/W) Asynchronous Clock Prescaler Register */ 20 | uint32_t RESERVED1[55]; /* Offsets: 0x014 .. 0x0EC */ 21 | __IO uint32_t SPPR; /*!< Offset: 0x0F0 (R/W) Selected Pin Protocol Register */ 22 | uint32_t RESERVED2[131]; /* Offsets: 0x0F4 .. 0x2FC */ 23 | __I uint32_t FFSR; /*!< Offset: 0x300 (R/ ) Formatter and Flush Status Register x */ 24 | __IO uint32_t FFCR; /*!< Offset: 0x304 (R/W) Formatter and Flush Control Register */ 25 | __I uint32_t FSCR; /*!< Offset: 0x308 (R/ ) Formatter Synchronization Counter Register */ 26 | uint32_t RESERVED3[759]; /* Offsets: 0x30C .. 0xEE4 */ 27 | __I uint32_t TRIGGER; /*!< Offset: 0xEE8 (R/ ) TRIGGER */ 28 | __I uint32_t FIFODATA0; /*!< Offset: 0xEEC (R/ ) Integration FIFO 0 Data */ 29 | __I uint32_t ITATBCTR2; /*!< Offset: 0xEF0 (R/ ) ITATBCTR2 */ 30 | uint32_t RESERVED4[1]; /* Offset: 0xEF4 */ 31 | __I uint32_t ITATBCTR0; /*!< Offset: 0xEF8 (R/ ) ITATBCTR0 */ 32 | __I uint32_t FIFODATA1; /*!< Offset: 0xEFC (R/ ) Integration FIFO 1 Data */ 33 | __IO uint32_t ITCTRL; /*!< Offset: 0xF00 (R/W) Integration Mode Control */ 34 | uint32_t RESERVED5[39]; /* Offsets: 0xF04 .. 0xF9C */ 35 | __IO uint32_t CLAIMSET; /*!< Offset: 0xFA0 (R/W) Claim Tag Set */ 36 | __IO uint32_t CLAIMCLR; /*!< Offset: 0xFA4 (R/W) Claim Tag Clear */ 37 | uint32_t RESERVED6[8]; /* Offsets: 0xFA8 .. 0xFC4 */ 38 | __I uint32_t DEVID; /*!< Offset: 0xFC8 (R/ ) Device ID */ 39 | uint32_t RESERVED7[1]; /* Offset: 0xFCC */ 40 | __I uint32_t PID4; /*!< Offset: 0xFD0 (R/ ) Peripheral identification register 4 */ 41 | __I uint32_t PID5; /*!< Offset: 0xFD4 (R/ ) Peripheral identification register 5 */ 42 | __I uint32_t PID6; /*!< Offset: 0xFD8 (R/ ) Peripheral identification register 6 */ 43 | __I uint32_t PID7; /*!< Offset: 0xFDC (R/ ) Peripheral identification register 7 */ 44 | __I uint32_t PID0; /*!< Offset: 0xFE0 (R/ ) Peripheral identification register 0 */ 45 | __I uint32_t PID1; /*!< Offset: 0xFE4 (R/ ) Peripheral identification register 1 */ 46 | __I uint32_t PID2; /*!< Offset: 0xFE8 (R/ ) Peripheral identification register 2 */ 47 | __I uint32_t PID3; /*!< Offset: 0xFEC (R/ ) Peripheral identification register 3 */ 48 | __I uint32_t CID0; /*!< Offset: 0xFF0 (R/ ) Component identification register 0 */ 49 | __I uint32_t CID1; /*!< Offset: 0xFF4 (R/ ) Component identification register 1 */ 50 | __I uint32_t CID2; /*!< Offset: 0xFF8 (R/ ) Component identification register 2 */ 51 | __I uint32_t CID3; /*!< Offset: 0xFFC (R/ ) Component identification register 3 */ 52 | } TPI_Type; 53 | 54 | 55 | /** \brief Structure type to access some registers conspicuously missing from the ITM (Internal Trace Macrocell) definition. */ 56 | typedef struct { 57 | uint32_t RESERVED0[1004]; /* OFfsets: 0x000 .. 0xFAC */ 58 | __O uint32_t LAR; /*!< Offset: 0xFB0 ( /W) Lock Access Register */ 59 | __I uint32_t LSR; /*!< Offset: 0xFB4 (R/ ) Lock Status Register */ 60 | } ITM_Ext_Type; 61 | 62 | #define TPI_BASE 0xE0040000 /*!< TPIU base address */ 63 | 64 | #define TPI ((TPI_Type *) TPI_BASE) /*!< TPIU configuration struct */ 65 | #define ITM_Ext ((ITM_Ext_Type *) ITM_BASE) /*!< ITM extension configuration struct */ 66 | 67 | #endif 68 | -------------------------------------------------------------------------------- /SWOStream.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | /* 4 | * Arduino class to write to Serial Wire Output (SWO) 5 | * 2020 Koen De Vleeschauwer 6 | * No copyright asserted. 7 | */ 8 | 9 | /* See orbunculum, https://github.com/orbcode/orbuculum, 10 | * and bmtrace, https://github.com/compuphase/Black-Magic-Probe-Book 11 | */ 12 | 13 | SWOStream::SWOStream(uint32_t swoSpeedBaud, swoProtocolType swoProtocol, 14 | uint32_t swoChannel, bool swoEnable, 15 | uint32_t cpuClockFreqHz) { 16 | 17 | channel = 0; 18 | 19 | /* Don't initialize SWO if baudrate == 0 */ 20 | if (swoSpeedBaud == 0) 21 | return; 22 | 23 | if ((swoChannel < 0) || (swoChannel > 31)) 24 | swoChannel = 0; 25 | channel = swoChannel; 26 | 27 | /* when using Async code, clock equals baudrate. 28 | * When using Manchester code, clock equals double the baudrate. 29 | */ 30 | 31 | if (swoProtocol == SWO_Manchester) 32 | swoSpeedBaud = 2 * swoSpeedBaud; 33 | uint32_t swoPrescaler = (cpuClockFreqHz / swoSpeedBaud) - 1; 34 | 35 | /* SWO initialisation */ 36 | 37 | #if defined(STM32F0xx) && !defined(STM32F042x6) && !defined(STM32F048xx) 38 | /* for STM32F03x, STM32F05x, STM32F07x, STM32F09x; not for STM32F04x */ 39 | DBGMCU->CR |= DBGMCU_CR_TRACE_IOEN; 40 | #elif defined(STM32F1xx) 41 | RCC->APB2ENR |= RCC_APB2ENR_AFIOEN; /* enable AFIO access */ 42 | AFIO->MAPR |= AFIO_MAPR_SWJ_CFG_1; /* disable JTAG to release TRACESWO */ 43 | DBGMCU->CR |= DBGMCU_CR_TRACE_IOEN; /* enable IO trace pins */ 44 | #elif defined(STM32F2xx) || defined(STM32F3xx) 45 | DBGMCU->CR |= DBGMCU_CR_TRACE_IOEN; 46 | #elif defined(STM32F4xx) || defined(STM32F7xx) 47 | RCC->AHB1ENR |= RCC_AHB1ENR_GPIOBEN; /* enable GPIOB clock */ 48 | GPIOB->MODER = 49 | (GPIOB->MODER & ~0x000000c0) | 0x00000080; /* alternate func for PB3 */ 50 | GPIOB->AFR[0] &= ~0x0000f000; /* set AF0 (==TRACESWO) on PB3 */ 51 | GPIOB->OSPEEDR |= 0x000000c0; /* set max speed on PB3 */ 52 | GPIOB->PUPDR &= ~0x000000c0; /* no pull-up or pull-down on PB3 */ 53 | DBGMCU->CR |= DBGMCU_CR_TRACE_IOEN; /* enable IO trace pins */ 54 | #elif defined(STM32G0xx) 55 | #error no SWO on Cortex-M0. SWO only on Cortex-M3, M4, M7 and M33. 56 | #elif defined(STM32G4xx) 57 | #elif defined(STM32H7xx) 58 | #elif defined(STM32L0xx) 59 | #error no SWO on Cortex-M0. SWO only on Cortex-M3, M4, M7 and M33. 60 | #elif defined(STM32L1xx) 61 | #elif defined(STM32L4xx) 62 | RCC->AHB2ENR |= RCC_AHB2ENR_GPIOBEN; 63 | GPIOB->MODER = 64 | (GPIOB->MODER & ~0x000000c0) | 0x00000080; /* alternate func for PB3 */ 65 | GPIOB->AFR[0] &= ~0x0000f000; /* set AF0 (==TRACESWO) on PB3 */ 66 | GPIOB->OSPEEDR |= 0x000000c0; /* set max speed on PB3 */ 67 | GPIOB->PUPDR &= ~0x000000c0; /* no pull-up or pull-down on PB3 */ 68 | DBGMCU->CR |= DBGMCU_CR_TRACE_IOEN; /* enable IO trace pins */ 69 | #elif defined(STM32MP1xx) 70 | #elif defined(STM32WBxx) 71 | #elif defined(ARDUINO_ARCH_SAM) 72 | #endif 73 | 74 | CoreDebug->DEMCR = CoreDebug_DEMCR_TRCENA_Msk; /* trace enable */ 75 | TPI->CSPSR = 1; /* protocol width = 1 bit */ 76 | TPI->SPPR = swoProtocol; /* 1 = Manchester, 2 = Asynchronous */ 77 | TPI->ACPR = swoPrescaler; /* frequency */ 78 | TPI->FFCR = 0; /* turn off formatter, discard ETM output */ 79 | #if defined(ARDUINO_ARCH_SAM) 80 | ITM_Ext->LAR = 0xC5ACCE55; /* unlock access to ITM registers */ 81 | #else 82 | ITM->LAR = 0xC5ACCE55; /* unlock access to ITM registers */ 83 | #endif 84 | ITM->TCR = 85 | ITM_TCR_SWOENA_Msk | ITM_TCR_ITMENA_Msk; /* trace control register */ 86 | ITM->TPR = 0; /* all ports accessible unprivileged */ 87 | if (swoEnable) 88 | ITM->TER = 0xFFFFFFFF; /* enable all stimulus channels */ 89 | else 90 | ITM->TER = 0x0; /* disable all stimulus channels */ 91 | }; 92 | 93 | SWOStream::~SWOStream() {} 94 | 95 | /* set current stimulus channel */ 96 | 97 | void SWOStream::setChannel(uint32_t swoChannel) { 98 | if ((swoChannel < 0) || (swoChannel > 31)) 99 | swoChannel = 0; 100 | channel = swoChannel; 101 | } 102 | 103 | /* enable/disable SWO */ 104 | 105 | void SWOStream::enable(bool swoEnable) { 106 | if (swoEnable) 107 | ITM->TER = 0xFFFFFFFF; /* enable all stimulus channels */ 108 | else 109 | ITM->TER = 0x0; /* disable all stimulus channels */ 110 | } 111 | 112 | /* print on traceswo console */ 113 | 114 | size_t SWOStream::write(uint8_t ch) { 115 | if (((ITM->TCR & ITM_TCR_ITMENA_Msk) != 0UL) && 116 | ((ITM->TER & 1UL << channel) != 0UL)) { /* enabled */ 117 | while (ITM->PORT[channel].u32 == 0UL) 118 | ; 119 | ITM->PORT[channel].u8 = ch; 120 | } 121 | return 1; 122 | } 123 | 124 | size_t SWOStream::write(const uint8_t *buffer, size_t size) { 125 | if (((ITM->TCR & ITM_TCR_ITMENA_Msk) != 0UL) && 126 | ((ITM->TER & 1UL << channel) != 0UL)) { /* enabled */ 127 | size_t n = size; 128 | while (n > 3) { 129 | uint32_t buf32; 130 | memcpy(&buf32, buffer, sizeof(buf32)); 131 | while (ITM->PORT[channel].u32 == 0UL) 132 | ; 133 | ITM->PORT[channel].u32 = buf32; 134 | buffer += 4; 135 | n -= 4; 136 | } 137 | if (n > 1) { 138 | uint16_t buf16; 139 | memcpy(&buf16, buffer, sizeof(buf16)); 140 | while (ITM->PORT[channel].u32 == 0UL) 141 | ; 142 | ITM->PORT[channel].u16 = buf16; 143 | buffer += 2; 144 | n -= 2; 145 | } 146 | while (n > 0) { 147 | while (ITM->PORT[channel].u32 == 0UL) 148 | ; 149 | ITM->PORT[channel].u8 = *buffer; 150 | buffer++; 151 | --n; 152 | } 153 | } 154 | return (size); 155 | } 156 | 157 | void SWOStream::flush() { 158 | /* 159 | * write null bytes to flush input buffers. typical buffer size is 64 bytes. 160 | * bmp feature: a write to channel 31 flushes output. 161 | */ 162 | 163 | uint32_t curr_channel = channel; 164 | channel = 31; 165 | for (int i = 0; i < 64; i++) 166 | SWOStream::write('\0'); 167 | channel = curr_channel; 168 | }; 169 | 170 | /* not truncated */ 171 | -------------------------------------------------------------------------------- /SWOStream.h: -------------------------------------------------------------------------------- 1 | #ifndef SWOSTREAM_H 2 | #define SWOSTREAM_H 3 | 4 | /* 5 | * Arduino class to write to Serial Wire Output (SWO) 6 | * 2020 Koen De Vleeschauwer 7 | * No copyright asserted. 8 | */ 9 | 10 | #include 11 | #if defined(ARDUINO_ARCH_SAM) 12 | # include "SWOArduinoSAM.h" 13 | #else 14 | # include 15 | #endif 16 | 17 | typedef enum { 18 | SWO_Manchester = 0x1, 19 | SWO_Async = 0x2, 20 | } swoProtocolType; 21 | 22 | class SWOStream : public Print { 23 | private: 24 | uint32_t channel; 25 | 26 | public: 27 | SWOStream(uint32_t swoSpeedBaud, swoProtocolType swoProtocol = SWO_Async, 28 | uint32_t swoChannel = 0, bool swoEnable = true, 29 | uint32_t cpuClockFreqHz = SystemCoreClock); 30 | ~SWOStream(); 31 | 32 | void setChannel(uint32_t swoChannel); 33 | void enable(bool swoEnable); 34 | virtual size_t write(uint8_t ch); 35 | virtual size_t write(const uint8_t *buffer, size_t size); 36 | virtual void flush(); 37 | }; 38 | #endif 39 | 40 | /* not truncated */ 41 | -------------------------------------------------------------------------------- /examples/HelloWorld/HelloWorld.ino: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | SWOStream s(2250000); // Check what baudrate your debugger supports 4 | int n=0; 5 | 6 | void setup() { 7 | } 8 | 9 | void loop() { 10 | s.print(n++); 11 | s.print(" hello, world! "); 12 | } 13 | 14 | /* not truncated */ 15 | -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=SerialWireOutput 2 | version=1.0.5 3 | author=Koen De Vleeschauwer 4 | maintainer=Koen De Vleeschauwer 5 | sentence=With Serial Wire Output (SWO) an stm32duino can write tracing and logging to a pc at high speed. 6 | paragraph=To use this library, you need a debugger probe to connect your arduino to your pc. Serial Wire Output only works on systems with arm processors, sorry. 7 | category=Communication 8 | url=https://github.com/koendv/SerialWireOutput 9 | architectures=stm32 10 | --------------------------------------------------------------------------------