├── fw_analog ├── make.bat ├── stm8sdefs.h ├── main.ihx └── main.c ├── docs ├── pcb.jpg ├── plug.jpg ├── plug2.jpg ├── schematic.png └── smdcircuit.jpg ├── fw_serial ├── make.bat ├── main.ihx ├── stm8sdefs.h └── main.c ├── .gitignore ├── stm8sdefs.h └── README.md /fw_analog/make.bat: -------------------------------------------------------------------------------- 1 | sdcc -lstm8 -mstm8 --out-fmt-ihx main.c 2 | pause -------------------------------------------------------------------------------- /docs/pcb.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/furrtek/MagicWandMods/HEAD/docs/pcb.jpg -------------------------------------------------------------------------------- /docs/plug.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/furrtek/MagicWandMods/HEAD/docs/plug.jpg -------------------------------------------------------------------------------- /docs/plug2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/furrtek/MagicWandMods/HEAD/docs/plug2.jpg -------------------------------------------------------------------------------- /docs/schematic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/furrtek/MagicWandMods/HEAD/docs/schematic.png -------------------------------------------------------------------------------- /docs/smdcircuit.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/furrtek/MagicWandMods/HEAD/docs/smdcircuit.jpg -------------------------------------------------------------------------------- /fw_serial/make.bat: -------------------------------------------------------------------------------- 1 | "D:\Program Files\SDCC\bin\sdcc" -lstm8 -mstm8 --out-fmt-ihx main.c 2 | pause -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | *.lk 3 | *.lst 4 | *.map 5 | *.rel 6 | *.rst 7 | *.sym 8 | *.cdb 9 | fw_analog/stvp.hex 10 | fw_serial/stvp.hex 11 | fw_serial/main.asm 12 | fw_analog/main.asm 13 | -------------------------------------------------------------------------------- /fw_analog/stm8sdefs.h: -------------------------------------------------------------------------------- 1 | #define PC_IDR (*(volatile unsigned char *)0x500B) 2 | #define PC_CR1 (*(volatile unsigned char *)0x500D) 3 | 4 | #define CLK_CKDIVR (*(volatile unsigned char *)0x50C6) 5 | 6 | #define TIM1_CR1 (*(volatile unsigned char *)0x5250) 7 | #define TIM1_CCMR1 (*(volatile unsigned char *)0x5258) 8 | #define TIM1_CCER1 (*(volatile unsigned char *)0x525C) 9 | #define TIM1_ARRH (*(volatile unsigned char *)0x5262) 10 | #define TIM1_ARRL (*(volatile unsigned char *)0x5263) 11 | #define TIM1_CCR1H (*(volatile unsigned char *)0x5265) 12 | #define TIM1_CCR1L (*(volatile unsigned char *)0x5266) 13 | #define TIM1_BKR (*(volatile unsigned char *)0x526D) 14 | 15 | #define TIM2_CR1 (*(volatile unsigned char *)0x5300) 16 | #define TIM2_IER (*(volatile unsigned char *)0x5303) 17 | #define TIM2_SR1 (*(volatile unsigned char *)0x5304) 18 | #define TIM2_PSCR (*(volatile unsigned char *)0x530E) 19 | #define TIM2_ARRH (*(volatile unsigned char *)0x530F) 20 | #define TIM2_ARRL (*(volatile unsigned char *)0x5310) 21 | 22 | #define ADC_CSR (*(volatile unsigned char *)0x5400) 23 | #define ADC_CR1 (*(volatile unsigned char *)0x5401) 24 | #define ADC_DRH (*(volatile unsigned char *)0x5404) 25 | -------------------------------------------------------------------------------- /stm8sdefs.h: -------------------------------------------------------------------------------- 1 | #define PC_IDR (*(volatile unsigned char *)0x500B) 2 | #define PC_CR1 (*(volatile unsigned char *)0x500D) 3 | 4 | #define CLK_CKDIVR (*(volatile unsigned char *)0x50C6) 5 | 6 | #define TIM1_CR1 (*(volatile unsigned char *)0x5250) 7 | #define TIM1_CCMR1 (*(volatile unsigned char *)0x5258) 8 | #define TIM1_CCER1 (*(volatile unsigned char *)0x525C) 9 | #define TIM1_ARRH (*(volatile unsigned char *)0x5262) 10 | #define TIM1_ARRL (*(volatile unsigned char *)0x5263) 11 | #define TIM1_CCR1H (*(volatile unsigned char *)0x5265) 12 | #define TIM1_CCR1L (*(volatile unsigned char *)0x5266) 13 | #define TIM1_BKR (*(volatile unsigned char *)0x526D) 14 | 15 | #define TIM2_CR1 (*(volatile unsigned char *)0x5300) 16 | #define TIM2_IER (*(volatile unsigned char *)0x5303) 17 | #define TIM2_SR1 (*(volatile unsigned char *)0x5304) 18 | #define TIM2_PSCR (*(volatile unsigned char *)0x530E) 19 | #define TIM2_ARRH (*(volatile unsigned char *)0x530F) 20 | #define TIM2_ARRL (*(volatile unsigned char *)0x5310) 21 | 22 | #define ADC_CSR (*(volatile unsigned char *)0x5400) 23 | #define ADC_CR1 (*(volatile unsigned char *)0x5401) 24 | #define ADC_DRH (*(volatile unsigned char *)0x5404) 25 | -------------------------------------------------------------------------------- /fw_analog/main.ihx: -------------------------------------------------------------------------------- 1 | :2080000082008083820000008200000082000000820000008200000082000000820000004D 2 | :2080200082000000820000008200000082000000820000008200000082000000820080A010 3 | :20804000820000008200000082000000820000008200000082000000820000008200000010 4 | :208060008200000082000000820000008200000082000000820000008200000082000000F0 5 | :1D808300AE00002707724F00005A26F9AE00022709D68239D700005A26F7CC80803F 6 | :03808000CC80AD04 7 | :2080A000725C000135010002350053048052150F100F0F0F0E5F1F140F090F080F07965C23 8 | :2080C0001F121E127F1E125CA680F71E125C5CA6A0F71E12A6C0E7031E12A6E0E7041E12A1 9 | :2080E000A6FFE705350850C6AE500DF6AA18F7354154013505540035015262350052633580 10 | :20810000005265350052663501525C356052583580526D350152503506530E3500530F35E4 11 | :2081200080531035015303350153009A725D00022764725F000272105401AE5400F64D2A38 12 | :20814000F9721F5400AE5404F65F971D007F1F0C0D0C2A051E0C501F0C1E0C72FB141F14BD 13 | :208160007B09A11F252E0F091E14541F0A0D0A2705AE00FF1F0A1E0AA3002025067B0B6B81 14 | :2081800008200C7B08A11025067B08A0106B085F1F1420020C09C60001A120258F725F00D0 15 | :2081A00001AE500BF6438818106B128488141297846B0FA4184D260688A6016B0884A11874 16 | :2081C000260E0D07270A0F077B10A8016B10201D9FA418A108260C887B0FA1058424040C7E 17 | :2081E0000E200AA11026060D0E27020A0E5F7B0E9772FB12F60D102607AE5266F7CC812CFA 18 | :20820000905F617B08615F97899089CD82215B044F9EA12024014FAE5266F7CC812C5B155B 19 | :0182200081DC 20 | :02823A00000042 21 | :198221001E037B0642891E04429F1B016B011E057B07429F1B018595810F 22 | :00000001FF 23 | -------------------------------------------------------------------------------- /fw_serial/main.ihx: -------------------------------------------------------------------------------- 1 | :2080000082008083820000008200000082000000820000008200000082000000820000004D 2 | :2080200082000000820000008200000082000000820000008200000082000000820080A010 3 | :2080400082000000820000008200000082000000820080B7820000008200000082000000D9 4 | :208060008200000082000000820000008200000082000000820000008200000082000000F0 5 | :1D808300AE00042707724F00005A26F9AE00052709D6831DD700045A26F7CC80804F 6 | :03808000CC80F3BE 7 | :2080A000725C0005725D00092704725A000935005304725C0006805202AE00011F01C6004C 8 | :2080C00008A4035F9772FB0190AE523190F6F7C600084CA403C70008350A0009350100073A 9 | :2080E000AE500FF6A820F75B0280AE5230F6AE5231F78152110F0D0F0C0F0B0F0A0F090F1E 10 | :20810000080F07965C1F101E107F1E105CA680F71E105C5CA6A0F71E10A6C0E7031E10A657 11 | :20812000E0E7041E10A6FFE705350850C635FF50C7AE500DF6AA18F7AE5011F6AA20F7AEE9 12 | :208140005012F6AA20F7350152333534523235005234352452353500523635005237350013 13 | :20816000523835005261350152623500526335005265350052663501525C35605258358008 14 | :20818000526D350152503506530E3500530F3580531035015303350153009A725D00092650 15 | :2081A00004725F0008AE5230F6A50F270CAE5230F6AE5231F6725F00087B0DA1012606A6B3 16 | :2081C000016B0F20020F0F0D0F2745725D00072743725F0007AE0001F6A10126370D082665 17 | :2081E00033AE0002F6AE000388F69784416B0A41725F0006A1202402A620350052503500D5 18 | :20820000525F3500525E35015250AE5266F72004725F0008C60005A1202403CC82E6725FDE 19 | :2082200000050D0827347B08A101262C35005261350052667B07A102260AA6056B08A60163 20 | :208240006B0720147B07A101260E35145261351E5266A6056B080F070A08AE500BF6438809 21 | :20826000180D6B0F8488140F97846B0CA4184D260688A6016B0A84A11826340D0927300FB2 22 | :20828000097B0DA8016B0D0F0BA6056B087B0DA1012606A6016B0F20020F0F0D0F2703A651 23 | :2082A00002214F6B07351452613528526620239FA418A108260F887B0CA1058424077B0B63 24 | :2082C0004C6B0B200DA11026090D0B27057B0B4A6B0B0D0D26100D08260C5F7B0B9772FBC5 25 | :2082E00010F6AE5266F7C60006A1082403CC819B725F00060D0F2603CC819B0D082703CC88 26 | :20830000819B0D0A260735005266CC819B7B0AA1FF2503CC819B0A0ACC819B5B118100000A 27 | :038320000000005A 28 | :00000001FF 29 | -------------------------------------------------------------------------------- /fw_serial/stm8sdefs.h: -------------------------------------------------------------------------------- 1 | #define PC_IDR (*(volatile unsigned char *)0x500B) 2 | #define PC_CR1 (*(volatile unsigned char *)0x500D) 3 | 4 | #define PD_ODR (*(volatile unsigned char *)0x500F) 5 | #define PD_DDR (*(volatile unsigned char *)0x5011) 6 | #define PD_CR1 (*(volatile unsigned char *)0x5012) 7 | 8 | #define CLK_CKDIVR (*(volatile unsigned char *)0x50C6) 9 | #define CLK_PCKENR (*(volatile unsigned char *)0x50C7) 10 | 11 | #define TIM1_CR1 (*(volatile unsigned char *)0x5250) 12 | #define TIM1_CCMR1 (*(volatile unsigned char *)0x5258) 13 | #define TIM1_CCER1 (*(volatile unsigned char *)0x525C) 14 | #define TIM1_CNTRL (*(volatile unsigned char *)0x525F) 15 | #define TIM1_CNTRH (*(volatile unsigned char *)0x525E) 16 | #define TIM1_PSCRH (*(volatile unsigned char *)0x5260) 17 | #define TIM1_PSCRL (*(volatile unsigned char *)0x5261) 18 | #define TIM1_ARRH (*(volatile unsigned char *)0x5262) 19 | #define TIM1_ARRL (*(volatile unsigned char *)0x5263) 20 | #define TIM1_CCR1H (*(volatile unsigned char *)0x5265) 21 | #define TIM1_CCR1L (*(volatile unsigned char *)0x5266) 22 | #define TIM1_BKR (*(volatile unsigned char *)0x526D) 23 | 24 | #define TIM2_CR1 (*(volatile unsigned char *)0x5300) 25 | #define TIM2_IER (*(volatile unsigned char *)0x5303) 26 | #define TIM2_SR1 (*(volatile unsigned char *)0x5304) 27 | #define TIM2_PSCR (*(volatile unsigned char *)0x530E) 28 | #define TIM2_ARRH (*(volatile unsigned char *)0x530F) 29 | #define TIM2_ARRL (*(volatile unsigned char *)0x5310) 30 | 31 | #define ADC_CSR (*(volatile unsigned char *)0x5400) 32 | #define ADC_CR1 (*(volatile unsigned char *)0x5401) 33 | #define ADC_DRH (*(volatile unsigned char *)0x5404) 34 | 35 | #define UART1_SR (*(volatile unsigned char *)0x5230) 36 | #define UART1_DR (*(volatile unsigned char *)0x5231) 37 | #define UART1_BRR1 (*(volatile unsigned char *)0x5232) 38 | #define UART1_BRR2 (*(volatile unsigned char *)0x5233) 39 | #define UART1_CR1 (*(volatile unsigned char *)0x5234) 40 | #define UART1_CR2 (*(volatile unsigned char *)0x5235) 41 | #define UART1_CR3 (*(volatile unsigned char *)0x5236) 42 | #define UART1_CR4 (*(volatile unsigned char *)0x5237) 43 | #define UART1_CR5 (*(volatile unsigned char *)0x5238) 44 | #define UART1_GTR (*(volatile unsigned char *)0x5239) 45 | #define UART1_PSCR (*(volatile unsigned char *)0x523A) 46 | 47 | -------------------------------------------------------------------------------- /fw_analog/main.c: -------------------------------------------------------------------------------- 1 | // "Europe Magic Wand" Hitachi clone firmware with analog-in (CV) input 2 | // Compile with SDCC for STM8S003F3 3 | // See README for programming instructions 4 | // Furrtek - 2016 5 | 6 | // PD5: Analog input (see schematic) 7 | // PC3: + button 8 | // PC4: - button 9 | // PC5: Motor control 10 | 11 | #include "stm8sdefs.h" 12 | 13 | #define MODE_NORMAL 0 14 | #define MODE_AIN 1 15 | 16 | unsigned char volatile timer_div = 0; // Timing counter 17 | unsigned char volatile adc_trig = 0; // Conversion request flag 18 | 19 | void TIM2_UPD_OVF_IRQHandler(void) __interrupt(13) { 20 | timer_div++; 21 | adc_trig = 1; 22 | TIM2_SR1 = 0; 23 | } 24 | 25 | main() { 26 | unsigned char mode = MODE_NORMAL; // Start in normal mode 27 | unsigned char new_input, active_input; 28 | unsigned char prev_input = 0; 29 | unsigned char duty_index = 0; 30 | signed int adc_value = 0; 31 | unsigned int adc_mean = 0; 32 | unsigned char adc_count = 0; 33 | unsigned char pwm_value = 0; 34 | unsigned char ccr_value = 0; 35 | unsigned char released = 0; 36 | 37 | // Duty cycle LUT, starts at 50% and goes up to 100% 38 | const unsigned char duty_lut[6] = { 0, 0x80, 0xA0, 0xC0, 0xE0, 0xFF }; 39 | 40 | CLK_CKDIVR = 1 << 3; // fCPU = 16MHz/2 = 8MHz 41 | 42 | PC_CR1 |= 0x18; // Pull-ups for buttons 43 | 44 | ADC_CR1 = 0x41; // f/8, ADON 45 | ADC_CSR = 5; 46 | 47 | // TIM1 is used for PWM generation 48 | TIM1_ARRH = 0x01; // Reload = 0x100 49 | TIM1_ARRL = 0x00; 50 | TIM1_CCR1H = 0x00; // Compare = 0x00 51 | TIM1_CCR1L = 0x00; 52 | TIM1_CCER1 = 1; // Compare mode 53 | TIM1_CCMR1 = 6 << 4; // PWM1 mode 54 | TIM1_BKR = 1 << 7; // Enable output 55 | TIM1_CR1 = 1; // Enable timer 56 | 57 | // TIM2 is used for timing ADC and input debounce 58 | TIM2_PSCR = 6; 59 | TIM2_ARRH = 0; 60 | TIM2_ARRL = 0x80; // 8MHz/64/128 = ~977Hz 61 | TIM2_IER = 1; // Enable overflow interrupt 62 | TIM2_CR1 = 1; // Enable timer 63 | 64 | __asm; // Enable interrupts 65 | rim 66 | __endasm; 67 | 68 | for(;;) { 69 | if (adc_trig) { 70 | adc_trig = 0; // Clear flag 71 | 72 | ADC_CR1 |= 1; // Start conversion 73 | while(!(ADC_CSR & 0x80)) {}; 74 | ADC_CSR &= ~0x80; 75 | 76 | adc_value = (ADC_DRH - 127); // Cancel out DC offset 77 | if (adc_value < 0) adc_value = -adc_value; 78 | adc_mean += adc_value; 79 | 80 | if (adc_count >= 31) { 81 | adc_count = 0; 82 | 83 | adc_mean = adc_mean >> 1; // /32*16 = /2 84 | 85 | if (adc_mean > 0xFF) adc_mean = 0xFF; // Cap to 255 86 | 87 | if (adc_mean >= 0x20) { 88 | pwm_value = adc_mean; // Noise threshold 89 | } else { 90 | if (pwm_value >= 0x10) pwm_value -= 0x10; // Auto decay 91 | } 92 | 93 | adc_mean = 0; 94 | } else { 95 | adc_count++; 96 | } 97 | } 98 | 99 | if (timer_div >= 32) { // 977/32 = 31Hz 100 | timer_div = 0; 101 | 102 | // Check for falling edge 103 | new_input = PC_IDR ^ 0xFF; 104 | active_input = (prev_input ^ new_input) & new_input; 105 | prev_input = new_input; 106 | 107 | // Detect both buttons released 108 | if ((new_input & 0x18) == 0x00) released = 1; 109 | 110 | if (((new_input & 0x18) == 0x18) && (released)) { 111 | // Toggle mode 112 | released = 0; 113 | mode ^= 1; 114 | } else { 115 | // Select speed 116 | if (((active_input & 0x18) == 0x08) && (duty_index < 5)) 117 | duty_index++; 118 | else if (((active_input & 0x18) == 0x10) && (duty_index > 0)) 119 | duty_index--; 120 | } 121 | 122 | if (mode == MODE_NORMAL) { 123 | TIM1_CCR1L = duty_lut[duty_index]; 124 | } else { 125 | // Multiply analog input by selected speed 126 | ccr_value = ((unsigned int)pwm_value * (unsigned int)duty_lut[duty_index]) >> 8; 127 | 128 | // Avoid stalling motor with value too low 129 | if (ccr_value < 0x20) ccr_value = 0; 130 | 131 | TIM1_CCR1L = ccr_value; 132 | } 133 | } 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MagicWandMods 2 | Hardware and firmware mods for the "Europe Magic Wand", a quality Hitachi Magic Wand clone. 3 | 4 | * Analog-in (CV): control by audio signal amplitude 5 | * Serial: control by serial port 6 | 7 | ## Teardown 8 | Be sure to unplug the EMW and wait a few minutes for the high-voltage capacitor to safely discharge before taking it apart. 9 | 10 | The EMW is held together by 3 big phillips screws: 11 | * On the back, close to the power cable, hidden by a plastic plug (hard to pull). 12 | * Under the buttons silicone strip (just glued, can be lifted with a flat screwdriver). 13 | * On the metal ring (visible). 14 | 15 | Inside, the PCB is held in place by a central screw and two other ones used to fasten the power cable. 16 | The PCB needs to be pulled out with a bit of effort because of the silicone joints. 17 | **Be careful about the flex cable** going to the buttons on the back of the PCB, gently lift the black part of the connector to release it. 18 | The motor should come loose out of the casing. 19 | 20 | ## Analog-in mod 21 | Follow this schematic: 22 | 23 | ![Schematic](docs/schematic.png) 24 | 25 | "AIN" is the analog input pin on the STM8 (pin #2, "PD5/AIN5"). R1 and R2 must be the same value and above 10k. 26 | 27 | I made the circuit with 1206 SMDs, but there's plenty of space for trough-hole components. 28 | 29 | ![Schematic](docs/smdcircuit.jpg) 30 | 31 | I took the +5V from the regulator (U2), and ground from C13. 32 | 33 | The wires can pick up some noise from the power supply circuit and the motor, but it's completly cancelled out by the firmware. Shielded audio cable isn't required. 34 | 35 | ### Usage 36 | Individually, the "+" and "-" buttons work the same way. 37 | The speed steps are the same as the original ones. 38 | 39 | Keeping both the "+" and "-" buttons pushed at the same time will toggle between the regular mode, and the analog-in mode. 40 | In the analog-in mode, the motor speed is multiplied by the amplitude of the input signal (so the speed adjustment and "off" speed still works). 41 | 42 | ## Serial mod 43 | Solder two wires: one to ground and one to the STM8 pin #3 ("UART1_RX/PD6"). No need to add any components. 44 | 45 | ### Usage 46 | Individually, the "+" and "-" buttons work the same way. 47 | The speed steps are the same as the original ones. 48 | 49 | Keeping both the "+" and "-" buttons pushed at the same time will toggle between the regular mode (one beep), and the serial control mode (two beeps). 50 | 51 | In the serial control mode, the commands are packets of 3 bytes sent in 9600 8N1 idle-high UART (3.3V or 5V) format. You can use an USB-to-TLL serial cable or an Arduino for example. **Do NOT use RS-232 levels !** 52 | 53 | * Byte 1: Constant value 0x01. 54 | * Byte 2: Power, 0 is off, 255 is max. Values below 40 might not make the motor start. 55 | * Byte 3: Duration in 8ms steps, 0 is the minimum, 254 is max, 255 is infinite duration. 56 | 57 | For example [0x01, 0x80, 0x58] means half power for 0.7 seconds. 58 | 59 | If you need low speed, kickstart the motor with a higher speed such as 100 for a short duration, then send a lower speed command right after. 60 | 61 | ## External connection 62 | 63 | Fit a 3.5mm jack socket next to the power cable, wrap it in 2 layers of heat-shrink tubing and hot-glue the wires to the casing to avoid any potential shorts with the mains voltage (**you DON'T want that**). 64 | 65 | ![Schematic](docs/plug.jpg) ![Schematic](docs/plug2.jpg) 66 | 67 | Connect the sleeve pin to the ground wire, and the tip (+ ring if you used a stereo cable) to the signal wire. 68 | 69 | ## Programming 70 | 71 | Locate the 4 programming points on the PCB: 72 | 73 | ![EMW](docs/pcb.jpg) 74 | 75 | * Connect a SWIM programmer (they're around $4 on eBay) to the annotated points. 76 | * Plug in the EMW. Be careful not to touch the PCB from now on ! 77 | * Get ST Visual Programmer from [st.com](https://www.st.com/en/development-tools/stvp-stm8.html). 78 | * Start STVP, select STLINK as the programmer and STM8S003F3 as the device. 79 | * Go in the OPTION BYTE tab. Make sure ROP is set to OFF, and AFR0 to "Port C6 Alternate Function". 80 | * Hit program, this will reset the protection and wipe the original firmware. 81 | * Back in the PROGRAM MEMORY tab, load the appropriate main.ihx file and hit program. 82 | * Disconnect the programmer, unplug the EMW, wait a bit, plug it back in. The new firmware should now be running. 83 | 84 | ## Disclaimer 85 | I'm not responsible if you fuck everything up and/or die. Be careful, there's exposed mains voltage inside the thing. 86 | This wasn't tested on animals (yet). 87 | -------------------------------------------------------------------------------- /fw_serial/main.c: -------------------------------------------------------------------------------- 1 | // "Europe Magic Wand" Hitachi clone firmware with UART control 2 | // Compile with SDCC for STM8S003F3 3 | // See README for programming instructions 4 | // Furrtek - 2022 5 | // TODO: "Kick" motor to get it started when low speed is requested 6 | 7 | // PD6: UART1_RX 8 | // PC3: + button 9 | // PC4: - button 10 | // PC5: Motor control (is it C6 ?) 11 | 12 | // 9600bps 8N1 idle high 13 | // Not higher because MCU runs off imprecise HSI oscillator 14 | // Command format: AA BB CC 15 | // AA: Byte, 0x01 16 | // BB: Byte, motor PWM value 17 | // CC: Byte, duration 0~254 in units of approx 8ms - if 255: infinite duration 18 | 19 | #include "stm8sdefs.h" 20 | 21 | #define MODE_NORMAL 0 22 | #define MODE_SERIAL 1 23 | 24 | unsigned char volatile timer_div = 0; // Timing counter 25 | unsigned char volatile duration_div = 0; // Timing counter 26 | unsigned char volatile rxed = 0; 27 | unsigned char volatile rx_data[4]; 28 | unsigned char volatile rx_idx = 0; 29 | unsigned char volatile rx_timeout = 0; 30 | 31 | void TIM2_UPD_OVF_IRQHandler(void) __interrupt(13) { 32 | timer_div++; 33 | if (rx_timeout) rx_timeout--; 34 | TIM2_SR1 = 0; 35 | duration_div++; 36 | } 37 | 38 | void UART1_RX_IRQHandler(void) __interrupt(18) { 39 | rx_data[rx_idx & 3] = UART1_DR; 40 | rx_idx = (rx_idx + 1) & 3; 41 | rx_timeout = 10; // Should be ~10ms 42 | rxed = 1; 43 | //PD_ODR ^= (1 << 5); // Toggle PD5 on rx byte 44 | } 45 | 46 | void main() { 47 | unsigned char mode = MODE_NORMAL; // Start in normal mode 48 | unsigned char new_input, active_input; 49 | unsigned char prev_input = 0; 50 | unsigned char duty_index = 0; 51 | unsigned char pwm_value = 0; 52 | unsigned char ccr_value = 0; 53 | unsigned char duration = 0; 54 | unsigned char released = 0; 55 | unsigned char beeping = 0; 56 | unsigned char beep_count = 0; 57 | 58 | // Duty cycle LUT for normal mode, starts at 50% and goes up to 100% 59 | const unsigned char duty_lut[6] = { 0, 0x80, 0xA0, 0xC0, 0xE0, 0xFF }; 60 | 61 | CLK_CKDIVR = 1 << 3; // fCPU = 16MHz/2 = 8MHz 62 | CLK_PCKENR = 0xFF; // Power everything up 63 | 64 | PC_CR1 |= 0x18; // Pull-ups for buttons 65 | 66 | // Debug 67 | //PD_DDR |= (1<<5); // PD5 out (UART1_TX) 68 | //PD_CR1 |= (1<<5); // PD5 out (UART1_TX) 69 | 70 | //UART1_DIV = 833; // 8MHz/9600 TODO:Choose a better divisor/speed ! 71 | UART1_BRR2 = 0x01; 72 | UART1_BRR1 = 0x34; // 833 = 0x341 73 | UART1_CR1 = 0x00; // 8N1 74 | UART1_CR2 = 0x24; // RIEN, RIE 0x2C; // RIEN, REN, TEN 75 | UART1_CR3 = 0x00; 76 | UART1_CR4 = 0x00; 77 | UART1_CR5 = 0x00; 78 | 79 | // TIM1 is used for PWM generation 80 | TIM1_PSCRL = 0; 81 | TIM1_ARRH = 0x01; // Reload = 0x100 82 | TIM1_ARRL = 0x00; 83 | TIM1_CCR1H = 0x00; // Compare = 0x00 84 | TIM1_CCR1L = 0x00; 85 | TIM1_CCER1 = 1; // Compare mode 86 | TIM1_CCMR1 = 6 << 4; // PWM1 mode 87 | TIM1_BKR = 1 << 7; // Enable output 88 | TIM1_CR1 = 1; // Enable timer 89 | 90 | // TIM2 is used for timing ADC and input debounce 91 | TIM2_PSCR = 6; // 2^6 92 | TIM2_ARRH = 0; 93 | TIM2_ARRL = 0x80; // 8MHz/64/128 = ~977Hz 94 | TIM2_IER = 1; // Enable overflow interrupt 95 | TIM2_CR1 = 1; // Enable timer 96 | 97 | __asm; // Enable interrupts 98 | rim 99 | __endasm; 100 | 101 | for(;;) { 102 | if (!rx_timeout) 103 | rx_idx = 0; // Reset rx buffer if more than 20 TIM2 interrupts since last byte received 104 | 105 | if (UART1_SR & 15) { 106 | // Clear any errors 107 | (void)UART1_SR; 108 | (void)UART1_DR; 109 | rx_idx = 0; 110 | } 111 | 112 | if (mode == MODE_SERIAL) { 113 | if (rxed) { 114 | rxed = 0; 115 | //if ((rx_idx & 3) == 3) { 116 | // Received a full frame 117 | //rx_idx = 0; 118 | 119 | if ((rx_data[0] == 0x01) && (!beeping)) { 120 | ccr_value = rx_data[1]; 121 | duration = rx_data[2]; 122 | duration_div = 0; 123 | 124 | // Avoid stalling motor with value too low 125 | if (ccr_value < 0x20) ccr_value = 0x20; 126 | 127 | TIM1_CR1 = 0; // Disable timer 128 | TIM1_CNTRL = 0; 129 | TIM1_CNTRH = 0; 130 | TIM1_CR1 = 1; // Enable timer 131 | TIM1_CCR1L = ccr_value; 132 | } 133 | //} 134 | } 135 | } else 136 | rx_idx = 0; // Ignore received bytes if not in serial mode 137 | 138 | if (timer_div >= 32) { // 977/32 = 31Hz 139 | timer_div = 0; 140 | 141 | if (beeping) { 142 | if (beeping == 1) { 143 | TIM1_PSCRL = 0; 144 | TIM1_CCR1L = 0; 145 | if (beep_count == 2) { 146 | beeping = 5; 147 | beep_count = 1; 148 | } else if (beep_count == 1) { 149 | TIM1_PSCRL = 20; 150 | TIM1_CCR1L = 30; 151 | beeping = 5; 152 | beep_count = 0; 153 | } 154 | } 155 | beeping--; 156 | } 157 | 158 | // Check for falling edge 159 | new_input = PC_IDR ^ 0xFF; 160 | active_input = (prev_input ^ new_input) & new_input; 161 | prev_input = new_input; 162 | 163 | // Detect both buttons released 164 | if ((new_input & 0x18) == 0x00) released = 1; 165 | 166 | if (((new_input & 0x18) == 0x18) && (released)) { 167 | // Toggle mode 168 | released = 0; 169 | mode ^= 1; 170 | duty_index = 0; // Make sure duty is zero when switching modes 171 | beeping = 5; 172 | beep_count = (mode == 1) ? 2 : 0; 173 | TIM1_PSCRL = 20; 174 | TIM1_CCR1L = 40; 175 | } else { 176 | // Select speed 177 | if (((active_input & 0x18) == 0x08) && (duty_index < 5)) { 178 | duty_index++; 179 | } 180 | else if (((active_input & 0x18) == 0x10) && (duty_index > 0)) 181 | duty_index--; 182 | } 183 | 184 | if ((mode == MODE_NORMAL) && (!beeping)) { 185 | TIM1_CCR1L = duty_lut[duty_index]; 186 | } 187 | } 188 | 189 | if (duration_div >= 8) { 190 | duration_div = 0; 191 | if ((mode == MODE_SERIAL) && (!beeping)) { 192 | // Serial mode: handle duration parameter 193 | if (duration == 0) { 194 | // Turn off motor 195 | TIM1_CCR1L = 0; 196 | } else { 197 | if (duration < 255) duration--; 198 | } 199 | } 200 | } 201 | } 202 | } 203 | 204 | --------------------------------------------------------------------------------