Skip to content

Instantly share code, notes, and snippets.

@TG9541
Last active May 12, 2022 19:21
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save TG9541/5f7bccde4553eb842e5537c49b1610f2 to your computer and use it in GitHub Desktop.
Save TG9541/5f7bccde4553eb842e5537c49b1610f2 to your computer and use it in GitHub Desktop.
Using the C0135 Relay Board Counter Inputs

Two Pulse Inputs for the C0135 "4 Relay MODBUS Board"

A friend needs a C0135 based MODBUS Node with two pulse counter inputs.

The requirements are modest:

  • provide reliabe pulse counting on two inputs
  • at a maximum frequency of about 200Hz
  • where the duty cycle isn't well known (but likely not always 50%)
  • without jamming the MODBUS server

Polling an input in the "background task" won't do the job as the rate is either too low or, at a higher rate, it jams the modbus server.

Hardware Considerations

According to the C0135 Hardware Description in the STM8 eForth Wiki the C0135 input terminals are connected to the following STM8S103F3 GPIO pins:

Input Pin Port Function
IN1 20 PD3 AIN4, TIM2_CH2
IN2 19 PD2 AIN3, [TIM2_CH3]
IN3 17 PC7 MISO, [TIM1_CH2]
IN4 16 PC6 MOSI, [TIM1_CH1]

The STM8S103F3P6 datasheet uses brackets around names of GPIO function (e.g., [TIM2_CH3]) to indicate an "alternative function" of the port. In practive that means "more complicated to use" since the STM8S architecture controlls function remapping through "option bytes" (see datasheet chapters 5.3 and 8.1). In STM8 eForth writing STM8S option bytes can be done with the word OPT!. This, however, should only be done once, e.g, while programming the device. Knowing the state of the flags is thus a concern.

Using TIM1 and TIM2 counter inputs

The counter input TIM2_CH2 is the only one that's an ordinary port function. Using it, however, requires using a different timer for the background task (e.g., using TIM1 if it is available).

According to chapter 8 table 14 the "option byte" OPT2 controls the following alternative functions:

  • AFR0 enables TIM1_CH1 and TIM1_CH2 (and TIM2_CH1)
  • AFR1 enables TIM2_CH3

This is all good and well - but we have to keep in mind that two pulse inputs are needed (i.e., TIM1 and TIM2). This means keeping the background task is more complicated than just configuring some options in globconf.inc.

Using the TIM4 counter

Freeing up TIM1 can be done by using TIM4 as the background ticker. Unfortunately, as the UART is needed as the MODBUS interface, TIM4 is used for timing the simulated serial interface.

A MODBUS server doesn't always need the "background task" feature - all that's really needed is a working background counter variable TIM. Incrementing TIM using the serial interface bit clock (usually a 104µs cadence) still requires some hackery as the timer is synchronized with Rx bits. This isn't the most attractive option.

Using the STM8S AWU as a BG Task timer

The AWU (Auto Wake-Up) timer is easily overlooked as a periodic timer. In fact it must be clocked from a 128 kHz source, normally from the LSI (Low Speed Internal) clock. It can, however, also be clocked from the HSE (High Speed External) source but not from the HSI (High Speed Internal) clock.

The STM8S Reference Manual RM0016 Chapter 9.3 states:

The division factor for HSE has to be programmed in the HSEPRSC[1:0] option bits Refer to in the option bytes section of the datasheet. The goal is to get 128 kHz at the output of the HSE prescaler.

In fact any C0135 board has a 8MHz crystalm which, when used as the main clock source really cripples the performance of the STM8 µC - a strange design decision!. Now this crystal can be brought to good use.

And RM0016 chapter 12.2 shows how it's to be configured: image

The AWU has one problem, though: it has to be started with the HALT instruction, i.e., by stopping the CPU. Obviously this can't be done by the background task itself, but only by the 'IDLE task. This of course, will add a random, but MODBUS dependent, delay to background task cadence, removing any benefit that using the crystal may provide.

For this use-case even an non-calibrated LSI timer may still be good enough.

Using External Interrupts

The STM8S architecture uses the same interrupt vector (and the same edge/level configuration) for all GPIOs of a port (e.g. PD). If two or more inputs of the same port are configured to generate an interrupt then it's up to the software to figure out which GPIO triggered the interrupt.

In the MODBUS application, however, PD1 is used as the simulated serial interface, and thus the EXTI of port D is needed for detecting the start-bit in Forth console host communication.

Unless one decides to use PA1 and/or PA2 for connecting the console, e.g., after desoldering the currently not needed 8MHz crystal, the only option for counting pulses with external interrupts is using port C for both inputs. This, however, requires avoiding race conditions that may occur when edges of both inputs change at nearly the same time.

A Solution

After considering the option, I decided to use an optimized edge-triggered ISR for detecting rising and falling edges of PC6 and PC7 (i.e. IN3 and IN4). The solution mirrors the state of a pulse signal (i.e., high or low) with a two-state state machine for each input (i.e., using a flag bit).

Here is the code of the resulting new word CCNT:

\res MCU: STM8S103
\res export PC_IDR PC_CR2
\res export EXTI_CR1 INT_EXTI2

#require ]B!
#require ]B@IF
#require :NVM
#require WIPE

NVM
  VARIABLE CCNT 1 ALLOT  \ 2 byte counter, 1 byte flags
RAM
  CCNT 2+ CONSTANT cflg
NVM
  :NVM ( -- )         \ a "headless" interrupt service routine 
    SAVEC   \ save the Forth state 

    [ PC_IDR 7 ]B@IF  \ IN3: count rising-falling edge sequence
      [ 1 cflg 0 ]B!
    ELSE
      [ cflg 0 ]B@IF
        [ 0 cflg 0 ]B!
        [ $3C C, CCNT C, ] \ inc cnt ; short, MSB
      THEN
    THEN

    [ PC_IDR 6 ]B@IF  \ IN4: count rising-falling edge sequence
      [ 1 cflg 1 ]B!
    ELSE
      [ cflg 1 ]B@IF
        [ 0 cflg 1 ]B!
        [ $3C C, CCNT 1+ C, ] \ inc cnt+1 ; short, LSB
      THEN
    THEN

    IRET  \ restore the Forth state and make an IRET
  [ OVERT ( xt ) INT_EXTI2 ! \ go to interpreter, finish dict entry, xt to ISR vector

  : cinit ( -- )  \ init counters cIN3 and cIN4
    \ IN4 (PC6) as interrupt input
    [ $9B C, ] \ SIM, make EXTI_CR1 writable
    [ 1 EXTI_CR1 4 ]B!  \ PC rising and falling edge
    [ 1 EXTI_CR1 5 ]B!
    [ 1 PC_CR2 7 ]B!    \ IN3 (PC7) interrupt enable
    [ 1 PC_CR2 6 ]B!    \ IN4 (PC6) interrupt enable
    [ $9A C, ] \ RIM
  ;

WIPE RAM

The code uses the fact that STM8 eForth is an STC Forth, which makes mixing assembler and Forth very easy. The word [ ... ]B@IF is a nice example since it extends the compiler with a native "bit-test relative-addressing" IF ... ELSE ... THEN structure. The extension resides in the RAM which is cleared by WIPE - which restores the normal behavior.

The code is, of course, very low-level, and it would have to be rewritten for any other architecture (it would work on STM8L, though). This isn't a big deal since it provides an abstraction for the "reliable counting device" as can be seen in the following example:

#require CCNT
#require ]B!
#require ]BCPL
#require A>

\res MCU: STM8S103
\res export PD_ODR PD_DDR PD_CR1

VARIABLE cIN3
VARIABLE cIN4

: in2tog ( -- )     \ IN2 (PD2) as toggle output for test
  [ 1 PD_DDR 2 ]B!  \ PD2 test output
  [ 1 PD_CR1 2 ]B!  \ PD2 push/pull
  [ PD_ODR 2 ]BCPL
;

: cread ( -- )   \ read and clear CCNT counters
  [ $4F C,             \  CLR A
    $31 C, CCNT , ]    \  EXG A, CCNT MSB
    A> cIN3 +!
  [ $4F C,             \  CLR A
    $31 C, CCNT 1+ , ] \  EXG A, CCNT LSB
    A> cIN4 +!
;

: ctest ( -- )  \ connect IN2 to IN3 and/or IN4 and run this a few times
  in2tog cread cIN3 ? in2tog cIN4 ?
;

cinit ctest

OK, admitedly, cread again uses in-line assembler code for reading and clearing the ISR counter in an ISR-safe way.

Conclusion

The problem of reliably counting pulse on two C0135 relay board inputs has been solved by using a single STM8 port EXTI and optimized STM8 eForth code.

Using the AWU is still an interesting option, and I may consider it for future applications.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment