這篇文章的主旨其實在說明 OsalTimerScheduler 的應用, 只不過 MCU 換成了 STM32 Discovery 開發板. 如果讀者還沒閱讀過 "好用的 OSALTimerScheduler" 這篇文章, 麻煩情您先閱讀. 因為本篇文章是基於 OsalTimerScheduler.  在開始之前, 麻煩你先至 ST 官方網站下載 STM32 Discovery 的 DEMO Source Code ,  好像叫做 StmSnippets 吧, 我不確定.  裡面有 ADC, CLOCK, DAC, CAN, DBG, DMA, ExternalIT, FLASH, GPIO, HDMI, I2C, IRTIM, IWDG, OPTION_BYTES, RTC, SPI, TIMERS, TSC, USART, WWDG 各式各樣的 DEMO source code. 本篇文章使用的是 Timer 資料夾裡面的第二項 "02_UpcounterOnEach2RisingEdgesOnETR", 直接修改開發的.  話不多說, 我們直接先來看 鍵盤掃描. 

 

鍵盤掃描 kscan.c / .h
 

#ifndef __KEYSCAN_H__
#define __KEYSCAN_H__

#define KEYSCAN_POLL_MS        23
typedef void (*KS_FUNC)(unsigned char num, unsigned char flag);

#define KEY_PRESSED                    0
#define KEY_CLICK_RELEASED            1
#define KEY_HOLD_RELEASED            2

void InitKeyScan(const KS_FUNC cb);
void KsTimer(unsigned int nId);
void StartKeyScan(void);
void StopKeyScan(void);

#endif //__KEYSCAN_H__
#include "stm32f0xx.h"

#include "osal_timer.h"
#include "keyscan.h"
#include "buzzer.h"

#ifndef BIT0
#define BIT0                                                            1
#define BIT1                                                            2
#define BIT2                                                            4
#define BIT3                                                            8
#define BIT4                                                            16
#define BIT5                                                            32
#define BIT6                                                            64
#define BIT7                                                            128

#define KS_ROW_NUM        8
#define KS_COL_NUM        8
#define KS_FF            (0XFF >> (8 - KS_ROW_NUM))

static unsigned char KeyNum = 0;
static unsigned char KeyPressed = 0;
static unsigned char P1;
static KS_FUNC callback = 0;

extern void OnOsalEvent(unsigned int nId);

///////////////////////////////////////////////////////////////////////////////
//
///////////////////////////////////////////////////////////////////////////////
void InitKeyScan(const KS_FUNC cb) {
    RCC->AHBENR |= RCC_AHBENR_GPIOBEN;

// INPUT
#if KS_COL_NUM >= 1
    GPIOB->MODER = (GPIOB->MODER & ~(GPIO_MODER_MODER0));
#endif // KS_ROW_NUM
#if KS_COL_NUM >= 2
    GPIOB->MODER = (GPIOB->MODER & ~(GPIO_MODER_MODER1));
#endif // KS_ROW_NUM
#if KS_COL_NUM >= 3
    GPIOB->MODER = (GPIOB->MODER & ~(GPIO_MODER_MODER2));
#endif // KS_ROW_NUM
#if KS_COL_NUM >= 4
    GPIOB->MODER = (GPIOB->MODER & ~(GPIO_MODER_MODER3));
#endif // KS_ROW_NUM
#if KS_COL_NUM >= 5
    GPIOB->MODER = (GPIOB->MODER & ~(GPIO_MODER_MODER4));
#endif // KS_ROW_NUM
#if KS_COL_NUM >= 6
    GPIOB->MODER = (GPIOB->MODER & ~(GPIO_MODER_MODER5));
#endif // KS_ROW_NUM
#if KS_COL_NUM >= 7
    GPIOB->MODER = (GPIOB->MODER & ~(GPIO_MODER_MODER6));
#endif // KS_ROW_NUM
#if KS_COL_NUM >= 8
    GPIOB->MODER = (GPIOB->MODER & ~(GPIO_MODER_MODER7));
#endif // KS_ROW_NUM

// OUTPUT, ROW, 掃瞄線
#if KS_ROW_NUM >= 1
    GPIOB->MODER = (GPIOB->MODER & ~GPIO_MODER_MODER8) | GPIO_MODER_MODER8_0;
#endif // KS_ROW_NUM
#if KS_ROW_NUM >= 2
    GPIOB->MODER = (GPIOB->MODER & ~GPIO_MODER_MODER9) | GPIO_MODER_MODER9_0;
#endif // KS_ROW_NUM
#if KS_ROW_NUM >= 3
    GPIOB->MODER = (GPIOB->MODER & ~GPIO_MODER_MODER10) | GPIO_MODER_MODER10_0;
#endif // KS_ROW_NUM
#if KS_ROW_NUM >= 4
    GPIOB->MODER = (GPIOB->MODER & ~GPIO_MODER_MODER11) | GPIO_MODER_MODER11_0;
#endif // KS_ROW_NUM
#if KS_ROW_NUM >= 5
    GPIOB->MODER = (GPIOB->MODER & ~GPIO_MODER_MODER12) | GPIO_MODER_MODER12_0;
#endif // KS_ROW_NUM
#if KS_ROW_NUM >= 6
    GPIOB->MODER = (GPIOB->MODER & ~GPIO_MODER_MODER13) | GPIO_MODER_MODER13_0;
#endif // KS_ROW_NUM
#if KS_ROW_NUM >= 7
    GPIOB->MODER = (GPIOB->MODER & ~GPIO_MODER_MODER14) | GPIO_MODER_MODER14_0;
#endif // KS_ROW_NUM
#if KS_ROW_NUM >= 8
    GPIOB->MODER = (GPIOB->MODER & ~GPIO_MODER_MODER15) | GPIO_MODER_MODER15_0;
#endif // KS_ROW_NUM

    if (cb)
        callback = cb;
}

