UART通信用ソフトウェアFIFOバッファ

作成者:Scott Schmit、最終更新日:2014年4月22日

背景

UART通信は、組み込み設計における非常にシンプルで安価な通信手段です。通常、マイクロコントローラは、各UARTレシーバおよびトランスミッタに対して、1バイトまたは2バイトのハードウェアバッファしか持っていません。マルチバイトの送信を必要とする場合、ユーザーはデータが上書きされたり廃棄されたりしないように、UARTトランスミッタが1バイトずつすべてのデータをシフトアウトするのを待つ必要があります。このバイト単位の待ち時間は、CPUが高速で動作しているときに、低いボーレートでUART通信を行うと増幅されます。ソフトウェアデータバッファは、UARTがトランスミッタまたはレシーバにデータをシフトするのを待つ間、データを格納するための大きなバッファを提供することによって、この問題を軽減します。

目的

本ライブラリは、UART通信のための汎用ソフトウェアバッファを提供することを目的とし、理解しやすいC/C++プログラミング言語で記述しています。バッファは割り込み駆動で、マイコンはUART通信と並行して機能を実行することができます。また、UARTのTXまたはRXを待っている間、マイクロコントローラを低電力モードにすることもできます(低電力モードがサポートされている場合)。

前提条件

  • このソフトウェアバッファを使用するには、マイコンが「UART TX buffer empty」および「UART RX data received」割り込みに対応している必要があります。
  • 読者はプログラミング言語C/C++の経験があることを前提としています。
  • FIFOバッファの予備知識があると有利ですが、必須ではありません。以下はバックグラウンド情報です:リンクをクリックしてください。

動作原理

ソフトウェアバッファは、サーキュラーFIFO(First-In First-Out)バッファのように動作します。データは時系列でバッファに入力、削除されます。バッファのサイズは sw_fifo.h の FIFO_BUFFER_SIZE で定義され、マイコンのRAM容量によって制限されます。ユーザーは、オーバーフローが起こらないようにしながらも、バッファのサイズをできるだけ小さくしておく必要があります。

バッファは外部フラグを使用して、データがRXまたはTXソフトウェアバッファに存在することを示します。 すべてのユーザー関数は、いつでもフラグをチェックして、データがいずれかのバッファに存在するかどうかを確認できます。フラグはsw_fifo.hで外部変数として宣言されています。したがって、フラグを使用するためには、ユーザーが自分のmainファイル(またはフラグチェックが行われる場所)で、以下のように宣言する必要があります。

volatile uint8_t uart_rx_fifo_not_empty_flag = 0; // this flag is automatically set and cleared by the software buffer
volatile uint8_t uart_tx_fifo_not_empty_flag = 0; // this flag is automatically set and cleared by the software buffer

ソフトウェアバッファには、ソフトウェアバッファのオーバーフロー状態を監視するためのフラグが2つあります。これらのフラグは、オーバーフロー状態が発生すると、ソフトウェアバッファによって自動的に設定されます。 しかし、ユーザーは手動でフラグをクリアする必要があります。バッファが十分に大きい場合、ユーザーはこれらのフラグをチェックすることなく、進めることができるはずです。しかし、オーバーフロー状態が発生したかどうかを確認することをお勧めします。これらのフラグはsw_fifo.hで外部変数として宣言されています。したがって、これらのフラグを使用するためには、ユーザーが自分のmainファイル(またはフラグのチェックが行われる場所)で、以下のように変数を宣言する必要があります。

volatile uint8_t uart_rx_fifo_ovf_flag = 0;  // this flag is not automatically cleared by the software buffer
volatile uint8_t uart_tx_fifo_ovf_flag = 0;  // this flag is not automatically cleared by the software buffer

