43a SysTickを使ってTIMを使わずにタイマ割り込みする (NVIC,GPIO,UART,LEDチカチカ)

systickという機能は、たぶんSTM32にReal-Time OSを載せたときに重要な機能なんだと思います.

基本的に1mSec割り込みをするタイマとして動作させます.

ここでは、TIMxを使わずに、SysTickを使ってLEDをチカチカさせてみます.

CPUにとって大切なsystickですが、なぜかSTM32のperipheralデータシートにはほとんど登場しません.

http://www.st.com/internet/com/TECHNICAL_RESOURCES/TECHNICAL_LITERATURE/REFERENCE_MANUAL/CD00246267.pdf

それはなんでかというと、systickはCPUであるCotex-M3の内部に実装された回路であって、STM社が実装したperipheral回路ではないからです.

STM社が発行するデータシートにはSTM社が作ったperipheral回路のみが解説されていて、Coretex-M3の回路はARM社が解説します.

ARM社によるCoretex-M3の解説のページはこちらです.

http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0337gj/Cihcbadd.html

その中で、systickの解説ページはこちらになります.

ネスト型ベクタ割り込みコントローラ→NVICのプログラマモデル→NVICレジスタの説明→SysTick制御およびステータスレジスタ

わたしはsystickの解説ページを詳細に読んでません.

systickの設定に使う関数は、3つしかありません.

1)SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8) // clock=8MHz

2)NVIC_SetPriority (SysTick_IRQn, 5); // change interrupt priority

3)SysTick_Config(SystemCoreClock / 1000); // SystemCoreClock=2400000000

1は、clockの選択です.24MHzか3MHzかを選択します.callしなければ24MHzが設定されます.

2は、割り込み優先順位です.callしなければ最低順位が設定されます.

3は、systickカウンタがグルグル回る最大値を設定します.

ここでは、3の中身を調べてみます.

SysTick_Config()のcode実体はこうなっています.いきなりこれを見てもよくわかりません.

/* @brief Initialize and start the SysTick counter and its interrupt.

* @param ticks number of ticks between two interrupts

* @return 1 = failed, 0 = successful

* Initialise the system tick timer and its interrupt and start the

* system tick timer / counter in free running mode to generate

* periodical interrupts. */

static __INLINE uint32_t SysTick_Config(uint32_t ticks)

{

if (ticks > SysTick_LOAD_RELOAD_Msk) return (1); // Reload value impossible

SysTick->LOAD = (ticks & SysTick_LOAD_RELOAD_Msk) - 1; // set reload register

NVIC_SetPriority (SysTick_IRQn, (1<<__NVIC_PRIO_BITS) - 1); // set Priority for Cortex-M0 System Interrupts

SysTick->VAL = 0; // Load the SysTick Counter Value

SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk |

SysTick_CTRL_TICKINT_Msk |

SysTick_CTRL_ENABLE_Msk; // Enable SysTick IRQ and SysTick Timer

return (0); // Function successful

}

codeを理解するために、最初にSysTickという構造体はどうなっているのかを調べました.

typedef struct

{

__IO uint32_t CTRL; // controlレジスタ

__IO uint32_t LOAD; // 24bitカウンタにLOAD-1~0までカウントダウンさせる

__IO uint32_t VAL; // 24bitカウンタの現在値

__I uint32_t CALIB; // Calibrationレジスタ

} SysTick_Type;

SysTick構造体は、Coretex-M3のレジスタアドレスにピッタシ重なるように定義されていると推察されます.

その宣言部分を捜したらこうなっていました.0xE000E0010から存在するようです.

/* Memory mapping of Cortex-M3 Hardware */

#define SCS_BASE (0xE000E000) /*!< System Control Space Base Address */

#define ITM_BASE (0xE0000000) /*!< ITM Base Address */

#define CoreDebug_BASE (0xE000EDF0) /*!< Core Debug Base Address */

#define SysTick_BASE (SCS_BASE + 0x0010) /*!< SysTick Base Address */

#define NVIC_BASE (SCS_BASE + 0x0100) /*!< NVIC Base Address */

#define SCB_BASE (SCS_BASE + 0x0D00) /*!< System Control Block Base Address */

#define InterruptType ((InterruptType_Type *) SCS_BASE) /*!< Interrupt Type Register */

#define SCB ((SCB_Type *) SCB_BASE) /*!< SCB configuration struct */

#define SysTick ((SysTick_Type *) SysTick_BASE) /*!< SysTick configuration struct */

#define NVIC ((NVIC_Type *) NVIC_BASE) /*!< NVIC configuration struct */

#define ITM ((ITM_Type *) ITM_BASE) /*!< ITM configuration struct */

#define CoreDebug ((CoreDebug_Type *) CoreDebug_BASE) /*!< Core Debug configuration struct */

systickレジスタのbitアサインは登場順にこんな風になっています.

#define SysTick_LOAD_RELOAD_Pos 0 // SysTick LOAD: RELOAD Position

#define SysTick_LOAD_RELOAD_Msk (0xFFFFFFul << SysTick_LOAD_RELOAD_Pos) // SysTick LOAD: RELOAD Mask

#define __NVIC_PRIO_BITS 4 // standard definition for NVIC Priority Bits

#define SysTick_CTRL_CLKSOURCE_Pos 2 // SysTick CTRL: CLKSOURCE Position

#define SysTick_CTRL_CLKSOURCE_Msk (1ul << SysTick_CTRL_CLKSOURCE_Pos) // SysTick CTRL: CLKSOURCE Mask

#define SysTick_CTRL_TICKINT_Pos 1 // SysTick CTRL: TICKINT Position

#define SysTick_CTRL_TICKINT_Msk (1ul << SysTick_CTRL_TICKINT_Pos) // SysTick CTRL: TICKINT Mask

#define SysTick_CTRL_ENABLE_Pos 0 // SysTick CTRL: ENABLE Position

#define SysTick_CTRL_ENABLE_Msk (1ul << SysTick_CTRL_ENABLE_Pos) // SysTick CTRL: ENABLE Mask

素材がわかったところで、SysTick_Config()のcodeをあらためて見てみます.

static __INLINE uint32_t SysTick_Config(uint32_t ticks)

{

↓引数はカウンタの初期値(=max値)です.カウンタは24bitですのでFFFFFFを超える値がinputされたらエラーでreturnします.

エラーreturnの返値は1です.

if (ticks > SysTick_LOAD_RELOAD_Msk) return (1); /* Reload value impossible */

↓ノーエラーなら構造体のLOADメンバーに代入

SysTick->LOAD = (ticks & SysTick_LOAD_RELOAD_Msk) - 1; /* set reload register */

↓NVICの割り込み優先度を設定しています.STM32-DISCOVERYのCPUの割り込み優先度は4bitですので、

_NVIC_PRIO_BITS=4と設定されています.そして、下記のcodeですと、systickの優先度=15に設定されます.

つまり、systickの割り込み優先度は最下位に設定されます.

NVIC_SetPriority (SysTick_IRQn, (1<<__NVIC_PRIO_BITS) - 1); /* set Priority for Cortex-M0 System Interrupts */

SysTick->VAL = 0; 24bitカウンタをクリア

↓コントロールレジスタを設定

CLKSOURCE

1 = コアクロック こちらが設定される

0 = 外部クロック 外部clockって何のことなのかは不明

TICKINT

1 = 0までカウントダウンすると、割り込み発生. こちらが設定される

0 = 0までカウントダウンしても、割り込みしない.

ENABLE

1 = 動く こちらが設定される

0 = 停止

SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk |

SysTick_CTRL_TICKINT_Msk |

SysTick_CTRL_ENABLE_Msk;

return (0); 正常終了時は0が返値

}

サンプルプログラムで何をするか?

PC上のterminal softを起動し、115200bps+parityなし+8bit+stop1bit+フロー制御なしで接続します.

●STM32-DISCOVERYを起動すると、下記のように1秒毎に表示されます.

SysTick count has reached to max, reload 1000. (1)

SysTick count has reached to max, reload 1000. (2)

SysTick count has reached to max, reload 1000. (3)

●それと同時にブルーLEDがチカチカしますので、LEDは1秒毎にチカチカします.

●terminal softでenter keyを入力すると、次のmenu表示がでます.

-----SysTick TEST MENU-----

int 10 : LED flash 10 Hz LEDの点滅周期を10Hzにします

off : LED off LEDをオフします

