Skip to content

Instantly share code, notes, and snippets.

@satakagi
Last active November 30, 2019 03:41
Show Gist options
  • Save satakagi/608f6c2c963c8e9453864f11b6fb1f3d to your computer and use it in GitHub Desktop.
Save satakagi/608f6c2c963c8e9453864f11b6fb1f3d to your computer and use it in GitHub Desktop.
Neopixel-I2C Driver for CHIRIMEN RPi3

このgistの内容はCHIRIMENの公式リポジトリに移行しました

ATTINY85をオピクセルLED用のI2Cコントローラとして構成し、I2C経由で簡単にネオピクセルLEDを制御できるオープンソースhttps://github.com/usedbytes/neopixel_i2c を利用して、CHIRIMEN for Raspberry Pi3のWebI2CからネオピクセルLEDを制御できるようにします。 ATTINY85にファームウェアを焼き込むところと、I2C信号レベルの変換がハードルかもしれません。

部品リスト

ブレッドボード版

  • ATTINY85 (下記のファームウェアを書き込んだもの)
  • I2Cレベルコンバータ
    • 下コメント欄の配線図ではsparkfun PCA9306ブレークアウトボードを使っていますが、同等の機能を持つものなら大丈夫だと思います
    • MOSFETを使ったジェネリック品でも動きました。
  • 10Kohm
  • 0.1μF
  • Neopixel LEDモジュール
    • 必ずしも8x8マトリクスパネルである必要はありません。(8x8パネルでの動作を確認しました)リング状のものやリボン状のものなどあります。
    • 下記の定数設定では最大160個まで、定数をさらに増やし167個までつなげられるそうです
  • その他 Raspberry Pi3、ブレッドボードや基板、ワイヤー 他
  • また、ファームウェアの焼き込みにWindowsマシンとusbasp (AVRマイコン用の安価なライタ)が必要です。

専用ボード

  • こちらのgitHubリポジトリに、設計図や基板発注用データを置いてあります
  • ドライバ部を専用ボード化したものです。あらかじめ作成したものを用意して提供するなどのケースでは便利
  • 上記のサイトの情報を参考にプリント基板を発注
    • elecrow(基板注文の参考)での発注実績あり。(gerberディレクトリの内容を全てzip圧縮し注文フォームに添付します) (Note:この基板のサイズ: 37mm x 18mm )
    • 納期10日程度。20枚で送料込み2000円弱程度でした
  • パーツリストも上記サイトに掲載
    • レベルコンバータをディスクリート部品(2N7000 x 2と2.2Kohm x 2)で構成しているのが相違点(より安価)
    • 全てのパーツは秋月電子通商で購入可能
  • もちろんNeopixel LEDモジュールは別途用意してください。

neopixel_i2cファームウェアビルドの注意点

  • https://github.com/usedbytes/neopixel_i2c に従って、USBASPを使ってファームウェアを書き込みますが、85個以上のLEDが制御できない問題があり、これを解消したforkをhttps://github.com/satakagi/neopixel_i2c に置きました。同時に以下の定数変更を行っていくらか使いやすくしてあります。
  • i2c/i2c_slave_defs.h の定数を変更
    • N_LEDS 160 (モジュールのLEDの個数。初期値だと16個しか制御できない)
    • I2C_SLAVE_ADDR 0x41 (PCA9685と被りがちなので0x40から変更した。下記のドライバのデフォルトも0x41になっています)
  • WinAVR / Cygwinを用いてmake(cygwin上で動かさないとオリジナルのmakefileではうまく通らないようです)
  • usbasp(参考:品物ドライバ使い方)を使ってATTiny85に焼き込み
    • make program で本体が転送される
    • make fuses でヒューズビットの設定がされる(デフォルトと異なる値なので必ず実行)

CHIRIMEN用ドライバとサンプル