// 掃描 scan line
static void PollKeyScan(unsigned char line) {
    static int long_buzz_flag = 0;
    int longPress = 0;

    unsigned char i = 0;
    unsigned char B = 0;
    unsigned short P0;
    static unsigned char O[] = {0,0,0,0,0,0,0,0,0,0,0,0};
    static unsigned int T[] = {0,0,0,0,0,0,0,0,0,0,0,0};
    unsigned int now = 0;

    if (line >= 8)                            return;
    P0 = 0XFF >> (8 - KS_ROW_NUM);
    P0 &= ~(1<<line);
    P0 <<= 8;
    GPIOB->ODR |= P0;

    P1 = (unsigned char)(GPIOB->IDR & 0X00FF);
    for (i = 0; i < KS_COL_NUM; i++) {
        B = (1 << i);

        // KEY SCAN 按下
        if ((KS_FF^B) == P1 && KS_FF == O[line]) {
            T[line] = GetOsalTimerTick();

            KeyNum = (KS_COL_NUM * line + i);
            KeyPressed = KEY_PRESSED;
            if (callback)                    callback(KeyNum, KeyPressed);
        }

        // KEY SCAN 按下超過 3秒
        else if ((KS_FF^B) == P1 && (KS_FF^B) == O[line]) {
            now = GetOsalTimerTick();
            if (now - T[line] >= 3000) {
                if (0 == long_buzz_flag) {
                    long_buzz_flag = 1;
                    // BUZZER ON
                    EnableBuzzer(1);
                    // 59 mini-second 後, BUZZER OFF
                    SetOsalTimer(BUZZ_OFF_OSALTIMER_ID, 59, OnOsalEvent);
                }
            }
        }

        // KEY SCAN 放開
        else if (KS_FF == P1 && (KS_FF^B) == O[line]) {
            now = GetOsalTimerTick();
            if (now - T[line] >= 3000)        longPress = 1, long_buzz_flag = 0;

            KeyNum = (KS_COL_NUM * line + i);
            if (longPress)                    KeyPressed = KEY_HOLD_RELEASED;
            else                            KeyPressed = KEY_CLICK_RELEASED;
            if (callback)                    callback(KeyNum, KeyPressed);
        }
    }
    O[line] = P1;
}

void KsTimer(unsigned int nId) {
    static unsigned char scan_line = 0;

    PollKeyScan(scan_line++), scan_line %= KS_ROW_NUM;
    SetOsalTimer(START_KEYSCAN_OSALTIMER_ID, KEYSCAN_POLL_MS, KsTimer);
    return;
}

void StartKeyScan(void) {
    SetOsalTimer(START_KEYSCAN_OSALTIMER_ID, KEYSCAN_POLL_MS, KsTimer);
}

void StopKeyScan(void) {
    KillOsalTimer(START_KEYSCAN_OSALTIMER_ID);
}

kscan.h 定義了: POLLING 周期 (23 mini-seconds), KEY_PRESSED, KEY_CLICK_RELEASED, KEY_HOLD_RELEASED

#define KEYSCAN_POLL_MS        23
#define KEY_PRESSED                    0
#define KEY_CLICK_RELEASED            1
#define KEY_HOLD_RELEASED            2

鍵盤掃描參考線路改天我有空時再 Update 上來, DEMO 的 CODE 是 8X8, 8個 INPUT 8個 OUTPUT, 所以 KeyNum 範圍會是 0~63. 我在 InitKeyScan() 初始化它們. 掃描線我使用 OsalTimer 來 POLLING, 由 StartKeyScan() 來觸發. StartKeyScan() 會使用 SetOsalTimer() 設置一個 OsalTimer, 從此以後 CallBack 函數 KsTimer() 就以 23 mini-seconds的週期持續地被呼叫. 換句話說, 掃描線 PollKeyScan() 以此頻率持續地被呼叫. 程式在 Source Code 都說明得蠻清楚的 包括: KEY SCAN 按下, KEY SCAN 按下超過 3秒, KEY SCAN 放開. 值得一題的是在這裡 KeyScan 按下放開後 59 mini-seconds 後, 蜂鳴器會響一聲. 鍵盤掃描的 CallBack 函數 由 InitKeyScan() 傳入, 之後我們再來回顧. 

 

蜂鳴器 buzzer.c / .h
 

#include "stm32f0xx.h"
#include "buzzer.h"

#ifndef TIMx_BASE 
#define TIMx_BASE        TIM1_BASE
#define TIMx                ((TIM_TypeDef *) TIMx_BASE)
#endif

///////////////////////////////////////////////////////////////////////////////
//
///////////////////////////////////////////////////////////////////////////////

void InitBuzzer(void) {
    RCC->APB2ENR |= RCC_APB2ENR_TIM1EN;
    RCC->AHBENR |= RCC_AHBENR_GPIOAEN;
    GPIOA->MODER = (GPIOA->MODER & ~(GPIO_MODER_MODER8)) | (GPIO_MODER_MODER8_1);
    GPIOA->AFR[1] |= 0x02;
}

void SetBuzzerFreq(unsigned int freq) {
    unsigned param;

    param = (4800000 / freq);
    param -= 1;
    TIMx->PSC |= param;
    TIMx->ARR = param;
    TIMx->CCR1 = param;
    TIMx->CCMR1 |= TIM_CCMR1_OC1M_0 | TIM_CCMR1_OC1M_1;

    TIMx->CCER |= TIM_CCER_CC1E;
}

void EnableBuzzer(unsigned char val) {
    if (val) {
        TIMx->BDTR |= TIM_BDTR_MOE;
        TIMx->CR1 |= TIM_CR1_CEN;
        return;
    }
    TIMx->BDTR &= ~TIM_BDTR_MOE;
    TIMx->CR1 &= ~TIM_CR1_CEN;
}

