LoginSignup
5
1

More than 3 years have passed since last update.

Nordic製マイコンでUARTドライバを作ってみた話

Last updated at Posted at 2020-01-26

はじめに

前職でもUARTドライバを書いたことがあるのですが、「システムの起動時かつ一度しか使用されない」&「こちらからコマンドを送らない限り、接続相手は何も送ってこない」仕様だったので、その時はブロッキングで動作するものを作っていたました。それで困ることが当時はなかったので。。。

やりたいこと

今回、送信処理・受信処理共に、ブロッキングではなくノンブロッキングで動作するドライバを書いてみたいと思います。

・CTS・RTSを使ったフロー制御は行わない。
・受信バッファを用意し、割り込みで取得したデータはバッファに蓄積する。
・受信データの正当性はドライバではチェックしない(サムチェックなど。そういった物はあくまでドライバを叩く上位層のアプリの取り決めなので、ドライバの仕事ではないためです。)
・全二重通信を同時に2ポートまで行える。
そして今回は、SDKは使用せずレジスタ直叩きでやってみたいと思います!!!
需要あるのかな...

制限事項(NordicのChipの仕様による)

・データビットは8bitのみで、他のbit幅に設定することはできません。
・パリティビットは、なし/あり設定できますが、偶数のみとなります。(マイコンのレジスタには奇数もあった気が。。。)
・ストップビットも1bitのみ使用できます。

ブロック図

スクリーンショット 2020-02-02 0.47.01.png

UARTE[0..1]とありますね。UARTのインスタンスは2つまで使用できるようですが、
厳密には、
 ・UART0のみ
 ・UARTE0、UARTE1を使用
 ・UART0、UARTE1を使用
の3パターンが設定できるとの事です。
UART1はUARTEのみ(EasyDMA)使用できます。

ソースコード

sample.c
#define UART_BUFFER_SIZE 256
#define UART_PORT0_TX_PIN NRF_GPIO_PIN_MAP(1, 1)
#define UART_PORT0_RX_PIN NRF_GPIO_PIN_MAP(1, 2)
#define UART_PORT1_TX_PIN NRF_GPIO_PIN_MAP(0, 6)
#define UART_PORT1_RX_PIN NRF_GPIO_PIN_MAP(0, 8)

typedef struct
{
    uint8_t buffer[UART_BUFFER_SIZE];
   volatile uint32_t head;
} ringBuffer;

ringBuffer rx_buffer0;
ringBuffer rx_buffer1;
ringBuffer tx_buffer0;
ringBuffer tx_buffer1;

// UARTE0の割り込みハンドラ
void UARTE0_UART0_IRQHandler(void)
{
    NRF_LOG_INFO("UARTE0_UART0_IRQHandler");

    if(NRF_UARTE0->EVENTS_CTS)
    {
        NRF_LOG_INFO("NRF_UARTE0->EVENTS_CTS");
        NRF_UARTE0->EVENTS_CTS = 0;
        return;
    }

    if(NRF_UARTE0->EVENTS_NCTS)
    {
        NRF_LOG_INFO("NRF_UARTE0->EVENTS_NCTS");
        return;
    }
    if(NRF_UARTE0->EVENTS_RXDRDY)
    {
        NRF_LOG_INFO("NRF_UARTE0->EVENTS_RXDRDY");
        NRF_UARTE0->EVENTS_RXDRDY = 0;
        return;
    }
    if(NRF_UARTE0->EVENTS_ENDRX)
    {
        NRF_UARTE0->EVENTS_ENDRX = 0;
        NRF_LOG_INFO("NRF_UARTE0->EVENTS_ENDRX");
        NRF_LOG_HEXDUMP_INFO(rx_buffer0.buffer,NRF_UARTE0->RXD.AMOUNT);
        NRF_LOG_INFO("bytes = %d",NRF_UARTE0->RXD.AMOUNT);
        return;
    }
    if(NRF_UARTE0->EVENTS_TXDRDY)
    {
        NRF_LOG_INFO("NRF_UARTE0->EVENTS_TXDRDY");
        NRF_UARTE0->EVENTS_TXDRDY = 0;
        return;
    }
    if(NRF_UARTE0->EVENTS_ENDTX)
    {
        NRF_LOG_INFO("NRF_UARTE0->EVENTS_ENDTX");
        NRF_UARTE0->EVENTS_ENDTX = 0;
        NRF_UARTE0->TASKS_STOPTX = 1;
        return;
    }
    if(NRF_UARTE0->EVENTS_ERROR)
    {
        NRF_LOG_INFO("NRF_UARTE0->EVENTS_ERROR");
        return;
    }
    if(NRF_UARTE0->EVENTS_RXTO)
    {
        NRF_LOG_INFO("NRF_UARTE0->EVENTS_RXTO");
        return;
    }
    if(NRF_UARTE0->EVENTS_RXSTARTED)
    {
        NRF_LOG_INFO("NRF_UARTE0->EVENTS_RXSTARTED");
        NRF_UARTE0->EVENTS_RXSTARTED = 0;
        return;
    }
    if(NRF_UARTE0->EVENTS_TXSTARTED)
    {
        NRF_LOG_INFO("NRF_UARTE0->EVENTS_TXSTARTED");
        NRF_UARTE0->EVENTS_TXSTARTED = 0;
        return;
    }
    if(NRF_UARTE0->EVENTS_TXSTOPPED)
    {
        NRF_LOG_INFO("NRF_UARTE0->EVENTS_TXSTOPPED");
        NRF_UARTE0->EVENTS_TXSTOPPED = 0;
        return;
    }
    return;
}

