STM32でI2CをS/Wで制御する「S/W I2C」を紹介しました。今回はS/W I2Cを使って16×2行のSC1602互換 LEDディスプレイを制御する手順を紹介します。Arduino用ではありません。
SC1062は、8bitまたは4bitのデータ線と3本の制御線が必要です。最低でも7本のGPOを消費する事になります。GPIOが少ないマイコンでは結構厳しいですよね。そこで、I2C経由でGPIOを制御できるI2Cパラレル変換ICと組み合わせてSC1602をI2Cで制御できるようにしたものが販売されてます。中国のAliExpressだと200円台で買える安さです。
今回は、このSC1602+I2Cを制御する例を紹介します。
私が購入したSC1602互換ディスプレイのI2Cパラレル変換ICは、NXPのPCF8574Tが使われていました。スレーブアドレスが異なるPCF8574Aもあるようです。また、TIからも互換ICが販売されているようです。今回このICを制御するのに仕様書を確認していましたが、TIの仕様書はちょっと誤記があるように思います。NXPの仕様書を元にプログラムしました。 (TIとNXPとで仕様が異なるのかもしれませんが)
PCF8574の基板(下の写真)をテスターで導通確認したところ、各ポートの対応は以下の通りでした。
SC1602 | PCF8574T |
D7 | P7 |
D6 | P6 |
D5 | P5 |
D4 | P4 |
LED | P3 |
E | P2 |
RW | P1 |
PS | P0 |
SC1602のコントロールIC HD44780(互換)の制御線は、4bitモードの場合7本必要になります。SC1602は4bitモードで動作させないといけませんね。残りの1本はバックライトLED ON/OFF用です。まぁうまいこと8本に収まりましたね。
LEDは、HD44780ではなくトランジスタに直接つながっています。1でバックライトON, 0でバックライトOFFです。
PCF8574は、1バイトのレジスタを介してデータのR/Wを行います。制御は簡単です。
PCF8574のレジスタbit 0からbit 7が、P0からP8に対応しています。レジスタに書き込むとビットに対応したポートがON/OFFします。バイトデータ送信後のACKのタイミングで各ポートが変化します。また、PCF8574にバイトデータを連続して書き込むとACKタイミング毎にポートが変化する事になります。
つまり、短いWaitであれば、バイトデータを複数回送信する事によりポートをH/Lさせるタイミングを調整することができます。この例(前回のSW_I2C のshortWait設定,STM32F103 clock 72MHzだと) 1byte(スタート、ストップは含まず)で大体43〜45μsでした。(ちょっとクロックが速すぎるみたいですね。waitを調整した方が良いですね。とりあえず、実力的には動作しているようですが…)
ReturnHomeのコマンド以外は1byte送信する時間でwaitが間に合いそうですね。
先のSW_I2Cクラスを継承してSC1602_I2Cクラスを作る事にします。
使っていないもの、実装していないものもありますが、そこはご愛嬌。
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 |
/* * sc1602_i2c.h * * Created on: 2019/10/14 * Author: http://www.e-momonga.com/honkytonk * Copyright 2019 honkytonk. All right reserved. */ #ifndef INC_SC1602_I2C_H_ #define INC_SC1602_I2C_H_ #include <stdint.h> #include "sw_i2c.h" #include "sc1602_fonts.h" typedef struct { uint8_t line; uint8_t column; uint8_t command; uint8_t message[16*2+2]; }sc1602_message_t; #define SC1602_COMMAND_NONE 0 #define SC1602_COMMAND_CLEAR_DISPLAY 1 #define SC1602_COMMAND_CURSOR_OFF 2 #define SC1602_COMMAND_CURSOR_ON 4 #define SC1602_COMMAND_CURSOR_BLINK 8 #define SC1602_CLEAR_DISPLAY 0x01 #define SC1602_RETURN_HOME 0x02 #define SC1602_FUNCTION_SET 0x20 #define SC1602_DISPLAY_ON_OFF 0x08 #define SC1602_ACP_DISPLAY_SHIFT 0x01 #define SC1602_DISPLAY_SHIFT (0x01 << 3) #define SC1602_CURSOR_ON (0x01 << 1) #define SC1602_DISPLAY_ON 0x04 #define SC1602_BLINK_CURSOR 0x01 #define SC1602_8_BITS_WIDTH (0x01 << 4) #define SC1602_4_BITS_WIDTH (0x00 << 4) #define SC1602_DISPLAY_2_LINE (0x01 << 3) #define SC1602_DISPLAY_1_LINE (0x00 << 3) #define SC1602_5x10_DOTS_FONT (0x01 << 2) #define SC1602_5x8_DOTS_FONT (0x00 << 2) #define SC1602_BUSY_FLAG (0x01 << 7) #define SC1602_INCREMENT (0x01 << 1) #define SC1602_NONE 0x00 #define SC1602_SET_DDR_ADDRESS 0x80 #define SC1602_D7 0x80 #define SC1602_D6 0x40 #define SC1602_D5 0x20 #define SC1602_D4 0x10 #define SC1602_LED 0x08 #define SC1602_E 0x04 #define SC1602_RW 0x02 #define SC1602_R 0x02 #define SC1602_W 0x00 #define SC1602_RS 0x01 #define SC1602_MAX_COLUMN 16 #define SC1602_MAX_LINES 2 #ifdef __cplusplus class SC1602_I2C : SW_I2C { private: bool ledSw; public: SC1602_I2C(uint8_t slave, gpio sda, gpio scl): SW_I2C(slave, sda, scl){ this->ledSw = true;} void clearDisplay(void); void returnHome(void); void sendInstruction(uint8_t command); void sendData(uint8_t command); void writeDataRam(const uint8_t *c); void setCGRamAddress(uint8_t address); void setDDRamAddress(uint8_t address); void setFunction(void); void setDisplayOnOff(bool area, bool cursor, bool blink); void displayString(uint8_t *str); void putChar(const uint8_t c){this->writeDataRam(&c);}; void setCursorPosition(uint8_t line, uint8_t column); bool isBusy(void); void initialize(void); void ledOnOff(bool ledOnOff){this->ledSw = ledOnOff ? true: false;} bool isLedOn(void){return this->ledSw;} uint8_t getAddressCounterAndBF(void); uint8_t getCurrentAddress(void); void setCGFont(uint8_t addres, const uint8_t *font); void setCustomFont(void) { for(uint8_t i = 0; i < 8; i++){ this->setCGFont(i, &font[i][0]); } } }; #endif #endif /* INC_SC1602_I2C_H_ */ |
メンバー関数は以下の通りです。
- HD44780には、スクロールの機能も有るようですが、使わないので実装していません。
- カスタムフォント(CG)のロードは、setCGFont(), setCustomFont()で行います。フォントデータを別ファイルにして、initialize()で初期化後にロードします。
- setCursorPosition()で入力位置を設定した後、displayString()で文字を表示します。文字列はnull終端です。もし、カスタムフォントの0x00を表示する場合は、エイリアスの0x08を指定してください。
- 一文字表示はputChar()でも可能です。
- コマンド等、HD47780の処理に時間がかかる場合は、isBusy()がtrueの間waitするようにしてください。
例では、initialize()の最後でclearDisplay()の後にbusyフラグをチェックしています。1ループ分ぐらいbusyになっているようです。(スペック1.52ms HD47780の仕様なので、互換ICでは若干異なるかもしれませんね)
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 |
/* * sc1602_i2c.cpp * * Created on: 2019/10/23 * Author: http://www.e-momonga.com/honkytonk * Copyright 2019 honkytonk. All right reserved. */ #include "sc1602_i2c.h" #include "cmsis_os.h" #ifdef __cplusplus extern "C" { #endif int tiny_printf( const char *format, ... ); #ifdef __cplusplus }; #endif void SC1602_I2C::initialize(void) { uint8_t data[3]; uint8_t led = (this->isLedOn()) ? SC1602_LED: SC1602_NONE; data[0] = led | 0x00; //-- send function set 3 times -- 8 bit width// data[1] = led | SC1602_E | (0x03 << 4); data[2] = data[1] & ~SC1602_E; this->writeBytes(this->getSlaveAddress(), data, 3); osDelay(5); // wait more than 4.1ms this->writeBytes(this->getSlaveAddress(), data, 3); osDelay(1); // wait more than 100us this->writeBytes(this->getSlaveAddress(), data, 3); osDelay(1); //-- send function set -- 4 bit width// data[1] = led | SC1602_E | (0x02 << 4); // data[2] = data[1] & ~SC1602_E; this->writeBytes(this->getSlaveAddress(), data, 3); this->setFunction(); // 4bit width, 2 lines, 5x8 dots font this->setDisplayOnOff(true, true, false); this->clearDisplay(); while(this->isBusy()){ osDelay(1); } } void SC1602_I2C::clearDisplay(void) { uint8_t data; data = SC1602_CLEAR_DISPLAY; this->sendInstruction(data); } void SC1602_I2C::sendInstruction(uint8_t command) { uint8_t led = (this->isLedOn()) ? SC1602_LED: SC1602_NONE; uint8_t data[8]; data[0] = led | (command & 0xf0); //RS = 0, R/W = W, E = 0, D4-D7 <- data(D4-D7) data[1] = data[0] |SC1602_E; //RS = 0, R/W = W, E = 1, D4-D7 <- data(D4-D7) data[2] = data[1] & ~SC1602_E; //RS = 0, R/W = W, E = 0, D4-D7 <- data(D4-D7) data[3] = data[2]; //RS = 0, R/W = W, E = 0, D4-D7 <- data(D4-D7) data[4] = led | ((command & 0x0f)<<4); //RS = 0, R/W = W, E = 0, D4-D7 <- data(D0-D3) data[5] = data[4] | SC1602_E; //RS = 0, R/W = W, E = 1, D4-D7 <- data(D0-D3) data[6] = data[5] & ~SC1602_E; //RS = 0, R/W = W, E = 0, D4-D7 <- data(D0-D3) data[7] = data[6]; //RS = 0, R/W = W, E = 0, D4-D7 <- data(D0-D3) this->writeBytes(this->getSlaveAddress(), data, 8); } void SC1602_I2C::sendData(uint8_t command) { uint8_t led = (this->isLedOn()) ? SC1602_LED: SC1602_NONE; uint8_t data[8]; data[0] = led | SC1602_RS | (command & 0xf0); // RS = 1, R/W = W, E = 0, D4-D7 <- command(D4-D7) data[1] = data[0]| SC1602_E; // RS = 1, R/W = W, E = 1, D4-D7 <- command(D4-D7) data[2] = data[1] & ~SC1602_E; // RS = 1, R/W = W, E = 0, D4-D7 <- command(D4-D7) data[3] = data[2]; // RS = 1, R/W = W, E = 0, D4-D7 <- command(D4-D7) data[4] = led | SC1602_RS | ((command & 0x0f)<<4); // RS = 1, R/W = W, E = 0, D4-D7 <- command(D0-D3) data[5] = data[4]| SC1602_E; // RS = 1, R/W = W, E = 1, D4-D7 <- command(D0-D3) data[6] = data[5] & ~SC1602_E; // RS = 1, R/W = W, E = 0, D4-D7 <- command(D0-D3) data[7] = data[6]; // RS = 1, R/W = W, E = 0, D4-D7 <- command(D0-D3) this->writeBytes(this->getSlaveAddress(), data, 8); } void SC1602_I2C::setCGRamAddress(uint8_t address) { uint8_t data; address &= 0x3f; data = (address | 0x40); this->sendInstruction(data); } #define SC1602_FONT_LINE_MAX 8 void SC1602_I2C::setCGFont(uint8_t address, const uint8_t *font) { if(address > 8) address = 8; this->setCGRamAddress(address << 3); for(uint8_t i = 0; i < SC1602_FONT_LINE_MAX; i++){ this->writeDataRam(font + i); } } void SC1602_I2C::setDDRamAddress(uint8_t address) { uint8_t data; data = 0; data = SC1602_SET_DDR_ADDRESS | (address & 0x7f); this->sendInstruction(data); } void SC1602_I2C::setFunction() { uint8_t data; data = 0x20 | SC1602_4_BITS_WIDTH | SC1602_DISPLAY_2_LINE | SC1602_5x8_DOTS_FONT; this->sendInstruction(data); } void SC1602_I2C::setDisplayOnOff(bool on, bool cursor, bool blink) { uint8_t data; data = SC1602_DISPLAY_ON_OFF; on ? (data |= SC1602_DISPLAY_ON) : (data &= ~SC1602_DISPLAY_ON); // entire display on/off cursor ? (data |= SC1602_CURSOR_ON) : (data &= ~SC1602_NONE); // cursor on/off blink ? (data |= SC1602_BLINK_CURSOR) : (data &= ~SC1602_NONE); // blink cursor on/off this->sendInstruction(data); } void SC1602_I2C::returnHome() { this->sendInstruction(SC1602_RETURN_HOME); } void SC1602_I2C::displayString(uint8_t *str) { uint8_t i; uint8_t c; bool loop; uint8_t adrs; i = 0; loop = true; while(loop){ c = str[i++]; switch(c){ case 0x00: loop = false; break; case '\n': adrs = this->getCurrentAddress(); if(adrs < 0x10){ this->setCursorPosition(1, 0); } break; default: this->putChar(c); } // i++; } } void SC1602_I2C::writeDataRam(const uint8_t *c) { uint8_t data[3]; uint8_t led = (this->isLedOn()) ? SC1602_LED: SC1602_NONE; data[0] = led | SC1602_RS | SC1602_W | (*c & 0xf0); data[1] = data[0] | SC1602_E; data[2] = data[1] & ~SC1602_E; this->writeBytes(this->getSlaveAddress(), data, 3); this->writeBytes(this->getSlaveAddress(), &data[2], 1); data[0] = led | SC1602_RS | SC1602_W | ((*c & 0x0f)<<4); data[1] = data[0] | SC1602_E; data[2] = data[1] & ~SC1602_E; this->writeBytes(this->getSlaveAddress(), data, 3); this->writeBytes(this->getSlaveAddress(), &data[2], 1); } void SC1602_I2C::setCursorPosition(uint8_t line, uint8_t column) { uint8_t data; if(line > 2) line = 1; if(column > 16) column = 15; data = line ? 0x40 : 0x00; data += column; this->setDDRamAddress(data); } bool SC1602_I2C::isBusy( ) { uint8_t ret; ret = this->getAddressCounterAndBF(); // return (ret & 0x80) ? tiny_printf("I'm busy now!\n"), true: false; return (ret & 0x80) ? true: false; } uint8_t SC1602_I2C::getCurrentAddress() { return (this->getAddressCounterAndBF() & 0x7f); } uint8_t SC1602_I2C::getAddressCounterAndBF( ) { uint8_t data[2]; uint8_t rdata; uint8_t led = (this->isLedOn()) ? SC1602_LED: SC1602_NONE; //-- High Nibble --- data[0] = led | 0xf0 | SC1602_R ; // RS = 0, R/W = R, E = 1, data = 0xf0 this->writeBytes(this->getSlaveAddress(), data, 1); data[1] = data[0] | SC1602_E; // RS = 0, R/W = R, E = 1, data = 0xf0 this->writeBytes(this->getSlaveAddress(), &data[1], 1); this->writeBytes(this->getSlaveAddress(), &data[1], 1); // send again to wait tDDR 160ns this->readBytes(this->getSlaveAddress(), &rdata, 1); // Read Busy Flag and AC address data[0] = (led | 0xf0 | SC1602_R) & ~SC1602_E ; // RS = 0, R/W = R, E = 0, data = 0xf0 this->writeBytes(this->getSlaveAddress(), data, 1); //--- Low Nibble --// data[0] = led | 0xf0 | SC1602_R | SC1602_E; // RS = 0, R/W = R, E = 1, data = 0xf0 this->writeBytes(this->getSlaveAddress(), data, 1); this->writeBytes(this->getSlaveAddress(), data, 1); // send again to wait tDDR 160ns this->readBytes(this->getSlaveAddress(), data, 1); // Read Busy Flag and AC address rdata = (rdata & 0xf0) | ((data[0] & 0xf0) >> 4); data[0] = (led | 0xf0 | SC1602_R) & ~SC1602_E ; // RS = 0, R/W = R, E = 0, data = 0xf0 this->writeBytes(this->getSlaveAddress(), data, 1); this->writeBytes(this->getSlaveAddress(), data, 1); return rdata; } |
custom fontの例です。「℃」を作ったつもりです。8バイトで1文字です。1バイトで1ラインです。8バイト目はカーソル用ですので、基本的にはブランク(all 0)です。
initialize()後に、setCustomFont()で一気にCGRamにロードします。ちゃんと8文字分作ってくださいね。
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 |
/* * sc1602_fonts.h * * Created on: 2019/10/23 * Author: http://www.e-momonga.com/honkytonk * Copyright 2019 honkytonk. All right reserved. */ #ifndef INC_SC1602_FONTS_H_ #define INC_SC1602_FONTS_H_ const uint8_t font[][8] = { { // 0 0b00011000, 0b00011000, 0b00000111, 0b00001000, 0b00001000, 0b00001000, 0b00000111, 0b00000000, }, --- 省略 --- }; #endif /* INC_SC1602_FONTS_H_ */ |
SC1602_I2Cクラスの使い方です。FreeRTOS(CMSIS-RTOS)のTaskにしています。
初期化完了後は、別のタスクからMailで送られた文字を受け取って表示しています。
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
/* * task_sc1602.cpp * * Created on: Jul 11, 2019 * Author: http://www.e-momonga.com/honkytonk * Copyright 2019 honkytonk. All right reserved. */ #include "main.h" #include "task_sc1602.h" #include "sc1602_i2c.h" #include "cmsis_os.h" osMailQId SC1602MailHandle; osMailQDef(SC1602Mail, 8, sc1602_message_t); void TaskSc1602(void const * argument) { class SC1602_I2C sc1602((0x27 << 1), {SDA_GPIO_Port, SDA_Pin}, {SCL_GPIO_Port, SCL_Pin}); SC1602MailHandle = osMailCreate(osMailQ(SC1602Mail), NULL); osDelay(50); sc1602.initialize(); sc1602.setCustomFont(); sc1602.setCursorPosition(0, 1); uint8_t str[] = "SC1602+BME280\n by honkytonk"; sc1602.displayString(str); while(1){ osEvent event = osMailGet(SC1602MailHandle, osWaitForever); if(event.status == osEventMail){ sc1602_message_t *message = (sc1602_message_t*)event.value.p; if(message->command == SC1602_COMMAND_CLEAR_DISPLAY){ osDelay(1000); sc1602.clearDisplay(); osDelay(1000); } else if(message->command == SC1602_COMMAND_CURSOR_OFF){ sc1602.setDisplayOnOff(true, false, false); osDelay(100); } else if(message->command == SC1602_COMMAND_CURSOR_ON){ sc1602.setDisplayOnOff(true, true, false); osDelay(100); } else if(message->command == SC1602_COMMAND_CURSOR_BLINK){ sc1602.setDisplayOnOff(true, true, true); osDelay(100); } sc1602.setCursorPosition(message->line, message->column); sc1602.displayString(message->message); osMailFree(SC1602MailHandle, message); } osDelay(10); } } |