ソフトウェアバッファには、各バッファが現在フルであるかどうかを監視するための2つのフラグも含まれています。これらのフラグはソフトウェアバッファによって自動的に処理されるため、ユーザーがクリアする必要はありません。繰り返しますが、もしバッファが十分に大きく作られていれば、ユーザーはこれらのフラグをチェックすることなく、進めることができるはずです。しかし、オーバーフローを防ぐために、バッファに書き込む前にバッファがフルになったかどうかを確認することをお勧めします。これらのフラグは、sw_fifo.hで外部変数として宣言されています。したがって、これらのフラグを使用するためには、ユーザーが自分のmain.cファイル(またはフラグのチェックが行われる場所)で、以下のように変数を宣言する必要があります。

volatile uint8_t uart_rx_fifo_full_flag = 0; // this flag is automatically set and cleared by the software buffer
volatile uint8_t uart_tx_fifo_full_flag = 0; // this flag is automatically set and cleared by the software buffer

このソフトウェアバッファと対話するためには、ユーザーの関数は単にuart_send_byte()uart_get_byte()関数を呼び出すだけで良いのです。ユーザーは、必要に応じて、6つの利用可能なソフトウェアバッファフラグを監視することができます。正しく機能させるために、ユーザーはグローバル割り込みを有効にする必要があります。また、使用するマイコンに固有の「TX hardware buffer empty」「received valid data」割り込みもそれぞれ有効にする必要があります。

このソフトウェアバッファは、RXとTXのハードウェアバッファを別々に持つデバイスで最も効果的に機能します。マイクロコントローラがRXとTXに同じハードウェアバッファを使用する場合、TXソフトウェアバッファにデータが存在する間は、「受信データ」割り込みを無効にする必要があります。

カスタマイズ

このソフトウェアバッファは、汎用的に書かれています。しかし、いくつかの部分はプラットフォーム固有です(どのマイコンを使用するかによって変わります)。これらの部分は、以下のようにダブルコメントバーで表示されています。

/////////////////////////////////////           
/* platform specific code required */
/////////////////////////////////////

エラー処理

受信エラー
  • 受信データ割り込みが実行された時のみ、RXソフトウェアバッファにデータが追加されるため、RXハードウェアバッファのアンダーフローエラーは発生しないはずです。
  • RXハードウェアバッファのオーバーフローの可能性はあり、必要であればユーザーが対処する必要があります。
  • フレームエラーやパリティエラーが発生する可能性があり、必要に応じてユーザーが処理する必要があります。
  • RXソフトウェアバッファのオーバーフローは、既存のデータを上書きすることはありません。このイベントが発生すると「uart_rx_fifo_ovf_flag」が設定され、ユーザーが手動でクリアするまで設定されたままになります。
  • RXソフトウェアバッファのアンダーフローの可能性があります。この場合、uart_get_byte()関数は0を返します。しかし、uart_get_byte()関数を呼び出す前に「uart_rx_fifo_not_empty_flag」をチェックすれば、これを回避することができます。
送信エラー
  • TXハードウェアバッファのオーバーフローは、ソフトウェアバッファが「TX buffer empty」割り込みの実行を待っているため、発生しないはずです。
  • ソフトウェアバッファは、最後のエレメントがソフトウェアバッファから削除されたとき、「TX buffer empty」割り込みを無効にするので、TXハードウェアバッファのアンダーフローは発生しないはずです。
  • TXソフトウェアバッファのオーバーフローは、既存のデータを上書きすることはありません。このイベントが発生すると「uart_tx_fifo_ovf_flag」が設定され、ユーザーが手動でクリアするまで設定されたままとなります。uart_send_byte()関数を呼び出す前に「uart_tx_fifo_full_flag」をチェックすることで、TX ソフトウェアバッファのオーバーフロー状態を回避することができます。

C/C++コード

////////////////////////////////////////////////////////////////////////////////////////
/* enter necessary header files for proper interrupt vector and UART/USART visibility */
////////////////////////////////////////////////////////////////////////////////////////

#include <sw_fifo.h>

typedef struct {
uint8_t data_buf[FIFO_BUFFER_SIZE]; // FIFO buffer
uint16_t i_first; // index of oldest data byte in buffer
uint16_t i_last; // index of newest data byte in buffer
uint16_t num_bytes; // number of bytes currently in buffer
}sw_fifo_typedef;

