chakokuのブログ(rev4)

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

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) ;
  }
}