// UARTE1の割り込みハンドラ
void UARTE1_IRQHandler(void)
{
    NRF_LOG_INFO("UARTE1_IRQHandler");

    if(NRF_UARTE1->EVENTS_CTS)
    {
        NRF_LOG_INFO("NRF_UARTE1->EVENTS_CTS");
        NRF_UARTE1->EVENTS_CTS = 0;
        return;
    }

    if(NRF_UARTE1->EVENTS_NCTS)
    {
        NRF_LOG_INFO("NRF_UARTE1->EVENTS_NCTS");
        return;
    }
    if(NRF_UARTE1->EVENTS_RXDRDY)
    {
        NRF_LOG_INFO("NRF_UARTE1->EVENTS_RXDRDY");
        NRF_UARTE1->EVENTS_RXDRDY = 0;
        return;
    }
    if(NRF_UARTE1->EVENTS_ENDRX)
    {
        NRF_UARTE1->EVENTS_ENDRX = 0;
        NRF_LOG_INFO("NRF_UARTE1->EVENTS_ENDRX");
        NRF_LOG_HEXDUMP_INFO(rx_buffer1.buffer,NRF_UARTE1->RXD.AMOUNT);
        NRF_LOG_INFO("bytes = %d",NRF_UARTE1->RXD.AMOUNT);
        return;
    }
    if(NRF_UARTE1->EVENTS_TXDRDY)
    {
        NRF_LOG_INFO("NRF_UARTE1->EVENTS_TXDRDY");
        NRF_UARTE1->EVENTS_TXDRDY = 0;
        return;
    }
    if(NRF_UARTE1->EVENTS_ENDTX)
    {
        NRF_LOG_INFO("NRF_UARTE1->EVENTS_ENDTX");
        NRF_UARTE1->EVENTS_ENDTX = 0;
        NRF_UARTE1->TASKS_STOPTX = 1;
        return;
    }
    if(NRF_UARTE1->EVENTS_ERROR)
    {
        NRF_LOG_INFO("NRF_UARTE1->EVENTS_ERROR");
        return;
    }
    if(NRF_UARTE1->EVENTS_RXTO)
    {
        NRF_LOG_INFO("NRF_UARTE1->EVENTS_RXTO");
        return;
    }
    if(NRF_UARTE1->EVENTS_RXSTARTED)
    {
        NRF_LOG_INFO("NRF_UARTE1->EVENTS_RXSTARTED");
        NRF_UARTE1->EVENTS_RXSTARTED = 0;
        return;
    }
    if(NRF_UARTE1->EVENTS_TXSTARTED)
    {
        NRF_LOG_INFO("NRF_UARTE1->EVENTS_TXSTARTED");
        NRF_UARTE1->EVENTS_TXSTARTED = 0;
        return;
    }
    if(NRF_UARTE1->EVENTS_TXSTOPPED)
    {
        NRF_LOG_INFO("NRF_UARTE1->EVENTS_TXSTOPPED");
        NRF_UARTE1->EVENTS_TXSTOPPED = 0;
        return;
    }
    return;
}

