chakokuのブログ(rev4)

日々のごった煮ブログです

お越しいただきありがとうございます。

このブログでは主に以下の内容を記載しています。

  • 何か作った話(自分は何かを作るために生きている..できたらクスっと笑えるものや、アートと絡みたい)
  • 音楽(AerophoneやDTM(FL-Studio))
  • 趣味のロードバイク(坂道をひーこら走ると結構大変な人生(笑)と重なる。。仕事のストレスが軽いと走らなくなる。。いかん)

記事が渾然一体となってカオス状態ですが、マイコンのプログラムも、ちゃりで走るのも、料理するのも、自分の中ではそれぞれが繋がっているので、カオスな日常をそのまま反映させています。リンクや引用はご自由にどうぞ。質問等ありましたらコメントに書いてください。微力ながら自分の分かる範囲で回答します。
とにかく、、人に「あほやなぁ」と言われる事に精進するのが我が人生。

2021/9/4 追記
いろんな趣味をかじってきたけど、最後まで残っているのはプログラミングという。。休日もプログラミングばっかりやっている。そんなに高度なものではなく、ドローン用のへなちょこ制御プログラムをフルスクラッチで作ろうとしています。姿勢制御とか、フィードバック制御とかが難しい。Try&ErrorはMicroPythonを使ってますが、仕上げはRustで書きたい。

2019/1/26 追記
長らくお世話になった「はてな」様ですが、はてなダイアリーがサービス終了となり、はてなブログに移しましたが、宣伝がちょっと辛いのでbloggerに引っ越す予定です。引っ越し先は以下
https://chakoku.blogspot.com/

2019/7/20
ひっこしたbloggerですが、どうにもUIが使い慣れないのと、なんか寂しい。ネット空間で寂しいとはどういうことなのか?自分でもよくわからないが、bloggreはなんか寂しい。。記事を書く気がおきない。で、Hatena Blogは無料だと広告がかなり邪魔なんだけど*1長年使い慣れたHatenaに戻ることにしました。Hatenaはほっとする~


まとめ:https://sites.google.com/site/chakokumatome/

*1:有料が高すぎるので払えない

Python+Tkinterによる9DoFセンサビューアの作成

9DoFからの計測値は数字だけ見てもなかなかどの軸で移動が検知されているのか分かりにくい。そこで、視覚的に把握できるようGUIのViewerを作った。Unityとかだと勉強しないといけないので、、Python+Tkinterで作成。普通の作り方だとボタン操作等を処理するため、Tkinterのイベントループを回すが、そうするとリアルタイムもグラフ描画はスレッド等を立てないといけなくなるので、メインループは自前で作ってTkinterの画面にはGUI等の操作系パーツは無しで描画専用とした。画面左側の3本の棒グラフはジャイロセンサの計測値(XYZ)、右側の3本の棒グラフは加速度センサの計測値(XYZ)、センサデータがプラス方向かマイナス方向か、また、強さが分かるように実装。

PC側で動作するビューアのソースは以下(急づくりで汚い)

#!/usr/bin/python3

import serial
import struct
import pdb
import tkinter


def draw_graph(val,pos_x):
   if (val > 0):
      canvas.create_rectangle(pos_x,250-val, pos_x+30, 250)
   else:
      canvas.create_rectangle(pos_x,250, pos_x + 30 ,250 - val)

#
#  bytes('F','F','E','0') -> -12 in int
#
def conv_hex2int(hex_in_bytes):
    val = int(hex_in_bytes, base=16)   # bytes ('F','F','E','4') -> 0xFFE4 in int
    hex_str = "{:04x}".format(val)      # 
    #print(hex_str)
    #pdb.set_trace()
    val = struct.unpack('>h', bytes.fromhex(hex_str))[0]   # 2byte
    return val



GRAPH_HEIGHT=200
MAX_GYRO_VALUE=22000
MAX_ACCEL_VALUE=15800


ser = serial.Serial("/dev/ttyS13", 38400)
cmd = bytes((0x53,))
ser.write(cmd)
ser.readline() # dummy read for drop gargase
ser.readline() # dummy read for drop gargase

root =  tkinter.Tk()
canvas = tkinter.Canvas(root,width=500,height=500)
canvas.pack()
root.update()

while True:
   data = ser.readline()
   #print(data)
   if len(data) < 27:
       continue
   tmp = bytes((data[5],data[6],data[2],data[3]))
   gx = conv_hex2int(tmp)
   tmp = bytes((data[11],data[12],data[8],data[9]))
   gy = conv_hex2int(tmp)
   tmp = bytes((data[17],data[18],data[14],data[15]))
   gz = conv_hex2int(tmp)

   tmp = bytes((data[26],data[27],data[23],data[24]))
   ax = conv_hex2int(tmp)
   tmp = bytes((data[32],data[33],data[29],data[30]))
   ay = conv_hex2int(tmp)
   tmp = bytes((data[38],data[39],data[35],data[36]))
   az = conv_hex2int(tmp)

   print(gx,gy,gz,ax,ay,az,end="")
   print("    ",end="")
   print(data)

   canvas.delete("all")
   canvas.create_line(30, 250, 470, 250)
   canvas.create_text(50, 20, text = "{:d}".format(gx), font = ('FixedSys', 12))
   canvas.create_text(100, 20, text = "{:d}".format(gy), font = ('FixedSys', 12))
   canvas.create_text(150, 20, text = "{:d}".format(gz), font = ('FixedSys', 12))

   draw_graph(GRAPH_HEIGHT * gx / MAX_GYRO_VALUE, 50)
   draw_graph(GRAPH_HEIGHT * gy / MAX_GYRO_VALUE, 100)
   draw_graph(GRAPH_HEIGHT * gz / MAX_GYRO_VALUE, 150)

   canvas.create_text(300, 20, text = "{:d}".format(ax), font = ('FixedSys', 12))
   canvas.create_text(350, 20, text = "{:d}".format(ay), font = ('FixedSys', 12))
   canvas.create_text(400, 20, text = "{:d}".format(az), font = ('FixedSys', 12))

   draw_graph(GRAPH_HEIGHT * ax / MAX_ACCEL_VALUE, 300)
   draw_graph(GRAPH_HEIGHT * ay / MAX_ACCEL_VALUE, 350)
   draw_graph(GRAPH_HEIGHT * az / MAX_ACCEL_VALUE, 400)

   root.update()



root.mainloop()

最終的には、計測した値を積分して姿勢推定までやりたいけど、まずは、加速度センサだけを使って機体の傾きを検知して、水平に保つ制御だけを作ってみる予定。何も工夫しないと制御が発振したりいろいろありそうだけど。。作ってみないとどう動くのか分からない。モータの出力はPWMを使って推力を調整する。制御アルゴリズムをドローン内で開発すると、試作>コンパイルデバッグ>試作のループに時間がかかりすぎるので、、初期バージョンは自立型とせず、センサデータをPCで取得して演算結果をドローンに戻して姿勢制御させてみる予定。そのためには、、PWMによるモータ制御部分を実装する必要がある。

■参考URL
PWM設定解説動画
STM32 TIMERS #1. PWM Output || DMA - YouTube

9DoFのセンサーをNUCLEO経由でPCに転送できた

Rustで書いた9DoF(ST社のLSM9DS1)の制御プログラムをNUCLEO-L476RG上で動かし、USB-Serial経由でPCに送信している。PC側はPythonのデータダンプツールを作って、9DoFから得られたジャイロと加速度センサの値を随時更新するようにした。実際の値は以下
この値は、LSM9DS1のレジスタ設定はデフォルトで、動作ONにしただけの状態。だから、、値域とか分能度とか、さらに調整が必要。

-----------------------------------------------------------------------
 ジャイロ    加速度     レジスタ値(ジャイロ)    (加速度)
-----------------------------------------------------------------------
-35 67 67   306 -1146 15855    G:DD FF 43 00 FF 00  X:32 01 86 FB EF 3D 
-29 72 72   375 -1087 15790    G:E3 FF 48 00 09 00  X:77 01 C1 FB AE 3D 
-27 63 63   157 -1186 15819    G:E5 FF 3F 00 0A 00  X:9D 00 5E FB CB 3D 
-49 73 73   390 -1140 15830    G:CF FF 49 00 00 FF  X:86 01 8C FB D6 3D 
-31 57 57   312 -1138 15830    G:E1 FF 39 00 12 00  X:38 01 8E FB D6 3D 
-36 65 65   220 -1123 15836    G:DC FF 41 00 04 00  X:DC 00 9D FB DC 3D 
-32 71 71   329 -1159 16003    G:E0 FF 47 00 14 00  X:49 01 79 FB 83 3E 
-13 70 70   300 -1108 15749    G:F3 FF 46 00 1B 00  X:2C 01 AC FB 85 3D 
-----------------------------------------------------------------------

センサパラメータを調整するとして、次は、このデータを視覚化したい。本当はUnity等を使って、リアルタイムでセンサの傾きを3Dで描画できたらいいのだけど、Unityよくわかっていないし、動かすまでが手間なので、、最低限の傾き計算をPython+GUIで表示させる予定

センサー制御はジャイロと加速度を動かすための最低限の設定だけを実施(Rustのソース)

const CTRL_REG1_G:u8 = 0x10;
const CTRL_REG6_XL:u8 = 0x20;

