How to write data from Master to Slave by specify register address?

Hello, anyone I wanna make I2C adapter for PC used.
It can let Master to read Slave data of specify register address as attached file.
But can not Master write to Salve by specify register address and multi data
Have anyone use 2 devices of Arduino Nano to simulate?

master_05.ino (3.61 KB)

slave_05.ino (1.46 KB)

Please show a sketch between code-tags in your post.

When the Master requests data from the Slave, the Slave does not know how many bytes the Master will request. That might be a problem sometimes.
However, when the Master sends data to the Slave, then the 'howMany' tells the Slave how many bytes are received.

In the Master:

Wire.beginTransmission( i2c_Slave_address);
Wire.write( register_address_or_location);
Wire.write( first_byte);
Wire.write( second_byte);
Wire.write( third_byte);
Wire.endTransmission();

In the Slave in the onReceive handler, use the first byte to set the register address or index to an array, or the number to a dataset.
If there are more bytes received, then use the rest as data and store them.

This works best if you make an array to simulate registers. In the same way as sensors have registers.

Instead of using this very flexible approach, it is often more straightforward and easier to send and receive a certain package of data. We use 'struct' for that. See the tutorial by Robin2: https://forum.arduino.cc/index.php?topic=683181.0.

In real life with sensors and data, the 'struct' will often be good enough.
For a programming exercise or for some kind of extra programming layer, then simulating registers can be used.

Koepel:
Please show a sketch between code-tags in your post.

When the Master requests data from the Slave, the Slave does not know how many bytes the Master will request. That might be a problem sometimes.
However, when the Master sends data to the Slave, then the 'howMany' tells the Slave how many bytes are received.

In the Master:

Wire.beginTransmission( i2c_Slave_address);

Wire.write( register_address_or_location);
Wire.write( first_byte);
Wire.write( second_byte);
Wire.write( third_byte);
Wire.endTransmission();




In the Slave in the onReceive handler, use the first byte to set the register address or index to an array, or the number to a dataset.
If there are more bytes received, then use the rest as data and store them.

This works best if you make an array to simulate registers. In the same way as sensors have registers.

Instead of using this very flexible approach, it is often more straightforward and easier to send and receive a certain package of data. We use 'struct' for that. See the tutorial by Robin2: https://forum.arduino.cc/index.php?topic=683181.0.

In real life with sensors and data, the 'struct' will often be good enough.
For a programming exercise or for some kind of extra programming layer, then simulating registers can be used.

Thanks Koepel replied, I make a function that name is writeToSlave()
I found Write.write() can not use String data directly.
So I make a String to U8 array ,Is correct?

void writeToSlave(byte DEV_ADR ,byte REG_ADR ,String DATA)
{
  //String to U8array
  int DATA_LENGTH = DATA.length(); 
  char U8array[DATA_LENGTH]; 
  DATA.toCharArray(U8array, DATA_LENGTH); 
  
  Wire.beginTransmission(DEV_ADR);
  Wire.write(REG_ADR);
  Wire.write(U8array);
  Wire.endTransmission();
}

The length of a string is without the zero-terminator.
The Wire.write() assumes that there is a zero-terminator.
I don't know if String.toCharArray adds a zero terminator. Often the String.c_str() is used.

Many will say that using the String object in a basic Arduino board (Uno, Nano, Leonard) should be avoided because of heap fragmentation.

The normal C++ language has the std:string.
The String object is made for Arduino, but everyone can use it everywhere. It can do many things with little source code. Not every Arduino function supports it. The I2C bus uses often fixed size binairy data. You might be the only one that wants the Wire library to be compatible with the String object. The purpose why you want to do that is not clear.

I have no clear view how your code can evolve into something useful. Can you tell what your goal is ?

Koepel:
The length of a string is without the zero-terminator.
The Wire.write() assumes that there is a zero-terminator.
I don't know if String.toCharArray adds a zero terminator. Often the String.c_str() is used.

Many will say that using the String object in a basic Arduino board (Uno, Nano, Leonard) should be avoided because of heap fragmentation.

The normal C++ language has the std:string.
The String object is made for Arduino, but everyone can use it everywhere. It can do many things with little source code. Not every Arduino function supports it. The I2C bus uses often fixed size binairy data. You might be the only one that wants the Wire library to be compatible with the String object. The purpose why you want to do that is not clear.

I have no clear view how your code can evolve into something useful. Can you tell what your goal is ?

My purpose is make a I2C adapter for PC,That command can through UART sent to Arduino
Like: Read:31,00,08 , 31=Device address=0x31, 00 is Register Address=0x00 , 8 is data length = 0d08
Like: Write,31,00,ABCDEFGH , ABCDEFGH is char array {'A','B','C','D','E','F','G','H'}
Master:

#include<Wire.h>

String cmd;
String PC_DevAddr;
String PC_RegAddr;
String PC_DataLength;
String PC_Data;
byte Output_U8_1D[256];
void setup()
{
  Wire.begin();//不指定 代表Master   
  Serial.begin(115200);
  Wire.setClock(400000);//設定400Khz
  Serial.setTimeout(5); 
}
void loop()
{ 
  if (Serial.available()) 
  {
    cmd= Serial.readString();  
    if(SearchStringBefore(cmd,"\r\n") == "Keyword not found!")
    {
    }
    else
    {

      if(SearchStringBefore(cmd,":") == "Read")
      {
        //Read command, ex: Read:31,01,32
        PC_DevAddr = SearchStringBefore(SearchStringAfter(cmd,"Read:"), "," );
        PC_RegAddr = SearchStringBefore(  SearchStringAfter(SearchStringAfter(cmd,"Read:"), "," ) ,  "," );    
        PC_DataLength = SearchStringBefore(   SearchStringAfter(  SearchStringAfter( SearchStringAfter(cmd,"Read:") , "," )  ,  ","  ),    "\r\n"     ); 
        /*
        Serial.println("PC Sent:");
        Serial.println(cmd);
        Serial.print("Dev Address:0x");
        Serial.println(PC_DevAddr);
        Serial.print("Reg Address:0x");
        Serial.println(PC_RegAddr);
        Serial.print("Data length:0d");
        Serial.println(PC_DataLength.toInt());
        */
        requestFromSlave(byte(HexStrToU16(PC_DevAddr)),byte(HexStrToU16(PC_RegAddr)),PC_DataLength.toInt());//DevAdr,RegAdr,Data length
      }
      else
      {
        //Write command, ex: Write:31,1F,255
        PC_DevAddr = SearchStringBefore(SearchStringAfter(cmd,"Write:"), "," );
        PC_RegAddr = SearchStringBefore(  SearchStringAfter(SearchStringAfter(cmd,"Write:"), "," ) ,  "," );    
        PC_Data = SearchStringBefore(   SearchStringAfter(  SearchStringAfter( SearchStringAfter(cmd,"Write:") , "," )  ,  ","  ),    "\r\n"     );
        /*
        Serial.println("PC Sent:");
        Serial.println(cmd);
        Serial.print("Dev Address:0x");
        Serial.println(PC_DevAddr);
        Serial.print("Reg Address:0x");
        Serial.println(PC_RegAddr);
        Serial.print("Data:Str");
        Serial.println( PC_Data  );
        */
        writeToSlave(byte(HexStrToU16(PC_DevAddr)),byte(  HexStrToU16( PC_RegAddr )  ), PC_Data );
      }  
    }
  }    


}
void writeToSlave(byte DEV_ADR ,byte REG_ADR ,String DATA)
{
  //String to U8array
  int DATA_LENGTH = DATA.length(); 
  char U8array[DATA_LENGTH]; 
  DATA.toCharArray(U8array, DATA_LENGTH); 
  Wire.beginTransmission(DEV_ADR);
  Wire.write(REG_ADR);
  Wire.write(U8array);
  Wire.endTransmission();
}
void requestFromSlave(byte DEV_ADR ,byte REG_ADR ,int DATA_LENGTH)
{
  //DATA_LENGTH=8 , n=9
  int n = DATA_LENGTH + 1;     // add one for zero-terminator
  char U8array[n];
  //U8array[9]
  Wire.beginTransmission(DEV_ADR);
  Wire.write(REG_ADR);
  Wire.endTransmission();  
  Wire.requestFrom(DEV_ADR, n - 1);
  //Wire.requestFrom(DEV_ADR, 8);
  //i=0~7
  for( int i=0; i<n-1; i++)
  {
    U8array[i] = Wire.read();   
  }
  U8array[n - 1] = '\0';  // put zero-terminator at the last position, after the data
  //U8array[9]='\0'
  Serial.println( U8array);
}
//搜尋字串後面
String SearchStringAfter(String Source,String Keyword)
{
  int SearchOutIndex;  
  int Keyword_length;
  SearchOutIndex = Source.indexOf(Keyword);
  Keyword_length = Keyword.length();
  if(SearchOutIndex == -1)
  {
    return "Keyword not found!";
  }
  else
  {
    return Source.substring(SearchOutIndex+Keyword.length());
  }  
}
//搜尋字串前面
String SearchStringBefore(String Source,String Keyword)
{
  int SearchOutIndex;  
  int Keyword_length;
  SearchOutIndex = Source.indexOf(Keyword);
  Keyword_length = Keyword.length();
  if(SearchOutIndex == -1)
  {
    return "Keyword not found!";
  }
  else
  {    
    return Source.substring(0,SearchOutIndex);
  }  
}
//Hex字串轉U16數字
unsigned int HexStrToU16(String hexString) 
{
  
  unsigned int decValue = 0;
  int nextInt;
  
  for (int i = 0; i < hexString.length(); i++) {
    
    nextInt = int(hexString.charAt(i));
    if (nextInt >= 48 && nextInt <= 57) nextInt = map(nextInt, 48, 57, 0, 9);
    if (nextInt >= 65 && nextInt <= 70) nextInt = map(nextInt, 65, 70, 10, 15);
    if (nextInt >= 97 && nextInt <= 102) nextInt = map(nextInt, 97, 102, 10, 15);
    nextInt = constrain(nextInt, 0, 15);
    
    decValue = (decValue * 16) + nextInt;
  }
  
  return decValue;
}
//Hex字串轉U8 array
int HexStrToU8array(String hexString)
{
  int hexStringLen=0;
  hexStringLen = hexString.length();
  hexStringLen = hexStringLen/2;    
  for(int i=0;i<hexStringLen;i++)
  {     
     Output_U8_1D[i] = byte(  HexStrToU16( hexString.substring(i*2,i*2+2) )  );
  }
}