// 初期化関数
void uart_init(void) {

    NRF_UARTE0->INTENCLR = 0xFFFFFFFF;
    NRF_UARTE0->INTENSET |= (0<<22) | (0<<20) | (0<<19) | (1<<8) | (0<<7) | (1<<4) | (0<<2);
    NVIC_ClearPendingIRQ(UARTE0_UART0_IRQn);
    NVIC_SetPriority(UARTE0_UART0_IRQn, NRFX_UARTE_DEFAULT_CONFIG_IRQ_PRIORITY);
    NVIC_EnableIRQ(UARTE0_UART0_IRQn);
    NRF_UARTE0->PSEL.TXD = UART_PORT0_TX_PIN;
    NRF_UARTE0->PSEL.RXD = UART_PORT0_RX_PIN;
    NRF_UARTE0->PSEL.CTS = 0xFFFFFFFF;
    NRF_UARTE0->PSEL.RTS = 0xFFFFFFFF;

    struct Config_Register{
        uint16_t HWFC : 1;
        uint16_t PARITY : 3;
        uint16_t STOP : 1;
        uint16_t RESERVE : 11;
    };
    union {
        struct Config_Register bit;
        uint16_t byte;
    } config;

    config.bit.HWFC = 0; // フロー制御なし
    config.bit.PARITY = 0; // パリティなし
    config.bit.STOP = 0; // Stop bit 1
    NRF_UARTE0->CONFIG = config.byte;
    NRF_UARTE0->BAUDRATE = 0x01D60000;
    NRF_UARTE0->ENABLE = 0x00000008;

    NRF_UARTE0->EVENTS_CTS = 0;
    NRF_UARTE0->EVENTS_ENDTX = 0;
    NRF_UARTE0->EVENTS_TXDRDY = 0;
    NRF_UARTE0->EVENTS_TXSTARTED = 0;
    NRF_UARTE0->EVENTS_RXSTARTED = 0;


    NRF_UARTE1->INTENCLR = 0xFFFFFFFF;
    NRF_UARTE1->INTENSET |= (0<<22) | (0<<20) | (0<<19) | (1<<8) | (0<<7) | (1<<4) | (0<<2);
    NVIC_ClearPendingIRQ(UARTE1_IRQn);  // 割り込みクリア
    NVIC_SetPriority(UARTE1_IRQn, NRFX_UARTE_DEFAULT_CONFIG_IRQ_PRIORITY);
    NVIC_EnableIRQ(UARTE1_IRQn);
    NRF_UARTE1->PSEL.TXD = UART_PORT1_TX_PIN;
    NRF_UARTE1->PSEL.RXD = UART_PORT1_RX_PIN;
    NRF_UARTE1->PSEL.CTS = 0xFFFFFFFF;
    NRF_UARTE1->PSEL.RTS = 0xFFFFFFFF;

    config.bit.HWFC = 0; // フロー制御なし
    config.bit.PARITY = 0; // パリティなし
    config.bit.STOP = 0; // Stop bit 1
    NRF_UARTE1->CONFIG = config.byte;
    NRF_UARTE1->BAUDRATE = 0x01D60000;
    NRF_UARTE1->ENABLE = 0x00000008;

    NRF_UARTE1->EVENTS_CTS = 0;
    NRF_UARTE1->EVENTS_ENDTX = 0;
    NRF_UARTE1->EVENTS_TXDRDY = 0;
    NRF_UARTE1->EVENTS_TXSTARTED = 0;
    NRF_UARTE1->EVENTS_RXSTARTED = 0;

    return;
}

void uart_write_data0(unsigned char c)
{
  int i = (tx_buffer0.head + 1) % UART_BUFFER_SIZE;

  tx_buffer0.buffer[tx_buffer0.head] = c;
  tx_buffer0.head = i;

}