on : LED on LEDの点滅をオンします

---------------------------

●int 10と入力すると、表示がせわしなくなり、LEDもチカチカが速まります.

SysTick count has reached to max, reload 100. (270)

SysTick count has reached to max, reload 100. (271)

SysTick count has reached to max, reload 100. (272)

以上のように、TIMxを使わずにLEDチカチカを実現できます.

UARTをUSBにつなぐ回路は?

UARTをUSBに変換する回路を使ってUSB経由でPCに接続します.

つかうのは秋月電子で売られているこの回路です. http://akizukidenshi.com/catalog/g/gK-01977/

STM32-DISCOVERYとの実体配線図はこのようになります.

ここではUSART3 defaultを使うので、下辺のPB10(TX),PB11(RX)から配線を取り出します.

他のUSARTを使いたい場合は下図の各所から配線を取り出します.

terminal softは別途用意してPCにインストールしてください.

XPとかの古いwindowsではhyper-terminalというのが最初からインストールされていましたが、vista以降は無くなってしまったと思います.

サンプルプログラム解説

サンプルプログラムはこのページの末尾からDLできます.projectの組み込み方法はこちらを参照してください.

重要な注意: 当初compile errorが出ました.エラー回避のために、syscalls.cというファイルを追加してあります.これはyagartからDLしたファイルですが、これがないとcompileできません.トラブルシュートの詳細はこちらのページを参照してください.

main.c

#include "stdio.h" このC標準関数3兄弟を使おうとするとsyscalls.cが必要になります.詳しくはこちらを参照

#include "string.h"

#include "stdlib.h"

#include "stm32f10x.h" STM32のhardwareに根ざした設定を多数やってます

#include "usrlib-uart3d.h" UART3を使う自作ライブラリ

#include "usrlib-gpio.h" GPIOを使う自作ライブラリ

NVIC_InitTypeDef NVIC_InitStructure; 割込優先度コントローラNVIC設定用構造体

GPIO_InitTypeDef GPIO_InitStructure; GPIO設定用構造体

void Menu(void); UARTのメニュー処理ルーチン

int main(void) メインルーチン始まり

{

↓つかうperipheralのclockをenableしています.

clock周波数を変更するようなことは特にしてないので24MHzが供給されます.

RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3, ENABLE);

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); // USART1 TX/RX (default)

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE); // LED

RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);

第一引数の選択肢は下記です.

RCC_APB1PeriphClockCmd()

RCC_APB1Periph_TIM2, RCC_APB1Periph_TIM3, RCC_APB1Periph_TIM4, RCC_APB1Periph_TIM6, RCC_APB1Periph_TIM7, RCC_APB1Periph_WWDG, RCC_APB1Periph_SPI2, RCC_APB1Periph_USART2, RCC_APB1Periph_USART3, RCC_APB1Periph_I2C1, RCC_APB1Periph_I2C2, RCC_APB1Periph_PWR, RCC_APB1Periph_DAC, RCC_APB1Periph_CEC

RCC_APB2PeriphClockCmd()

RCC_APB2Periph_AFIO, RCC_APB2Periph_GPIOA, RCC_APB2Periph_GPIOB, RCC_APB2Periph_GPIOC, RCC_APB2Periph_GPIOD, RCC_APB2Periph_GPIOE, RCC_APB2Periph_GPIOF, RCC_APB2Periph_GPIOG, RCC_APB2Periph_ADC1, RCC_APB2Periph_TIM1, RCC_APB2Periph_SPI1, RCC_APB2Periph_USART1, RCC_APB2Periph_TIM15, RCC_APB2Periph_TIM16, RCC_APB2Periph_TIM17

↓割込優先度コントローラの設定

NVIC割り込みコントローラは4bitの優先度を持っていて、つまり最大で16個の割り込み優先度設定が出来るようです、

割り込み優先度には、PreemptionPriority とSubPriority にカテゴライズされています.

前者が大分類、後者が小分類というような階層になっています.

大分類に3bitを割り当て、小分類に1bitを割り当てるような階層作りができます.

NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);

それをやるのがこの↑関数です.引数によって、大分類と小分類のbitアサインを決めます.

| PreemptionPriority | SubPriority |

---------------------------------------------------------

NVIC_PriorityGroup_0 | 0 (0bit) | 0-15 (4bit) |