蜂鳴器其實它是一個 PWM 訊號, 軟硬體設計於 TIMER1. InitBuzzer() 初始化 TIMER1 ; SetBuzzerFreq() 設定 頻率. 
EnableBuzzer(1) 啟動 PWM 訊號;  而 EnableBuzzer(0) 是停止 PWM 訊號. 

 

風扇 fan.c / .h
 

#ifndef __FAN_H__
#define __FAN_H__

#define  FAN_CTRL_OFF                0
#define  FAN_CTRL_9V                1
#define  FAN_CTRL_12V                2

void InitFanCtrl(void);
void FanCtrl(unsigned char level);

#endif //__FAN_H__
#include "stm32f0xx.h"
#include "fan.h"

void InitFanCtrl(void) {
    RCC->AHBENR |= RCC_AHBENR_GPIOAEN;
    GPIOA->MODER = (GPIOA->MODER & ~GPIO_MODER_MODER9) | GPIO_MODER_MODER9_0;
    GPIOA->MODER = (GPIOA->MODER & ~GPIO_MODER_MODER10) | GPIO_MODER_MODER10_0;
}

void FanCtrl(unsigned char level) {
    if (FAN_CTRL_OFF == level) {
        GPIOB->ODR &= ~GPIO_ODR_9;
        return;
    }
    if (FAN_CTRL_9V == level) {
        GPIOB->ODR |= GPIO_ODR_9;
        GPIOB->ODR &= ~GPIO_ODR_10;
        return;
    }
    if (FAN_CTRL_12V == level) {
        GPIOB->ODR |= GPIO_ODR_9;
        GPIOB->ODR |= GPIO_ODR_10;
        return;
    }
}

 

緊急停止 esp.c / .h
 

#ifndef __ESP_H__
#define __ESP_H__

#define ESP_POLL_MS            13
#define ESP_PORT            GPIOA->IDR
#define ESP_PIN_MASK        (1<<13)

///////////////////////////////////////////////////////////////////////////////
//
///////////////////////////////////////////////////////////////////////////////

void InitEsp(void);
void EspTimer(unsigned int nId);
unsigned char GetEspStatus(void);

void StartEspMonitor(void);
void StopEspMonitor(void);

#endif //__ESP_H__
#include "stm32f0xx.h"
#include "esp.h"
#include "osal_timer.h"

///////////////////////////////////////////////////////////////////////////////
//
///////////////////////////////////////////////////////////////////////////////

static unsigned char NF = 0;
static unsigned char LF = 0;
unsigned char esp_status = 0;

///////////////////////////////////////////////////////////////////////////////
//
///////////////////////////////////////////////////////////////////////////////

extern void OnOsalEvent(unsigned int nId);

///////////////////////////////////////////////////////////////////////////////
//
///////////////////////////////////////////////////////////////////////////////

void InitEsp(void) {
    RCC->AHBENR |= RCC_AHBENR_GPIOAEN;
    GPIOA->MODER = (GPIOA->MODER & ~(GPIO_MODER_MODER13));
}

unsigned char GetEspStatus(void) {
    return esp_status;
}

void EspTimer(unsigned int nId) {
    if (ESP_PORT & ESP_PIN_MASK)        NF = 0;
    else                                NF = 1;

    if (1 == LF && 0 == NF) {
        esp_status = 0;
        SetOsalTimer(ESP_NOTIFY_OSALTIMER_ID, 0, OnOsalEvent);
    }
    if (0 == LF && 1 == NF) {
        esp_status = 1;
        SetOsalTimer(ESP_NOTIFY_OSALTIMER_ID, 0, OnOsalEvent);
    }
    SetOsalTimer(START_ESP_MONITOR_OSALTIMER_ID, ESP_POLL_MS, EspTimer);
    return;
}

void PollEsp(void){ }

void StartEspMonitor(void) {
    SetOsalTimer(START_ESP_MONITOR_OSALTIMER_ID, ESP_POLL_MS, EspTimer);
}

void StopEspMonitor(void) {
    KillOsalTimer(START_ESP_MONITOR_OSALTIMER_ID);
}

esp.jpg


跑步機的緊急停止大概是長得這樣, 如上圖所示. 通常是掛在脖子上, 萬一有緊急狀況 (緊急停止開關脫落) , 跑步機會緊急停止. 
上述靜態變數 NF/LF , N 代表的是 NEW , L 代表的是 LAST. InitEsp() 初始化 GPIO 設為 INPUT. 從這段 Code 看起來是 Normal High Active Low, 從跑步機來看也就是 Normal 緊急停止開關是插上去的, Active 緊急狀態時緊急停止開關是脫落的. OnOsalEvent() 我們待會兒再談. 

 

穿戴裝置 Salutron 心跳 salutron.c / .h
 

#ifndef __SALUTRON_H__
#define __SALUTRON_H__

#define SALUTRON_POLL_MS            1

///////////////////////////////////////////////////////////////////////////////
//
///////////////////////////////////////////////////////////////////////////////

void InitSalutron(void);
unsigned char calc_whr(void);
unsigned char calc_chr(void);
void PollSalutronHr(void);

#endif //__SALUTRON_H__
#include "stm32f0xx.h"
#include "salutron.h"

#define SALUTRON_HR_BUFFER_COUNT    2
#define SALUTRON_TIMEOUT            600

#define WHR_PORT                    GPIOA->IDR
#define CHR_PORT                    GPIOA->IDR

#define WHR_PIN_MASK                (1<<11)
#define CHR_PIN_MASK                (1<<12)


/////////////////////////////
// WIRELESS HEART-RATE
static unsigned char WHR = 0;