// データ送信
void uart_send_data0(uart_port_select_t port_select, uint8_t* p_send_data, uint8_t send_data_len) {
    NRF_LOG_INFO("uart_send_data0");

    tx_buffer0.head = 0;

    while( tx_buffer0.head < send_data_len )
    {
        uart_write_data0(*p_send_data);
        p_send_data++;
    }


    //send data
    NRF_UARTE0->EVENTS_ENDTX = 0;
    NRF_UARTE0->EVENTS_TXSTOPPED = 0;
    NRF_UARTE0->TXD.PTR = (uint32_t)&tx_buffer0.buffer[0]; //reset pointer to start of buffer
    NRF_UARTE0->TXD.MAXCNT = tx_buffer0.head;
    NRF_UARTE0->TASKS_STARTTX = 1;  //trigger start task to send data to host
    while(NRF_UARTE0->EVENTS_ENDTX == 0 && NRF_UARTE0->EVENTS_TXSTOPPED == 0){}

    NRF_LOG_INFO("NRF_UARTE0->EVENTS_ENDTX == 0 && NRF_UARTE0->EVENTS_TXSTOPPED == 0");

    return;
}

static void uart_write_data1(unsigned char c)
{
  int i = (tx_buffer1.head + 1) % UART_BUFFER_SIZE;

  tx_buffer1.buffer[tx_buffer1.head] = c;
  tx_buffer1.head = i;

}

void uart_send_data1(uart_port_select_t port_select, uint8_t* p_send_data, uint8_t send_data_len) {
    NRF_LOG_INFO("uart_send_data1");
    tx_buffer1.head = 0;

    while( tx_buffer1.head < send_data_len )
    {
        uart_write_data1(*p_send_data);
        p_send_data++;
    }


    //send data
    NRF_UARTE1->EVENTS_ENDTX = 0;
    NRF_UARTE1->EVENTS_TXSTOPPED = 0;
    NRF_UARTE1->TXD.PTR = (uint32_t)&tx_buffer1.buffer[0]; //reset pointer to start of buffer
    NRF_UARTE1->TXD.MAXCNT = tx_buffer1.head;
    NRF_UARTE1->TASKS_STARTTX = 1;  //trigger start task to send data to host
    while(NRF_UARTE1->EVENTS_ENDTX == 0 && NRF_UARTE1->EVENTS_TXSTOPPED == 0){}

    NRF_LOG_INFO("NRF_UARTE1->EVENTS_ENDTX == 0 && NRF_UARTE1->EVENTS_TXSTOPPED == 0");

    return true;
}



void uart_read(void) {
    NRF_LOG_INFO("uart_read");

    NRF_UARTE0->SHORTS = (UARTE_SHORTS_ENDRX_STARTRX_Enabled << UARTE_SHORTS_ENDRX_STARTRX_Pos);
    NRF_UARTE0->EVENTS_RXDRDY = 0;
    NRF_UARTE0->EVENTS_ENDRX = 0;
    NRF_UARTE0->EVENTS_RXTO = 0;
    NRF_UARTE0->RXD.MAXCNT = 10; // 10バイト受信したら割り込みしてくれます
    NRF_UARTE0->RXD.PTR = (uint32_t)&rx_buffer0; //reset pointer to start of buffer
    NRF_UARTE0->TASKS_STARTRX = 1;  //trigger start task to send data to host
    NRF_LOG_INFO("NRF_UARTE0->TASKS_STARTRX = 1");


    NRF_UARTE1->SHORTS = (UARTE_SHORTS_ENDRX_STARTRX_Enabled << UARTE_SHORTS_ENDRX_STARTRX_Pos);
    NRF_UARTE1->EVENTS_RXDRDY = 0;
    NRF_UARTE1->EVENTS_ENDRX = 0;
    NRF_UARTE1->EVENTS_RXTO = 0;
    NRF_UARTE1->RXD.MAXCNT = 10; // 10バイト受信したら割り込みしてくれます
    NRF_UARTE1->RXD.PTR = (uint32_t)&rx_buffer1; //reset pointer to start of buffer
    NRF_UARTE1->TASKS_STARTRX = 1;  //trigger start task to send data to host
    NRF_LOG_INFO("NRF_UARTE1->TASKS_STARTRX = 1");

}

最後に

上記のコードで送受信を割り込みで行える事が確認できました!!!
SDKは使わず、レジスタ直叩きだったのでマイコン仕様書とにらめっこしながらの作業で疲れましたね。。。
(SDKのv16.0.0を使っての動作確認も出来てますので、そちらを使うことをオススメします)

実は、UARTE1の割り込みタイミングでハードフォルトになってしまう現象が発生し、大分困ったのですがそれはまた後日記事にする事にします。。

5
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
1