« mbedプロトタイピングツール | トップページ | STM32 Primer2のバッテリー不調 »

STM32 Primer2でシリアルポートを使用する(割り込み編)

前回に引き続き、STM32 Primer2でシリアルポート(USART2)を使った実験を行いました。前回はポーリング形式のデーター送受信でしたが、今回は割り込みを使ってみました。

検証環境

  • CircleOS  3.80
  • STM32F10x Standard Peripherals Library V3.1.0 (以下STライブラリ)
  • Ride7 V7.24.09
  • シリアルポートとしてUSART2を使用

動作概要

  1. データー受信時(RXNE = 1)に割り込みをかけ、受信データーを受信バッファ(RxBuffer)に格納
  2. getchar()関数の呼び出しで受信バッファから1文字分のデーターを取得。受信データーがない場合は、0を返す(標準のgetcharと異なる部分)
  3. putchar()関数の呼び出しで送信データーを送信バッファ(TxBuffer)に格納
  4. 送信バッファのデーターを、Transmit Data Register Empty(TXE)割り込発生契機にUSART2_DRに書き込むことによってデーターを送信する。送信バッファが空になると割り込みを停止する

一文字毎に割り込みを発生させるのもレジスタの退避などでオーバーヘッドがありそうですが、前回書いた通り、STM32 Primer2のCircleOSではユーザーアプリに処理が回ってくる間隔が長いため(33ms以上)、データーの取りこぼしを防止するためには受信側の割り込み処理は必須です。

STM32における割り込みの優先度

Cortex-M3 テクニカルリファレンスマニュアルとSTライブラリの間で、割り込み優先度レベルの表記に差分がありました。内容を以下に示します。

STM32は16レベルの割り込み優先度をサポートします(4bit幅の優先度レベルフィールド)。優先度レベルフィールドは、横取り優先度(preemption priority)とサブ優先度(sub-priority)に分割されます。また、横取り優先度とサブ優先度の分割はPriority Groupの指定によってプログラマブルになっています。

Cortex-M3 テクニカルリファレンスマニュアルでは、Priority Group 0は横取り優先度に優先度レベルフィールドの上位7 bitを割り当てる定義になっているため、STM32では16レベル全てが異なる横取り優先度になります。

一方で、STライブラリではmisc.hにて以下のようにNVIC_PriorityGroup_x定数を定義しています。 NVIC_PriorityGroup_0に対してサブ優先度フィールドを4 bit割り当てているため、横取り優先度がありません。

NVIC_PriorityGroup PreemptionPri SubPriority Description
NVIC_PriorityGroup_0    0 0-15  0 bits pre-emption priority
4 bits subpriority
NVIC_PriorityGroup_1 0-1 0-7 1 bits pre-emption priority
3 bits subpriority
NVIC_PriorityGroup_2 0-3 0-3 2 bits pre-emption priority
2 bits subpriority
NVIC_PriorityGroup_3 0-7 0-1 3bits pre-emption priority
1bits subpriority
NVIC_PriorityGroup_4 0-15 0 4bits pre-emption priority
0bits subpriority

実は、STライブラリのmisc.hの中でPriority Group値の読替を行っており、STライブラリのNVIC_PriorityGroup_0は、Cortex-M3 テクニカルリファレンスのPriorityGroup 7に該当し等価です(当たり前ですが)。
 #define NVIC_PriorityGroup_0         ((uint32_t)0x700)

また、上位2 bitが横取り優先度となるグループ設定を行った場合、Cortex-M3 テクニカルリファレンスの表記では、横取り優先度レベルは8 bitフィールドの上位2 bitで表されるため、0xC0, 0x80, 0x40, 0x00の値を取ります。一方STライブラリでは3, 2, 1, 0の値を指定します。

STM32 Primer2の場合、CircleOSにてNVIC_PriorityGroup_2を設定しています。また、Mems用のTIM2割り込みを、横取り優先度=1, サブ優先度=1に設定してしています。今回、USART2の割り込に対して、横取り優先度=2, サブ優先度=0を設定しました。

プログラムコード

前回の「STM32 Primer2でシリアルポートを使用する」で使用したSTM32F10X_IO_putchar.cに割り込みハンドラとバッファ制御処理を追加しました。割り込み優先度はSTライブラリを使用して以下のように設定しました。
  NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;
  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;
  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;

Priority GroupはCircleOS起動時に設定済みのため触っていません。割り込みハンドラはCircleOSが管理しているため、以下のようにCircleOSのUTIL_SetIrqHandlerL関数を呼び出して登録します。
 UTIL_SetIrqHandler(0x00D8, USART2_IRQHandler);

0x00D8はUSART2_IRQ用割り込みベクターのアドレス(ベクターテーブル先頭からのオフセット値)です。

割り込みハンドラー(USART2_IRQHandler)では、STライブラリのUSART_GetITStatus()関数を使って割り込み要因を調べ、要因別の処理を行っています。コード全体を以下に示します。

#include "stm32f10x.h"
#include "circle_api.h"

/* Private defines -----------------------------------------------------------*/
#define BUFF_SIZE     255

/* Private variables ---------------------------------------------------------*/
unsigned char __io_init_done = 0;

u8 TxBuffer[BUFF_SIZE];
u8 RxBuffer[BUFF_SIZE];

vu8 TxWrCounter = 0x00;         // Write counter for Tx data buffer
vu8 TxRdCounter = 0x00;         // Read counter for Tx data buffer
vu8 RxWrCounter = 0x00;         // Write counter for Rx data buffer
vu8 RxRdCounter = 0x00;         // Read counter for Rx data buffer

//unsigned int __io_Main_Osc = 8000000;

/* Private function prototypes -----------------------------------------------*/
void USART2_IRQHandler(void);


//called by the user app to tell us what is the main osc frequency
void __io_SetMainOscFreq( unsigned int NewFreq )
    {
    //__io_Main_Osc=NewFreq; //save new freq value
    __io_init_done = 0; //force reinit at next putchar
    //we need to keep this even if we ignore the NewFreq param
    //because if the user calls this, it means that he probably changed his clock settings
    //so we must really reinit
    }

/*******************************************************************************
* Function Name  : InitUSART
* Description    : Initialize USART2 and NVIC
*                  Set Irqvector of USART2 IRQ
*
*******************************************************************************/
void __io_init( void )
  {
  GPIO_InitTypeDef GPIO_InitStructure;
  USART_InitTypeDef USART_InitStructure;
  NVIC_InitTypeDef NVIC_InitStructure;

  /* Enable GPIOx and AFIO clocks */
  /* USART2 used for Primer2 */
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE);

  /* Enable USART2 clocks */ 
  RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);

  /* Set IRQ Handler to NVIC table */
  UTIL_SetIrqHandler(0x00D8, USART2_IRQHandler);

  /* NVIC_Configuration to enable USART2 IRQ */
  NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;
  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;
  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
  NVIC_Init(&NVIC_InitStructure);
 
  /* Configure the GPIO ports */
  /* Configure USARTx_Tx as alternate function push-pull */
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
  GPIO_Init(GPIOA, &GPIO_InitStructure);

  /* Configure USARTx_Rx as input floating */
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
  GPIO_Init(GPIOA, &GPIO_InitStructure);

/* USARTx configuration ------------------------------------------------------*/
  USART_InitStructure.USART_BaudRate = 9600;
  USART_InitStructure.USART_WordLength = USART_WordLength_8b;
  USART_InitStructure.USART_StopBits = USART_StopBits_1;
  USART_InitStructure.USART_Parity = USART_Parity_No ;
  USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
  USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
  /*
  USART_InitStructure.USART_Clock = USART_Clock_Disable;
  USART_InitStructure.USART_CPOL = USART_CPOL_Low;
  USART_InitStructure.USART_CPHA = USART_CPHA_2Edge;
  USART_InitStructure.USART_LastBit = USART_LastBit_Disable;
  */
 
  /* Configure the USARTx */
  USART_Init(USART2, &USART_InitStructure);

  USART_ClearITPendingBit(USART2, USART_IT_RXNE);
  USART_ClearITPendingBit(USART2, USART_IT_TXE);

  /* Enable USART2 Receive and Transmit interrupts */
  USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);
  USART_ITConfig(USART2, USART_IT_TXE, ENABLE);

  /* Enable the USART2 */
  USART_Cmd(USART2, ENABLE);

  // IO init done!
  __io_init_done = 1;
  }


/*******************************************************************************
* Function Name  : DeenitUSART
* Description    : Restore Irqvector of USART2 irq
*                  Disable USART2 and USART2 irq
*
*******************************************************************************/
void DeinitUSART(void)
{
  USART_ITConfig(USART2, USART_IT_RXNE, DISABLE);
  USART_ITConfig(USART2, USART_IT_TXE, DISABLE);
  USART_Cmd(USART2, DISABLE);

  __io_init_done = 0;
}


/*******************************************************************************
* Function Name  : __io_putchar
* Description    : Write data to TxBuffer (Do not write USART DR)
*                  Actrual data transfer is done by IRQ Handler
* Input          : charactor to send
*******************************************************************************/
void __io_putchar( char c )
   {
   //init UART if needed
   if( !__io_init_done )
      {
      __io_init();
      }

   // \n is not enough. Need \r too!
   if( c == 0x0A )
      {
      __io_putchar( 0x0D );
      }

/*
    while(USART_GetFlagStatus(USART2, USART_FLAG_TXE) == RESET)
      {
      }
    USART_SendData(USART2, (u8)c);
*/
    u16 emptyFlag;

    if (TxWrCounter == TxRdCounter)
        emptyFlag = 1;
    else
        emptyFlag = 0;
    TxBuffer[TxWrCounter] = (u8)c;
    if (++TxWrCounter == BUFF_SIZE)
        TxWrCounter = 0;

    if (emptyFlag)
      USART_ITConfig(USART2, USART_IT_TXE, ENABLE);
   }

int
putchar (c)
int c;
    {
    __io_putchar( (char) c );
    return c;
    }


