素数の音を聴く【PICマイコン】

Pocket

素数の音楽




理系たるもの、一度は素数に魅せられた身。前回のPICマイコンに無理やり素数を計算させてみたでは、PICをまさに計算機として素数を計算させ、LCDに表示しました。

ここで「素数の音が聞けたら音楽になるのではないか」という謎発想が思い浮かび、プログラムを書いてみました。

音を鳴らすのはこちらの記事の回路・関数を使います。

工作レシピ

用意するもの

製作手順

出来れば、動けばいい人のためのPIC 入門編を一通り理解していることが望ましいです。

PICマイコンが初めての方は、上記のリンク先から学び、応用編のLCD(液晶ディスプレイ)の部分までこなせばこの楽器(?)を作ることができます。

  1. 「コピペで超簡単!PICでI2C接続のLCDを使う」に沿って、LCDを接続する
  2. 「PICマイコンに無理やり素数を計算させてみた」に沿って、素数を表示してみる
  3. 圧電スピーカーの使い方を参考に、スピーカーをPICに繋ぐ(下のプログラム例ではRA1に繋いでいます)
  4. 後述の完成プログラムを書き込む




完成プログラム


    // PIC16F1938 Configuration Bit Settings

        // 'C' source line config statements

        // CONFIG1
        #pragma config FOSC = INTOSC    // Oscillator Selection (INTOSC oscillator: I/O function on CLKIN pin)
        #pragma config WDTE = OFF       // Watchdog Timer Enable (WDT disabled)
        #pragma config PWRTE = OFF      // Power-up Timer Enable (PWRT disabled)
        #pragma config MCLRE = OFF      // MCLR Pin Function Select (MCLR/VPP pin function is digital input)
        #pragma config CP = OFF         // Flash Program Memory Code Protection (Program memory code protection is disabled)
        #pragma config CPD = OFF        // Data Memory Code Protection (Data memory code protection is disabled)
        #pragma config BOREN = OFF      // Brown-out Reset Enable (Brown-out Reset disabled)
        #pragma config CLKOUTEN = OFF   // Clock Out Enable (CLKOUT function is disabled. I/O or oscillator function on the CLKOUT pin)
        #pragma config IESO = OFF       // Internal/External Switchover (Internal/External Switchover mode is disabled)
        #pragma config FCMEN = OFF      // Fail-Safe Clock Monitor Enable (Fail-Safe Clock Monitor is disabled)

        // CONFIG2
        #pragma config WRT = OFF        // Flash Memory Self-Write Protection (Write protection off)
        #pragma config VCAPEN = OFF     // Voltage Regulator Capacitor Enable (All VCAP pin functionality is disabled)
        #pragma config PLLEN = ON// PLL Enable (4x PLL disabled)
        #pragma config STVREN = ON      // Stack Overflow/Underflow Reset Enable (Stack Overflow or Underflow will cause a Reset)
        #pragma config BORV = LO        // Brown-out Reset Voltage Selection (Brown-out Reset Voltage (Vbor), low trip point selected.)
        #pragma config LVP = ON         // Low-Voltage Programming Enable (Low-voltage programming enabled)

        // #pragma config statements should precede project file includes.
        // Use project enums instead of #define for ON and OFF.

        #include <xc.h>
        #include <pic16f1938.h>
        #include <math.h>
        #include <stdio.h>
        #define _XTAL_FREQ 32000000
        #define LCD_ADD 0x7C
        #define sound RA1

        void I2C_Master_Init(const unsigned long c)
        {
          SSPCON1 = 0b00101000;
          SSPCON2 = 0;
          SSPADD = (_XTAL_FREQ/(4*c))-1;
          SSPSTAT = 0b00000000 ;    // 標準速度モードに設定する(100kHz)
        }

        void I2C_Master_Wait()
        {
          while ((SSPSTAT & 0x04) || (SSPCON2 & 0x1F));
        }

        void I2C_Master_Start()
        {
          I2C_Master_Wait();
          SEN = 1;
        }

        void I2C_Master_RepeatedStart()
        {
          I2C_Master_Wait();
          RSEN = 1;
        }

        void I2C_Master_Stop()
        {
          I2C_Master_Wait();
          PEN = 1;
        }

        void I2C_Master_Write(unsigned d)
        {
          I2C_Master_Wait();
          SSPBUF = d;
        }
        void writeData(char t_data){
            I2C_Master_Start();
            I2C_Master_Write(LCD_ADD);
            I2C_Master_Write(0x40);
            I2C_Master_Write(t_data);
            I2C_Master_Stop();
            __delay_ms(10);
        }
        void writeCommand(char t_command){
            I2C_Master_Start();
            I2C_Master_Write(LCD_ADD);
            I2C_Master_Write(0x00);
            I2C_Master_Write(t_command);
            I2C_Master_Stop();
            __delay_ms(10);
        }
        void PICinit(){
          OSCCON = 0b01110000;
          ANSELA = 0b00000000;
          ANSELB = 0b00000000;
          TRISA  = 0b00000000;
          TRISB  = 0b00000000;
          TRISC  = 0b00011000;
          PORTA  = 0b00000000;    //2進数で書いた場合
          PORTB  = 0x00;          //16進数で書いた場合
        }
        void LCD_Init(){            //LCDの初期化
          I2C_Master_Init(100000);
          __delay_ms(400);
          writeCommand(0x38);
          __delay_ms(20);
          writeCommand(0x39);
          __delay_ms(20);
          writeCommand(0x14);
          __delay_ms(20);
          writeCommand(0x73);
          __delay_ms(20);
          writeCommand(0x52);
          __delay_ms(20);
          writeCommand(0x6C);
          __delay_ms(250);
          writeCommand(0x38);
          __delay_ms(20);
          writeCommand(0x01);
          __delay_ms(20);
          writeCommand(0x0C);
          __delay_ms(20);
        }

        void LCD_str(char *c) {     //LCDに配列の文字を表示
          unsigned char i,wk;
          for (i=0 ; ; i++) {
            wk = c[i];
            if  (wk == 0x00) {break;}
            writeData(wk);
          }
        }
    void do1(){
            for(long i = 1;i<26;i++){
                sound = 1;
                __delay_us(1908);
                sound = 0;
                __delay_us(1908);
            }
        }
        void re1(){
            for(long i = 1;i<29;i++){
                sound = 1;
                __delay_us(1700);
                sound = 0;
                __delay_us(1700);
            }
        }
        void mi1(){
            for(long i = 1;i<33;i++){
                sound = 1;
                __delay_us(1515);
                sound = 0;
                __delay_us(1515);
            }
        }
        void fa1(){
            for(long i = 1;i<35;i++){
                sound = 1;
                __delay_us(1432);
                sound = 0;
                __delay_us(1432);
            }
        }
        void so1(){
            for(long i = 1;i<39;i++){
                sound = 1;
                __delay_us(1275);
                sound = 0;
                __delay_us(1275);
            }
        }
        void ra1(){
            for(long i = 1;i<44;i++){
                sound = 1;
                __delay_us(1136);
                sound = 0;
                __delay_us(1136);
            }
        }
        void si1(){
            for(long i = 1;i<49;i++){
                sound = 1;
                __delay_us(1012);
                sound = 0;
                __delay_us(1012);
            }
        }
        void do2(){
            for(long i = 1;i<52;i++){
                sound = 1;
                __delay_us(956);
                sound = 0;
                __delay_us(956);
            }
        }

        int main(void){
          PICinit();      //PICを初期化
          LCD_Init();

          char prime[16];
          writeCommand(0x02);   //ホームへカーソル移動
          int i,j,k;

          sprintf(prime,"PrimeNum:2");
          LCD_str(prime);               //LCDに最初の素数2を表示
          for(i=3;i<=10000;i+=2){
    		k=0;                        //素数判定変数をリセット
    		for(j=3;j<=sqrt(i);j+=2)    //jは割る数で、iの平方根までカウントアップ
    		{
    			if(i%j==0)              //もしjで割れた場合(つまり、素数じゃない場合)
    			{
    				k=1;                //素数判定変数に1を代入
    				break;              //ループ離脱
    			}
    		}

    		if(k==0){                   //素数の場合
                sprintf(prime,"PrimeNum:%d",i);     //primeに文字列"PrimeNum:i"をセット
                writeCommand(0x02);   //ホームへカーソル移動
                LCD_str(prime);                     //LCDに文字列primeを表示
                switch(i%10){           //1の位で音を変化
                    case 1:
                        do1();
                        break;
                    case 2:
                        re1();
                        break;
                    case 3:
                        mi1();
                        break;
                    case 5:
                        so1();
                        break;
                    case 7:
                        ra1();
                        break;
                    case 9:
                        do2();
                        break;
                    default:
                        __delay_ms(1);
                }
            }
          }

        }
  

