このページで示したロボットの倒立振子の検討の記録の続きのページです。
なお、EEPROMの内容は、このページで作成した内容と同じですが、その処理は使っていません。
(別途のRAMにSPI受信処理を埋め込む方式で検討しています)
で示したように振動を止めることができませんでした。| 項目 | Master | Slave |
|---|---|---|
| クロック | 生成する(出力端子) | 受け取る(受信端子) |
| CS制御 | 制御する(出力端子) | 選択される(受信端子) |
| 通信開始 | できる | できない |
| 通信速度 | 決定する | 従う |
| MOSI(Master Out Slave In) | 送信 | 受信 |
| MISO(Master In Slave Out) | 受信 | 送信 |
| CN10 端子番号 | PIC32MXの端子番号 | 変更前のUART1 | 変更後のSPI(スレーブ) | 拡張基板内 コネクタ番号 | Raspberry ピン番号とSPI端子(マスター) |
|---|---|---|---|---|---|
| 1 | 18 RB9 | RTS(出力) | RB9を SS2 イネーブル入力端子にPPSで変更 | 1 | Pin24 GPIO8 (CE0) |
| 2 | 28 | Vcc(3.3v) | Vcc(3.3v) | ||
| 3 | 27 | GND | GND | 2 | GND |
| 4 | 12 RA4 | RX(入力) | RA4をSDI2(入力)にPPSで変更 | 3 | Pin19 GPIO10 (MOSI) |
| 5 | 11 RB4 | TX(出力) |
RB4を単なる入力端子に変更 SCLK2 (PIC32のRB15をPPSで変更した26番ピンにジャンパー接続) | 4 | Pin23 GPIO11 (SCLK) |
| 6 | 17 RB8 | CTS(入力) | RB8をSDO2(出力)にPPSで変更 | 5 | Pin21 GPIO9 (MISO) |
|
|
[Raspberry Pi 3 Model A+]と[UMEHOSHI ITA]を乗せたモータ付き台車で、 拡張ボードでSPI用のコネクタを追加した回路結線図&配置図 ![]() |
suzuki@raspberrypi:~ $ ls /dev/spidev* /dev/spidev0.0 /dev/spidev0.1 suzuki@raspberrypi:~ $ suzuki@raspberrypi:~ $ lsmod | grep spi spidev 20480 0 spi_bcm2835 20480 0 suzuki@raspberrypi:~ $/dev/spidev0.0と /dev/spidev0.1は、それぞれが SPIバス0のCE0がGPIO8(Pin24)で使えることと、CE1のGPIO7(Pin26)が使えることを示している。
import spidev
import time
spi = spidev.SpiDev() # SPI操作オブジェクト生成
spi.open(0, 0) # bus=0 device=0 (CE0) でSPIオープン
spi.max_speed_hz = 7812500 # 7.8 MHzSPIクロック
spi.mode = 0 # クロック信号(SCLK)はLow待機、LowからHighに立ち上がる瞬間でデータを読み取り
v=0 # 送信データ(0,1,2・・・・と増やした値を Enterキー操作で少しずつ送る)
while True:
send_data = [v] # 例えばビッグエンディアンで0x15AFを送るなら[0x15, 0xAF]のリストで送る。決まってないが一般の受信側のオーダーで送る。
input("Enter>") # Enterで送信へ進む
print("Send :", send_data)
recv_data = spi.xfer2(send_data)# 送受信
# print("Send :", send_data) # (send_dataの内容は、受信データ置き換わる挙動もある)
print("Recv :", recv_data)
time.sleep(0.1)
v += 1 # 送信データの後進
#include <xc.h>
#include <stdio.h>
#include <stdlib.h>
#include "common.h"
#include <proc/p32mx270f256b.h>
#include <sys/attribs.h>//割込み関係の定義
int spi_out_v = 0x0ff; // SPI出力用データ
/*
PIC32MX270F256B-50I/SP (SP は 28pin SPDIP パッケージ)のUART1を、
18番ピンをRTS(出力)、12ピンをRX(入力)、11ピンをTX(出力)、17ピンRB8をCTS(入力)
として使っているが、 これを、次のSPI2のスレーブモードに設定し直す
18番ピンをCS GPIO入力、12ピンをSDI2(入力)、11ピンRB5をSDO2(出力)、17ピンをデジタル入力
そのためのPPSは「ピン配線設定」関数。つまり **「配線設定」**です。
*/
void PPS_Init_SPI2_Slave(void)
{
/* ---------- UART1のフロー制御(CTS/RTS)を解除 ---------- */
U1MODEbits.UEN = 0b00; // UART1はTX/RXのみ使用(CTS/RTSピンを開放)
U1MODEbits.ON = 0; // UART1自体を使わない場合は停止
ODCBbits.ODCB9 = 0; // RB9デフォルト設定のオープンドレイン無効
CNPUBbits.CNPUB9 = 0;//CNPUB (Change Notice Pull-up B) レジスタの第9ビットを0
//(これにより、RB9ピンの内部プルアップ抵抗を無効)
CNPDBbits.CNPDB9 = 0;//CNPDB (Change Notice Pull-down B)レジスタの第9ビットを 0
//(これにより、RB9ピンの内部プルダウン抵抗を無効)
LATBbits.LATB9 = 1; // RB9のRTS(出力)をHiにする
TRISBbits.TRISB9 = 1;// RB9を入力ピンにする (基板のパターンが繋がっているため)
LATBbits.LATB4 = 1; // RB4を1にする。(必要ないかも?)
TRISBbits.TRISB4 = 1; // RB4を入力用に指定
ODCBbits.ODCB4 = 0; // デフォルト設定のオープンドレイン無効
CNPUBbits.CNPUB4 = 0;//内部プルアップ抵抗を無効
CNPDBbits.CNPDB4 = 0;//内部プルダウン抵抗を無効
TRISAbits.TRISA4 = 1; // RA4 (pin12) SDI2入力へ
ANSELBbits.ANSB15 = 0; // 26番ピンのRB15をデジタルモードに設定
TRISBbits.TRISB15 = 1; // スレーブの場合は入力、マスターなら0(出力)に設定
TRISBbits.TRISB8 = 0; // RB8を出力用に指定
CNPUBbits.CNPUB8 = 0;//内部プルアップ抵抗を無効
/* ---------- PPS設定アンロック ---------- */
SYSKEY = 0xAA996655; // 書き込み保護解除キー1
SYSKEY = 0x556699AA; // 書き込み保護解除キー2
CFGCONbits.IOLOCK = 0; // PPSレジスタのロック解除
/* ---------- 入力PPS設定 ---------- */
SS2R = 0b0100; // SS2入力をRB9へ
SDI2R = 0b0010; // SDI2入力をRA4へ
/* ---------- 出力PPS設定 ---------- */
RPB8R = 0b0100; // SDO2へ
asm("NOP");
/* ---------- PPS再ロック ---------- */
CFGCONbits.IOLOCK = 1;
SYSKEY = 0; // 保護再有効
}
/*
SPI2に関する周辺機能設定の初期設定
* クロック極性、スレーブ設定、割り込みなど、ここだけ変更する可能性を考慮して
* 「ピン配線設定」のPPS_Init_SPI2_Slaveを別関数にしている。
*/
void Init_SPI2_Slave(void)
{
_RB5 = 1; // D1 LED の初期点灯設定
PPS_Init_SPI2_Slave(); // PIC32MX270F256B-50I/SP のSPI2スレーブ用端子にピン割り当てを変更
SPI2CON = 0; // SPI2制御レジスタ初期化
SPI2STAT = 0; // ステータス初期化
/* ---------- SPIモード設定 ---------- */
SPI2CONbits.MSTEN = 0; // 0 = スレーブモード
SPI2CONbits.SSEN = 1; // SS有効 Slave Select Enable (Slave mode) bit
SPI2CONbits.MODE16 = 0; // 8bit通信
SPI2CONbits.MODE32 = 0; // 32bit通信無効
/* SPIモード0設定 (多くのマスターが使用) */
SPI2CONbits.CKP = 0; // クロックアイドルLow
SPI2CONbits.CKE = 1; // クロック立上りでデータ更新
SPI2CONbits.SMP = 0; // スレーブでは通常0
SPI2BUF; // バッファをダミー読み出し
SPI2STATbits.SPIROV = 0; // 受信オーバーフロークリア
// SPI用Enhanced Buffer(ENHBUF)を0(デフォルト)に指定(重要)
SPI2CONbits.ENHBUF = 0;
SPI2CONbits.SRXISEL = 1; //01受信バッファが1byteに指定
SPI2CONbits.ON = 1; // SPI2モジュール有効
SPI2BUF = spi_out_v;// 最初の送信データのセット(重要)
}
void timer4() // SPIスレーブ のポーリング処理の確認用タイマー処理
{
static int count = 0;
if(++count % 1000 != 0) return;
// count が 1000の倍数時だけ(0.5秒ごと)に以下を実行
_RB5 = ! _RB5;// このタイマーの動作確認用の D1 LED 点滅の出力を反転
if (SPI2STATbits.SPIRBF == 1) { // SPI受信バッファが一杯になったら
// ここを通るなら、SPI受信ができている(配線とSPI設定はOK)
uint8_t rxData = SPI2BUF; // 受信データを読み出す(これでSPIRBFが0に戻る)
_clear_beep_code();
_debug_hex8(0, rxData , 1); // 受信データ確認
spi_out_v-=1;
while(SPI2STATbits.SPITBE == 0); // 送信バッファ空待ち
SPI2BUF = spi_out_v;// 次の送信データのセット
}
if (SPI2STATbits.SPIROV == 1) { // エラーフラグが立っていないか
SPI2STATbits.SPIROV = 0; // 一旦クリア
_debug_hex16(0, SPI2BUF, 1);
}
}
// UMEHOSHI ITA のRAMプログラムの設定希望プログラム(0x80005000番地より起動)
__attribute__((address( 0x80005000 ))) void start (void);
void start()
{
_clear_beep_code(); // _debug_hex? 関数利用の初期化
_UM_PTR_GOTO_BEEP = NULL; // debug_hex?の出力をループしない設定
Init_SPI2_Slave();// SPI2の初期化
// デフォルトで0.00005秒ごとに呼び出される関数に、SPIスレーブ のポーリング処理のtimer4を設定
_HANDLES[_IDX_TIMER_4_FUNC] = timer4;
T4CONbits.ON = 1;// timer4割込みオン(SPIスレーブ のポーリングの起動)
_RB5 = 0; // D1 LED の消灯
_debug_hex4(0,0x0f,1);// _debug_hex? の動作確認用
_send_string("start END\r\n");//起動関数終了の表示
}
上記はTimer4のインターバルタイマーの割り込みを使い、この繰り返しの中でSPIの受信を監視して、受信処理をする例です。
void __ISR(_SPI2_VECTOR, IPL3SOFT) SPI2_Handler(void)
{
((void (**)(void) )_PTR_HANDLERS)[_IDX_SPI2_FUNC]();
}
つまり、_PTR_HANDLERS)[_IDX_SPI2_FUNC]に、ユーザー作成の割り込み処理のポイントを記憶しておいて、それを実行させるコードです。#define _IDX_PPS_INIT_SPI2_SLAVE 70 // ★ SPI2のPPS 設定 PPS_Init_SPI2_Slave #define _PPS_Init_SPI2_Slave() (((void (**)(void) )_PTR_HANDLERS)[_IDX_PPS_INIT_SPI2_SLAVE]()) #define _IDX_INIT_SPI2_SLAVE 71 // ★ SPI2の初期設定 割り込みを含む初期化 Init_SPI2_Slave #define _Init_SPI2_Slave() (((void (**)(void) )_PTR_HANDLERS)[_IDX_INIT_SPI2_SLAVE]()) #define _IDX_SPI2_FUNC 72 // ★ SPI2割り込みで呼ぶ関数記憶用の添え字そして、上記の_PTR_HANDLERS[_IDX_PPS_INIT_SPI2_SLAVE]に記憶する次のデフォルト関数を次のように定義しています。
/*
PIC32MX270F256B-50I/SP (SP は 28pin SPDIP パッケージ)のUART1を、
18番ピンをRTS(出力)、12ピンをRX(入力)、11ピンをTX(出力)、17ピンRB8をCTS(入力)
として使っているが、 これを、次のSPI2のスレーブモードに設定し直す
18番ピンをCS GPIO入力、12ピンをSDI2(入力)、11ピンRB5をSDO2(出力)、17ピンをデジタル入力
そのためのPPSは「ピン配線設定」関数。つまり **「配線設定」**です。
*/
void PPS_Init_SPI2_Slave(void)
{
/* ---------- UART1のフロー制御(CTS/RTS)を解除 ---------- */
U1MODEbits.UEN = 0b00; // UART1はTX/RXのみ使用(CTS/RTSピンを開放)
U1MODEbits.ON = 0; // UART1自体を使わない場合は停止
ODCBbits.ODCB9 = 0; // RB9デフォルト設定のオープンドレイン無効
CNPUBbits.CNPUB9 = 0;//CNPUB (Change Notice Pull-up B) レジスタの第9ビットを0
//(これにより、RB9ピンの内部プルアップ抵抗を無効)
CNPDBbits.CNPDB9 = 0;//CNPDB (Change Notice Pull-down B)レジスタの第9ビットを 0
//(これにより、RB9ピンの内部プルダウン抵抗を無効)
LATBbits.LATB9 = 1; // RB9のRTS(出力)をHiにする
TRISBbits.TRISB9 = 1;// RB9を入力ピンにする (基板のパターンが繋がっているため)
LATBbits.LATB4 = 1; // RB4を1にする。(必要ないかも?)
TRISBbits.TRISB4 = 1; // RB4を入力用に指定
ODCBbits.ODCB4 = 0; // デフォルト設定のオープンドレイン無効
CNPUBbits.CNPUB4 = 0;//内部プルアップ抵抗を無効
CNPDBbits.CNPDB4 = 0;//内部プルダウン抵抗を無効
TRISAbits.TRISA4 = 1; // RA4 (pin12) SDI2入力へ
ANSELBbits.ANSB15 = 0; // 26番ピンのRB15をデジタルモードに設定
TRISBbits.TRISB15 = 1; // スレーブの場合は入力、マスターなら0(出力)に設定
TRISBbits.TRISB8 = 0; // RB8を出力用に指定
CNPUBbits.CNPUB8 = 0;//内部プルアップ抵抗を無効
/* ---------- PPS設定アンロック ---------- */
SYSKEY = 0xAA996655; // 書き込み保護解除キー1
SYSKEY = 0x556699AA; // 書き込み保護解除キー2
CFGCONbits.IOLOCK = 0; // PPSレジスタのロック解除
/* ---------- 入力PPS設定 ---------- */
SS2R = 0b0100; // SS2入力をRB9へ
SDI2R = 0b0010; // SDI2入力をRA4へ
/* ---------- 出力PPS設定 ---------- */
RPB8R = 0b0100; // SDO2へ
asm("NOP");
/* ---------- PPS再ロック ---------- */
CFGCONbits.IOLOCK = 1;
SYSKEY = 0; // 保護再有効
}
同様で、my_option.cに_PTR_HANDLERS[__IDX_PPS_INIT_SPI2_SLAVE]に記憶する次のデフォルト関数を次のように定義しています。
/*
SPI2に関する周辺機能設定の初期設定
* クロック極性、スレーブ設定、割り込みなど、ここだけ変更する可能性を考慮して
* 「ピン配線設定」のPPS_Init_SPI2_Slaveを別関数にしている。
*/
void Init_SPI2_Slave(void)
{
_RB5 = 1; // D1 LED の初期点灯設定
PPS_Init_SPI2_Slave(); // PIC32MX270F256B-50I/SP のSPI2スレーブ用端子にピン割り当てを変更
SPI2CON = 0; // SPI2制御レジスタ初期化
SPI2STAT = 0; // ステータス初期化
/* ---------- SPIモード設定 ---------- */
SPI2CONbits.MSTEN = 0; // 0 = スレーブモード
SPI2CONbits.SSEN = 1; // SS有効 Slave Select Enable (Slave mode) bit
SPI2CONbits.MODE16 = 0; // 8bit通信
SPI2CONbits.MODE32 = 0; // 32bit通信無効
/* SPIモード0設定 (多くのマスターが使用) */
SPI2CONbits.CKP = 0; // クロックアイドルLow
SPI2CONbits.CKE = 1; // クロック立上りでデータ更新
SPI2CONbits.SMP = 0; // スレーブでは通常0
SPI2BUF; // バッファをダミー読み出し
SPI2STATbits.SPIROV = 0; // 受信オーバーフロークリア
// SPI用Enhanced Buffer(ENHBUF)を0(デフォルト)に指定(重要)
SPI2CONbits.ENHBUF = 0;
SPI2CONbits.SRXISEL = 1; //01受信バッファが1byteに指定
/* ---------- 割り込み設定 ---------- */
IFS1bits.SPI2RXIF = 0; // 受信割り込みフラグクリア
IEC1bits.SPI2RXIE = 1; // SPI2受信割り込み許可
//IEC1bits.SPI2TXIE = 1; // SPI2送信割り込み許可
IEC1bits.SPI2EIE = 1; // SPI2エラー割り込み許可
IPC9bits.SPI2IP = 3; // 優先度(Priority)を 3 に設定 (1?7)
IPC9bits.SPI2IS = 0; // 副優先度(Sub-priority)を 0 に設定 (0?3)
SPI2CONbits.ON = 1; // SPI2モジュール有効
}
以上の関数定義は、my_sys.cのinit_handle_area関数内に、次のコード追加で行っています。
extern void PPS_Init_SPI2_Slave(void);
extern void Init_SPI2_Slave(void);
extern void SPI2_Slave_func(void);
handlers[_IDX_PPS_INIT_SPI2_SLAVE]=(void *)PPS_Init_SPI2_Slave; // SPI2利用のためのPPS設定
handlers[_IDX_INIT_SPI2_SLAVE]=(void *)Init_SPI2_Slave; // SPI2の初期化デファルト関数登録
handlers[_IDX_SPI2_FUNC]=(void *)SPI2_Slave_func; // SPI2受信割り込みデファルト
#include <xc.h>
#include <stdio.h>
#include <stdlib.h>
#include "common.h"
#include <proc/p32mx270f256b.h>
#include <sys/attribs.h>//割込み関係の定義
int spi_out_v = 0x0ff; // SPI出力用データ
void SPI2_Slave_func(void)// スレーブ用割り込み処理から呼ばれる
{
/* ---- 受信割り込み ---- */
if (IFS1bits.SPI2RXIF)// SPI2RXIF = 受信バッファにデータが入った
{
uint8_t data;
data = SPI2BUF; // SPI受信データを読む
// 読まないとSPIROV(オーバーフロー)が発生する
/* ここで data を処理する */
_clear_beep_code();
_debug_hex8(0, data, 1); // 受信データ確認
spi_out_v-=1;
while(SPI2STATbits.SPITBE == 0); // 送信バッファ空待ち
SPI2BUF = spi_out_v;// 次の送信データのセット
}
/* ---- 送信割り込み ---- */
/* PIC32(および多くのマイコン)において、「割り込みフラグ(IFS)」と「割り込み許可ビット(IEC)」は独立して動いているため
受信で割り込みがあると、送信割り込みを不許可にしても、「割り込みフラグ(IFS)」がセットされる。
(書かない方はよい。)
if (IFS1bits.SPI2TXIF)// SPI2TXIF = 送信バッファが空
{
SPI2BUF = 0x55; // 次の送信データを書く
// 書くと送信開始される
IFS1bits.SPI2TXIF = 0; // TX割り込みフラグクリア
}
*/
/* ---- エラー割り込み ---- */
if (IFS1bits.SPI2EIF)
{
SPI2STATbits.SPIROV = 0; // 受信オーバーフロー解除
IFS1bits.SPI2EIF = 0; // エラーフラグクリア
}
}
// UMEHOSHI ITA のRAMプログラムの設定希望プログラム(0x80005000番地より起動)
__attribute__((address( 0x80005000 ))) void start (void);
void start()
{
_clear_beep_code(); // _debug_hex? 関数利用の初期化
_UM_PTR_GOTO_BEEP = NULL; // debug_hex?の出力をループしない設定
_HANDLES[_IDX_SPI2_FUNC] = (void *)SPI2_Slave_func;// SPI割り込み関数の登録
((void (**)(void) )_PTR_HANDLERS)[_IDX_INIT_SPI2_SLAVE]();// SPI2の初期化
SPI2BUF = spi_out_v;// 最初の送信データのセット(重要)
_RB5 = 0; // D1 LED の消灯
_debug_hex4(0,0x0f,1);// _debug_hex? の動作確認用
_send_string("start END\r\n");//起動関数終了の表示
}
| SPI送信データの意味 | 右モータのPWMの データMSByte | 右モータのPWMの データLSByte | 左モータのPWMの データMSByte | 左モータのPWMの データLSByte |
|---|---|---|---|---|
| 上のデータを識別番号 | 0 | 1 | 2 | 3 |
import spidev
import time
def spi_send(spi: spidev.SpiDev, datas: list):
# spiで、listのbyte列を送る。 (送信成功で、Trueを返す)
mas_req_idx:int=0 # 要求されたデータの添え字
last_idx = len(datas)-1
fail_count=0
while True:
send_data = datas[mas_req_idx]
rec_data = spi.xfer2([send_data])[0] # 1byte送受信
print(f"send[{mas_req_idx}]:{send_data},receive: {rec_data}")
if rec_data > last_idx: return False # スレーブから想定外の受信
if rec_data == mas_req_idx: # 受信エラーと判断し、先頭から送り直す。
mas_req_idx = 0
fail_count+=1 # 失敗数カウント
if fail_count > 5: return False # 送信失敗
time.sleep(0.001) # チョット待つ
continue
if mas_req_idx == last_idx: return True # 送信成功終了
mas_req_idx = rec_data # 次のデータの添え字に更新
#
#
spi = spidev.SpiDev() # SPI操作オブジェクト生成
spi.open(0, 0) # bus=0 device=0 (CE0) でSPIオープン
spi.max_speed_hz = 7812500 # 7.8 MHzSPIクロック
spi.mode = 0 # クロック信号(SCLK)はLow待機、LowからHighに立ち上がる瞬間でデータを読み取り
value=4847 # 0x12ef
#value=20479 # 0x4fff
data_list = list(value.to_bytes(2, byteorder='big' , signed=True))
value=-479 # 0xfe21
data_list += list(value.to_bytes(2, byteorder='big', signed=True))
print(data_list)
print(spi_send(spi, data_list)) # 送信、確認
while True:
value = int(input("PWM data>>"))
data_list = list(value.to_bytes(2, byteorder='big', signed=True)) # 右 PWM
print(f"{data_list[0]:X}, {data_list[1]:X}")
data_list += list(value.to_bytes(2, byteorder='big', signed=True)) # 左 PWM
print(spi_send(spi, data_list)) # 送信、確認
#
上記では、右用の20479と左用の-479の32ビットを送った後、-32768〜32767の範囲でキー入力した値を、右と左に同じ値のPWMをまとめた32ビットをSPIで送信するプログラムです。
上記の
Parspberry PIのSPIマスター用コードからのPWMの情報を受け取るUMEHOSHI ITA基板側のSPIスレーブコードを以下に示します。
//UMEHOSHI ITA基板側のSPIスレーブコード(pwm_pi3ma_spi.c)
#include <xc.h>
#include <stdio.h>
#include <stdlib.h>
#include "common.h"
#include <proc/p32mx270f256b.h>
#include <sys/attribs.h>//割込み関係の定義
int reset_on_zero_sequence(int val); // ゼロが連続したときにモータなどを初期化する
void set_PWM(int16_t right_pwm, int16_t left_pwm ); // 右と左のPWMデータをセット
#define LEN 4
int slv_req_idx=1; // 次に要求する識別番号
uint8_t datas[LEN]={0,0,0,0};// 受信データ記憶域
void SPI2_Slave_func(void)// スレーブ用割り込み処理から呼ばれる
{
/* ---- 受信割り込み ---- */
if (IFS1bits.SPI2RXIF)// SPI2RXIF = 受信バッファにデータが入った
{
//_send_hex_low(TMR2); // 確認用
//_send_hex_low(PR2); // 確認用
//_send_hex_low(IEC0bits.T2IE ); // 確認用
//_send_hex_low(T2CONbits.ON); // 確認用
//_send_string("\r\n"); // 確認用
uint8_t val;
val = SPI2BUF; // SPI受信データを読む(読まないとSPIROVのオーバーフローが発生)
// 前に要求した識別番号を得てそれが示す受信データとして格納
if( slv_req_idx == 0) {
datas[LEN -1] = val; // バッファの最後に要求した受信データを格納
} else {
datas[slv_req_idx -1] = val;// 要求した受信データを格納
}
// 次に要求するデータを記憶する添え字に更新
slv_req_idx ++;
if( slv_req_idx == LEN ){
slv_req_idx = 0;
}
if( reset_on_zero_sequence(val) ) {// ゼロが連続したときの初期化
slv_req_idx = 1;// 初期化要求なら次の要求番号を1
SPI2BUF = slv_req_idx;// 次の送信データをセット
IFS1bits.SPI2RXIF = 0; // 受信割り込みフラグクリア
return;
}
while(SPI2STATbits.SPITBE == 0); // 送信バッファ空待ち
SPI2BUF = slv_req_idx;// 次の送信データをセット
// バッファに溜まった情報をデコードしてPWMデータに戻す。
if (slv_req_idx == 1){// PIC32はリトルエンディアンに合わせてデコード
int16_t right_pwm= ((uint16_t)datas[0] << 8) | datas[1];
int16_t left_pwm = ((uint16_t)datas[2] << 8) | datas[3];
set_PWM(right_pwm, left_pwm );// 右と左のPWMデータをセット
}
IFS1bits.SPI2RXIF = 0; // 受信割り込みフラグクリア
}
/* ---- エラー割り込み ---- */
if (IFS1bits.SPI2EIF)
{
SPI2STATbits.SPIROV = 0; // 受信オーバーフロー解除
slv_req_idx = 1;
SPI2BUF = slv_req_idx;// 次の送信データ先頭用にセット
IFS1bits.SPI2EIF = 0; // エラーフラグクリア
_debug_hex16(9, 0x0ffff , 1);
}
}
int reset_on_zero_sequence(int val) // ゼロが連続したときにモータなどを初期化する
{
static int zero_count=0;// 初期化判定用 0のbyteを連続5個受け取りで初期化
if( val == 0){ // zero の連続5個受信で、次の受信を先頭データと初期化
zero_count++;
if( zero_count == 5){
zero_count = 0;
// モータ全停止
OC4RS = 0; // CN7[3-4]制御(正転情報)(RB13)
OC3RS = 0; // CN7[1-2]制御(逆転情報)(RB14)
OC5RS = 0; // CN6[1-2]制御(正転情報)(RB2)
OC1RS = 0; // CN6[3-4]制御(逆転情報)(RB3)
_debug_hex8(8, 0, 1); // 16ビット受信データ確認
return 1;
}
} else zero_count = 0;
return 0;
}
void set_PWM(int16_t right_pwm, int16_t left_pwm )// 右と左のPWMデータをセット
{
_send_decimal(right_pwm,10);// _send_hex_low(right_pwm); // 確認用
_send_string(": right_pwm , "); // 確認用
_send_decimal(left_pwm,10); // _send_hex_low(left_pwm); // 確認用
_send_string(": left_pwm \r\n"); // 確認用
//_clear_beep_code(); //デバック用ビープ領域初期化
//_debug_hex16(0, right_pwm , 1); // 16ビット受信データ確認
//_debug_hex16(1, left_pwm , 1); // 16ビット受信データ確認
//OC4RS = 0;//0x3fff; 実験用コード
if(right_pwm >= 0){
OC4RS = right_pwm; // CN7[3-4]制御(RB13)
OC3RS = 0x0; // CN7[1-2]制御(RB14)
} else {
OC4RS = 0x0; // CN7[3-4]制御(RB13)
OC3RS = -right_pwm;; // CN7[1-2]制御(RB14)
}
if(left_pwm >= 0){
OC5RS = left_pwm; // CN6[1-2]制御(RB2)
OC1RS = 0x0; // CN6[3-4]制御(RB3)
} else {
OC5RS = 0x0; // CN6[1-2]制御(RB2)
OC1RS = -left_pwm; // CN6[3-4]制御(RB3)
}
}
// UMEHOSHI ITA のRAMプログラムの設定用プログラム(0x80005000番地より起動)
__attribute__((address( 0x80005000 ))) void start (void);
void start()
{
//__builtin_disable_interrupts();// PIC32MX270F256B-50I/SPの割り込みをまとめて一括禁止(Disable)
// これは、コンパイラ(XC32)の組み込み関数(Built-in Function) 「asm volatile ("di");」でも良い
_clear_beep_code(); // _debug_hex? 関数利用の初期化
_UM_PTR_GOTO_BEEP = NULL; // debug_hex?の出力をループしない設定
_set_pwd_mode(1); // PWM モードへ変更( SPI2の初期化の前に行う。そうしないとPWMが動かない:理由不明)
_HANDLES[_IDX_SPI2_FUNC] = (void *)SPI2_Slave_func;// 上記のSPI割り込み関数の登録
((void (**)(void) )_PTR_HANDLERS)[_IDX_INIT_SPI2_SLAVE]();// SPI2の初期化
slv_req_idx=1; // 最初設定
SPI2BUF = slv_req_idx;// 最初の送信データのセット(重要)
_RB5 = 0; // D1 LED の消灯
T2CONbits.ON = 1; //Timer2の機能を有効(PWM利用ではこの有効設定が必要)
PR2=0x09c3F; // Timer2の周期設定
//IEC0bits.T2IE = 0; // Timer2 Interrupt Enable を 0 (禁止) にする。(割り込み必要無し)
//OC4RS = 0x1fff; //CN7のPWMの値設定確認用
//_debug_hex4(0,slv_req_idx,1);// _debug_hex? の動作確認用
//_send_string("start END\r\n");//起動関数終了の表示
//__builtin_enable_interrupts();// PIC32MX270F256B-50I/SPの割り込みをまとめて一括許可(Enable)
// 上記は、組み込み関数(asm volatile ("ei");でも良い
}
以下は上記SPI送受信の実行計です。左に合わせて右は改行や説明などを追加しています。
| Raspberry PI側のPythonのSPIマスター実行例 | PIC32MXの実行例(「umehoshiEdit」ツールでの表示) |
|---|---|
suzuki@raspberrypi:/usr/local/apps $ sudo python spimaslist.py [18, 239, 254, 33] ← 送信予定のリスト send[0]:18,receive: 1 ←18が1byte送信で、1の受信が要求番号 send[1]:239,receive: 2 ←1の受信で、send[1]の239の1byteを送信 send[2]:254,receive: 3 send[3]:33,receive: 0 True PWM data>>0 ← 以降はキー入力データを送信(0のPWMはモータ停止) 0, 0 send[0]:0,receive: 1 send[1]:0,receive: 2 send[2]:0,receive: 3 send[3]:0,receive: 0 True PWM data>>-32768 ← このキー入力値はモータの正転の最大値 80, 0 send[0]:128,receive: 1 send[1]:0,receive: 2 send[2]:128,receive: 3 send[3]:0,receive: 0 True PWM data>>32767 7F, FF send[0]:127,receive: 1 send[1]:255,receive: 2 send[2]:127,receive: 3 send[3]:255,receive: 0 True PWM data>>0 0, 0 send[0]:0,receive: 1 send[1]:0,receive: 2 send[2]:0,receive: 3 send[3]:0,receive: 0 True PWM data>> |
START:80005000 ← 起動の表示(80005000番地より実行)
+4847: right_pwm , -479: left_pwm ←32bit受信でモータが回る
0: right_pwm , 0: left_pwm ←0の受信でモータが停止
-32768: right_pwm , -32768: left_pwm ←この受信値はモータ正転最大
+32767: right_pwm , +32767: left_pwm ←この受信値はモータ逆転最大
0: right_pwm , 0: left_pwm ←0の受信でモータが停止
|
umeusb.send_cmdfile("/usr/local/apps/pwm_pi3ma_spi.c.umh") # SPIでPWM送信するモジュールを初期化
#!/usr/bin/python3
# -*- coding: utf-8 -*-
# ssd1306.py のファイル名で、ssd1306を使った128×64ドット有機ELディスプレイをI2Cで操作するdraw_text関数の定義
# 利用する場合、『from SSD1306 import i2c, draw_text』 を記述して使うとよい。
import board
import busio
from adafruit_ssd1306 import SSD1306_I2C # SSD1306ディスプレイ用
from PIL import Image, ImageDraw, ImageFont
import time
i2c = busio.I2C(board.SCL, board.SDA)# --- I2C初期化 ---
# --- SSD1306ディスプレイ初期化 (128x64の場合) -----------
oled = SSD1306_I2C(128, 64, i2c)
oled.contrast(128) # 0?255
oled.fill(0) # --- クリア
oled.show() # ---表示
# --- Pillowで描画領域を作成 ---
image = Image.new("1", (oled.width, oled.height))
draw = ImageDraw.Draw(image)
font = ImageFont.load_default()# --- フォント設定 ---
def draw_text(txt: str, row=0, showFlag = True, newImageFlag = False, font=font, fill=255):
'''txtの文字列を、row行目に設定する。
showFlagをFalseにすると表示はしないで、次の表示に使うイメージに内部を更新する。
newImageFlagをTrueにすると、以前の描画イメージをクリアして、新しい文字列として更新する。
'''
global image,draw
if newImageFlag:
image = Image.new("1", (oled.width, oled.height)) # イメージ作り直し(全体クリア)
draw = ImageDraw.Draw(image)
# --- テキスト描画 (0=黒、255=白)上記設定で、横21文字---
draw.text((0, row*15), txt , font=font, fill=255)
# --- 画面に表示 ---
oled.image(image)
if showFlag: oled.show()
if __name__ == '__main__':
draw_text(f"UMEHOSHI ITA",0,showFlag=False,newImageFlag=True)
draw_text(f"123456789ABCDEFGHIJKLMN",1,showFlag=False)
draw_text(f"SSD1306 Display",2)
|
![]() |