From 8b061d5fb99f42577619b5c06783f80a0c97a342 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20van=20Dorst?= Date: Sun, 4 Jan 2026 10:25:14 +0100 Subject: [PATCH 1/5] SYS_TICK: Use Timer2 instead of Timer0. Downside of Timer0 is, we have to manually reload the timer value. Timer2 can do that automaticly. This saves a lot of time in the interrupt-handler of the Timer. Currently Timer2 is used for baudrate-generation but in a separete commit this is moved to Timer1. --- crtstart.asm | 6 ++--- rtl837x_sfr.h | 17 +++++++++---- rtlplayground.c | 64 ++++++++++++++++++++++++++++++------------------- 3 files changed, 55 insertions(+), 32 deletions(-) diff --git a/crtstart.asm b/crtstart.asm index 87c3627..3a51c1c 100644 --- a/crtstart.asm +++ b/crtstart.asm @@ -16,12 +16,12 @@ __interrupt_vect: .ds 5 ljmp _isr_ext1 ; 0x13 .ds 5 - reti + reti ; 0x1b TIMER 1 IRQ .ds 7 ljmp _isr_serial ; 0x23 .ds 5 - reti ; 0x2b TIMER 2 IRQ - .ds 7 + ljmp _isr_timer2 ; 0x2b TIMER 2 IRQ + .ds 5 reti ; 0x33 NOT used by DW8051 .ds 7 reti ; 0x3b Serial port 1 RX/TX IRQ diff --git a/rtl837x_sfr.h b/rtl837x_sfr.h index d8a7024..bd81320 100644 --- a/rtl837x_sfr.h +++ b/rtl837x_sfr.h @@ -40,11 +40,6 @@ __sfr __at(0x91) EXIF; __sfr __at(0xf8) EIP; __sbit __at(0xf9) PX3; -/* SFR control registers for serial communication */ -__sfr __at(0xc8) T2CON; -__sfr __at(0xca) RCAP2L; -__sfr __at(0xcb) RCAP2H; - /* SFR Bank control register: 0x0-3f. A value of 0 is bank 1 */ __sfr __at(0x96) PSBANK; // SFR used to store return bank for trampoline @@ -101,3 +96,15 @@ __sfr __at(0xb6) SFR_NIC_RING_H; /* Standard 8051 sfr */ // Timer 0 value __sfr16 __at(0x8c8a) T0_U16; + +// Timer 1 enable interrupt +__sbit __at(0xad) ET2; + +// Timer 2 +__sfr __at(0xcc) TL2; +__sfr __at(0xcd) TH2; +__sfr16 __at(0xcdcc) T2_U16; +__sfr __at(0xc8) T2CON; +__sfr __at(0xca) RCAP2L; +__sfr __at(0xcb) RCAP2H; +__sfr16 __at(0xcbca) RCAP2_U16; diff --git a/rtlplayground.c b/rtlplayground.c index aaa2f43..e1b00f8 100644 --- a/rtlplayground.c +++ b/rtlplayground.c @@ -28,7 +28,6 @@ void crc16(__xdata uint8_t *v) __naked; // Upload Firmware to 1M #define FIRMWARE_UPLOAD_START 0x100000 -#define SYS_TICK_HZ 200 #define SERIAL_BAUD_RATE 115200 /* All RTL839x switches have an external 25MHz Oscillator, @@ -54,12 +53,20 @@ void crc16(__xdata uint8_t *v) __naked; #define CLOCK_DIV 0 #endif -// Derive divider for the system ticks -#define TIMER0_DIV (CLOCK_HZ / 12 / SYS_TICK_HZ) -#if TIMER0_DIV > 0xFFFF -#error "SYS_TICH_HZ to low, must be >= 158" +/* Derive divider for the system ticks + TIMER2 can divide the F_CPU by 4 or 12. + So the F_TICKS are in the range of: + - F_TIMER_DIV4_OVERFLOW = F_SYS / DIV4 / 1..65536 = 125MHz / 4 / 1..65536 = 31.25 MHz .. 476.8 Hz + - T_TIMER_DIV12_OVERFLOW = F_SYS / DIV12 / 1..65536 = 125MHz / 12 / 1..65536 = 10.42 MHz .. 158.9 Hz + Selecting dividor 12 settings to get lowest timer tick posiable which is already high. +*/ +#define SYS_TICK_HZ 200 + +#define TIMER2_DIV (CLOCK_HZ / 12 / SYS_TICK_HZ) +#if TIMER2_DIV > 0xFFFF +#error "SYS_TICK_HZ to low, must be >= 159" #endif -#define SYSTICK_TIMER0_VALUE (0x10000 - TIMER0_DIV) +#define SYSTICK_TIMER2_VALUE (0x10000 - TIMER2_DIV) __xdata uint8_t idle_ready; @@ -118,15 +125,19 @@ __xdata uint8_t sfp_options[2]; void isr_timer0(void) __interrupt(1) { - TR0 = 0; // Stop timer 0 - T0_U16 = SYSTICK_TIMER0_VALUE; - TR0 = 1; // Re-start timer 0 +} + +// Timer2: Handle SYS_TICK +void isr_timer2(void) __interrupt(5) +{ ticks++; if (sleep_ticks > 0) sleep_ticks--; sec_counter++; + // Clear TF2 & EXF2 by software + T2CON &= ~0xC0; } @@ -287,18 +298,25 @@ void isr_ext3(void) __interrupt(9) write_char('W'); } - -void setup_timer0(void) +// Timer2: handles system tick. +void setup_timer2(void) { - TMOD = 0x11; // Timer 1: Mode 1, Timer 0: Mode 1, i.e. 16 bit counters, no auto-reload - /* The TH0 registers contain the high/low byte that we load into Timer0 when T0 - * overflows to 0x10000 - */ - T0_U16 = SYSTICK_TIMER0_VALUE; + T2CON = 0x00; // Timer2: Mode 16-bit timer with auto-reload, disable the timer. + + // Timer 2 clock select F_SYS / 12; + // T2M = 0 uses clk/12; + CKCON &= ~0x20; + + // The RCAP2 registers contain the high/low byte that is loaded into + // timer2 when T2 overflows to 0x10000 + RCAP2_U16 = SYSTICK_TIMER2_VALUE; + + T2CON |= 0x04; // Timer2: Enable + + // IP |= 0x20; // TEST: Make Timer 2 interrupt as high priority. + ET2 = 1; // Enable Timer2 interrupt. + - CKCON &= 0xc7; - TCON = 0x10; // Start timer 0 - ET0 = 1; // Enable timer interrupts } @@ -1704,16 +1722,14 @@ void bootloader(void) IE = 0; EIE = 0; // SFR e8: EIE. Disable all external IRQs - // Disable all interrupts (global interrupt enable bit) - EA = 0; // SFR A8.7 / IE.7 - idle_ready = 0; // HW setup, serial, timer, external IRQs setup_clock(); + setup_timer2(); setup_serial(); - setup_timer0(); setup_external_irqs(); - EA = 1; // Enable all IRQs + + EA = 1; // Enable global interrupt // Set default for SFP pins so we can start up a module already inserted sfp_pins_last = 0x33; // signal LOS and no module inserted (for both slots, even if only 1 present) From bbd465681044ca0bfd13a61d1fb62ef5cd985206 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20van=20Dorst?= Date: Sun, 4 Jan 2026 10:48:48 +0100 Subject: [PATCH 2/5] SERIAL: Use Timer1 instead of Timer2 Use Timer1 as baudrate generator. Timer1 can be programmed to have the same accuracy and deviation as Timer2, up to 115200 at F_SYS = 125 Mhz. See comment setup_serial_timer1() comment for valid baudrates and settings. Timer1 is used in 8-bit auto-reload mode. Timer2 16-bit auto-reload can now be used for other tasks like SYS_TICK. --- rtlplayground.c | 52 ++++++++++++++++++++++++++++++++++++------------- 1 file changed, 38 insertions(+), 14 deletions(-) diff --git a/rtlplayground.c b/rtlplayground.c index e1b00f8..91bd7f8 100644 --- a/rtlplayground.c +++ b/rtlplayground.c @@ -28,6 +28,7 @@ void crc16(__xdata uint8_t *v) __naked; // Upload Firmware to 1M #define FIRMWARE_UPLOAD_START 0x100000 +// See setup_serial_timer1() for valid baudrate settings! #define SERIAL_BAUD_RATE 115200 /* All RTL839x switches have an external 25MHz Oscillator, @@ -1662,25 +1663,48 @@ void init_smi(void) } -/* Set up serial port 0 using Timer 2 with an external trigger - * as baud generator. - * The external clock generator uses a crystal at 25MHz. +/* Set up serial port 0 using Timer 1 as baudrate generator. + * For x Bd these settings are needed, see table below. + * NOTE: Settings only valid for F_SYS = 125 MHz! + * | Wanted | | TMR | F_SYS | | Actual | | + * | baudrate | SMOD0 | DIV | DIV | TH1 | baudrate | Error | + * | -------- | ----- | --- | ----- | ---- | -------- | ------ | + * | 1200 | 0 | 12 | 255 | 0x01 | 1276.6 | 6.00% | + * | 2400 | 0 | 12 | 136 | 0x78 | 2393.5 | −0.27% | + * | 4800 | 0 | 4 | 203 | 0x35 | 4810.7 | 0.22% | + * | 9600 | 1 | 4 | 203 | 0x35 | 9621.3 | 0.22% | + * | 14400 | 1 | 4 | 136 | 0x78 | 14361.2 | −0.27% | + * | 19200 | 1 | 4 | 102 | 0x9a | 19148.3 | −0.27% | + * | 38400 | 1 | 4 | 51 | 0xcd | 38296.6 | −0.27% | + * | 57600 | 1 | 4 | 34 | 0xde | 57444.9 | −0.27% | + * | 115200 | 1 | 4 | 17 | 0xef | 114889.7 | −0.27% | */ -void setup_serial(void) +#if CLOCK_HZ != 125000000 +#warning "SERIAL 0 baudrate setting may only valid for F_CPU = 125 MHz!" +#endif +void setup_serial_timer1(void) { - IE = 0; + // Timer 1: Mode 2: automatic reload + TMOD &= 0x0F; + TMOD |= 0xA0; // Timer1: GATE, Mode2: Timer, 8-bit with auto-reload + CKCON |= 0x10; // Timer1 clock divider: F_SYS / 4: T2M = 1, Timer 1 uses clk/4 - T2CON = 0x34; // Enable RCLK/TCLK (serial transmit/receive clock for T2), TR2 (Timer 2 RUN), disable CP/RL2 (bit 0) - SCON = 0x50; // Mode = 1: ASYNC 8N1 with T2 as baud-rate generator, REN_0 Receive enable + PCON |= 0x80; // SMOD0 = 1; Double the Baud Rate, don't divide Timer 1 Overflag signal. - // The RCAP2 registers contain the high/low byte that is loaded into - // timer2 when T2 overflows to 0x10000 - RCAP2H = (0x10000 - ((CLOCK_HZ / SERIAL_BAUD_RATE + 16) / 32)) >> 8; - RCAP2L = (0x10000 - ((CLOCK_HZ / SERIAL_BAUD_RATE + 16) / 32)) % 0xff; + SCON = 0x50; // Mode = 1: ASYNC 8N1 with Timer 2 as baud-rate generator, REN_0 Receive enable - PCON |= 0x80; // Double the Baud Rate + /* The TH1 register contain the reload value, timer1 when T1 overflows to 0x100. + * NOTE: compiler computs the wrong value. 0xF0 is calculated but 0xEF is the right value for 115200. + * Also https://www.keil.com/products/c51/baudrate.asp confirms this. + * Added 32 before div by 64 to make sure rounding is correct so that the results are right. + * + * TH1 = 0x100 - (2^SMOD0 * F_SYS) / ( TMR1_DIV / BAUDRATE * 32) + */ + TH1 = (0x100 - (((CLOCK_HZ / SERIAL_BAUD_RATE) + 32) / (4 * 16))) & 0xff; - SCON = 0x50; + TCON |= 0x40; // Start timer 1 + + ET1 = 0; // Timer1 Interrupt is NOT wanted! TI = 1; RI = 0; @@ -1726,7 +1750,7 @@ void bootloader(void) // HW setup, serial, timer, external IRQs setup_clock(); setup_timer2(); - setup_serial(); + setup_serial_timer1(); setup_external_irqs(); EA = 1; // Enable global interrupt From b818a94202f992aa0404cb9d4a63ae2c3d8c69c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20van=20Dorst?= Date: Sun, 4 Jan 2026 10:55:43 +0100 Subject: [PATCH 3/5] SERIAL: Wrong use of TI-flag causes IRQ-storm. TI-interrupt flag is used in the code, this is only fine when SERIAL-interrupt is not enabled/used but when is it used, it generates an interrupt which is not handled, so it keeps generating interrupts and trashing the performance and caused starvation on lower priority interrupts like TIMER 2. Use an extra flag to signal to the code that the TX-buffer is empty. --- rtlplayground.c | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/rtlplayground.c b/rtlplayground.c index 91bd7f8..53c9cd7 100644 --- a/rtlplayground.c +++ b/rtlplayground.c @@ -121,6 +121,7 @@ __xdata char sfp_module_vendor[2][17]; __xdata char sfp_module_model[2][17]; __xdata char sfp_module_serial[2][17]; __xdata uint8_t sfp_options[2]; +__sbit tx_buf_empty = 1; #define ETHERTYPE_OFFSET (12 + VLAN_TAG_SIZE + RTL_TAG_SIZE) @@ -145,9 +146,13 @@ void isr_timer2(void) __interrupt(5) void isr_serial(void) __interrupt(4) { if (RI == 1) { + RI = 0; sbuf[sbuf_ptr] = SBUF; sbuf_ptr = (sbuf_ptr + 1) & (SBUF_SIZE - 1); - RI = 0; + } + if (TI == 1) { + TI = 0; + tx_buf_empty = 1; } } @@ -155,14 +160,14 @@ void isr_serial(void) __interrupt(4) void write_char(char c) { do { - } while (TI == 0); - TI = 0; + } while (tx_buf_empty == 0); if (c =='\n') { + tx_buf_empty = 0; SBUF = '\r'; do { - } while (TI == 0); - TI = 0; + } while (tx_buf_empty == 0); } + tx_buf_empty = 0; SBUF = c; } @@ -1705,8 +1710,8 @@ void setup_serial_timer1(void) TCON |= 0x40; // Start timer 1 ET1 = 0; // Timer1 Interrupt is NOT wanted! - TI = 1; - RI = 0; + TI = 0; // Clear TI-interrupt flag + RI = 0; // Clear RI-interrupt flag ES = 1; // Enable serial IRQ } From 5b4b0efb1604644254b882386c3c74e3c00024ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20van=20Dorst?= Date: Mon, 5 Jan 2026 21:44:10 +0100 Subject: [PATCH 4/5] installer: remove unused sys_tick timer code. --- installer/installer.c | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/installer/installer.c b/installer/installer.c index d528c7a..4764ff4 100644 --- a/installer/installer.c +++ b/installer/installer.c @@ -11,7 +11,6 @@ #include "../rtl837x_sfr.h" #include "../rtl837x_regs.h" -#define SYS_TICK_HZ 100 #define SERIAL_BAUD_RATE 57600 #define CLOCK_HZ 125000000 @@ -26,10 +25,6 @@ #define CLOCK_DIV 0 #endif -volatile __xdata uint32_t ticks; -volatile __xdata uint8_t sec_counter; -volatile __xdata uint16_t sleep_ticks; - // We buffer 1 sector as this is also the erase size __xdata uint8_t buffer[0x1000]; __xdata uint8_t dio_enabled; @@ -38,16 +33,6 @@ __code uint8_t * __code hex = "0123456789abcdef"; void isr_timer0(void) __interrupt(1) { - TR0 = 0; // Stop timer 0 - TH0 = (0x10000 - (CLOCK_HZ / SYS_TICK_HZ / 32)) >> 8; - TL0 = (0x10000 - (CLOCK_HZ / SYS_TICK_HZ / 32)) % 0xff; - - ticks++; - if (sleep_ticks > 0) - sleep_ticks--; - sec_counter++; - - TR0 = 1; // Re-start timer 0 } From c5c6b97ad138986b8408f83c69a67b6e44a5e501 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20van=20Dorst?= Date: Mon, 5 Jan 2026 21:55:19 +0100 Subject: [PATCH 5/5] installer: SERIAL: Use Timer1 instead of Timer2 Same as in the main code. Use Timer1 as baudrate generator. Timer1 can be programmed to have the same accuracy and deviation as Timer2, up to 115200 at F_SYS = 125 Mhz. See comment setup_serial_timer1() comment for valid baudrates and settings. Timer1 is used in 8-bit auto-reload mode. Only differance is that the serial is transmit only and poll-based so no interrupts are used to transmit data. --- installer/installer.c | 59 ++++++++++++++++++++++++++++++------------- rtlplayground.c | 4 ++- 2 files changed, 45 insertions(+), 18 deletions(-) diff --git a/installer/installer.c b/installer/installer.c index 4764ff4..e4063fc 100644 --- a/installer/installer.c +++ b/installer/installer.c @@ -11,6 +11,7 @@ #include "../rtl837x_sfr.h" #include "../rtl837x_regs.h" +// See setup_serial_timer1() for valid baudrate settings! #define SERIAL_BAUD_RATE 57600 #define CLOCK_HZ 125000000 @@ -87,25 +88,52 @@ void print_short(uint16_t a) } } -void setup_serial(void) +/* Set up serial port 0 using Timer 1 as baudrate generator. + * For x Bd these settings are needed, see table below. + * NOTE: Settings only valid for F_SYS = 125 MHz! + * | Wanted | | TMR | F_SYS | | Actual | | + * | baudrate | SMOD0 | DIV | DIV | TH1 | baudrate | Error | + * | -------- | ----- | --- | ----- | ---- | -------- | ------ | + * | 1200 | 0 | 12 | 255 | 0x01 | 1276.6 | 6.00% | + * | 2400 | 0 | 12 | 136 | 0x78 | 2393.5 | −0.27% | + * | 4800 | 0 | 4 | 203 | 0x35 | 4810.7 | 0.22% | + * | 9600 | 1 | 4 | 203 | 0x35 | 9621.3 | 0.22% | + * | 14400 | 1 | 4 | 136 | 0x78 | 14361.2 | −0.27% | + * | 19200 | 1 | 4 | 102 | 0x9a | 19148.3 | −0.27% | + * | 38400 | 1 | 4 | 51 | 0xcd | 38296.6 | −0.27% | + * | 57600 | 1 | 4 | 34 | 0xde | 57444.9 | −0.27% | + * | 115200 | 1 | 4 | 17 | 0xef | 114889.7 | −0.27% | + */ +#if CLOCK_HZ != 125000000 +#warning "SERIAL 0 baudrate setting may only valid for F_CPU = 125 MHz!" +#endif +void setup_serial_timer1(void) { - IE = 0; + // Timer 1: Mode 2: automatic reload + TMOD &= 0x0F; + TMOD |= 0xA0; // Timer1: GATE, Mode2: Timer, 8-bit with auto-reload + CKCON |= 0x10; // Timer1 clock divider: F_SYS / 4: T2M = 1, Timer 1 uses clk/4 - T2CON = 0x34; // Enable RCLK/TCLK (serial transmit/receive clock for T2), TR2 (Timer 2 RUN), disable CP/RL2 (bit 0) - SCON = 0x50; // Mode = 1: ASYNC 8N1 with T2 as baud-rate generator, REN_0 Receive enable + PCON |= 0x80; // SMOD0 = 1; Double the Baud Rate, don't divide Timer 1 Overflag signal. - // The RCAP2 registers contain the high/low byte that is loaded into - // timer2 when T2 overflows to 0x10000 - RCAP2H = (0x10000 - (CLOCK_HZ / SERIAL_BAUD_RATE / 32)) >> 8; - RCAP2L = (0x10000 - (CLOCK_HZ / SERIAL_BAUD_RATE / 32)) % 0xff; + SCON = 0x50; // Mode = 1: ASYNC 8N1 with Timer 2 as baud-rate generator, REN_0 Receive enable - PCON |= 0x80; // Double the Baud Rate + /* The TH1 register contain the reload value, timer1 when T1 overflows to 0x100. + * NOTE: compiler computs the wrong value. 0xF0 is calculated but 0xEF is the right value for 115200. + * Also https://www.keil.com/products/c51/baudrate.asp confirms this. + * Added 32 before div by 64 to make sure rounding is correct so that the results are right. + * + * TH1 = 0x100 - (2^SMOD0 * F_SYS) / ( TMR1_DIV / BAUDRATE * 32) + */ + TH1 = (0x100 - (((CLOCK_HZ / SERIAL_BAUD_RATE) + 32) / (4 * 16))) & 0xff; - SCON = 0x50; - TI = 1; - RI = 0; + TCON |= 0x40; // Start timer 1 - ES = 1; // Enable serial IRQ + ET1 = 0; // Timer1 Interrupt is NOT wanted! + TI = 1; // Set TI-interrupt/flag, to flag that the TX-buf is empty. + RI = 0; // Clear RI-interrupt flag + + ES = 0; // Disable serial IRQ, software is only printing data and just polls TI-flag. } /* @@ -330,10 +358,7 @@ void installer(void) IE = 0; EIE = 0; // SFR e8: EIE. Disable all external IRQs - // Disable all interrupts (global interrupt enable bit) - EA = 0; // SFR A8.7 / IE.7 - - setup_serial(); + setup_serial_timer1(); print_string("\nRTLPlayground installer starting...\n"); // Initialize flash functions with disable DIO because writing does not work otherwise diff --git a/rtlplayground.c b/rtlplayground.c index 53c9cd7..94e75c4 100644 --- a/rtlplayground.c +++ b/rtlplayground.c @@ -121,7 +121,7 @@ __xdata char sfp_module_vendor[2][17]; __xdata char sfp_module_model[2][17]; __xdata char sfp_module_serial[2][17]; __xdata uint8_t sfp_options[2]; -__sbit tx_buf_empty = 1; +__sbit tx_buf_empty; #define ETHERTYPE_OFFSET (12 + VLAN_TAG_SIZE + RTL_TAG_SIZE) @@ -1713,6 +1713,8 @@ void setup_serial_timer1(void) TI = 0; // Clear TI-interrupt flag RI = 0; // Clear RI-interrupt flag + tx_buf_empty = 1; // Set tx `serial buffer is empty`-software flag. + ES = 1; // Enable serial IRQ }