├── .gitignore ├── test └── README ├── platformio.ini ├── lib └── README ├── include └── README ├── .travis.yml ├── README.md ├── src └── main.cpp └── libsigrok.patch /.gitignore: -------------------------------------------------------------------------------- 1 | .pio 2 | .vscode/ 3 | -------------------------------------------------------------------------------- /test/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for PIO Unit Testing and project tests. 3 | 4 | Unit Testing is a software testing method by which individual units of 5 | source code, sets of one or more MCU program modules together with associated 6 | control data, usage procedures, and operating procedures, are tested to 7 | determine whether they are fit for use. Unit testing finds problems early 8 | in the development cycle. 9 | 10 | More information about PIO Unit Testing: 11 | - https://docs.platformio.org/page/plus/unit-testing.html 12 | -------------------------------------------------------------------------------- /platformio.ini: -------------------------------------------------------------------------------- 1 | ;PlatformIO Project Configuration File 2 | ; 3 | ; Build options: build flags, source filter 4 | ; Upload options: custom upload port, speed and extra flags 5 | ; Library options: dependencies, extra library storages 6 | ; Advanced options: extra scripting 7 | ; 8 | ; Please visit documentation for the other options and examples 9 | ; https://docs.platformio.org/page/projectconf.html 10 | 11 | [env:genericSTM32F103C8] 12 | ; NOTE: 5.6.0 usde to work 13 | platform = ststm32 14 | board = genericSTM32F103C8 15 | framework = arduino 16 | 17 | monitor_port = /dev/ttyUSB2 18 | monitor_speed = 1200000 -------------------------------------------------------------------------------- /lib/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for project specific (private) libraries. 3 | PlatformIO will compile them to static libraries and link into executable file. 4 | 5 | The source code of each library should be placed in a an own separate directory 6 | ("lib/your_library_name/[here are source files]"). 7 | 8 | For example, see a structure of the following two libraries `Foo` and `Bar`: 9 | 10 | |--lib 11 | | | 12 | | |--Bar 13 | | | |--docs 14 | | | |--examples 15 | | | |--src 16 | | | |- Bar.c 17 | | | |- Bar.h 18 | | | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html 19 | | | 20 | | |--Foo 21 | | | |- Foo.c 22 | | | |- Foo.h 23 | | | 24 | | |- README --> THIS FILE 25 | | 26 | |- platformio.ini 27 | |--src 28 | |- main.c 29 | 30 | and a contents of `src/main.c`: 31 | ``` 32 | #include 33 | #include 34 | 35 | int main (void) 36 | { 37 | ... 38 | } 39 | 40 | ``` 41 | 42 | PlatformIO Library Dependency Finder will find automatically dependent 43 | libraries scanning project source files. 44 | 45 | More information about PlatformIO Library Dependency Finder 46 | - https://docs.platformio.org/page/librarymanager/ldf.html 47 | -------------------------------------------------------------------------------- /include/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for project header files. 3 | 4 | A header file is a file containing C declarations and macro definitions 5 | to be shared between several project source files. You request the use of a 6 | header file in your project source file (C, C++, etc) located in `src` folder 7 | by including it, with the C preprocessing directive `#include'. 8 | 9 | ```src/main.c 10 | 11 | #include "header.h" 12 | 13 | int main (void) 14 | { 15 | ... 16 | } 17 | ``` 18 | 19 | Including a header file produces the same results as copying the header file 20 | into each source file that needs it. Such copying would be time-consuming 21 | and error-prone. With a header file, the related declarations appear 22 | in only one place. If they need to be changed, they can be changed in one 23 | place, and programs that include the header file will automatically use the 24 | new version when next recompiled. The header file eliminates the labor of 25 | finding and changing all the copies as well as the risk that a failure to 26 | find one copy will result in inconsistencies within a program. 27 | 28 | In C, the usual convention is to give header files names that end with `.h'. 29 | It is most portable to use only letters, digits, dashes, and underscores in 30 | header file names, and at most one dot. 31 | 32 | Read more about using header files in official GCC documentation: 33 | 34 | * Include Syntax 35 | * Include Operation 36 | * Once-Only Headers 37 | * Computed Includes 38 | 39 | https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html 40 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # Continuous Integration (CI) is the practice, in software 2 | # engineering, of merging all developer working copies with a shared mainline 3 | # several times a day < https://docs.platformio.org/page/ci/index.html > 4 | # 5 | # Documentation: 6 | # 7 | # * Travis CI Embedded Builds with PlatformIO 8 | # < https://docs.travis-ci.com/user/integration/platformio/ > 9 | # 10 | # * PlatformIO integration with Travis CI 11 | # < https://docs.platformio.org/page/ci/travis.html > 12 | # 13 | # * User Guide for `platformio ci` command 14 | # < https://docs.platformio.org/page/userguide/cmd_ci.html > 15 | # 16 | # 17 | # Please choose one of the following templates (proposed below) and uncomment 18 | # it (remove "# " before each line) or use own configuration according to the 19 | # Travis CI documentation (see above). 20 | # 21 | 22 | 23 | # 24 | # Template #1: General project. Test it using existing `platformio.ini`. 25 | # 26 | 27 | # language: python 28 | # python: 29 | # - "2.7" 30 | # 31 | # sudo: false 32 | # cache: 33 | # directories: 34 | # - "~/.platformio" 35 | # 36 | # install: 37 | # - pip install -U platformio 38 | # - platformio update 39 | # 40 | # script: 41 | # - platformio run 42 | 43 | 44 | # 45 | # Template #2: The project is intended to be used as a library with examples. 46 | # 47 | 48 | # language: python 49 | # python: 50 | # - "2.7" 51 | # 52 | # sudo: false 53 | # cache: 54 | # directories: 55 | # - "~/.platformio" 56 | # 57 | # env: 58 | # - PLATFORMIO_CI_SRC=path/to/test/file.c 59 | # - PLATFORMIO_CI_SRC=examples/file.ino 60 | # - PLATFORMIO_CI_SRC=path/to/test/directory 61 | # 62 | # install: 63 | # - pip install -U platformio 64 | # - platformio update 65 | # 66 | # script: 67 | # - platformio ci --lib="." --board=ID_1 --board=ID_2 --board=ID_N 68 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Use stm32f103 as a hobby oscilloscope. 2 | 3 | This has two channels, measuring on PA0 and PA1, 4 | this can be easily increased in the code though. 5 | Be careful to not fry your microcontroller, 6 | don't give more than 3.3V (VDD) to them. 7 | 8 | It uses USART1 (Pins A9 and A10) to communicate with your computer. 9 | 10 | ### Functional overview: 11 | 12 | * `TIM1` triggers `ADC1` to sample 13 | * `DMA1_CH1` transfers `ADC1->DR` to memory 14 | * `DMA1_CH1` interrupt handler starts `DMA1_CH4` (on half and complete) 15 | * `DMA1_CH4` transfers memory to `USART1` 16 | 17 | ### Protocol 18 | 19 | Commands are sent from computer over usart: 20 | 21 | * `x01 STOP` stops current sampling (stops TIM1) 22 | * `x02 START` starts sampling (starts TIM1) 23 | * `x03 GET_SAMPLERATE` Returns the samplerate, as ascii, newline terminated 24 | 25 | 26 | Samples will be flowing out of USART, 2 bytes per channel. 27 | Note that the ADC is only 12bit so the highest 4bits are unused in each sample. 28 | 29 | 30 | ### Noise 31 | 32 | If your stm32f103 package doesn't have a VREF pin 33 | (most don't, for example the bluepill doesn't) 34 | VDD will be the reference point, and any noise there 35 | will affect your measurements, but if you worry about 36 | +-100mV this is not the tool for you anyway. 37 | 38 | As the ADC input impedence is quite low you may want to put a voltage buffer in front of each channel, 39 | e.g. an opamp with the output connected to negative input, 40 | and then put your signal on the positive input of the opamp. 41 | 42 | ### Sigrok 43 | 44 | To use with sigrok/pulseview, apply [libsigrok.patch](./libsigrok.patch), 45 | the device scanning is just hardcoded, so make sure to change protocol.h 46 | to point to the serial device your stm32 shows up as if you want to use it with 47 | pulseview: 48 | 49 | ``` 50 | #define STM32SCOPE_DEFAULT_CONN "/dev/ttyUSB0"; 51 | ``` 52 | 53 | With sigrok you can specify the device directly: 54 | 55 | ``` 56 | LD_LIBRARY_PATH=../lib ./sigrok-cli --driver=stm32scope:conn=/dev/ttyUSB0 --samples=200 --channels=A0,A1 57 | ``` 58 | 59 | 60 | License: 61 | libigrok.patch: GPLv3 62 | The rest: MIT 63 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | // A0 and A1, used to meassure signal 2 | // USART used for data, pins, A9 TX, A10 RX 3 | // TIM1 (timer) that tells ADC to meassure 4 | // ADC (analog 2 digital converter) gives us 12bit measurment of voltage 5 | // DMA (direct memory access) shuffles them to RAM 6 | // DMA shuffles the RAM out over serial data port to computer 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | static const uint32_t baud_rate = 1200000; 14 | static const uint8_t error_pin = PB12; 15 | 16 | static const uint8_t adc_clock_divider = 2; 17 | static const adc_smp_rate adc_sampletime = ADC_SMPR_7_5; // Also change in parameter_sanity_check 18 | 19 | static const uint32_t adc_timer_prescaler = 40; 20 | static const uint32_t adc_timer_arr = 64; 21 | static const uint32_t adc_samplerate = CLOCK_SPEED_HZ / adc_timer_prescaler / adc_timer_arr; 22 | // Time to *perform* one sample 23 | // 11.6: 24 | // Tconv = sampling time + 12.5 25 | // with 7.5 cycles sampling time, TConv = 19.5 adc cycles 26 | // with 72MHz cpu clock, and 8 divider on adc 27 | // total time = (19.5 cycles) / (72 MHz / 8) = 2.17µs 28 | // so can go up to 460ksps with current settings, but transmission is bottleneck 29 | // 30 | // With adc div 8, sample time 77.5 cycles, 72MHz clock 31 | // 1 adc cycle = 8/72MHz 32 | // total_time = 77.5 + 12.5 adc cycles = 90 adc cycles = 10µs 33 | // up to 100ksps 34 | // 35 | // With adc div 8, sample time 239.5 cycles 36 | // total time = 239.5 + 12.5 adc cycles = 252 adc cycles = 28µs 37 | // up to 35ksps 38 | 39 | 40 | 41 | enum commands 42 | { 43 | CMD_STOP = 1, 44 | CMD_START = 2, 45 | CMD_GET_SAMPLERATE = 3, 46 | CMD_GET_ERROR = 4, 47 | CMD_SET_TRIGGER = 5, 48 | }; 49 | enum cmd_state_type 50 | { 51 | CMD_STATE_IDLE = 0, 52 | CMD_STATE_READ_ARG = 1, 53 | }; 54 | 55 | volatile auto cmd_state = CMD_STATE_IDLE; 56 | volatile uint8_t cmd_buf[6]; 57 | volatile size_t cmd_buf_ix = 0; 58 | 59 | enum error_type 60 | { 61 | NO_ERROR = 0, 62 | USART_BUSY_ERROR = 1, 63 | ADC_DMA_ERROR = 1 << 2, 64 | USART_DMA_ERROR = 1 << 3, 65 | INVALID_ADC_CLK_DIV = 1 << 4, 66 | BAD_PARAMETERS_TIMER_FASTER_THAN_ADC = 1 << 5, 67 | BAD_PARAMETERS_SERIAL_TOO_SLOW = 1 << 6, 68 | }; 69 | volatile uint32_t error = NO_ERROR; 70 | 71 | STM32ADC myADC(ADC1); 72 | //Channels to be acquired. 73 | uint8 pins[] = {PA0, PA1}; 74 | 75 | const size_t nPins = sizeof(pins) / sizeof(uint8); 76 | 77 | // Array for the ADC data 78 | const size_t adc_buffer_len = 200 * nPins; 79 | uint16_t adc_buffer[adc_buffer_len]; 80 | 81 | uint32 nirqs_o = 0; 82 | 83 | volatile bool usart_busy = false; 84 | 85 | 86 | // AWD 87 | // Set when AWD fired. 88 | // TODO figure out how to tag the exact event that triggered the interrupt, possible? 89 | volatile bool adc_awd_did_interrupt = false; 90 | 91 | 92 | /* Dump half ADC buffer on usart */ 93 | void trigger_adc_buffer_to_usart(bool second_half) 94 | { 95 | if (usart_busy) 96 | { 97 | error |= USART_BUSY_ERROR; 98 | return; 99 | } 100 | usart_busy = true; 101 | 102 | // Tag interrupt happened 103 | // This sucks as it will wiggle around and not be exactly when the AWD fired. 104 | if(adc_awd_did_interrupt) { 105 | if (second_half) { 106 | adc_buffer[adc_buffer_len / 2] |= 1<<14; 107 | } else { 108 | adc_buffer[0] |= 1<<14; 109 | } 110 | adc_awd_did_interrupt = false; 111 | } 112 | 113 | dma_setup_transfer( 114 | DMA1, 115 | DMA_CH4, 116 | &USART1->regs->DR, 117 | DMA_SIZE_8BITS, 118 | adc_buffer + (second_half ? adc_buffer_len / 2: 0), 119 | DMA_SIZE_8BITS, 120 | (DMA_MINC_MODE | DMA_FROM_MEM | DMA_TRNS_CMPLT)); 121 | // Note; /2 because we're transfering half of it, *2 because 16bit data sent as 8bit 122 | dma_set_num_transfers(DMA1, DMA_CH4, (adc_buffer_len) * 2 / 2); 123 | dma_enable(DMA1, DMA_CH4); 124 | } 125 | 126 | void on_adc_dma_complete() 127 | { 128 | const auto irq_cause = dma_get_irq_cause(DMA1, DMA_CH1); 129 | switch (irq_cause) 130 | { 131 | case DMA_TRANSFER_HALF_COMPLETE: 132 | trigger_adc_buffer_to_usart(false); 133 | break; 134 | 135 | case DMA_TRANSFER_COMPLETE: 136 | trigger_adc_buffer_to_usart(true); 137 | break; 138 | 139 | case DMA_TRANSFER_ERROR: 140 | error |= ADC_DMA_ERROR; 141 | break; 142 | 143 | default: 144 | break; 145 | } 146 | return; 147 | } 148 | 149 | void on_adc_awd_interrupt() 150 | { 151 | // Disable watchdog (interrupt bit already cleared) 152 | ADC1->regs->CR1 &= ~ADC_CR1_AWDEN; 153 | 154 | DMA1->regs->CMAR1; 155 | dma_channel_reg_map *chan_regs; 156 | 157 | 158 | // TODO come back to this, this is too error prone 159 | // chan_regs = dma_channel_regs(DMA1, DMA_CH1); 160 | // uint16_t * value = reinterpret_cast(chan_regs->CMAR); 161 | adc_awd_did_interrupt = true; 162 | } 163 | 164 | volatile bool got_usart_irq = false; 165 | 166 | /* Reset usart busy when data has been transferred */ 167 | void on_dma_to_usart_done() 168 | { 169 | const dma_irq_cause usart_irq_cause = dma_get_irq_cause(DMA1, DMA_CH4); 170 | switch (usart_irq_cause) 171 | { 172 | case DMA_TRANSFER_ERROR: 173 | error |= USART_DMA_ERROR; 174 | default: 175 | break; 176 | } 177 | usart_busy = false; 178 | got_usart_irq = true; 179 | } 180 | 181 | void setup_timer() 182 | { 183 | // TODO: WTF, timer_init makes it not work, while just rcc_clk_enable is fine... 184 | // timer_init(TIMER1); 185 | rcc_clk_enable(TIMER1->clk_id); 186 | timer_set_prescaler(TIMER1, adc_timer_prescaler); 187 | timer_set_reload(TIMER1, adc_timer_arr); 188 | timer_set_compare(TIMER1, TIMER_CH1, adc_timer_arr / 2); 189 | timer_oc_set_mode(TIMER1, TIMER_CH1, TIMER_OC_MODE_PWM_1, 0); 190 | timer_cc_enable(TIMER1, TIMER_CH1); 191 | timer_cc_set_pol(TIMER1, TIMER_CH1, 1); 192 | 193 | timer_resume(TIMER1); 194 | } 195 | 196 | void setup_adc() 197 | { 198 | // Set up our analog pin(s) 199 | for (size_t j = 0; j < nPins; j++) 200 | pinMode(pins[j], INPUT_ANALOG); 201 | 202 | // Slow ADC the fuck down, this sets the ADCPRE (hopefully) 203 | // 00: PCLK2/2 204 | // 01: PCLK2/4 205 | // 10: PCLK2/6 206 | // 11: PCLK2/8 207 | uint32_t div = RCC_ADCPRE_PCLK_DIV_8; 208 | switch (adc_clock_divider) { 209 | case 2: 210 | div = RCC_ADCPRE_PCLK_DIV_2; 211 | break; 212 | case 4: 213 | div = RCC_ADCPRE_PCLK_DIV_4; 214 | break; 215 | case 6: 216 | div = RCC_ADCPRE_PCLK_DIV_6; 217 | break; 218 | case 8: 219 | div = RCC_ADCPRE_PCLK_DIV_8; 220 | break; 221 | default: 222 | error |= INVALID_ADC_CLK_DIV; 223 | }; 224 | RCC_BASE->CFGR = (RCC_BASE->CFGR & ~RCC_CFGR_ADCPRE) | div; 225 | 226 | delay_us(1000); // Maybe not needed, whatever 227 | 228 | // ADC calibration should be done at startup 229 | myADC.calibrate(); 230 | 231 | // myADC.setSampleRate(ADC_SMPR_1_5); //set the Sample Rate 232 | // myADC.setSampleRate(ADC_SMPR_7_5); 233 | // myADC.setSampleRate(ADC_SMPR_239_5); 234 | myADC.setSampleRate(adc_sampletime); 235 | myADC.setScanMode(); //set the ADC in Scan mode. 236 | myADC.setPins(pins, nPins); //set how many and which pins to convert. 237 | myADC.resetContinuous(); 238 | // myADC.setContinuous(); 239 | myADC.setTrigger(ADC_EXT_EV_TIM1_CC1); 240 | 241 | //set the DMA transfer for the ADC. 242 | //in this case we want to increment the memory side and run it in circular mode 243 | dma_set_priority(DMA1, DMA_CH1, DMA_PRIORITY_HIGH); 244 | 245 | myADC.setDMA( 246 | adc_buffer, 247 | adc_buffer_len, 248 | (DMA_MINC_MODE | DMA_CIRC_MODE | DMA_HALF_TRNS | DMA_TRNS_CMPLT), 249 | on_adc_dma_complete); 250 | 251 | //start the conversion. 252 | myADC.startConversion(); 253 | 254 | // enable_awd_irq(ADC1); 255 | adc_attach_interrupt(ADC1, ADC_AWD, on_adc_awd_interrupt); 256 | } 257 | 258 | void stop() 259 | { 260 | timer_pause(TIMER1); 261 | } 262 | 263 | void start() 264 | { 265 | timer_resume(TIMER1); 266 | digitalWrite(error_pin, LOW); 267 | } 268 | 269 | 270 | /** 271 | * Trigger mode 272 | * - Analog watchog is set up for the specified threshold 273 | * - When watchdog triggers, the measured value will be tagged 274 | * - User can then detect this tag 275 | * - TODO tag should go in the high unused bits, but how to target it correctly in time? 276 | */ 277 | void handle_trigger_cmd() { 278 | uint8_t cmd = cmd_buf[0]; 279 | if (cmd != CMD_SET_TRIGGER) { 280 | // TODO set error 281 | return; 282 | } 283 | if (cmd_buf_ix < 5) { 284 | // Wait for more arguments 285 | cmd_state = CMD_STATE_READ_ARG; 286 | return; 287 | } 288 | 289 | uint8 channel = cmd_buf[1]; 290 | uint16_t value_low = (((uint16_t)cmd_buf[2]) << 8) + cmd_buf[3]; // TOD low and high, maybe multi-byte 291 | uint16_t value_high = (((uint16_t)cmd_buf[4]) << 8) + cmd_buf[5]; 292 | 293 | // TODO check that arguments make sense 294 | 295 | 296 | // set_awd_channel(ADC1, channel); 297 | ADC1->regs->CR1 |= (channel & ADC_CR1_AWDCH); 298 | 299 | // set_awd_low_limit(ADC1, (uint32)value_low); 300 | // set_awd_high_limit(ADC1, (uint32)value_high); 301 | ADC1->regs->LTR = value_low; 302 | ADC1->regs->HTR = value_high; 303 | 304 | // enable_awd(ADC1); 305 | ADC1->regs->CR1 |= ADC_CR1_AWDEN; 306 | 307 | cmd_state = CMD_STATE_IDLE; 308 | } 309 | 310 | void handle_cmd() 311 | { 312 | uint8_t cmd = cmd_buf[0]; 313 | switch (cmd) 314 | { 315 | case CMD_STOP: 316 | stop(); 317 | cmd_state = CMD_STATE_IDLE; 318 | break; 319 | case CMD_START: 320 | start(); 321 | cmd_state = CMD_STATE_IDLE; 322 | break; 323 | case CMD_GET_SAMPLERATE: 324 | Serial1.write((uint8_t*) &adc_samplerate, 4); 325 | // Serial1.println(adc_samplerate); 326 | cmd_state = CMD_STATE_IDLE; 327 | break; 328 | case CMD_GET_ERROR: 329 | Serial1.println(error); 330 | cmd_state = CMD_STATE_IDLE; 331 | break; 332 | case CMD_SET_TRIGGER: 333 | handle_trigger_cmd(); 334 | break; 335 | } 336 | } 337 | 338 | 339 | extern "C" 340 | { 341 | void __irq_usart1(void) 342 | { 343 | // Reading clears the thing 344 | while ((USART1_BASE->SR & USART_SR_RXNE) != 0) { 345 | switch (cmd_state) { 346 | case CMD_STATE_IDLE: 347 | cmd_buf_ix = 0; 348 | // fallthrough 349 | case CMD_STATE_READ_ARG: { 350 | cmd_buf[cmd_buf_ix] = USART1_BASE->DR & 0xff; 351 | if (cmd_buf[0] != 0) { 352 | handle_cmd(); 353 | cmd_buf_ix += 1; 354 | } 355 | break; 356 | } 357 | } 358 | } 359 | 360 | // This just ties in to existing Serial stuff so Serial1.print keeps working 361 | /* TXE signifies readiness to send a byte to DR. */ 362 | if ((USART1_BASE->CR1 & USART_CR1_TXEIE) && (USART1_BASE->SR & USART_SR_TXE)) 363 | { 364 | if (!rb_is_empty(USART1->wb)) 365 | USART1_BASE->DR = rb_remove(USART1->wb); 366 | else 367 | USART1_BASE->CR1 &= ~((uint32)USART_CR1_TXEIE); // disable TXEIE 368 | } 369 | } 370 | } 371 | 372 | 373 | void parameter_sanity_check() { 374 | uint32_t channels = nPins; 375 | 376 | // This is 10(adc_smpr + 12.5) 377 | uint32_t sampletime_cycles_x10 = 75 + 125; 378 | 379 | // Time it takes for adc to sample all channels, in PCLK cycles 380 | uint32_t sampletime = sampletime_cycles_x10 * adc_clock_divider * channels / 10; 381 | 382 | // Time between timer triggers 383 | uint32_t timer_interval = adc_timer_prescaler * adc_timer_arr; 384 | 385 | 386 | if (timer_interval <= sampletime) { 387 | error |= BAD_PARAMETERS_TIMER_FASTER_THAN_ADC; 388 | } 389 | 390 | uint32_t send_capacity_bytes_per_second = baud_rate / 10; // 10 bits per byte (2 for star/stop bits) 391 | uint32_t requested_bytes_per_second = 2 * channels * (CLOCK_SPEED_HZ / timer_interval); 392 | if (send_capacity_bytes_per_second <= requested_bytes_per_second) { 393 | error |= BAD_PARAMETERS_SERIAL_TOO_SLOW; 394 | } 395 | } 396 | 397 | void setup() 398 | { 399 | Serial1.begin(baud_rate); 400 | pinMode(error_pin, OUTPUT); 401 | parameter_sanity_check(); 402 | 403 | for (size_t i = 0; i < adc_buffer_len; i++) 404 | { 405 | adc_buffer[i] = 0; 406 | } 407 | 408 | // Prepare usart DMA 409 | USART1->regs->CR3 |= USART_CR3_DMAT; 410 | 411 | // Interrupt on incoming 412 | USART1->regs->CR1 |= USART_CR1_RXNEIE; 413 | 414 | dma_attach_interrupt(DMA1, DMA_CH4, on_dma_to_usart_done); 415 | 416 | setup_timer(); 417 | setup_adc(); 418 | 419 | stop(); // TODO no! 420 | } 421 | 422 | void loop() 423 | { 424 | if (error != NO_ERROR) 425 | { 426 | digitalWrite(error_pin, HIGH); 427 | } 428 | // Everything relies on interrupts, do nothing 429 | asm("wfi \n"); 430 | }; 431 | -------------------------------------------------------------------------------- /libsigrok.patch: -------------------------------------------------------------------------------- 1 | diff --git a/Makefile.am b/Makefile.am 2 | index 66d5f506..0d7049c0 100644 3 | --- a/Makefile.am 4 | +++ b/Makefile.am 5 | @@ -554,6 +554,12 @@ src_libdrivers_la_SOURCES += \ 6 | src/hardware/siglent-sds/protocol.c \ 7 | src/hardware/siglent-sds/api.c 8 | endif 9 | +if HW_STM32SCOPE 10 | +src_libdrivers_la_SOURCES += \ 11 | + src/hardware/stm32scope/protocol.h \ 12 | + src/hardware/stm32scope/protocol.c \ 13 | + src/hardware/stm32scope/api.c 14 | +endif 15 | if HW_SYSCLK_LWLA 16 | src_libdrivers_la_SOURCES += \ 17 | src/hardware/sysclk-lwla/lwla.h \ 18 | diff --git a/configure.ac b/configure.ac 19 | index 3ab83af9..a0294589 100644 20 | --- a/configure.ac 21 | +++ b/configure.ac 22 | @@ -305,6 +305,7 @@ SR_DRIVER([SCPI PPS], [scpi-pps]) 23 | SR_DRIVER([serial DMM], [serial-dmm], [serial_comm]) 24 | SR_DRIVER([serial LCR], [serial-lcr], [serial_comm]) 25 | SR_DRIVER([Siglent SDS], [siglent-sds]) 26 | +SR_DRIVER([stm32scope], [stm32scope]) 27 | SR_DRIVER([Sysclk LWLA], [sysclk-lwla], [libusb]) 28 | SR_DRIVER([Sysclk SLA5032], [sysclk-sla5032], [libusb]) 29 | SR_DRIVER([Teleinfo], [teleinfo], [serial_comm]) 30 | diff --git a/src/hardware/stm32scope/api.c b/src/hardware/stm32scope/api.c 31 | new file mode 100644 32 | index 00000000..cdb744ad 33 | --- /dev/null 34 | +++ b/src/hardware/stm32scope/api.c 35 | @@ -0,0 +1,243 @@ 36 | +/* 37 | + * This file is part of the libsigrok project. 38 | + * 39 | + * Copyright (C) 2019 Simon Schmidt 40 | + * 41 | + * This program is free software: you can redistribute it and/or modify 42 | + * it under the terms of the GNU General Public License as published by 43 | + * the Free Software Foundation, either version 3 of the License, or 44 | + * (at your option) any later version. 45 | + * 46 | + * This program is distributed in the hope that it will be useful, 47 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of 48 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 49 | + * GNU General Public License for more details. 50 | + * 51 | + * You should have received a copy of the GNU General Public License 52 | + * along with this program. If not, see . 53 | + */ 54 | + 55 | +#include 56 | +#include "protocol.h" 57 | + 58 | +static struct sr_dev_driver stm32scope_driver_info; 59 | + 60 | +static const uint32_t scanopts[] = { 61 | + SR_CONF_CONN, 62 | + SR_CONF_SERIALCOMM, 63 | +}; 64 | + 65 | +static const uint32_t drvopts[] = { 66 | + SR_CONF_OSCILLOSCOPE, 67 | +}; 68 | + 69 | +static const uint32_t devopts[] = { 70 | + SR_CONF_CONTINUOUS, 71 | + SR_CONF_LIMIT_SAMPLES | SR_CONF_GET | SR_CONF_SET, 72 | + SR_CONF_SAMPLERATE | SR_CONF_GET, 73 | +}; 74 | + 75 | +static GSList *scan(struct sr_dev_driver *di, GSList *options) 76 | +{ 77 | + struct dev_context *devc; 78 | + struct sr_serial_dev_inst *serial; 79 | + struct sr_dev_inst *sdi; 80 | + struct sr_config *src; 81 | + GSList *devices, *l; 82 | + const char *conn, *serialcomm; 83 | + long samplerate; 84 | + 85 | + devices = NULL; 86 | + conn = serialcomm = NULL; 87 | + for (l = options; l; l = l->next) 88 | + { 89 | + src = l->data; 90 | + switch (src->key) 91 | + { 92 | + case SR_CONF_CONN: 93 | + conn = g_variant_get_string(src->data, NULL); 94 | + break; 95 | + case SR_CONF_SERIALCOMM: 96 | + serialcomm = g_variant_get_string(src->data, NULL); 97 | + break; 98 | + } 99 | + } 100 | + if (!conn) 101 | + { 102 | + sr_err("stm32scope no SR_CONF_CONN provided, using default"); 103 | + conn = STM32SCOPE_DEFAULT_CONN; 104 | + } 105 | + if (!serialcomm) 106 | + serialcomm = STM32SCOPE_DEFAULT_SERIALCOMM; 107 | + 108 | + serial = sr_serial_dev_inst_new(conn, serialcomm); 109 | + 110 | + if (serial_open(serial, SERIAL_RDWR) != SR_OK) 111 | + return NULL; 112 | + 113 | + if (stm32scope_getsamplerate(serial, &samplerate) != SR_OK) 114 | + goto scan_cleanup; 115 | + 116 | + sdi = g_malloc0(sizeof(struct sr_dev_inst)); 117 | + sdi->status = SR_ST_INACTIVE; 118 | + sdi->vendor = g_strdup("STM32Scope"); 119 | + sdi->model = g_strdup("v1.0"); 120 | + devc = g_malloc0(sizeof(struct dev_context)); 121 | + devc->state = STM32SCOPE_STOPPED; 122 | + devc->samplerate = (uint64_t)samplerate; 123 | + sdi->inst_type = SR_INST_SERIAL; 124 | + sdi->conn = serial; 125 | + sdi->priv = devc; 126 | + 127 | + sr_channel_new(sdi, 0, SR_CHANNEL_ANALOG, TRUE, "A0"); 128 | + sr_channel_new(sdi, 0, SR_CHANNEL_ANALOG, TRUE, "A1"); 129 | + 130 | + devices = g_slist_append(devices, sdi); 131 | + 132 | +scan_cleanup: 133 | + serial_close(serial); 134 | + 135 | + return std_scan_complete(di, devices); 136 | +} 137 | + 138 | +static int config_get(uint32_t key, GVariant **data, 139 | + const struct sr_dev_inst *sdi, const struct sr_channel_group *cg) 140 | +{ 141 | + struct dev_context *devc = sdi->priv; 142 | + 143 | + (void)cg; 144 | + 145 | + switch (key) 146 | + { 147 | + case SR_CONF_LIMIT_SAMPLES: 148 | + return sr_sw_limits_config_get(&devc->limits, key, data); 149 | + case SR_CONF_SAMPLERATE: 150 | + *data = g_variant_new_uint64(devc->samplerate); 151 | + break; 152 | + default: 153 | + return SR_ERR_NA; 154 | + } 155 | + 156 | + return SR_OK; 157 | +} 158 | + 159 | +static int config_set(uint32_t key, GVariant *data, 160 | + const struct sr_dev_inst *sdi, const struct sr_channel_group *cg) 161 | +{ 162 | + struct dev_context *devc; 163 | + 164 | + (void)cg; 165 | + 166 | + devc = sdi->priv; 167 | + 168 | + switch (key) 169 | + { 170 | + case SR_CONF_LIMIT_SAMPLES: 171 | + return sr_sw_limits_config_set(&devc->limits, key, data); 172 | + default: 173 | + return SR_ERR_NA; 174 | + } 175 | + 176 | + return SR_OK; 177 | +} 178 | + 179 | +static int config_list(uint32_t key, GVariant **data, 180 | + const struct sr_dev_inst *sdi, const struct sr_channel_group *cg) 181 | +{ 182 | + switch (key) 183 | + { 184 | + case SR_CONF_SCAN_OPTIONS: 185 | + case SR_CONF_DEVICE_OPTIONS: 186 | + return STD_CONFIG_LIST(key, data, sdi, cg, scanopts, drvopts, devopts); 187 | + // case SR_CONF_DATA_SOURCE: 188 | + // *data = g_variant_new_strv(ARRAY_AND_SIZE(data_sources)); 189 | + // break; 190 | + default: 191 | + return SR_ERR_NA; 192 | + } 193 | + 194 | + return SR_OK; 195 | +} 196 | + 197 | +static int dev_acquisition_start(const struct sr_dev_inst *sdi) 198 | +{ 199 | + struct sr_serial_dev_inst *serial; 200 | + struct dev_context *devc; 201 | + 202 | + serial = sdi->conn; 203 | + devc = sdi->priv; 204 | + 205 | + sr_sw_limits_acquisition_start(&devc->limits); 206 | + 207 | + std_session_send_df_header(sdi); 208 | + 209 | + serial_source_add( 210 | + sdi->session, 211 | + serial, 212 | + G_IO_IN, 213 | + 50, 214 | + stm32scope_receive_data, 215 | + (void *)sdi); 216 | + 217 | + if (stm32scope_start(sdi) != SR_OK) 218 | + { 219 | + return SR_ERR; 220 | + } 221 | + return SR_OK; 222 | +} 223 | + 224 | +static int dev_acquisition_stop(struct sr_dev_inst *sdi) 225 | +{ 226 | + struct sr_serial_dev_inst *serial; 227 | + const char *prefix; 228 | + int ret; 229 | + 230 | + if (!sdi) 231 | + { 232 | + sr_err("%s: Invalid argument.", __func__); 233 | + return SR_ERR_ARG; 234 | + } 235 | + 236 | + serial = sdi->conn; 237 | + prefix = sdi->driver->name; 238 | + 239 | + if ((ret = serial_source_remove(sdi->session, serial)) < 0) 240 | + { 241 | + sr_err("%s: Failed to remove source: %d.", prefix, ret); 242 | + return ret; 243 | + } 244 | + 245 | + if (stm32scope_stop(sdi) != SR_OK) 246 | + { 247 | + return SR_ERR; 248 | + }; 249 | + 250 | + // For some reason this causes error on second capture 251 | + // TODO figure out why, and why it's the default 252 | + // if ((ret = sr_dev_close(sdi)) < 0) { 253 | + // sr_err("%s: Failed to close device: %d.", prefix, ret); 254 | + // return ret; 255 | + // } 256 | + 257 | + return std_session_send_df_end(sdi); 258 | +} 259 | + 260 | +static struct sr_dev_driver stm32scope_driver_info = { 261 | + .name = "stm32scope", 262 | + .longname = "stm32scope", 263 | + .api_version = 1, 264 | + .init = std_init, 265 | + .cleanup = std_cleanup, 266 | + .scan = scan, 267 | + .dev_list = std_dev_list, 268 | + .dev_clear = std_dev_clear, 269 | + .config_get = config_get, 270 | + .config_set = config_set, 271 | + .config_list = config_list, 272 | + .dev_open = std_serial_dev_open, 273 | + .dev_close = std_serial_dev_close, 274 | + .dev_acquisition_start = dev_acquisition_start, 275 | + .dev_acquisition_stop = dev_acquisition_stop, 276 | + .context = NULL, 277 | +}; 278 | +SR_REGISTER_DEV_DRIVER(stm32scope_driver_info); 279 | diff --git a/src/hardware/stm32scope/protocol.c b/src/hardware/stm32scope/protocol.c 280 | new file mode 100644 281 | index 00000000..9ddf85f7 282 | --- /dev/null 283 | +++ b/src/hardware/stm32scope/protocol.c 284 | @@ -0,0 +1,228 @@ 285 | +/* 286 | + * This file is part of the libsigrok project. 287 | + * 288 | + * Copyright (C) 2019 Simon Schmidt 289 | + * 290 | + * This program is free software: you can redistribute it and/or modify 291 | + * it under the terms of the GNU General Public License as published by 292 | + * the Free Software Foundation, either version 3 of the License, or 293 | + * (at your option) any later version. 294 | + * 295 | + * This program is distributed in the hope that it will be useful, 296 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of 297 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 298 | + * GNU General Public License for more details. 299 | + * 300 | + * You should have received a copy of the GNU General Public License 301 | + * along with this program. If not, see . 302 | + */ 303 | + 304 | +#include 305 | +#include "protocol.h" 306 | +#include "libsigrok-internal.h" 307 | + 308 | +// TODO in device conf 309 | +const guint8 NUM_CHANNELS = 2; 310 | +const float VOLTAGE_REFERENCE = 3.3; 311 | + 312 | +const uint8_t N_CHANNELS = 2; 313 | + 314 | +static void stm32scope_live_data(struct sr_dev_inst *sdi, const uint8_t *buf) 315 | +{ 316 | + struct dev_context *devc; 317 | + struct sr_datafeed_packet packet; 318 | + struct sr_datafeed_analog analog; 319 | + struct sr_analog_encoding encoding; 320 | + struct sr_analog_meaning meaning; 321 | + struct sr_analog_spec spec; 322 | + struct sr_channel *ch; 323 | + uint16_t raw_value; 324 | + float value; 325 | + int i, digits; 326 | + 327 | + // TODO? yes no? 328 | + digits = 4; 329 | + devc = sdi->priv; 330 | + 331 | + // TODO 332 | + // if (devc->data_source != DATA_SOURCE_LIVE) 333 | + // return; 334 | + 335 | + for (i = 0; i < NUM_CHANNELS; i++) 336 | + { 337 | + ch = g_slist_nth_data(sdi->channels, i); 338 | + if (!ch->enabled) 339 | + continue; 340 | + 341 | + raw_value = buf[2 * i + 1] << 8 | buf[2 * i]; 342 | + value = VOLTAGE_REFERENCE * (0.0 + raw_value) / ((1 << 12) + 0.0); 343 | + 344 | + sr_analog_init(&analog, &encoding, &meaning, &spec, digits); 345 | + analog.num_samples = 1; 346 | + analog.data = &value; 347 | + analog.meaning->mq = SR_MQ_VOLTAGE; 348 | + analog.meaning->unit = SR_UNIT_VOLT; 349 | + // TODO correct flags? 350 | + analog.meaning->mqflags = SR_MQFLAG_DC; 351 | + analog.meaning->channels = g_slist_append(NULL, ch); 352 | + 353 | + packet.type = SR_DF_ANALOG; 354 | + packet.payload = &analog; 355 | + sr_session_send(sdi, &packet); 356 | + g_slist_free(analog.meaning->channels); 357 | + } 358 | + 359 | + sr_sw_limits_update_samples_read(&devc->limits, 1); 360 | +} 361 | + 362 | +static const uint8_t *stm32scope_parse_data(struct sr_dev_inst *sdi, 363 | + const uint8_t *buf, int len) 364 | +{ 365 | + if (len < 2 * NUM_CHANNELS) 366 | + /* 2 bytes per channel, need more data. */ 367 | + return NULL; 368 | + 369 | + stm32scope_live_data(sdi, buf); 370 | + return buf + 2 * NUM_CHANNELS; 371 | +} 372 | + 373 | +static int stm32scope_send_command(struct sr_serial_dev_inst *serial, int cmd) 374 | +{ 375 | + sr_info("stm32scope: Sending cmd=%d", cmd); 376 | + if (serial_write_blocking(serial, &cmd, 1, 10) < 0) 377 | + { 378 | + sr_err("Unable to send command, serial error"); 379 | + return SR_ERR; 380 | + } 381 | + return SR_OK; 382 | +} 383 | + 384 | +SR_PRIV int stm32scope_start(const struct sr_dev_inst *sdi) 385 | +{ 386 | + struct sr_serial_dev_inst *serial; 387 | + struct dev_context *devc; 388 | + int ret; 389 | + devc = sdi->priv; 390 | + serial = sdi->conn; 391 | + 392 | + sr_info("stm32scope: Starting"); 393 | + ret = stm32scope_send_command(serial, STM32SCOPE_CMD_START) != SR_OK; 394 | + if (ret != 0) 395 | + { 396 | + return ret; 397 | + }; 398 | + 399 | + devc->state = STM32SCOPE_RUNNING; 400 | + return SR_OK; 401 | +} 402 | + 403 | +SR_PRIV int stm32scope_stop(const struct sr_dev_inst *sdi) 404 | +{ 405 | + struct sr_serial_dev_inst *serial; 406 | + struct dev_context *devc; 407 | + int ret; 408 | + 409 | + devc = sdi->priv; 410 | + serial = sdi->conn; 411 | + 412 | + sr_info("stm32scope: Stopping"); 413 | + ret = stm32scope_send_command(serial, STM32SCOPE_CMD_STOP) != SR_OK; 414 | + if (ret != SR_OK) 415 | + { 416 | + return ret; 417 | + }; 418 | + devc->state = STM32SCOPE_STOPPED; 419 | + return SR_OK; 420 | +} 421 | + 422 | +SR_PRIV int stm32scope_getsamplerate( 423 | + struct sr_serial_dev_inst *serial, 424 | + long *samplerate) 425 | +{ 426 | + char *buf; 427 | + int len; 428 | + int ret; 429 | + 430 | + sr_info("stm32scope: get sample rate"); 431 | + 432 | + ret = stm32scope_send_command(serial, STM32SCOPE_CMD_GETSAMPLERATE) != SR_OK; 433 | + if (ret != SR_OK) 434 | + { 435 | + return ret; 436 | + }; 437 | + 438 | + // Read the value back 439 | + len = 10; 440 | + buf = g_malloc0(len); 441 | + ret = serial_readline(serial, &buf, &len, 10); 442 | + if (ret < 0) 443 | + { 444 | + sr_err("Serial error on getsample response"); 445 | + return ret; 446 | + } 447 | + sr_info("serial ret=%d", ret); 448 | + ret = sr_atol(buf, samplerate); 449 | + if (ret != SR_OK) 450 | + { 451 | + sr_err("Error parsing return value for getsamplerate"); 452 | + return ret; 453 | + } 454 | + sr_info("stm32scope: Got samplerate %d", *samplerate); 455 | + return SR_OK; 456 | +} 457 | + 458 | +SR_PRIV int stm32scope_receive_data(int fd, int revents, void *cb_data) 459 | +{ 460 | + struct sr_dev_inst *sdi; 461 | + struct dev_context *devc; 462 | + struct sr_serial_dev_inst *serial; 463 | + const uint8_t *ptr, *next_ptr, *end_ptr; 464 | + int len; 465 | + 466 | + (void)fd; 467 | + 468 | + if (!(sdi = cb_data) || !(devc = sdi->priv) || revents != G_IO_IN) 469 | + return TRUE; 470 | + 471 | + if (devc->state != STM32SCOPE_RUNNING) 472 | + { 473 | + sr_err("stm32scope not running"); 474 | + } 475 | + 476 | + serial = sdi->conn; 477 | + 478 | + /* Try to get as much data as the buffer can hold. */ 479 | + len = sizeof(devc->buf) - devc->buf_len; 480 | + len = serial_read_nonblocking(serial, devc->buf + devc->buf_len, len); 481 | + if (len < 1) 482 | + { 483 | + sr_err("Serial port read error: %d.", len); 484 | + return FALSE; 485 | + } 486 | + devc->buf_len += len; 487 | + 488 | + /* Now look for packets in that data. */ 489 | + ptr = devc->buf; 490 | + end_ptr = ptr + devc->buf_len; 491 | + while ((next_ptr = stm32scope_parse_data(sdi, ptr, end_ptr - ptr))) 492 | + ptr = next_ptr; 493 | + 494 | + /* If we have any data left, move it to the beginning of our buffer. */ 495 | + memmove(devc->buf, ptr, end_ptr - ptr); 496 | + devc->buf_len -= ptr - devc->buf; 497 | + 498 | + /* If buffer is full and no valid packet was found, wipe buffer. */ 499 | + if (devc->buf_len >= sizeof(devc->buf)) 500 | + { 501 | + devc->buf_len = 0; 502 | + return FALSE; 503 | + } 504 | + 505 | + if (sr_sw_limits_check(&devc->limits)) 506 | + { 507 | + sr_dev_acquisition_stop(sdi); 508 | + return TRUE; 509 | + } 510 | + 511 | + return TRUE; 512 | +} 513 | diff --git a/src/hardware/stm32scope/protocol.h b/src/hardware/stm32scope/protocol.h 514 | new file mode 100644 515 | index 00000000..6f2c38e2 516 | --- /dev/null 517 | +++ b/src/hardware/stm32scope/protocol.h 518 | @@ -0,0 +1,63 @@ 519 | +/* 520 | + * This file is part of the libsigrok project. 521 | + * 522 | + * Copyright (C) 2019 Simon Schmidt 523 | + * 524 | + * This program is free software: you can redistribute it and/or modify 525 | + * it under the terms of the GNU General Public License as published by 526 | + * the Free Software Foundation, either version 3 of the License, or 527 | + * (at your option) any later version. 528 | + * 529 | + * This program is distributed in the hope that it will be useful, 530 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of 531 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 532 | + * GNU General Public License for more details. 533 | + * 534 | + * You should have received a copy of the GNU General Public License 535 | + * along with this program. If not, see . 536 | + */ 537 | + 538 | +#ifndef LIBSIGROK_HARDWARE_STM32SCOPE_PROTOCOL_H 539 | +#define LIBSIGROK_HARDWARE_STM32SCOPE_PROTOCOL_H 540 | + 541 | +#include 542 | +#include 543 | +#include 544 | +#include "libsigrok-internal.h" 545 | + 546 | +#define LOG_PREFIX "stm32scope" 547 | +#define STM32SCOPE_BUF_SIZE (1024) 548 | + 549 | +#define STM32SCOPE_DEFAULT_SERIALCOMM "1200000/8n1"; 550 | +#define STM32SCOPE_DEFAULT_CONN "/dev/ttyUSB0"; 551 | +#define STM32SCOPE_BAUD_RATE 1200000; 552 | + 553 | +enum stm32scope_state 554 | +{ 555 | + STM32SCOPE_STOPPED, 556 | + STM32SCOPE_RUNNING, 557 | +}; 558 | + 559 | +enum stm32scope_command 560 | +{ 561 | + STM32SCOPE_CMD_STOP = 1, 562 | + STM32SCOPE_CMD_START = 2, 563 | + STM32SCOPE_CMD_GETSAMPLERATE = 3, 564 | +}; 565 | + 566 | +struct dev_context 567 | +{ 568 | + struct sr_sw_limits limits; 569 | + enum stm32scope_state state; 570 | + uint64_t samplerate; 571 | + uint8_t buf[STM32SCOPE_BUF_SIZE]; 572 | + unsigned int buf_len; 573 | +}; 574 | + 575 | +SR_PRIV int stm32scope_start(const struct sr_dev_inst *sdi); 576 | +SR_PRIV int stm32scope_stop(const struct sr_dev_inst *sdi); 577 | +SR_PRIV int stm32scope_receive_data(int fd, int revents, void *cb_data); 578 | +SR_PRIV int stm32scope_getsamplerate( 579 | + struct sr_serial_dev_inst *serial, 580 | + long *samplerate); 581 | +#endif 582 | --------------------------------------------------------------------------------