As others have mentioned, this is best done in assembly. Here is some code to try: void delay_sub(unsigned char i) { // convert 20, 21, 22 etc to count in R7 of 1, 2, 3 (extra cycle added if i is odd) ; cycles rrc A ; 1 c = 1 if odd jnc even ; 2 or 4 extra 2 cycles if branch taken (spoils cache) nop ; 1 nop ; 1 clc ; 1 even: subb A,#6 ; 1 mov R7,A ; 1 R7 now = (i / 2) - 6 //while (i--); loop: djnz R7, loop ; 2 loop address should be in cache, so no extra cycles needed ret ; 6 } if i even: 5+7+R7*2+6 = minimum of 20 22 24 ... R7 = 1, 2, 3 if i odd: 5+8+R7*2+6 = minimum of 21 23 25 ... R7 = 1, 2, 3 It assumes a call is made like ACALL(nn), where nn is a constant or a variable in a byte variable, so that the parameter can be passed using a MOV A, instruction. The minimum timing you can do is 20 clocks, as you asked for. mov A,#n ; 1 acall ; 4 There is no check that the parameter is greater than or equal to 20, any values less than 20 will give incorrect timing. The mov instruction and acall will take 5 cycles. First off, the count (i) is divided by two to account for the DJNZ instruction taking two cycles. Then the count is adjusted to add a cycle if i is odd. Finally a fixed value is subtracted so the value in the register to be decremented (R7) is in the range 1, 2, 3 ... R7 is then decremented in a tight loop (two cycles per count). There is a fixed cycle count of 6 for the return. If you have to use a LCALL instead of a ACALL, the minimum timing you can do will be 22 clocks, and you will need to add a nop in front of the rrc instruction to balance out the extra cycle for the LCALL. The behavior of the jump instructions are based on the datasheet for the C8051F38x as I understand them (in terms of when the instruction cache is spoiled or not). This may be different for other versions of the 8051.