// CONTACT HEART-RATE
static unsigned char CHR = 0;


///////////////////////////////////////////////////////////////////////////////
//
///////////////////////////////////////////////////////////////////////////////

void InitSalutron(void) {
    RCC->AHBENR |= RCC_AHBENR_GPIOAEN;
    GPIOA->MODER = (GPIOA->MODER & ~(GPIO_MODER_MODER11));
    GPIOA->MODER = (GPIOA->MODER & ~(GPIO_MODER_MODER12));

    // Select Pull-down
    //GPIOA->PUPDR = (GPIOA->PUPDR & ~(GPIO_PUPDR_PUPDR0));

#if 0
    struct GPIO_TypeDef;
#endif

}

// 傳回 WIRELESS-HR, 建議 100~500 ms 呼叫一次
unsigned char calc_whr(void) {
    return (unsigned char)WHR;
}

// 傳回 CONTACT-HR, 建議 100~500 ms 呼叫一次
unsigned char calc_chr(void) {
    return (unsigned char)CHR;
}

// 1 or 10 ms 呼叫一次
void PollSalutronHr(void) {
    unsigned char i = 0;
    unsigned int sample = 0;

    // NEW WIRELESS-HR PIN STATE
    static unsigned char NW = 0;
    // NEW CONTACT-HR PIN STATE
    static unsigned char NC = 0;
    // LAST WIRELESS-HR PIN STATE
    static unsigned char LW = 0;
    // LAST CONTACT-HR PIN STATE
    static unsigned char LC = 0;

    static unsigned int pulseW[] = {0,0,0,0,0,0,0,0};
    static unsigned int pulseC[] = {0,0,0,0,0,0,0,0};

    static unsigned int tick = 0;

    static unsigned int WhrUpTick = 0;
    static unsigned int ChrUpTick = 0;


/////////////////////////////
    if (WHR_PORT & WHR_PIN_MASK)        NW = 0;
    else                                    NW = 1;
    if (CHR_PORT & CHR_PIN_MASK)            NC = 0;
    else                                    NC = 1;

/////////////////////////////
    if (0 == LW && 1 == NW) {
        // wireless pin rising edge
        WhrUpTick = tick;
    }
    else if (1 == LW && 0 == NW) {
        // wireless pin falling edge
        if (10 == SALUTRON_POLL_MS) {
            if (tick - WhrUpTick > 1 && tick - WhrUpTick < 4) {
                for (i = SALUTRON_HR_BUFFER_COUNT - 1; i > 0; i--)
                    pulseW[i] = pulseW[i - 1];
                pulseW[0] = tick;
                if (pulseW[0] > pulseW[SALUTRON_HR_BUFFER_COUNT - 1] && tick > 301)
                    sample = ((SALUTRON_HR_BUFFER_COUNT - 1) * 60 * 100)  / (pulseW[0] - pulseW[SALUTRON_HR_BUFFER_COUNT - 1]);
                if (sample < 45)                        { sample = 0; }
                else if (sample > 220)                    { sample = 220; }
                if (sample > 0)
                    WHR = (unsigned char)sample;
            }
        }
        if (1 == SALUTRON_POLL_MS) {
            if (tick - WhrUpTick > 18 && tick - WhrUpTick < 22) {
                for (i = SALUTRON_HR_BUFFER_COUNT - 1; i > 0; i--)
                    pulseW[i] = pulseW[i - 1];
                pulseW[0] = tick;
                if (pulseW[0] > pulseW[SALUTRON_HR_BUFFER_COUNT - 1] && tick > 3001)
                    //sample = ((SALUTRON_HR_BUFFER_COUNT - 1) * 60 * 1000)  / (pulseW[0] - pulseW[SALUTRON_HR_BUFFER_COUNT - 1]);
                    sample = (unsigned int)(((double)(SALUTRON_HR_BUFFER_COUNT - 1) * 60.00F * 1000.00F)  / (double)(pulseW[0] - pulseW[SALUTRON_HR_BUFFER_COUNT - 1]));
                if (sample < 45)                        { sample = 0; }
                else if (sample > 220)                    { sample = 220; }
                if (sample > 0)
                    WHR = (unsigned char)sample;
            }
        }
    }
    else if ( (0 == LW && 0 == NW) || (1 == LW && 1 == NW) ) {
        // wireless pin smooth
        if (10 == SALUTRON_POLL_MS) {
            if (tick - pulseW[SALUTRON_HR_BUFFER_COUNT - 1] > SALUTRON_TIMEOUT && 0 != pulseW[SALUTRON_HR_BUFFER_COUNT - 1]) {
                for (i = 0; i < SALUTRON_HR_BUFFER_COUNT; i++)
                    pulseW[i] = 0;
                WHR = 0;
            }
        }
        if (1 == SALUTRON_POLL_MS) {
            if (tick - pulseW[SALUTRON_HR_BUFFER_COUNT - 1] > SALUTRON_TIMEOUT * 10 && 0 != pulseW[SALUTRON_HR_BUFFER_COUNT - 1]) {
                for (i = 0; i < SALUTRON_HR_BUFFER_COUNT; i++)
                    pulseW[i] = 0;
                WHR = 0;
            }
        }
    }

/////////////////////////////
    if (0 == LC && 1 == NC) {
        ChrUpTick = tick;
    }
    else if (1 == LC && 0 == NC) {
        if (10 == SALUTRON_POLL_MS) {
            if (tick - ChrUpTick > 1 && tick - ChrUpTick < 4) {
                for (i = SALUTRON_HR_BUFFER_COUNT - 1; i > 0; i--)
                    pulseC[i] = pulseC[i - 1];
                pulseC[0] = tick;
                if (pulseC[0] > pulseC[SALUTRON_HR_BUFFER_COUNT - 1] && tick > 301)
                    sample = ((SALUTRON_HR_BUFFER_COUNT - 1) * 60 * 100)  / (pulseC[0] - pulseC[SALUTRON_HR_BUFFER_COUNT - 1]);
                if (sample < 45)                        { sample = 0; }
                else if (sample > 220)                    { sample = 220; }
                if (sample > 0)
                    CHR = (unsigned char)sample;
            }
        }
        if (1 == SALUTRON_POLL_MS) {
            if (tick - ChrUpTick > 18 && tick - ChrUpTick < 22) {
                for (i = SALUTRON_HR_BUFFER_COUNT - 1; i > 0; i--)
                    pulseC[i] = pulseC[i - 1];
                pulseC[0] = tick;
                if (pulseC[0] > pulseC[SALUTRON_HR_BUFFER_COUNT - 1] && tick > 3001)
                    //sample = ((SALUTRON_HR_BUFFER_COUNT - 1) * 60 * 1000)  / (pulseC[0] - pulseC[SALUTRON_HR_BUFFER_COUNT - 1]);
                    sample = (unsigned int)(((double)(SALUTRON_HR_BUFFER_COUNT - 1) * 60.00F * 1000.00F)  / (double)(pulseC[0] - pulseC[SALUTRON_HR_BUFFER_COUNT - 1]));
                if (sample < 45)                        { sample = 0; }
                else if (sample > 220)                    { sample = 220; }
                if (sample > 0)
                    CHR = (unsigned char)sample;
            }
        }
    }
    else if ( (0 == LC && 0 == NC) || (1 == LC && 1 == NC) ) {
        if (10 == SALUTRON_POLL_MS) {
            if (tick - pulseC[SALUTRON_HR_BUFFER_COUNT - 1] > SALUTRON_TIMEOUT && 0 != pulseC[SALUTRON_HR_BUFFER_COUNT - 1]) {
                for (i = 0; i < SALUTRON_HR_BUFFER_COUNT; i++)
                    pulseC[i] = 0;
                CHR = 0;
            }
        }
        if (1 == SALUTRON_POLL_MS) {
            if (tick - pulseC[SALUTRON_HR_BUFFER_COUNT - 1] > SALUTRON_TIMEOUT * 10 && 0 != pulseC[SALUTRON_HR_BUFFER_COUNT - 1]) {
                for (i = 0; i < SALUTRON_HR_BUFFER_COUNT; i++)
                    pulseC[i] = 0;
                CHR = 0;
            }
        }
    }

/////////////////////////////
    LC = NC;
    LW = NW;

    tick++;
}

