• MAX10 USB-Blasterが認識されない

    CQ誌のMAX10付属回路が完成!
    PICのファームウェアもFPGAから書き込めたし、回路は問題なさそう。ただ・・・

    max10_first

    認識されない!

    PCに繋いで、本に載っている通り Programmerを起動し「Currently Selected Hardware」を見ても作ったデバイスが出てこない。
    おかしいよね。インストール時にUSB-Blasterのドライバはインストールされているはずなのに。

    デバイスドライバー上でもエラーになっている

    ここで、そもそもWindowsが認識しているかどうかをチェックする。
    ハンダ付けにミスってUSBの電源はOKだったけど通信ができてないかもしれないからね。
    Windowsの「デバイスドライバー」を起動(左下メニューを右クリック)すると、見事にUSB-Blasterがエラーになっている。

    usbblaster-error
    ドライバーの自動検出を試してもダメ。
    ドライバーがちゃんと入ってないみたい。

    Windowsをテストモードにする

    実はWindows8や10以降からデバイスドライバーはちゃんとした署名(?)が入ったものでないとインストールされないようになっているんだけど、どうやらUSB-Blasterのドライバーはちゃんとなってないみたい。
    なので、Windows10をテストモードにしてドライバーを再度入れなおす。
    テストモードへの切り替え方は

    1. コマンドプロンプトを管理者モードで起動する(スタートメニュー右クリックの中にある)
    2. コマンドプロンプトで「bcdedit /set TESTSIGNING ON 」と打って改行
    3. Windowsを再起動

    これだけでOK。再起動後は画面右下にテストモードだよ気をつけてねって言う文字が常に出る。

    windows10_testmode

    この状態でドライバーを再度入れる。元々あったインストーラーをもう一回実行してもいい。
    もしドライバーだけ入れたいときは
    https://www.altera.com/support/support-resources/knowledge-base/solutions/rd06212010_676.html

    のDownloadからドライバーをダウンロードして、
    「デバイスドライバー」から問題になっているUSB-Blasterをダブルクリックで開き、「ドライバーの更新」から「ファイルを指定して開く」でさっきダウンロードしたものを選択すればOK。
    するとインストール時にこんなのが出てくる。

    windows10_driver

    これはAlteraから提供されているので信じて「このドライバーソフトウェアをインストールします」を選べばインストールされます。

    ちゃんと認識した!

    もう一度Programmerを開くとデバイスの中にUSB-Blasterがちゃんと出てきました。

  • OpenCV カメラの歪みをなおすキャリブレーション C++

    カメラはレンズを通して撮影するのですが、その場合どうしても映像がゆがんでしまいます。
    直線を撮影しても少し歪むんです。
    その歪みを治すためのプログラムが、元々OpenCVには入っています。
    今回はそれを使ってみます。

    opencv_caibration

    歪みの直し方

    歪はどのぐらい歪んでいるかを検出できれば直せます。上の画像のようにチェスボードの模様を歪を治したいカメラで撮影して、この直線がどのぐらい歪んでるかで補正することができるわけです。OpenCVにはチェスボード模様を使った補正関数が元々用意されています。

    まずは複数枚撮影

    まずは歪んでる写真を取りましょう。
    チェスボード画像は
    http://opencv.jp/sample/pics/chesspattern_7x10.pdf
    からダウンロードできます。これを印刷しましょう。

    そしてカメラで何度か撮影します。できればカメラは同じ位置の方が良いです。
    その時に色々な位置・角度において、歪みをちゃんと検出できるようにします。
    (ちなみにチェスボードを机から浮かせて撮影するのもありなようです)
    debugimage1 debugimage5

    さて、撮影したら(calibrationimage0.png calibrationimge1.png)といった連番で同じフォルダに保存しておきましょう。

    取り込んでキャリブレーション

        // デバッグウインドウを開く
        namedWindow("debugwindow", CV_WINDOW_AUTOSIZE);
        
        int horizonalCrossCount = 10;
        int verticalCrossCount = 7;
        
        // calibrate points
        vector<vector<Point3f>> object_points;
        vector<vector<Point2f>> image_points;
        vector<Point3f> obj;
        for(int j=0;j< horizonalCrossCount * verticalCrossCount;j++) {
            obj.push_back(Point3f(j/horizonalCrossCount, j%horizonalCrossCount, 0.0f));
        }
        
        unsigned int numberOfImages = 7;
        for(unsigned int i=0; i<numberOfImages; i++) {
            
            char filename[32];
            sprintf(filename, "/projects/desktop/Resources/calibration/debugimage%d.png", i);
            Mat frame = imread(filename);
            Mat gray;
            
            flip(frame, frame, -1);
            cvtColor(frame, gray, CV_BGR2GRAY);
            
            // 10-7 チェスを探す
            Size chessboardPatterns(horizonalCrossCount, verticalCrossCount);
            vector<Point2f> centers;
            bool found = findChessboardCorners(gray, chessboardPatterns, centers, CALIB_CB_ADAPTIVE_THRESH | CALIB_CB_NORMALIZE_IMAGE);
            if (found) {
                // 見つけたpointを高精度化する
                cornerSubPix(gray, centers, Size(11,11), Size(-1,-1), TermCriteria (TermCriteria::EPS+TermCriteria::COUNT, 30, 0.1));
                object_points.push_back(obj);
                image_points.push_back(centers);
                
                // draw
                drawChessboardCorners(gray, chessboardPatterns, Mat(centers), true);
                imshow(_debugWindow, gray);
            } else {
                cout << "not found" << endl;
            }
            
            int key = waitKey(1);
            if (key == 'q') {
                break;
            }
        }
        
        // calibrate
        vector<Mat> rvecs, tvecs;
        Mat mtx(3,3, CV_64F);
        Mat dist(8,1, CV_64F);
        calibrateCamera(object_points, image_points, _captureSize, mtx, dist, rvecs, tvecs);
        
        unsigned int i=0;
        while(1) {
            Mat frame;
            _cap >> frame;
            flip(frame, frame, -1);
            
            if (++i > 10) {
                Mat undistorted;
                undistort(frame, undistorted, mtx, dist);
                imshow(_debugWindow, undistorted);
                if (i==20) {
                    i=0;
                }
            } else {
                imshow(_debugWindow, frame);
            }
            
            int key = waitKey(1);
            if (key == 'q') {
                break;
            }
        }

    実際の例を貼っておきます。
    ファイルへのパスやなどは自分で修正してくださいね。

    やっていることはさっき撮影した画像を1つずつとりだしては、
    findChessboardCorners関数で7*10のチェスを探し出します。
    見つけたらcornerSubPixで点の精度を上げてimage_points配列に保存しています。

    この作業を全部の画像で出来たら
    calibrateCamera
    関数で補正値を取り出しています。
    mtxとdistです。
    ちなみにrevsとtvecsもこの関数から得られるものですが、mixとdistを計算するために内部で計算されたカメラの特性です。
    この特性を元に「画像を実際に補正するための値」がmtxとdistになりますので、mtxとdistだけあれば良いわけです。

    その下の無限ループでは実際にカメラの画像を取得しては表示しています。
    ただし、10フレームごとに補正あり・なしを繰り返すので見てると補正されているのがわかると思います。
    undistort関数にカメラの画像とmtxとdistを渡せば歪が補正された結果を取得できます。

    mtxとdistの保存

    一度計算したmtxとdistを保存したくなると思います。
    mat構造体はymlファイルに書き出すことができます。

    書き出しはこんな感じで。

        FileStorage fs("/projects/calibration.yml", FileStorage::WRITE);
        fs << "mtx" << mtx;
        fs << "dist" << dist;
        fs.release();

    読み出しはこんな感じで

        FileStorage fs("/projects/calibration.yml", FileStorage::READ);
    
        Mat _camera_mtx, _camera_dist;
        fs["mtx"] >> _camera_mtx;
        fs["dist"] >> _camera_dist;
        fs.release();

     

    この辺が参考になります

    http://www.swarthmore.edu/NatSci/mzucker1/opencv-2.4.10-docs/doc/tutorials/calib3d/camera_calibration/camera_calibration.html

    http://docs.opencv.org/3.1.0/dc/dbb/tutorial_py_calibration.html#gsc.tab=0

    http://opencv.jp/sample/camera_calibration.html

    http://opencv.jp/opencv-2.1/cpp/camera_calibration_and_3d_reconstruction.html?highlight=calibratecamera#calibrateCamera

  • STM32 USARTを使う。受信

    前前回はGPIのAFを設定して前回は送信をやりました。
    そしてやっと今回で受信です。
    やりたいのは「z」という文字を受け取ったら「b」を送るというものです。

    受信フラグ

    実は難しいことは前回まででほとんどやってしまったので今回は楽です。

    受信が完了したらSRレジスタのRXNEビットが1になるので、1になったらDRレジスタを読み出せば受信したデータが読めます。

    #include "stm32f4xx.h"
    
    static void SetSysClock(void);
    
    void GPIO_init(void)
    {
        RCC-> AHB1ENR      |= RCC_AHB1ENR_GPIOAEN;
    
        GPIOA-> MODER      = 0x00000000;
        GPIOA-> PUPDR      = 0x00000000;            // no pullup down
    
         // PA2のAF7がUSART2_TX
        GPIOA-> AFR[0]     = 0;
        GPIOA-> AFR[0]     |= GPIO_AF7_USART2 << 8;
        GPIOA-> MODER      |= 0b10 << 4;            // PA2=AF
    
         // PA3のAF7がUSART2_RX
        GPIOA-> AFR[0]     |= GPIO_AF7_USART2 << 12;
        GPIOA-> MODER      |= 0b10 << 6;            // PA3=AF
    }
    
    void UART_init( void)
    {
        RCC-> APB1ENR |= RCC_APB1ENR_USART2EN;
    
        USART2-> BRR = (22 << 4) + 13;
        USART2-> CR1 = USART_CR1_UE | USART_CR1_TE | USART_CR1_RE;
    }
    
    void UART_send(uint32_t character)
    {
        USART2->DR = character;
        while ((USART2->SR & USART_SR_TXE) == 0);
    }
    
    int main(void)
    {
    	SetSysClock();
    	GPIO_init();
    	UART_init();
    
    	UART_send('a');
    
        while(1) {
        	if(USART2->SR & USART_SR_RXNE) {
        		if (USART2->DR == 'z') {
        			UART_send('b');
        		}
        	}
        }
    }
    
    
    
    
    #define PLL_M      8
    #define PLL_N      336
    #define PLL_P      2
    #define PLL_Q      7
    #define PLLI2S_N   258
    #define PLLI2S_R   3
    
    static void SetSysClock(void)
    {
      __IO uint32_t StartUpCounter = 0, HSEStatus = 0;
    
      RCC->CR |= ((uint32_t)RCC_CR_HSEON);
    
      do {
        HSEStatus = RCC->CR & RCC_CR_HSERDY;
        StartUpCounter++;
      } while((HSEStatus == 0) && (StartUpCounter != HSE_STARTUP_TIMEOUT));
    
      if ((RCC->CR & RCC_CR_HSERDY) != RESET) {
        HSEStatus = (uint32_t)0x01;
      } else {
        HSEStatus = (uint32_t)0x00;
      }
    
      if (HSEStatus == (uint32_t)0x01) {
        RCC->APB1ENR |= RCC_APB1ENR_PWREN;
        PWR->CR |= PWR_CR_VOS;
    
        RCC->CFGR |= RCC_CFGR_HPRE_DIV1;
    
        RCC->CFGR |= RCC_CFGR_PPRE2_DIV2; // APB2は84Mhz
    
        RCC->CFGR |= RCC_CFGR_PPRE1_DIV4;	// APB1は 42Mhz
    
        RCC->PLLCFGR = PLL_M | (PLL_N << 6) | (((PLL_P >> 1) -1) << 16) | (RCC_PLLCFGR_PLLSRC_HSE) | (PLL_Q << 24);
    
        RCC->CR |= RCC_CR_PLLON;
    
        while((RCC->CR & RCC_CR_PLLRDY) == 0);
    
        FLASH->ACR = FLASH_ACR_ICEN |FLASH_ACR_DCEN |FLASH_ACR_LATENCY_5WS;
    
        RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_SW));
        RCC->CFGR |= RCC_CFGR_SW_PLL;
    
        while ((RCC->CFGR & (uint32_t)RCC_CFGR_SWS ) != RCC_CFGR_SWS_PLL);
    
      } else {
      	GPIOD->ODR |= 1<<14;
      }
    
      RCC->CFGR &= ~RCC_CFGR_I2SSRC;
      RCC->PLLI2SCFGR = (PLLI2S_N << 6) | (PLLI2S_R << 28);
      RCC->CR |= ((uint32_t)RCC_CR_PLLI2SON);
      while((RCC->CR & RCC_CR_PLLI2SRDY) == 0);
    }
    

    動かすと起動直後にa送ってきます。キーボードで小文字のzを送ってみましょう。
    そうするとbという文字が受信できると思います。stm_usart_recv

    前回のプログラムと違うところは、
    まずCR1レジスタの受信有効化ビットREを1にしています。

        USART2-> CR1 = USART_CR1_UE | USART_CR1_TE | USART_CR1_RE;

    そしてメインループのwhile(1)で受信を待ちます。
    受信データが来て読み出せるようになったらSRレジスタのRXNEビットが1になりますから、1になるのを待っているわけです。

        	if(USART2->SR & USART_SR_RXNE) {
        		if (USART2->DR == 'z') {
        			UART_send('b');
        		}
        	}

    そして、1になったら読み出し、zかどうかをチェックします。
    zだったらbを送信しています。

    ちなみにRXNEビットはDRレジスタからデータを読みだした段階で自動的に0に戻ります。便利ですね。

    DRとは

    前回送信にもDRレジスタを。受け取ったデータの読み取りにもDRをつかいました。
    実はDRレジスタというのは存在しなくて、これは書き込みと読み取りで別のレジスタを見るようになっている特殊なレジスタです。stm_usart_dr

    リファレンスマニュアルにはこのようになっています。
    本当は送信用のTDRと受信用のRDRがあるのですが、DRに書き込むとTDRに、DRを読むとRDRから読み込むことになります。

    USARTの他の機能

    STMのUSARTには他にもいろいろ機能があります。
    まず、当然ながら受信したら割り込みといったことは出来ます。
    それ以外にも複数マイコンとUSARTでつなぐLINという機能や赤外線通信に使えるIrDA。
    などなど、いろいろな機能があります。
    今回はシンプルな送受信をしましたが、リファレンスマニュアルを見ながらいろいろ使ってみましょう。

    まとめ

    • CR1のREビットを1にすると受信できるようになる
    • 受信するとSRレジスタのRXNEが1になり、データはDRから読み出せる。
    • 読みだすと自動的にRXNEは0に戻る

     

  • STM32 USARTを使う。通信速度の設定と送信

    前回USARTの配線まで済ませましたので、今回はプログラムを書いてみましょう。

    目標とするプログラムは

    起動したら「a」と一文字送り、逆に「z」という文字を受け取ったら「b」という文字を送り返す

    にしましょう。

    文字を送信するには

    送信は受信より簡単です。受信はいつ起こるか分かりませんが、送信は自分からするものなので簡単なんです。

    送信するにはUSARTのDRレジスタにデータを書き込んでむだけでOKです。すると、USARTの別の送信用レジスタにデータがセットされて、決められた通信速度に合わせて順番にTXピンからデータが1ビットずつ出てきます。

    通信速度を決める

    UARTペリフェラルはタイマーの時みたいにペリフェラルをONにしちゃえばすぐ使えます。
    ただ、通信速度を決めておかないといけません。
    もちろん他にも設定はあるのですが(パリティチェックやストップビットの長さなど)
    デフォルトで、とりあえずでよく使う設定

    • 8bit
    • パリティチェックなし
    • ストップビットは長さ1
    • クロック端子なし

    になってますから、パソコン側をこれに合わせるとして速度だけ気にしましょう。

    今回は115200bpsにしてみます。
    速度を決めるのはUSARTのBRRレジスタの値です。ここの値を大きくすると遅くなります。
    じゃあ実際にどの値にすれば115200になるかというと、

    stm_usart_function

    こういう関係になっています。
    左側のTx/Rx baudは通信速度で、fckはクロックです。
    当たり前ですが、CPUのクロックと関係があります。
    ただし、USART2はAPB1のクロックで動くので今は48Mhzとなります。
    下にあるOVER8というのはオーバーサンプリングというもので、16bit(デフォルト)なら0で、8bitにしたら1となります。
    そしてUSARTDIVというのがBRRレジスタの値です。
    なので、115200にするにはUSARTDIVは
    USARTDIV=42000000/(8×(2-0)×115200)
    により22.796458,,,,
    となります。

    ちなみにこんな計算しなくてもよく使いそうな通信速度似合わせるためのUSARTDIVの値はリファレンスマニュアルに載っています。

    stm_usart_brr

    これによると42Mhzの時に1115200bpsにするには22.8125 というのが載っています。
    僕らが計算した値と若干違いますね。何ででしょうか。

    まず、BRRレジスタは固定少数のレジスタす。

    stm_usart_brrr

    下4bitが小数点以下。15-4bitの12bitが実数部となります。
    固定少数というのは意外と簡単で、
    まず XX.YY という少数より大きな実数部と、少数以下の仮数部に別れます。
    XXの方はBRRレジスタの15-4bitの値がそのまま使われます。
    問題は少数の方ですが、これはBRRレジスタには4bitあります。つまり0~15の値が入るわけです。
    これでどうやって少数を表すかというと、1を16で割った値を基礎として、それが何個なのかで表現します。難しいですがこういうことです

    XX + (4bitの仮数部の値)/16

    仮数部が0のときは少数以下は0となります。
    つぎに、1の時には1/16の値となり、2の時には2/16の値となるわけです。
    そして一番大きな15の時には15/16の値となるわけです。

    では、22.8125の0.8125は何なのかというと13/16がちょうど0.8125となります。
    つまりさっきの表は「本当の値とはちょっとずれるけど、BRRレジスタにセットできる最も近い値」を書いてくれているわけです。

    これで115200に通信速度をセットするにはBRRに22.8125をセットすれば良くて、そのためには

    USART2-> BRR = (22 << 4) + 13;

    このようにセットすれば良いということがわかります。

    誤差

    ちなみに、本当なら22.796458,,,,をセットしなきゃいけないのに22.8125をセットしています。つまり通信速度が若干違うわけです。
    どれぐらいズレてるかはさっきの表でもError%として載っています。
    BRRを使えば好きな通信速度に出来るわけですが、欲しい通信速度ぴったりにはなかなか合わないわけです。
    このズレは1バイト2バイト送るぐらいでは問題になりません。通信のはじまりと終わりごろでは若干ずれますが、ズレは小さいので受け取る側が間違うほどではないからです。
    ただし、連続でデータを送るときには注意が必要です。だんだんズレていくのがなんとなくわかると思います。

    実際のプログラム

    それでは、起動したら「a」という文字を送るプログラムを書いてみましょう。

    #include "stm32f4xx.h"
    
    static void SetSysClock(void);
    
    void GPIO_init(void)
    {
        RCC-> AHB1ENR      |= RCC_AHB1ENR_GPIOAEN;
    
        GPIOA-> MODER      = 0x00000000;
        GPIOA-> PUPDR      = 0x00000000;            // no pullup down
    
         // PA2のAF7がUSART2_TX
        GPIOA-> AFR[0]     = 0;
        GPIOA-> AFR[0]     |= GPIO_AF7_USART2 << 8;
        GPIOA-> MODER      |= 0b10 << 4;            // PA2=AF
    
         // PA3のAF7がUSART2_RX
        GPIOA-> AFR[0]     |= GPIO_AF7_USART2 << 12;
        GPIOA-> MODER      |= 0b10 << 6;            // PA3=AF
    }
    
    void UART_init( void)
    {
        RCC-> APB1ENR |= RCC_APB1ENR_USART2EN;
    
        USART2-> BRR = (22 << 4) + 13;
        USART2-> CR1 = USART_CR1_UE | USART_CR1_TE;
    }
    
    int main(void)
    {
        SetSysClock();
        GPIO_init();
        UART_init();
    
        USART2-> DR = 'a';
    
        while(1);
    }
    
    
    #define PLL_M      8
    #define PLL_N      336
    #define PLL_P      2
    #define PLL_Q      7
    #define PLLI2S_N   258
    #define PLLI2S_R   3
    
    static void SetSysClock(void)
    {
      __IO uint32_t StartUpCounter = 0, HSEStatus = 0;
    
      RCC->CR |= ((uint32_t)RCC_CR_HSEON);
    
      do {
        HSEStatus = RCC->CR & RCC_CR_HSERDY;
        StartUpCounter++;
      } while((HSEStatus == 0) && (StartUpCounter != HSE_STARTUP_TIMEOUT));
    
      if ((RCC->CR & RCC_CR_HSERDY) != RESET) {
        HSEStatus = (uint32_t)0x01;
      } else {
        HSEStatus = (uint32_t)0x00;
      }
    
      if (HSEStatus == (uint32_t)0x01) {
        RCC->APB1ENR |= RCC_APB1ENR_PWREN;
        PWR->CR |= PWR_CR_VOS;
    
        RCC->CFGR |= RCC_CFGR_HPRE_DIV1;
    
        RCC->CFGR |= RCC_CFGR_PPRE2_DIV2; // APB2は84Mhz
    
        RCC->CFGR |= RCC_CFGR_PPRE1_DIV4;	// APB1は 42Mhz
    
        RCC->PLLCFGR = PLL_M | (PLL_N << 6) | (((PLL_P >> 1) -1) << 16) | (RCC_PLLCFGR_PLLSRC_HSE) | (PLL_Q << 24);
    
        RCC->CR |= RCC_CR_PLLON;
    
        while((RCC->CR & RCC_CR_PLLRDY) == 0);
    
        FLASH->ACR = FLASH_ACR_ICEN |FLASH_ACR_DCEN |FLASH_ACR_LATENCY_5WS;
    
        RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_SW));
        RCC->CFGR |= RCC_CFGR_SW_PLL;
    
        while ((RCC->CFGR & (uint32_t)RCC_CFGR_SWS ) != RCC_CFGR_SWS_PLL);
    
      } else {
      	GPIOD->ODR |= 1<<14;
      }
    
      RCC->CFGR &= ~RCC_CFGR_I2SSRC;
      RCC->PLLI2SCFGR = (PLLI2S_N << 6) | (PLLI2S_R << 28);
      RCC->CR |= ((uint32_t)RCC_CR_PLLI2SON);
      while((RCC->CR & RCC_CR_PLLI2SRDY) == 0);
    }
    

    このプログラムを書き込んでFTDIをつないだパソコンで115200bpsでUSARTを受信できるようにするとSTMマイコンをリセットするごとにaという文字を受信できると思います。

    stm_usart_mac

    プログラムを見てみましょう。

    GPIO_init();

    の中でやっているのは前回やったAlternativeFunctionの設定です。
    次にUART_initですが、これがUART2ペリフェラルの有効化と速度の設定をしています。

    void UART_init( void)
    {
        RCC-> APB1ENR |= RCC_APB1ENR_USART2EN;
    
        USART2-> BRR = (22 << 4) + 13;
        USART2-> CR1 = USART_CR1_UE | USART_CR1_TE;
    }

    まずは、恒例のクロックの有効化です。
    そしてBRRレジスタに値を入れて通信速度の設定をしています。
    そしてコントロールレジスタCR1のUEビットとTEビットを1にしています。
    UEがUARTの有効化ビットで、これを1にするとUSARTが使えるようになります。
    また、TEビットが送信の有効化で、1にすると送信できるようになります。

    そして、実際の送信は

        USART2-> DR = 'a';

    ここで行われています。
    DRレジスタに送信したいデータをセットします。
    セットするだけで自動的に送信用のシフトレジスタにデータが送られて、1bitずつ決められた速度にあわせて送信されていきます。

    送信中

    本当なら他のデータの送信中はDRレジスタにデータを入れてTEビットを1にしても送信されません。さっきのは起動直後で明らかにデータは送信中じゃないからできることです。

    DRレジスタにデータをセットできるかどうかはSRレジスタのTXEビットを見ればわかります。
    TXEビットは前回DRレジスタにセットしたデータが送信用のシフトレジスタに移動して、DRレジスタに新しいデータをセットできることを示します。
    連続送信できるようにしたプログラムを書いてみます。

    #include "stm32f4xx.h"
    
    static void SetSysClock(void);
    
    void GPIO_init(void)
    {
        RCC-> AHB1ENR      |= RCC_AHB1ENR_GPIOAEN;
    
        GPIOA-> MODER      = 0x00000000;
        GPIOA-> PUPDR      = 0x00000000;            // no pullup down
    
         // PA2のAF7がUSART2_TX
        GPIOA-> AFR[0]     = 0;
        GPIOA-> AFR[0]     |= GPIO_AF7_USART2 << 8;
        GPIOA-> MODER      |= 0b10 << 4;            // PA2=AF
    
         // PA3のAF7がUSART2_RX
        GPIOA-> AFR[0]     |= GPIO_AF7_USART2 << 12;
        GPIOA-> MODER      |= 0b10 << 6;            // PA3=AF
    }
    
    void UART_init( void)
    {
        RCC-> APB1ENR |= RCC_APB1ENR_USART2EN;
    
        USART2-> BRR = (22 << 4) + 13;
        USART2-> CR1 = USART_CR1_UE | USART_CR1_TE;
    }
    
    void UART_send(uint32_t character)
    {
        USART2-> DR = character;
        while ((USART2->SR & USART_SR_TXE) == 0);
    }
    
    int main(void)
    {
    	SetSysClock();
    	GPIO_init();
    	UART_init();
    
    	UART_send('a');
    	UART_send('a');
    	UART_send('a');
    
    
        while(1);
    }
    
    
    
    
    #define PLL_M      8
    #define PLL_N      336
    #define PLL_P      2
    #define PLL_Q      7
    #define PLLI2S_N   258
    #define PLLI2S_R   3
    
    static void SetSysClock(void)
    {
      __IO uint32_t StartUpCounter = 0, HSEStatus = 0;
    
      RCC->CR |= ((uint32_t)RCC_CR_HSEON);
    
      do {
        HSEStatus = RCC->CR & RCC_CR_HSERDY;
        StartUpCounter++;
      } while((HSEStatus == 0) && (StartUpCounter != HSE_STARTUP_TIMEOUT));
    
      if ((RCC->CR & RCC_CR_HSERDY) != RESET) {
        HSEStatus = (uint32_t)0x01;
      } else {
        HSEStatus = (uint32_t)0x00;
      }
    
      if (HSEStatus == (uint32_t)0x01) {
        RCC->APB1ENR |= RCC_APB1ENR_PWREN;
        PWR->CR |= PWR_CR_VOS;
    
        RCC->CFGR |= RCC_CFGR_HPRE_DIV1;
    
        RCC->CFGR |= RCC_CFGR_PPRE2_DIV2; // APB2は84Mhz
    
        RCC->CFGR |= RCC_CFGR_PPRE1_DIV4;	// APB1は 42Mhz
    
        RCC->PLLCFGR = PLL_M | (PLL_N << 6) | (((PLL_P >> 1) -1) << 16) | (RCC_PLLCFGR_PLLSRC_HSE) | (PLL_Q << 24);
    
        RCC->CR |= RCC_CR_PLLON;
    
        while((RCC->CR & RCC_CR_PLLRDY) == 0);
    
        FLASH->ACR = FLASH_ACR_ICEN |FLASH_ACR_DCEN |FLASH_ACR_LATENCY_5WS;
    
        RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_SW));
        RCC->CFGR |= RCC_CFGR_SW_PLL;
    
        while ((RCC->CFGR & (uint32_t)RCC_CFGR_SWS ) != RCC_CFGR_SWS_PLL);
    
      } else {
      	GPIOD->ODR |= 1<<14;
      }
    
      RCC->CFGR &= ~RCC_CFGR_I2SSRC;
      RCC->PLLI2SCFGR = (PLLI2S_N << 6) | (PLLI2S_R << 28);
      RCC->CR |= ((uint32_t)RCC_CR_PLLI2SON);
      while((RCC->CR & RCC_CR_PLLI2SRDY) == 0);
    }
    

    この中で前回と違うのは送信後にTXEビットのチェックをしているところです。

        USART2-> DR = character;
        while ((USART2->SR & USART_SR_TXE) == 0);

    DRレジスタに新しいデータを入れるとTXEビットは0となります。
    送信用シフトレジスタにデータを転送中というのが0の意味です。
    そして転送が完了してDRにデータを入れれるようになると1になりますので、1になるまでwhile文で待機するわけです。
    ちなみにこのTXEは新しいデータをDRレジスタに入れるとまた0に戻ります。

    無駄にwhile文で待つことになりますが、簡易的にはこれで大丈夫です。
    本当ならDMAを使ったり、TXEビットが1になるきっかけで割りこませることも出来ますので、それを使うと無駄にCPUで待機するなんてことはしなくても良くなります

    まとめ

    • DRレジスタに送りたいデータを入れるだけで送信が始まる
    • 通信速度は固定少数のBRRレジスタで決まり、リファレンスによく使う値が載ってる
    • CR1のUEビットを1にすると送信できるようになる
    • DRレジスタに新しいデータを入れれるかはTXEビットをチェックする
  • STM32 USARTを使う。ピンのAlternativeFunctionを設定する

    さて、前回はタイマという内部的な話でしたが、今回はシリアル通信をやってみましょう。

    シリアル通信とは?

    シリアル通信の内容はちょっと個々では省きます。知ってる人向けね。
    一応概要だけ書くと、元々は”ちょっと離れた”マイコン同士が通信するための通信方式で、最低でも送信・受信・GNDの3本をつなげばお互いに通信できます。

    もちろんこれ用のペリフェラルがあって、STM32F407にはUSARTペリフェラルというのが入っています。
    これを使えばピンの設定や通信速度の設定をするだけで実際の通信がどうなっているのか気にしなくても送信と受信ができます。
    今回はこれを使ってパソコンと通信してみましょう

    配線する

    今回はソフトだけでは作れなくて、回路も必要になります。
    STMと通信するためにパソコンからUSART通信できるFTDIケーブルをつかいます。
    これを使えばパソコンのソフトからUSARTを送受信できます。

    stmftdi
    出典: 秋月電子

    あとはSTMのどこに繋げばいいのでしょうか。
    普通ならUSARTの送信をするTXと受信をするRXのピンがあるはずですが、どこにUSARTのピンがあるんでしょうか。

    GPIOのAlternativeFunction

    実はUSART専用のピンはなくて、GPIOとの共有ピンになっていてGPIOの設定をすることでUSARTピンとして切り替えて使います。
    切り替えると当然ながらGPIOとしては入力も出力も使えなくなるのですが、USARTのピンとして使えるようになります。
    ただ、「じゃあPortAの0をTx(送信)にする!」みたいにどのピンでもUSARTのピンに出来るわけではありません。
    まず、ピン1つに16種類までの機能が割り振られていて、その16種類のどれかに切り替えるという感じになります。
    GPIOのこの機能の切り替えをAlternativeFunctionというのですが、
    例えばGPIOAの0番ピンはAlternativeFunction1として設定するとタイマー用のピンとして使えて、AlternativeFunction8にするとUSART4のTXとして使えます。

    stm_uart_af
    AlternativeFunctionのどれが何なのかはハードウェアリファレンスに全て書いてあります。

    stm32f4_uart

    なので、USARTの送信を使いたい!と思ったらUSART_TXというのがUSARTの送信なのでこれがAFの中に入っているピンを探さないといけないです。PortAなんだったら、PA2のAF7がUSART2のTX(USARTは1~6まである)ですし、PA9のAF7もそうです。他にもありますね。

    いろいろありますが、今回は僕がLEDなど他のものに何もつながってないのを確認したPA2とPA3をUSARTのTXとRXに切り替えて使いましょう。

    配線する

    先に配線しちゃいましょう。
    PA2がTXでPA3がRXというのが決まったので、FTDIとつなぎます。

    stm_uart_connect

    こんな風に3本繋げばOKです。
    僕はピンヘッダを使ってこんな風にしました。

    stm_uart_photo

    これで回路の準備は完了です。
    プログラムに入りましょう。

    UARTにピンを切り替える

    では、実際にピンをUSARTに切り替えてみましょう。
    GPIOペリフェラルで設定するだけです。
    やることはモードをAFに切り替えて、AFの中でもUSARTがあるAF7に切り替えるだけです。
    GPIOを初めて使った時の設定を思い出してみましょう。
    AFへの切り替えはMODERレジスタです。
    Screen Shot 2016-01-05 at 22.16.17

    ピン1つ1つを入力にしたり出力にしたり出来るれジスタでしたね。
    これの設定 10 がAlternative functionというもので、入力でも出力でもなく、他のペリフェラルに切り替えるというものです。
    PA2とPA3をAlternativeFunction(10)にします。

    次にAlternativeFunctionのどれなのかを選択します。
    使うのはGPIOのAFRLかAFRHレジスタです。
    stm_uart_af

    4bitで指定します。
    AFRLとAFRHの2つのレジスタがあるのは、1つのピンに設定が4bitもあり、レジスタに入りきらなかったので2つのレジスタに分かれています。
    AFRLは ポートの0〜7
    AFRHは ポートの8~16
    に関する設定が入っています。
    今回はPA2とPA3をAF7にしたいので、AFRLのAFRL2とAFRL3のところを 0111にすればOKです。

    ここまでの設定をプログラムにするとこうなります

    GPIOA-> MODER      = 0;
    GPIOA-> AFR[0]     = 0;
    
    GPIOA-> AFR[0]     |= GPIO_AF7_USART2 << 8;
    GPIOA-> MODER      |= 0b10 << 4;
    
    GPIOA-> AFR[0]     |= GPIO_AF7_USART2 << 12;
    GPIOA-> MODER      |= 0b10 << 6;

    まずAFRLとAFRHはAFR[0]とAFR[1]としてアクセスできるように用意されています。
    また、AF7は0111という値ですが、
    GPIOA->AFR[0] |= 0b0111 << 8;
    としなくても見やすくなるようにGPIO_AF7_USART2として既に用意されていますのでこれを使います。

    いよいよUSARTを使う

    ここまで長かったですが、USARTをピンにつないだだけです。
    ここからやっとUSARTを使います。
    ちょっと長くなったので次回やりましょう。