NVIC_PriorityGroup_1 | 0-1 (1bit) | 0-7 (3bit) |

NVIC_PriorityGroup_2 | 0-3 (2bit) | 0-3 (2bit) |

NVIC_PriorityGroup_3 | 0-7 (3bit) | 0-1 (1bit) |

NVIC_PriorityGroup_4 | 0-15(4bit) | 0 (0bit) |

ここでは、SysTickとUSART3の2つの割り込みを使うので NVIC_PriorityGroup_1 にしました.

NVIC_InitStructure.NVIC_IRQChannel = SysTick_IRQn; SysTick割り込み

NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; 最高優先度

NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;

NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;

NVIC_Init(&NVIC_InitStructure);

NVIC_InitStructure.NVIC_IRQChannel = USART3_IRQn; UART割り込み

NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; 2番目優先度

NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;

NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;

NVIC_Init(&NVIC_InitStructure);

UART_Init(); UARTの設定のため自作ライブラリの初期設定をコール

↓LEDチカチカの設定

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9; PC8とPC9がLEDのポート.そこを出力に設定する.

GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz; 出力ドライブ能力は最低でよい

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; フツーの出力ポートに設定

GPIO_Init(GPIOC, &GPIO_InitStructure);

↓SysTickの設定

①SysTick_Config(SystemCoreClock / 1000); // SystemCoreClock=2400000000

②//SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8) // clock=3MHz

③//NVIC_SetPriority (SysTick_IRQn, 5); // change interrupt priority

①24000000÷1000=24000でカウンタがグルグル回るので、1mSで割り込みがかかる

②コメントアウトしてあります.この関数をcallするとclockが3MHzになりますので、SysTick割り込みが8mSec毎になってしまいます

③コメントあうとしてあります.この関数をcallすると割り込み優先度が6番目になります

while(1) { Menu(); } メニューを無限ループする

} メインルーチンおしまい

↓debug用関数

#ifdef USE_FULL_ASSERT

void assert_failed(uint8_t* file, uint32_t line){ while (1) { } }

#endif

↓SysTick割り込みルーチン (1mSec毎)

char UARTbuf[100]; UART転送バッファ

int times = 1; LED点滅回数カウンタ

uint32_t systick_cnt, カウンタ

systick_reload=1000, カウンタreload値 1000なら1sec

systick_on=1; LED点滅する・しないフラグ

void SysTick_Handler(void)

{

if(systick_on==0) return; LEDオフならreturn

if(systick_cnt!=0x00) systick_cnt--; 0でなければカウントダウン

else 0ならばreload

{

systick_cnt=systick_reload; カウンタreload

LedBlueToggle(); LED点滅

sprintf(UARTbuf,"SysTick count has reached to max, reload %d. (%d)\r\n",systick_reload,times++); UART表示

UART_PutString(UARTbuf);

}

}

↓メニュー処理ルーチン 詳しい説明は省きます.赤字のところがキモです.

void Menu(void)

{

int n,i;

char tmp[30];

if(UART_STR_EXIST==0) return; // there is not any command line

n=separate_line(command); // divide a line into words

// display command line

strcpy(UARTbuf,"\r\ncommand: ");

for(i=0;i<n;i++)

{

sprintf(tmp,"(%d)%s/ ",i,Field[i]);

strcat(UARTbuf,tmp);

}

strcat(UARTbuf,"\r\n");

UART_PutString(UARTbuf);

if(strcmp(Field[0],"int")==0)

{

float f;

f = atof(Field[1]);

systick_reload = (uint32_t)(1000.0/f);

systick_cnt = systick_reload;

}

else if(strcmp(Field[0],"off")==0)

{

GPIO_ResetBits(GPIOC, GPIO_Pin_8);

systick_on=0;

}

else if(strcmp(Field[0],"on")==0)

{

systick_on=1;

}

else UART_PutString("command error\r\n");

UART_PutString("\r\n");

UART_PutString("-----SysTick TEST MENU-----\r\n");

UART_PutString("int 10 : LED flash 10 Hz\r\n");

UART_PutString("off : LED off\r\n");

UART_PutString("on : LED on\r\n");

UART_PutString("-----------------------\r\n\n");

UART_STR_EXIST=0;

return;

}