salutron.jpg


這裡實做的 Salution 心跳分為 WIRELESS 以及 CONTACT. WIRELESS 大概長得如上圖所示; 而 CONTACT 大概都做在跑步機的把手上. 
calc_whr() 計算 WIRELESS 心跳; calc_chr() 則計算 CONTACT 心跳; 值得一題的是 WIRELESS及CONTACT HR 都有做平滑處理. 邏輯我就不再多做說明了; 相信讀者的數學能力並不比我差. 

 

ISR (UART ISR / SYSTEM ISR)
 

....
///////////////////////////////////////////////////////////////////////////////
//            Cortex-M0 Processor Exceptions Handlers
///////////////////////////////////////////////////////////////////////////////

// UART 中斷服務常式
void USART1_IRQHandler(void) {
    uint8_t characterReceive = 0;

    // UART 資料傳送完畢
    if((USART1->ISR & USART_ISR_TC) == USART_ISR_TC) {
        if (0 == utleft)    USART1->ICR |= USART_ICR_TCCF, put = utb;
        else                USART1->TDR = *put++, utleft--;
    }
    // UART 資料接收
    if((USART1->ISR & USART_ISR_RXNE) == USART_ISR_RXNE) {
        characterReceive = (uint8_t)(USART1->RDR);
        OnUartRx((unsigned char)characterReceive);
    }
    //else { NVIC_DisableIRQ(USART1_IRQn); }
}

void NMI_Handler(void){ }

void HardFault_Handler(void){
    while (1);
}

void SVC_Handler(void) {}

void PendSV_Handler(void) {}

//#define TMR0_IRQHandler        SysTick_Handler
// SYSTEM TICK  中斷服務常式, 1-ms 中斷 1 次
void SysTick_Handler(void) {
    time1ms_flag = 1;
    IsrPollOsalTimer();
}

 

先來看 ISR 函數. 靜態變數待會再看. SYSTEM TICK  中斷服務常式 如同之前文章所介紹 呼叫 IsrPollTimer() . Uart ISR 則是處理 Uart 傳送及接收. 本篇所 DEMO 的 Source 重要的函數應該都是以 On開頭, 譬如說 OnUartRx() 代表 Uart 收到資料時.  

 

On 開頭的函數 main.c
 