Slave:

#include<Wire.h>

#define I2C_SLAVE_ADDRESS 0x31
byte  REG_ADR;
byte REG_0x00[8] = "????????";
byte REG_0x00_ADR = 0;
void setup()
{
  Serial.begin(115200);
  ackToMaster(0x31); 
}
void loop()
{
}
void ackToMaster(byte DEV_ADR)
{
  Wire.begin(DEV_ADR);//指定Address,代表Slave模式
  Wire.setClock(400000);//設定400Khz
  Wire.onReceive(receiveEvent);//取得Reg Address
  delay(0);
  Wire.onRequest(requestEvent);//回饋Reg Address內容
  delay(0);
}
void receiveEvent(int howMany)
{ 
  byte data;
  //讀取Address
  if (howMany >= 1)
  {
    REG_ADR = Wire.read();
    howMany--;
  }
  Serial.print("REG_ADR:");
  Serial.println(REG_ADR);
  
  if(REG_ADR == 0x00)
  {    
    REG_0x00_ADR=0;
    //讀取資料
    while( howMany > 0)
    { 
      Serial.println(howMany);
      data = Wire.read();
      REG_0x00[REG_0x00_ADR] = data;
      REG_0x00_ADR++;    
      howMany--;
    }    
  }
 


  
}
void requestEvent()
{

  byte Data_0x01[30] = {0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x42,0x42,0x42,0x42,0x42,0x42,0x42,0x42,0x42,0x42,0x43,0x43,0x43,0x43,0x43,0x43,0x43,0x43,0x43,0x43};
  byte Data_0x42[] = {0b11000011,0b00011101, 0xFF, 0xFF, 0xFF ,0xFF, 0x01, 0x02};
                  // 0-7            8-15    16-23 24-31 32-39 40-47 48-55 56-63
  /*
    StaticGridSts(0)
    DynamicGridSts(1)   
    InternalErrorStatus(6)
    Communications_Timeout(7)
    ExternalErrorStatus(8)
    ZoomViewResp(10-12)
    Data(16-47)
 */
  switch(REG_ADR)
  {
    case 0x00:
              //收到Master讀取指令,將Reg拋出
              Wire.write( REG_0x00, 8);
              break;
    case 0x01:
              Wire.write("00000000001111111111000000000011",32);
              break;
    case 0x08:
              Wire.write("this is ", 9);
              break;
    case 0x10:
              Wire.write("I2C test", 8);
              break;
    case 0x42:
              Wire.write(Data_0x42, 8);
              break;
  }
}

In Master command:
1.Write:31,00,AAAAAAAA
2.Read:31,00,8
Result is AAAAAAA?
It's weird why only write 7 bytes?

Can you tell more ?
Is the Slave always an Arduino board, or can it be a sensor as well ?
Such things do exist, there might even be a library for it somewhere.
Do you want to type the commands in a serial monitor or terminal app as readable text ?
One of the alternatives is using sscanf().

How close do you think that you are for a working interface ?
I think you are not even halfway. The main trouble is that you want to use a String object with the I2C bus and that the zero-terminator is not handled well.

This is not nice programming. It seems to be over-complicated.

PC_Data = SearchStringBefore(   SearchStringAfter(  SearchStringAfter( SearchStringAfter(cmd,"Write:") , "," )  ,  ","  ),    "\r\n"     );

This part is not okay:

  int DATA_LENGTH = DATA.length();
  char U8array[DATA_LENGTH];
  ...
  Wire.write(U8array);

The length is 8, but Wire.write() expects a zero-terminator string. So where is the zero-terminator ?

This is an array of 8 bytes:

byte REG_0x00[8];

This is a text of 9 bytes (8 question marks plus a zero-terminator)

"????????"

Therefor this is not okay:

byte REG_0x00[8] = "????????";

You should not use a Serial function in the onReceive or onRequest handler.

This topic was automatically closed 120 days after the last reply. New replies are no longer allowed.