2014年6月10日火曜日

ENC28J60をPICで動かす


さて、NTP時計を作るために真っ先にネットワーク関係のプログラムを構築しなくてはなりませんね。
ENC28J60はSPIインターフェースでマイコンと接続することができるので、Arduinoなどでもライブラリが作られていて、結構ポピュラーに使われているようです。PICに関して言えば、ENC28J60とともにMicrochipの商品ということもあり、Microchipが無料でライブラリを出しています。

Microchip Libraries for Applications

さて、早速ダウンロードと行きたいところですが、このページの英語を注意深く読むと、最新版のライブラリ(2013/12/20リリース)ではTCP/IPのサポートがされていないことが書いています。なので、このページ下部のLegacy MLAタブを選択して、2013/06/15のバージョンをダウンロードする必要があります。

さて、ダウンロードしてきてインストールすると、何やらいろいろなデモプログラム等がありますが、どこをどういじっていいのか見当も付きません。
しかし、いろいろと調べていると、さすがPICの神様。後閑さんのウェブページにかなり有用な記事があることがわかります。

TCP/IPスタック Ver4.5xの使い方

ここの「2.プログラム全体構成」の中にプログラムの構成のしかたが書いてあります。さすがです。

ライブラリの microchip_solutions_v2013-06-15\Microchip\TCPIP Stack にあるソースコードの中から、今回は以下のファイルを必要ファイルとしてプロジェクトに組み込みました。
ファイル名を見ればどれがどの役割を果たすプログラムかはなんとなくわかると思います。
また、Includeファイルのある場所にパスを通しておく必要があります。
MPLAB Xではプロジェクトのプロパティから左ペインでxc16-gccを選択し、Option categoriesをPreprocessing and messagesにするとインクルードディレクトリのパスを設定する項目が出てきます。

あとひとつ、最後にコンパイルのオプションを指定したインクルードファイルを用意する必要があります。用意するのは
  • HardwareProfile.h
  • TCPIPConfig.h
の2つです。 microchip_solutions_v2013-06-15\TCPIP\Demo App などから拾ってきて適当に改造すればいいですが、その「適当」がまた難しいところではあります。

HardwareProfile.hに関しては、ほぼ跡形もなく改造することになります。
めんどくさいんでもうコード貼っちゃいます。

#ifndef HARDWARE_PROFILE_H
#define HARDWARE_PROFILE_H

#include "Compiler.h"

// Define a macro describing this hardware set up (used in other files)
#define EXPLORER_16

#define MAXIMUM_PIC_FREQ  (32000000ul)


// LED Definition
#define MIN_1  (LATAbits.LATA4)
#define LED_7SEG_D (LATAbits.LATA7)
#define SEC_1  (LATAbits.LATA8)
#define MIN_10  (LATAbits.LATA9)
#define LED_7SEG_C (LATBbits.LATB2)
#define LED_7SEG_A (LATBbits.LATB3)
#define SEC_10  (LATBbits.LATB4)
#define LED_7SEG_DP (LATBbits.LATB14)
#define LED_7SEG_E (LATBbits.LATB15)
#define LED_7SEG_B (LATCbits.LATC0)
#define LED_7SEG_F (LATCbits.LATC1)
#define LED_7SEG_G (LATCbits.LATC2)
#define LED_COLON (LATCbits.LATC3)
#define HOUR_1  (LATCbits.LATC4)
#define HOUR_10  (LATCbits.LATC5)

#define MASK_7SEG_RA ((uint16_t)0x0080)
#define MASK_7SEG_RB ((uint16_t)0xC00C)
#define MASK_7SEG_RC ((uint16_t)0x0007)

#define MASK_7SEGTR_RA ((uint16_t)0x0310)
#define MASK_7SEGTR_RB ((uint16_t)0x0010)
#define MASK_7SEGTR_RC ((uint16_t)0x0030)

// Switch Definition
#define SW_RED  (PORTAbits.RA10)
#define SW_BLUE  (PORTBbits.RB13)
#define SW_GREEN (PORTBbits.RB12)
#define SW_YELLOW (PORTBbits.RB11)


// These directly influence timed events using the Tick module.  They also are used for UART and SPI baud rate generation.
#define GetSystemClock()  (MAXIMUM_PIC_FREQ)   // Hz
#define GetInstructionClock() (GetSystemClock()/2) // Normally GetSystemClock()/4 for PIC18, GetSystemClock()/2 for PIC24/dsPIC, and GetSystemClock()/1 for PIC32.  Might need changing if using Doze modes.
#define GetPeripheralClock() (GetSystemClock()/2) // Normally GetSystemClock()/4 for PIC18, GetSystemClock()/2 for PIC24/dsPIC, and GetSystemClock()/1 for PIC32.  Divisor may be different if using a PIC32 since it's configurable.

// ENC28J60 I/O pins
#define ENC_CS_TRIS   (TRISBbits.TRISB10) // Comment this line out if you are using the ENC424J600/624J600, MRF24WB0M, or other network controller.
#define ENC_CS_IO   (LATBbits.LATB10)
// SPI SCK, SDI, SDO pins are automatically controlled by the
// PIC24/dsPIC SPI module
#define ENC_SPI_IF   (IFS0bits.SPI1IF)
#define ENC_SSPBUF   (SPI1BUF)
#define ENC_SPISTAT   (SPI1STAT)
#define ENC_SPISTATbits  (SPI1STATbits)
#define ENC_SPICON1   (SPI1CON1)
#define ENC_SPICON1bits  (SPI1CON1bits)
#define ENC_SPICON2   (SPI1CON2)

// LCD I/O pins
#define LCD_CS_IO   (LATBbits.LATB8)
#define LCD_RS_IO   (LATBbits.LATB7)
#define LCD_SDO_IO   (LATBbits.LATB5)
#define LCD_SCK_IO   (LATBbits.LATB6)

#endif // #ifndef HARDWARE_PROFILE_H


もとのデモプログラムのHardwareProfile.hを見てくれてもわかりますが、ほとんどがENC28J60と関係ない表記です。何のピンが何に割り当てられているかとか、そういったことが書いてあります。なので、このNTP時計のHardwareProfile.hもそんな感じになっています。
重要なのはENC28J60 I/O pinsのところで、そこにENC28J60と接続するのに使うレジスタと、ソフトウェアから制御するチップセレクトピン(CS)を定義しています。
ちなみに、もとのデモプログラムではこの中でコンフィグレーションビットの設定をしています。が、#ifdefはできるだけ避けたいのと、自分はいつもmain.cの中で定義していたので、我流にしたがってこちらからは削除しています。

つづいてTCPIPConfig.hですが、これはデモプログラムのものに手を加えるという形にします。
割と最初のほうにApplication Optionsという項目がありますので、ここを必要最低限のものを残してコメントアウトします。

// =======================================================================
//   Application Options
// =======================================================================

/* Application Level Module Selection
 *   Uncomment or comment the following lines to enable or
 *   disabled the following high-level application modules.
 */