/**
  ******************************************************************************
  * @file    02_UpcounterOnEach2RisingEdgesOnETR/main.c 
  * @author  MCD Application Team
  * @version V1.0.0
  * @date    05-March-2014
  * @brief   This code example shows how to configure the TIMx ETR   
  *          to use it as external clock
  *
 ===============================================================================
                    #####       MCU Resources     #####
 ===============================================================================
   - RCC
   - TIM1
   - GPIO PA12 for TIM1 ETR
   - GPIO PC8 and PC9
   - SYSTICK (to manage led blinking)

 ===============================================================================
                    ##### How to use this example #####
 ===============================================================================
    - this file must be inserted in a project containing  the following files :
      o system_stm32f0xx.c, startup_stm32f0xx.s
      o stm32f0xx.h to get the register definitions
      o CMSIS files
 ===============================================================================
                    ##### How to test this example #####
 ===============================================================================
    - This example configures the TIM1 in order to increment the counter
      once two rising edges have occurred on its external trigger (ETR).
      This channel is used as external clock.
      The GPIO PA12, corresponding to TIM1_ETR, is configured as alternate function 
      and the AFR2 is selected.
      The solder bridge SB20 must be soldered to wire the connector pin 
      to the MCU pin.
      To test this example, the user button must be conected to PA12, this is 
      easily done by wiring PA0 to PA12 thru the connector pins.
      The TIM1 counter is loaded in a global variable named Counter which can be 
      monitored in live. The counter value is also used to blink the led a number
      of times equals to its value while the green led is on.
    - This example can be easily ported on another timer having the ETR feature
      by modifying TIMx definition.
      
  *    
  ******************************************************************************
  * @attention
  *
  * <h2><center>&copy; COPYRIGHT 2014 STMicroelectronics</center></h2>
  *
  * Licensed under MCD-ST Liberty SW License Agreement V2, (the "License");
  * You may not use this file except in compliance with the License.
  * You may obtain a copy of the License at:
  *
  *        http://www.st.com/software_license_agreement_liberty_v2
  *
  * Unless required by applicable law or agreed to in writing, software 
  * distributed under the License is distributed on an "AS IS" BASIS, 
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  *
  ******************************************************************************
  */
///////////////////////////////////////////////////////////////////////////////
//
///////////////////////////////////////////////////////////////////////////////

//#include "cmsis_os.h"
#include "stm32f0xx.h"
#include "osal_timer.h"
#include "buzzer.h"
#include "salutron.h"
#include "keyscan.h"
#include "esp.h"
#include "fan.h"


//////////////////////////////////////////////////////////////////////
// define
//////////////////////////////////////////////////////////////////////

#define PT0_SOF_0            0
#define PT0_SOF_1            1
#define PT0_LEN_H            2
#define PT0_LEN_L            3
#define PT0_RESERVE            4
#define PT0_D_RQ_RP            5
#define PT0_D_ID_H            6
#define PT0_D_ID_L            7
#define PT0_DATA_START        8

#define TIMx_BASE            TIM1_BASE
#define TIMx                    ((TIM_TypeDef *)TIMx_BASE)

#define MAX_TX_BUFFER        128
#define MAX_RX_BUFFER        128


///////////////////////////////////////////////////////////////////////////////
// 靜態變數
///////////////////////////////////////////////////////////////////////////////

// 1 MINI-SECOND 旗標
static unsigned char time1ms_flag = 0;

// UART 傳送 BUFFER
static unsigned char utb[MAX_TX_BUFFER+8];
static unsigned char *put = utb;
static unsigned char utleft = 0;


///////////////////////////////////////////////////////////////////////////////
//
///////////////////////////////////////////////////////////////////////////////

// UART 傳送
// 參數0: 傳出 BUFFER, 參數1: 傳出長度
int UartTx(const unsigned char * OUT buff, int len) {
    if (0 == buff)        return 0;
    if (0 == len)        return 0;
    if (len > 128)        return 0;
    // 拷貝 BUFF 至 靜態變數 ut
    osal_memcpy((put = utb), (const void *)buff, (utleft = len));
    USART1->TDR = *put++, utleft--;
    return len;
}


///////////////////////////////////////////////////////////////////////////////
//
///////////////////////////////////////////////////////////////////////////////

// UART 設定 step 1
__INLINE void InitGpioUart(void) {
    /* Enable the peripheral clock of GPIOA */
    RCC->AHBENR |= RCC_AHBENR_GPIOAEN;

    /* GPIO configuration for USART1 signals */
    /* (1) Select AF mode (10) on PA9 and PA10 */
    /* (2) AF1 for USART1 signals */
    GPIOA->MODER = (GPIOA->MODER & ~(GPIO_MODER_MODER14 |GPIO_MODER_MODER15))\
                 | (GPIO_MODER_MODER14_1 | GPIO_MODER_MODER15_1); /* (1) */
    GPIOA->AFR[1] = (GPIOA->AFR[1] &~ (GPIO_AFRH_AFRH6 | GPIO_AFRH_AFRH7))\
                  | (1 << (1 * 4)) | (1 << (2 * 4)); /* (2) */
}

// UART 設定 step 2
__INLINE void InitUart(void) {
    // Enable the peripheral clock USART1
    RCC->APB2ENR |= RCC_APB2ENR_USART1EN;

    /* Configure USART1 */
    /* (1) oversampling by 16, 9600 baud */
    /* (2) 8 data bit, 1 start bit, 1 stop bit, no parity, reception mode */
    USART1->BRR = 480000 / 96; /* (1) */
    USART1->CR1 = USART_CR1_RXNEIE | USART_CR1_RE | USART_CR1_UE; /* (2) */

#if 1
    while((USART1->ISR & USART_ISR_TC) != USART_ISR_TC);
    //Transmission Complete Clear Flag
    USART1->ICR |= USART_ICR_TCCF;
    // Transmission Complete Interrupt Enable
    USART1->CR1 |= USART_CR1_TCIE;
#endif

    /* Configure IT */
    /* (3) Set priority for USART1_IRQn */
    /* (4) Enable USART1_IRQn */
    NVIC_SetPriority(USART1_IRQn, 0); /* (3) */
    NVIC_EnableIRQ(USART1_IRQn); /* (4) */
}


///////////////////////////////////////////////////////////////////////////////
//
///////////////////////////////////////////////////////////////////////////////

