├── DLO-138.ino ├── README.md ├── binaries ├── DLO-138_encoder_1.0.bin └── DLO-138_switches_1.0.bin ├── capture.ino ├── control.ino ├── display.ino ├── encoder.ino ├── global.h ├── interface.ino ├── io.ino ├── pics ├── HardwareMod.png ├── pic1.png ├── pic2.png ├── pic3.png └── pic4.png ├── src └── TFTLib │ ├── Adafruit_TFTLCD_8bit_STM32.cpp │ ├── Adafruit_TFTLCD_8bit_STM32.h │ ├── Readme.md │ ├── hx8347g.cpp │ ├── hx8347g.h │ ├── hx8357x.cpp │ ├── hx8357x.h │ ├── ili932x.cpp │ ├── ili932x.h │ ├── ili9341.cpp │ └── ili9341.h ├── variables.h └── zconfig.ino /DLO-138.ino: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | // needs to be Adafruit GFX Library v1.1.4, check/change your installed version 5 | // otherwise you will get a black screen or compiler errors 6 | 7 | #include "src/TFTLib/Adafruit_TFTLCD_8bit_STM32.h" 8 | #include "global.h" 9 | #include "variables.h" 10 | 11 | 12 | #define FIRMWARE_VERSION "1.0" 13 | 14 | // ------------------------ 15 | void setup() { 16 | // ------------------------ 17 | 18 | afio_cfg_debug_ports(AFIO_DEBUG_NONE); //added to disable the debug port. My stock DSO-138 won't allow the screen to work without this 19 | // see http://www.stm32duino.com/viewtopic.php?t=1130#p13919 for more info 20 | 21 | 22 | DBG_INIT(SERIAL_BAUD_RATE); 23 | DBG_PRINT("Dual channel O Scope with two logic channels, ver: "); 24 | DBG_PRINTLN(FIRMWARE_VERSION); 25 | 26 | // set digital and analog stuff 27 | initIO(); 28 | 29 | // load scope config or factory reset to defaults 30 | loadConfig(digitalRead(BTN4) == LOW); 31 | 32 | // init the IL9341 display 33 | initDisplay(); 34 | } 35 | 36 | 37 | 38 | // ------------------------ 39 | void loop() { 40 | // ------------------------ 41 | controlLoop(); 42 | } 43 | 44 | 45 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DLO-138 2 | An open source firmware for DSO-138 Oscilloscope. 3 | ![Photo](https://github.com/ardyesp/DLO-138/blob/master/pics/pic4.png) 4 | 5 | DSO-138 is an excellent piece of hardware based on ARM Cortex M3 core STM32F103 processor and sufficient for most beginner users. The stock firmware, while quite responsive, can use a few improvements. The main shortcoming which prompted the development of DLO-138 firmware is the inability to get waveform data into a computer for further analysis and the lack of a second channel. Engineers troubleshooting hardware issues need to mark reference points on waveform so having another analog or digital channel can greatly improve analysis. This firmware hopes to improve on these issues. 6 | 7 | ## Features 8 | - Two analog channels 9 | - Two digital logic channels (SWDIO and SWDIO pins (PA13 and PA14) on board) 10 | - Serial port interface for captured waveform data 11 | - Trigger source selectable from Analog Channel 1 or Digital Channel 12 | - Option to use rotary encoder instead of + - and SEL switches 13 | - 2K sample depth 14 | 15 | This firmware can be used on stock DSO-138 hardware as well. Select one of the pre-compiled binaries to suit the board. Follow the firmware upgrade instructions for DSO-138. At any time, you can reflash DSO-138 with JYE Tech provided firmware. 16 | 17 | # Cost 18 | Extra features come at an additional cost. In the case of DLO-138, it is the loss of lowest timebase. Maximum sampling rate in DLO-138 is 20 µs/div instead of 10 µs/div. In the 20 µs/div range, firmware under-samples ADC channels, often reading same data twice. To use the second analog channel, analog front end has to be duplicated on a daughter board. On a stock hardware, this firmware can be used to provide two digital logic channels. 19 | 20 | # Build 21 | The build environment uses Arduino. For help with setting up IDE visit http://www.stm32duino.com 22 | 23 | For graphics output, this project depends on the Adafruit GFX Library v1.1.4. 24 | Install it via the Library Manager of your Arduino IDE. 25 | Other/newer versions of this library are likely to not compile or will give you a black screen. 26 | 27 | # Hardware 28 | Following changes can be applied selectively, to get maximum functionality from board. The firmware can be run on unmodified hardware as well. 29 | ![Mod Schematic](https://github.com/ardyesp/DLO-138/blob/master/pics/HardwareMod.png) 30 | 31 | # Usage: 32 | Push button in encoder (SEL if using switches) moves focus to next parameter 33 | Left/Right turn in encoder (+/- if using switches) changes the parameter which is in focus 34 | Short press OK to HOLD the waveform and output it on serial port 35 | Long press OK button: 36 | 37 | Focus Action 38 | Trigger Level Zero the trigger level to Analog channel 1 39 | Wave X scrollbar Center waveform on screen (at trigger point) 40 | Wave Y cursor Zero the cursor. If Analog CH1 coupling is GND, waveform reference base is set 41 | Other Toggle on screen Analog CH1 statistics display 42 | 43 | Press and hold OK button at power up to reset settings to default 44 | 45 | # Flash binaries directly via serial interface 46 | 47 | When using Windows you can follow the guide from jyetech: 48 | https://jyetech.com/wp-content/uploads/2018/07/dso138-firmware-upgrade.pdf 49 | 50 | The guide uses the graphical programming tool provided by ST: 51 | https://www.st.com/en/development-tools/flasher-stm32.html 52 | 53 | --- 54 | 55 | When using Linux, you can use the open source command line tool stm32flash: 56 | 57 | Install stm32flash: 58 | ``` 59 | sudo apt-get install stm32flash 60 | ``` 61 | 62 | Connect your TTL-UART-to-USB converter to the DSO138 and bridge jumpers J1 and J2 on the back of the PCB just like in the above manual. 63 | 64 | Unlock the flash of the STM32: 65 | ``` 66 | sudo stm32flash /dev/ttyUSB0 -k -b 115200 67 | 68 | sudo stm32flash /dev/ttyUSB0 -u -b 115200 69 | ``` 70 | 71 | Flash new firmware: 72 | ``` 73 | sudo stm32flash /dev/ttyUSB0 -w binaries/DLO-138_switches_1.0.bin -b 115200 74 | ``` 75 | 76 | Remove the solder bridges on J1 and J2 and enjoy the alternative firmware on your DSO138. 77 | 78 | 79 | # References 80 | DSO-138 - http://www.jyetech.com/Products/LcdScope/e138.php 81 | 82 | STM32Duino - http://www.stm32duino.com 83 | 84 | STM32F103 - http://www.st.com/en/microcontrollers/stm32f103.html 85 | 86 | Adafruint Graphics Library - https://github.com/adafruit/Adafruit-GFX-Library 87 | 88 | Parallel 8 bit ILI9341 library - https://github.com/stevstrong/Adafruit_TFTLCD_8bit_STM32 89 | 90 | 91 | -------------------------------------------------------------------------------- /binaries/DLO-138_encoder_1.0.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ardyesp/DLO-138/2989944fad5d5d9ca7170f02ec004d1d93d23a26/binaries/DLO-138_encoder_1.0.bin -------------------------------------------------------------------------------- /binaries/DLO-138_switches_1.0.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ardyesp/DLO-138/2989944fad5d5d9ca7170f02ec004d1d93d23a26/binaries/DLO-138_switches_1.0.bin -------------------------------------------------------------------------------- /capture.ino: -------------------------------------------------------------------------------- 1 | 2 | // sampling delay table in quarter-microseconds 3 | const int16_t samplingDelay[] = {-1, 0, 14, 38, 86, 229, 468, 948, 2385, 4776, 9570, 23940}; 4 | const uint16_t timeoutDelayMs[] = {50, 50, 50, 100, 100, 100, 150, 250, 500, 1000, 2000, 4500}; 5 | 6 | int16_t sDly, tDly; 7 | boolean minSamplesAcquired; 8 | boolean triggerRising; 9 | long prevTime = 0; 10 | 11 | // hold pointer references for updating variables in memory 12 | uint16_t *sIndexPtr = &sIndex; 13 | volatile boolean *keepSamplingPtr = &keepSampling; 14 | volatile boolean *triggeredPtr = &triggered; 15 | 16 | 17 | 18 | // ------------------------ 19 | void setSamplingRate(uint8_t timeBase) { 20 | // ------------------------ 21 | sDly = samplingDelay[timeBase]; 22 | tDly = timeoutDelayMs[timeBase]; 23 | // sampling rate changed, break out from previous sampling loop 24 | keepSampling = false; 25 | // disable scan timeout timer 26 | Timer2.pause(); 27 | } 28 | 29 | 30 | 31 | // ------------------------ 32 | void setTriggerRising(boolean rising) { 33 | // ------------------------ 34 | // trigger changed, break out from previous sampling loop 35 | keepSampling = false; 36 | triggerRising = rising; 37 | // attach interrupt to trigger pin 38 | detachInterrupt(TRIGGER_IN); 39 | if(rising) 40 | attachInterrupt(TRIGGER_IN, triggerISR, RISING); 41 | else 42 | attachInterrupt(TRIGGER_IN, triggerISR, FALLING); 43 | } 44 | 45 | 46 | 47 | // ------------------------ 48 | void sampleWaves(boolean wTimeout) { 49 | // ------------------------ 50 | if(wTimeout) 51 | // setup timed interrupt to terminate scanning if trigger not found 52 | startScanTimeout(tDly); 53 | 54 | // start sampling loop - until timeout or trigger 55 | startSampling(sDly); 56 | // disable scan timeout timer 57 | Timer2.pause(); 58 | } 59 | 60 | 61 | 62 | // local operations below 63 | 64 | 65 | 66 | // ------------------------ 67 | void startScanTimeout(int16_t mSec) { 68 | // ------------------------ 69 | // interrupt triggers at 1 70 | Timer2.setCount(2); 71 | Timer2.setPeriod(mSec * 1000); 72 | Timer2.resume(); 73 | } 74 | 75 | 76 | 77 | 78 | 79 | /* 80 | Custom handler for Trigger line interrupt (EXTI8). This avoids the 81 | overhead of demux'ing interrupt line and calling handler, and is faster. 82 | Arduino Core - exti.c has to be modified. Comment out the function __irq_exti9_5 83 | defined there, or add __weak qualifier. 84 | See: 85 | http://www.stm32duino.com/viewtopic.php?f=3&t=1816 86 | https://github.com/leaflabs/libmaple/blob/master/notes/interrupts.txt 87 | 88 | // ------------------------ 89 | extern "C" void __irq_exti9_5(void) { 90 | // ------------------------ 91 | // custom interrupt handler for exti 5 to 9 92 | // since we have only one interrupt always assume it is Trigger -> exti 8 93 | 94 | asm volatile( 95 | " cbnz %[triggered], fin_trig \n\t" // if(!triggered) 96 | " cbnz %[minSamples], validCond \n\t" // if(!minSamplesAcquired) 97 | " cmp %[sIndex], %[halfSamples] \n\t" // if(sIndex < NUM_SAMPLES/2) 98 | " bcc fin_trig \n\t" 99 | 100 | "validCond: \n\t" 101 | " mov %[tIndex], %[sIndex] \n\t" // tIndex = sIndex; 102 | " mov %[triggered], #1 \n\t" // triggered = true; 103 | 104 | "fin_trig: \n\t" 105 | " ldr r1, =0x40010400 \n\t" // load EXTI base address 106 | " mov r0, #0x03E0 \n\t" // clear all 5-9 interrupts 107 | " str r0, [r1, #20] \n\t" // into EXTI_PR 108 | " nop \n\t" 109 | " nop \n\t" 110 | 111 | : [triggered] "+r" (triggered), [tIndex] "+r" (tIndex) 112 | : [sIndex] "r" (sIndex), [minSamples] "r" (minSamplesAcquired), [halfSamples] "I" (NUM_SAMPLES/2) 113 | : "r0", "r1", "cc" 114 | ); 115 | } 116 | */ 117 | 118 | 119 | // ------------------------ 120 | void triggerISR(void) { 121 | // ------------------------ 122 | if(!triggered) { 123 | // skip this trigger if min samples not acquired 124 | if(!minSamplesAcquired && (sIndex < NUM_SAMPLES/2)) 125 | return; 126 | 127 | // snap the position where trigger occurred 128 | tIndex = sIndex; 129 | // avoid multiple triggering 130 | triggered = true; 131 | } 132 | } 133 | 134 | 135 | 136 | 137 | // ------------------------ 138 | void scanTimeoutISR(void) { 139 | // ------------------------ 140 | keepSampling = false; 141 | // disable scan timeout timer 142 | Timer2.pause(); 143 | } 144 | 145 | 146 | 147 | 148 | // ------------------------ 149 | void startSampling(int16_t lDelay) { 150 | // ------------------------ 151 | keepSampling = true; 152 | minSamplesAcquired = false; 153 | uint16_t lCtr = 0; 154 | 155 | // clear old dataset 156 | samplingTime = 0; 157 | triggered = false; 158 | sIndex = 0; 159 | 160 | prevTime = micros(); 161 | 162 | if(lDelay < 0) { 163 | 164 | asm volatile( 165 | " ldrh r9, [%[sIndex]] \n\t" // load sIndex value 166 | 167 | "top_1: \n\t" 168 | " ldrb r0, [%[keepSampling]] \n\t" // while(keepSampling) 169 | " cbz r0, finished_1 \n\t" 170 | 171 | " ldr r1, =0x40012400 \n\t" // load ADC1 base address, ADC2 = +0x400 172 | 173 | " ldr r0, [r1, #0x4C] \n\t" // get and save ADC1 DR 174 | " strh r0, [%[ch1], r9, lsl #1] \n\t" 175 | " ldr r0, [r1, #0x44C] \n\t" // get and save ADC2 DR 176 | " strh r0, [%[ch2], r9, lsl #1] \n\t" 177 | 178 | " ldr r1, =0x40010800 \n\t" // load GPIOA address 179 | " ldr r0, [r1, #0x08] \n\t" // get and save GPIOA IDR 180 | " strh r0, [%[dCH], r9, lsl #1] \n\t" 181 | 182 | " adds r9, #1 \n\t" // increment sIndex 183 | " cmp r9, %[nSamp] \n\t" // if(sIndex == NUM_SAMPLES) 184 | " bne notOverflowed_1 \n\t" 185 | " mov r9, #0 \n\t" // sIndex = 0; 186 | 187 | " stmfd sp!,{r9, %[keepSampling], %[sIndex], %[triggered], %[ch1], %[ch2], %[dCH], %[lCtr]} \n\t" 188 | " bl %[snapMicros] \n\t" // micros() - r0 contains the 32bit result 189 | " ldmfd sp!,{r9, %[keepSampling], %[sIndex], %[triggered], %[ch1], %[ch2], %[dCH], %[lCtr]} \n\t" 190 | 191 | "notOverflowed_1: \n\t" 192 | " strh r9, [%[sIndex]] \n\t" // save sIndex 193 | 194 | " ldrb r0, [%[triggered]] \n\t" // if(triggered) 195 | " cbz r0, notTriggered_1 \n\t" 196 | 197 | " adds %[lCtr], #1 \n\t" // lCtr++ 198 | " cmp %[lCtr], %[halfSamples] \n\t" // if(lCtr == NUM_SAMPLES/2) 199 | " beq finished_1 \n\t" 200 | 201 | "notTriggered_1: \n\t" 202 | " b top_1 \n\t" 203 | "finished_1: \n\t" 204 | 205 | : 206 | : [keepSampling] "r" (keepSamplingPtr), [sIndex] "r" (sIndexPtr), [triggered] "r" (triggeredPtr), 207 | [ch1] "r" (ch1Capture), [ch2] "r" (ch2Capture), [dCH] "r" (bitStore), [lCtr] "r" (lCtr), 208 | [nSamp] "I" (NUM_SAMPLES), [halfSamples] "I" (NUM_SAMPLES/2), 209 | [snapMicros] "i" (snapMicros) 210 | : "r0", "r1", "r9", "memory", "cc" 211 | ); 212 | 213 | } 214 | else if(lDelay == 0) { 215 | 216 | asm volatile( 217 | " ldrh r9, [%[sIndex]] \n\t" // load sIndex value 218 | 219 | "top_2: \n\t" 220 | " ldrb r0, [%[keepSampling]] \n\t" // while(keepSampling) 221 | " cbz r0, finished_2 \n\t" 222 | 223 | " ldr r1, =0x40012400 \n\t" // load ADC1 base address, ADC2 = +0x400 224 | 225 | "waitADC1_2: \n\t" 226 | " ldr r0, [r1, #0] \n\t" // ADC1 SR 227 | " lsls r0, r0, #30 \n\t" // get to EOC bit 228 | " bpl waitADC1_2 \n\t" 229 | 230 | "waitADC2_2: \n\t" 231 | " ldr r0, [r1, #0x400] \n\t" // ADC2 SR 232 | " lsls r0, r0, #30 \n\t" // get to EOC bit 233 | " bpl waitADC2_2 \n\t" 234 | 235 | " ldr r0, [r1, #0x4C] \n\t" // get and save ADC1 DR 236 | " strh r0, [%[ch1], r9, lsl #1] \n\t" 237 | " ldr r0, [r1, #0x44C] \n\t" // get and save ADC2 DR 238 | " strh r0, [%[ch2], r9, lsl #1] \n\t" 239 | 240 | " ldr r1, =0x40010800 \n\t" // load GPIOA address 241 | " ldr r0, [r1, #0x08] \n\t" // get and save GPIOA IDR 242 | " strh r0, [%[dCH], r9, lsl #1] \n\t" 243 | 244 | " adds r9, #1 \n\t" // increment sIndex 245 | " cmp r9, %[nSamp] \n\t" // if(sIndex == NUM_SAMPLES) 246 | " bne notOverflowed_2 \n\t" 247 | " mov r9, #0 \n\t" // sIndex = 0; 248 | 249 | " stmfd sp!,{r9, %[keepSampling], %[sIndex], %[triggered], %[ch1], %[ch2], %[dCH], %[lCtr]} \n\t" 250 | " bl %[snapMicros] \n\t" // micros() - r0 contains the 32bit result 251 | " ldmfd sp!,{r9, %[keepSampling], %[sIndex], %[triggered], %[ch1], %[ch2], %[dCH], %[lCtr]} \n\t" 252 | 253 | "notOverflowed_2: \n\t" 254 | " strh r9, [%[sIndex]] \n\t" // save sIndex 255 | 256 | " ldrb r0, [%[triggered]] \n\t" // if(triggered) 257 | " cbz r0, notTriggered_2 \n\t" 258 | 259 | " adds %[lCtr], #1 \n\t" // lCtr++ 260 | " cmp %[lCtr], %[halfSamples] \n\t" // if(lCtr == NUM_SAMPLES/2) 261 | " beq finished_2 \n\t" 262 | 263 | "notTriggered_2: \n\t" 264 | " b top_2 \n\t" 265 | "finished_2: \n\t" 266 | 267 | : 268 | : [keepSampling] "r" (keepSamplingPtr), [sIndex] "r" (sIndexPtr), [triggered] "r" (triggeredPtr), 269 | [ch1] "r" (ch1Capture), [ch2] "r" (ch2Capture), [dCH] "r" (bitStore), [lCtr] "r" (lCtr), 270 | [nSamp] "I" (NUM_SAMPLES), [halfSamples] "I" (NUM_SAMPLES/2), 271 | [snapMicros] "i" (snapMicros) 272 | : "r0", "r1", "r9", "memory", "cc" 273 | ); 274 | 275 | } 276 | else { 277 | 278 | asm volatile( 279 | " ldrh r9, [%[sIndex]] \n\t" // load sIndex value 280 | 281 | "top_3: \n\t" 282 | " ldrb r0, [%[keepSampling]] \n\t" // while(keepSampling) 283 | " cbz r0, finished_3 \n\t" 284 | 285 | " ldr r1, =0x40012400 \n\t" // load ADC1 base address, ADC2 = +0x400 286 | 287 | "waitADC1_3: \n\t" 288 | " ldr r0, [r1, #0] \n\t" // ADC1 SR 289 | " lsls r0, r0, #30 \n\t" // get to EOC bit 290 | " bpl waitADC1_3 \n\t" 291 | 292 | "waitADC2_3: \n\t" 293 | " ldr r0, [r1, #0x400] \n\t" // ADC2 SR 294 | " lsls r0, r0, #30 \n\t" // get to EOC bit 295 | " bpl waitADC2_3 \n\t" 296 | 297 | " ldr r0, [r1, #0x4C] \n\t" // get and save ADC1 DR 298 | " strh r0, [%[ch1], r9, lsl #1] \n\t" 299 | " ldr r0, [r1, #0x44C] \n\t" // get and save ADC2 DR 300 | " strh r0, [%[ch2], r9, lsl #1] \n\t" 301 | 302 | " ldr r1, =0x40010800 \n\t" // load GPIOA address 303 | " ldr r0, [r1, #0x08] \n\t" // get and save GPIOA IDR 304 | " strh r0, [%[dCH], r9, lsl #1] \n\t" 305 | 306 | " adds r9, #1 \n\t" // increment sIndex 307 | " cmp r9, %[nSamp] \n\t" // if(sIndex == NUM_SAMPLES) 308 | " bne notOverflowed_3 \n\t" 309 | " mov r9, #0 \n\t" // sIndex = 0; 310 | 311 | " stmfd sp!,{r9, %[keepSampling], %[sIndex], %[triggered], %[ch1], %[ch2], %[dCH], %[lCtr], %[tDelay]} \n\t" 312 | " bl %[snapMicros] \n\t" // micros() - r0 contains the 32bit result 313 | " ldmfd sp!,{r9, %[keepSampling], %[sIndex], %[triggered], %[ch1], %[ch2], %[dCH], %[lCtr], %[tDelay]} \n\t" 314 | 315 | "notOverflowed_3: \n\t" 316 | " strh r9, [%[sIndex]] \n\t" // save sIndex 317 | 318 | " ldrb r0, [%[triggered]] \n\t" // if(triggered) 319 | " cbz r0, notTriggered_3 \n\t" 320 | 321 | " adds %[lCtr], #1 \n\t" // lCtr++ 322 | " cmp %[lCtr], %[halfSamples] \n\t" // if(lCtr == NUM_SAMPLES/2) 323 | " beq finished_3 \n\t" 324 | 325 | "notTriggered_3: \n\t" 326 | " mov r0, %[tDelay] \n\t" // inter sample delay 327 | "1: \n\t" 328 | " subs r0, #1 \n\t" 329 | " bhi 1b \n\t" 330 | 331 | " b top_3 \n\t" 332 | "finished_3: \n\t" 333 | 334 | : 335 | : [keepSampling] "r" (keepSamplingPtr), [sIndex] "r" (sIndexPtr), [triggered] "r" (triggeredPtr), 336 | [ch1] "r" (ch1Capture), [ch2] "r" (ch2Capture), [dCH] "r" (bitStore), [lCtr] "r" (lCtr), 337 | [nSamp] "I" (NUM_SAMPLES), [halfSamples] "I" (NUM_SAMPLES/2), [tDelay] "r" (lDelay), 338 | [snapMicros] "i" (snapMicros) 339 | : "r0", "r1", "r9", "memory", "cc" 340 | ); 341 | 342 | } 343 | 344 | } 345 | 346 | 347 | 348 | // ------------------------ 349 | inline void snapMicros() { 350 | // ------------------------ 351 | samplingTime = micros() - prevTime; 352 | prevTime = micros(); 353 | minSamplesAcquired = true; 354 | } 355 | 356 | 357 | 358 | 359 | 360 | // ------------------------ 361 | void dumpSamples() { 362 | // ------------------------ 363 | float timePerSample = ((float)samplingTime) / NUM_SAMPLES; 364 | DBG_PRINT("Net sampling time (us): "); DBG_PRINTLN(samplingTime); 365 | DBG_PRINT("Per Sample (us): "); DBG_PRINTLN(timePerSample); 366 | DBG_PRINT("Timebase: "); DBG_PRINT(getTimebaseLabel()); DBG_PRINTLN("/div"); 367 | DBG_PRINT("Actual Timebase (us): "); DBG_PRINTLN(timePerSample * 25); 368 | DBG_PRINT("CH1 Coupling: "); DBG_PRINT(cplNames[couplingPos]); DBG_PRINT(", Range: "); DBG_PRINT(rngNames[rangePos]); DBG_PRINTLN("/div"); 369 | DBG_PRINTLN("CH2 Coupling: --, Range: +-2048"); 370 | 371 | DBG_PRINT("Triggered: "); 372 | if(triggered) { 373 | DBG_PRINTLN("YES"); 374 | } 375 | else { 376 | DBG_PRINTLN("NO"); 377 | } 378 | 379 | // calculate stats on this sample set 380 | calculateStats(); 381 | 382 | DBG_PRINT("CH1 Stats"); 383 | if(wStats.mvPos) { 384 | DBG_PRINTLN(" (mV):"); 385 | } 386 | else { 387 | DBG_PRINTLN(" (V):"); 388 | } 389 | 390 | DBG_PRINT("\tVmax: "); DBG_PRINT(wStats.Vmaxf); 391 | DBG_PRINT(", Vmin: "); DBG_PRINT(wStats.Vminf); 392 | DBG_PRINT(", Vavr: "); DBG_PRINT(wStats.Vavrf); 393 | DBG_PRINT(", Vpp: "); DBG_PRINT(wStats.Vmaxf - wStats.Vminf); 394 | DBG_PRINT(", Vrms: "); DBG_PRINTLN(wStats.Vrmsf); 395 | 396 | if(wStats.pulseValid) { 397 | DBG_PRINT("\tFreq: "); DBG_PRINT(wStats.freq); 398 | DBG_PRINT(", Cycle: "); DBG_PRINT(wStats.cycle); DBG_PRINT(" ms"); 399 | DBG_PRINT(", PW: "); DBG_PRINT(wStats.avgPW/1000); DBG_PRINT(" ms"); 400 | DBG_PRINT(", Duty: "); DBG_PRINT(wStats.duty); DBG_PRINTLN(" %"); 401 | } 402 | else { 403 | DBG_PRINT("Freq: "); DBG_PRINT("--"); 404 | DBG_PRINT(", Cycle: "); DBG_PRINT("--"); 405 | DBG_PRINT(", PW: "); DBG_PRINT("--"); 406 | DBG_PRINT(", Duty: "); DBG_PRINTLN("--"); 407 | } 408 | 409 | DBG_PRINTLN(""); 410 | DBG_PRINTLN("Time\tCH1\tCH2\tD_CH1\tD_CH2"); 411 | uint16_t idx = 0; 412 | 413 | // sampling stopped at sIndex - 1 414 | for(uint16_t k = sIndex; k < NUM_SAMPLES; k++) 415 | printSample(k, timePerSample * idx++); 416 | 417 | for(uint16_t k = 0; k < sIndex; k++) 418 | printSample(k, timePerSample * idx++); 419 | 420 | DBG_PRINTLN(""); 421 | } 422 | 423 | 424 | 425 | 426 | // ------------------------ 427 | void printSample(uint16_t k, float timeStamp) { 428 | // ------------------------ 429 | DBG_PRINT(timeStamp); 430 | DBG_PRINT("\t"); 431 | DBG_PRINT((ch1Capture[k] - zeroVoltageA1) * adcMultiplier[rangePos]); 432 | DBG_PRINT("\t"); 433 | DBG_PRINT(ch2Capture[k] - zeroVoltageA2); 434 | DBG_PRINT("\t"); 435 | DBG_PRINT((bitStore[k] & 0x2000) ? 1 : 0); 436 | DBG_PRINT("\t"); 437 | DBG_PRINT((bitStore[k] & 0x4000) ? 1 : 0); 438 | 439 | if(triggered && (tIndex == k)) 440 | DBG_PRINT("\t<--TRIG"); 441 | 442 | DBG_PRINTLN(); 443 | } 444 | 445 | -------------------------------------------------------------------------------- /control.ino: -------------------------------------------------------------------------------- 1 | 2 | enum { TRIGGER_AUTO, TRIGGER_NORM, TRIGGER_SINGLE }; 3 | uint8_t triggerType; 4 | 5 | 6 | // ------------------------ 7 | void setTriggerType(uint8_t tType) { 8 | // ------------------------ 9 | triggerType = tType; 10 | // break any running capture loop 11 | keepSampling = false; 12 | } 13 | 14 | 15 | 16 | 17 | // ------------------------ 18 | void controlLoop() { 19 | // ------------------------ 20 | // start by reading the state of analog system 21 | readInpSwitches(); 22 | 23 | if(triggerType == TRIGGER_AUTO) { 24 | captureDisplayCycle(true); 25 | } 26 | 27 | else if(triggerType == TRIGGER_NORM) { 28 | captureDisplayCycle(false); 29 | } 30 | 31 | else { 32 | // single trigger 33 | clearWaves(); 34 | indicateCapturing(); 35 | // blocking call - until trigger 36 | sampleWaves(false); 37 | indicateCapturingDone(); 38 | hold = true; 39 | // request repainting of screen labels in next draw cycle 40 | repaintLabels(); 41 | // draw the waveform 42 | drawWaves(); 43 | blinkLED(); 44 | // dump captured data on serial port 45 | dumpSamples(); 46 | 47 | // freeze display 48 | while(hold); 49 | 50 | // update display indicating hold released 51 | drawLabels(); 52 | } 53 | 54 | // process any long pending operations which cannot be serviced in ISR 55 | } 56 | 57 | 58 | 59 | 60 | // ------------------------ 61 | void captureDisplayCycle(boolean wTimeOut) { 62 | // ------------------------ 63 | indicateCapturing(); 64 | // blocking call - until timeout or trigger 65 | sampleWaves(wTimeOut); 66 | // draw the waveform 67 | indicateCapturingDone(); 68 | drawWaves(); 69 | // inter wait before next sampling 70 | if(triggered) 71 | blinkLED(); 72 | 73 | if(hold) { 74 | // update UI labels 75 | drawLabels(); 76 | // dump captured data on serial port 77 | dumpSamples(); 78 | } 79 | 80 | // freeze display if requested 81 | while(hold); 82 | } 83 | -------------------------------------------------------------------------------- /display.ino: -------------------------------------------------------------------------------- 1 | // TFT display constants 2 | #define PORTRAIT 0 3 | #define LANDSCAPE 1 4 | 5 | #define TFT_WIDTH 320 6 | #define TFT_HEIGHT 240 7 | #define GRID_WIDTH 300 8 | #define GRID_HEIGHT 210 9 | 10 | #define GRID_COLOR 0x4208 11 | #define ADC_MAX_VAL 4096 12 | #define ADC_2_GRID 800 13 | 14 | 15 | Adafruit_TFTLCD_8bit_STM32 tft; 16 | 17 | // rendered waveform data is stored here for erasing 18 | int16_t ch1Old[GRID_WIDTH] = {0}; 19 | int16_t ch2Old[GRID_WIDTH] = {0}; 20 | int8_t bitOld[GRID_WIDTH] = {0}; 21 | 22 | // grid variables 23 | uint8_t hOffset = (TFT_WIDTH - GRID_WIDTH)/2; 24 | uint8_t vOffset = (TFT_HEIGHT - GRID_HEIGHT)/2; 25 | uint8_t dHeight = GRID_HEIGHT/8; 26 | 27 | // plot variables -- modified by interface section 28 | // controls which section of waveform is displayed on screen 29 | // 0 < xCursor < (NUM_SAMPLES - GRID_WIDTH) 30 | int16_t xCursor; 31 | // controls the vertical positioning of waveform 32 | int16_t yCursors[4]; 33 | // controls which waveforms are displayed 34 | boolean waves[4]; 35 | // prints waveform statistics on screen 36 | boolean printStats = true; 37 | // repaint the labels on screen in draw loop 38 | boolean paintLabels = false; 39 | 40 | // labels around the grid 41 | enum {L_timebase, L_triggerType, L_triggerEdge, L_triggerLevel, L_waves, L_window, L_vPos1, L_vPos2, L_vPos3, L_vPos4}; 42 | uint8_t currentFocus = L_timebase; 43 | 44 | 45 | // ------------------------ 46 | void focusNextLabel() { 47 | // ------------------------ 48 | currentFocus++; 49 | 50 | if((currentFocus == L_vPos1) && !waves[0]) 51 | currentFocus++; 52 | 53 | if((currentFocus == L_vPos2) && !waves[1]) 54 | currentFocus++; 55 | 56 | if((currentFocus == L_vPos3) && !waves[2]) 57 | currentFocus++; 58 | 59 | if((currentFocus == L_vPos4) && !waves[3]) 60 | currentFocus++; 61 | 62 | if(currentFocus > L_vPos4) 63 | currentFocus = L_timebase; 64 | } 65 | 66 | 67 | 68 | 69 | // ------------------------ 70 | void repaintLabels() { 71 | // ------------------------ 72 | paintLabels = true; 73 | } 74 | 75 | 76 | 77 | // ------------------------ 78 | void initDisplay() { 79 | // ------------------------ 80 | tft.reset(); 81 | tft.begin(0x9341); 82 | tft.setRotation(LANDSCAPE); 83 | tft.fillScreen(ILI9341_BLACK); 84 | banner(); 85 | 86 | delay(4000); 87 | 88 | // and paint o-scope 89 | clearWaves(); 90 | } 91 | 92 | 93 | 94 | 95 | // ------------------------ 96 | void drawWaves() { 97 | // ------------------------ 98 | static boolean printStatsOld = false; 99 | 100 | if(printStatsOld && !printStats) 101 | clearStats(); 102 | 103 | printStatsOld = printStats; 104 | 105 | // draw the grid 106 | drawGrid(); 107 | 108 | // clear and draw signal traces 109 | clearNDrawSignals(); 110 | 111 | // if requested update the stats 112 | if(printStats) 113 | drawStats(); 114 | 115 | // if label repaint requested - do so now 116 | if(paintLabels) { 117 | drawLabels(); 118 | paintLabels = false; 119 | } 120 | } 121 | 122 | 123 | 124 | 125 | // ------------------------ 126 | void clearWaves() { 127 | // ------------------------ 128 | // clear screen 129 | tft.fillScreen(ILI9341_BLACK); 130 | // and paint o-scope 131 | drawGrid(); 132 | drawLabels(); 133 | } 134 | 135 | 136 | 137 | boolean cDisplayed = false; 138 | 139 | // ------------------------ 140 | void indicateCapturing() { 141 | // ------------------------ 142 | if((currentTimeBase > T2MS) || (triggerType != TRIGGER_AUTO)) { 143 | cDisplayed = true; 144 | tft.setTextColor(ILI9341_PINK, ILI9341_BLACK); 145 | tft.setCursor(140, 20); 146 | tft.print("Sampling..."); 147 | } 148 | } 149 | 150 | 151 | 152 | // ------------------------ 153 | void indicateCapturingDone() { 154 | // ------------------------ 155 | if(cDisplayed) { 156 | tft.fillRect(140, 20, 66, 8, ILI9341_BLACK); 157 | cDisplayed = false; 158 | } 159 | } 160 | 161 | 162 | 163 | 164 | 165 | // local operations below 166 | 167 | 168 | 169 | 170 | // 0, 1 Analog channels. 2, 3 digital channels 171 | // ------------------------ 172 | void clearNDrawSignals() { 173 | // ------------------------ 174 | static boolean wavesOld[4] = {false,}; 175 | static int16_t yCursorsOld[4]; 176 | 177 | // snap the values to prevent interrupt from changing mid-draw 178 | int16_t xCursorSnap = xCursor; 179 | int16_t zeroVoltageA1Snap = zeroVoltageA1; 180 | int16_t zeroVoltageA2Snap = zeroVoltageA2; 181 | int16_t yCursorsSnap[4]; 182 | boolean wavesSnap[4]; 183 | yCursorsSnap[0] = yCursors[0]; 184 | yCursorsSnap[1] = yCursors[1]; 185 | yCursorsSnap[2] = yCursors[2]; 186 | yCursorsSnap[3] = yCursors[3]; 187 | wavesSnap[0] = waves[0]; 188 | wavesSnap[1] = waves[1]; 189 | wavesSnap[2] = waves[2]; 190 | wavesSnap[3] = waves[3]; 191 | 192 | // draw the GRID_WIDTH section of the waveform from xCursorSnap 193 | int16_t val1, val2; 194 | int16_t transposedPt1, transposedPt2; 195 | uint8_t shiftedVal; 196 | 197 | // sampling stopped at sIndex - 1 198 | int j = sIndex + xCursorSnap; 199 | if(j >= NUM_SAMPLES) 200 | j = j - NUM_SAMPLES; 201 | 202 | // go through all the data points 203 | for(int i = 1, jn = j + 1; i < GRID_WIDTH - 1; j++, i++, jn++) { 204 | if(jn == NUM_SAMPLES) 205 | jn = 0; 206 | 207 | if(j == NUM_SAMPLES) 208 | j = 0; 209 | 210 | // erase old line segment 211 | if(wavesOld[3]) { 212 | val1 = (bitOld[i] & 0b10000000) ? dHeight : 0; 213 | val2 = (bitOld[i + 1] & 0b10000000) ? dHeight : 0; 214 | // clear the line segment 215 | transposedPt1 = GRID_HEIGHT + vOffset + yCursorsOld[3] - val1; 216 | transposedPt2 = GRID_HEIGHT + vOffset + yCursorsOld[3] - val2; 217 | plotLineSegment(transposedPt1, transposedPt2, i, ILI9341_BLACK); 218 | } 219 | 220 | if(wavesOld[2]) { 221 | val1 = (bitOld[i] & 0b01000000) ? dHeight : 0; 222 | val2 = (bitOld[i + 1] & 0b01000000) ? dHeight : 0; 223 | // clear the line segment 224 | transposedPt1 = GRID_HEIGHT + vOffset + yCursorsOld[2] - val1; 225 | transposedPt2 = GRID_HEIGHT + vOffset + yCursorsOld[2] - val2; 226 | plotLineSegment(transposedPt1, transposedPt2, i, ILI9341_BLACK); 227 | } 228 | 229 | if(wavesOld[1]) { 230 | val1 = (ch2Old[i] * GRID_HEIGHT)/ADC_2_GRID; 231 | val2 = (ch2Old[i + 1] * GRID_HEIGHT)/ADC_2_GRID; 232 | // clear the line segment 233 | transposedPt1 = GRID_HEIGHT + vOffset + yCursorsOld[1] - val1; 234 | transposedPt2 = GRID_HEIGHT + vOffset + yCursorsOld[1] - val2; 235 | plotLineSegment(transposedPt1, transposedPt2, i, ILI9341_BLACK); 236 | } 237 | 238 | if(wavesOld[0]) { 239 | val1 = (ch1Old[i] * GRID_HEIGHT)/ADC_2_GRID; 240 | val2 = (ch1Old[i + 1] * GRID_HEIGHT)/ADC_2_GRID; 241 | // clear the line segment 242 | transposedPt1 = GRID_HEIGHT + vOffset + yCursorsOld[0] - val1; 243 | transposedPt2 = GRID_HEIGHT + vOffset + yCursorsOld[0] - val2; 244 | plotLineSegment(transposedPt1, transposedPt2, i, ILI9341_BLACK); 245 | } 246 | 247 | // draw new segments 248 | if(wavesSnap[3]) { 249 | shiftedVal = bitStore[j] >> 7; 250 | val1 = (shiftedVal & 0b10000000) ? dHeight : 0; 251 | val2 = ((bitStore[jn] >> 7) & 0b10000000) ? dHeight : 0; 252 | bitOld[i] &= 0b01000000; 253 | bitOld[i] |= shiftedVal & 0b10000000; 254 | // draw the line segment 255 | transposedPt1 = GRID_HEIGHT + vOffset + yCursorsSnap[3] - val1; 256 | transposedPt2 = GRID_HEIGHT + vOffset + yCursorsSnap[3] - val2; 257 | plotLineSegment(transposedPt1, transposedPt2, i, DG_SIGNAL2); 258 | } 259 | 260 | if(wavesSnap[2]) { 261 | shiftedVal = bitStore[j] >> 7; 262 | val1 = (shiftedVal & 0b01000000) ? dHeight : 0; 263 | val2 = ((bitStore[jn] >> 7) & 0b01000000) ? dHeight : 0; 264 | bitOld[i] &= 0b10000000; 265 | bitOld[i] |= shiftedVal & 0b01000000; 266 | // draw the line segment 267 | transposedPt1 = GRID_HEIGHT + vOffset + yCursorsSnap[2] - val1; 268 | transposedPt2 = GRID_HEIGHT + vOffset + yCursorsSnap[2] - val2; 269 | plotLineSegment(transposedPt1, transposedPt2, i, DG_SIGNAL1); 270 | } 271 | 272 | if(wavesSnap[1]) { 273 | val1 = ((ch2Capture[j] - zeroVoltageA2Snap) * GRID_HEIGHT)/ADC_2_GRID; 274 | val2 = ((ch2Capture[jn] - zeroVoltageA2Snap) * GRID_HEIGHT)/ADC_2_GRID; 275 | ch2Old[i] = ch2Capture[j] - zeroVoltageA2Snap; 276 | // draw the line segment 277 | transposedPt1 = GRID_HEIGHT + vOffset + yCursorsSnap[1] - val1; 278 | transposedPt2 = GRID_HEIGHT + vOffset + yCursorsSnap[1] - val2; 279 | plotLineSegment(transposedPt1, transposedPt2, i, AN_SIGNAL2); 280 | } 281 | 282 | if(wavesSnap[0]) { 283 | val1 = ((ch1Capture[j] - zeroVoltageA1Snap) * GRID_HEIGHT)/ADC_2_GRID; 284 | val2 = ((ch1Capture[jn] - zeroVoltageA1Snap) * GRID_HEIGHT)/ADC_2_GRID; 285 | ch1Old[i] = ch1Capture[j] - zeroVoltageA1Snap; 286 | // draw the line segment 287 | transposedPt1 = GRID_HEIGHT + vOffset + yCursorsSnap[0] - val1; 288 | transposedPt2 = GRID_HEIGHT + vOffset + yCursorsSnap[0] - val2; 289 | plotLineSegment(transposedPt1, transposedPt2, i, AN_SIGNAL1); 290 | } 291 | 292 | } 293 | 294 | 295 | // store the drawn parameters to old storage 296 | wavesOld[0] = wavesSnap[0]; 297 | wavesOld[1] = wavesSnap[1]; 298 | wavesOld[2] = wavesSnap[2]; 299 | wavesOld[3] = wavesSnap[3]; 300 | 301 | yCursorsOld[0] = yCursorsSnap[0]; 302 | yCursorsOld[1] = yCursorsSnap[1]; 303 | yCursorsOld[2] = yCursorsSnap[2]; 304 | yCursorsOld[3] = yCursorsSnap[3]; 305 | 306 | } 307 | 308 | 309 | 310 | 311 | // ------------------------ 312 | inline void plotLineSegment(int16_t transposedPt1, int16_t transposedPt2, int index, uint16_t color) { 313 | // ------------------------ 314 | // range checks 315 | if(transposedPt1 > (GRID_HEIGHT + vOffset)) 316 | transposedPt1 = GRID_HEIGHT + vOffset; 317 | if(transposedPt1 < vOffset) 318 | transposedPt1 = vOffset; 319 | if(transposedPt2 > (GRID_HEIGHT + vOffset)) 320 | transposedPt2 = GRID_HEIGHT + vOffset; 321 | if(transposedPt2 < vOffset) 322 | transposedPt2 = vOffset; 323 | 324 | // draw the line segments 325 | tft.drawLine(index + hOffset, transposedPt1, index + hOffset, transposedPt2, color); 326 | } 327 | 328 | 329 | 330 | 331 | 332 | // ------------------------ 333 | void drawVCursor(int channel, uint16_t color, boolean highlight) { 334 | // ------------------------ 335 | int cPos = GRID_HEIGHT + vOffset + yCursors[channel]; 336 | tft.fillTriangle(0, cPos - 5, hOffset, cPos, 0, cPos + 5, color); 337 | if(highlight) 338 | tft.drawRect(0, cPos - 7, hOffset, 14, ILI9341_WHITE); 339 | } 340 | 341 | 342 | 343 | 344 | // ------------------------ 345 | void drawGrid() { 346 | // ------------------------ 347 | uint8_t hPacing = GRID_WIDTH / 12; 348 | uint8_t vPacing = GRID_HEIGHT / 8; 349 | 350 | for(int i = 1; i < 12; i++) 351 | tft.drawFastVLine(i * hPacing + hOffset, vOffset, GRID_HEIGHT, GRID_COLOR); 352 | 353 | for(int i = 1; i < 8; i++) 354 | tft.drawFastHLine(hOffset, i * vPacing + vOffset, GRID_WIDTH, GRID_COLOR); 355 | 356 | for(int i = 1; i < 5*8; i++) 357 | tft.drawFastHLine(hOffset + GRID_WIDTH/2 - 3, i * vPacing/5 + vOffset, 7, GRID_COLOR); 358 | 359 | for(int i = 1; i < 5*12; i++) 360 | tft.drawFastVLine(i * hPacing/5 + hOffset, vOffset + GRID_HEIGHT/2 - 4, 7, GRID_COLOR); 361 | 362 | tft.drawRect(hOffset, vOffset, GRID_WIDTH, GRID_HEIGHT, ILI9341_WHITE); 363 | } 364 | 365 | 366 | 367 | 368 | // ------------------------ 369 | void drawLabels() { 370 | // ------------------------ 371 | // draw the static labels around the grid 372 | 373 | // erase top bar 374 | tft.fillRect(hOffset, 0, TFT_WIDTH, vOffset, ILI9341_BLACK); 375 | tft.fillRect(hOffset + GRID_WIDTH, 0, hOffset, TFT_HEIGHT, ILI9341_BLACK); 376 | 377 | // paint run/hold information 378 | // ----------------- 379 | tft.setCursor(hOffset + 2, 4); 380 | 381 | if(hold) { 382 | tft.setTextColor(ILI9341_WHITE, ILI9341_RED); 383 | tft.print(" HOLD "); 384 | } 385 | else { 386 | tft.setTextColor(ILI9341_GREEN, ILI9341_BLACK); 387 | tft.print("RUN"); 388 | } 389 | 390 | // draw x-window at top, range = 200px 391 | // ----------------- 392 | int sampleSizePx = 160; 393 | float lOffset = (TFT_WIDTH - sampleSizePx)/2; 394 | tft.drawFastVLine(lOffset, 3, vOffset - 6, ILI9341_GREEN); 395 | tft.drawFastVLine(lOffset + sampleSizePx, 3, vOffset - 6, ILI9341_GREEN); 396 | tft.drawFastHLine(lOffset, vOffset/2, sampleSizePx, ILI9341_GREEN); 397 | 398 | // where does xCursor lie in this range 399 | float windowSize = GRID_WIDTH * sampleSizePx/NUM_SAMPLES; 400 | float xCursorPx = xCursor * sampleSizePx/NUM_SAMPLES + lOffset; 401 | if(currentFocus == L_window) 402 | tft.drawRect(xCursorPx, 4, windowSize, vOffset - 8, ILI9341_WHITE); 403 | else 404 | tft.fillRect(xCursorPx, 4, windowSize, vOffset - 8, ILI9341_GREEN); 405 | 406 | 407 | // print active wave indicators 408 | // ----------------- 409 | tft.setCursor(250, 4); 410 | if(waves[0]) { 411 | tft.setTextColor(AN_SIGNAL1, ILI9341_BLACK); 412 | tft.print("A1 "); 413 | } 414 | else 415 | tft.print(" "); 416 | 417 | if(waves[1]) { 418 | tft.setTextColor(AN_SIGNAL2, ILI9341_BLACK); 419 | tft.print("A2 "); 420 | } 421 | else 422 | tft.print(" "); 423 | 424 | if(waves[2]) { 425 | tft.setTextColor(DG_SIGNAL1, ILI9341_BLACK); 426 | tft.print("D1 "); 427 | } 428 | else 429 | tft.print(" "); 430 | 431 | if(waves[3]) { 432 | tft.setTextColor(DG_SIGNAL2, ILI9341_BLACK); 433 | tft.print("D2"); 434 | } 435 | 436 | if(currentFocus == L_waves) 437 | tft.drawRect(247, 0, 72, vOffset, ILI9341_WHITE); 438 | 439 | // erase left side of grid 440 | tft.fillRect(0, 0, hOffset, TFT_HEIGHT, ILI9341_BLACK); 441 | 442 | // draw new wave cursors 443 | // ----------------- 444 | if(waves[3]) 445 | drawVCursor(3, DG_SIGNAL2, (currentFocus == L_vPos4)); 446 | if(waves[2]) 447 | drawVCursor(2, DG_SIGNAL1, (currentFocus == L_vPos3)); 448 | if(waves[1]) 449 | drawVCursor(1, AN_SIGNAL2, (currentFocus == L_vPos2)); 450 | if(waves[0]) 451 | drawVCursor(0, AN_SIGNAL1, (currentFocus == L_vPos1)); 452 | 453 | // erase bottom bar 454 | tft.fillRect(hOffset, GRID_HEIGHT + vOffset, TFT_WIDTH, vOffset, ILI9341_BLACK); 455 | 456 | // print input switch pos 457 | // ----------------- 458 | tft.setTextColor(ILI9341_YELLOW, ILI9341_BLACK); 459 | tft.setCursor(hOffset + 10, GRID_HEIGHT + vOffset + 4); 460 | tft.print(rngNames[rangePos]); 461 | tft.setCursor(hOffset + 50, GRID_HEIGHT + vOffset + 4); 462 | tft.print(cplNames[couplingPos]); 463 | 464 | // print new timebase 465 | // ----------------- 466 | tft.setTextColor(ILI9341_WHITE, ILI9341_BLACK); 467 | tft.setCursor(145, GRID_HEIGHT + vOffset + 4); 468 | if(currentFocus == L_timebase) 469 | tft.drawRect(140, GRID_HEIGHT + vOffset, 45, vOffset, ILI9341_WHITE); 470 | tft.print(getTimebaseLabel()); 471 | 472 | // print trigger type 473 | // ----------------- 474 | tft.setTextColor(ILI9341_GREEN, ILI9341_BLACK); 475 | tft.setCursor(230, GRID_HEIGHT + vOffset + 4); 476 | if(currentFocus == L_triggerType) 477 | tft.drawRect(225, GRID_HEIGHT + vOffset, 35, vOffset, ILI9341_WHITE); 478 | 479 | switch(triggerType) { 480 | case TRIGGER_AUTO: 481 | tft.print("AUTO"); 482 | break; 483 | case TRIGGER_NORM: 484 | tft.print("NORM"); 485 | break; 486 | case TRIGGER_SINGLE: 487 | tft.print("SING"); 488 | break; 489 | } 490 | 491 | // draw trigger edge 492 | // ----------------- 493 | if(currentFocus == L_triggerEdge) 494 | tft.drawRect(266, GRID_HEIGHT + vOffset, 15, vOffset + 4, ILI9341_WHITE); 495 | 496 | int trigX = 270; 497 | 498 | if(triggerRising) { 499 | tft.drawFastHLine(trigX, TFT_HEIGHT - 3, 5, ILI9341_GREEN); 500 | tft.drawFastVLine(trigX + 4, TFT_HEIGHT -vOffset + 2, vOffset - 4, ILI9341_GREEN); 501 | tft.drawFastHLine(trigX + 4, TFT_HEIGHT -vOffset + 2, 5, ILI9341_GREEN); 502 | tft.fillTriangle(trigX + 2, 232, trigX + 4, 230, trigX + 6, 232, ILI9341_GREEN); 503 | } 504 | else { 505 | tft.drawFastHLine(trigX + 4, TFT_HEIGHT - 3, 5, ILI9341_GREEN); 506 | tft.drawFastVLine(trigX + 4, TFT_HEIGHT -vOffset + 2, vOffset - 4, ILI9341_GREEN); 507 | tft.drawFastHLine(trigX - 1, TFT_HEIGHT -vOffset + 2, 5, ILI9341_GREEN); 508 | tft.fillTriangle(trigX + 2, 231, trigX + 4, 233, trigX + 6, 231, ILI9341_GREEN); 509 | } 510 | 511 | 512 | // draw trigger level on right side 513 | // ----------------- 514 | int cPos = GRID_HEIGHT + vOffset + yCursors[0] - getTriggerLevel()/3; 515 | tft.fillTriangle(TFT_WIDTH, cPos - 5, TFT_WIDTH - hOffset, cPos, TFT_WIDTH, cPos + 5, AN_SIGNAL1); 516 | if(currentFocus == L_triggerLevel) 517 | tft.drawRect(GRID_WIDTH + hOffset, cPos - 7, hOffset, 14, ILI9341_WHITE); 518 | } 519 | 520 | 521 | // #define DRAW_TIMEBASE 522 | 523 | // ------------------------ 524 | void drawStats() { 525 | // ------------------------ 526 | static long lastCalcTime = 0; 527 | boolean clearStats = false; 528 | 529 | // calculate stats once a while 530 | if(millis() - lastCalcTime > 300) { 531 | lastCalcTime = millis(); 532 | calculateStats(); 533 | clearStats = true; 534 | } 535 | 536 | // draw stat labels 537 | tft.setTextColor(ILI9341_RED, ILI9341_BLACK); 538 | 539 | tft.setCursor(25, 20); 540 | tft.print("Freq:"); 541 | tft.setCursor(25, 30); 542 | tft.print("Cycle:"); 543 | tft.setCursor(25, 40); 544 | tft.print("PW:"); 545 | tft.setCursor(25, 50); 546 | tft.print("Duty:"); 547 | #ifdef DRAW_TIMEBASE 548 | tft.setCursor(25, 60); 549 | tft.print("T/div:"); 550 | #endif 551 | 552 | tft.setCursor(240, 20); 553 | tft.print("Vmax:"); 554 | tft.setCursor(240, 30); 555 | tft.print("Vmin:"); 556 | tft.setCursor(240, 40); 557 | tft.print("Vavr:"); 558 | tft.setCursor(240, 50); 559 | tft.print("Vpp:"); 560 | tft.setCursor(240, 60); 561 | tft.print("Vrms:"); 562 | 563 | // print new stats 564 | tft.setTextColor(ILI9341_WHITE, ILI9341_BLACK); 565 | 566 | if(clearStats) 567 | tft.fillRect(60, 20, 50, 50, ILI9341_BLACK); 568 | 569 | if(wStats.pulseValid) { 570 | tft.setCursor(60, 20); 571 | tft.print((int) wStats.freq); 572 | tft.setCursor(60, 30); 573 | tft.print(wStats.cycle); tft.print(" ms"); 574 | tft.setCursor(60, 40); 575 | tft.print(wStats.avgPW/1000); tft.print(" ms"); 576 | tft.setCursor(60, 50); 577 | tft.print(wStats.duty); tft.print(" %"); 578 | } 579 | 580 | #ifdef DRAW_TIMEBASE 581 | tft.setCursor(60, 60); 582 | int timebase = ((double)samplingTime * 25) / NUM_SAMPLES; 583 | if(timebase > 10000) { 584 | tft.print(timebase/1000); tft.print(" ms"); 585 | } 586 | else { 587 | tft.print(timebase); tft.print(" us"); 588 | } 589 | #endif 590 | 591 | if(clearStats) 592 | tft.fillRect(270, 20, GRID_WIDTH + hOffset - 270 - 1, 50, ILI9341_BLACK); 593 | 594 | drawVoltage(wStats.Vmaxf, 20, wStats.mvPos); 595 | drawVoltage(wStats.Vminf, 30, wStats.mvPos); 596 | drawVoltage(wStats.Vavrf, 40, wStats.mvPos); 597 | drawVoltage(wStats.Vmaxf - wStats.Vminf, 50, wStats.mvPos); 598 | drawVoltage(wStats.Vrmsf, 60, wStats.mvPos); 599 | 600 | } 601 | 602 | 603 | 604 | 605 | // ------------------------ 606 | void calculateStats() { 607 | // ------------------------ 608 | // extract waveform stats 609 | int16_t Vmax = -ADC_MAX_VAL, Vmin = ADC_MAX_VAL; 610 | int32_t sumSamples = 0; 611 | int64_t sumSquares = 0; 612 | int32_t freqSumSamples = 0; 613 | 614 | for(uint16_t k = 0; k < NUM_SAMPLES; k++) { 615 | int16_t val = ch1Capture[k] - zeroVoltageA1; 616 | if(Vmax < val) 617 | Vmax = val; 618 | if(Vmin > val) 619 | Vmin = val; 620 | 621 | sumSamples += val; 622 | freqSumSamples += ch1Capture[k]; 623 | sumSquares += (val * val); 624 | } 625 | 626 | // find out frequency 627 | uint16_t fVavr = freqSumSamples/NUM_SAMPLES; 628 | boolean dnWave = (ch1Capture[sIndex] < fVavr - 10); 629 | boolean firstOne = true; 630 | uint16_t cHigh = 0; 631 | 632 | uint16_t sumCW = 0; 633 | uint16_t sumPW = 0; 634 | uint16_t numCycles = 0; 635 | uint16_t numHCycles = 0; 636 | 637 | // sampling stopped at sIndex - 1 638 | for(uint16_t sCtr = 0, k = sIndex; sCtr < NUM_SAMPLES; sCtr++, k++) { 639 | if(k == NUM_SAMPLES) 640 | k = 0; 641 | 642 | // mark the points where wave transitions the average value 643 | if(dnWave && (ch1Capture[k] > fVavr + 10)) { 644 | if(!firstOne) { 645 | sumCW += (sCtr - cHigh); 646 | numCycles++; 647 | } 648 | else 649 | firstOne = false; 650 | 651 | dnWave = false; 652 | cHigh = sCtr; 653 | } 654 | 655 | if(!dnWave && (ch1Capture[k] < fVavr - 10)) { 656 | if(!firstOne) { 657 | sumPW += (sCtr - cHigh); 658 | numHCycles++; 659 | } 660 | 661 | dnWave = true; 662 | } 663 | } 664 | 665 | double tPerSample = ((double)samplingTime) / NUM_SAMPLES; 666 | float timePerDiv = tPerSample * 25; 667 | double avgCycleWidth = sumCW * tPerSample / numCycles; 668 | 669 | wStats.avgPW = sumPW * tPerSample / numHCycles; 670 | wStats.duty = wStats.avgPW * 100 / avgCycleWidth; 671 | wStats.freq = 1000000/avgCycleWidth; 672 | wStats.cycle = avgCycleWidth/1000; 673 | wStats.pulseValid = (avgCycleWidth != 0) && (wStats.avgPW != 0) && ((Vmax - Vmin) > 20); 674 | 675 | wStats.mvPos = (rangePos == RNG_50mV) || (rangePos == RNG_20mV) || (rangePos == RNG_10mV); 676 | wStats.Vrmsf = sqrt(sumSquares/NUM_SAMPLES) * adcMultiplier[rangePos]; 677 | wStats.Vavrf = sumSamples/NUM_SAMPLES * adcMultiplier[rangePos]; 678 | wStats.Vmaxf = Vmax * adcMultiplier[rangePos]; 679 | wStats.Vminf = Vmin * adcMultiplier[rangePos]; 680 | } 681 | 682 | 683 | 684 | 685 | // ------------------------ 686 | void drawVoltage(float volt, int y, boolean mvRange) { 687 | // ------------------------ 688 | // text is standard 5 px wide 689 | int numDigits = 1; 690 | int lVolt = volt; 691 | 692 | // is there a negative sign at front 693 | if(volt < 0) { 694 | numDigits++; 695 | lVolt = -lVolt; 696 | } 697 | 698 | // how many digits before 0 699 | if(lVolt > 999) 700 | numDigits++; 701 | if(lVolt > 99) 702 | numDigits++; 703 | if(lVolt > 9) 704 | numDigits++; 705 | 706 | // mv range has mV appended at back 707 | if(mvRange) { 708 | numDigits += 1; 709 | int x = GRID_WIDTH + hOffset - 10 - numDigits * 5; 710 | tft.setCursor(x, y); 711 | int iVolt = volt; 712 | tft.print(iVolt); 713 | tft.print("m"); 714 | } 715 | else { 716 | // non mV range has two decimal pos and V appended at back 717 | numDigits += 3; 718 | int x = GRID_WIDTH + hOffset -10 - numDigits * 5; 719 | tft.setCursor(x, y); 720 | tft.print(volt); 721 | } 722 | 723 | } 724 | 725 | 726 | 727 | 728 | 729 | // ------------------------ 730 | void clearStats() { 731 | // ------------------------ 732 | tft.fillRect(hOffset, vOffset, GRID_WIDTH, 80, ILI9341_BLACK); 733 | } 734 | 735 | 736 | 737 | // ------------------------ 738 | void banner() { 739 | // ------------------------ 740 | tft.setTextColor(ILI9341_WHITE, ILI9341_BLACK); 741 | tft.setTextSize(2); 742 | tft.setCursor(110, 30); 743 | tft.print("DLO-138"); 744 | tft.drawRect(100, 25, 100, 25, ILI9341_WHITE); 745 | 746 | tft.setTextSize(1); 747 | tft.setCursor(30, 70); 748 | tft.print("Dual Channel O-Scope with logic analyzer"); 749 | 750 | tft.setCursor(30, 95); 751 | tft.print("Usage: "); 752 | tft.setTextColor(ILI9341_YELLOW, ILI9341_BLACK); 753 | tft.print("https://github.com/ardyesp/DLO-138"); 754 | 755 | tft.setTextColor(ILI9341_WHITE, ILI9341_BLACK); 756 | tft.setCursor(30, 120); 757 | tft.print("DSO-138 hardware by JYE-Tech"); 758 | 759 | tft.setCursor(30, 145); 760 | tft.print("Firmware version: "); 761 | tft.print(FIRMWARE_VERSION); 762 | 763 | tft.setTextSize(1); 764 | tft.setCursor(30, 200); 765 | tft.print("GNU GENERAL PUBLIC LICENSE Version 3"); 766 | } 767 | 768 | 769 | -------------------------------------------------------------------------------- /encoder.ino: -------------------------------------------------------------------------------- 1 | /* Rotary encoder methods 2 | CW CCW 3 | 01 00 4 | 11 10 5 | 10 11 6 | 00 01 7 | */ 8 | 9 | int encoderVal = 0; 10 | 11 | 12 | // ------------------------ 13 | // called by ISR 14 | int getEncoderSteps() { 15 | // ------------------------ 16 | static int encoderPrevVal = 0; 17 | int newCount = encoderVal - encoderPrevVal; 18 | 19 | // 4 ticks make one step in rotary encoder 20 | int numSteps = newCount / 4; 21 | int remainder = newCount % 4; 22 | 23 | encoderPrevVal = encoderVal - remainder; 24 | return numSteps; 25 | } 26 | 27 | 28 | 29 | // ------------------------ 30 | // ISR 31 | void readEncoderISR() { 32 | // ------------------------ 33 | static byte lastPos = 0b00; 34 | 35 | byte aNow = digitalRead(ENCODER_A); 36 | byte bNow = digitalRead(ENCODER_B); 37 | 38 | byte posNow = aNow << 1 | bNow; 39 | 40 | if((lastPos == 0b01) && (posNow == 0b11)) 41 | encoderVal++; 42 | else if((lastPos == 0b11) && (posNow == 0b10)) 43 | encoderVal++; 44 | else if((lastPos == 0b10) && (posNow == 0b00)) 45 | encoderVal++; 46 | else if((lastPos == 0b00) && (posNow == 0b01)) 47 | encoderVal++; 48 | else if((lastPos == 0b00) && (posNow == 0b10)) 49 | encoderVal--; 50 | else if((lastPos == 0b10) && (posNow == 0b11)) 51 | encoderVal--; 52 | else if((lastPos == 0b11) && (posNow == 0b01)) 53 | encoderVal--; 54 | else if((lastPos == 0b01) && (posNow == 0b00)) 55 | encoderVal--; 56 | 57 | lastPos = posNow; 58 | 59 | // convert the encoder reading into rounded steps 60 | int steps = getEncoderSteps(); 61 | 62 | if(steps != 0) { 63 | // take action 64 | encoderChanged(steps); 65 | } 66 | } 67 | 68 | 69 | 70 | long lastABPress = 0; 71 | 72 | 73 | // ------------------------ 74 | // ISR 75 | void readASwitchISR() { 76 | // ------------------------ 77 | if(millis() - lastABPress < BTN_DEBOUNCE_TIME) 78 | return; 79 | lastABPress = millis(); 80 | 81 | encoderChanged(-1); 82 | } 83 | 84 | 85 | 86 | // ------------------------ 87 | // ISR 88 | void readBSwitchISR() { 89 | // ------------------------ 90 | if(millis() - lastABPress < BTN_DEBOUNCE_TIME) 91 | return; 92 | lastABPress = millis(); 93 | 94 | encoderChanged(1); 95 | } 96 | 97 | 98 | -------------------------------------------------------------------------------- /global.h: -------------------------------------------------------------------------------- 1 | // comment out following line to use DSO push buttons instead of encoder 2 | #define USE_ENCODER 3 | 4 | // serial print macros 5 | #define DBG_INIT(...) { Serial.begin(__VA_ARGS__); } 6 | #define DBG_PRINT(...) { Serial.print(__VA_ARGS__); } 7 | #define DBG_PRINTLN(...) { Serial.println(__VA_ARGS__); } 8 | 9 | #define SERIAL_BAUD_RATE 115200 10 | 11 | // analog and digital samples storage depth 12 | #define NUM_SAMPLES 2048 13 | 14 | // display colours 15 | #define AN_SIGNAL1 ILI9341_YELLOW 16 | #define AN_SIGNAL2 ILI9341_MAGENTA 17 | #define DG_SIGNAL1 ILI9341_RED 18 | #define DG_SIGNAL2 ILI9341_BLUE 19 | 20 | // pin definitions (DSO138) 21 | #define BOARD_LED PA15 22 | #define TEST_WAVE_PIN PA7 // 1KHz square wave output 23 | #define TRIGGER_IN PA8 24 | #define TRIGGER_LEVEL PB8 25 | #define VGEN PB9 // used to generate negative voltage in DSO138 26 | 27 | // captured inputs 28 | #define AN_CH1 PA0 // analog channel 1 29 | #define AN_CH2 PA4 // analog channel 2 30 | #define DG_CH1 PA13 // digital channel 1 - 5V tolerant pin. Pin mask throughout code has to match digital pin 31 | #define DG_CH2 PA14 // digital channel 2 - 5V tolerant pin. Pin mask throughout code has to match digital pin 32 | 33 | // misc analog inputs 34 | #define VSENSSEL1 PA2 35 | #define VSENSSEL2 PA1 36 | #define CPLSEL PA3 37 | 38 | // switches 39 | #define ENCODER_SW PB12 40 | #define ENCODER_A PB13 41 | #define ENCODER_B PB14 42 | #define BTN4 PB15 43 | 44 | // TFT pins are hard coded in Adafruit_TFTLCD_8bit_STM32.h file 45 | // TFT_RD PB10 46 | // TFT_WR PC15 47 | // TFT_RS PC14 48 | // TFT_CS PC13 49 | // TFT_RST PB11 50 | 51 | // FLASH memory address defines 52 | #define PARAM_PREAMBLE 0 53 | #define PARAM_TIMEBASE 1 54 | #define PARAM_TRIGTYPE 2 55 | #define PARAM_TRIGDIR 3 56 | #define PARAM_XCURSOR 4 57 | #define PARAM_YCURSOR 5 // 5,6,7,8 - 4 params 58 | #define PARAM_WAVES 9 // 9,10,11,12 - 4 params 59 | #define PARAM_TLEVEL 13 60 | #define PARAM_STATS 14 61 | #define PARAM_ZERO1 15 62 | #define PARAM_ZERO2 16 63 | 64 | #define LED_ON digitalWrite(BOARD_LED, LOW) 65 | #define LED_OFF digitalWrite(BOARD_LED, HIGH) 66 | 67 | // number of pixels waveform moves left/right or up/down 68 | #define XCURSOR_STEP 25 69 | #define YCURSOR_STEP 5 70 | 71 | 72 | #define BTN_DEBOUNCE_TIME 350 73 | -------------------------------------------------------------------------------- /interface.ino: -------------------------------------------------------------------------------- 1 | 2 | 3 | // ------------------------ 4 | const char* getTimebaseLabel() { 5 | // ------------------------ 6 | return tbNames[currentTimeBase]; 7 | } 8 | 9 | 10 | 11 | // interface operations defined below 12 | 13 | long lastBtnPress = 0; 14 | 15 | 16 | 17 | // ------------------------ 18 | void btn4ISR() { 19 | // ------------------------ 20 | static boolean pressed = false; 21 | static long pressedTime = 0; 22 | 23 | // btn pressed or released? 24 | if(!pressed && (digitalRead(BTN4) == LOW)) { 25 | // debounce 26 | if(millis() - pressedTime < BTN_DEBOUNCE_TIME) 27 | return; 28 | pressedTime = millis(); 29 | pressed = true; 30 | } 31 | 32 | 33 | if(pressed && (digitalRead(BTN4) == HIGH)) { 34 | // debounce 35 | if(millis() - pressedTime < 5) 36 | return; 37 | 38 | pressed = false; 39 | 40 | // is it a short press 41 | if(millis() - pressedTime < 1000) { 42 | // toggle hold 43 | hold = !hold; 44 | repaintLabels(); 45 | } 46 | else { 47 | // long press reset parameter to default 48 | resetParam(); 49 | } 50 | } 51 | } 52 | 53 | 54 | 55 | // ------------------------ 56 | void readESwitchISR() { 57 | // ------------------------ 58 | // debounce 59 | if(millis() - lastBtnPress < BTN_DEBOUNCE_TIME) 60 | return; 61 | lastBtnPress = millis(); 62 | 63 | // select different parameters to change 64 | focusNextLabel(); 65 | 66 | // request repainting of screen labels 67 | repaintLabels(); 68 | 69 | // manually update display if frozen 70 | if(hold) 71 | drawWaves(); 72 | 73 | if(triggerType != TRIGGER_AUTO) 74 | // break the sampling loop 75 | keepSampling = false; 76 | } 77 | 78 | 79 | 80 | 81 | // ------------------------ 82 | void resetParam() { 83 | // ------------------------ 84 | // which label has current focus 85 | switch(currentFocus) { 86 | case L_triggerLevel: 87 | // set trigger level to 0 88 | setTriggerLevel(0); 89 | saveParameter(PARAM_TLEVEL, 0); 90 | repaintLabels(); 91 | break; 92 | case L_window: 93 | // set x in the middle 94 | changeXCursor((NUM_SAMPLES - GRID_WIDTH)/2); 95 | break; 96 | case L_vPos1: 97 | // zero the trace base 98 | calculateTraceZero(0); 99 | changeYCursor(0, -GRID_HEIGHT/2 - 1); 100 | break; 101 | case L_vPos2: 102 | // zero the trace base 103 | calculateTraceZero(1); 104 | changeYCursor(1, -GRID_HEIGHT/2 - 1); 105 | break; 106 | case L_vPos3: 107 | changeYCursor(2, -GRID_HEIGHT/2 - 1); 108 | break; 109 | case L_vPos4: 110 | changeYCursor(3, -GRID_HEIGHT/2 - 1); 111 | break; 112 | default: 113 | // toggle stats printing 114 | printStats = !printStats; 115 | saveParameter(PARAM_STATS, printStats); 116 | break; 117 | } 118 | 119 | // manually update display if frozen 120 | if(hold) 121 | drawWaves(); 122 | 123 | if(triggerType != TRIGGER_AUTO) 124 | // break the sampling loop 125 | keepSampling = false; 126 | } 127 | 128 | 129 | 130 | 131 | // ------------------------ 132 | void calculateTraceZero(int waveID) { 133 | // ------------------------ 134 | // calculate zero only if switch is in GND position 135 | if(couplingPos != CPL_GND) 136 | return; 137 | 138 | if(waveID > 1) 139 | return; 140 | 141 | uint16_t *wave = (waveID == 0)? ch1Capture : ch2Capture; 142 | 143 | // zero the trace 144 | int32_t sumSamples = 0; 145 | 146 | for(uint16_t k = 0; k < NUM_SAMPLES; k++) { 147 | sumSamples += wave[k]; 148 | } 149 | 150 | uint16_t Vavr = sumSamples/NUM_SAMPLES; 151 | 152 | if(waveID == 0) { 153 | zeroVoltageA1 = Vavr; 154 | saveParameter(PARAM_ZERO1, zeroVoltageA1); 155 | } 156 | else { 157 | zeroVoltageA2 = Vavr; 158 | saveParameter(PARAM_ZERO2, zeroVoltageA2); 159 | } 160 | } 161 | 162 | 163 | 164 | 165 | // ------------------------ 166 | void encoderChanged(int steps) { 167 | // ------------------------ 168 | // which label has current focus 169 | switch(currentFocus) { 170 | case L_timebase: 171 | if(steps > 0) decrementTimeBase(); else incrementTimeBase(); 172 | break; 173 | case L_triggerType: 174 | if(steps > 0) incrementTT(); else decrementTT(); 175 | break; 176 | case L_triggerEdge: 177 | if(steps > 0) setTriggerRising(); else setTriggerFalling(); 178 | break; 179 | case L_triggerLevel: 180 | if(steps > 0) incrementTLevel(); else decrementTLevel(); 181 | break; 182 | case L_waves: 183 | if(steps > 0) incrementWaves(); else decrementWaves(); 184 | break; 185 | case L_window: 186 | if(steps > 0) changeXCursor(xCursor + XCURSOR_STEP); else changeXCursor(xCursor - XCURSOR_STEP); 187 | break; 188 | case L_vPos1: 189 | if(steps > 0) changeYCursor(0, yCursors[0] - YCURSOR_STEP); else changeYCursor(0, yCursors[0] + YCURSOR_STEP); 190 | break; 191 | case L_vPos2: 192 | if(steps > 0) changeYCursor(1, yCursors[1] - YCURSOR_STEP); else changeYCursor(1, yCursors[1] + YCURSOR_STEP); 193 | break; 194 | case L_vPos3: 195 | if(steps > 0) changeYCursor(2, yCursors[2] - YCURSOR_STEP); else changeYCursor(2, yCursors[2] + YCURSOR_STEP); 196 | break; 197 | case L_vPos4: 198 | if(steps > 0) changeYCursor(3, yCursors[3] - YCURSOR_STEP); else changeYCursor(3, yCursors[3] + YCURSOR_STEP); 199 | break; 200 | } 201 | 202 | // manually update display if frozen 203 | if(hold) 204 | drawWaves(); 205 | 206 | if(triggerType != TRIGGER_AUTO) 207 | // break the sampling loop 208 | keepSampling = false; 209 | } 210 | 211 | 212 | 213 | // ------------------------ 214 | void incrementTLevel() { 215 | // ------------------------ 216 | int16_t tL = getTriggerLevel(); 217 | setTriggerLevel(tL + 5); 218 | saveParameter(PARAM_TLEVEL, tL); 219 | repaintLabels(); 220 | } 221 | 222 | 223 | 224 | // ------------------------ 225 | void decrementTLevel() { 226 | // ------------------------ 227 | int16_t tL = getTriggerLevel(); 228 | setTriggerLevel(tL - 5); 229 | saveParameter(PARAM_TLEVEL, tL); 230 | repaintLabels(); 231 | } 232 | 233 | 234 | // A1, D1, D2, A2 235 | // 0, 2, 3, 1 236 | // ------------------------ 237 | void incrementWaves() { 238 | // ------------------------ 239 | // add more waves 240 | if(waves[1]) { 241 | return; 242 | } 243 | else if(waves[3]) { 244 | waves[1] = true; 245 | saveParameter(PARAM_WAVES + 1, waves[1]); 246 | } 247 | else if(waves[2]) { 248 | waves[3] = true; 249 | saveParameter(PARAM_WAVES + 3, waves[3]); 250 | } 251 | else { 252 | waves[2] = true; 253 | saveParameter(PARAM_WAVES + 2, waves[2]); 254 | } 255 | 256 | repaintLabels(); 257 | } 258 | 259 | 260 | 261 | 262 | // ------------------------ 263 | void decrementWaves() { 264 | // ------------------------ 265 | // remove waves 266 | if(waves[1]) { 267 | waves[1] = false; 268 | saveParameter(PARAM_WAVES + 1, waves[1]); 269 | } 270 | else if(waves[3]) { 271 | waves[3] = false; 272 | saveParameter(PARAM_WAVES + 3, waves[3]); 273 | } 274 | else if(waves[2]) { 275 | waves[2] = false; 276 | saveParameter(PARAM_WAVES + 2, waves[2]); 277 | } 278 | else { 279 | return; 280 | } 281 | 282 | repaintLabels(); 283 | } 284 | 285 | 286 | 287 | // ------------------------ 288 | void setTriggerRising() { 289 | // ------------------------ 290 | if(triggerRising) 291 | return; 292 | 293 | setTriggerRising(true); 294 | saveParameter(PARAM_TRIGDIR, triggerRising); 295 | repaintLabels(); 296 | } 297 | 298 | 299 | 300 | // ------------------------ 301 | void setTriggerFalling() { 302 | // ------------------------ 303 | if(!triggerRising) 304 | return; 305 | 306 | setTriggerRising(false); 307 | saveParameter(PARAM_TRIGDIR, triggerRising); 308 | repaintLabels(); 309 | } 310 | 311 | 312 | 313 | 314 | // ------------------------ 315 | void incrementTT() { 316 | // ------------------------ 317 | if(triggerType == TRIGGER_SINGLE) 318 | return; 319 | 320 | setTriggerType(triggerType + 1); 321 | // trigger type is not saved 322 | // saveParameter(PARAM_TRIGTYPE, triggerType); 323 | repaintLabels(); 324 | } 325 | 326 | 327 | 328 | // ------------------------ 329 | void decrementTT() { 330 | // ------------------------ 331 | if(triggerType == TRIGGER_AUTO) 332 | return; 333 | setTriggerType(triggerType - 1); 334 | // trigger type is not saved 335 | // saveParameter(PARAM_TRIGTYPE, triggerType); 336 | repaintLabels(); 337 | } 338 | 339 | 340 | 341 | 342 | // ------------------------ 343 | void incrementTimeBase() { 344 | // ------------------------ 345 | if(currentTimeBase == T50MS) 346 | return; 347 | 348 | setTimeBase(currentTimeBase + 1); 349 | } 350 | 351 | 352 | 353 | // ------------------------ 354 | void decrementTimeBase() { 355 | // ------------------------ 356 | if(currentTimeBase == T20US) 357 | return; 358 | 359 | setTimeBase(currentTimeBase - 1); 360 | } 361 | 362 | 363 | 364 | // ------------------------ 365 | void setTimeBase(uint8_t timeBase) { 366 | // ------------------------ 367 | currentTimeBase = timeBase; 368 | setSamplingRate(timeBase); 369 | saveParameter(PARAM_TIMEBASE, currentTimeBase); 370 | // request repainting of screen labels 371 | repaintLabels(); 372 | } 373 | 374 | 375 | 376 | 377 | // ------------------------ 378 | void toggleWave(uint8_t num) { 379 | // ------------------------ 380 | waves[num] = !waves[num]; 381 | saveParameter(PARAM_WAVES + num, waves[num]); 382 | repaintLabels(); 383 | } 384 | 385 | 386 | 387 | // ------------------------ 388 | void changeYCursor(uint8_t num, int16_t yPos) { 389 | // ------------------------ 390 | if(yPos > 0) 391 | yPos = 0; 392 | 393 | if(yPos < -GRID_HEIGHT) 394 | yPos = -GRID_HEIGHT; 395 | 396 | yCursors[num] = yPos; 397 | saveParameter(PARAM_YCURSOR + num, yCursors[num]); 398 | repaintLabels(); 399 | } 400 | 401 | 402 | 403 | // ------------------------ 404 | void changeXCursor(int16_t xPos) { 405 | // ------------------------ 406 | if(xPos < 0) 407 | xPos = 0; 408 | 409 | if(xPos > (NUM_SAMPLES - GRID_WIDTH)) 410 | xPos = NUM_SAMPLES - GRID_WIDTH; 411 | 412 | xCursor = xPos; 413 | saveParameter(PARAM_XCURSOR, xCursor); 414 | repaintLabels(); 415 | } 416 | 417 | 418 | 419 | -------------------------------------------------------------------------------- /io.ino: -------------------------------------------------------------------------------- 1 | int16_t trigLevel = 0; 2 | 3 | 4 | // ------------------------ 5 | void initIO() { 6 | // ------------------------ 7 | // set pin I/O direction 8 | pinMode(BOARD_LED, OUTPUT); 9 | pinMode(AN_CH1, INPUT_ANALOG); 10 | pinMode(AN_CH2, INPUT_ANALOG); 11 | pinMode(DG_CH1, INPUT_PULLDOWN); 12 | pinMode(DG_CH2, INPUT_PULLDOWN); 13 | pinMode(TRIGGER_IN, INPUT_PULLUP); 14 | 15 | // calibrate the ADC channels at startup 16 | adc_calibrate(ADC1); 17 | adc_calibrate(ADC2); 18 | setADC(); 19 | 20 | // start 1KHz square wave 21 | pinMode(TEST_WAVE_PIN, PWM); 22 | Timer3.setPeriod(1000); 23 | pwmWrite(TEST_WAVE_PIN, 17850); 24 | DBG_PRINTLN("Test square wave started"); 25 | 26 | // input button and encoder 27 | pinMode(ENCODER_SW, INPUT_PULLUP); 28 | pinMode(ENCODER_A, INPUT_PULLUP); 29 | pinMode(ENCODER_B, INPUT_PULLUP); 30 | pinMode(BTN4, INPUT_PULLUP); 31 | 32 | attachInterrupt(ENCODER_SW, readESwitchISR, FALLING); 33 | attachInterrupt(BTN4, btn4ISR, CHANGE); 34 | 35 | #ifdef USE_ENCODER 36 | attachInterrupt(ENCODER_A, readEncoderISR, CHANGE); 37 | attachInterrupt(ENCODER_B, readEncoderISR, CHANGE); 38 | #else 39 | attachInterrupt(ENCODER_A, readASwitchISR, FALLING); 40 | attachInterrupt(ENCODER_B, readBSwitchISR, FALLING); 41 | #endif 42 | 43 | // init trigger level PWM 44 | // start 20KHz square wave on trigger out reference and negative v gen 45 | Timer4.setPeriod(50); 46 | pinMode(TRIGGER_LEVEL, PWM); 47 | pinMode(VGEN, PWM); 48 | pwmWrite(VGEN, 700); 49 | 50 | blinkLED(); 51 | 52 | // init scan timeout timer 53 | initScanTimeout(); 54 | } 55 | 56 | 57 | 58 | 59 | // ------------------------ 60 | void setADC() { 61 | // ------------------------ 62 | int pinMapADCin1 = PIN_MAP[AN_CH1].adc_channel; 63 | int pinMapADCin2 = PIN_MAP[AN_CH2].adc_channel; 64 | 65 | // opamp is low impedance, set fastest sampling 66 | adc_set_sample_rate(ADC1, ADC_SMPR_1_5); 67 | adc_set_sample_rate(ADC2, ADC_SMPR_1_5); 68 | 69 | adc_set_reg_seqlen(ADC1, 1); 70 | ADC1->regs->SQR3 = pinMapADCin1; 71 | // set ADC1 continuous mode 72 | ADC1->regs->CR2 |= ADC_CR2_CONT; 73 | // set ADC2 in regular simultaneous mode 74 | ADC1->regs->CR1 |= 0x60000; 75 | ADC1->regs->CR2 |= ADC_CR2_SWSTART; 76 | 77 | // set ADC2 continuous mode 78 | ADC2->regs->CR2 |= ADC_CR2_CONT; 79 | ADC2->regs->SQR3 = pinMapADCin2; 80 | } 81 | 82 | 83 | 84 | // ------------------------ 85 | void blinkLED() { 86 | // ------------------------ 87 | LED_ON; 88 | delay(10); 89 | LED_OFF; 90 | } 91 | 92 | 93 | 94 | // ------------------------ 95 | void initScanTimeout() { 96 | // ------------------------ 97 | Timer2.setChannel1Mode(TIMER_OUTPUTCOMPARE); 98 | Timer2.pause(); 99 | Timer2.setCompare1(1); 100 | Timer2.attachCompare1Interrupt(scanTimeoutISR); 101 | } 102 | 103 | 104 | 105 | // ------------------------ 106 | int16_t getTriggerLevel() { 107 | // ------------------------ 108 | return trigLevel; 109 | } 110 | 111 | 112 | 113 | // ------------------------ 114 | void setTriggerLevel(int16_t tLvl) { 115 | // ------------------------ 116 | // 600 = 20% duty 117 | // 1800 = 50% 118 | trigLevel = tLvl; 119 | pwmWrite(TRIGGER_LEVEL, 1800 + trigLevel); 120 | } 121 | 122 | 123 | 124 | 125 | 126 | // ------------------------ 127 | void readInpSwitches() { 128 | // ------------------------ 129 | static uint8_t couplingOld, rangeOld; 130 | 131 | uint16_t cpl, pos1, pos2; 132 | adc_reg_map *ADC1regs = ADC1->regs; 133 | 134 | // ADC1 and ADC2 are free running at max speed 135 | 136 | // change to switch 1 137 | ADC1regs->SQR3 = PIN_MAP[VSENSSEL2].adc_channel; 138 | delayMicroseconds(100); 139 | pos2 = (uint16_t) (ADC1regs->DR & ADC_DR_DATA); 140 | 141 | ADC1regs->SQR3 = PIN_MAP[VSENSSEL1].adc_channel; 142 | delayMicroseconds(100); 143 | pos1 = (uint16_t) (ADC1regs->DR & ADC_DR_DATA); 144 | 145 | ADC1regs->SQR3 = PIN_MAP[CPLSEL].adc_channel; 146 | delayMicroseconds(100); 147 | cpl = (uint16_t) (ADC1regs->DR & ADC_DR_DATA); 148 | 149 | if(cpl < 400) 150 | couplingPos = CPL_GND; 151 | else if(cpl < 2000) 152 | couplingPos = CPL_AC; 153 | else 154 | couplingPos = CPL_DC; 155 | 156 | if(pos1 < 400) 157 | rangePos = RNG_1V; 158 | else if(pos1 < 2000) 159 | rangePos = RNG_0_1V; 160 | else 161 | rangePos = RNG_10mV; 162 | 163 | if(pos2 < 400) 164 | rangePos -= 2; 165 | else if(pos2 < 2000) 166 | rangePos -= 1; 167 | else 168 | rangePos -= 0; 169 | 170 | // check if switch position changed from previous snap 171 | if(couplingPos != couplingOld) { 172 | couplingOld = couplingPos; 173 | repaintLabels(); 174 | } 175 | 176 | if(rangePos != rangeOld) { 177 | rangeOld = rangePos; 178 | repaintLabels(); 179 | } 180 | 181 | // read the negative voltage generator 182 | // *** 183 | 184 | // switch ADC1 back to capture channel 185 | ADC1regs->SQR3 = PIN_MAP[AN_CH1].adc_channel; 186 | delayMicroseconds(100); 187 | } 188 | 189 | -------------------------------------------------------------------------------- /pics/HardwareMod.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ardyesp/DLO-138/2989944fad5d5d9ca7170f02ec004d1d93d23a26/pics/HardwareMod.png -------------------------------------------------------------------------------- /pics/pic1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ardyesp/DLO-138/2989944fad5d5d9ca7170f02ec004d1d93d23a26/pics/pic1.png -------------------------------------------------------------------------------- /pics/pic2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ardyesp/DLO-138/2989944fad5d5d9ca7170f02ec004d1d93d23a26/pics/pic2.png -------------------------------------------------------------------------------- /pics/pic3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ardyesp/DLO-138/2989944fad5d5d9ca7170f02ec004d1d93d23a26/pics/pic3.png -------------------------------------------------------------------------------- /pics/pic4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ardyesp/DLO-138/2989944fad5d5d9ca7170f02ec004d1d93d23a26/pics/pic4.png -------------------------------------------------------------------------------- /src/TFTLib/Adafruit_TFTLCD_8bit_STM32.cpp: -------------------------------------------------------------------------------- 1 | // IMPORTANT: LIBRARY MUST BE SPECIFICALLY CONFIGURED FOR EITHER TFT SHIELD 2 | // OR BREAKOUT BOARD USAGE. SEE RELEVANT COMMENTS IN Adafruit_TFTLCD_8bit_STM32.h 3 | 4 | // Graphics library by ladyada/adafruit with init code from Rossum 5 | // MIT license 6 | 7 | // #include 8 | 9 | //#include "pins_arduino.h" 10 | //#include "wiring_private.h" 11 | #include "Adafruit_TFTLCD_8bit_STM32.h" 12 | //#include "pin_magic.h" 13 | 14 | #include "ili932x.h" 15 | #include "ili9341.h" 16 | #include "hx8347g.h" 17 | #include "hx8357x.h" 18 | 19 | 20 | // LCD controller chip identifiers 21 | #define ID_932X 0 22 | #define ID_7575 1 23 | #define ID_9341 2 24 | #define ID_HX8357D 3 25 | #define ID_UNKNOWN 0xFF 26 | 27 | uint32_t readReg32(uint8_t r); 28 | uint16_t readReg(uint8_t r); 29 | 30 | /*****************************************************************************/ 31 | // Constructor for shield (fixed LCD control lines) 32 | /*****************************************************************************/ 33 | Adafruit_TFTLCD_8bit_STM32 :: Adafruit_TFTLCD_8bit_STM32(void) 34 | : Adafruit_GFX(TFTWIDTH, TFTHEIGHT) 35 | { 36 | //Set command lines as output 37 | //Note: CRH and CRL are both 32 bits wide 38 | //Each pin is represented by 4 bits 0x3 (hex) sets that pin to O/P 39 | // TFT_CNTRL->regs->CRL = (TFT_CNTRL->regs->CRL & 0xFFFF0000) | 0x00003333; 40 | 41 | pinMode(TFT_RD, OUTPUT); 42 | pinMode(TFT_WR, OUTPUT); 43 | pinMode(TFT_RS, OUTPUT); 44 | pinMode(TFT_CS, OUTPUT); 45 | 46 | CS_IDLE; // Set all control bits to HIGH (idle) 47 | CD_DATA; // Signals are ACTIVE LOW 48 | WR_IDLE; 49 | RD_IDLE; 50 | /**/ 51 | //reset(); 52 | //set up 8 bit parallel port to write mode. 53 | setWriteDir(); 54 | } 55 | 56 | /*****************************************************************************/ 57 | void Adafruit_TFTLCD_8bit_STM32::begin(uint16_t id) 58 | { 59 | uint8_t i = 0; 60 | 61 | reset(); 62 | 63 | if ((id == 0x9325) || (id == 0x9328)) { 64 | 65 | driver = ID_932X; 66 | ili932x_begin(); 67 | 68 | } else if (id == 0x9341) { 69 | 70 | driver = ID_9341; 71 | ili9341_begin(); 72 | 73 | } else if (id == 0x8357) { 74 | // HX8357D 75 | driver = ID_HX8357D; 76 | hx8357x_begin(); 77 | 78 | } else if(id == 0x7575) { 79 | 80 | driver = ID_7575; 81 | hx8347g_begin(); 82 | 83 | } else { 84 | driver = ID_UNKNOWN; 85 | } 86 | } 87 | 88 | /*****************************************************************************/ 89 | void Adafruit_TFTLCD_8bit_STM32::reset(void) 90 | { 91 | // toggle RST low to reset 92 | if (TFT_RST > 0) { 93 | pinMode(TFT_RST, OUTPUT); 94 | digitalWrite(TFT_RST, HIGH); 95 | delay(10); 96 | digitalWrite(TFT_RST, LOW); 97 | delay(10); 98 | digitalWrite(TFT_RST, HIGH); 99 | delay(100); 100 | } 101 | /* 102 | // Data transfer sync 103 | CS_ACTIVE; 104 | CD_COMMAND; 105 | write8(0x00); write8(0x00); 106 | for(uint8_t i=0; i<3; i++) WR_STROBE; // Three extra 0x00s 107 | CS_IDLE; 108 | */ 109 | //WriteCmdData(0xB0, 0x0000); //R61520 needs this to read ID 110 | } 111 | 112 | /*****************************************************************************/ 113 | // Sets the LCD address window (and address counter, on 932X). 114 | // Relevant to rect/screen fills and H/V lines. Input coordinates are 115 | // assumed pre-sorted (e.g. x2 >= x1). 116 | /*****************************************************************************/ 117 | void Adafruit_TFTLCD_8bit_STM32::setAddrWindow(int x1, int y1, int x2, int y2) 118 | { 119 | if(driver == ID_932X) { 120 | ili932x_setAddrWindow(x1, y1, x2, y2); 121 | } else if(driver == ID_7575) { 122 | hx8347g_setAddrWindow(x1, y1, x2, y2); 123 | } else if ((driver == ID_9341) || (driver == ID_HX8357D)){ 124 | ili9341_setAddrWindow(x1, y1, x2, y2); 125 | } 126 | } 127 | 128 | /*****************************************************************************/ 129 | // Fast block fill operation for fillScreen, fillRect, H/V line, etc. 130 | // Requires setAddrWindow() has previously been called to set the fill 131 | // bounds. 'len' is inclusive, MUST be >= 1. 132 | /*****************************************************************************/ 133 | void Adafruit_TFTLCD_8bit_STM32::flood(uint16_t color, uint32_t len) 134 | { 135 | uint16_t blocks; 136 | uint8_t i, hi = color >> 8, 137 | lo = color; 138 | 139 | CS_ACTIVE; 140 | CD_COMMAND; 141 | if (driver == ID_9341) { 142 | write8(ILI9341_MEMORYWRITE); 143 | } else if (driver == ID_932X) { 144 | write8(0x00); // High command byte must be 0 145 | write8(ILI932X_RW_GRAM); 146 | } else if (driver == ID_HX8357D) { 147 | write8(HX8357_RAMWR); 148 | } else { 149 | write8(0x22); // Write data to GRAM 150 | } 151 | 152 | // Write first pixel normally, decrement counter by 1 153 | CD_DATA; 154 | write8(hi); 155 | write8(lo); 156 | len--; 157 | 158 | blocks = (uint16_t)(len / 64); // 64 pixels/block 159 | if(hi == lo) { 160 | // High and low bytes are identical. Leave prior data 161 | // on the port(s) and just toggle the write strobe. 162 | while(blocks--) { 163 | i = 16; // 64 pixels/block / 4 pixels/pass 164 | do { 165 | WR_STROBE; WR_STROBE; WR_STROBE; WR_STROBE; // 2 bytes/pixel 166 | WR_STROBE; WR_STROBE; WR_STROBE; WR_STROBE; // x 4 pixels 167 | } while(--i); 168 | } 169 | // Fill any remaining pixels (1 to 64) 170 | for(i = (uint8_t)len & 63; i--; ) { 171 | WR_STROBE; 172 | WR_STROBE; 173 | } 174 | } else { 175 | while(blocks--) { 176 | i = 16; // 64 pixels/block / 4 pixels/pass 177 | do { 178 | write8(hi); write8(lo); write8(hi); write8(lo); 179 | write8(hi); write8(lo); write8(hi); write8(lo); 180 | } while(--i); 181 | } 182 | for(i = (uint8_t)len & 63; i--; ) { 183 | write8(hi); 184 | write8(lo); 185 | } 186 | } 187 | CS_IDLE; 188 | } 189 | 190 | /*****************************************************************************/ 191 | void Adafruit_TFTLCD_8bit_STM32::drawFastHLine(int16_t x, int16_t y, int16_t length, uint16_t color) 192 | { 193 | int16_t x2; 194 | 195 | // Initial off-screen clipping 196 | if((length <= 0 ) || 197 | (y < 0 ) || ( y >= TFTHEIGHT) || 198 | (x >= TFTWIDTH) || ((x2 = (x+length-1)) < 0 )) return; 199 | 200 | if(x < 0) { // Clip left 201 | length += x; 202 | x = 0; 203 | } 204 | if(x2 >= TFTWIDTH) { // Clip right 205 | x2 = TFTWIDTH - 1; 206 | length = x2 - x + 1; 207 | } 208 | 209 | setAddrWindow(x, y, x2, y); 210 | flood(color, length); 211 | if(driver == ID_932X) setAddrWindow(0, 0, TFTWIDTH - 1, TFTHEIGHT - 1); 212 | else hx8347g_setLR(); 213 | } 214 | 215 | /*****************************************************************************/ 216 | void Adafruit_TFTLCD_8bit_STM32::drawFastVLine(int16_t x, int16_t y, int16_t length, uint16_t color) 217 | { 218 | int16_t y2; 219 | 220 | // Initial off-screen clipping 221 | if((length <= 0 ) || 222 | (x < 0 ) || ( x >= TFTWIDTH) || 223 | (y >= TFTHEIGHT) || ((y2 = (y+length-1)) < 0 )) return; 224 | if(y < 0) { // Clip top 225 | length += y; 226 | y = 0; 227 | } 228 | if(y2 >= TFTHEIGHT) { // Clip bottom 229 | y2 = TFTHEIGHT - 1; 230 | length = y2 - y + 1; 231 | } 232 | 233 | setAddrWindow(x, y, x, y2); 234 | flood(color, length); 235 | if(driver == ID_932X) setAddrWindow(0, 0, TFTWIDTH - 1, TFTHEIGHT - 1); 236 | else hx8347g_setLR(); 237 | } 238 | 239 | /*****************************************************************************/ 240 | void Adafruit_TFTLCD_8bit_STM32::fillRect(int16_t x1, int16_t y1, int16_t w, int16_t h, uint16_t fillcolor) 241 | { 242 | int16_t x2, y2; 243 | 244 | // Initial off-screen clipping 245 | if( (w <= 0 ) || (h <= 0 ) || 246 | (x1 >= TFTWIDTH) || (y1 >= TFTHEIGHT) || 247 | ((x2 = x1+w-1) < 0 ) || ((y2 = y1+h-1) < 0 )) return; 248 | if(x1 < 0) { // Clip left 249 | w += x1; 250 | x1 = 0; 251 | } 252 | if(y1 < 0) { // Clip top 253 | h += y1; 254 | y1 = 0; 255 | } 256 | if(x2 >= TFTWIDTH) { // Clip right 257 | x2 = TFTWIDTH - 1; 258 | w = x2 - x1 + 1; 259 | } 260 | if(y2 >= TFTHEIGHT) { // Clip bottom 261 | y2 = TFTHEIGHT - 1; 262 | h = y2 - y1 + 1; 263 | } 264 | 265 | setAddrWindow(x1, y1, x2, y2); 266 | flood(fillcolor, (uint32_t)w * (uint32_t)h); 267 | if(driver == ID_932X) setAddrWindow(0, 0, TFTWIDTH - 1, TFTHEIGHT - 1); 268 | else hx8347g_setLR(); 269 | } 270 | 271 | /*****************************************************************************/ 272 | void Adafruit_TFTLCD_8bit_STM32::fillScreen(uint16_t color) 273 | { 274 | if(driver == ID_932X) { 275 | // For the 932X, a full-screen address window is already the default 276 | // state, just need to set the address pointer to the top-left corner. 277 | // Although we could fill in any direction, the code uses the current 278 | // screen rotation because some users find it disconcerting when a 279 | // fill does not occur top-to-bottom. 280 | ili932x_fillScreen(color); 281 | } else if ((driver == ID_9341) || (driver == ID_7575) || (driver == ID_HX8357D)) { 282 | // For these, there is no settable address pointer, instead the 283 | // address window must be set for each drawing operation. However, 284 | // this display takes rotation into account for the parameters, no 285 | // need to do extra rotation math here. 286 | setAddrWindow(0, 0, TFTWIDTH - 1, TFTHEIGHT - 1); 287 | } 288 | flood(color, (long)TFTWIDTH * (long)TFTHEIGHT); 289 | } 290 | 291 | /*****************************************************************************/ 292 | void Adafruit_TFTLCD_8bit_STM32::drawPixel(int16_t x, int16_t y, uint16_t color) 293 | { 294 | // Clip 295 | if((x < 0) || (y < 0) || (x >= TFTWIDTH) || (y >= TFTHEIGHT)) return; 296 | 297 | if(driver == ID_932X) { 298 | 299 | ili932x_drawPixel(x, y, color); 300 | 301 | } else if(driver == ID_7575) { 302 | 303 | uint8_t hi, lo; 304 | switch(rotation) { 305 | default: lo = 0 ; break; 306 | case 1 : lo = 0x60; break; 307 | case 2 : lo = 0xc0; break; 308 | case 3 : lo = 0xa0; break; 309 | } 310 | writeRegister8( HX8347G_MEMACCESS , lo); 311 | // Only upper-left is set -- bottom-right is full screen default 312 | writeRegisterPair(HX8347G_COLADDRSTART_HI, HX8347G_COLADDRSTART_LO, x); 313 | writeRegisterPair(HX8347G_ROWADDRSTART_HI, HX8347G_ROWADDRSTART_LO, y); 314 | hi = color >> 8; lo = color; 315 | CD_COMMAND; write8(0x22); CD_DATA; write8(hi); write8(lo); 316 | 317 | } else if ((driver == ID_9341) || (driver == ID_HX8357D)) { 318 | setAddrWindow(x, y, TFTWIDTH-1, TFTHEIGHT-1); 319 | writeRegister16(0x2C, color); 320 | } 321 | } 322 | 323 | /*****************************************************************************/ 324 | // Issues 'raw' an array of 16-bit color values to the LCD; used 325 | // externally by BMP examples. Assumes that setWindowAddr() has 326 | // previously been set to define the bounds. Max 255 pixels at 327 | // a time (BMP examples read in small chunks due to limited RAM). 328 | /*****************************************************************************/ 329 | void Adafruit_TFTLCD_8bit_STM32::pushColors(uint16_t *data, uint8_t len, boolean first) 330 | { 331 | uint16_t color; 332 | uint8_t hi, lo; 333 | CS_ACTIVE; 334 | if(first == true) { // Issue GRAM write command only on first call 335 | CD_COMMAND; 336 | if(driver == ID_932X) write8(0x00); 337 | if ((driver == ID_9341) || (driver == ID_HX8357D)){ 338 | write8(0x2C); 339 | } else { 340 | write8(0x22); 341 | } 342 | } 343 | CD_DATA; 344 | while(len--) { 345 | color = *data++; 346 | hi = color >> 8; // Don't simplify or merge these 347 | lo = color; // lines, there's macro shenanigans 348 | write8(hi); // going on. 349 | write8(lo); 350 | } 351 | CS_IDLE; 352 | } 353 | 354 | /*****************************************************************************/ 355 | // Pass 8-bit (each) R,G,B, get back 16-bit packed color 356 | /*****************************************************************************/ 357 | uint16_t Adafruit_TFTLCD_8bit_STM32::color565(uint8_t r, uint8_t g, uint8_t b) 358 | { 359 | return ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3); 360 | } 361 | 362 | 363 | /*****************************************************************************/ 364 | void Adafruit_TFTLCD_8bit_STM32::setRotation(uint8_t x) 365 | { 366 | // Call parent rotation func first -- sets up rotation flags, etc. 367 | Adafruit_GFX::setRotation(x); 368 | // Then perform hardware-specific rotation operations... 369 | 370 | if (driver == ID_932X) { 371 | 372 | ili932x_setRotation(x); 373 | 374 | } else if (driver == ID_7575) { 375 | 376 | hx8347g_setRotation(x); 377 | 378 | } else if (driver == ID_9341) { 379 | // MEME, HX8357D uses same registers as 9341 but different values 380 | uint16_t t; 381 | 382 | switch (rotation) { 383 | case 2: 384 | t = ILI9341_MADCTL_MX | ILI9341_MADCTL_BGR; 385 | break; 386 | case 3: 387 | t = ILI9341_MADCTL_MV | ILI9341_MADCTL_BGR; 388 | break; 389 | case 0: 390 | t = ILI9341_MADCTL_MY | ILI9341_MADCTL_BGR; 391 | break; 392 | case 1: 393 | t = ILI9341_MADCTL_MX | ILI9341_MADCTL_MY | ILI9341_MADCTL_MV | ILI9341_MADCTL_BGR; 394 | break; 395 | } 396 | writeRegister8(ILI9341_MADCTL, t ); // MADCTL 397 | // For 9341, init default full-screen address window: 398 | setAddrWindow(0, 0, TFTWIDTH - 1, TFTHEIGHT - 1); // CS_IDLE happens here 399 | 400 | } else if (driver == ID_HX8357D) { 401 | // MEME, HX8357D uses same registers as 9341 but different values 402 | uint16_t t; 403 | 404 | switch (rotation) { 405 | case 2: 406 | t = HX8357B_MADCTL_RGB; 407 | break; 408 | case 3: 409 | t = HX8357B_MADCTL_MX | HX8357B_MADCTL_MV | HX8357B_MADCTL_RGB; 410 | break; 411 | case 0: 412 | t = HX8357B_MADCTL_MX | HX8357B_MADCTL_MY | HX8357B_MADCTL_RGB; 413 | break; 414 | case 1: 415 | t = HX8357B_MADCTL_MY | HX8357B_MADCTL_MV | HX8357B_MADCTL_RGB; 416 | break; 417 | } 418 | writeRegister8(ILI9341_MADCTL, t ); // MADCTL 419 | // For 8357, init default full-screen address window: 420 | setAddrWindow(0, 0, TFTWIDTH - 1, TFTHEIGHT - 1); // CS_IDLE happens here 421 | } 422 | } 423 | 424 | /*****************************************************************************/ 425 | uint8_t read8_(void) 426 | { 427 | RD_ACTIVE; 428 | delayMicroseconds(10); 429 | uint8_t temp = ( (TFT_DATA->regs->IDR>>TFT_DATA_NIBBLE) & 0x00FF); 430 | delayMicroseconds(1); 431 | RD_IDLE; 432 | delayMicroseconds(1); 433 | return temp; 434 | } 435 | 436 | // speed optimization 437 | static void writeCommand(uint8_t c) __attribute__((always_inline)); 438 | /*****************************************************************************/ 439 | static void writeCommand(uint8_t c) 440 | { 441 | CS_ACTIVE; 442 | CD_COMMAND; 443 | write8(0); 444 | write8(c); 445 | } 446 | 447 | /*****************************************************************************/ 448 | // Because this function is used infrequently, it configures the ports for 449 | // the read operation, reads the data, then restores the ports to the write 450 | // configuration. Write operations happen a LOT, so it's advantageous to 451 | // leave the ports in that state as a default. 452 | /*****************************************************************************/ 453 | uint16_t Adafruit_TFTLCD_8bit_STM32::readPixel(int16_t x, int16_t y) 454 | { 455 | if((x < 0) || (y < 0) || (x >= TFTWIDTH) || (y >= TFTHEIGHT)) return 0; 456 | 457 | if(driver == ID_932X) { 458 | 459 | return ili932x_readPixel(x, y); 460 | 461 | } else if(driver == ID_7575) { 462 | 463 | uint8_t r, g, b; 464 | writeRegisterPair(HX8347G_COLADDRSTART_HI, HX8347G_COLADDRSTART_LO, x); 465 | writeRegisterPair(HX8347G_ROWADDRSTART_HI, HX8347G_ROWADDRSTART_LO, y); 466 | writeCommand(0x22); // Read data from GRAM 467 | setReadDir(); // Set up LCD data port(s) for READ operations 468 | CD_DATA; 469 | read8(r); // First byte back is a dummy read 470 | read8(r); 471 | read8(g); 472 | read8(b); 473 | setWriteDir(); // Restore LCD data port(s) to WRITE configuration 474 | CS_IDLE; 475 | return (((uint16_t)r & B11111000) << 8) | 476 | (((uint16_t)g & B11111100) << 3) | 477 | ( b >> 3); 478 | } else return 0; 479 | } 480 | 481 | /*****************************************************************************/ 482 | uint16_t Adafruit_TFTLCD_8bit_STM32::readID(void) 483 | { 484 | /* 485 | for (uint8_t i=0; i<128; i++) { 486 | Serial.print("$"); Serial.print(i, HEX); 487 | Serial.print(" = 0x"); Serial.println(readReg(i), HEX); 488 | } 489 | */ 490 | /* 491 | Serial.println("!"); 492 | for (uint8_t i=0; i<4; i++) { 493 | Serial.print("$"); Serial.print(i, HEX); 494 | Serial.print(" = 0x"); Serial.println(readReg(i), HEX); 495 | } 496 | */ 497 | /**/ 498 | if (readReg32(0x04) == 0x8000) { // eh close enough 499 | // setc! 500 | writeRegister24(HX8357D_SETC, 0xFF8357); 501 | delay(300); 502 | //Serial.println(readReg(0xD0), HEX); 503 | if (readReg32(0xD0) == 0x990000) { 504 | return 0x8357; 505 | } 506 | } 507 | 508 | uint16_t id = readReg32(0xD3); 509 | if (id != 0x9341) { 510 | id = readReg(0); 511 | } 512 | //Serial.print("ID: "); Serial.println(id,HEX); 513 | return id; 514 | } 515 | 516 | /*****************************************************************************/ 517 | uint32_t readReg32(uint8_t r) 518 | { 519 | uint32_t id; 520 | uint8_t x; 521 | 522 | // try reading register #4 523 | writeCommand(r); 524 | setReadDir(); // Set up LCD data port(s) for READ operations 525 | CD_DATA; 526 | delayMicroseconds(50); 527 | read8(x); 528 | id = x; // Do not merge or otherwise simplify 529 | id <<= 8; // these lines. It's an unfortunate 530 | read8(x); 531 | id |= x; // shenanigans that are going on. 532 | id <<= 8; // these lines. It's an unfortunate 533 | read8(x); 534 | id |= x; // shenanigans that are going on. 535 | id <<= 8; // these lines. It's an unfortunate 536 | read8(x); 537 | id |= x; // shenanigans that are going on. 538 | CS_IDLE; 539 | setWriteDir(); // Restore LCD data port(s) to WRITE configuration 540 | return id; 541 | } 542 | /*****************************************************************************/ 543 | uint16_t readReg(uint8_t r) 544 | { 545 | uint16_t id; 546 | uint8_t x; 547 | 548 | writeCommand(r); 549 | setReadDir(); // Set up LCD data port(s) for READ operations 550 | CD_DATA; 551 | delayMicroseconds(50); 552 | read8(x); 553 | id = x; // Do not merge or otherwise simplify 554 | id <<= 8; // these lines. It's an unfortunate 555 | read8(x); 556 | id |= x; // shenanigans that are going on. 557 | CS_IDLE; 558 | setWriteDir(); // Restore LCD data port(s) to WRITE configuration 559 | 560 | //Serial.print("Read $"); Serial.print(r, HEX); 561 | //Serial.print(":\t0x"); Serial.println(id, HEX); 562 | return id; 563 | } 564 | 565 | /*****************************************************************************/ 566 | void writeRegister8(uint8_t a, uint8_t d) 567 | { 568 | writeCommand(a); 569 | CD_DATA; 570 | write8(d); 571 | CS_IDLE; 572 | } 573 | 574 | /*****************************************************************************/ 575 | void writeRegister16(uint16_t a, uint16_t d) 576 | { 577 | writeCommand(a); 578 | CD_DATA; 579 | write8(d>>8); 580 | write8(d); 581 | CS_IDLE; 582 | } 583 | 584 | /*****************************************************************************/ 585 | void writeRegisterPair(uint8_t aH, uint8_t aL, uint16_t d) 586 | { 587 | writeRegister8(aH, d>>8); 588 | writeRegister8(aL, d); 589 | } 590 | 591 | /*****************************************************************************/ 592 | void writeRegister24(uint8_t r, uint32_t d) 593 | { 594 | writeCommand(r); // includes CS_ACTIVE 595 | CD_DATA; 596 | write8(d >> 16); 597 | write8(d >> 8); 598 | write8(d); 599 | CS_IDLE; 600 | } 601 | 602 | /*****************************************************************************/ 603 | void writeRegister32(uint8_t r, uint32_t d) 604 | { 605 | writeCommand(r); 606 | CD_DATA; 607 | write8(d >> 24); 608 | write8(d >> 16); 609 | write8(d >> 8); 610 | write8(d); 611 | CS_IDLE; 612 | } 613 | -------------------------------------------------------------------------------- /src/TFTLib/Adafruit_TFTLCD_8bit_STM32.h: -------------------------------------------------------------------------------- 1 | // IMPORTANT: SEE COMMENTS @ LINE 15 REGARDING SHIELD VS BREAKOUT BOARD USAGE. 2 | 3 | // Graphics library by ladyada/adafruit with init code from Rossum 4 | // MIT license 5 | 6 | #ifndef _ADAFRUIT_TFTLCD_8BIT_STM32_H_ 7 | #define _ADAFRUIT_TFTLCD_8BIT_STM32_H_ 8 | 9 | #define PROGMEM 10 | #define pgm_read_byte(addr) (*(const unsigned char *)(addr)) 11 | #define pgm_read_word(addr) (*(const unsigned short *)(addr)) 12 | 13 | #if ARDUINO >= 100 14 | #include "Arduino.h" 15 | #else 16 | #include "WProgram.h" 17 | #endif 18 | 19 | #include 20 | 21 | /*****************************************************************************/ 22 | #define TFTWIDTH 320 23 | #define TFTHEIGHT 320 // HACK: Should be 240, but text print gets clipped at 240 width in landscape mode 24 | 25 | // Initialization command tables for different LCD controllers 26 | #define TFTLCD_DELAY 0xFF 27 | 28 | // For compatibility with sketches written for older versions of library. 29 | // Color function name was changed to 'color565' for parity with 2.2" LCD 30 | // library. 31 | #define Color565 color565 32 | 33 | /*****************************************************************************/ 34 | // Define pins and Output Data Registers 35 | /*****************************************************************************/ 36 | 37 | #define TFT_DATA GPIOB 38 | // Port data bits D0..D7: 39 | // enable only one from below lines corresponding to your HW setup: 40 | #define TFT_DATA_NIBBLE 0 // take the lower 8 bits: 0..7 41 | //#define TFT_DATA_NIBBLE 8 // take the higher 8 bits: 8..15 42 | 43 | //Control pins |RD |WR |RS |CS |RST| 44 | #define TFT_RD PB10 45 | #define TFT_WR PC15 46 | #define TFT_RS PC14 47 | #define TFT_CS PC13 48 | #define TFT_RST PB11 49 | 50 | #if 0 51 | // use old definition, standard bit toggling, low speed 52 | #define RD_ACTIVE digitalWrite(TFT_RD, LOW) 53 | #define RD_IDLE digitalWrite(TFT_RD, HIGH) 54 | #define WR_ACTIVE digitalWrite(TFT_WR, LOW) 55 | #define WR_IDLE digitalWrite(TFT_WR, HIGH) 56 | #define CD_COMMAND digitalWrite(TFT_RS, LOW) 57 | #define CD_DATA digitalWrite(TFT_RS, HIGH) 58 | #define CS_ACTIVE digitalWrite(TFT_CS, LOW) 59 | #define CS_IDLE digitalWrite(TFT_CS, HIGH) 60 | #else 61 | #define TFT_RD_MASK BIT10 62 | #define TFT_WR_MASK BIT15 63 | #define TFT_RS_MASK BIT14 64 | #define TFT_CS_MASK BIT13 65 | 66 | // use fast bit toggling, very fast speed! 67 | #define RD_ACTIVE { GPIOB->regs->BRR = TFT_RD_MASK; } 68 | #define RD_IDLE { GPIOB->regs->BSRR = TFT_RD_MASK; } 69 | #define WR_ACTIVE { GPIOC->regs->BRR = TFT_WR_MASK; } 70 | #define WR_IDLE { GPIOC->regs->BSRR = TFT_WR_MASK; } 71 | #define CD_COMMAND { GPIOC->regs->BRR = TFT_RS_MASK; } 72 | #define CD_DATA { GPIOC->regs->BSRR = TFT_RS_MASK; } 73 | #define CS_ACTIVE { GPIOC->regs->BRR = TFT_CS_MASK; } 74 | #define CS_IDLE { GPIOC->regs->BSRR = TFT_CS_MASK; } 75 | #endif 76 | 77 | #define WR_STROBE { WR_ACTIVE; WR_IDLE; } 78 | 79 | //Set pins to the 8 bit number 80 | #define write8(c) { TFT_DATA->regs->BSRR = (((c^0xFF)<<16) | (c))<0 89 | #define setWriteDir() ( TFT_DATA->regs->CRH = 0x33333333 ) // set the lower 8 bits as output 90 | #else 91 | #define setWriteDir() ( TFT_DATA->regs->CRL = 0x33333333 ) // set the lower 8 bits as output 92 | #endif 93 | 94 | // set the pins to input mode 95 | // not required to mask and assign, because all pins of bus are set together 96 | // 8 in hex is 0b1000, which means input, same as pinmode() 97 | #if TFT_DATA_NIBBLE>0 98 | #define setReadDir() ( TFT_DATA->regs->CRH = 0x88888888 ) // set the upper 8 bits as input 99 | #else 100 | #define setReadDir() ( TFT_DATA->regs->CRL = 0x88888888 ) // set the lower 8 bits as input 101 | #endif 102 | 103 | 104 | /*****************************************************************************/ 105 | 106 | #define swap(a, b) { int16_t t = a; a = b; b = t; } 107 | 108 | 109 | // Color definitions 110 | #define ILI9341_BLACK 0x0000 /* 0, 0, 0 */ 111 | #define ILI9341_NAVY 0x000F /* 0, 0, 128 */ 112 | #define ILI9341_DARKGREEN 0x03E0 /* 0, 128, 0 */ 113 | #define ILI9341_DARKCYAN 0x03EF /* 0, 128, 128 */ 114 | #define ILI9341_MAROON 0x7800 /* 128, 0, 0 */ 115 | #define ILI9341_PURPLE 0x780F /* 128, 0, 128 */ 116 | #define ILI9341_OLIVE 0x7BE0 /* 128, 128, 0 */ 117 | #define ILI9341_LIGHTGREY 0xC618 /* 192, 192, 192 */ 118 | #define ILI9341_DARKGREY 0x7BEF /* 128, 128, 128 */ 119 | #define ILI9341_BLUE 0x001F /* 0, 0, 255 */ 120 | #define ILI9341_GREEN 0x07E0 /* 0, 255, 0 */ 121 | #define ILI9341_CYAN 0x07FF /* 0, 255, 255 */ 122 | #define ILI9341_RED 0xF800 /* 255, 0, 0 */ 123 | #define ILI9341_MAGENTA 0xF81F /* 255, 0, 255 */ 124 | #define ILI9341_YELLOW 0xFFE0 /* 255, 255, 0 */ 125 | #define ILI9341_WHITE 0xFFFF /* 255, 255, 255 */ 126 | #define ILI9341_ORANGE 0xFD20 /* 255, 165, 0 */ 127 | #define ILI9341_GREENYELLOW 0xAFE5 /* 173, 255, 47 */ 128 | #define ILI9341_PINK 0xF81F 129 | 130 | 131 | /*****************************************************************************/ 132 | // **** IF USING THE LCD BREAKOUT BOARD, COMMENT OUT THIS NEXT LINE. **** 133 | // **** IF USING THE LCD SHIELD, LEAVE THE LINE ENABLED: **** 134 | 135 | //#define USE_ADAFRUIT_SHIELD_PINOUT 1 136 | 137 | /*****************************************************************************/ 138 | class Adafruit_TFTLCD_8bit_STM32 : public Adafruit_GFX { 139 | 140 | public: 141 | 142 | //Adafruit_TFTLCD_8bit_STM32(uint8_t cs, uint8_t cd, uint8_t wr, uint8_t rd, uint8_t rst); 143 | Adafruit_TFTLCD_8bit_STM32(void); 144 | 145 | void begin(uint16_t id = 0x9325); 146 | void drawPixel(int16_t x, int16_t y, uint16_t color); 147 | void drawFastHLine(int16_t x0, int16_t y0, int16_t w, uint16_t color); 148 | void drawFastVLine(int16_t x0, int16_t y0, int16_t h, uint16_t color); 149 | void fillRect(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t c); 150 | void fillScreen(uint16_t color); 151 | void reset(void); 152 | void setRegisters8(uint8_t *ptr, uint8_t n); 153 | void setRegisters16(uint16_t *ptr, uint8_t n); 154 | void setRotation(uint8_t x); 155 | // These methods are public in order for BMP examples to work: 156 | void setAddrWindow(int x1, int y1, int x2, int y2); 157 | void pushColors(uint16_t *data, uint8_t len, boolean first); 158 | 159 | uint16_t color565(uint8_t r, uint8_t g, uint8_t b), 160 | readPixel(int16_t x, int16_t y), 161 | readID(void); 162 | 163 | private: 164 | 165 | void init(), 166 | // These items may have previously been defined as macros 167 | // in pin_magic.h. If not, function versions are declared: 168 | //setLR(void), 169 | flood(uint16_t color, uint32_t len); 170 | uint8_t driver; 171 | 172 | }; 173 | 174 | extern uint16_t readReg(uint8_t r); 175 | //extern void writeCommand(uint16_t c); 176 | extern void writeRegister8(uint8_t a, uint8_t d); 177 | extern void writeRegister16(uint16_t a, uint16_t d); 178 | extern void writeRegister24(uint8_t a, uint32_t d); 179 | extern void writeRegister32(uint8_t a, uint32_t d); 180 | extern void writeRegisterPair(uint8_t aH, uint8_t aL, uint16_t d); 181 | 182 | 183 | #endif 184 | -------------------------------------------------------------------------------- /src/TFTLib/Readme.md: -------------------------------------------------------------------------------- 1 | Fast TFT library taken from: 2 | 3 | https://github.com/stevstrong/Adafruit_TFTLCD_8bit_STM32 4 | 5 | -------------------------------------------------------------------------------- /src/TFTLib/hx8347g.cpp: -------------------------------------------------------------------------------- 1 | // IMPORTANT: LIBRARY MUST BE SPECIFICALLY CONFIGURED FOR EITHER TFT SHIELD 2 | // OR BREAKOUT BOARD USAGE. SEE RELEVANT COMMENTS IN Adafruit_TFTLCD.h 3 | 4 | // Graphics library by ladyada/adafruit with init code from Rossum 5 | // MIT license 6 | 7 | 8 | #include "Adafruit_TFTLCD_8bit_STM32.h" 9 | //#include "pin_magic.h" 10 | 11 | #include "hx8347g.h" 12 | 13 | /*****************************************************************************/ 14 | static const uint8_t HX8347G_regValues[] PROGMEM = { 15 | 0x2E , 0x89, 16 | 0x29 , 0x8F, 17 | 0x2B , 0x02, 18 | 0xE2 , 0x00, 19 | 0xE4 , 0x01, 20 | 0xE5 , 0x10, 21 | 0xE6 , 0x01, 22 | 0xE7 , 0x10, 23 | 0xE8 , 0x70, 24 | 0xF2 , 0x00, 25 | 0xEA , 0x00, 26 | 0xEB , 0x20, 27 | 0xEC , 0x3C, 28 | 0xED , 0xC8, 29 | 0xE9 , 0x38, 30 | 0xF1 , 0x01, 31 | 32 | // skip gamma, do later 33 | 34 | 0x1B , 0x1A, 35 | 0x1A , 0x02, 36 | 0x24 , 0x61, 37 | 0x25 , 0x5C, 38 | 39 | 0x18 , 0x36, 40 | 0x19 , 0x01, 41 | 0x1F , 0x88, 42 | TFTLCD_DELAY , 5 , // delay 5 ms 43 | 0x1F , 0x80, 44 | TFTLCD_DELAY , 5 , 45 | 0x1F , 0x90, 46 | TFTLCD_DELAY , 5 , 47 | 0x1F , 0xD4, 48 | TFTLCD_DELAY , 5 , 49 | 0x17 , 0x05, 50 | 51 | 0x36 , 0x09, 52 | 0x28 , 0x38, 53 | TFTLCD_DELAY , 40 , 54 | 0x28 , 0x3C, 55 | 56 | 0x02 , 0x00, 57 | 0x03 , 0x00, 58 | 0x04 , 0x00, 59 | 0x05 , 0xEF, 60 | 0x06 , 0x00, 61 | 0x07 , 0x00, 62 | 0x08 , 0x01, 63 | 0x09 , 0x3F 64 | }; 65 | 66 | /*****************************************************************************/ 67 | // Unlike the 932X drivers that set the address window to the full screen 68 | // by default (using the address counter for drawPixel operations), the 69 | // 7575 needs the address window set on all graphics operations. In order 70 | // to save a few register writes on each pixel drawn, the lower-right 71 | // corner of the address window is reset after most fill operations, so 72 | // that drawPixel only needs to change the upper left each time. 73 | /*****************************************************************************/ 74 | void hx8347g_setLR(void) 75 | { 76 | writeRegisterPair(HX8347G_COLADDREND_HI, HX8347G_COLADDREND_LO, TFTWIDTH - 1); 77 | writeRegisterPair(HX8347G_ROWADDREND_HI, HX8347G_ROWADDREND_LO, TFTHEIGHT - 1); 78 | } 79 | 80 | /*****************************************************************************/ 81 | void hx8347g_begin(void) 82 | { 83 | uint8_t a, d, i = 0; 84 | CS_ACTIVE; 85 | while(i < sizeof(HX8347G_regValues)) { 86 | a = pgm_read_byte(&HX8347G_regValues[i++]); 87 | d = pgm_read_byte(&HX8347G_regValues[i++]); 88 | if(a == TFTLCD_DELAY) delay(d); 89 | else writeRegister8(a, d); 90 | } 91 | hx8347g_setRotation(0); 92 | hx8347g_setLR(); // Lower-right corner of address window 93 | } 94 | 95 | /*****************************************************************************/ 96 | // Sets the LCD address window (and address counter, on 932X). 97 | // Relevant to rect/screen fills and H/V lines. Input coordinates are 98 | // assumed pre-sorted (e.g. x2 >= x1). 99 | /*****************************************************************************/ 100 | void hx8347g_setAddrWindow(int x1, int y1, int x2, int y2) 101 | { 102 | writeRegisterPair(HX8347G_COLADDRSTART_HI, HX8347G_COLADDRSTART_LO, x1); 103 | writeRegisterPair(HX8347G_ROWADDRSTART_HI, HX8347G_ROWADDRSTART_LO, y1); 104 | writeRegisterPair(HX8347G_COLADDREND_HI , HX8347G_COLADDREND_LO , x2); 105 | writeRegisterPair(HX8347G_ROWADDREND_HI , HX8347G_ROWADDREND_LO , y2); 106 | } 107 | 108 | /*****************************************************************************/ 109 | void hx8347g_fillScreen(uint16_t color) 110 | { 111 | } 112 | 113 | /*****************************************************************************/ 114 | void hx8347g_drawPixel(int16_t x, int16_t y, uint16_t color) 115 | { 116 | } 117 | 118 | /*****************************************************************************/ 119 | void hx8347g_setRotation(uint8_t x) 120 | { 121 | uint8_t t; 122 | switch(x) { 123 | default: t = 0 ; break; 124 | case 1 : t = 0x60; break; 125 | case 2 : t = 0xc0; break; 126 | case 3 : t = 0xa0; break; 127 | } 128 | writeRegister8(HX8347G_MEMACCESS, t); 129 | // 7575 has to set the address window on most drawing operations. 130 | // drawPixel() cheats by setting only the top left...by default, 131 | // the lower right is always reset to the corner. 132 | hx8347g_setLR(); // CS_IDLE happens here 133 | } 134 | 135 | /*****************************************************************************/ 136 | uint16_t hx8347g_readPixel(int16_t x, int16_t y) 137 | { 138 | } 139 | -------------------------------------------------------------------------------- /src/TFTLib/hx8347g.h: -------------------------------------------------------------------------------- 1 | #ifndef _HX8347G_H_ 2 | #define _HX8347G_H_ 3 | 4 | 5 | // Register names 6 | #define HX8347G_COLADDRSTART_HI 0x02 7 | #define HX8347G_COLADDRSTART_LO 0x03 8 | #define HX8347G_COLADDREND_HI 0x04 9 | #define HX8347G_COLADDREND_LO 0x05 10 | #define HX8347G_ROWADDRSTART_HI 0x06 11 | #define HX8347G_ROWADDRSTART_LO 0x07 12 | #define HX8347G_ROWADDREND_HI 0x08 13 | #define HX8347G_ROWADDREND_LO 0x09 14 | #define HX8347G_MEMACCESS 0x16 15 | 16 | 17 | /*****************************************************************************/ 18 | extern void hx8347g_setLR(void); 19 | extern void hx8347g_begin(void); 20 | extern void hx8347g_setAddrWindow(int x1, int y1, int x2, int y2); 21 | extern void hx8347g_fillScreen(uint16_t color); 22 | extern void hx8347g_drawPixel(int16_t x, int16_t y, uint16_t color); 23 | extern void hx8347g_setRotation(uint8_t x); 24 | extern uint16_t hx8347g_readPixel(int16_t x, int16_t y); 25 | 26 | 27 | #endif 28 | -------------------------------------------------------------------------------- /src/TFTLib/hx8357x.cpp: -------------------------------------------------------------------------------- 1 | // IMPORTANT: LIBRARY MUST BE SPECIFICALLY CONFIGURED FOR EITHER TFT SHIELD 2 | // OR BREAKOUT BOARD USAGE. SEE RELEVANT COMMENTS IN Adafruit_TFTLCD.h 3 | 4 | // Graphics library by ladyada/adafruit with init code from Rossum 5 | // MIT license 6 | 7 | 8 | #include "Adafruit_TFTLCD_8bit_STM32.h" 9 | //#include "pin_magic.h" 10 | 11 | #include "hx8357x.h" 12 | 13 | /*****************************************************************************/ 14 | static const uint8_t HX8357D_regValues[] PROGMEM = { 15 | HX8357_SWRESET, 0, 16 | HX8357D_SETC, 3, 0xFF, 0x83, 0x57, 17 | TFTLCD_DELAY, 250, 18 | HX8357_SETRGB, 4, 0x00, 0x00, 0x06, 0x06, 19 | HX8357D_SETCOM, 1, 0x25, // -1.52V 20 | HX8357_SETOSC, 1, 0x68, // Normal mode 70Hz, Idle mode 55 Hz 21 | HX8357_SETPANEL, 1, 0x05, // BGR, Gate direction swapped 22 | HX8357_SETPWR1, 6, 0x00, 0x15, 0x1C, 0x1C, 0x83, 0xAA, 23 | HX8357D_SETSTBA, 6, 0x50, 0x50, 0x01, 0x3C, 0x1E, 0x08, 24 | // MEME GAMMA HERE 25 | HX8357D_SETCYC, 7, 0x02, 0x40, 0x00, 0x2A, 0x2A, 0x0D, 0x78, 26 | HX8357_COLMOD, 1, 0x55, 27 | HX8357_MADCTL, 1, 0xC0, 28 | HX8357_TEON, 1, 0x00, 29 | HX8357_TEARLINE, 2, 0x00, 0x02, 30 | HX8357_SLPOUT, 0, 31 | TFTLCD_DELAY, 150, 32 | HX8357_DISPON, 0, 33 | TFTLCD_DELAY, 50, 34 | }; 35 | 36 | /*****************************************************************************/ 37 | void hx8357x_begin(void) 38 | { 39 | uint8_t i = 0; 40 | //CS_ACTIVE; 41 | while(i < sizeof(HX8357D_regValues)) { 42 | uint8_t r = pgm_read_byte(&HX8357D_regValues[i++]); 43 | uint8_t len = pgm_read_byte(&HX8357D_regValues[i++]); 44 | if(r == TFTLCD_DELAY) { 45 | delay(len); 46 | } else { 47 | //Serial.print("Register $"); Serial.print(r, HEX); 48 | //Serial.print(" datalen "); Serial.println(len); 49 | 50 | CS_ACTIVE; 51 | CD_COMMAND; 52 | write8(r); 53 | CD_DATA; 54 | for (uint8_t d=0; d= x1). 68 | /*****************************************************************************/ 69 | void hx8357x_setAddrWindow(int x1, int y1, int x2, int y2) 70 | { 71 | } 72 | 73 | /*****************************************************************************/ 74 | void hx8357x_fillScreen(uint16_t color) 75 | { 76 | } 77 | 78 | /*****************************************************************************/ 79 | void hx8357x_drawPixel(int16_t x, int16_t y, uint16_t color) 80 | { 81 | } 82 | 83 | /*****************************************************************************/ 84 | void hx8357x_setRotation(uint8_t x) 85 | { 86 | } 87 | 88 | /*****************************************************************************/ 89 | uint16_t hx8357x_readPixel(int16_t x, int16_t y) 90 | { 91 | } 92 | -------------------------------------------------------------------------------- /src/TFTLib/hx8357x.h: -------------------------------------------------------------------------------- 1 | #ifndef _HX8357X_H_ 2 | #define _HX8357X_H_ 3 | 4 | 5 | // Register names 6 | #define HX8357_NOP 0x00 7 | #define HX8357_SWRESET 0x01 8 | #define HX8357_RDDID 0x04 9 | #define HX8357_RDDST 0x09 10 | 11 | #define HX8357B_RDPOWMODE 0x0A 12 | #define HX8357B_RDMADCTL 0x0B 13 | #define HX8357B_RDCOLMOD 0x0C 14 | #define HX8357B_RDDIM 0x0D 15 | #define HX8357B_RDDSDR 0x0F 16 | 17 | #define HX8357_SLPIN 0x10 18 | #define HX8357_SLPOUT 0x11 19 | #define HX8357B_PTLON 0x12 20 | #define HX8357B_NORON 0x13 21 | 22 | #define HX8357_INVOFF 0x20 23 | #define HX8357_INVON 0x21 24 | #define HX8357_DISPOFF 0x28 25 | #define HX8357_DISPON 0x29 26 | 27 | #define HX8357_CASET 0x2A 28 | #define HX8357_PASET 0x2B 29 | #define HX8357_RAMWR 0x2C 30 | #define HX8357_RAMRD 0x2E 31 | 32 | #define HX8357B_PTLAR 0x30 33 | #define HX8357_TEON 0x35 34 | #define HX8357_TEARLINE 0x44 35 | #define HX8357_MADCTL 0x36 36 | #define HX8357_COLMOD 0x3A 37 | 38 | #define HX8357_SETOSC 0xB0 39 | #define HX8357_SETPWR1 0xB1 40 | #define HX8357B_SETDISPLAY 0xB2 41 | #define HX8357_SETRGB 0xB3 42 | #define HX8357D_SETCOM 0xB6 43 | 44 | #define HX8357B_SETDISPMODE 0xB4 45 | #define HX8357D_SETCYC 0xB4 46 | #define HX8357B_SETOTP 0xB7 47 | #define HX8357D_SETC 0xB9 48 | 49 | #define HX8357B_SET_PANEL_DRIVING 0xC0 50 | #define HX8357D_SETSTBA 0xC0 51 | #define HX8357B_SETDGC 0xC1 52 | #define HX8357B_SETID 0xC3 53 | #define HX8357B_SETDDB 0xC4 54 | #define HX8357B_SETDISPLAYFRAME 0xC5 55 | #define HX8357B_GAMMASET 0xC8 56 | #define HX8357B_SETCABC 0xC9 57 | #define HX8357_SETPANEL 0xCC 58 | 59 | 60 | #define HX8357B_SETPOWER 0xD0 61 | #define HX8357B_SETVCOM 0xD1 62 | #define HX8357B_SETPWRNORMAL 0xD2 63 | 64 | #define HX8357B_RDID1 0xDA 65 | #define HX8357B_RDID2 0xDB 66 | #define HX8357B_RDID3 0xDC 67 | #define HX8357B_RDID4 0xDD 68 | 69 | #define HX8357D_SETGAMMA 0xE0 70 | 71 | #define HX8357B_SETGAMMA 0xC8 72 | #define HX8357B_SETPANELRELATED 0xE9 73 | 74 | #define HX8357B_MADCTL_MY 0x80 75 | #define HX8357B_MADCTL_MX 0x40 76 | #define HX8357B_MADCTL_MV 0x20 77 | #define HX8357B_MADCTL_ML 0x10 78 | #define HX8357B_MADCTL_RGB 0x00 79 | #define HX8357B_MADCTL_BGR 0x08 80 | #define HX8357B_MADCTL_MH 0x04 81 | 82 | 83 | /*****************************************************************************/ 84 | extern void hx8357x_begin(void); 85 | extern void hx8357x_setAddrWindow(int x1, int y1, int x2, int y2); 86 | extern void hx8357x_fillScreen(uint16_t color); 87 | extern void hx8357x_drawPixel(int16_t x, int16_t y, uint16_t color); 88 | extern void hx8357x_setRotation(uint8_t x); 89 | extern uint16_t hx8357x_readPixel(int16_t x, int16_t y); 90 | 91 | 92 | #endif 93 | -------------------------------------------------------------------------------- /src/TFTLib/ili932x.cpp: -------------------------------------------------------------------------------- 1 | // IMPORTANT: LIBRARY MUST BE SPECIFICALLY CONFIGURED FOR EITHER TFT SHIELD 2 | // OR BREAKOUT BOARD USAGE. SEE RELEVANT COMMENTS IN Adafruit_TFTLCD.h 3 | 4 | // Graphics library by ladyada/adafruit with init code from Rossum 5 | // MIT license 6 | 7 | 8 | #include "Adafruit_TFTLCD_8bit_STM32.h" 9 | //#include "pin_magic.h" 10 | 11 | #include "ili932x.h" 12 | 13 | static uint8_t rotation; 14 | 15 | /*****************************************************************************/ 16 | static const uint16_t ILI932x_regValues[] = { 17 | ILI932X_START_OSC , 0x0001, // Start oscillator 18 | TFTLCD_DELAY , 50, // 50 millisecond delay 19 | ILI932X_DRIV_OUT_CTRL , 0x0100, 20 | ILI932X_DRIV_WAV_CTRL , 0x0700, 21 | ILI932X_ENTRY_MOD , 0x1030, 22 | ILI932X_RESIZE_CTRL , 0x0000, 23 | ILI932X_DISP_CTRL2 , 0x0202, 24 | ILI932X_DISP_CTRL3 , 0x0000, 25 | ILI932X_DISP_CTRL4 , 0x0000, 26 | ILI932X_RGB_DISP_IF_CTRL1, 0x0, 27 | ILI932X_FRM_MARKER_POS , 0x0, 28 | ILI932X_RGB_DISP_IF_CTRL2, 0x0, 29 | ILI932X_POW_CTRL1 , 0x0000, 30 | ILI932X_POW_CTRL2 , 0x0007, 31 | ILI932X_POW_CTRL3 , 0x0000, 32 | ILI932X_POW_CTRL4 , 0x0000, 33 | TFTLCD_DELAY , 200, 34 | ILI932X_POW_CTRL1 , 0x1690, 35 | ILI932X_POW_CTRL2 , 0x0227, 36 | TFTLCD_DELAY , 50, 37 | ILI932X_POW_CTRL3 , 0x001A, 38 | TFTLCD_DELAY , 50, 39 | ILI932X_POW_CTRL4 , 0x1800, 40 | ILI932X_POW_CTRL7 , 0x002A, 41 | TFTLCD_DELAY , 50, 42 | ILI932X_GAMMA_CTRL1 , 0x0000, 43 | ILI932X_GAMMA_CTRL2 , 0x0000, 44 | ILI932X_GAMMA_CTRL3 , 0x0000, 45 | ILI932X_GAMMA_CTRL4 , 0x0206, 46 | ILI932X_GAMMA_CTRL5 , 0x0808, 47 | ILI932X_GAMMA_CTRL6 , 0x0007, 48 | ILI932X_GAMMA_CTRL7 , 0x0201, 49 | ILI932X_GAMMA_CTRL8 , 0x0000, 50 | ILI932X_GAMMA_CTRL9 , 0x0000, 51 | ILI932X_GAMMA_CTRL10 , 0x0000, 52 | ILI932X_GRAM_HOR_AD , 0x0000, 53 | ILI932X_GRAM_VER_AD , 0x0000, 54 | ILI932X_HOR_START_AD , 0x0000, 55 | ILI932X_HOR_END_AD , 0x00EF, 56 | ILI932X_VER_START_AD , 0X0000, 57 | ILI932X_VER_END_AD , 0x013F, 58 | ILI932X_GATE_SCAN_CTRL1 , 0xA700, // Driver Output Control (R60h) 59 | ILI932X_GATE_SCAN_CTRL2 , 0x0003, // Driver Output Control (R61h) 60 | ILI932X_GATE_SCAN_CTRL3 , 0x0000, // Driver Output Control (R62h) 61 | ILI932X_PANEL_IF_CTRL1 , 0X0010, // Panel Interface Control 1 (R90h) 62 | ILI932X_PANEL_IF_CTRL2 , 0X0000, 63 | ILI932X_PANEL_IF_CTRL3 , 0X0003, 64 | ILI932X_PANEL_IF_CTRL4 , 0X1100, 65 | ILI932X_PANEL_IF_CTRL5 , 0X0000, 66 | ILI932X_PANEL_IF_CTRL6 , 0X0000, 67 | ILI932X_DISP_CTRL1 , 0x0133, // Main screen turn on 68 | }; 69 | 70 | 71 | /*****************************************************************************/ 72 | void ili932x_begin(void) 73 | { 74 | //Serial.println("initializing ILI932x..."); 75 | uint16_t a, d; 76 | uint8_t i = 0; 77 | 78 | while(i < sizeof(ILI932x_regValues) / sizeof(uint16_t)) { 79 | a = pgm_read_word(&ILI932x_regValues[i++]); 80 | d = pgm_read_word(&ILI932x_regValues[i++]); 81 | if(a == TFTLCD_DELAY) delay(d); 82 | else { 83 | //Serial.print("writing to 0x"); Serial.print(a,HEX); Serial.print(" value: 0x"); Serial.println(d,HEX); 84 | writeRegister16(a, d); 85 | } 86 | } 87 | rotation = 0; 88 | ili932x_setRotation(rotation); 89 | //setAddrWindow(0, 0, TFTWIDTH-1, TFTHEIGHT-1); // done in setRotation() 90 | } 91 | 92 | /*****************************************************************************/ 93 | // Sets the LCD address window (and address counter, on 932X). 94 | // Relevant to rect/screen fills and H/V lines. Input coordinates are 95 | // assumed pre-sorted (e.g. x2 >= x1). 96 | /*****************************************************************************/ 97 | void ili932x_setAddrWindow(int x1, int y1, int x2, int y2) 98 | { 99 | // Values passed are in current (possibly rotated) coordinate 100 | // system. 932X requires hardware-native coords regardless of 101 | // MADCTL, so rotate inputs as needed. The address counter is 102 | // set to the top-left corner -- although fill operations can be 103 | // done in any direction, the current screen rotation is applied 104 | // because some users find it disconcerting when a fill does not 105 | // occur top-to-bottom. 106 | int x, y, t; 107 | switch(rotation) { 108 | default: 109 | x = x1; 110 | y = y1; 111 | break; 112 | case 1: 113 | t = y1; 114 | y1 = x1; 115 | if (y2>(TFTWIDTH-1)) { y2 = (TFTWIDTH-1); } 116 | x1 = TFTWIDTH - 1 - y2; 117 | y2 = x2; 118 | x2 = TFTWIDTH - 1 - t; 119 | x = x2; 120 | y = y1; 121 | break; 122 | case 2: 123 | t = x1; 124 | x1 = TFTWIDTH - 1 - x2; 125 | x2 = TFTWIDTH - 1 - t; 126 | t = y1; 127 | y1 = TFTHEIGHT - 1 - y2; 128 | y2 = TFTHEIGHT - 1 - t; 129 | x = x2; 130 | y = y2; 131 | break; 132 | case 3: 133 | t = x1; 134 | x1 = y1; 135 | y1 = TFTHEIGHT - 1 - x2; 136 | if (y2>(TFTWIDTH-1)) { y2 = (TFTWIDTH-1); } 137 | x2 = y2; 138 | y2 = TFTHEIGHT - 1 - t; 139 | x = x1; 140 | y = y2; 141 | break; 142 | } 143 | //Serial.print("setAddrWindow: rot: "); Serial.print(rotation); Serial.print(", x1: "); Serial.print(x1); Serial.print(", y1: "); Serial.print(y1); Serial.print(", x2: "); Serial.print(x2); Serial.print(", y2: "); Serial.println(y2); 144 | writeRegister16(ILI932X_HOR_START_AD, x1); // Set window address 145 | writeRegister16(ILI932X_HOR_END_AD, x2); 146 | writeRegister16(ILI932X_VER_START_AD, y1); 147 | writeRegister16(ILI932X_VER_END_AD, y2); 148 | writeRegister16(ILI932X_GRAM_HOR_AD, x ); // Set address counter to top left 149 | writeRegister16(ILI932X_GRAM_VER_AD, y ); 150 | } 151 | 152 | /*****************************************************************************/ 153 | void ili932x_fillScreen(uint16_t color) 154 | { 155 | // For the 932X, a full-screen address window is already the default 156 | // state, just need to set the address pointer to the top-left corner. 157 | // Although we could fill in any direction, the code uses the current 158 | // screen rotation because some users find it disconcerting when a 159 | // fill does not occur top-to-bottom. 160 | uint16_t x, y; 161 | switch(rotation) { 162 | default: x = 0 ; y = 0 ; break; 163 | case 1 : x = TFTWIDTH - 1; y = 0 ; break; 164 | case 2 : x = TFTWIDTH - 1; y = TFTHEIGHT - 1; break; 165 | case 3 : x = 0 ; y = TFTHEIGHT - 1; break; 166 | } 167 | writeRegister16(ILI932X_GRAM_HOR_AD, x); 168 | writeRegister16(ILI932X_GRAM_VER_AD, y); 169 | } 170 | 171 | /*****************************************************************************/ 172 | void ili932x_drawPixel(int16_t x, int16_t y, uint16_t color) 173 | { 174 | int16_t t; 175 | switch(rotation) { 176 | case 1: 177 | t = x; 178 | x = TFTWIDTH - 1 - y; 179 | y = t; 180 | break; 181 | case 2: 182 | x = TFTWIDTH - 1 - x; 183 | y = TFTHEIGHT - 1 - y; 184 | break; 185 | case 3: 186 | t = x; 187 | x = y; 188 | y = TFTHEIGHT - 1 - t; 189 | break; 190 | } 191 | writeRegister16(ILI932X_GRAM_HOR_AD, x); 192 | writeRegister16(ILI932X_GRAM_VER_AD, y); 193 | writeRegister16(ILI932X_RW_GRAM, color); 194 | } 195 | 196 | /*****************************************************************************/ 197 | void ili932x_setRotation(uint8_t rot) 198 | { 199 | uint16_t t, x2 = 0, y2 = 0; 200 | rotation = rot; 201 | switch(rot) { 202 | default: t = 0x1030; x2 = TFTWIDTH-1; y2 = TFTHEIGHT-1; break; 203 | case 1 : t = 0x1028; x2 = TFTHEIGHT-1; y2 = TFTWIDTH-1; break; 204 | case 2 : t = 0x1000; x2 = TFTWIDTH-1; y2 = TFTHEIGHT-1; break; 205 | case 3 : t = 0x1018; x2 = TFTHEIGHT-1; y2 = TFTWIDTH-1; break; 206 | } 207 | writeRegister16(ILI932X_ENTRY_MOD, t ); // MADCTL 208 | // For 932X, init default full-screen address window: 209 | ili932x_setAddrWindow(0, 0, x2, y2); 210 | } 211 | 212 | /*****************************************************************************/ 213 | uint16_t ili932x_readPixel(int16_t x, int16_t y) 214 | { 215 | int16_t t,r; 216 | switch(rotation) { 217 | case 1: 218 | t = x; 219 | x = TFTWIDTH - 1 - y; 220 | y = t; 221 | break; 222 | case 2: 223 | x = TFTWIDTH - 1 - x; 224 | y = TFTHEIGHT - 1 - y; 225 | break; 226 | case 3: 227 | t = x; 228 | x = y; 229 | y = TFTHEIGHT - 1 - t; 230 | break; 231 | } 232 | writeRegister16(ILI932X_GRAM_HOR_AD, x); 233 | writeRegister16(ILI932X_GRAM_VER_AD, y); 234 | // Inexplicable thing: sometimes pixel read has high/low bytes 235 | // reversed. A second read fixes this. Unsure of reason. Have 236 | // tried adjusting timing in read8() etc. to no avail. 237 | for(uint8_t pass=0; pass<2; pass++) { 238 | r = readReg(ILI932X_RW_GRAM); // Read data from GRAM 239 | } 240 | return r; 241 | } 242 | -------------------------------------------------------------------------------- /src/TFTLib/ili932x.h: -------------------------------------------------------------------------------- 1 | #ifndef _ILI932X_H_ 2 | #define _ILI932X_H_ 3 | 4 | 5 | // Register names from Peter Barrett's Microtouch code 6 | #define ILI932X_START_OSC 0x00 7 | #define ILI932X_DRIV_OUT_CTRL 0x01 8 | #define ILI932X_DRIV_WAV_CTRL 0x02 9 | #define ILI932X_ENTRY_MOD 0x03 10 | #define ILI932X_RESIZE_CTRL 0x04 11 | #define ILI932X_DISP_CTRL1 0x07 12 | #define ILI932X_DISP_CTRL2 0x08 13 | #define ILI932X_DISP_CTRL3 0x09 14 | #define ILI932X_DISP_CTRL4 0x0A 15 | #define ILI932X_RGB_DISP_IF_CTRL1 0x0C 16 | #define ILI932X_FRM_MARKER_POS 0x0D 17 | #define ILI932X_RGB_DISP_IF_CTRL2 0x0F 18 | #define ILI932X_POW_CTRL1 0x10 19 | #define ILI932X_POW_CTRL2 0x11 20 | #define ILI932X_POW_CTRL3 0x12 21 | #define ILI932X_POW_CTRL4 0x13 22 | #define ILI932X_GRAM_HOR_AD 0x20 23 | #define ILI932X_GRAM_VER_AD 0x21 24 | #define ILI932X_RW_GRAM 0x22 25 | #define ILI932X_POW_CTRL7 0x29 26 | #define ILI932X_FRM_RATE_COL_CTRL 0x2B 27 | #define ILI932X_GAMMA_CTRL1 0x30 28 | #define ILI932X_GAMMA_CTRL2 0x31 29 | #define ILI932X_GAMMA_CTRL3 0x32 30 | #define ILI932X_GAMMA_CTRL4 0x35 31 | #define ILI932X_GAMMA_CTRL5 0x36 32 | #define ILI932X_GAMMA_CTRL6 0x37 33 | #define ILI932X_GAMMA_CTRL7 0x38 34 | #define ILI932X_GAMMA_CTRL8 0x39 35 | #define ILI932X_GAMMA_CTRL9 0x3C 36 | #define ILI932X_GAMMA_CTRL10 0x3D 37 | #define ILI932X_HOR_START_AD 0x50 38 | #define ILI932X_HOR_END_AD 0x51 39 | #define ILI932X_VER_START_AD 0x52 40 | #define ILI932X_VER_END_AD 0x53 41 | #define ILI932X_GATE_SCAN_CTRL1 0x60 42 | #define ILI932X_GATE_SCAN_CTRL2 0x61 43 | #define ILI932X_GATE_SCAN_CTRL3 0x6A 44 | #define ILI932X_PART_IMG1_DISP_POS 0x80 45 | #define ILI932X_PART_IMG1_START_AD 0x81 46 | #define ILI932X_PART_IMG1_END_AD 0x82 47 | #define ILI932X_PART_IMG2_DISP_POS 0x83 48 | #define ILI932X_PART_IMG2_START_AD 0x84 49 | #define ILI932X_PART_IMG2_END_AD 0x85 50 | #define ILI932X_PANEL_IF_CTRL1 0x90 51 | #define ILI932X_PANEL_IF_CTRL2 0x92 52 | #define ILI932X_PANEL_IF_CTRL3 0x93 53 | #define ILI932X_PANEL_IF_CTRL4 0x95 54 | #define ILI932X_PANEL_IF_CTRL5 0x97 55 | #define ILI932X_PANEL_IF_CTRL6 0x98 56 | 57 | extern void ili932x_begin(void); 58 | extern void ili932x_setAddrWindow(int x1, int y1, int x2, int y2); 59 | extern void ili932x_fillScreen(uint16_t color); 60 | extern void ili932x_drawPixel(int16_t x, int16_t y, uint16_t color); 61 | extern void ili932x_setRotation(uint8_t x); 62 | extern uint16_t ili932x_readPixel(int16_t x, int16_t y); 63 | 64 | 65 | #endif 66 | -------------------------------------------------------------------------------- /src/TFTLib/ili9341.cpp: -------------------------------------------------------------------------------- 1 | // IMPORTANT: LIBRARY MUST BE SPECIFICALLY CONFIGURED FOR EITHER TFT SHIELD 2 | // OR BREAKOUT BOARD USAGE. SEE RELEVANT COMMENTS IN Adafruit_TFTLCD.h 3 | 4 | // Graphics library by ladyada/adafruit with init code from Rossum 5 | // MIT license 6 | 7 | 8 | #include "Adafruit_TFTLCD_8bit_STM32.h" 9 | //#include "pin_magic.h" 10 | 11 | #include "ili9341.h" 12 | 13 | 14 | /*****************************************************************************/ 15 | void ili9341_begin(void) 16 | { 17 | writeRegister8(ILI9341_SOFTRESET, 0); 18 | delay(50); 19 | writeRegister8(ILI9341_DISPLAYOFF, 0); 20 | 21 | writeRegister8(ILI9341_POWERCONTROL1, 0x23); 22 | writeRegister8(ILI9341_POWERCONTROL2, 0x10); 23 | writeRegister16(ILI9341_VCOMCONTROL1, 0x2B2B); 24 | writeRegister8(ILI9341_VCOMCONTROL2, 0xC0); 25 | writeRegister8(ILI9341_MEMCONTROL, ILI9341_MADCTL_MY | ILI9341_MADCTL_BGR); 26 | writeRegister8(ILI9341_PIXELFORMAT, 0x55); 27 | writeRegister16(ILI9341_FRAMECONTROL, 0x001B); 28 | 29 | writeRegister8(ILI9341_ENTRYMODE, 0x07); 30 | /* writeRegister32(ILI9341_DISPLAYFUNC, 0x0A822700);*/ 31 | 32 | writeRegister8(ILI9341_SLEEPOUT, 0); 33 | delay(120); 34 | writeRegister8(ILI9341_DISPLAYON, 0); 35 | ili9341_setAddrWindow(0, 0, TFTWIDTH-1, TFTHEIGHT-1); 36 | } 37 | 38 | /*****************************************************************************/ 39 | // Sets the LCD address window (and address counter, on 932X). 40 | // Relevant to rect/screen fills and H/V lines. Input coordinates are 41 | // assumed pre-sorted (e.g. x2 >= x1). 42 | /*****************************************************************************/ 43 | void ili9341_setAddrWindow(int x1, int y1, int x2, int y2) 44 | { 45 | uint32_t t; 46 | 47 | t = x1; 48 | t <<= 16; 49 | t |= x2; 50 | writeRegister32(ILI9341_COLADDRSET, t); // HX8357D uses same registers! 51 | t = y1; 52 | t <<= 16; 53 | t |= y2; 54 | writeRegister32(ILI9341_PAGEADDRSET, t); // HX8357D uses same registers! 55 | } 56 | 57 | /*****************************************************************************/ 58 | void ili9341_fillScreen(uint16_t color) 59 | { 60 | } 61 | 62 | /*****************************************************************************/ 63 | void ili9341_drawPixel(int16_t x, int16_t y, uint16_t color) 64 | { 65 | } 66 | 67 | /*****************************************************************************/ 68 | void ili9341_setRotation(uint8_t x) 69 | { 70 | } 71 | 72 | /*****************************************************************************/ 73 | uint16_t ili9341_readPixel(int16_t x, int16_t y) 74 | { 75 | } 76 | -------------------------------------------------------------------------------- /src/TFTLib/ili9341.h: -------------------------------------------------------------------------------- 1 | #ifndef _ILI9341_H_ 2 | #define _ILI9341_H_ 3 | 4 | 5 | // Register names 6 | #define ILI9341_SOFTRESET 0x01 7 | #define ILI9341_SLEEPIN 0x10 8 | #define ILI9341_SLEEPOUT 0x11 9 | #define ILI9341_NORMALDISP 0x13 10 | #define ILI9341_INVERTOFF 0x20 11 | #define ILI9341_INVERTON 0x21 12 | #define ILI9341_GAMMASET 0x26 13 | #define ILI9341_DISPLAYOFF 0x28 14 | #define ILI9341_DISPLAYON 0x29 15 | #define ILI9341_COLADDRSET 0x2A 16 | #define ILI9341_PAGEADDRSET 0x2B 17 | #define ILI9341_MEMORYWRITE 0x2C 18 | #define ILI9341_PIXELFORMAT 0x3A 19 | #define ILI9341_FRAMECONTROL 0xB1 20 | #define ILI9341_DISPLAYFUNC 0xB6 21 | #define ILI9341_ENTRYMODE 0xB7 22 | #define ILI9341_POWERCONTROL1 0xC0 23 | #define ILI9341_POWERCONTROL2 0xC1 24 | #define ILI9341_VCOMCONTROL1 0xC5 25 | #define ILI9341_VCOMCONTROL2 0xC7 26 | #define ILI9341_MEMCONTROL 0x36 27 | #define ILI9341_MADCTL 0x36 28 | 29 | #define ILI9341_MADCTL_MY 0x80 30 | #define ILI9341_MADCTL_MX 0x40 31 | #define ILI9341_MADCTL_MV 0x20 32 | #define ILI9341_MADCTL_ML 0x10 33 | #define ILI9341_MADCTL_RGB 0x00 34 | #define ILI9341_MADCTL_BGR 0x08 35 | #define ILI9341_MADCTL_MH 0x04 36 | 37 | 38 | /*****************************************************************************/ 39 | extern void ili9341_begin(void); 40 | extern void ili9341_setAddrWindow(int x1, int y1, int x2, int y2); 41 | extern void ili9341_fillScreen(uint16_t color); 42 | extern void ili9341_drawPixel(int16_t x, int16_t y, uint16_t color); 43 | extern void ili9341_setRotation(uint8_t x); 44 | extern uint16_t ili9341_readPixel(int16_t x, int16_t y); 45 | 46 | 47 | #endif 48 | -------------------------------------------------------------------------------- /variables.h: -------------------------------------------------------------------------------- 1 | // global capture variables 2 | uint16_t ch1Capture[NUM_SAMPLES] = {0}; 3 | uint16_t ch2Capture[NUM_SAMPLES] = {0}; 4 | uint16_t bitStore[NUM_SAMPLES] = {0}; 5 | uint16_t sIndex = 0; 6 | uint16_t tIndex = 0; 7 | volatile boolean triggered = false; 8 | 9 | volatile boolean keepSampling = true; 10 | long samplingTime; 11 | volatile boolean hold = false; 12 | // waveform calculated statistics 13 | struct Stats { 14 | boolean pulseValid; 15 | double avgPW; 16 | float duty; 17 | float freq; 18 | float cycle; 19 | 20 | boolean mvPos; 21 | float Vrmsf; 22 | float Vavrf; 23 | float Vmaxf; 24 | float Vminf; 25 | } wStats; 26 | 27 | enum {CPL_GND, CPL_AC, CPL_DC}; 28 | const char* cplNames[] = {"GND", "AC", "DC"}; 29 | enum {RNG_5V, RNG_2V, RNG_1V, RNG_0_5V, RNG_0_2V, RNG_0_1V, RNG_50mV, RNG_20mV, RNG_10mV}; 30 | const char* rngNames[] = {"5V", "2V", "1V", "0.5V", "0.2V", "0.1V", "50mV", "20mV", "10mV"}; 31 | const float adcMultiplier[] = {0.05085, 0.02034, 0.01017, 0.005085, 0.002034, 0.001017, 0.5085, 0.2034, 0.1017}; 32 | // analog switch enumerated values 33 | uint8_t couplingPos, rangePos; 34 | 35 | // this represents the offset voltage at ADC input (1.66V), when Analog input is zero 36 | int16_t zeroVoltageA1, zeroVoltageA2; 37 | 38 | // timebase enumerations and store 39 | enum {T20US, T30US, T50US, T0_1MS, T0_2MS, T0_5MS, T1MS, T2MS, T5MS, T10MS, T20MS, T50MS}; 40 | const char* tbNames[] = {"20 uS", "30 uS", "50 uS", "0.1 mS", "0.2 mS", "0.5 mS", "1 mS", "2 mS", "5 mS", "10 mS", "20 mS", "50 mS"}; 41 | uint8_t currentTimeBase; 42 | 43 | -------------------------------------------------------------------------------- /zconfig.ino: -------------------------------------------------------------------------------- 1 | // zconfig: since we are referencing variables defined in other files 2 | 3 | #define PREAMBLE_VALUE 2859 4 | 5 | 6 | // ------------------------ 7 | void loadConfig(boolean reset) { 8 | // ------------------------ 9 | DBG_PRINTLN("Loading stored config..."); 10 | 11 | if(EEPROM.init() != EEPROM_OK) { 12 | loadDefaults(); 13 | return; 14 | } 15 | 16 | // read preamble 17 | if(reset || (EEPROM.read(PARAM_PREAMBLE) != PREAMBLE_VALUE)) { 18 | loadDefaults(); 19 | formatSaveConfig(); 20 | return; 21 | } 22 | 23 | // load all the parameters from EEPROM 24 | uint16_t data; 25 | 26 | data = EEPROM.read(PARAM_TIMEBASE); 27 | setTimeBase(data); 28 | 29 | // trigger type is not persisted 30 | setTriggerType(TRIGGER_AUTO); 31 | 32 | data = EEPROM.read(PARAM_TRIGDIR); 33 | setTriggerRising(data == 1); 34 | 35 | data = EEPROM.read(PARAM_XCURSOR); 36 | xCursor = data; 37 | 38 | data = EEPROM.read(PARAM_YCURSOR); 39 | yCursors[0] = data; 40 | 41 | data = EEPROM.read(PARAM_YCURSOR + 1); 42 | yCursors[1] = data; 43 | 44 | data = EEPROM.read(PARAM_YCURSOR + 2); 45 | yCursors[2] = data; 46 | 47 | data = EEPROM.read(PARAM_YCURSOR + 3); 48 | yCursors[3] = data; 49 | 50 | data = EEPROM.read(PARAM_WAVES); 51 | waves[0] = data; 52 | 53 | data = EEPROM.read(PARAM_WAVES + 1); 54 | waves[1] = data; 55 | 56 | data = EEPROM.read(PARAM_WAVES + 2); 57 | waves[2] = data; 58 | 59 | data = EEPROM.read(PARAM_WAVES + 3); 60 | waves[3] = data; 61 | 62 | data = EEPROM.read(PARAM_TLEVEL); 63 | setTriggerLevel(data); 64 | 65 | printStats = EEPROM.read(PARAM_STATS); 66 | zeroVoltageA1 = EEPROM.read(PARAM_ZERO1); 67 | zeroVoltageA2 = EEPROM.read(PARAM_ZERO2); 68 | 69 | 70 | DBG_PRINTLN("Loaded config:"); 71 | DBG_PRINT("Timebase: ");DBG_PRINTLN(currentTimeBase); 72 | DBG_PRINT("Trigger Rising: ");DBG_PRINTLN(triggerRising); 73 | DBG_PRINT("Trigger Type: ");DBG_PRINTLN(triggerType); 74 | DBG_PRINT("X Cursor Pos: ");DBG_PRINTLN(xCursor); 75 | DBG_PRINT("Y Cursors: ");DBG_PRINT(yCursors[0]);DBG_PRINT(", ");DBG_PRINT(yCursors[1]);DBG_PRINT(", ");DBG_PRINT(yCursors[2]);DBG_PRINT(", ");DBG_PRINTLN(yCursors[3]); 76 | DBG_PRINT("Waves: ");DBG_PRINT(waves[0]);DBG_PRINT(", ");DBG_PRINT(waves[1]);DBG_PRINT(", ");DBG_PRINT(waves[2]);DBG_PRINT(", ");DBG_PRINTLN(waves[3]); 77 | DBG_PRINT("Trigger Level: ");DBG_PRINTLN(data); 78 | DBG_PRINT("Print Stats: ");DBG_PRINTLN(printStats); 79 | DBG_PRINT("Wave1 Zero: ");DBG_PRINTLN(zeroVoltageA1); 80 | DBG_PRINT("Wave2 Zero: ");DBG_PRINTLN(zeroVoltageA2); 81 | 82 | // check if EEPROM left enough space, or else invoke formatSaveConfig 83 | } 84 | 85 | 86 | 87 | // ------------------------ 88 | void loadDefaults() { 89 | // ------------------------ 90 | DBG_PRINTLN("Loading defaults"); 91 | 92 | setTimeBase(T30US); 93 | setTriggerRising(true); 94 | setTriggerType(TRIGGER_AUTO); 95 | setTriggerLevel(0); 96 | 97 | // set x in the middle 98 | xCursor = (NUM_SAMPLES - GRID_WIDTH)/2; 99 | 100 | // set y in the middle 101 | yCursors[0] = -70; 102 | yCursors[1] = -90; 103 | yCursors[2] = -110; 104 | yCursors[3] = -130; 105 | 106 | // show all waves 107 | waves[0] = true; 108 | waves[1] = true; 109 | waves[2] = true; 110 | waves[3] = true; 111 | 112 | printStats = false; 113 | 114 | zeroVoltageA1 = 1985; 115 | zeroVoltageA2 = 1985; 116 | } 117 | 118 | 119 | 120 | // ------------------------ 121 | void formatSaveConfig() { 122 | // ------------------------ 123 | DBG_PRINTLN("Formatting EEPROM"); 124 | EEPROM.format(); 125 | DBG_PRINTLN("Saving all config params...."); 126 | 127 | saveParameter(PARAM_PREAMBLE, PREAMBLE_VALUE); 128 | 129 | saveParameter(PARAM_TIMEBASE, currentTimeBase); 130 | saveParameter(PARAM_TRIGDIR, triggerRising); 131 | saveParameter(PARAM_XCURSOR, xCursor); 132 | saveParameter(PARAM_YCURSOR, yCursors[0]); 133 | saveParameter(PARAM_YCURSOR + 1, yCursors[1]); 134 | saveParameter(PARAM_YCURSOR + 2, yCursors[2]); 135 | saveParameter(PARAM_YCURSOR + 3, yCursors[3]); 136 | 137 | saveParameter(PARAM_WAVES, waves[0]); 138 | saveParameter(PARAM_WAVES + 1, waves[1]); 139 | saveParameter(PARAM_WAVES + 2, waves[2]); 140 | saveParameter(PARAM_WAVES + 3, waves[3]); 141 | 142 | saveParameter(PARAM_TLEVEL, trigLevel); 143 | saveParameter(PARAM_STATS, printStats); 144 | 145 | saveParameter(PARAM_ZERO1, zeroVoltageA1); 146 | saveParameter(PARAM_ZERO2, zeroVoltageA2); 147 | } 148 | 149 | 150 | 151 | // ------------------------ 152 | void saveParameter(uint16_t param, uint16_t data) { 153 | // ------------------------ 154 | uint16 status = EEPROM.write(param, data); 155 | if(status != EEPROM_OK) { 156 | DBG_PRINT("Unable to save param in EEPROM, code: ");DBG_PRINTLN(status); 157 | } 158 | } 159 | 160 | 161 | --------------------------------------------------------------------------------