E85 / E86 BMW Z4 – Android tablet integration

Post Reply
danergo
Newbie
Newbie
Posts: 3
Joined: Wed Apr 06, 2016 12:06 pm

E85 / E86 BMW Z4 – Android tablet integration

Post by danergo » Mon Oct 22, 2018 10:08 pm

Hello, js_online,

I followed your tutorial to build a similar system as yours. I'm stuck with the motor board actually.

I did not want to remove the original connector from the L9935 board's wire, because all the necessary signals (for SPI) are presented on the connector (except /EN pin on the L9935 - /EN is pulled low automatically as the board receives +5V power supply).
(They all have a small resistor and a filtering capacitor, but anyway the pins on the connector are going to the L9935 itself.)

However I can't succeed with Arduino. (I'm using Mega2560, because I don't have any UNOs here).
Of course, on the Mega, the SPI pins are located differently, that's not an issue.
The very first SPI message arrives to the L9935 and it executes it, but all the following SPI commands are skipped.

But if I restart the arduino, the first SPI message arrives again to L9935 and it executes it.
(Technically: I don't need to 'reset' the motor board to receive an SPI command and execute it. Somewhy arduino seems to stuck in some SPI magic)

Could you help me in this manner?

Thanks very much!
js_online wrote:
Tue May 19, 2015 12:54 am
I'd like to share details of setup using Arduino R3 UNO and native L9935 stepper motor driver using Serial Peripheral Interface (SPI) (without using Arduino Motor Shield).

User avatar
McKoval
Member
Member
Posts: 474
Joined: Thu Mar 31, 2011 10:58 am
Location: Krakow

E85 / E86 BMW Z4 – Android tablet integration

Post by McKoval » Tue Oct 23, 2018 11:15 am

danergo wrote:
Mon Oct 22, 2018 10:08 pm
I did not want to remove the original connector from the L9935 board's wire, because all the necessary signals (for SPI) are presented on the connector (except /EN pin on the L9935 - /EN is pulled low automatically as the board receives +5V power supply).
(They all have a small resistor and a filtering capacitor, but anyway the pins on the connector are going to the L9935 itself.)
Hi,
could you please share photos (and schemes if its possiblle), how did you connect wires to the connector?

I'm stuck at the stage of soldering into the board, and as I don't have time to search for help, the project got stalled almost a year ago..
Image

js_online
Newbie
Newbie
Posts: 24
Joined: Mon May 18, 2015 11:49 pm

E85 / E86 BMW Z4 – Android tablet integration

Post by js_online » Fri Dec 07, 2018 1:45 am

danergo wrote:
Mon Oct 22, 2018 10:08 pm
The very first SPI message arrives to the L9935 and it executes it, but all the following SPI commands are skipped.
As I see from the code activateL9935EN() function re-activates device before each set of commands. You can check L9935 datasheet for details, but this is done for the purpose. The main thing is achieve at least one move of motor, this will indicate that you L9935 board is wired properly and SPI interterface is initialized properly too. Write simple code and make it work.
Simple pseudo-code of what I mean is below:

Code: Select all

#include <SPI.h>
#include <EEPROM.h>
...
//Setup Wiring
//ICSP Pins for Arduino Nano
//1 - MISO  - Arduino Uno Pin 12
//2 - VCC+  - eq used
//3 - SCK   - eq Arduino Uno Pin 13
//4 - MOSI  - eq Arduino Uno Pin 11
//5 - Reset - Not used
//6 - GND   - common ground, can be used as second ground as Arduino Uno had 2 GNDs nearby VCC pin, but only one is present for Arduino Nano 
int EN=9;        //Enable L9935 (Low Active)
...
//setup section
//Setup SPI
  SPI.begin();                           //wake up the SPI bus.
  SPI.setBitOrder(MSBFIRST);             //data to be sent MSB (most significant byte) first
  SPI.setClockDivider(SPI_CLOCK_DIV16);  //1Mhz SPI clock 16/16
  SPI.setDataMode(SPI_MODE3);  
...
//Loop section
//Code 
  //Activating L9935
  digitalWrite(EN, HIGH);     //disable L9935 
  delay(1);
  digitalWrite(EN, LOW);      //enable L9935. Falling slope of EN activates the device. After ten.sck the device is ready to work.
  delay(1);

//One Move to open screen
  setSPIValue(15);    //00001111 CH A enabled, polarity=HIGH (Current=900mA). CH B disabled.
  setSPIValue(56);    //00111000 CH A disabled. CH B enabled, polarity=LOW (Current=900mA).
  setSPIValue(6);     //00000110 CH A enabled, polarity=LOW (Current=900mA). CH B disabled. 
  setSPIValue(57);    //00111001 CH A disabled. CH B enabled, polarity=HIGH (Current=900mA). 

//End Loop section
...
//function 
void setSPIValue(int SPI_COMMAND)
{
  digitalWrite(SLAVESEL, LOW);        //Start of frame. Falling slope of CSN indicates start of frame. //Arduino Nano used for LL9935 only
  SPI.transfer(SPI_COMMAND);          //Send value (0~255) in decimal format. Data transfer (reading SDI into the register) takes place at the rising slopes of SCK.
  //ERR_CODE = digitalRead(DATAIN);   //Read error code
  digitalWrite(SLAVESEL, HIGH);       //End of frame. //Arduino Nano used for LL9935 only
  delay(1);
}


If you are not comfortable with coding I recommend you to achieve working solution with the same setup first and only after it works you can start migrating step by step to different board. You can buy Arduino UNO for 2.5$ on AliExpress, find somewhere similar motor or buy second used screen as I did to play with and pack original screen for "better times".


Actually long time ago I modified code for Arduino Nano. Board takes less space with the same functionality.
You can buy Nano board on AliExpress for 2$.
I am sharing code here.
ICSP Pins on the side are used for SPI interface on Nano board (google pinout of ICSP to do wiring correctly, these are 6 pins at the mid end of the board). Use exactly the same pins!!!
See comments in code for more details.

Code: Select all

/*==========================================================================================================================
Created by JS online, jsonline1111@gmail.com
Last update 04-Jan-2016  v.1.1

Arduino Nano code to operate L9935 stepper motor driver.
Main functionality:
1. Opening screen fully - after ignition is turned on or by open button
2. Closing screen fully - after ignition is turned off or by close button
3. Saving monitor last position to Arduino internal EEPROM memory after ignition is turned off (full step only).
4. Opening by small steps - by step open button
5. Closign by small steps - by step close button (min position is limited to half way - the same as in original behavior)

Enable Serial Monitor Ctrl + Shift + M to see debug messages.
==========================================================================================================================*/
#include <SPI.h>
#include <EEPROM.h>

//L9935 pinning
int SLAVESEL=10; //using digital pin 10 for SPI slave select (used for LL9935 only in NANO version)
//Pins below connected by ICSP 
//int DATAOUT=11;  //using digital pin 11 for MOSI (Master Out Slave In) - master line for sending data to slave
//int DATAIN=12;   //using digital pin 12 for MISO (Master In Slave Out) - the slave sending data to the master
//int SPICLOCK=13; //using digital pin 13 for SCK (Serial Clock) - The clock pulses which synchronize data transmission generated by the master 

//ICSP Pins
//1 - MISO  - Arduino Uno Pin 12
//2 - VCC+  - eq used
//3 - SCK   - eq Arduino Uno Pin 13
//4 - MOSI  - eq Arduino Uno Pin 11
//5 - Reset - Not used
//6 - GND   - common ground, can be used as second ground as Arduino Uno had 2 GNDs nearby VCC pin, but only one is present for Arduino Nano 
int EN=9;        //Enable L9935 (Low Active)

//Board Monitor Buttons
#define OPEN_BUTTON 6       //montior open button full
#define CLOSE_BUTTON 7      //montior close button full
#define STEP_OPEN 4         //montior open button step 
#define STEP_CLOSE 5        //montior close button step

/*Ignition key 12V relay 
R3 UNO, L9935 stepper motor (tablet and other consumers) are powered from +12V ACC (Battery) - X18802 18pin white board monitor socket, Pin 8 = +12V ACC (thru fuse F49), Pin 9 = GND). 
Timer is used for power off delay option. Timer is activated by +12V IGN (Ignition). Power off delay is required to allow close screen after ignition key is turned off and to do not leave 
consumers connected to +12V ACC permanently. Timer logic is the following:
1. after ignition is turned on everything is powered on. Timer Output is operational and connected to +12 ACC: consumers are powered.
2. after ignition is turned off Timer is activated and disconnects +12V Timer Output from +12V Timer Input (ACC) after X seconds (time is adjusted by trimmer and is from 1 sec up to 20 mins). 
After timeout Timer disconnects ACC 12V power, so R3 UNO, L9935 stepper motor and other consumers are also unpowered.*/ 

#define IGN_PWR 8           //igntion 12V power detection. Relay connected to 12V iginition power connects/disconnects GND with IGN_PWR wire.
int ignState = 0;
  
//Delays - miliseconds used for various delays
int DELAY_SS=1;         //very short delay
int DELAY_S=16;         //short delay
int DELAY_N=100;        //standard delay
int DELAY_L=500;        //long delay
int DELAY_LL=2500;        //long delay
int DELAY_INIT=5000;    //delay on ignition

//Monitor positioning: over movement protection and last position. Last saved position works by operating buttons only. Positioning is not tracked if monitor is adjusted manually.
//Last position of monitor is saved after ignition 12v power is turned off. Last position of monitor is restored after ingnition 12v power is tuned on.
float POS_CUR=0;          //monitor current positon full step counter
int POS_CUR_HS=0;         //monitor position half step counter
float POS_STEP_HS=0.25;   //monitor position half step
int POS_STEP_MIN=7;       //limiting min half step position
float POS_MAX=10.7;       //monitor maximum position
int POS_CUR_SAVED=0;      //monitor saved position (full step only)
int POS_CUR_ADDR=0;       //EEPROM address for storing current position (full step only)
int FINE_DOWN=0;          //fine tuning move down flag

//int ERR_CODE;    //read L9935 SPI error code in case if DATAIN pin is connected

// the setup function runs once when you press reset or power the board
void setup() {
  
  //Arduino Nano 
  //SDI, SDO, SCK processed thru ICS
  //pinMode(DATAOUT, OUTPUT);          //SDI
  //pinMode(DATAIN, INPUT);            //SDO not used
  //pinMode(SPICLOCK, OUTPUT);         //SCK
  pinMode(SLAVESEL, OUTPUT);           //CSN
  pinMode(EN, OUTPUT);                 //Enable LL9935

  pinMode(OPEN_BUTTON, INPUT_PULLUP);      //Open Button Full
  pinMode(CLOSE_BUTTON, INPUT_PULLUP);     //Close Button Full
  pinMode(STEP_OPEN, INPUT_PULLUP);        //Open Button Step
  pinMode(STEP_CLOSE, INPUT_PULLUP);       //Close Button Step  
  
  pinMode(IGN_PWR, INPUT_PULLUP);          //Iginition 12V
      
  SPI.begin();                           //wake up the SPI bus.
  SPI.setBitOrder(MSBFIRST);             //data to be sent MSB (most significant byte) first
  SPI.setClockDivider(SPI_CLOCK_DIV16);  //1Mhz SPI clock 16/16
  SPI.setDataMode(SPI_MODE3);            //SPI_MODE3 - CPOL=1 the base value of the clock is one; CPHA=1, data are captured on clock's rising edge and data is propagated on a falling edge

  // initialize serial communication at 9600 bits per second:
  Serial.begin(9600);
  
  //Init screen (activate L9935 and test motor states with high current)
  screenInit();
  
  //detect initial state of ignition 12V power
  ignState = digitalRead(IGN_PWR);
  if (ignState==LOW)   {
    //ignition 12V power is on
    Serial.println("Initial Power ON delay!");        
    delay(DELAY_INIT);     
    Serial.println("Initial Ignition ON!");
    readPOS_CUR(POS_CUR_ADDR); //reading current monitor position from internal EEPROM memory
    screenUp(POS_CUR_SAVED);
  }
  else   {
    //ignition 12V power is off
    screenDown();
  } 
  
}

// the loop function runs over and over again forever
void loop() {
  
  int switchValue; 
  
    switchValue = digitalRead(IGN_PWR);
    if (switchValue != ignState)
    {
      if (switchValue==LOW) {
        //ignition 12V power has been turned on
        //power on delay
        Serial.println("Standard Power ON delay!");        
        delay(DELAY_INIT);         
        Serial.println("Standard Ignition ON!");
        readPOS_CUR(POS_CUR_ADDR); //reading current monitor position from internal EEPROM memory
        screenUp(POS_CUR_SAVED);
      }
      else {
        //ignition 12V power has been turned off
        writePOS_CUR(POS_CUR_ADDR, POS_CUR); //writing current monitor position to internal EEPROM memory
        screenDown();
      }
      //udpate state
      ignState = switchValue;
    }  
  
  switchValue = digitalRead(OPEN_BUTTON);
  if (switchValue==LOW) {
    screenUp(-1);    //sending -1 to execute standard logic without restoring last position 
  }
  
  switchValue = digitalRead(CLOSE_BUTTON);
  if (switchValue==LOW) {
    screenDown();
  }
  
  switchValue = digitalRead(STEP_OPEN);
  if (switchValue==LOW) {
    activateL9935EN();
    stepUpHalf();
  }
  
  switchValue = digitalRead(STEP_CLOSE);
  if (switchValue==LOW) {
    activateL9935EN();
    stepDownHalf();
  }
}

/* L9935 basic info. For more info see L9935 technical data sheet.
Command byte: 6 first bits (0-5) are used for command, Last 2 bits (6,7) are used for command response - return error code (not processed in current code, can be read and processed thru DATAIN pin).
Command byte bits: 76543210 
bit5,bit4: current range of bridge A (Outputs A1 and A2)
bit3: polarity of bridge A
bit2,bit1: current range of bridge B (Outputs B1 and B2)
bit0: polarity of bridge B
bit7,bit6: Error1 and Error 2
Command byte should be converted from binary to decimal (00001111 = 15) and passed as decimal to setValue function.

Current settings (bit5 bit4 for A1 and A2 or bit2 bit1 for B1 and B2). HIGH=H=1; LOW=L=0;
H H = 1 1 = 0 - not used in current code
H L = 1 0 = 60 mA  6% internally sensed - used in current code for parking, screen can be moved manually in this mode.
L H = 0 1 = 550 mA 61 % - used in current code for step opening and step closing
L L = 0 0 = 900 mA 100 % - used in current code for opening and closing screen fully
*/

//functions
void setSPIValue(int SPI_COMMAND)
{
  digitalWrite(SLAVESEL, LOW);        //Start of frame. Falling slope of CSN indicates start of frame. //Arduino Nano used for LL9935 only
  SPI.transfer(SPI_COMMAND);          //Send value (0~255) in decimal format. Data transfer (reading SDI into the register) takes place at the rising slopes of SCK.
  //ERR_CODE = digitalRead(DATAIN);   //Read error code
  digitalWrite(SLAVESEL, HIGH);       //End of frame. //Arduino Nano used for LL9935 only
  delay(DELAY_S);
}

void activateL9935EN(void) {
    Serial.println("Activating L9935!");
    digitalWrite(EN, HIGH);     //disable L9935 
    delay(DELAY_SS);
    digitalWrite(EN, LOW);      //enable L9935. Falling slope of EN activates the device. After ten.sck the device is ready to work.
    delay(DELAY_SS);
}

void screenUp(float POS) { 
  
    activateL9935EN();
    
    float b;   
    //Initial opening: saved positon from internal EEPROM will be used. Other cases: standard logic calculating current and max position.
    if (POS >= 0) b = POS; else b = POS_MAX-POS_CUR;     
    for (int a = 0; a < b; a++)
    {
      stepUp();
    }  
    stepUpHalf();
    screenPark(); 
    
}

void screenDown(void) { 
    
    activateL9935EN();
    FINE_DOWN=1;
    int b = POS_CUR;
    while (POS_CUR >= 0.25)
    {    
      if(POS_CUR>=1.2) stepDown(); else stepDownHalf();
    }
    
    POS_CUR=0;        //resetting current position
    POS_CUR_HS=0;     //resetting half step position
    FINE_DOWN=0;
    screenPark();

}

void stepUp(void) { 
  
   //full step mode processing for complete opening;
   //POS_CUR direction 0 --> 1 --> 2 --> 3
   /*States:
    0 = CH A enabled, polarity=HIGH. CH B disabled.
    1 = CH A disabled. CH B disabled, polarity=LOW.
    2 = CH A enabled, polarity = LOW. CH B disabled.
    1 = CH A disabled. CH B enabled, polarity=HIGH.
   */
   
   if(POS_CUR <= (POS_MAX - 1)) {  
      setSPIValue(15);    //00001111 CH A enabled, polarity=HIGH (Current=900mA). CH B disabled.
      setSPIValue(56);    //00111000 CH A disabled. CH B enabled, polarity=LOW (Current=900mA).
      setSPIValue(6);     //00000110 CH A enabled, polarity=LOW (Current=900mA). CH B disabled. 
      setSPIValue(57);    //00111001 CH A disabled. CH B enabled, polarity=HIGH (Current=900mA). 
      POS_CUR += 1;
      Serial.print("POS_CUR=");Serial.println(POS_CUR); 
   }
}

void stepDown(void) {
   //full step mode processing for complete closing;
   //POS_CUR direction 0 --> 3 --> 2 --> 1

   if(POS_CUR > 0) {     
      setSPIValue(15);    //00001111 CH A enabled, polarity=HIGH (Current=900mA). CH B disabled.
      setSPIValue(57);    //00111001 CH A disabled. CH B enabled, polarity=HIGH (Current=900mA). 
      setSPIValue(7);     //00000111 CH A enabled, polarity=LOW (Current=900mA). CH B disabled.
      setSPIValue(48);    //00110000 CH A disabled. CH B enabled, polarity=LOW (Current=900mA).
      POS_CUR -= 1;
      Serial.print("POS_CUR=");Serial.println(POS_CUR);
   }
}

void screenPark(void) { 
    /*Parking allows manual movement of screen by hand (counter is not working in this case). 
      Stepper motor current is set to HL (internally sensed).
      HH will not fix monitor at all as current is 0.
      HL and LL with 550mA and 900mA current will hold monitor and will not allow manual movement.
    */  
    setSPIValue(47);    //00101111 CH A enabled, polarity=HIGH. CH B disabled (Current 60mA).
    delay(DELAY_S); 
}


void stepUpHalf(void) {
/*Half steps can be used here by using middle states (currently not used) - step 1.2, step 2.2, step 3.2, step 4.2.
In this case both channels are enabled, use current max 550mA (avoid higher current 900mA, overheating risk exists).
*/
   if(POS_CUR <= (POS_MAX - POS_STEP_HS) && POS_CUR >= POS_STEP_MIN) {  
   //POS_CUR_HS direction 0 --> 1 --> 2 --> 3

   if(POS_CUR_HS == 0) {
      setSPIValue(31);    //00011111 CH A enabled, polarity=HIGH (Current=550mA). CH B disabled.  --current step
      setSPIValue(58);    //00111010 CH A disabled. CH B enabled, polarity=LOW. (Current=550mA). --next step
      setSPIValue(60);    //00111100 CH A disabled. CH B enabled, polarity=LOW (Current=60mA).   --park
      Serial.print("STEP1"); 
  }
  
   if(POS_CUR_HS == 1) {
      setSPIValue(58);    //00111010 CH A disabled. CH B enabled, polarity=LOW (Current=550mA).  --current step
      setSPIValue(22);    //00010110 CH A enabled, polarity=LOW (Current=550mA). CH B disabled. --next step
      setSPIValue(38);    //00100110 CH A enabled, polarity=LOW (Current=60mA). CH B disabled.  --park
      Serial.print("STEP2"); 
   }   

   if(POS_CUR_HS == 2) {
      setSPIValue(22);    //00010110 CH A enabled, polarity=LOW (Current=550mA). CH B disabled.   --current step
      setSPIValue(51);    //00110011 CH A disabled. CH B enabled, polarity=HIGH (Current=550mA). --next step
      setSPIValue(53);    //00110101 CH A disabled. CH B enabled, polarity=HIGH (Current=60mA).  --park
      Serial.print("STEP3"); 
   }   

   if(POS_CUR_HS == 3) { 
      setSPIValue(51);    //00110011 CH A disabled. CH B enabled, polarity=HIGH (Current=550mA).  --current step
      setSPIValue(31);    //00011111 CH A enabled (Current=550mA), polarity=HIGH. CH B disabled. --next step
      setSPIValue(47);    //00101111 CH A enabled (Current=60mA), polarity=HIGH. CH B disabled.  --park
      Serial.print("STEP4"); 
   }   
       
      if(POS_CUR_HS == 3) POS_CUR_HS=0; else POS_CUR_HS +=1;   
      Serial.print(" POS_CUR_HS=");Serial.print(POS_CUR_HS); 
      POS_CUR += POS_STEP_HS;
      Serial.print(" POS_CUR=");Serial.println(POS_CUR);
      delay(DELAY_L);
   }
}

void stepDownHalf(void) {
/*Half steps can be used here by using middle states (currently not used) - step 1.2, step 2.2, step 3.2, step 4.2.
  In this case both channels are enabled, use current max 550mA (avoid 900mA current, overheating risk exists).
*/

   if(POS_CUR > POS_STEP_MIN || FINE_DOWN==1) {   
   //POS_CUR_HS direction 0 --> 3 --> 2 --> 1
    
   if(POS_CUR_HS == 0) {
      setSPIValue(31);    //00011111 CH A enabled, polarity=HIGH (Current=550mA). CH B disabled.  --current step
      setSPIValue(59);    //00111011 CH A disabled. CH B enabled, polarity=HIGH (Current=550mA). --next step
      setSPIValue(61);    //00111101 CH A disabled. CH B enabled, polarity=HIGH (Current=60mA).  --park
      Serial.print("STEP1"); 
   }

   if(POS_CUR_HS == 3) {
      setSPIValue(59);    //00111011 CH A disabled. CH B enabled, polarity=HIGH (Current=550mA). --current step
      setSPIValue(23);    //00010111 CH A enabled, polarity=LOW. CH B disabled (Current=550mA). --next step
      setSPIValue(39);    //00100111 CH A enabled, polarity=LOW. CH B disabled (Current=60mA).  --park
      Serial.print("STEP2");
   }   

   if(POS_CUR_HS == 2) {
      setSPIValue(23);    //00010111 CH A enabled, polarity=LOW. CH B disabled (Current=550mA).  --current step
      setSPIValue(58);    //00111010 CH A disabled. CH B enabled, polarity=LOW (Current=550mA). --next step
      setSPIValue(60);    //00111100 CH A disabled. CH B enabled, polarity=LOW (Current=60mA).  --park
      Serial.print("STEP3"); 
   }   

   if(POS_CUR_HS == 1) { 
      setSPIValue(58);    //00111010 CH A disabled. CH B enabled, polarity=LOW (Current=550mA).   --current step
      setSPIValue(31);    //00011111 CH A enabled, polarity=HIGH (Current=550mA). CH B disabled. --next step
      setSPIValue(47);    //00101111 CH A enabled, polarity=HIGH (Current=60mA). CH B disabled.  --park      
      Serial.print("STEP4"); 
   }      

      if(POS_CUR_HS == 0) POS_CUR_HS=3; else POS_CUR_HS -=1;
      
      Serial.print(" POS_CUR_HS=");Serial.print(POS_CUR_HS); 
      POS_CUR -= POS_STEP_HS;
      Serial.print(" POS_CUR=");Serial.println(POS_CUR);
      delay(DELAY_L);
   }
}

void screenInit() {
/*Motor initialization. Required if motor stayed unpowered for long time. Otherwise monitor is not opening fully on the first time.*/
      Serial.println("Initializing Screen!");
      activateL9935EN();
  
      setSPIValue(31);    //00011111 CH A enabled, polarity=HIGH (Current=550mA). CH B disabled.  
      delay(DELAY_LL);
      setSPIValue(58);    //00111010 CH A disabled. CH B enabled, polarity=LOW. (Current=550mA). 
      delay(DELAY_LL);      
  
      screenPark(); 
      Serial.println("Screen Initialization Done!");
} 

void readPOS_CUR(int ADDR) {
//Reads a byte from the EEPROM. Locations that have never been written to have the value of 255. 

  //Serial.println("Reading current position from internal EEPROM!"); 
  POS_CUR_SAVED = EEPROM.read(ADDR); 
  Serial.print("Reading DONE! Current position: ");Serial.println(POS_CUR_SAVED);
}

void writePOS_CUR(int ADDR, int VAL) {
//Write a byte to the EEPROM. The value is written only if differs from the one already saved at the same address. 

  //Serial.print("Writing current positon to internal EEPROM: "); Serial.println(POS_CUR);
  EEPROM.update(ADDR, VAL);
  Serial.println("Writing DONE!");
}

Post Reply