// 包裝為 PROTOCOL
// 參數0: DESCRIPTOR
// 參數1: ACK:1, REQ:0
// 參數2: DATA 長度
// 參數3: 傳出 DATA POINTER
// 參數4: 傳回 包裝好之 PROTOCOL DATA
// 返回值: 傳回 包裝好之 PROTOCOL DATA 長度
int pack_ui_frame(unsigned char did, unsigned char ack, unsigned short len, const unsigned char * OUT in, unsigned char * IN out) {
    unsigned short i;
    unsigned short crc = 0;

    if (0 == out)                        return 0;
    for (i = 0; i < len + 10; i++)        *(out + i) = 0;

// SOF FIELDS
    out[PT0_SOF_0] = 0XFE, out[PT0_SOF_1] = 0XFE;

// LEN FIELDS
    out[PT0_LEN_H] = HI_UINT16(len), out[PT0_LEN_L] = LO_UINT16(len);

// RESERVED
    out[PT0_RESERVE] = 0;

// REQ or RSP
    out[PT0_D_RQ_RP] = ack;

// DESCRIPTOR ID FIELDS
    out[PT0_D_ID_H] = HI_UINT16(did), out[PT0_D_ID_L] = LO_UINT16(did);

// DATA FIELDS
    for (i = 0; i < len; i++)
        *(out + PT0_DATA_START + i) = *(in + i);

// CRC16 FIELDS
    crc = ModbusCrc16(out, len + 8);
    out[PT0_DATA_START + len] = HI_UINT16(crc), out[1 + PT0_DATA_START + len] = LO_UINT16(crc);
    return (len + 2 + PT0_DATA_START);
}

// 從 UI 送達之 PROTOCOL 封包
// 參數0: 傳出 PROTOCOL DATA POINTER
// 參數1: PROTOCOL DATA 長度
void OnUserMessage(const unsigned char * OUT packet, int length) {
    int len = 0;
    unsigned char c = 0;
    static unsigned char frame[MAX_RX_BUFFER+8];
    unsigned short did;
    unsigned int LEN;

    // DESCRIPTOR
    did = BUILD_UINT16(packet[PT0_D_ID_L], packet[PT0_D_ID_H]);
    LEN = BUILD_UINT16(packet[PT0_LEN_L], packet[PT0_LEN_H]);

    // HELLO REQ
    if (0X0000 == did) {
        // HELLO RSP
        len = pack_ui_frame(did, 1, 0, 0, frame);
        UartTx(frame, len);
        return;
    }

    // BUZZ REQ
    if (0X0018 == did && 1 == LEN) {
        c = frame[PT0_DATA_START];
        if (c)    EnableBuzzer(1);
        else        EnableBuzzer(0);

        // BUZZ RSP
        len = pack_ui_frame(did, 1, 1, &c, frame);
        UartTx(frame, len);
        return;
    }
}

// UART DATA RECEIEVE
static void OnUartRx(unsigned char ch) {
    unsigned short crc = 0;
    unsigned short chk = 1;

    static unsigned char frame[MAX_RX_BUFFER+8];
    static unsigned short LEN = 0;
    static int offs = 0;

    if (PT0_SOF_0 == offs) {
        if (ch == 0XFE) {
            frame[offs++] = ch;
        }
        return;
    }
    if (PT0_SOF_1 == offs) {
        if (ch == 0XFE) {
            frame[offs++] = ch;
            return;
        }
        offs = 0;
        return;
    }
    if (PT0_LEN_H == offs) {
        frame[offs++] = ch;
        return;
    }
    if (PT0_LEN_L == offs) {
        frame[offs++] = ch;
        LEN = BUILD_UINT16(frame[PT0_LEN_L], frame[PT0_LEN_H]);
        if (LEN > MAX_RX_BUFFER)        offs = 0;
        return;
    }
    if (PT0_D_RQ_RP == offs) {
        if (0 != ch) {
            offs = 0;
            return;
        }
        frame[offs++] = ch;
        return;
    }
    if (offs >= LEN + 2 + PT0_DATA_START) {
        frame[offs++] = ch;
        crc = (unsigned short)ModbusCrc16(&frame[PT0_SOF_0], LEN + PT0_DATA_START);
        chk = BUILD_UINT16(frame[1 + PT0_DATA_START + LEN], frame[PT0_DATA_START + LEN]);
        if (crc == chk)
            OnUserMessage(frame, offs);
        offs = 0;
        return;
    }
    frame[offs++] = ch;
    return;
}

// KEY SCAN 按下 / 放開 時, 
// flag
// 0: KEY_PRESSED
// 1: KEY_CLICK_RELEASED
// 2: KEY_HOLD_RELEASED
static void OnKeyScan(unsigned char num, unsigned char flag) {
    int len;
    unsigned char c[] = {0, 0};
    static unsigned char frame[MAX_RX_BUFFER+8];

    if (KEY_PRESSED == flag)
        return;
    c[0] = num, c[1] = flag;
    len = pack_ui_frame(0X0010, 1, 2, c, frame);
    UartTx(frame, len);
}

// 緊急停止開關被拔起, ESP 代表 緊急停止
static void OnEspUnplug(void) {
    int len;
    const unsigned char c = 0;
    static unsigned char frame[MAX_RX_BUFFER+8];

    len = pack_ui_frame(0X005D, 1, 1, &c, frame);
    UartTx(frame, len);
}

// 緊急停止開關被插入
static void OnEspPlug(void) {
    int len;
    const unsigned char c = 1;
    static unsigned char frame[MAX_RX_BUFFER+8];

    len = pack_ui_frame(0X005D, 1, 1, &c, frame);
    UartTx(frame, len);
}