sw_fifo_typedef rx_fifo = { {0}, 0, 0, 0 }; // declare a receive software buffer
sw_fifo_typedef tx_fifo = { {0}, 0, 0, 0 }; // declare a transmit software buffer

/**************************************************************************************************************/
// UART receive interrupt sub-routine
// - interrupts when valid data exists in rx hardware buffer
// - checks if there’s room in the rx software buffer
// - if there’s room, it transfers the received data into the sw buffer
// - automatically handles “uart_rx_buffer_full_flag”
// - sets overflow flag upon software buffer overflow (doesn’t overwrite existing data)
//////////////////////////////////////////////
/
enter name of UART RX IRQ Handler here */ {
//////////////////////////////////////////////

/* Explicitly clear the source of interrupt if necessary */

if(rx_fifo.num_bytes == FIFO_BUFFER_SIZE) { // if the sw buffer is full
uart_rx_fifo_ovf_flag = 1; // set the overflow flag
}else if(rx_fifo.num_bytes < FIFO_BUFFER_SIZE) { // if there’s room in the sw buffer

///////////////////////////////////////////////////
/* read error/status reg here if desired         */
/* handle any hardware RX errors here if desired */
///////////////////////////////////////////////////
 
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
rx_fifo.data_buf[rx_fifo.i_last] = /* enter pointer to UART rx hardware buffer here */ // store the received data as the newest data element in the sw buffer
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
 
rx_fifo.i_last++;                              // increment the index of the most recently added element
rx_fifo.num_bytes++;                           // increment the bytes counter

}
if(rx_fifo.num_bytes == FIFO_BUFFER_SIZE) { // if sw buffer just filled up
uart_rx_fifo_full_flag = 1; // set the RX FIFO full flag
}
if(rx_fifo.i_last == FIFO_BUFFER_SIZE) { // if the index has reached the end of the buffer,
rx_fifo.i_last = 0; // roll over the index counter
}
uart_rx_fifo_not_empty_flag = 1; // set received-data flag
} // end UART RX IRQ handler
/***************************************************************************************************************/

/**************************************************************************************************************/
// UART transmit interrupt sub-routine
// - interrupts when the tx hardware buffer is empty
// - checks if data exists in the tx software buffer
// - if data exists, it places the oldest element of the sw buffer into the tx hardware buffer
// - if the sw buffer is emptied, it disables the “hw buffer empty” interrupt
// - automatically handles “uart_tx_buffer_full_flag”
//////////////////////////////////////////////
/
enter name of UART TX IRQ Handler here */ {
//////////////////////////////////////////////

/* Explicitly clear the source of interrupt if necessary */

if(tx_fifo.num_bytes == FIFO_BUFFER_SIZE) { // if the sw buffer is full
uart_tx_fifo_full_flag = 0; // clear the buffer full flag because we are about to make room
}
if(tx_fifo.num_bytes > 0) { // if data exists in the sw buffer

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/* enter pointer to UART tx hardware buffer here */ = tx_fifo.data_buf[tx_fifo.i_first]; // place oldest data element in the TX hardware buffer
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
 
tx_fifo.i_first++;                        // increment the index of the oldest element
tx_fifo.num_bytes--;                      // decrement the bytes counter

}
if(tx_fifo.i_first == FIFO_BUFFER_SIZE) { // if the index has reached the end of the buffer,
tx_fifo.i_first = 0; // roll over the index counter
}
if(tx_fifo.num_bytes == 0) { // if no more data exists

uart_tx_fifo_not_empty_flag = 0;          // clear flag

//////////////////////////////////////////////////////////////////////////
/* disable UART "TX hw buffer empty" interrupt here                     */
/* if using shared RX/TX hardware buffer, enable RX data interrupt here */
//////////////////////////////////////////////////////////////////////////

}
}// end UART TX IRQ handler
/***************************************************************************************************************/