前回から追加した部分は1の位の数によって音を変えるというところです。具体的には


    if(k==0){                   //素数の場合
            sprintf(prime,"PrimeNum:%d",i);     //primeに文字列"PrimeNum:i"をセット
            writeCommand(0x02);   //ホームへカーソル移動
            LCD_str(prime);                     //LCDに文字列primeを表示
            switch(i%10){           //1の位で音を変化
                case 1:
                    do1();
                    break;
                case 2:
                    re1();
                    break;
                case 3:
                    mi1();
                    break;
                case 5:
                    so1();
                    break;
                case 7:
                    ra1();
                    break;
                case 9:
                    do2();
                    break;
                default:
                    __delay_ms(1);
            }
        }
      }
  

この部分です。do1()等は、PIC16F1938でスピーカーを鳴らしてみたで作成した関数ですので、こちらを参考にしてください。

関連記事   PICマイコンで圧電スピーカーを鳴らしてみた【PIC16F1938】

switch(i%10)で、1の位の数が分かります。例えば1234であれば、これを10で割った余りは4ですね。ですので、1234%10 == 4となります。これを、caseで分岐させています。2以外の偶数は元から素数ではないので、除外しています。2も1回しか現れないので、の音は1回しかなりません。さみしい。同様に5も一回ですね。ですので、このプログラムが進行していった時になる音というのは「ド、ミ、ラ、ド(1オクターブ高い方)」の4種になります。ですが、意外と音楽っぽくなっているのではないでしょうか

このcase文の中は好きな音に変更できるのが分かると思います。好きな音にして、違いを楽しんでみてください。それでは、快適な素数ライフを。




Pocket

返信を残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です