#[no_mangle]
#[entry]
fn main() ->! {

  driver::gpio_setup();
  driver::serial_setup();
  driver::i2c_setup();
  let i2c_addr = 0x6a;

  driver::i2c_write_reg(i2c_addr, CTRL_REG1_G, 0x80);
  driver::i2c_write_reg(i2c_addr, CTRL_REG6_XL, 0x80);

9DoFセンサーからの値取得+Serial出力ルーチン

fn sensor_reg_gylo(i2c_addr:u8){

  driver::serial_putc(b'G'); 
  driver::serial_putc(b':'); 
  for offset in 0..6 {
     let addr = 0x18 + offset;
     let val = driver::i2c_read_reg(i2c_addr, addr);
     driver::serial_puthex(val);          
     driver::serial_putc(b' '); 
  }
}

fn sensor_reg_accel(i2c_addr:u8){

  driver::serial_putc(b'X'); 
  driver::serial_putc(b':'); 
  for offset in 0..6 {
     let addr = 0x28 + offset;
     let val = driver::i2c_read_reg(i2c_addr, addr);
     driver::serial_puthex(val);          
     driver::serial_putc(b' '); 
  }
}

STM32/RustでHALを使わずレジスタを直接操作してI2Cの send/receive

9DoFのセンサのI/FがI2Cであったので、HALを使わないI2C用ドライバを作っていた。タイミングのパラメータ等、ベストではないけど、一応動くようになった。これまでずっとバスアービトレーションが異常であったが、根本原因はI2CデバイスによるI2CバスGND引き込みプルアップの整合不十分だろうと思う。この理由として、ずっとバスアービトレーションが異常だったのだが、オシロのプローブをはずすと安定して動くようになった。プローブを付ける、付けないで動作が変わるのは、I2Cバスの電気特性がギリギリ条件を満たしている状態なんだろうと推測。I2Cバスが動作するようになったので、、9DoF用のドライバを作れば、STM32 +Rustによる、ジャイロ、加速度センサのデータ採取が可能となる。

//
//  stm32 device driver (NUCLEOL476RG(STM32L476RG専用)
// 

// RCC
pub const RCC_BASE: u32 = 0x4002_1000;
pub const RCC_APB1ENR1_OFFSET: u32 = 0x58;
pub const RCC_AHB2ENR_OFFSET : u32 = 0x4C;   // RCC_AHB2ENR
pub const RCC_CCIPR_OFFSET : u32 = 0x88;
pub const RCC_I2C1SEL_FLD : u32 = 12;
pub const RCC_USART2_SEL : u32 = 1;

pub const RCC_GPIOAEN_BIT : u8 = 0;
pub const RCC_GPIOBEN_BIT : u8 = 1;
pub const RCC_GPIOCEN_BIT : u8 = 2;
pub const RCC_GPIODEN_BIT : u8 = 3;
pub const RCC_I2C1EN_BIT : u8 = 21;
pub const RCC_USART2EN_BIT : u8 = 17;


// GPIO
pub const GPIOA_BASE: u32 = 0x4800_0000;   //APB2PERIPH_BASE
pub const GPIOB_BASE: u32 = 0x4800_0400;
pub const GPIOC_BASE: u32 = 0x4800_0800;
pub const GPIOD_BASE: u32 = 0x4800_0C00;
pub const GPIO_MODER_OFFSET: u32 = 0x0;  // GPIOA
pub const GPIO_OTYPER_OFFSET: u32 = 0x04;
pub const GPIO_OSPEEDR_OFFSET: u32 = 0x08;
pub const GPIO_PUPDR_OFFSET: u32 = 0x0C; 
pub const GPIO_BSRR_OFFSET: u32 = 0x18;
pub const GPIO_AFRL_OFFSET: u32 = 0x20;
pub const GPIO_AFRH_OFFSET: u32 = 0x24;
pub const GPIO_PIN_1 : u8 = 1;
pub const GPIO_PIN_2 : u8 = 2;
pub const GPIO_PIN_3 : u8 = 3;
pub const GPIO_PIN_4 : u8 = 4;
pub const GPIO_PIN_5 : u8 = 5;
pub const GPIO_PIN_8 : u8 = 8;
pub const GPIO_PIN_9 : u8 = 9;


// UART
pub const USART2_BASE : u32 = 0x4000_4400;
pub const UART_BRR_OFFSET : u32 = 0x0c;
pub const UART_ISR_OFFSET : u32 = 0x1c;
pub const UART_CR1_OFFSET : u32 = 0;
pub const UART_RDR_OFFSET: u32 = 0x24;
pub const UART_TDR_OFFSET: u32 = 0x28;
pub const UART_UE_BIT : u8 = 0;
pub const UART_RE_BIT : u8 = 2;
pub const UART_TE_BIT : u8 = 3;
pub const UART_TC_BIT : u8 = 6;
pub const UART_TXE_BIT : u8 = 7;
pub const UART_RXNE_BIT : u8 = 5;


// I2C
pub const I2C1_BASE : u32 = 0x4000_5400;  // I2C1
pub const I2C_CR1_OFFSET : u32  = 0x0;
pub const I2C_CR2_OFFSET : u32  = 0x4;
pub const I2C_TIMINGR_OFFSET : u32  = 0x10;
pub const I2C_ISR_OFFSET : u32  = 0x18;
pub const I2C_ICR_OFFSET : u32  = 0x1c;
pub const I2C_RXDR_OFFSET : u32  = 0x24;
pub const I2C_TXDR_OFFSET : u32  = 0x28;

pub const I2C_PRESEC_BIT_FLD: u8 = 28; 
pub const I2C_SCLDEL_BIT_FLD: u8 = 20; 
pub const I2C_SDADEL_BIT_FLD: u8 = 16; 
pub const I2C_SCLH_BIT_FLD: u8 = 8;    
pub const I2C_SCLL_BIT_FLD: u8 = 0;    
pub const I2C_NBYTES_FLD : u8 = 16;


pub const I2C_PE_BIT : u8 = 0;
pub const I2C_START_BIT : u8 = 13;
pub const I2C_STOP_BIT : u8 = 14;
pub const I2C_RDWRN_BIT : u8 = 10;
pub const I2C_RDWRN_WRITE : u8 = 0;
pub const I2C_RDWRN_READ : u8 = 1;
pub const I2C_TXIS_BIT : u8 = 1;
pub const I2C_RXNE_BIT : u8 = 2;
pub const I2C_NACKF_BIT: u8 = 4; 
pub const I2C_TC_BIT : u8 = 6;
pub const I2C_ARLO_BIT : u8 = 9;


// I2C master initialization
// Master communication initialization (address phase)
//In order to initiate the communication, the user must program 
//the following parameters forthe addressed slave in the I2C_CR2 register:
//. Addressing mode (7-bit or 10-bit): ADD10
//. Slave address to be sent: SADD[9:0]
//. Transfer direction: RD_WRN
//. In case of 10-bit address read: HEAD10R bit. HEAD10R must be 
//  configure to indicate if the complete address sequence must be sent, 
//  or only the header in case of a direction change.
//. The number of bytes to be transferred: NBYTES[7:0]. 
//  If the number of bytes is equal to or greater than 255 bytes, 
//  NBYTES[7:0] must initially be filled with 0xFF.
// The user must then set the START bit in I2C_CR2 register. 
// Changing all the above bits is not allowed when START bit is set.


//Master transmitter


pub fn i2c_setup() {

  // enable I2C1 Block
  // set GPIO  PB  Enable
  //RCC + RCC_AHB2ENR(0x4C)  GPIOB_EN(1) <- 1
  //let mut addr:u32 = (RCC_BASE + RCC_AHB2ENR_OFFSET) as *mut u32; // 0x4c
  let mut addr = (RCC_BASE + RCC_AHB2ENR_OFFSET) as *mut u32; // 0x4c
  let mut set_flag :u32  = 0x1 << RCC_GPIOBEN_BIT; // GPIOBEN:1
  unsafe { 
	let mut val:u32 = core::ptr::read_volatile(addr) ;
        val |= set_flag;
        core::ptr::write_volatile(addr, val);
  }

  // set GPIO  PB8, PB9 to alternate mode
  addr = (GPIOB_BASE + GPIO_MODER_OFFSET) as *mut u32;
  let mut set_mode :u32 = 0x2 << (GPIO_PIN_8 * 2); // 0x2 ... Alternate MODE
  set_mode |= 0x2 << (GPIO_PIN_9 * 2);         // 0x2 ... Alternate MODE
  let mut mask_bit : u32 = 0x3 << (GPIO_PIN_8 * 2); 
  mask_bit |=  0x3 << (GPIO_PIN_9 * 2); 
  mask_bit = !mask_bit;
  unsafe { 
	let current = core::ptr::read_volatile(addr) ;
        let mut val = current & mask_bit;
        val |= set_mode;
	core::ptr::write_volatile(addr, val) ;
  }

  // set GPIO  PB8,PB9 to opendrain mode   -> yes open drain
  addr = (GPIOB_BASE + GPIO_OTYPER_OFFSET) as *mut u32;
  set_mode = (1 << GPIO_PIN_8) | (1 << GPIO_PIN_9);
  unsafe { 
        let mut val = core::ptr::read_volatile(addr) ;
        val |= set_mode;
	core::ptr::write_volatile(addr, val) ;  
  }

  // set GPIO  PB8, PB9 to no Pullup (0b00  no pull up)
  addr = (GPIOB_BASE + GPIO_PUPDR_OFFSET) as *mut u32;
  mask_bit = !((0x3 << (GPIO_PIN_8 * 2)) | (0x3 << (GPIO_PIN_9 * 2)));
  unsafe { 
	let mut val = core::ptr::read_volatile(addr) ;
        val &= mask_bit;
	core::ptr::write_volatile(addr, val) ;
  }

  // set GPIO  PB8, PB9 to HighSpeed (0b11) => yes high -> no high
  addr = (GPIOB_BASE + GPIO_OSPEEDR_OFFSET) as *mut u32;
  set_mode  = 0x3  << (GPIO_PIN_8 * 2); // 0x3 ... SuperHighSpeed
  set_mode |= 0x3  << (GPIO_PIN_9 * 2); // 0x3 ... SuperHighSpeed
  unsafe { 
	let mut val = core::ptr::read_volatile(addr) ;
        val |= set_mode;
	core::ptr::write_volatile(addr, val) ;
  }

  // set GPIO  PB8, PB9 to AF4
  addr = (GPIOB_BASE + GPIO_AFRH_OFFSET) as *mut u32;
  set_mode  = 0x4  << ((GPIO_PIN_8 - 8) * 4); // 0x4 ... I2C
  set_mode |= 0x4  << ((GPIO_PIN_9 - 8) * 4); // 0x4 ... I2C
  mask_bit = 0xF << (GPIO_PIN_8 * 2) | 0xF << (GPIO_PIN_9 * 2); 
  mask_bit = !mask_bit;
  unsafe { 
	let mut val = core::ptr::read_volatile(addr) ;
        val &= mask_bit;
        val |= set_mode;
	core::ptr::write_volatile(addr, val) ;     // <<<<<!!!!!
  }

  // enable I2C1 Block
  //RCC + RCC_APB1ENR1(0x58) I2C1_EN(21) <- 1
  addr = (RCC_BASE + RCC_APB1ENR1_OFFSET) as *mut u32; // 0x58
  set_flag = 0x1 << RCC_I2C1EN_BIT;       // I2C1EN(0x21)
  unsafe { 
	let mut val = core::ptr::read_volatile(addr) ;
        val |= set_flag;
        core::ptr::write_volatile(addr, val);
  }

  // select clock (use SYSCLK)(USART2SEL)  
  addr = (RCC_BASE + RCC_CCIPR_OFFSET) as *mut u32;    // 0x88
  set_mode = 0x1 << RCC_I2C1SEL_FLD;  // 1:SYSCLK  I2C1_SEL:12 
  mask_bit = !(0x3 << RCC_I2C1SEL_FLD);
  unsafe { 
	let mut val= core::ptr::read_volatile(addr) ;
        val &= mask_bit;
        val |= set_mode;
	core::ptr::write_volatile(addr, val) ;
  }


  // I2C master initialization (Master transmitter)
  // Software reset
  // I2C_CR1 [ PE ] <- 0

  // I2C Reset(Peripheral disable)
  addr = (I2C1_BASE + I2C_CR1_OFFSET) as *mut u32;    // L210 stop here
  set_flag = 0x0 << I2C_PE_BIT;
  mask_bit = !(0x1 << I2C_PE_BIT);
  unsafe { 
	let mut val = core::ptr::read_volatile(addr) ;
        val &= mask_bit;
        val |= set_flag;
	core::ptr::write_volatile(addr, val) ;
  }
  // dummy read for timing
  unsafe { 
	let _dummy = core::ptr::read_volatile(addr) ;
  }
  unsafe { 
	let _dummy = core::ptr::read_volatile(addr) ;
  }


  // Configure ANFOFF and DNF[3:0] in I2C_CR1
  //  * ANFOFF: Analog noise filter OFF (leave as it it ... 0)
  //  * DNF   : Digital noise filter off (leave as it it ... 0)
  ();  // do nothing

  // Configure  params in I2C_TIMINGR
  // PRESC[3:0], <- 1 /(8Mz)   in 4Mhz 0
  // SDADEL[3:0], <- 2 
  // SCLDEL[3:0], <- 4 
  // SCLH[7:0],   <- c3
  // SCLL[7:0]    <- c7
   set_flag =  0x0  << I2C_PRESEC_BIT_FLD;        // 28
   set_flag |= 0x4  << I2C_SCLDEL_BIT_FLD;        // 20
   set_flag |= 0x2  << I2C_SDADEL_BIT_FLD;        // 16
   set_flag |= 0xc3 << I2C_SCLH_BIT_FLD;         // 8
   set_flag |= 0xc7 << I2C_SCLL_BIT_FLD;         // 0
  addr = (I2C1_BASE + I2C_TIMINGR_OFFSET) as *mut u32;  // 
  unsafe { 
	core::ptr::write_volatile(addr, set_flag) ;
  }

  // Configure NOSTRETCH in I2C_CR1
  //NOSTRETCH: Clock stretching disable (if 1)
  // 0 .... stretch enable    leave it as it is
  (); 

  // I2C Enable(Peripheral Enable)
  // wait until I2C_CR1 [ PE ] == 0
  // I2C_CR1 [ PE ] <- 1
  addr = (I2C1_BASE + I2C_CR1_OFFSET) as *mut u32;    // 0x88
  set_flag = 0x1 << I2C_PE_BIT;
  unsafe { 
	let val = core::ptr::read_volatile(addr) ;
	core::ptr::write_volatile(addr, val | set_flag) ;
  }

}



pub fn i2c_write(i2c_addr:u8, reg_addr:u8) -> i8 {

  // autoend is off  (default off)   AUTOEND(25) in I2C_CR2(0x4)
  // Master communication initialization (address phase)
  //In order to initiate the communication, 
  // the user must program the following parameters for
  // the addressed slave in the I2C_CR2 register:
  //. Addressing mode (7-bit or 10-bit): ADD10
  // . Slave address to be sent: SADD[9:0]
  // . Transfer direction: RD_WRN
  // . In case of 10-bit address read: HEAD10R bit. HEAD10R must be 
  //configure to indicate
  //if the complete address sequence must be sent, or only the header 
  //in case of a direction change.
  // . The number of bytes to be transferred: NBYTES[7:0]. If the 
  // number of bytes is equal to or greater than 255 bytes, NBYTES[7:0] 
  //must initially be filled with 0xFF.

  // Addressing mode (7-bit): ADD10 of I2C_CR2

  // I2C_CR2_OFFSET // 04
  // ADD10_BIT // 11   <-  0    7bit is 0 so leave
  

  serial_putc(b'[');
  serial_putc(b'S');
  serial_putc(b']');

  //
  let i2c_addr:u8 = 0x6a;
  let reg_addr:u8 = 0x0f; // hello

  //set address 
  let mut set_flag:u32 = 0;
  set_flag = (i2c_addr << 1) as u32;       // set SADD
  set_flag |= (I2C_RDWRN_WRITE as u32) << I2C_RDWRN_BIT;    // rw_flag  (0:write  / 1:read)

  // set send 1byte (write register address(1b))
  //NBYTES <- 1 in I2C_CR2          I2C_NBYTES_FIELD (16)
  let size = 1;
  set_flag |= size << I2C_NBYTES_FLD;

  // Bit 13 START: Start generation (set START bit)
  set_flag |= 1 << I2C_START_BIT;

  let mut addr = (I2C1_BASE + I2C_CR2_OFFSET) as *mut u32;     
  unsafe {   
       let mut val = core::ptr::read_volatile(addr) ; 
       val |= set_flag;
       core::ptr::write_volatile(addr, val) ;           // stop here 311
  }

  //while TXIS == 0  in I2C_ISR
  addr = (I2C1_BASE + I2C_ISR_OFFSET) as *mut u32;  
  let mut val : u32 = 0;
  loop {
      unsafe { 
          val = core::ptr::read_volatile(addr);
      }
      if ((val & (1 << I2C_TXIS_BIT)) != 0) ||
         ((val & (1 << I2C_NACKF_BIT)) != 0) ||
         ((val & (1 << I2C_ARLO_BIT)) != 0) {
              break;
      }
  }

  if (val & (1 << I2C_NACKF_BIT)) != 0 {

      serial_putc(b'-');
      serial_putc(b'>');
      serial_putc(b'N');
      serial_putc(b'G');
      serial_putc(b'/');
      serial_putc(b'N');
      serial_putc(b'K');

      // STOP: Start generation (set START bit)
      i2c_stop(i2c_addr);
      return -1;   // return value  -1 error

  }else if (val & (1 << I2C_ARLO_BIT)) != 0 {
      serial_putc(b'-');
      serial_putc(b'>');
      serial_putc(b'N');
      serial_putc(b'G');
      serial_putc(b'/');
      serial_putc(b'L');
      serial_putc(b'S');

      // STOP: Start generation (set START bit)
      i2c_stop(i2c_addr);
      return -1;   // return value  -1 error

  }else if (val & (1 << I2C_TXIS_BIT)) != 0 {

    serial_putc(b'-');
    serial_putc(b'>');
    serial_putc(b'o');
    serial_putc(b'k');

  }else{

   serial_putc(b'-');
   serial_putc(b'>');
   serial_putc(b'?');
   serial_putc(b'?');

    return -2;   // return value  -2 unkown error

 }

  // Master sender wait
  // TXDR <- reg_addr
  addr = (I2C1_BASE + I2C_TXDR_OFFSET) as *mut u32;   // L375
  val = reg_addr as u32;
  unsafe {   
      core::ptr::write_volatile(addr, val);
  }

  // wait until tc
  //wait until TC(6) set (transfer complete)
  // while I2C_TC_BIT == 0  in I2C_ISR

  addr = (I2C1_BASE + I2C_ISR_OFFSET) as *mut u32;  
  val = 0x00;
  while (val & (1 << I2C_TC_BIT)) == 0 {
      unsafe {   
         val = core::ptr::read_volatile(addr);
       }
   }
   0      // return value  (Transmission complete)

}


//
//
//  <i2c_addr(r)>________
//                <data>
//  |~|_|~|_|~|_|~|_|~|_|~|_|~|

pub fn i2c_read(i2c_addr:u8) -> u8 {

  //
  let i2c_addr:u8 = 0x6a;
  let reg_addr:u8 = 0x0f; // hello

  //-------------------------------------------
  // read data

  //set address 
  let mut val:u32 = 0;
  val |= (i2c_addr << 1) as u32;        // set SADD
  val |= (I2C_RDWRN_READ as u32) << I2C_RDWRN_BIT;    // rw_flag  (0:write  / 1:read)

  // receive send 1byte (receive data from I2C device)
  //NBYTES <- 1 in I2C_CR2          I2C_NBYTES_FIELD (16)
  let size = 1;
  val |= size << I2C_NBYTES_FLD;
  let mut addr = (I2C1_BASE + I2C_CR2_OFFSET) as *mut u32;  
  unsafe {   
      core::ptr::write_volatile(addr, val) ;      // chk here L424
  }

  serial_putc(b'[');
  serial_putc(b'S');
  serial_putc(b']');

  // Bit 13 START: Start generation (set START bit)
  //START <- 1 in I2C_CR2           I2C_START_BIT (13)
  let mut set_flag: u32 = 1 << I2C_START_BIT;
  addr = (I2C1_BASE + I2C_CR2_OFFSET) as *mut u32;  
  unsafe { 
	let mut val = core::ptr::read_volatile(addr) ;
        val |= set_flag;                            // bug must check 9/19
	core::ptr::write_volatile(addr, val) ;      // send read L438
  }

  //wait until RXNE(6) st (receive not Empty)
  // while I2C_RXNE_BIT == 0  in I2C_ISR
  addr = (I2C1_BASE + I2C_ISR_OFFSET) as *mut u32;  
  val = 0x00;
  while (val & (1 << I2C_RXNE_BIT)) == 0 {
      unsafe {   
         val = core::ptr::read_volatile(addr);
       }
   }

   serial_putc(b'-');
   serial_putc(b'>');
   serial_putc(b'e');
   serial_putc(b'x');

  // receive data
  //received_data <- RXDR 
  // data <- RXDR 
  addr = (I2C1_BASE + I2C_RXDR_OFFSET) as *mut u32;  
  unsafe {   
      val = core::ptr::read_volatile(addr);
  }
  let read_val:u8 = (val & 0xff) as u8;
  read_val
}

pub fn i2c_stop(i2c_addr:u8){

  // TC == 1? 
  //send stop;

  // STOP: Start generation (set START bit)
  // STOP <- 1 in I2C_CR2           I2C_STOP_BIT (13)
  let mut set_flag :u32 = 1 << I2C_STOP_BIT;
  let mut addr = (I2C1_BASE + I2C_CR2_OFFSET) as *mut u32;  
  unsafe { 
	let mut val = core::ptr::read_volatile(addr) ;
        val |= set_flag;
	core::ptr::write_volatile(addr, val) ;
  }
}

素のSTM32で、I2Cの制御プログラムでトラブル中

次は、HALを使わず素でI2Cを制御しようと思って、いろいろ試作しているけど、うまくいかない。I2Cの9DoFセンサ(LSM9DS1)と接続して、STM32からI2Cアドレスを出すタイミングでなぜかI2CバスがHi-Zになってしまうようで、STM32からクロックを出せないでいる。STM32のステータスレジスタもNACK等のフラグではなくBUSYのまま。だから、何か待ちに入ってしまっているようだ。なぜ待ちに入るのか? センサからDELAY要求が来ているから??? TIMINGRのタイミング制御レジスタも4HMz版がないからちょっと設定が怪しい。。

上記がオシロの画面なのだが、、スタートコンディションを送り出した次のクロック操作の時点でバスを渡しているような印象だ。だから、、アドレスを渡すつもりがないのか、あるいは、スレーブモードに切り替わったのか??
この時のレジスタの値は以下。これらのビットを読み解けば、I2Cのブロックがどういうつもりでウエイト?しているのかが分かるかも。。

(gdb) p/x *(0x40005400)  $2 = 0x1
(gdb) p/x *(0x40005404)  $3 = 0x120d4
(gdb) p/x *(0x40005408)  $4 = 0x0
(gdb) p/x *(0x4000540C)  $5 = 0x0
(gdb) p/x *(0x40005410)  $6 = 0x42c3c7
(gdb) p/x *(0x40005414)  $7 = 0x0
(gdb) p/x *(0x40005418)  $8 = 0x8001
(gdb) p/x *(0x4000541C)  $9 = 0x0
(gdb) p/x *(0x40005420) $10 = 0x0
(gdb) p/x *(0x40005424) $11 = 0x0
(gdb) p/x *(0x40005428) $12 = 0x0

何度かテストしていると、ステータスとして、ARLOが立ってる場合がある。これはアービトレーション喪失なのだが、
なぜアービトレーション喪失するか??多分、センサがバスを使おうとするから(推測)。一発目のクロックで
センサーが何か応答して、バスをBUSYにしてしまい、STM32側からは送り出せず、アービトレーション喪失と判断して
バスをHiZにするのだろう。なぜBUSYにするのか??センサ側にデバッグ手段がないと分からん。あるいは、、
I2Cバスアナライザとか。。

仕様書より

アービトレーション喪失の場合、マスタはスレーブモードに自動的に切り替えて、スレーブとしてアドレス指定された
場合は専用アドレスを確認応答できます。

スタートコンディションのホールドタイムは4.0usecらしいのだが、この波形を見ているとそれぐらいは時間猶予を持たせていそうだ。だったらセンサーにとって、バス仕様の何に文句あるのだろうか。。

■参考URL
https://www.nxp.com/docs/ja/user-guide/UM10204.pdf

STM32/RustでHALを使わずレジスタを直接操作してUART send/receive

ひきつづき、HALを使わず、素でレジスタを叩いてUART用のsend/receiveルーチンを作成
以下のソースはシステムクロック4HMzで、38400bpsで通信する。抽象化していないので、STM32L47xxxシリーズ専用。
変数型もu32でなくてもいいところがあるけど、、無駄が多くてもまずは動かすのが目標
UARTが使えるようになったので、次はI2Cでセンサと接続する。

//
//  stm32 device driver
// 

pub const GPIOA_BASE: u32 = 0x4800_0000;   //APB2PERIPH_BASE + 0x0800;
pub const MODER_OFFSET: u32 = 0x0;  // GPIOA
pub const BSRR_OFFSET: u32 = 0x18;
pub const AFRL_OFFSET: u32 = 0x20;


pub const RCC_BASE: u32 = 0x4002_1000;
pub const AHB2ENR_OFFSET : u32 = 0x4C;   // RCC_AHB2ENR
pub const APB1ENR1_OFFSET: u32 = 0x58;

pub const GPIO_PIN_2 : u32 = 2;
pub const GPIO_PIN_3 : u32 = 3;
pub const GPIO_PIN_5 : u32 = 5;

pub const RCC_CCIPR : u32 = 0x88;
pub const USART2_SEL : u32 = 1;
pub const USART2_BASE : u32 = 0x4000_4400;
pub const USART2EN : u32 = 17;
pub const BRR_OFFSET : u32 = 0x0c;
pub const ISR_OFFSET : u32 = 0x1c;
pub const CR1_OFFSET : u32 = 0;
pub const RDR_OFFSET: u32 = 0x24;
pub const TDR_OFFSET: u32 = 0x28;

pub const UE_BIT : u8 = 0;
pub const RE_BIT : u8 = 2;
pub const TE_BIT : u8 = 3;
pub const TC_BIT : u8 = 6;
pub const TXE_BIT : u8 = 7;
pub const RXNE_BIT : u8 = 5;


pub fn serial_putc(ch : u8){
   // send data
   let mut addr = (USART2_BASE + TDR_OFFSET) as *mut u32;  // 0x28
   let ch32 : u32 = ch as u32;
   unsafe {   
      core::ptr::write_volatile(addr, ch32);
      //let _ = my_wait(0x1);
   }

   // wait until TC(Transmission complete) is set
   let mut val : u32 = 0;
   addr = (USART2_BASE + ISR_OFFSET) as *mut u32;  // 0x1c
   while (val & (1 << TC_BIT)) == 0 {
      unsafe {   
         val = core::ptr::read_volatile(addr);
       }
   }
}

pub fn serial_getc()-> u8 {

   // wait until RXNE is set
   let mut val : u32 = 0;
   let mut addr = (USART2_BASE + ISR_OFFSET) as *mut u32;  // 0x1c
   while (val & (1 << RXNE_BIT)) == 0 {
      unsafe {   
         val = core::ptr::read_volatile(addr);
       }
   }

   // recv data
   addr = (USART2_BASE + RDR_OFFSET) as *mut u32;  // 0x28
   let val : u32;
   unsafe {   
      val = core::ptr::read_volatile(addr);
   }
   let ch = val as u8;
   ch 
}


pub fn setup_serial(){

  // set GPIO  PA2, PA3 to alternate mode
  let mut addr = (GPIOA_BASE + MODER_OFFSET) as *mut u32;
  let mut set_mode  = 0x2 << (GPIO_PIN_2 * 2); // 0x2 ... Alternate MODE
  set_mode |= 0x2 << (GPIO_PIN_3 * 2);         // 0x2 ... Alternate MODE
  unsafe { 
	let current = core::ptr::read_volatile(addr) ;
        let mut val = current & 0xFF_FF_FF_0F;
        val |= set_mode;
	core::ptr::write_volatile(addr, val) ;
  }

  // set GPIO  PA2, PA3 to AF7
  addr = (GPIOA_BASE + AFRL_OFFSET) as *mut u32;
  set_mode  = 0x7 << (GPIO_PIN_2 * 4); // 0x7 ... UART
  set_mode |= 0x7 << (GPIO_PIN_3 * 4); // 0x7 ... UART
  unsafe { 
	let current = core::ptr::read_volatile(addr) ;
        let mut val = current & 0xFF_FF_00_FF;
        val |= set_mode;
	core::ptr::write_volatile(addr, val) ;
  }

  // enable
  addr = (RCC_BASE + APB1ENR1_OFFSET) as *mut u32; // 0x58
  set_mode = 0x1 << USART2EN;        // USART2EN 17
  unsafe { 
	let current = core::ptr::read_volatile(addr) ;
        let mut val = current & 0xFF_FC_FF_FF;
        val |= set_mode;
         core::ptr::write_volatile(addr, val);
  }

  // set clock (USART2SEL)
  addr = (RCC_BASE + RCC_CCIPR) as *mut u32;    // 0x88
  set_mode = 0x1 << (USART2_SEL * 2 );  //01 ....  SYSCLK
  unsafe { 
	let current = core::ptr::read_volatile(addr) ;
        let mut val = current & 0xFF_FF_FF_F3;
        val |= set_mode;
	core::ptr::write_volatile(addr, val) ;
  }

  // set baud rate
  let div = 104;  // 38400  (4MHz/38400 = 104.166666)
  addr = (USART2_BASE + BRR_OFFSET) as *mut u32;  // 
  unsafe { 
	core::ptr::write_volatile(addr, div) ;
  }

  // enable UE/TE/RE
  set_mode = 0x1 << UE_BIT;            // UE_BIT = 0
  set_mode |= 0x1 << TE_BIT;           // TE_BIT = 3
  set_mode |= 0x1 << RE_BIT;           // RE_BIT = 2
  addr = (USART2_BASE + CR1_OFFSET) as *mut u32;  // 
  unsafe { 
	let current = core::ptr::read_volatile(addr) ;
        let mut val = current & 0xff_ff_ff_f2;
        val |= set_mode;
	core::ptr::write_volatile(addr, val) ;
  }

  // send data      just test
  addr = (USART2_BASE + TDR_OFFSET) as *mut u32;  // 0x28
  for _i in 0..20 {
      unsafe {   
           let val = 0x33;
   	   core::ptr::write_volatile(addr, val) ;
      }
      let _ = my_wait(0x10);
   }
}

参考にした動画。。。アドレスとかビット配列が違うが分かりやすい。感謝
#3. How to Configure UART using REGISTERS || STM32F4 - YouTube
I2C版は以下
#4. STM32F4 I2C Using Registers || Master mode || NO HAL || - YouTube

STM32+Rustで初期状態のクロック設定を確認

前回、HALを使わずレジスタ直叩きでLチカさせてみた。次はシリアルをつなぎたいが、シリアルを動かすにはUARTのブロックにどのようなクロックが供給されているのかを理解しないといけない。でないとボーレートが計算できないから。前回のソースではクロックのレジスタは特に設定しておらず、Rustのスタートアップに任せた状態であった。この状態でどのようなクロックで動いているのかを確認(といいながら、、今日はSystemClockまで)
手順はgdbでNUCLEOボードにつないで、gdbのコンソールからレジスタにアクセスする。
クロックレジスタ群のベースが、0x4002 1000なので、そこから04番地ごとにレジスタが並んでいる。

(gdb) p/x *(0x40021000)	
$1 = 0x63
(gdb) p/x *(0x40021004)	
$2 = 0x1072007e
(gdb) p/x *(0x40021008)	
$3 = 0x0
(gdb) p/x *(0x4002100c)	$4 = 0x1000

上記レジスタ値となっておりこれを仕様書と見比べて解釈

クロック制御レジスタ(RCC_CR)..... 0x63
0000 0000 0000 0000 
0000 0000 0110 0011

MSIRANGE[3:0]: 0110    // レンジ 6(約 4 MHz、リセット値)
MSIRDY :1              // MSI クロックレディフラグ
MSION  :1              // MSI クロック有効化

内部クロックソース較正レジスタ(RCC_ICSCR) .... 0x1072007e
10 72 00 7e
0001 0000 0111 0010 
0000 0000 0111 1110

クロック設定レジスタ(RCC_CFGR) .... 0x0
HPRE[3:0]:0               // AHB プリスケーラ 分周なし
SWS[1:0]:0 (MSI)          // システムクロックスイッチステータス
                           // SYSCLKとしてMSIを選択

システムクロックとしては4MHzが出ていると理解。ブロック図で言うと以下

APB1/APB2のPrescalerは1分周でそのまま素通し、UARTに供給されるクロックは、PCLK1(4MHz)、SYSCLK(4MHz)から選ぶことになる。選択の設定はしていないけど、この状態だと供給できるのは4MHz。ここから分周設定して、119200bps等を生成することになるだろう。だけど、、マイコンコアに供給するクロックが4MHzだと本来の性能の1/20なので、まずはPLLを正しく設定して、80MHzが供給できるようにすべきだろう。