// Driver for https://github.com/usedbytes/neopixel_i2c
// Programmed by Satoru Takagi
var NEOPIXEL_I2C = function(i2cPort,slaveAddress){
this.i2cPort = i2cPort;
this.i2cSlave = null;
if ( slaveAddress ){
this.slaveAddress = slaveAddress;
} else {
this.slaveAddress = 0x41;
}
this.N_LEDS = 160;
this.GLOBALMODE = false;
this.pixRegStart = 0x04;
};
NEOPIXEL_I2C.prototype = {
sleep: function(ms){
return new Promise((resolve)=>{setTimeout(resolve,ms);});
},
init: async function(N_LEDS){
if ( N_LEDS ){
this.N_LEDS = N_LEDS;
}
var i2cSlave = await this.i2cPort.open(this.slaveAddress);
this.i2cSlave = i2cSlave;
await this.i2cSlave.write8(0x00,0x01); // reset
/**
await this.i2cSlave.write8(0x01,0x20); // global color set dim white
await this.i2cSlave.write8(0x02,0x20);
await this.i2cSlave.write8(0x03,0x20);
for ( var i = 0 ; i < this.N_LEDS ; i++ ){
await setPixel(i , 0, 0, 0);
}
**/
},
setPixel: async function(pixelNumber,red,green,blue){
if ( this.GLOBALMODE ){
await this.i2cSlave.write8(0x00,0x01);
this.GLOBALMODE = false;
}
if(this.i2cSlave == null){
throw Error("i2cSlave Address does'nt yet open!");
}
if ( pixelNumber < 0 || pixelNumber >= this.N_LEDS){
throw Error("pixelNumber should be 0 to "+(this.N_LEDS-1));
}
await this.i2cSlave.write8( pixelNumber * 3 + this.pixRegStart , green & 0xff );
await this.i2cSlave.write8( pixelNumber * 3 + 1 + this.pixRegStart , red & 0xff );
await this.i2cSlave.write8( pixelNumber * 3 + 2 + this.pixRegStart , blue& 0xff );
},
setPixels: async function(rgbArray,startPixel){
if (rgbArray.length/3 + startPixel >this.N_LEDS){
throw Error("pixelNumber overflow : rgbArray.length , startPixel , this.N_LEDS: " + rgbArray.length +","+ startPixel +","+ this.N_LEDS );
}
var startAddr = this.pixRegStart;
if (startPixel){
startAddr += startPixel*3;
}
if ( this.GLOBALMODE ){
await this.i2cSlave.write8(0x00,0x01);
this.GLOBALMODE = false;
}
var data = [];
data.push(startAddr);
for ( var i = 0 ; i <rgbArray.length ; i++){
data.push(rgbArray[i]);
if ( startAddr + i == 254 ){ // レジスタとして見えるピクセルの末尾でいったん書き出し(polifill srvのuint8バグ回避)
await this.i2cSlave.writeBytes(data);
data = [];
data.push(255); // 末尾ギリギリからさらに書き出す
}
}
await this.i2cSlave.writeBytes(data);
},
setGlobal: async function(red,green,blue){
await this.i2cSlave.write8( 1 , green & 0xff );
await this.i2cSlave.write8( 2 , red & 0xff );
await this.i2cSlave.write8( 3 , blue& 0xff );
if ( !this.GLOBALMODE ){
await this.i2cSlave.write8(0x00,0x02);
this.GLOBALMODE = true;
}
}
};
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>NEOPIXEL_I2C Example</title>
</head>
<body>
<script src="../../../polyfill/polyfill.js"></script>
<script src="../../drivers/i2c-NEOPIXEL_I2C.js"></script>
<script src="npi2c.js"></script>
<h1>NEOPIXEL_I2C Example</h1>
<!-- <img src="NOEPIXEL_I2C.png" height="400"/>-->
<img src="NOEPIXEL_I2C_BRD.png" height="400"/>
<div>
<input type="button" onclick="setGlobalColor(0x20,0x00,0x00)" value="R"></input>
<input type="button" onclick="setGlobalColor(0x00,0x20,0x00)" value="G"></input>
<input type="button" onclick="setGlobalColor(0x00,0x00,0x20)" value="B"></input>
<input type="button" onclick="setGlobalColor(0x00,0x00,0x00)" value="OFF"></input>
<br>
<input type="button" onclick="setPattern0()" value="PATTERN0"></input>
<input type="button" onclick="setPattern1()" value="PATTERN1"></input>
<input type="button" onclick="setPattern2()" value="PATTERN2"></input>
<input type="button" onclick="setPattern3()" value="PATTERN3"></input>
</div>
</body>
</html>
// NEOPIXEL_I2Cボードを使ってNEOPIXEL LEDを制御します
var npixPromise;
onload =async function(){
try{
console.log("onload");
var i2cAccess = await navigator.requestI2CAccess();
var i2cPort = i2cAccess.ports.get(1);
var npix = new NEOPIXEL_I2C(i2cPort, 0x41);
await npix.init(128);
console.log("init end");
npixPromise = npix;
} catch (error) {
console.error("error", error);
}
}
async function setGlobalColor(red,green,blue){
var npix = await npixPromise;
npix.setGlobal(red,green,blue);
}
async function setPattern0(iH){
startH = 0;
if ( iH ){
startH=iH;
}
var npix = await npixPromise;
var grbArray = [];
for ( var i = 0 ; i < npix.N_LEDS ; i++ ){
var h = startH + 360 * i / npix.N_LEDS;
var s = 1;
var v = 0.1;
var rgb = hsvToRgb(h , s , v );
grbArray.push( rgb[1]);
grbArray.push( rgb[0]);
grbArray.push( rgb[2]);
// await npix.setPixel(i , rgb[0],rgb[1],rgb[2]);
}
await npix.setPixels(grbArray);
}
async function setPattern1(){
for ( var startH = 0 ; startH < 720 ; startH += 10){
await setPattern0(startH);
await sleep(30);
}
}
var pattern =
[0x000000,0x000000,0x000000,0x002020,0x002020,0x000000,0x000000,0x000000,
0x000000,0x000000,0x002020,0x000000,0x000000,0x002020,0x000000,0x000000,
0x000000,0x002020,0x000000,0x000000,0x000000,0x000000,0x002020,0x000000,
0x002020,0x000000,0x000000,0x000000,0x000000,0x000000,0x000000,0x002020,
0x002020,0x202000,0x202000,0x202000,0x202000,0x202000,0x202000,0x002020,
0x002020,0x000000,0x000000,0x000000,0x000000,0x000000,0x000000,0x002020,
0x002020,0x000000,0x000000,0x000000,0x000000,0x000000,0x000000,0x002020,
0x002020,0x000000,0x000000,0x000000,0x000000,0x000000,0x000000,0x002020,
0x200020,0x200020,0x200020,0x200020,0x200020,0x200020,0x200020,0x000000,
0x200020,0x000000,0x000000,0x000000,0x000000,0x000000,0x000000,0x200020,
0x200020,0x000000,0x000000,0x000000,0x000000,0x000000,0x000000,0x200020,
0x200020,0x200000,0x200000,0x200000,0x200000,0x200000,0x200000,0x000000,
0x200020,0x000000,0x000000,0x000000,0x000000,0x000000,0x000000,0x200020,
0x200020,0x000000,0x000000,0x000000,0x000000,0x000000,0x000000,0x200020,
0x200020,0x000000,0x000000,0x000000,0x000000,0x000000,0x000000,0x200020,
0x200020,0x200020,0x200020,0x200020,0x200020,0x200020,0x200020,0x000000,
];
async function setPattern2(spos){
var sp = 0;
if ( spos ){
sp = spos;
}
var npix = await npixPromise;
var grbArray = [];
for ( var i = 0 ; i < pattern.length ; i++ ){
var si = (i + sp*8) % pattern.length ;
var r = pattern[si]>>16 & 0xff;
var g = pattern[si]>>8 & 0xff;
var b = pattern[si] & 0xff;
grbArray.push(g);
grbArray.push(r);
grbArray.push(b);
}
await npix.setPixels(grbArray);
}
async function setPattern3(){
for ( var sp = 0 ; sp < 33 ; sp++ ){
await setPattern2(sp % (pattern.length/8));
await sleep(20);
}
}
// from https://qiita.com/hachisukansw/items/633d1bf6baf008e82847
function hsvToRgb(H,S,V) {
//https://en.wikipedia.org/wiki/HSL_and_HSV#From_HSV
H = H %360;
var C = V * S;
var Hp = H / 60;
var X = C * (1 - Math.abs(Hp % 2 - 1));
var R, G, B;
if (0 <= Hp && Hp < 1) {[R,G,B]=[C,X,0]};
if (1 <= Hp && Hp < 2) {[R,G,B]=[X,C,0]};
if (2 <= Hp && Hp < 3) {[R,G,B]=[0,C,X]};
if (3 <= Hp && Hp < 4) {[R,G,B]=[0,X,C]};
if (4 <= Hp && Hp < 5) {[R,G,B]=[X,0,C]};
if (5 <= Hp && Hp < 6) {[R,G,B]=[C,0,X]};
var m = V - C;
[R, G, B] = [R+m, G+m, B+m];
R = Math.floor(R * 255);
G = Math.floor(G * 255);
B = Math.floor(B * 255);
return [R ,G, B];
}
@satakagi
Copy link
Author

実体配線図の例
noepixel_i2c

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment