STM32 (スイッチ長押し/短押し制御)
1.本日の内容
(1) STM32マイコン(nucleof401re)を使用して、
スイッチの長押し/短押しの判定を行う。
・目次
2.内容
(1) やる内容の詳細
・nucleof401REスイッチのスイッチ(青ボタン)の長押し/短押し制御を行う。
・長押し時:LEDの点滅/LED消灯
・短押し時:LED点灯/消灯
を行う。
スイッチを押した時の、状態の変化を状態遷移図で示す。
これを元に、状態遷移表を作成する。
・MODE1:LED点灯
・MODE2:LED消灯
・MODE3:LED点滅
状態遷移図及び状態遷移表については下記が参考になります。
・状態遷移表を使用した要求分析モデル:状態遷移表による設計手法(3)(1/3 ページ) - MONOist
・UMLステートマシンと状態遷移図 - astah-info
・設計 | PlantUMLによる状態遷移図の書き方, 状態遷移表からのテスト抽出方法 - わくわくBank
(2) 使用部品
(2) CubeIDEの設定
ピン及び、基本設定として、CubeIDEで下記設定を行う。
・LEDのGPIO出力設定(PA5:GPIO_OUT)
・KEYのGPIO入力設定(PC13:GPIO_IN)
・タイマ割り込み設定(TIM3)
→1ms毎に割り込み発生
・デバック用にUART設定(UART2)
(3) プログラムの作成
今回は、下記ファイルを作成する。
・割り込み作成用ファイル :IntR.c/IntR.h
→参考:STM32マイコン26(割り込み:TIM3_IRQHandler)
・キー制御用ファイル :key_ctrl.c/key_ctrl.h
→参考:STM32マイコン_16(レジスタ操作でスイッチ入力) - Project_OKI’s diary
・led制御用ファイル :led.c/led.h
→参考:STM32マイコン_10(レジスタ操作でLED点滅) - Project_OKI’s diary
STM32マイコン_15(レジスタ操作でLED点滅2) - Project_OKI’s diary
・printf用ファイル :printf.c/printf.h
→参考:STM32マイコン_12(printfの導入) - Project_OKI’s diary
・メイン処理作成用ファイル :self_main.c/self_main.h
・基本型設定用ファイル :typedef.h
・STM32レジスタ設定ファイル:STMSys.h
→参考:STM32マイコン_16(レジスタ操作でスイッチ入力) - Project_OKI’s diary
今回のファイルは、JAVADOCの形式に従ってコメントを書く。
これで書くと、doxygenによって、コードからドキュメントを生成することが可能になる。
JAVADOCについて参考:
・いまさら聞けない「Javadoc」と「アノテーション」入門:【改訂版】Eclipseではじめるプログラミング(22)(1/4 ページ) - @IT
プログラムのキー入力に関するざっくりとした流れ
全体的な関数の紐づけ
///main.c (CubeIDEによって自動生成)のユーザー書き込み部分
#include "self_main.h"
#include "printf.h"
setbuf(stdout,NULL);
self_main();
///stm32f4xx_it.c (割り込みについて記載)
#include "typedef.h"
#include "IntR.h"
Tim3_Int();
///self_main.c (メイン処理、main.cとは別に、自分で書き換えやすいように作成)
#include "typedef.h"
#include "STMLib/STMSys.h"
#include "IntR.h"
#include "key_ctrl.h"
#include "printf.h"
#include "self_main.h"
#include "led.h"
volatile TSysCtrlInf GSysCtrlInf;
volatile TKeyCtrlInf GKeyCtrlInf;
void MainLoop(void);
int self_main( void )
{
__disable_irq();
InitData();
InitSystem();
__enable_irq();
MainLoop();
return( 0 );
}
データ
void InitData( void )
{
GSysCtrlInf.eCMode = INIT;
GSysCtrlInf.eNMode = INIT;
Key_Init();
}
#if 1
void MainLoop( void )
{
GKeyCtrlInf.key1_LongWatchEna = true;
while( 1 ) {
switch( GSysCtrlInf.eCMode ) {
case INIT:
IdleMode_Proc();
break;
case LCD_FOR:
LCD_For_Proc();
break;
case LCD_REV:
LCD_Rev_Proc();
break;
default:
GSysCtrlInf.eNMode = INIT;
break;
}
if ( GSysCtrlInf.eCMode != GSysCtrlInf.eNMode ) {
GSysCtrlInf.eCMode = GSysCtrlInf.eNMode;
}
}
}
void IdleMode_Proc(){
UCHAR ucKey;
Led1(HIGH);
ucKey = Key_Proc();
if(ucKey == KEY1_SHORT){
GSysCtrlInf.eNMode = LCD_FOR;
}else{
}
}
void LCD_For_Proc(){
UCHAR ucKey;
Led1(LOW);
ucKey = Key_Proc();
if(ucKey == KEY1_SHORT){
GSysCtrlInf.eNMode = INIT;
}else if(ucKey == KEY1_LONG){
GSysCtrlInf.eNMode = LCD_REV;
}
}
void LCD_Rev_Proc(){
UCHAR ucKey;
ledctrl.led1blif=true;
ucKey = Key_Proc();
if(ucKey == KEY1_SHORT){
ledctrl.led1blif=false;
GSysCtrlInf.eNMode = INIT;
}else if(ucKey == KEY1_LONG){
ledctrl.led1blif=false;
GSysCtrlInf.eNMode = LCD_FOR;
}
}
#endif
///self_main.h (メイン処理のヘッダファイル)
#ifndef MAIN_H
#define MAIN_H
#define PORT_KEY1 KEY1_GPIO_Port
#define BIT_KEY1 KEY1_Pin
#define PORT_KEY2 KEY2_GPIO_Port
#define BIT_KEY2 KEY2_Pin
#define PORT_KEY3 KEY3_GPIO_Port
#define BIT_KEY3 KEY3_Pin
typedef enum {
INIT=0,
LCD_FOR,
LCD_REV,
MODE3
} TMMODE;
typedef struct {
TMMODE eCMode;
TMMODE eNMode;
} TSysCtrlInf;
extern volatile TSysCtrlInf GSysCtrlInf;
extern int self_main(void );
extern void InitData(void);
extern void MainLoop(void );
extern void IdleMode_Proc(void);
extern void LCD_For_Proc(void);
extern void LCD_Rev_Proc(void);
#endif
///IntR.c (割り込み用ファイルの作成)
#include "typedef.h"
#include "STMLib/STMSys.h"
#include "key_ctrl.h"
#include "IntR.h"
#include "main.h" #include "led.h"
#include "printf.h"
volatile TTimCtrlInf GTimCtrlInf;
extern TIM_HandleTypeDef htim3;
void InitSystem()
{
HAL_TIM_Base_Start_IT(&htim3);
}
void Tim3_Int()
{
GTimCtrlInf.uc5msCnt++;
GTimCtrlInf.uc1sCnt++;
if(GTimCtrlInf.uc5msCnt >= TIM5MS_CNT_VAL){
GTimCtrlInf.uc5msCnt = 0;
Key_Input();
}
if(GTimCtrlInf.uc1sCnt >= TIM1S_CNT_VAL){
GTimCtrlInf.uc1sCnt = 0;
Led1Blink();
}
}
///IntR.h (割り込み用ヘッダファイル)
#ifndef INTR_H
#define INTR_H
#define TIM5MS_CNT_VAL ( 5 )
#define TIM1S_CNT_VAL ( 1000 )
typedef struct {
UCHAR uc5msCnt;
UINT uc1sCnt;
} TTimCtrlInf;
extern volatile TTimCtrlInf GTimCtrlInf;
extern void Tim3_Int();
extern void InitSystem();
#endif
///key_ctrl.c (キー制御用ファイル)
#include "typedef.h"
#include "key_ctrl.h"
#include "self_main.h"
#include "STMLib/STMSys.h"
#include "printf.h"
extern void Key_DatSet( UCHAR ucDat );
extern UCHAR Key_DatGet( void );
void Key_Init( void )
{
UCHAR ucCnt;
GKeyCtrlInf.ucState = KEYS_IDLE;
GKeyCtrlInf.ucInp = 0;
GKeyCtrlInf.ucPrev = 0;
GKeyCtrlInf.ucFix = 0;
GKeyCtrlInf.ucTimCnt = 0;
GKeyCtrlInf.ucWp = 0;
GKeyCtrlInf.ucRp = 0;
for( ucCnt=0; ucCnt < KEY_BUFF_SIZE; ucCnt++ ) {
GKeyCtrlInf.ucBuff[ucCnt] = 0;
}
GKeyCtrlInf.Key1_OnTimCnt = 0;
GKeyCtrlInf.eKey1_Det =0;
}
USHORT Key_In(void)
{
volatile USHORT usKey1;
USHORT usKey = 0;
usKey1 = KEY1_IN();
usKey = (usKey1<<KEY1SFT);
return( usKey );
}
void Key_Input( void )
{
USHORT ucKey1;
ucKey1 = Key_In();
GKeyCtrlInf.ucInp = KEY_NONE;
GKeyCtrlInf.ucInp |= (ucKey1 & BIT_KEY1 )? KEY1_SET : KEY_NONE;
if ( GKeyCtrlInf.ucState == KEYS_IDLE ) {
if ( GKeyCtrlInf.ucInp != GKeyCtrlInf.ucPrev ) {
GKeyCtrlInf.ucPrev = GKeyCtrlInf.ucInp;
GKeyCtrlInf.ucTimCnt = 0;
GKeyCtrlInf.ucState = KEYS_CHG;
}else {
if ( GKeyCtrlInf.ucFix & KEY1_SET ) {
if ( GKeyCtrlInf.Key1_OnTimCnt < 0xffff ) {
GKeyCtrlInf.Key1_OnTimCnt++;
}
}
}
}else {
GKeyCtrlInf.ucTimCnt++;
if ( GKeyCtrlInf.ucInp != GKeyCtrlInf.ucPrev ) {
GKeyCtrlInf.ucPrev = GKeyCtrlInf.ucInp;
GKeyCtrlInf.ucTimCnt = 0;
}else if (GKeyCtrlInf.ucTimCnt >= KEY_CHATTER_REMOVE_TIM_CNT) {
if ( (GKeyCtrlInf.ucFix & KEY1_SET) != (GKeyCtrlInf.ucInp & KEY1_SET) ) {
if ( ( GKeyCtrlInf.ucFix & KEY1_SET ) == 0 ) {
Key_DatSet( KEY1_ON );
} else {
Key_DatSet( KEY1_OFF );
}
}
GKeyCtrlInf.ucFix = GKeyCtrlInf.ucInp;
GKeyCtrlInf.ucState = KEYS_IDLE;
GKeyCtrlInf.ucTimCnt = 0;
}
}
}
UCHAR Key_Proc( void )
{
UCHAR ucKey;
ucKey = Key_DatGet();
if ( (ucKey == KEY1_ON) || (ucKey == KEY1_OFF) ) {
GKeyCtrlInf.Key1_OnTimCnt = 0;
}
if ( ( ucKey == KEY_NONE ) &&
( GKeyCtrlInf.eKey1_Det == false ) &&
( GKeyCtrlInf.Key1_OnTimCnt >= TIM_KEY_LONG ) ) {
GKeyCtrlInf.eKey1_Det = ON;
ucKey = KEY1_LONG;
}
if ( ( ucKey == KEY1_OFF ) &&
( GKeyCtrlInf.Key1_OnTimCnt < TIM_KEY_LONG ) ) {
if ( GKeyCtrlInf.eKey1_Det == false ) {
ucKey = KEY1_SHORT;
} else {
GKeyCtrlInf.eKey1_Det = OFF;
}
}
return( ucKey );
}
void Key_DatSet( UCHAR ucDat )
{
UCHAR ucWp;
__disable_irq();
ucWp = ( GKeyCtrlInf.ucWp + 1 );
ucWp = ( ucWp >= KEY_BUFF_SIZE )? 0 : ucWp;
if ( ucWp == GKeyCtrlInf.ucRp ) {
}
else {
GKeyCtrlInf.ucBuff[GKeyCtrlInf.ucWp] = ucDat;
GKeyCtrlInf.ucWp = ucWp;
}
__enable_irq();
}
UCHAR Key_DatGet( void )
{
UCHAR ucDat;
if ( GKeyCtrlInf.ucWp == GKeyCtrlInf.ucRp ){
ucDat = KEY_NONE;
}
else {
ucDat = GKeyCtrlInf.ucBuff[GKeyCtrlInf.ucRp];
__disable_irq();
GKeyCtrlInf.ucRp++;
GKeyCtrlInf.ucRp = ( GKeyCtrlInf.ucRp >= KEY_BUFF_SIZE )? 0 : GKeyCtrlInf.ucRp;
__enable_irq();
}
return( ucDat );
}
///key_ctrl.h (キー制御用ヘッダファイル)
#ifndef KEY_CTRL_H
#define KEY_CTRL_H
#define KEY1_IN() ( InGPIOBitR(PORT_KEY1,BIT_KEY1))
#define KEY1SFT ( 13 )
#define TIM_KEY_LONG ( 400 )
#define KEY_CHATTER_REMOVE_TIM_CNT ( 2 )
#define KEY_NONE (0x00 )
#define KEY_OFF (0x80 )
#define KEY1_SET (0x01 )
#define KEY1_ON (0x01 )
#define KEY1_OFF ( KEY1_ON | KEY_OFF )
#define KEY_LONG (0x20 )
#define KEY1_LONG ( KEY_LONG | KEY1_ON )
#define KEY_SHORT (0x40 )
#define KEY1_SHORT ( KEY_SHORT | KEY1_ON )
#define KEYS_IDLE (0 )
#define KEYS_CHG (1 )
#define KEY_BUFF_SIZE (10 )
typedef struct {
UCHAR ucState;
UCHAR ucInp;
UCHAR ucPrev;
UCHAR ucFix;
UCHAR ucTimCnt;
UCHAR ucBuff[KEY_BUFF_SIZE];
UCHAR ucWp;
UCHAR ucRp;
USHORT Key1_OnTimCnt;
bool key1_LongWatchEna;
bool eKey1_Det;
} TKeyCtrlInf;
extern volatile TKeyCtrlInf GKeyCtrlInf;
extern void Key_Init( void );
extern void Key_Input( void );
extern UCHAR Key_Proc( void );
#endif
///printf.c (出力デバック確認用ファイル)
#include "printf.h"
extern UART_HandleTypeDef huart2;
int __io_putchar(int c) {
if( c == '\n' ) {
int b = '\r';
HAL_UART_Transmit(&huart2,(uint8_t*)&b, 1, 1);
}
HAL_UART_Transmit(&huart2,(uint8_t*)&c, 1, 1);
return 0;
}
///printf.h (出力デバック用ヘッダファイル)
#ifndef PRINTF_H
#define PRINTF_H
#include "main.h"
#include "stdio.h"
#endif
///typedef.h (基本型ヘッダファイル)
#ifndef TYPEDEF_H
#define TYPEDEF_H
#undef Disable
#define Disable 0xffff
#undef false
#undef true
typedef enum{false=0
,true
}bool;
typedef enum{OFF=0
,ON
}ONOFF;
typedef enum{LOW=0
,HIGH
}SIGNAL;
typedef enum{UP=0
,DOWN
}UPDOWN;
typedef signed char SCHAR;
typedef unsigned char UCHAR;
typedef signed short SSHORT;
typedef unsigned short USHORT;
#endif
///STMSys.h (STM32レジスタ設定ファイル)
#ifndef STMSYS_H
#define STMSYS_H
#include "main.h"
#define OutGPIOBit(port,pin,data) (port->ODR=((port->ODR&~(pin))|((data==0) ? 0 : (pin))))
#define OutGPIOBitR(port,pin,data) (port->ODR=((port->ODR&~(pin))|(data==0) ? (pin) : 0)))
#define InGPIOBitR(port,pin) ((((port->IDR)&(pin))==0)?0:1)
#define InGPIOBitR(port,pin) ((((port->IDR)&(pin))==0)?1:0)
#endif
(4) プログラムの説明(キー入力)
(a) #define KEY1_IN() ( InGPIOBitR(PORT_KEY1,BIT_KEY1))
key_ctrl.hにて作成されている、これにより、KEY1の入力を確認する。
nucleof401のスイッチは、デフォルトで3.3Vでプルアップ(High)されており、
スイッチを押すと、GNDに接続され、LOW(0V)となる。
(b)InGPIOBitR は、STMSys.h (STM32レジスタ設定ファイル)にて作成されている。
#defineによって、
((((port->IDR)&(pin))==0)?1:0) を InGPIOBitR(port,pin) に置き換えている。
この動作については、下記を参照
指定したpinが0の場合、1を返し、pinが1だったら0を返している。
(c) Key_In(void)関数について
Key_Input関数によって呼び出される。
usKey1 = KEY1_IN();
usKey = ( usKey1<<KEY1SFT | (usKey2 << KEY2SFT) | (usKey3 << KEY3SFT) );
KEY1_IN()で取り込んだ値(1 or 0)をusKey1に取り込む。
usKey1<<KEY1SFTによって、1<<13 となる。
これは、KEY1がPC13の為、1<<13とすることにより、
KEY1_GPIO_Portと同じ値になる。
このusKey = usKey1<<KEY1SFT の値を返す。
(d) key_ctrl.c の Key_Input関数 について
タイマ割り込みによって5ms毎に呼び出される。
(Tim3_Int();によって呼び出される。)
ucKey1 = Key_In();
GKeyCtrlInf.ucInp = KEY_NONE;
GKeyCtrlInf.ucInp |= (ucKey1 & BIT_KEY1 )? KEY1_SET : KEY_NONE;
BIT_KEY1は、KEY1_GPIO_Portのこと。
つまり、
ucKey1 & BIT_KEY1というのは、
ucKey1 = KEY1_GPIO_Port 又は それ以外の値が入力されるので、
・ucKey1がKEY1_GPIO_Portの時
ucKey1 & BIT_KEY1は1(true)になる。
・ucKey1がKEY1_GPIO_Port以外の時
ucKey1 & BIT_KEY1は0(false)となる。
・ucKey1 & BIT_KEY1が1(true)の時
GKeyCtrlInf.ucInp |=KEY1_SET (
・ucKey1 & BIT_KEY1が0(false)の時
GKeyCtrlInf.ucInp |=KEY1_SET : KEY_NONE (
が設定される。
参考:
のビット演算子と条件演算子の項目を参照
(e) GKeyCtrlInf.ucInp != GKeyCtrlInf.ucPrev
GKeyCtrlInf.ucPrevは、一回前に実行した時のKEY設定コードが入る。
1回前のKEY設定コードと今回のKEY設定コードを比較することにより、
KEYが変化したかどうかを確認している。
KEY設定コードが変化している場合、下記初期化を行う。
・GKeyCtrlInf.ucPrev = GKeyCtrlInf.ucInp;
・GKeyCtrlInf.ucTimCnt = 0;
・GKeyCtrlInf.ucState = KEYS_CHG;
(f) KEYが変化した場合、下記処理によって、チャタリングの確認を行う。
KEYが押されたままで、変化しない間、
チャタリング除去カウンタを+1し続け、それが設定した除去期間を超えた場合、
チャタリング除去が出来たものとして、次の処理に進む。
それ以外の場合は、
・チャタリング除去カウンタの更新と
・キー入力状態の変化の確認 のみ行う。
GKeyCtrlInf.ucTimCnt++;
if ( GKeyCtrlInf.ucInp != GKeyCtrlInf.ucPrev ) {
GKeyCtrlInf.ucPrev = GKeyCtrlInf.ucInp; GKeyCtrlInf.ucTimCnt = 0;
}
else if (GKeyCtrlInf.ucTimCnt >= KEY_CHATTER_REMOVE_TIM_CNT) {
(g) if ( (GKeyCtrlInf.ucFix & KEY1_SET) != (GKeyCtrlInf.ucInp & KEY1_SET) ) {
GKeyCtrlInf.ucFixは確定している現在のキーの状態を確認している。
GKeyCtrlInf.ucFix と KEY1_SETが同じだった場合1、
GKeyCtrlInf.ucFix と KEY1_SETが違う場合、0 となり、
それを、GKeyCtrlInf.ucInp & KEY1_SETと比較している。
これは、
・キー1が押されてない状態から押された状態に変化したのか。
・キー1が押された状態から、押されてない状態に変化したのか。
というのを確認している。
(h) if ( ( GKeyCtrlInf.ucFix & KEY1_SET ) == 0 )
これがtureの時、
KEY1の状態が OFF からONに変化したことになるので、
Key_DatSet( KEY1_ON ); によって、キー入力バッファにKEY1_ONコードを格納
falseの時
KEY1の状態が ON からOFFに変化したことになるので、
Key_DatSet( KEY1_OFF); によって、キー入力バッファにKEY1_OFFコードを格納
以下に、上記動作の変数の状態変化について記載する。
下記表の名前省略及び表している数値
ucFix :GKeyCtrlInf.ucFix →現在のボタンの確定している状態を表す。
ucInp :GKeyCtrlInf.ucInp →今のボタンを読み込んだ状態を表す。
ucPrev :GKeyCtrlInf.ucPrev →1回前に関数が実行された時のボタンの状態を表す。
BIT_KEY1 :0x01<<13
KEY1_SET :0x01
ucFixが0 :KEY入力無し
ucFixがKEY1_SET:KEY1が押されている状態
(i)if ( GKeyCtrlInf.ucFix & KEY1_SET ) {
if ( GKeyCtrlInf.Key1_OnTimCnt < 0xffff ) {
GKeyCtrlInf.Key1_OnTimCnt++;
この処理により、
GKeyCtrlInf.ucFixがKEY1_SETの間、KEY1長押し時間カウントを+1し続ける。
また、0xffff以上に値が大きくならないようにしている。
(j) Key_Proc( void )
これは、メイン処理で実行される。
ucKey = Key_DatGet();
でキーバッファに格納されているキーコードを取得する。
長押しされている場合、キーが押されていない状態の場合は、KEY_NONE
キー1の状態が変化した場合は、KEY1_ON又は、KEY1_OFFが返される。
(今回はKEY1のみの為)
メイン処理の ucKey = Key_Proc(); に入る。
(l) if(ucKey == KEY1_SHORT){
メイン処理の(j)で格納されたucKeyの値によって、if文で動作を変更する。
今回は、モードの切り替えにより、LEDの状態を変化させている。
3.関連記事
関連記事一覧:
組み込みC言語: