So after having a working version of Semyon I wanted to familiarize myself with use of the special hardware present in the device. That is, timers, external interrupts, and special power modes.
So the STC15F104W has 2 timers, called T0 and T2.
T0 is really a 16 bit auto-reload timer. One can disable auto-reload or use other timer modes like the 8051 traditional 8-bit auto-reload timer. The traditional control bits for the timer exist.
T2 is a skinnier version, only functioning as a 16 bit auto-reload timer. It is totally non-compatible with T2 present in the 8052 MCU, and has no bit controls - one has to fiddle with the whole control registers themselves.
None of these has a prescaler except for the 12 clock prescaler for legacy support, which is kinda lame. However, given the auto-reload feature, one can easily use overflow interrupts to get that exact functionality without giving up any clock cycle precision.
The first target for changes was the delay calls. The DJNZ loops are simple but this is a classic place to use a timer at. The delay functions now looked thus:
delay_debounce: mov r7, #0x01 sjmp delay_loop delay_display2: mov r7, #0x05 sjmp delay_loop delay_display: mov r7, #0x16 delay_loop: mov TL0, #0x00 mov TH0, #0xc0 delay_loop_2: setb TR0 jnb TF0, . clr TF0 djnz r7, delay_loop_2 clr TR0 ret
This is really setting the timer, and continually polling it. The timer is set to initial value of 0xC000, which is effectively a 14-bit timer which overflows faster. The loop is repeated R7 times, and thus granularity is achieved.
The next victim must be the seed generation. As mentioned in previous logs, it incremented the LFSR, pooling user input in-between. Replacing it with a time is classic too:
initialize: ;This is the initialization phase of semyon. ;It should also generate the seed value for PRNG. mov V_LED_CNT, #1 mov V_LED_MAX, #1 mov TL0, #0x01 mov TH0, #0x00 mov TMOD, #0x00 mov AUXR, #0x81 setb TR0 initialize_seed_loop: mov a, P3 orl a, #P_LED_ALL cjne a, #0xff, initialize_ret sjmp initialize_seed_loop initialize_ret: mov a, P3 orl a, #P_LED_ALL cpl a cjne a, #0x00, initialize_ret clr TR0 clr TF0 mov V_SEED_L, TL0 mov V_SEED_H, TH0 lcall delay_display mov V_STATE, #S_DISPLAY_SEQUENCE ret
That is lots of timer configurations, then enableing the counter and polling user input, then waiting for user to release the buttons, and using the timer value as the seed.
This makes the seed to increment about 47 times faster. It is almost feasible to use a 24-bit LFSR!
In STC15 family there are 5 external interrupts - the traditional INT0 and INT1, and INT2, INT3 and INT4 which are only falling edge activated. In STC15F104W, P3.2 to P3.5 are mapped to INT0 to INT3 respectively, which means they can be used to get user input.
So I declared the relevant interrupt vectors:
.org 0x0003 ;ext0 _int_GLED: mov V_INTERRUPT_LED, #P_N_LED_G ljmp ext_interrupt_handler .org 0x0013 ;ext1 _int_BLED: mov V_INTERRUPT_LED, #P_N_LED_B ljmp ext_interrupt_handler .org 0x0053 ;ext2 _int_YLED: mov V_INTERRUPT_LED, #P_N_LED_Y ljmp ext_interrupt_handler .org 0x005b ;ext3 _int_RLED: mov V_INTERRUPT_LED, #P_N_LED_R ljmp ext_interrupt_handler
V_INTERRUPT_LED is a new variable I declared to store a value indicating which button was pressed, and is used in the game logic akin to the way the polled P3 value was used.
All these external interrupts jump to the same handler, which disables the external interrupts:
ext_interrupt_handler: anl AUXR2, #~0x30 ;EX3 | EX2 anl IE, #~0x05 ;EX1 | EX0 reti
Power and Clock Control
Waiting for external interrupts to happen using an idle loop that polls something still misses the point. What I really want is to enable external interrupts, and then halt the CPU until the interrupt happens.
There's a register that allows one to do it, called PCON.
PD and IDL bits set the MCU to Power-Down and Idle modes, respectively.
In Idle mode, the CPU is shut down, but the rest of the hardware still function - that is all the peripherals, including timers, com. and ADCs. The CPU will wake up at any interrupt set to it.
In contrast, Power-Down mode shuts down the whole device, so it can only wake up in case of external interrupts.
Idle mode is what I wanted earlier - and it replaces the idle loops with much elegance.
ext_int_get_input_beginning: mov IE, #0x05 ;EX1 | EX0 mov AUXR2, #0x30 ;EX3 | EX2 setb EA orl PCON, #0x01 ;IDL clr EA ;Some debounce logic
For user input where no timer should run in the background, one can also go Power-Down altogether:
orl PCON, #0x02 ;PD = power down
In the delay routines, the IDL mode applies too.
Moreover, one might want to slow down the whole system clock, as the timers can't be prescaled. This can work for the delays as no computations are required to be made when delay is called.
Prescaling is done using the PCON2 register (also called CLK_DIV) which is specific to STC MCUs. It's 3 LSBs control a system clock divider up to 128.
Using the divider and idle mode make the delay much more elegant, littered only by SFR configuring:
delay_debounce: mov T2L, #0x00 mov T2H, #0xfc sjmp delay_activate delay_display2: mov T2L, #0x00 mov T2H, #0xe0 sjmp delay_activate delay_display: mov T2L, #0x00 mov T2H, #0xb0 ;sjmp delay_activate delay_activate: orl IE2, #0x04 ;Enable T2 interrupt orl AUXR, #0x04 ;T2 is 1clk orl PCON2, #0x07 ;clk/128 setb EA orl AUXR, #0x10 ;enable T2 orl PCON, #0x01 ;IDL anl AUXR, #~0x10 ;disable T2 clr EA anl PCON2, #~0x07 ;clk/1 ret
Know your hardware
I might be an extremist, but IMO the hardware and real-life events are the focus of embedded design, and the programming is only a tool to get there.
Thus, peripheral configuration and use is the essence of embedded programming. It is there where the slim line between programming and real-life is.
An embedded code which makes almost no use of the existing peripherals is really missing the point. E.g. writing code that wiggles GPIOs using digitalWrite() in arduino is not embedded code - the thought process is that of general purpose computer programming rather than a real-time/control mindset. Moreover I believe that one haven't really used an MCU until he activated some of it's hardware by tweaking the SFRs directly.
Even if I sound completely nuts, using the peripherals still has one good point - it is very educational for me. Using peripherals rather than funky code solutions is the right direction towards more complex projects which shall require peripheral use, say real-time control, which is very cool.