//#define STACK_USE_UART     // Application demo using UART for IP address display and stack configuration
//#define STACK_USE_UART2TCP_BRIDGE  // UART to TCP Bridge application example
//#define STACK_USE_IP_GLEANING
#define STACK_USE_ICMP_SERVER   // Ping query and response capability
#define STACK_USE_ICMP_CLIENT   // Ping transmission capability
//#define STACK_USE_HTTP2_SERVER   // New HTTP server with POST, Cookies, Authentication, etc.
//#define STACK_USE_SSL_SERVER   // SSL server socket support (Requires SW300052)
//#define STACK_USE_SSL_CLIENT   // SSL client socket support (Requires SW300052)
#define STACK_USE_AUTO_IP               // Dynamic link-layer IP address automatic configuration protocol
#define STACK_USE_DHCP_CLIENT   // Dynamic Host Configuration Protocol client for obtaining IP address and other parameters
//#define STACK_USE_DHCP_SERVER   // Single host DHCP server
//#define STACK_USE_FTP_SERVER   // File Transfer Protocol (old)
//#define STACK_USE_SMTP_CLIENT   // Simple Mail Transfer Protocol for sending email
//#define STACK_USE_SNMP_SERVER   // Simple Network Management Protocol v2C Community Agent
//#define STACK_USE_SNMPV3_SERVER   // Simple Network Management Protocol v3 Agent
//#define STACK_USE_TFTP_CLIENT   // Trivial File Transfer Protocol client
//#define STACK_USE_GENERIC_TCP_CLIENT_EXAMPLE // HTTP Client example in GenericTCPClient.c
//#define STACK_USE_GENERIC_TCP_SERVER_EXAMPLE // ToUpper server example in GenericTCPServer.c
//#define STACK_USE_TELNET_SERVER   // Telnet server
//#define STACK_USE_ANNOUNCE    // Microchip Embedded Ethernet Device Discoverer server/client
#define STACK_USE_DNS     // Domain Name Service Client for resolving hostname strings to IP addresses
//#define STACK_USE_DNS_SERVER   // Domain Name Service Server for redirection to the local device
//#define STACK_USE_NBNS     // NetBIOS Name Service Server for repsonding to NBNS hostname broadcast queries
//#define STACK_USE_REBOOT_SERVER   // Module for resetting this PIC remotely.  Primarily useful for a Bootloader.
#define STACK_USE_SNTP_CLIENT   // Simple Network Time Protocol for obtaining current date/time from Internet
//#define STACK_USE_UDP_PERFORMANCE_TEST // Module for testing UDP TX performance characteristics.  NOTE: Enabling this will cause a huge amount of UDP broadcast packets to flood your network on the discard port.  Use care when enabling this on production networks, especially with VPNs (could tunnel broadcast traffic across a limited bandwidth connection).
//#define STACK_USE_TCP_PERFORMANCE_TEST // Module for testing TCP TX performance characteristics
//#define STACK_USE_DYNAMICDNS_CLIENT  // Dynamic DNS client updater module
//#define STACK_USE_BERKELEY_API   // Berekely Sockets APIs are available
//#define STACK_USE_ZEROCONF_LINK_LOCAL // Zeroconf IPv4 Link-Local Addressing
//#define STACK_USE_ZEROCONF_MDNS_SD  // Zeroconf mDNS and mDNS service discovery

これは、主にStackTask.cでTCP/IP関係のプログラムを無限ループの中で呼び出していくときに使われます。定義されている項目のみ呼び出されるので、コメントアウトしたらそのモジュールは使用されません。容量の都合もありますし、不必要なものは徹底的に省いています。

そして、もう少し下へ行くと MY_DEFAULT_MAC_BYTE1~MY_DEFAULT_MAC_BYTE6というマクロが定義されています。これは、このネットワークアダプタのMACアドレスになります。
MACアドレスは6バイトのネットワークアダプタ固有のアドレスなので、世界中で重複したMACアドレスは存在しないことになっています。はい。まあ、自分は量産するわけでもないので適当に決め打ちで入力しちゃってます。デフォルトで入っているのはMicrochip社が購入しているMACアドレスですね。本気でMACアドレスを買おうとすると何百ドルもするようです…。


これで、だいたいのライブラリの構築は終わりました。

最後はmain関数の作成ですね。
ずばり、main関数はとてもシンプルになっています。

int main(void)
{
 __delay_ms(300);
 
 InitPeripheral();
 TickInit();
 InitAppConfig();
 StackInit();

 while(1) {
  StackTask();
  StackApplications();
  ProcessIO();
 }
 return (EXIT_SUCCESS);
}

非常にシンプルです。
起動してからは安定待ちとして適当に300msくらい待っています。気持ちの問題です。
InitPeripheral();はペリフェラルの初期化をしている関数です。ENC28J60で使うSPIのピン割り当てはここでする必要があります
TickInit();はライブラリのTick.cの中の関数です。詳しくは後日書くつもりですが、まあ時間を測るライブラリです。
InitAppConfig();はネットワーク関係でとても重要な関数ですので後述します。
StackInit();はStackTask.cの中にある、TCP/IPスタックを初期化する関数です。
StackTask();もStackTask.cにあり、TCP/IPスタックの処理をする関数です。
StackApplications();もStackTask.cにあり、SNTPとかのTCP/IPアプリケーションを動かす関数です。
ProcessIO();はENC28J60とは何も関係ないプログラムを処理するための関数です。

こんな感じっすかね。
InitAppConfigだけ詳しく説明しておきます。

APP_CONFIG AppConfig;
static ROM BYTE SerializedMACAddress[6] = {MY_DEFAULT_MAC_BYTE1, MY_DEFAULT_MAC_BYTE2, MY_DEFAULT_MAC_BYTE3, MY_DEFAULT_MAC_BYTE4, MY_DEFAULT_MAC_BYTE5, MY_DEFAULT_MAC_BYTE6};
static void InitAppConfig(void)
{
    // Start out zeroing all AppConfig bytes to ensure all fields are
    // deterministic for checksum generation
    memset((void*)&AppConfig, 0x00, sizeof(AppConfig));

    AppConfig.Flags.bIsDHCPEnabled = TRUE;
    AppConfig.Flags.bInConfigMode = TRUE;
    memcpypgm2ram((void*)&AppConfig.MyMACAddr, (ROM void*)SerializedMACAddress, sizeof(AppConfig.MyMACAddr));
    AppConfig.MyIPAddr.Val = MY_DEFAULT_IP_ADDR_BYTE1 | MY_DEFAULT_IP_ADDR_BYTE2<<8ul | MY_DEFAULT_IP_ADDR_BYTE3<<16ul | MY_DEFAULT_IP_ADDR_BYTE4<<24ul;
    AppConfig.DefaultIPAddr.Val = AppConfig.MyIPAddr.Val;
    AppConfig.MyMask.Val = MY_DEFAULT_MASK_BYTE1 | MY_DEFAULT_MASK_BYTE2<<8ul | MY_DEFAULT_MASK_BYTE3<<16ul | MY_DEFAULT_MASK_BYTE4<<24ul;
    AppConfig.DefaultMask.Val = AppConfig.MyMask.Val;
    AppConfig.MyGateway.Val = MY_DEFAULT_GATE_BYTE1 | MY_DEFAULT_GATE_BYTE2<<8ul | MY_DEFAULT_GATE_BYTE3<<16ul | MY_DEFAULT_GATE_BYTE4<<24ul;
    AppConfig.PrimaryDNSServer.Val = MY_DEFAULT_PRIMARY_DNS_BYTE1 | MY_DEFAULT_PRIMARY_DNS_BYTE2<<8ul  | MY_DEFAULT_PRIMARY_DNS_BYTE3<<16ul  | MY_DEFAULT_PRIMARY_DNS_BYTE4<<24ul;
    AppConfig.SecondaryDNSServer.Val = MY_DEFAULT_SECONDARY_DNS_BYTE1 | MY_DEFAULT_SECONDARY_DNS_BYTE2<<8ul  | MY_DEFAULT_SECONDARY_DNS_BYTE3<<16ul  | MY_DEFAULT_SECONDARY_DNS_BYTE4<<24ul;
}

このAppConfigという変数は、TCP/IPスタックライブラリの中でも使われてる変数です。グローバル変数はstaticじゃなければ、他のファイルからでもexternで修飾することで使えますね。すなわち、この変数を定義してやらないとコンパイルエラーになります。
まあ、デフォルトのIPアドレスとかMACアドレスとかをロードしてるだけなんで難しいことは無いと思います。

これで、とりあえずENC28J60が動くようになるはずです。

は~長かった。

ちなみに、PIC24FJ64GA004で、ここまででプログラムメモリの使用率が50%近くなるはずです。
恐ろしや…。

(余談ですが、コンパイルはXC16 v1.21でやっています。C18でコンパイルしようとしたらコンパイルが通らなくて、どうも調べたところによるとC18のバグとかいう話でした。本当かどうかは知りませんが…。)

0 件のコメント:

コメントを投稿