NordicSemiのnRF24LE1/nRF24LU1P搭載ボードが400-500円ぐらいでAliexpressで売ってるのを見つける。使ったこともないがなんだか面白そうなので購入してみた。
購入した24LE1ボード用のmProプログラマーという製品がテストボードとともに日本のアマゾンで売ってるのをたまたま見つけ迷わず購入。ところが、これがそもそもの迷走の始まりであった...
製品説明に記載されていたmProプログラマーのソフトはかなり古いものだったので最新と思われるV1.6を使うことにしたが、なんか怪しそうな中国製のUSBドライバーのインストールが必要とわかり、どーーしようかなと暫く躊躇っていたが意を決してインストール。Windows10対応とは記載されていなかったが見なかったことにしてインストールを強行してみると思いのほかあっさり入ってしまう。
さて準備完了ということで説明書もないプログラマーを起動するといきなりエラーダイアログの洗礼、何度か試した後、どうやらこれが正しい動作らしいとわかる。(-_-; 気を取り直して次に書き込みしてみると今度は文字化けした中国語のエラー表示ばかりでどうにも先に進めない。説明書を見つけるも操作説明はなく一旦諦めてしまったが、後日、mProプログラマーの使い方を説明してるYouTubeを見つけてしまう。見てみるとやはり起動時にエラー表示が出ている。なんだか安心してしまった。(笑) でも、喋ってる言葉(英語ではない?)が意味不明である。映像だけを必死に見つめているとなんとなく光が見えてきた。右クリックメニューでチップエディタを開きチップライブラリを読み込みしないといけないらしい。右クリックじゃわかんねーよ!と思いつつもやってみると3個ほどの名前が表示された。次にメイン画面のコンボボックスでチップ名を選択すればいいのかと思ったら選択リストが空っぽじゃん?しょうがないのでさきほど表示されてた名前のうちの書き込み対象のチップ名を正確に手入力してみる。コンボボックスの意味ねーじゃん!というツッコミはさておき、その後の書き込みは見事に成功。しかし、かなり不安定である。何度か繰り返し操作すると何をしてもエラーになる。そうなったときはmProプログラマーの電源オンオフで復活するようだ。PC側ソフトも怪しいけどファームも怪しっぽい。が、使えないことはないので気にせず次に進むことに。
24LU1PのほうはUSB経由で書き込みできるとわかるが、その書き込みソフトはNordicサイトで公開されているSDKに含まれるLinux用ソースを自分でコンパイルする必要がある。手持ちのOrangePi-Zeroでコンパイルし実行してみるとなぜかエラーに。あれ?なんでやねんと思ったら起動オプションの指定を間違えてた。(-_-;) 購入したのが32Kのものとばかり思ってたがチップの表面をよく見ると16という数字が見える。16Kオプションを指定しなければいけないのに、間違えて32Kオプションを指定してしまったのが敗因であった。それ以降、そのデバイスは何度やっても書き込みエラーとなる。あーーあ、やっちまったかと一度はゴミ箱に放り込んだものの電気的な破壊行為などしてないので壊れてないはずと捨てるのを思いとどまる。
別の24LU1Pを試してみると見事に成功したが2度目以降は駄目。ここで初めて出荷時ファームが一度っきりの書き込み対応であることがわかる。mProプログラマーが24LU1Pにも対応してることを思い出し、早速、再プログラムしてみるが成功したのは最初の一度だけ。なにやら書き込んだファームがたまたまファームプロテクトするものだったらしく実行後はプログラマーで読み書きができなくなるようだ。mProプログラマーにはプロテクトに対応しているような項目があるので色々試してみたがどんどんおかしくなるばかりで最後にはついに何も出来なくなってしまう。(T_T;
最初から楽しようと思ったのが大間違いとも言えるがググッてもほとんど情報が出てこない。自分と同じ症状に悩むどっかの国の人がいることがわかった程度で解決策らしいものは見つからない。ただ、このまま駄目になったらしいものを捨てるのも悔しいのでプログラマーを自分で作ることに。必要なのはSPIインターフェースと2本のGPIOだけ。だけど専用HWなどいちいち作りたくないしと考えていたら手持ちのFTDIのC232HMというMPSSEケーブルが使えるのではと思い立つ。
このケーブルは転送速度も速くUART/I2C/SPI/GPIOに対応しているので一本あるととても便利。それはさておき、あとはPC側ソフトを作るだけでいいはずと思ったがやはり現実はそうあまくはなかった。調べてみるとFTDIが公開してるMPSSEライブラリではMPSSEケーブルのGPIOには対応できないことがわかる。そういえばPCからI2Cを使いたくてFTDIチップ搭載ボードを購入したがMPSSEライブラリがSCLのLowホールドによるウェイトに対応してないことがわかりMPSSEを直接叩くプログラムを自作したんだったと思い出す。SPIのソースを見てもバグを仕様変更で治してしまったように感じる部分があるし、はっきり言って読むのが嫌になるような汚いコードである。よくこんなの公開できるなと思う...が、バグバグしてるわけではないので仕様的に問題ない用途にはそのまま使えることも確かだけど...まぁ、MPSSEのサンプル・コードだと思ったほうが良いのかも。
I2Cに比べればSPIのほうが単純なので今回もMPSSEライブラリを使わずにとも思ったが少しでも楽したいので今回はSPI初期化とSPI-Read/Writeの基本機能のみを利用しSPIのCSN制御とRESET/PROGなどのGPIO制御などを独自処理したコンソールアプリケーションを、Visual Studio 2015 Community(無料版)で開発してみた。
一番悩んだのは、nRF24LU1Pのドキュメントにだけ記載されているが実際にはnRF24LE1にも存在するRDISIPというInfoPageプロテクト機能への対応である。プロテクトを解除するには、ERASE ALLに続きInfoPageをERASEするしか方法がないが、InfoPageの先頭16バイトにはDSYS(Device System Parameter)と呼ばれる領域があり、MCUの動作に必要な情報が格納されているため事前にDSYS領域を保存しておきERASE後に再プログラムしなければならないとある。RDISIPが設定されるとMCUの実行コード内からは読み出しできるがSPIからの読みだしは禁止される。ERASE ALL後に専用ファームをを書き込み&実行させ、DSYS領域をCODEかNVDATA領域のどこかSPIから読める場所に保存しておいてSPIでPC側に読みだせば良いのではと思ったが、さらにRDISMBというCODE/NVDATAをプロテクトする設定が有効にされたりするとお手上げ状態になってしまう。
そこで考えたのがプログラム・モードで使用中のSPIをそのまま流用してしまうという荒業。まず、MCUに専用ファームを書き込んだ後、プログラム・モードを一旦解除し専用ファームを実行させる。専用ファームはプログラム・モードと同じ仕様でSPIを初期化後、PCからのSPI読み出しを待機。SPI経由でPC側にDSYS情報を読み出した後、再度プログラム・モードに戻すという作戦である。
DSYS領域が読めるうちに保存しとけばいいだけなので、あえて、こんなことする人いないだろうなと思いつつも挑戦してみたらSPIスレーブ処理で見事にハマった。ドキュメントを見ると24LE1はダブルバッファで常にバッファを満たすためのちょっとした工夫が必要、24LU1Pはシングルバッファなので普通にコードを書けば良いはずと思ったが実際にコードを書き試験してみるとデータが抜け/化け/ダブりの3拍子で全くうまくいかない。しかし、ドキュメントを再度読み返してみたらこうすべしとコメントが書いてあるではないか...完全に見落としていたが、全ての問題は、CSNやSPIの状態を調べるためにSPIステータスレジスタを直接ポーリングしていたこと。SPIステータスレジスタは読むとクリアされるのでポーリングのために頻繁に読むとステータスを読み落とすことがあるようだ。コメントを参考に割り込みは許可せずに割り込み設定だけ行い、割り込み要求フラグをポーリングするように修正したところウソのようにうまくいくようになってしまった。24LU1Pについてはシングルバッファであるため連続読み出しをすると処理が追い付かないのでPC側でバイト毎に少し間隔を空けてあげる必要があった。24LU1Pのライブラリにスレーブ機能の実装がないのはなぜなんだろうと思っていたが性能上の問題のせいかも。連続転送だとSPIクロック1MHzでも苦しいかもしれない。
[Firmware for Read DSYS]
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 |
#include <flash.h> __sfr __at (0xA6) INTEXP; __sbit __at (0xAF) EA; __sbit __at (0xC2) SPIF; __sbit __at (0xCE) I3FR; // // 24LE1 // __sfr __at (0xBC) SPISCON0; __sfr __at (0xBE) SPISSTAT; __sfr __at (0xBF) SPISDAT; // // 24LU1P // __sfr __at (0xBC) SSCONF; __sfr __at (0xBD) SSDATA; __sfr __at (0xBE) SSSTAT; __sfr __at (0xC9) P0EXP; uint8_t dsys[0x20]; void main() { // // disable interrupts // EA = 0; // // Read DSYS // flash_bytes_read(0x0000, dsys, sizeof(dsys), true); // // Setup SPI Slave // if (SPISCON0 == 0x70) { // // for 24LE1 (Double Buffered) // I3FR = 1; // Select INT3 Rising Edge INTEXP = 0x01; // Enable Slave SPI Interrupt to INT3 SPISCON0 = 0x01; // Enable Slave SPI SPIF = 0; while (1) { // // wait CSN low // do { while (!SPIF) ; SPIF = 0; } while (!(SPISSTAT & 0x10)); // // Data Transfer // SPISDAT = dsys[0]; for (uint8_t i = 1; i < sizeof(dsys); ) { SPISDAT = dsys[i++]; // // wait transaction // while (!SPIF) ; SPIF = 0; // // CSN high ? // if (SPISSTAT & 0x20) break; } } } else { // // for 24LU1P (Single Buffered) // P0EXP = 0x02; // Map SPI Slave on P0 I3FR = 1; // Select INT3 Rising Edge INTEXP = 0x01; // Enable Slave SPI Interrupt to INT3 SSCONF = 0x01; // Enable Slave SPI SPIF = 0; while (1) { // // wait CSN low // do { while (!SPIF) ; SPIF = 0; } while (!(SSSTAT & 0x02)); // // Data Transfer // for (uint8_t i = 0; i < sizeof(dsys); ) { SSDATA = dsys[i++]; // // wait transaction // while (!SPIF) ; SPIF = 0; // // CSN high ? // if (SSSTAT & 0x04) break; } } } } |
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 |
#include "flash.h" // 24LU1P only __sfr __at (0x8E) CKCON; // Common #define PMW 0x10 __sfr __at (0x87) PCON; __sfr __at (0xFA) FCR; __sbit __at (0xAF) EA; __sbit __at (0xD5) F0; __sbit __at (0xFC) RDYN; __sbit __at (0xFD) WEN; __sbit __at (0xFB) INFEN; void flash_page_erase(uint8_t pn, bool code) { uint8_t ckcon_val; // Save interrupt enable state and disable interrupts: F0 = EA; EA = 0; // 24LU1P only // Enable flash write operation: // Backup CKCON value and force CKCON to 0x01. nRF24LU1p PAN 011 #2 ckcon_val = CKCON; CKCON = 0x01; FCR = 0xAA; FCR = 0x55; if (code) PCON |= PMW; WEN = 1; // // Write the page address to FCR to start the page erase operation. This // operation is "self timed" when executing from the flash; the CPU will // halt until the operation is finished: FCR = pn; // // When running from XDATA RAM we need to wait for the operation to finish: while (RDYN == 1) ; WEN = 0; if (code) PCON &= ~PMW; // 24LU1P only // Restore CKCON state CKCON = ckcon_val; // Restore interrupt enable state EA = F0; } void flash_bytes_write(uint16_t a, const uint8_t *p, uint16_t n, bool code) { uint8_t ckcon_val; // Save interrupt enable state and disable interrupts: F0 = EA; EA = 0; // 24LU1P only // Enable flash write operation: // Backup CKCON value and force CKCON to 0x01. nRF24LU1p PAN 011 #2 ckcon_val = CKCON; CKCON = 0x01; FCR = 0xAA; FCR = 0x55; if (code) PCON |= PMW; WEN = 1; // // Write the bytes directly to the flash. This operation is // "self timed"; the CPU will halt until the operation is // finished: while (n--) { //lint --e{613} Suppress possible use of null pointer warning: *(uint8_t __xdata *)a++ = *p++; // // When running from XDATA RAM we need to wait for the operation to // finish: while (RDYN == 1) ; } WEN = 0; if (code) PCON &= ~PMW; // 24LU1P only // Restore CKCON state CKCON = ckcon_val; // Restore interrupt enable state EA = F0; } void flash_bytes_read(uint16_t a, uint8_t *p, uint16_t n, bool info) { if (info) INFEN = 1; while (n--) *p++ = *(uint8_t __xdata *)a++; if (info) INFEN = 0; } |
1 2 3 4 5 6 7 8 9 10 11 |
#ifndef __FLASH_H__ #define __FLASH_H__ #include <stdint.h> #include <stdbool.h> void flash_page_erase(uint8_t pn, bool code); void flash_bytes_write(uint16_t a, const uint8_t *p, uint16_t n, bool code); void flash_bytes_read(uint16_t a, uint8_t *p, uint16_t n, bool info); #endif // __FLASH_H__ |
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 |
/* Copyright (c) 2007 Nordic Semiconductor. All Rights Reserved. * * The information contained herein is property of Nordic Semiconductor ASA. * Terms and conditions of usage are described in detail in NORDIC * SEMICONDUCTOR STANDARD SOFTWARE LICENSE AGREEMENT. * * Licensees are granted free, non-transferable use of the information. NO * WARRENTY of ANY KIND is provided. This heading must NOT be removed from * the file. * * $LastChangedRevision: 186 $ */ /** @file * Type definitions for firmware projects developed at Nordic Semiconductor. * * Standard storage classes in C, such as @c char, @c int, and @c long, are not always * interpreted in the same way by the compiler. The types here are defined by their * bit length and signed/unsigned property, as their names indicate. The correlation * between the name and properties of the storage class should be true, regardless of * the compiler being used. */ #ifndef __STDINT_H__ #define __STDINT_H__ #if defined(__C51__) | defined(__SDCC__) typedef unsigned char uint8_t; ///< 8 bit unsigned int typedef signed char int8_t; ///< 8 bit signed int typedef unsigned int uint16_t; ///< 16 bit unsigned int typedef signed int int16_t; ///< 16 bit signed int typedef unsigned long uint32_t; ///< 32 bit unsigned int typedef signed long int32_t; ///< 32 bit signed int #endif // __C51__ #ifndef NULL #define NULL (void*)0 #endif #endif // __STDINT_H__ |
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 |
/* Copyright (c) 2007 Nordic Semiconductor. All Rights Reserved. * * The information contained herein is property of Nordic Semiconductor ASA. * Terms and conditions of usage are described in detail in NORDIC * SEMICONDUCTOR STANDARD SOFTWARE LICENSE AGREEMENT. * * Licensees are granted free, non-transferable use of the information. NO * WARRENTY of ANY KIND is provided. This heading must NOT be removed from * the file. * * $LastChangedRevision: 230 $ */ /** @file * Type definitions for firmware projects developed at Nordic Semiconductor. * * Standard storage classes in C, such as @c char, @c int, and @c long, are not always * interpreted in the same way by the compiler. The types here are defined by their * bit length and signed/unsigned property, as their names indicate. The correlation * between the name and properties of the storage class should be true, regardless of * the compiler being used. */ #ifndef __STDBOOL_H__ #define __STDBOOL_H__ #ifdef __SDCC__ typedef unsigned char boolean; #define bool boolean #else //lint -strong(b,_Bool) typedef unsigned char _Bool; ///< Boolean type #define bool _Bool #endif #define true ((bool)1) #define false ((bool)0) #define __bool_true_false_are_defined 1 #endif // __STDBOOL_H__ |
ケーブルとの接続は、24LE1用の2.54mm(2x5P)ピンソケットと24LU1P用の2.0mm(1x7P)ピンヘッダの両方が使えるものがないかと探してたらaitendoで見つけた。
P-X16P(100円)なる変換基板を数ヶ所リワーク後に、24LE1用2.54mmピンソケットと24LU1P用2.0mmピンヘッダ、及びMPSSEケーブル接続用2.54mmピンヘッダを取り付けて完成。
自作プログラマーのコマンドライン・オプション。(/?というオプションはない)
MPSSEケーブルをデバイスとPCに接続後、自作プログラマーを起動したところ。このソフトによりゴミ箱行き直前だった24LU1Pは見事に復活。捨てずに済みました。
特徴としては、mProプログラマーよりもましなところ(笑)、じゃなくて、中国語のエラー表示が出ないところ(笑)、でもなくて、DSYS保護機能があることかな?。最悪のことも考えてDSYSを再プログラムすることもできます。って、こんなの使いたい人っているのか...
次はMPSSEケーブルで、Atmel-AVR用プログラマーでも作ってみようかな。やる気がでたらだけど...それにしても比較的マイナーなものが好みで有名なPICすらまだ使ったことがないが、今の世の中には様々なCPUがありすぎて興味は尽きない。開発も簡単かつ無償でできる良い時代になったなと思う。昔昔、インテルの386が出たとき仮想86というプロテクトモードを利用してMS-DOSをマルチタスク化するOSみたいなものをアセンブラ言語で開発してたのが懐かしいかも。
[Release notes]
–2018-01-06
nRF24LU1PのNV-DATA領域の扱いをDAEN設定により適切に扱えるように変更。今まではDAENに関係なくR/Wできるようになっていてコード領域の最後が重複してしまっていた。
–2017-12-27 変数参照機能を追加
コマンド文字列内の${name}形式を置換する機能を追加。変数はコマンドラインのname=value指定、或いは、環境変数の参照に対応。以外に便利そう。(笑)
–2017-07-12 FILL/COPY/MOVEコマンドを追加
なんとなくあったほうがいいのかなと思い追加。(笑)
–2017-06-30 RESET/PROG制御タイミング変更
MCUのSPIピンがGPIOとして使われていても電気的に問題ないよう制御タイミングを変更。
–2017-06-28 ERASE仕様変更に伴うVERIFY仕様変更
ERASE仕様を変更したことでVERIFY時に対象ページ以外もVERIFYしてしまうためERASEと同仕様に変更。
–2017-06-27 バグ修正(仕様勘違い)とERASE仕様変更
ERASE address 仕様だと実質的に使えないので、LOAD/EDIT/READ後のメモリ内容から0xFF以外が存在するページのみをERASEするように仕様変更。CODE書き込みの一般的操作手順は次の通り。
CLEAR
LOAD firmware.hex
ERASE CODE or ERASE ALL // ※EARSE ALLは、NVDATAも消去。
PROG CODE
VERIFY CODE
[Download]
Compiled Binary and Source Code (Visual Studio Project)
[Hardware]
FTDI USB 2.0 Hi-Speed to MPSSE cable
C232HM-EDHSL-0 (RED-Wire 5.0V/450mA)
C232HM-DDHSL-0 (RED-Wire 3.3V/250mA)
MPSSE Supported Chip
FT232H
FT2232H
FT4232H
FT2232C