/***************************************************************************************************************/
// UART data transmit function
// - checks if there’s room in the transmit sw buffer
// - if there’s room, it transfers data byte to sw buffer
// - automatically handles “uart_tx_buffer_full_flag”
// - sets the overflow flag upon software buffer overflow (doesn’t overwrite existing data)
// - if this is the first data byte in the buffer, it enables the “hw buffer empty” interrupt
void uart_send_byte(uint8_t byte) {

///////////////////////////////////////////////////////////
/* disable interrupts while manipulating buffer pointers */
///////////////////////////////////////////////////////////

if(tx_fifo.num_bytes == FIFO_BUFFER_SIZE) { // no room in the sw buffer
uart_tx_fifo_ovf_flag = 1; // set the overflow flag
}else if(tx_fifo.num_bytes < FIFO_BUFFER_SIZE) { // if there’s room in the sw buffer
tx_fifo.data_buf[tx_fifo.i_last] = byte; // transfer data byte to sw buffer
tx_fifo.i_last++; // increment the index of the most recently added element
tx_fifo.num_bytes++; // increment the bytes counter
}
if(tx_fifo.num_bytes == FIFO_BUFFER_SIZE) { // if sw buffer is full
uart_tx_fifo_full_flag = 1; // set the TX FIFO full flag
}
if(tx_fifo.i_last == FIFO_BUFFER_SIZE) { // if the “new data” index has reached the end of the buffer,
tx_fifo.i_last = 0; // roll over the index counter
}

///////////////////////
/* enable interrupts */
///////////////////////

if(tx_fifo.num_bytes > 0) { // if there is data in the buffer

uart_tx_fifo_not_empty_flag = 1;               // set flag
 
///////////////////////////////////////////////////////////////////////////
/* if using shared RX/TX hardware buffer, disable RX data interrupt here */
/* enable UART "TX hw buffer empty" interrupt here                       */
///////////////////////////////////////////////////////////////////////////

}
}
/***************************************************************************************************************/

/***************************************************************************************************************/
// UART data receive function
// - checks if data exists in the receive sw buffer
// - if data exists, it returns the oldest element contained in the buffer
// - automatically handles “uart_rx_buffer_full_flag”
// - if no data exists, it clears the uart_rx_flag
uint8_t uart_get_byte(void) {

///////////////////////////////////////////////////////////
/* disable interrupts while manipulating buffer pointers */
///////////////////////////////////////////////////////////

uint8_t byte = 0;
if(rx_fifo.num_bytes == FIFO_BUFFER_SIZE) { // if the sw buffer is full
uart_rx_fifo_full_flag = 0; // clear the buffer full flag because we are about to make room
}
if(rx_fifo.num_bytes > 0) { // if data exists in the sw buffer
byte = rx_fifo.data_buf[rx_fifo.i_first]; // grab the oldest element in the buffer
rx_fifo.i_first++; // increment the index of the oldest element
rx_fifo.num_bytes–; // decrement the bytes counter
}else{ // RX sw buffer is empty
uart_rx_fifo_not_empty_flag = 0; // clear the rx flag
}
if(rx_fifo.i_first == FIFO_BUFFER_SIZE) { // if the index has reached the end of the buffer,
rx_fifo.i_first = 0; // roll over the index counter
}

///////////////////////
/* enable interrupts */
///////////////////////

return byte; // return the data byte
}
/***************************************************************************************************************/

#define FIFO_BUFFER_SIZE 128 // software buffer size (in bytes)

// UART data transmit function
// - checks if there’s room in the transmit sw buffer
// - if there’s room, it transfers data byte to sw buffer
// - automatically handles “uart_tx_buffer_full_flag”
// - sets the overflow flag upon software buffer overflow (doesn’t overwrite existing data)
// - if this is the first data byte in the buffer, it enables the “hw buffer empty” interrupt
void uart_send_byte(uint8_t byte);

// UART data receive function
// - checks if data exists in the receive sw buffer
// - if data exists, it returns the oldest element contained in the buffer
// - automatically handles “uart_rx_buffer_full_flag”
// - if no data exists, it clears the uart_rx_flag
uint8_t uart_get_byte(void);

volatile extern uint8_t uart_rx_fifo_not_empty_flag; // this flag is automatically set and cleared by the software buffer
volatile extern uint8_t uart_rx_fifo_full_flag; // this flag is automatically set and cleared by the software buffer
volatile extern uint8_t uart_rx_fifo_ovf_flag; // this flag is not automatically cleared by the software buffer
volatile extern uint8_t uart_tx_fifo_full_flag; // this flag is automatically set and cleared by the software buffer
volatile extern uint8_t uart_tx_fifo_ovf_flag; // this flag is not automatically cleared by the software buffer
volatile extern uint8_t uart_tx_fifo_not_empty_flag; // this flag is automatically set and cleared by the software buffer

  • 使用例
    次のコードは、ユーザーがメイン関数からUARTソフトウェアバッファを操作する方法を示しています。
#include <sw_fifo.h> // make software buffer visible to this file

volatile uint8_t uart_rx_fifo_not_empty_flag = 0;
volatile uint8_t uart_rx_fifo_full_flag = 0;
volatile uint8_t uart_rx_fifo_ovf_flag = 0;
volatile uint8_t uart_tx_fifo_full_flag = 0;
volatile uint8_t uart_tx_fifo_ovf_flag = 0;
volatile uint8_t uart_tx_fifo_not_empty_flag = 0;

int main (void) {
uint8_t i = 0;
uint8_t rx_data = 0;

// initialize clocks
// disable global interrupts
// initialize gpio
// initialize uart/usart

// enable “UART RX” interrupt and “TX hardware buffer empty” interrupt
// enable global interrupts

while(uart_tx_fifo_full_flag); // wait for room to open up in the software buffer
uart_send_byte(‘A’); // transmit ASCII character ‘A’
while(uart_tx_fifo_full_flag);
uart_send_byte(0x41); // transmit ASCII character ‘A’

// transmit ASCII characters 1-5
for(i=0; i<5; i++) {
while(uart_tx_fifo_full_flag);
uart_send_byte(i+48);
}

while(1) {
// enter sleep mode if supported (wake from UART Rx)

  while(uart_rx_fifo_not_empty_flag) {     // if data exists in software buffer
     rx_data = uart_get_byte();            // grab first data byte from software buffer
      
     /* handle received byte as desired */

     uart_send_byte(rx_data);              // example of how to echo received ASCII characters
  }

  // check for rx overflow condition
  if(uart_rx_fifo_ovf_flag) {

     /* handle rx overflow condition as desired */

     uart_rx_fifo_ovf_flag = 0;  // clear the rx overflow flag
  }

  // check for tx overflow condition
  if(uart_tx_fifo_ovf_flag) {

     /* handle tx overflow condition as desired */

     uart_tx_fifo_ovf_flag = 0;  // clear the tx overflow flag
  }

  // if you need to disable global interrupts, you should wait until the tx fifo is empty
  while(uart_tx_fifo_not_empty_flag);
  /* Disable global interrupts */

  /* Do something */

  /* Re-enable global interrupts */

} // end while
} // end main

筆者からのコメント

ソフトウェアバッファの良いところは、アプリケーションに合わせてカスタマイズできることです。好きなだけ大きくしたり小さくしたりできますし、割り込み駆動のソフトウェアバッファを使えば、UART通信と並行して他の機能を実行することも可能です。マイコンが対応していれば、UARTで文字の着信を待っている間、低消費電力モードにすることができます。このソフトウェアバッファを、「UART RX割り込みからのウェイク」スリープモードに対応したマイコンに実装したところ、見事に成功したのです!このページの目的は、あらゆるマイコンに適用可能な、C/C++ベースの汎用ソフトウェアバッファを提供することでした。便利でわかりやすいと思います。私は、数種類のマイコンに実装して成功しました。コードは自由に使用できますが、「自己責任」になります。コーディングをお楽しみください!

  • Scott

質問/コメント

ご質問やご意見はDigi-KeyのTechForumまでお願いします。




オリジナル・ソース(English)