PICの疑似乱数(rand)の使い方について

〔標準関数の使い方とサブルーチンサンプル集に戻る〕


HI-TECH C Compiler for PIC10/12/16 MCUs(Lite Mode) と MPLAB(R) XC8 C Compiler には
標準関数でrand()と言う疑似乱数発生関数が有ります。
使い方は、
(これ以下のプログラムはMPLAB(R) XC8 C Compile V1.00コンパイラ仕様にて記述しています)

#include <xc.h>
#include <stdlib.h>

main()
{
     int d ;

     d = rand() ;   // この行を実行する度に乱数が発生する
}
ですがぁ.....

実行結果1
 この図はPICのrand()関数(下記)をArduinoで実行させ、シリアルモニターに
 表示させた結果です。
 (PICは表示させるのが面倒なのでArduinoで実験です)

 表示は乱数になっていますが、電源を切り立上げ直して再実行させても
 この結果は同じに表示します。
 何度再実行してもこの発生した乱数の現れ方順番は同じになります。


ここでPICのrand()標準関数のソース(C:\Program Files\Microchip\xc8\v1.00\sources\rand.c)を見てみましょう。
#include <stdlib.h>

static	long	randx;
static	char	randf;

void srand(unsigned x)
{
	randx = x;
	randf = 1;
}
rand(void)
{
	if(!randf) srand(1); // ここが問題の行です
	return((int)((randx = randx*1103515245L + 12345) >> 16) & 077777);
}
ってなっています。
srand(1)が固定で可変されていません、この1をプログラムの起動実行時に色々な数値で設定すれば 何度再実行しても乱数の現れ方は異なったパターンになります。
srand(1)は最初のrand()実行時に1度しか実行されません。

ではPICで色々な数値を可変設定するには?、
接続されていないアナログ端子を読んで設定値にすれば良いのです。
rand(void)
{
	if(!randf) srand(ここにアナログ値をセット);
	return((int)((randx = randx*1103515245L + 12345) >> 16) & 077777);
}
こんな感じです、接続されていないアナログ端子の電圧は不定(ノイズ利用)なので丁度よさげですね。
でぇ、Arduinoで実験すると、
	if(!randf) srand(analogRead(0)) ; // アナログ0番ピンより読込む
に書き換えて2回再実行させた結果が下の図です。
 実行結果2  実行結果3
どうでしょうか、異なった乱数の現れ方パターンでしょう。


次に乱数の発生の仕方を指示したい場合が実際には良くあります。
それは例えば、1〜10までの範囲内で乱数を発生させたいみたいなぁ。
PICのrand()標準関数では出来ませんが、下記の様に変更すれば出来ます。
それは
rand(int min,int max)
{
     int ans ;

	if(!randf) srand(1);
	ans = (int)(((randx = randx*1103515245L + 12345) >> 16) & 077777) ;
        return min + ans  % (max - min + 1) ;
}
って書き換えてやればOKです。
でぇ、
     d = rand(1,10) ;   // この行を実行する度に乱数が発生する
と行えば1〜10までの範囲で乱数が発生するわけです。
でもぉ、PICの標準関数を変更するのは少し嫌なのでぇ、
下記の様にオリジナルのMy関数を作成し使用しましょう。
[疑似乱数発生関数:random] *1)
(以下をコピー&ペーストしてファイル名random.cで保存しましょう)
----random.c-----------------------------------------------------
#include <xc.h>
#include "random.h"

static long randomx ;

// 疑似乱数の初期値を設定する処理
void randomSeed(long val)
{
     unsigned int temp;

     if (val == 0) {
          while(1) {
               GO_nDONE = 1 ;      // PICにアナログ値読取り開始を指示
               while(GO_nDONE) ;   // PICが読取り完了するまで待つ
               temp = ( ADRESH << 8 ) | ADRESL ;  // 10ビットの分解能力です
               if (temp > 0) break ;
          }
          randomx = temp ;
     } else randomx = val ;   // 指定数値をそのまま初期値とする
}
// 疑似乱数を発生させる処理
long random(long min,long max)
{
     long d ;

     d = ((randomx = randomx*1103515245L + 12345) >> 16) & 077777 ;
     return min + d  % (max - min + 1) ;
}
-----------------------------------------------------------------
(以下をコピー&ペーストしてファイル名random.hで保存しましょう)
----random.h-----------------------------------------------------
#ifndef _RANDOM_H_
#define _RANDOM_H_

void randomSeed(long val) ;
long random(long min,long max) ;

#endif
-----------------------------------------------------------------
下記サンプルプログラムはPIC16F1827での使用例です。
500ms毎にCCP3(2番ピン)に接続したLEDの明るさをランダムに変化点灯させます。
また、PWM(CCP3)の機能やLEDの接続のしかた等はこちらを参考にして下さい。
-----------------------------------------------------------------
#include <xc.h>
#include "random.h"

#define _XTAL_FREQ 8000000    // delay用に必要(クロック8MHzを指定)

// コンフィギュレーション1の設定
// CLKOUTピンをRA6ピンで使用する(CLKOUTEN_OFF):内部クロックを使用する(FOSC_INTOSC)
// 外部クロック監視しない(FCMEN_OFF):外部・内部クロックの切替えでの起動はなし(IESO_OFF)
// 電源電圧降下常時監視機能ON(BOREN_ON):電源ONから64ms後にプログラムを開始する(PWRTEN_ON)
// ウオッチドッグタイマー無し(WDTE_OFF):
// 外部リセット信号は使用せずにデジタル入力(RA5)ピンとする(MCLRE_OFF)
// プログラムメモリーを保護しない(CP_OFF):データメモリーを保護しない(CPD_OFF)
__CONFIG(CLKOUTEN_OFF & FOSC_INTOSC & FCMEN_OFF & IESO_OFF & BOREN_ON &
         PWRTE_ON & WDTE_OFF & MCLRE_OFF & CP_OFF & CPD_OFF) ;
// コンフィギュレーション2の設定
// 動作クロックを32MHzでは動作させない(PLLEN_OFF)
// スタックがオーバフローやアンダーフローしたらリセットをする(STVREN_ON)
// 低電圧プログラミング機能使用しない(LVP_OFF)
// Flashメモリーを保護しない(WRT_OFF):電源電圧降下常時監視電圧(2.5V)設定(BORV_HI)
__CONFIG(PLLEN_OFF & STVREN_ON & WRT_OFF & BORV_HI & LVP_OFF) ;

// メインの処理
void main()
{
     long ans ;

     OSCCON = 0b01110010 ;    // 内部クロックは8MHzとする
     ANSELA = 0b00000000 ;    // AN0-AN4は使用しない全てデジタルI/Oとする
     ANSELB = 0b00000000 ;    // AN5-AN11は使用しない全てデジタルI/Oとする
     TRISA  = 0b00000000 ;    // ピン(RA)は全て出力に割当てる(RA5は入力のみとなる)
     TRISB  = 0b00000000 ;    // ピン(RB)は全て出力に割当てる
     PORTA  = 0b00000000 ;    // RA出力ピンの初期化(全てLOWにする)
     PORTB  = 0b00000000 ;    // RB出力ピンの初期化(全てLOWにする)
     // A/Dの設定(疑似乱数の初期値として利用する)
     ADCON1 = 0b10010000 ;    // 読取値は右寄せ、A/D変換クロックはFOSC/8、VDDをリファレンスに
     ADCON0 = 0b00000101 ;    // アナログ変換情報設定(AN1から読込む)
     __delay_us(5) ;          // アナログ変換情報が設定されるまでとりあえず待つ
     // PWM(CCP3)の設定(LEDの点灯制御に利用する)
     CCPTMRS = 0b00010000 ;   // CCP3機能はTimer4を使用する
     CCP3CON = 0b00001100 ;   // PWM機能(モジュール)を使用する
     T4CON   = 0b00000010 ;   // TMR4プリスケーラ値を16倍に設定
     CCPR3L  = 0 ;            // デューティ値は0で初期化
     CCPR3H  = 0 ;
     TMR4    = 0 ;            // タイマー4カウンターを初期化
     PR4     = 124 ;          // PWMの周期を設定(1000Hzで設定)
     TMR4ON  = 1 ;            // TMR4(PWM)スタート

     randomSeed(0) ;          // 乱数の初期値はアナログAN1を読みだして設定する
                              // 0以外の数値ならその数値を初期値として設定する

     while(1) {
          ans = random(1,255) ;// 1〜255の範囲内で乱数を発生させる
          CCPR3L = (char)ans ; // 乱数値でPWMのデュティ比を可変させる
          __delay_ms(500) ;    // 0.5秒後に繰り返し
     }
}
-----------------------------------------------------------------
乱数の初期値をアナログから読み込む場合は、レジスタANSELx/TRISx/ADCON0で
読み込む端子をアナログ読み込みとして設定をしないとだめですよ。
(上記サンプルは設定されていないです)

注意としてはrandomSeed()は立ち上がり時に1回だけ実行すればOKです。
あとぉ、randomSeed()内に記述のレジスタで GO_nDONE が有りますが、これはPICによっては 名前が異なる場合が有ります、例えば12F675/16F819などは GODONE です、 その場合は書き換えないとだめです。



randomSeed関数の変更(*1) 2016/04/14


【きむ茶工房ガレージハウス】
Copyright (C) 2006-2016 Shigehiro Kimura All Rights Reserved.