RPM Counter

For the CNC router I'm building, I want to have an RPM counter on the spindle. By using an infrared opto-detector and an STM8S Discovery board I can now have an accurate readout of its speed. For the detector, I'm using the Vishay TCRT1000. The typically detecting distance is only 1mm but it is relatively cheap.The TCRT5000 is a longer-range alternative, and used in the resulting spindle controller project.

The tricky part is to make full use of the timers of the STM8S, which is quite hard considering the available documentation; There is some reasonable information on the functioning of the registers and hardware itself, but not on the Standard Peripheral Lib from ST. You basically have to dig into the .c and .h files of the library and compare what ST has done to what the datasheet describes.

I had problems especially when using both the Capture-Compare interrupt (to detect time between pulses) and the Overflow interrupt of Timer1 together.

For debugging interrupts, the ST-Link Swim tool is good but disturbs the timer results. Therefore I have connected a simple USB-to-serial converter so I can see the result on a serial terminal directly. In the final version, a HD44780 LCD will display the current RPM.

/* MAIN.C file */
#include "stm8s.h"
#include <string.h>
#include <stdio.h>
void SerialPutChar(char c);
void SerialPutString(char *s);
u16 measuredValues[8];
u32 avg;
u8 idx = 0;
u8 underSpeed = 0;
float rpm = 0;
float MultFactor = 120000000.0f;
void main(void){
    u32 i = 0;
    u16 printVal = 0;
    unsigned char str[21] = "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";
    
    //Switch to 16Mhz Crystal Osc clock
    CLK_ClockSwitchConfig(CLK_SWITCHMODE_AUTO, CLK_SOURCE_HSE, DISABLE, DISABLE);
    
    /* ------ Set up pulse counter timer ---- */
    TIM1_DeInit();
    /* Set TIMER1 to 2Mhz clock (=16Mhz/8) */
    TIM1_TimeBaseInit(7, TIM1_COUNTERMODE_UP, 65535, 0);
    /* TIM1 Chan1 is used (PC1) with rising edge and no divider nor filter */
  TIM1_ICInit(TIM1_CHANNEL_1, TIM1_ICPOLARITY_RISING, TIM1_ICSELECTION_DIRECTTI,  TIM1_ICPSC_DIV1, 0x00);
  /* Use Trigger Input (TRGI) to synchronize the counter is TI1FP1: Filtered timer input 1 (TI1FP1) */
  TIM1_SelectInputTrigger(TIM1_TS_TI1FP1);
  /* Reset mode - Rising edge of the selected trigger signal (TI1FP1 in this case) re-initializes the counter and
     generates an update of the registers. */
  TIM1_SelectSlaveMode(TIM1_SLAVEMODE_RESET);
  /* Overflow interrupt ; Capture1 interrupt enabled */
  TIM1_ITConfig(TIM1_IT_UPDATE | TIM1_IT_CC1, ENABLE);
  /* Generate an update event to ensure values are set */
  TIM1_GenerateEvent(TIM1_EVENTSOURCE_UPDATE);
  /* Clear CC1 and update interrupt flag */
  TIM1_ClearFlag(TIM1_FLAG_CC1 | TIM1_FLAG_UPDATE);
    /* Update URS bit of TIM_CR1 so only over/underflow generates an interrupt*/
    TIM1_UpdateRequestConfig(TIM1_UPDATESOURCE_REGULAR);
    /* Enable TIM1 */
  TIM1_Cmd(ENABLE);
    
    /* ------ Create square wave for testing purposes ----- */
    TIM2_TimeBaseInit(TIM2_PRESCALER_8, 3999);
    /* PWM1 Mode configuration: Channel1 */
    TIM2_OC1Init(TIM2_OCMODE_PWM1, TIM2_OUTPUTSTATE_ENABLE, 2000, TIM2_OCPOLARITY_HIGH);//31.25Hz
    TIM2_OC1PreloadConfig(ENABLE);
    TIM2_ARRPreloadConfig(ENABLE);
    /* TIM2 enable counter */
    TIM2_Cmd(ENABLE);
    /* ----------------------------------------------------- */
  /* Enable general interrupts */
  enableInterrupts();
    /* Set GPIO for LED LD1 (PD0)  */
    GPIO_DeInit(GPIOD);
    GPIO_Init(GPIOD, 0x01 , GPIO_MODE_OUT_PP_LOW_FAST);
  /* UART2 configured as follow: BaudRate = 115200 baud, Word Length = 8 Bits, One Stop Bit, No parity, Receive and transmit enabled, UART2 Clock disabled */
    UART2_DeInit();
  UART2_Init((u32)115200, UART2_WORDLENGTH_8D, UART2_STOPBITS_1, UART2_PARITY_NO, UART2_SYNCMODE_CLOCK_DISABLE, UART2_MODE_TXRX_ENABLE);
    
    /* Main loop */
  while (1){
        avg = 0;
        for (i=0;i<9;i++){
                avg = avg + measuredValues[i] +1;
        }
        avg = avg>>3;//Divide by 8
       
        rpm = MultFactor/((double)avg);
       
        printVal = (u16) avg;
        sprintf(str, "%.1f RPM\r\n", rpm);
        SerialPutString(str);
        sprintf(str, "%u Puls\r\n", printVal);
        SerialPutString(str);
       
        if (underSpeed==1)
            SerialPutString("U\r\n");
       
    }
}
void SerialPutChar(char c){
    UART2_SendData8(c);
    while ((UART2->SR & UART2_SR_TXE ) != UART2_SR_TXE );
}
void SerialPutString(char *s){
    while (*s != '\0'){
        SerialPutChar(*s);
        s++;
    }
}

/* INTERRUPT VECTOR TABLE FOR STM8 devices */

#include "stm8s_it.h"
typedef void @far (*interrupt_handler_t)(void);
struct interrupt_vector {
    unsigned char interrupt_instruction;
    interrupt_handler_t interrupt_handler;
};
@far @interrupt void NonHandledInterrupt (void){
    return;
}
extern void _stext();     /* startup routine */
struct interrupt_vector const _vectab[] = {
    {0x82, (interrupt_handler_t)_stext}, /* reset */
    {0x82, NonHandledInterrupt}, /* trap  */
    {0x82, NonHandledInterrupt}, /* irq0  */
    {0x82, NonHandledInterrupt}, /* irq1  */
    {0x82, NonHandledInterrupt}, /* irq2  */
    {0x82, NonHandledInterrupt}, /* irq3  */
    {0x82, NonHandledInterrupt}, /* irq4  */
    {0x82, NonHandledInterrupt}, /* irq5  */
    {0x82, NonHandledInterrupt}, /* irq6  */
    {0x82, NonHandledInterrupt}, /* irq7  */
    {0x82, NonHandledInterrupt}, /* irq8  */
    {0x82, NonHandledInterrupt}, /* irq9  */
    {0x82, NonHandledInterrupt}, /* irq10 */
    {0x82, TIM1_UPD_OVF_TRG_BRK_IRQHandler}, /* irq11 */
    {0x82, TIM1_CAP_COM_IRQHandler}, /* irq12 */
    {0x82, NonHandledInterrupt}, /* irq13 */
    {0x82, NonHandledInterrupt}, /* irq14 */
    {0x82, NonHandledInterrupt}, /* irq15 */
    {0x82, NonHandledInterrupt}, /* irq16 */
    {0x82, NonHandledInterrupt}, /* irq17 */
    {0x82, NonHandledInterrupt}, /* irq18 */
    {0x82, NonHandledInterrupt}, /* irq19 */
    {0x82, NonHandledInterrupt}, /* irq20 */
    {0x82, NonHandledInterrupt}, /* irq21 */
    {0x82, NonHandledInterrupt}, /* irq22 */
    {0x82, NonHandledInterrupt}, /* irq23 */
    {0x82, NonHandledInterrupt}, /* irq24 */
    {0x82, NonHandledInterrupt}, /* irq25 */
    {0x82, NonHandledInterrupt}, /* irq26 */
    {0x82, NonHandledInterrupt}, /* irq27 */
    {0x82, NonHandledInterrupt}, /* irq28 */
    {0x82, NonHandledInterrupt}, /* irq29 */
};
#include "stm8s_it.h"
extern u16 measuredValues[8];
extern u8 idx;
extern u8 underSpeed;
#ifdef _COSMIC_
@far @interrupt void TIM1_CAP_COM_IRQHandler(void)
#else /* _RAISONANCE_ */
void TIM1_CAP_COM_IRQHandler(void) interrupt 12
#endif /* _COSMIC_ */
{
    measuredValues[idx] = TIM1_GetCapture1();
    idx++;
    if (idx>8)
        idx=0;
    TIM1_ClearFlag(TIM1_FLAG_CC1);//Clear capture-compare flag
    underSpeed = 0;
    GPIO_WriteReverse(GPIOD, GPIO_PIN_0);//Toggle LED
}
#ifdef _COSMIC_
@far @interrupt void TIM1_UPD_OVF_TRG_BRK_IRQHandler(void)
#else /* _RAISONANCE_ */
void TIM1_UPD_OVF_TRG_BRK_IRQHandler(void) interrupt 11
#endif /* _COSMIC_ */
{
    measuredValues[idx] = 65535;
    idx++;
    if (idx>8)
        idx=0;
    underSpeed = 1;
    TIM1_ClearITPendingBit(TIM1_IT_UPDATE);
    GPIO_WriteReverse(GPIOD, GPIO_PIN_0);//Toggle LED
}