shonen.hateblo.jp

やったこと,しらべたことを書く.

Windowsアプリケーション(C#)からarduino の 1.5inch RGB OLED Module に画像を送る

f:id:m_buyoh:20181218141729j:plain

前記事の結果を使います

shonen.hateblo.jp

やること

  • アプリ側
    • 画像を読み込む.
    • 画像から128x128の領域を選択する
    • 128x128の領域の画像をarduinoにシリアルを通して送る
  • arduino
    • OLEDのセットアップ
    • 受け取った画像を表示する

code

csharp側

Form1.csを載せます.デザイナーで作成したコンポーネント等の名前はデフォルトのままです.

Form1 コンストラクタでは,通信ポートを SerialPort.GetPortNames() から適当に選んでいます.

arduinoと通信するメソッドは CropAndSendSerial です.イベント処理中にやっているので,おそらく連打に弱いです.

CropAndSendSerial メソッドでは,選択した128x128の領域のビットマップを作成,ビットマップから通信バッファ buff を作成して,シリアルに送ります. 特に何も加工せず,128x128px24bitカラーの画像をarduinoに送りつけます.

using System;
using System.Drawing;
using System.IO.Ports;
using System.Windows.Forms;

namespace ImgArduino
{
    public partial class Form1 : Form
    {

        Image image = null;
        Bitmap cropped = null;
        Rectangle selection = new Rectangle(0, 0, 128, 128);

        public Form1()
        {
            InitializeComponent();
            var portNames = SerialPort.GetPortNames();
            if (portNames.Length == 0) throw new Exception("ポートが無い");
            serialPort1.PortName = portNames[0];
        }

        //

        private void CropAndSendSerial()
        {
            if (image == null)
                return;

            if (cropped == null)
                cropped = new Bitmap(128, 128);

            using (var g = Graphics.FromImage(cropped))
            {
                g.DrawImageUnscaled(image, -selection.X, -selection.Y);
            }

            byte[] buff = new byte[128 * 128 * 3];
            for (int i = 0; i < 128; ++i)
            {
                for (int j = 0; j < 128; ++j)
                {
                    buff[(i * 128 + j) * 3 + 0] = (byte)(cropped.GetPixel(j, i).R);
                    buff[(i * 128 + j) * 3 + 1] = (byte)(cropped.GetPixel(j, i).G);
                    buff[(i * 128 + j) * 3 + 2] = (byte)(cropped.GetPixel(j, i).B);
                }
            }
            serialPort1.Open();
            serialPort1.Write(buff, 0, 128 * 128 * 3);
            serialPort1.Close();
        }

        //

        private void Form1_Paint(object sender, PaintEventArgs e)
        {
            if (image != null)
                e.Graphics.DrawImageUnscaled(image, new Point(0, 0));
            e.Graphics.DrawRectangle(Pens.Red, selection);
        }

        private void 画像選択OToolStripMenuItem_Click(object sender, EventArgs e)
        {
            var res = openFileDialog1.ShowDialog();
            if (res != DialogResult.OK) return;
            var fileName = openFileDialog1.FileName;

            image = Image.FromFile(fileName);
            Invalidate();
        }

        //

        Point pointLast = Point.Empty;

        private void Form1_MouseDown(object sender, MouseEventArgs e)
        {
            pointLast = new Point(e.X, e.Y);
        }

        private void Form1_MouseMove(object sender, MouseEventArgs e)
        {
            if (pointLast == Point.Empty) return;
            selection.X += e.X - pointLast.X;
            selection.Y += e.Y - pointLast.Y;
            if (selection.X < 0) selection.X = 0;
            if (selection.Y < 0) selection.Y = 0;
            pointLast = new Point(e.X, e.Y);
            Invalidate();
        }

        private void Form1_MouseUp(object sender, MouseEventArgs e)
        {
            pointLast = Point.Empty;
            CropAndSendSerial();
        }

        private void Form1_MouseLeave(object sender, EventArgs e)
        {
            pointLast = Point.Empty;
        }

    }
}

arduino

arduinoのOLEDドライバ部分(setupとloop以外)のコードを載せます.前記事と同じなので説明は省略.

#include "SPI.h"

#define SSD1351_WIDTH   128
#define SSD1351_HEIGHT  128

#define SSD1351_CMD_SETCOLUMN       0x15
#define SSD1351_CMD_SETROW          0x75
#define SSD1351_CMD_WRITERAM        0x5C
#define SSD1351_CMD_READRAM         0x5D
#define SSD1351_CMD_SETREMAP        0xA0
#define SSD1351_CMD_STARTLINE       0xA1
#define SSD1351_CMD_DISPLAYOFFSET   0xA2
#define SSD1351_CMD_DISPLAYALLOFF   0xA4
#define SSD1351_CMD_DISPLAYALLON    0xA5
#define SSD1351_CMD_NORMALDISPLAY   0xA6
#define SSD1351_CMD_INVERTDISPLAY   0xA7
#define SSD1351_CMD_FUNCTIONSELECT  0xAB
#define SSD1351_CMD_DISPLAYOFF      0xAE
#define SSD1351_CMD_DISPLAYON       0xAF
#define SSD1351_CMD_PRECHARGE       0xB1
#define SSD1351_CMD_DISPLAYENHANCE  0xB2
#define SSD1351_CMD_CLOCKDIV        0xB3
#define SSD1351_CMD_SETVSL          0xB4
#define SSD1351_CMD_SETGPIO         0xB5
#define SSD1351_CMD_PRECHARGE2      0xB6
#define SSD1351_CMD_SETGRAY         0xB8
#define SSD1351_CMD_USELUT          0xB9
#define SSD1351_CMD_PRECHARGELEVEL  0xBB
#define SSD1351_CMD_VCOMH           0xBE
#define SSD1351_CMD_CONTRASTABC     0xC1
#define SSD1351_CMD_CONTRASTMASTER  0xC7
#define SSD1351_CMD_MUXRATIO        0xCA
#define SSD1351_CMD_COMMANDLOCK     0xFD
#define SSD1351_CMD_HORIZSCROLL     0x96
#define SSD1351_CMD_STOPSCROLL      0x9E
#define SSD1351_CMD_STARTSCROLL     0x9F

namespace OLED{

  const uint8_t oled_cs = 10;
  const uint8_t oled_rst = 8;
  const uint8_t oled_dc = 7;

  void update_cs(uint8_t x)  {
    digitalWrite(oled_cs, x);
  }
  
  void update_rst(uint8_t x) {
    digitalWrite(oled_rst, x);
  }
  
  void update_dc(uint8_t x) {
    digitalWrite(oled_dc, x);
  }

  void write_command(uint8_t cmd) {
    update_dc(LOW);
    SPI.transfer(cmd);
    update_dc(HIGH);
  }
  
  void write_data(uint8_t dat) {
    update_dc(HIGH);
    SPI.transfer(dat);
    update_dc(LOW);
  }

  void ram_address(){ // ?
    write_command(SSD1351_CMD_SETCOLUMN);
    write_data(0x00);
    write_data(0x7f);
  
    write_command(SSD1351_CMD_SETROW);
    write_data(0x00);
    write_data(0x7f);
  }
  
  void init(void) {
    pinMode(oled_cs, OUTPUT);
    pinMode(oled_rst, OUTPUT);
    pinMode(oled_dc, OUTPUT);
  
    SPI.setDataMode(SPI_MODE0);
    SPI.setBitOrder(MSBFIRST);
    SPI.setClockDivider(SPI_CLOCK_DIV2);
    SPI.begin();
    
    update_cs(LOW);
    update_rst(LOW);
    delay(500);
    update_rst(HIGH);
    delay(500);
      
    write_command(SSD1351_CMD_COMMANDLOCK);
    write_data(0x12);
    write_command(SSD1351_CMD_COMMANDLOCK);
    write_data(0xB1);
  
    write_command(SSD1351_CMD_DISPLAYOFF);
    write_command(SSD1351_CMD_DISPLAYALLOFF);  // Normal Display mode
  
    ram_address();
  
    write_command(SSD1351_CMD_CLOCKDIV);
    write_data(0xF1);
  
    write_command(SSD1351_CMD_MUXRATIO);  
    write_data(0x7F);
  
    write_command(SSD1351_CMD_SETREMAP);  //set re-map & data format
    //write_data(0x74); //Horizontal address increment
    write_data(0xb4);
    
    write_command(SSD1351_CMD_STARTLINE);  //set display start line
    write_data(0x00); //start 00 line
  
    write_command(SSD1351_CMD_DISPLAYOFFSET);  //set display offset
    write_data(0x00);
  
    write_command(SSD1351_CMD_FUNCTIONSELECT);  
    write_data(0x01);  // 元のOLED_Driver.cppではwrite_commandだがwrite_dataの間違い?
  
    write_command(SSD1351_CMD_SETVSL);  
    write_data(0xA0);   
    write_data(0xB5);  
    write_data(0x55);    
  
    write_command(SSD1351_CMD_CONTRASTABC);  
    write_data(0xC8); 
    write_data(0x80);
    write_data(0xC0);
  
    write_command(SSD1351_CMD_CONTRASTMASTER);  
    write_data(0x0F);
  
    write_command(SSD1351_CMD_PRECHARGE);  
    write_data(0x32);
  
    write_command(SSD1351_CMD_DISPLAYENHANCE);  
    write_data(0xA4);
    write_data(0x00);
    write_data(0x00);
  
    write_command(SSD1351_CMD_PRECHARGELEVEL);  
    write_data(0x17);
  
    write_command(SSD1351_CMD_PRECHARGE2);  
    write_data(0x01);
  
    write_command(SSD1351_CMD_VCOMH);  
    write_data(0x05);
  
    write_command(SSD1351_CMD_NORMALDISPLAY);
  
    // Clear_Screen();
    write_command(SSD1351_CMD_DISPLAYON);   //display on
  }

  
  void set_coordinate(uint16_t x, uint16_t y)  {
  
    if ((x >= SSD1351_WIDTH) || (y >= SSD1351_HEIGHT))
      return;
    //Set x and y coordinate
    write_command(SSD1351_CMD_SETCOLUMN);
    write_data(x);
    write_data(SSD1351_WIDTH-1);
    
    write_command(SSD1351_CMD_SETROW);
    write_data(y);
    write_data(SSD1351_HEIGHT-1);
    
    write_command(SSD1351_CMD_WRITERAM);
  }

}

setuploop 部分です.

OLED::ram_address();で描画範囲の指定(全画面指定), OLED::write_command(SSD1351_CMD_WRITERAM);でOLEDを書き込みモードにします.

シリアルから画素データが届きますが,これは1色8bitのデータなので,1色6bitのデータに変換するためにシフトしています.

void setup() {
  OLED::init();
  
  Serial.begin(9600);
  OLED::ram_address();
  OLED::write_command(SSD1351_CMD_WRITERAM);
}

const uint16_t wh = 128*128*3;
uint16_t idx = 0;

void loop() {
  while (Serial.available() > 0) {
    int val = Serial.read();
    OLED::write_data(val >> 2);
    if (++idx >= wh) { // 終端画素に到達したら先頭へ戻る
      idx = 0;
      OLED::ram_address();
      OLED::write_command(SSD1351_CMD_WRITERAM);
    }
  }
}

動画

www.instagram.com