LCDキャラクターディスプレイ / Cで記述する / ポートもピンもすべて任意に指定できるようにする

テキストからは逸れるが今度は4ビットモードでポートもピンもすべて任意に指定できるようにする。ビットのセット、クリアを、たとい同じポートであったとしてもポート単位ではなくピン1本ずつおこなう。

https://github.com/ti-nspire/AVR/tree/master/LCD_Character
サンプルファイル:

#define F_CPU 8000000UL

#include <avr/io.h>
#include <util/delay.h>
#include "LCDClass.h"

int main(){
    LCDClass lcd("PB0","PD7","PD6","PD5","PB7","PB6"); // (RS,EN,D4,D5,D6,D7)の順番で指定する。
    lcd.init();
    
    lcd.gotoRowCol(1, 1) ; lcd.print("123abcXYZ");
    lcd.gotoRowCol(1, 11); lcd.print("ハロー,");
    lcd.gotoRowCol(2, 2) ; lcd.print("world");

    while(1){
        for(int i=50; i>-50; i--){
            lcd.gotoRowCol(2, 10); lcd.print(i);
            _delay_ms(100);
        }
    }

    return 0;
}

ライブラリーのヘッダーファイル:

#ifndef LCDClass_H_
#define LCDClass_H_

#define F_CPU 8000000UL

#include <avr/io.h>
#include <util/delay.h>
#include <stdio.h>

const uint8_t LEFT_MOST[] = {0x80,
                             0xC0,};

class LCDClass{
    private:
    volatile uint8_t  *DDR_RS,  *DDR_EN,  *DDR_D4,  *DDR_D5,  *DDR_D6,  *DDR_D7; // DDRnレジスタのアドレスを格納する変数。
    volatile uint8_t *PORT_RS, *PORT_EN, *PORT_D4, *PORT_D5, *PORT_D6, *PORT_D7; // PORTnのアドレスを格納する変数。
    uint8_t RS, EN, D4, D5, D6, D7; // ポート番号を格納する変数。

    void triggerWriting();            // EN信号をHi→Loと変化させる函数。
    void writeByte2LCD(uint8_t byte); // 1バイトを上位ニブル、下位ニブルの順にLCDへ書き込む函数。

    public:
    void init();
    void writeCommand(uint8_t command);        // コマンドを書き込む函数。
    void writeChar(char ch);                   // 1文字を表示する函数。
    void print(const char *str);               // 文字列を表示する函数。
    void print(int16_t val);                   // 数字を表示する函数。
    void gotoRowCol(uint8_t row, uint8_t col); // カーソル位置を指定する函数。
    void clearDisplay();


    LCDClass(const char*rs, const char*en, const char*d4, const char*d5, const char*d6, const char*d7);
};

#endif

ライブラリーの実装ファイル:

#include "LCDClass.h"

void LCDClass::init(){
    *DDR_RS |= (1 << RS); // LCDに接続するマイコンの端子のIOをOUTにする。
    *DDR_EN |= (1 << EN);
    *DDR_D4 |= (1 << D4);
    *DDR_D5 |= (1 << D5);
    *DDR_D6 |= (1 << D6);
    *DDR_D7 |= (1 << D7);

    *PORT_EN &= ~(1 << EN); // 念のため最初はdisableにしておく。しなくてもよい。

    writeCommand(0x33); _delay_ms(2);
    writeCommand(0x32); _delay_ms(2);
    writeCommand(0x28); _delay_ms(2);
    writeCommand(0x0F); _delay_ms(2);
    writeCommand(0x01); _delay_ms(2);
    writeCommand(0x06); _delay_ms(2);
}
void LCDClass::triggerWriting(){
    *PORT_EN |=  (1 << EN); _delay_us(1);   // LCDのEN端子をHi→
    *PORT_EN &= ~(1 << EN); _delay_us(100); // Loと切り換えて、LoになったタイミングでLCDへ書き込む。
}
void LCDClass::writeByte2LCD(uint8_t byte){
    uint8_t nibbles[2] = {(uint8_t)(byte >> 4), (uint8_t)(byte & 0x0F)};
        
    for(int i=0; i<2; i++){
        *PORT_D4 &= ~(1 << D4);                    // 一旦4ビット全部を1ビットずつクリアしてから、
        *PORT_D5 &= ~(1 << D5);
        *PORT_D6 &= ~(1 << D6);
        *PORT_D7 &= ~(1 << D7);

        *PORT_D4 |= ( nibbles[i]       & 1) << D4; // 1ビットずつセットし直して、
        *PORT_D5 |= ((nibbles[i] >> 1) & 1) << D5;
        *PORT_D6 |= ((nibbles[i] >> 2) & 1) << D6;
        *PORT_D7 |= ((nibbles[i] >> 3) & 1) << D7;
        triggerWriting();                          // 書き込む。
    }                                              // をニブル2個分繰り返す。
}
void LCDClass::writeCommand(uint8_t command){
    *PORT_RS &= ~(1 << RS);
    writeByte2LCD(command);
}
void LCDClass::writeChar(char ch){
    *PORT_RS |= (1 << RS);
    writeByte2LCD((uint8_t)ch);
}
void LCDClass::print(const char *str){
    int i=0;
    while(str[i]){           // null文字が見つかるまで、
        writeChar(str[i++]); // 1文字ずつ順番に表示する。
    }
}
void LCDClass::print(int16_t val){
    char str[6];
    sprintf(str, "%05d", val); // 数値を文字列に変換して、
    print(str);                // 1数字ずつ順番に表示する。
}
void LCDClass::gotoRowCol(uint8_t row, uint8_t col){
    writeCommand(LEFT_MOST[row-1] + (col-1));
    _delay_us(100);
}
void LCDClass::clearDisplay(){
    writeCommand(1);
    _delay_ms(2);
}


LCDClass::LCDClass(const char*rs, const char*en, const char*d4, const char*d5, const char*d6, const char*d7){
    volatile uint8_t  **DDRs[6] = { &DDR_RS,  &DDR_EN,  &DDR_D4,  &DDR_D5,  &DDR_D6,  &DDR_D7}; // DDRnレジスタのアドレスを格納する変数のアドレスを排列化しておく。
    volatile uint8_t **PORTs[6] = {&PORT_RS, &PORT_EN, &PORT_D4, &PORT_D5, &PORT_D6, &PORT_D7}; // PORTnレジスタのアドレスを格納する変数のアドレスを排列化しておく。

    // 指定した端子のDDRnとPORTnとを取得しておく。
    const char*sute[] = {rs, en, d4, d5, d6, d7};
    for(int i=0; i<6; i++){
        switch(sute[i][1]){
            case 'B': *DDRs[i] = &DDRB; *PORTs[i] = &PORTB; break;
            case 'C': *DDRs[i] = &DDRC; *PORTs[i] = &PORTC; break;
            case 'D': *DDRs[i] = &DDRD; *PORTs[i] = &PORTD; break;
            default : break;
        }
    }
    
    // 指定した端子のポート番号を取得しておく。
    RS = (uint8_t)(rs[2]-'0');
    EN = (uint8_t)(en[2]-'0');
    D4 = (uint8_t)(d4[2]-'0');
    D5 = (uint8_t)(d5[2]-'0');
    D6 = (uint8_t)(d6[2]-'0');
    D7 = (uint8_t)(d7[2]-'0');
}