void OnOsalEvent(unsigned int nId) {
    int len;
    unsigned char c = 0;
    static unsigned char frame[MAX_RX_BUFFER+8];

    // 緊急停止
    if (ESP_NOTIFY_OSALTIMER_ID == nId) {
        if (GetEspStatus())        OnEspPlug();
        else                        OnEspUnplug();
        return;
    }
    // 心跳
    if (HR_PERIOD_REPORT_OSALTIMER_ID == nId) {
        static unsigned char x = 0;
        if (x % 2) {
            // 回報無線心跳
            c = calc_whr();
            len = pack_ui_frame(0X0011, 1, 1, &c, frame);
            UartTx(frame, len);
        }
        else {
            // 回報有線心跳
            c = calc_chr();
            len = pack_ui_frame(0X0012, 1, 1, &c, frame);
            UartTx(frame, len);
        }
        x++;
        SetOsalTimer(HR_PERIOD_REPORT_OSALTIMER_ID, 499, OnOsalEvent);
        return;
    }
    // BUZZER OFF
    if (BUZZ_OFF_OSALTIMER_ID == nId) {
        EnableBuzzer(0);
        return;
    }
    return;
}

int main(void) {
    // 48MHZ, 1ms config
    SysTick_Config(48000);

    InitGpioUart();
    InitUart();

    InitFanCtrl();
    InitEsp();
    InitKeyScan(OnKeyScan);
    InitSalutron();
    InitBuzzer();
    InitOsalTimer();

    StartKeyScan();
    StartEspMonitor();
    SetOsalTimer(HR_PERIOD_REPORT_OSALTIMER_ID, 499, OnOsalEvent);

    while (1) {
        if (time1ms_flag) {
            time1ms_flag = 0;
            PollSalutronHr();
        }
        //else
        OsalTimerScheduler();
    }
}


///////////////////////////////////////////////////////////////////////////////
//            Cortex-M0 Processor Exceptions Handlers
///////////////////////////////////////////////////////////////////////////////

// UART 中斷服務常式
void USART1_IRQHandler(void) {
    uint8_t characterReceive = 0;

    // UART 資料傳送完畢
    if((USART1->ISR & USART_ISR_TC) == USART_ISR_TC) {
        if (0 == utleft)    USART1->ICR |= USART_ICR_TCCF, put = utb;
        else                USART1->TDR = *put++, utleft--;
    }
    // UART 資料接收
    if((USART1->ISR & USART_ISR_RXNE) == USART_ISR_RXNE) {
        characterReceive = (uint8_t)(USART1->RDR);
        OnUartRx((unsigned char)characterReceive);
    }
    //else { NVIC_DisableIRQ(USART1_IRQn); }
}

void NMI_Handler(void) {}

void HardFault_Handler(void) {
    while (1);
}

void SVC_Handler(void) {}

void PendSV_Handler(void) {}

//#define TMR0_IRQHandler        SysTick_Handler
// SYSTEM TICK  中斷服務常式, 1-ms 中斷 1 次
void SysTick_Handler(void) {
    time1ms_flag = 1;
    IsrPollOsalTimer();
}

 

接下來我們要把所有東西串在一起. 先來看 On開頭的函數

OnUserMessage() : 
Decode 從 UI (Uart) 送達之 PROTOCOL 封包. 

OnUartRx() : 
當 Uart 收到資料時. 由 Uart ISR 呼叫. 

OnKeyScan() : 
當鍵盤掃描 KEY SCAN 按下 / 放開 時. 由 InitKeyScan() 傳入 OnKeyScan() 函數, 該函數指標存放在 callback 靜態變數, 由 PollKeyScan() 呼叫 OnKeyScan() .

OnEspPlug()/OnEspUnplug() :
緊急停止開關被插入/緊急停止開關被拔起. 由 Uart Tx 回報. 

OnOsalEvent(): 
OsalTimer 包括了 ESP_NOTIFY_OSALTIMER_ID, HR_PERIOD_REPORT_OSALTIMER_ID, BUZZ_OFF_OSALTIMER_ID 共用了這一個 OSAL_TIMER_FUNC. 譬如說
...
SetOsalTimer(HR_PERIOD_REPORT_OSALTIMER_ID, 499, OnOsalEvent);
...
SetOsalTimer(BUZZ_OFF_OSALTIMER_ID, 59, OnOsalEvent);
...
SetOsalTimer(ESP_NOTIFY_OSALTIMER_ID, 0, OnOsalEvent);
...
我們用 TimerId 來分辨彼此: 
    // 緊急停止
    if (ESP_NOTIFY_OSALTIMER_ID == nId) {
        if (GetEspStatus())        OnEspPlug();
        else                        OnEspUnplug();
        return;
    }
    // 回報心跳
    if (HR_PERIOD_REPORT_OSALTIMER_ID == nId) {
      ...
    }
    // BUZZER OFF
    if (BUZZ_OFF_OSALTIMER_ID == nId) {
        EnableBuzzer(0);
        return;
    }

 

主程序 main.c
 

...
int main(void) {
    // 48MHZ, 1ms config
    SysTick_Config(48000);

    InitGpioUart();
    InitUart();

    InitFanCtrl();
    InitEsp();
    InitKeyScan(OnKeyScan);
    InitSalutron();
    InitBuzzer();
    InitOsalTimer();

    StartKeyScan();
    StartEspMonitor();
    SetOsalTimer(HR_PERIOD_REPORT_OSALTIMER_ID, 499, OnOsalEvent);

    while (1) {
        if (time1ms_flag) {
            time1ms_flag = 0;
            PollSalutronHr();
        }
        //else
        OsalTimerScheduler();
    }
}
...

主程序 main() 就很簡單:
1. 設定 TIMER (SYSTEM TICK) . 
2. 初始化 Init...
3. 無窮迴圈 POLLING OsalTimerScheduler() 以及 PollSalutronHr(). PollSalutronHr() 要注意 1 mini-second. 

 

 

 


 

 

z.png
Email: jasonc@mail2000.com.tw

 

 

 

 

 

 

arrow
arrow

    Lexra 發表在 痞客邦 留言(0) 人氣()