/*******************************************************************************
* Function Name  : __io_getchar
* Description    : Read data from RxBuffer (Do not read USART DR)
*                  Actrual data reception is done by IRQ Handler
* Return         : RxBuffer not empty-> Received charactor
*                : RxBuffer empty    -> 0
*******************************************************************************/
int __io_getchar()
   {
   //init UART if needed
   if( !__io_init_done )
      {
      __io_init();
      }

/*
   unsigned short value;
   unsigned short wStatus;

   while(USART_GetFlagStatus(USART2, USART_FLAG_RXNE) == RESET)
      {
      }
   value = USART_ReceiveData(USART2);

   return (int)(value);
*/
    u8 ch, rxRdNext;

    if (RxRdCounter == RxWrCounter)
        return 0;

    rxRdNext = RxRdCounter + 1;
    if (rxRdNext == BUFF_SIZE)
        rxRdNext = 0;

    ch = RxBuffer[rxRdNext];
    RxRdCounter = rxRdNext;
    return (int)ch;
   }


int getchar()
   {
   return __io_getchar();
   }


/*******************************************************************************
* Function Name  : USART2_IRQHandler
* Description    : Write received data to RxBuffer
*                  Send data from TxBuffer
*******************************************************************************/
void USART2_IRQHandler(void)
{
  u8 rxWrNext;

  if(USART_GetITStatus(USART2, USART_IT_RXNE) != RESET)
  {
    rxWrNext = RxWrCounter + 1;
    if(rxWrNext == BUFF_SIZE)
    {
      rxWrNext = 0;
    }
    if (rxWrNext == RxRdCounter)     // Rx buffer full (Write counter overtake Read counter)
    {
      USART_ReceiveData(USART2);
      return;
    }
   
    RxBuffer[rxWrNext] = USART_ReceiveData(USART2);
    RxWrCounter = rxWrNext;          // Now RxWrCounte point next index (Wr > Rd)
  }

  if(USART_GetITStatus(USART2, USART_IT_TXE) != RESET)
  {   
    if (TxRdCounter == TxWrCounter)         // Tx buffer empty
    {
      USART_ITConfig(USART2, USART_IT_TXE, DISABLE);
      return;
    }
   
    USART_SendData(USART2, TxBuffer[TxRdCounter++]);
    if(TxRdCounter == BUFF_SIZE)
    {
      TxRdCounter = 0;
    }
  }
}

ビルド方法

STM32 Primer2でシリアルポートを使用する」に示した方法を使ってRide7でビルドします。STライブラリのソースに加えて、STM32F10X_IO_putchar.cもプロジェクトフォルダーにおいてコンパイルします。LD Linker設定のUART0 Putcharライブラリ設定はNoにします。

アプリケーションからの使用方法

以下のようにgatchar(), putchar()関数を呼び出すと、裏で割り込みベースの入出力が動きます。

/*******************************************************************************
* Function Name  : Application_Handler
* Description    : Management of the Circle_App.
*
* Input          : None
* Return         : MENU_CONTINUE
*******************************************************************************/
enum MENU_code Application_Handler(void)
    {

    // TODO: Write your application handling here.
    int c;
   
    while ( (c = getchar()) !=0 )
        putchar(c);

    // This routine will get called repeatedly by CircleOS, until we
    // return MENU_LEAVE

#if 1
    // If the button is pressed, the application is exited
    if(BUTTON_GetState() == BUTTON_PUSHED)
        {
        BUTTON_WaitForRelease();
        DeinitUSART();
        return MENU_Quit();
        }
#endif

    return MENU_CONTINUE;   // Returning MENU_LEAVE will quit to CircleOS
    }

アプリケーションを終了する際に、DeinitUSART()を呼んで割り込みの停止とUSART2の停止を行っています。割り込みを有効にしたままアプリケーションを終了した後で、シリアルポートにデーターを送るとCircleOSがメニュー操作を受け付けなくなってしまったため、後始末として入れています。

リングバッファの管理が手抜きなのですが(書き込みポインターが読み出しポインターに追いついたか否かだかで制御しており、バッファーに溜まっているデーター量は管理していない)、取りこぼし等発生することなく動いています。

« mbedプロトタイピングツール | トップページ | STM32 Primer2のバッテリー不調 »

STM32-ARM」カテゴリの記事

コメント

> NVIC_PriorityGroup_0に対してサブ優先度フィールドを4 bit割り当てているため、横取り優先度がありません。

ああ、そう言う事か。
なんかこの優先度設定が解り辛くて、と言うか解っていなくて、悩んでいたんですよね。

STライブラリやCircleOSのコードを眺めていると、横取り優先度1とかを使っているのですが、CQ出版の「ARM Cortex-M3システム開発ガイド」7.2章を見るとそんな優先度は設定できない筈なので私も悩みました。混乱を招くライブラリの仕様ですよねぇ。

この記事へのコメントは終了しました。

« mbedプロトタイピングツール | トップページ | STM32 Primer2のバッテリー不調 »

2018年10月
  1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31      
無料ブログはココログ