简体   繁体   English

i2c 字节写函数,这段代码如何工作? 我无法理解完整

[英]i2c byte write function, how works this code? I can´t understand complety

Can someone explain me how works this lines有人能解释一下这条线是如何工作的吗

template <class T>
...const T& value)...
.
.
. 
const uint8_t* p = (const uint8_t*)(const void*)&value;

on this code (i2c byte write for eeprom)在此代码上(eeprom 的 i2c 字节写入)

template <class T>
uint16_t writeObjectSimple(uint8_t i2cAddr, uint16_t addr, const T& value){

      const uint8_t* p = (const uint8_t*)(const void*)&value;    
      uint16_t i;
      for (i = 0; i < sizeof(value); i++){
            Wire.beginTransmission(i2cAddr);
                  Wire.write((uint16_t)(addr >> 8));  // MSB
                  Wire.write((uint16_t)(addr & 0xFF));// LSB
                  Wire.write(*p++);
            Wire.endTransmission();
            addr++;
            delay(5);  //max time for writing in 24LC256
      }
      return i;
}

template <class T>
uint16_t readObjectSimple(uint8_t i2cAddr, uint16_t addr, T& value){

            uint8_t* p = (uint8_t*)(void*)&value;
            uint8_t objSize = sizeof(value);
            uint16_t i;      
            for (i = 0; i < objSize; i++){
                  Wire.beginTransmission (i2cAddr);
                        Wire.write((uint16_t)(addr >> 8));  // MSB
                        Wire.write((uint16_t)(addr & 0xFF));// LSB
                  Wire.endTransmission();
                  Wire.requestFrom(i2cAddr, (uint8_t)1);         
                  if(Wire.available()){
                        *p++ = Wire.read();
                  }
                  addr++;
            }
            return i;
}

I think the lines works like pointers?我认为这些线条像指针一样工作? I can't understand how the code store correctly each type of data when I do that当我这样做时,我无法理解代码如何正确存储每种类型的数据

struct data{
    uint16_t yr;
    uint8_t mont;
    uint8_t dy;
    uint8_t hr;
    uint8_t mn;
    uint8_t ss; 
};
.
.
.
data myString;
writeObjectSimple(0x50,0,myString);

And then recover the values correctly using然后使用正确恢复值

data myStringRead;
readObjectSimple(0x50,0,myStringRead)

the function i2c byte write detect some special character between each data type to store in the correct place?函数 i2c byte write 检测每种数据类型之间的一些特殊字符以存储在正确的位置?

thx谢谢

First I have to state, that this code has been written by a person not fully familiar with the differences between how C++ and C deal with pointer types.首先我必须声明,这段代码是由一个不完全熟悉 C++ 和 C 如何处理指针类型之间的差异的人编写的。 My impression that this person has a strong C background and was simply trying to shut up a C++ compiler to throw warnings.我的印象是这个人有很强的 C 背景,只是试图关闭 C++ 编译器来抛出警告。

Let's break down what this line of code does让我们分解一下这行代码的作用

const uint8_t* p = (const uint8_t*)(const void*)&value;

The intent here is to take a buffer of an arbitrary type – which we don't even know here, because it's a template type – and treat it as if it were a buffer of unsigned 8 bit integers.这里的目的是获取一个任意类型的缓冲区——我们在这里甚至不知道,因为它是一个模板类型——并将其视为一个无符号 8 位整数的缓冲区。 The reason for that is, that later on the contents of this buffer are to be sent over a wire bit by bit (this is called "bit banging").这样做的原因是,稍后该缓冲区的内容将通过线路逐位发送(这称为“位碰撞”)。

In C the way to do this would have been to write在 C 中,这样做的方法是编写

const uint8_t* p = (const void*)&value;

This works, because in C is perfectly valid to assign a void* typed pointer to a non-void pointer and vice versa.这是有效的,因为在 C 中将void*类型的指针分配给非 void 指针是完全有效的,反之亦然。 The important rule set by the C language however is, that – technically – when you convert a void* pointer to a non-void type, then the void* pointer must have been obtained by taking the address ( & operator) of an object of the same type.然而,C 语言设置的重要规则是——从技术上讲——当你将void*指针转换为非 void 类型时, void*指针必须是通过获取对象的地址( &运算符)获得的相同的类型。 In practice however implementations allow casting of a void* types pointer to any type that is alignment compatible to the original object and for most – but not all!然而,在实践中,实现允许将void*类型指针转换为与原始对象对齐兼容的任何类型,并且对于大多数 - 但不是全部! – architectures uint8_t buffers may be aligned to any address. – 架构uint8_t缓冲区可以与任何地址对齐。

However in C++ this back-and-forth assignment of void* pointers is not allowed implicitly.然而,在 C++ 中,这种来回分配的void*指针是不允许隐式的。 C++ requires an explicit cast (which is also why you can often see C++ programmers writing in C code something like struct foo *p = (struct foo*)malloc(…) ). C++ 需要显式转换(这也是为什么您经常会看到 C++ 程序员用 C 代码编写类似struct foo *p = (struct foo*)malloc(…) )。

So what you'd write in C++ is所以你用 C++ 写的是

const uint8_t* p = (const uint8_t*)&value;

and that actually works and doesn't throw any warnings.这实际上有效并且不会发出任何警告。 However some static linter tools will frown upon it.然而,一些静态的 linter工具会对它不屑一顾 So the first cast (you have to read casts from right to left) first discards the original typing by casting to void* to satisfy the linter, then the second cast casts to the target type to satisfy the compiler.因此,第一次强制转换(您必须从右到左阅读强制转换)首先通过强制转换为void*来丢弃原始类型以满足 linter,然后第二次强制转换为目标类型以满足编译器。

The proper C++ idiom however would have been to use a reinterpret_cast which most linters will also accept然而,正确的 C++ 习惯用法是使用reinterpret_cast ,大多数 linter 也将接受

const uint8_t* p = reinterpret_cast<const uint8_t*>(&value);

However all this casting still invokes implementation defined behavior and when it comes to bit banging you will be hit by endianess issues (the least).然而,所有这些转换仍然调用实现定义的行为,当涉及到位碰撞时,您将受到字节序问题(最少)的影响。

Bit banging itself works by extracting each bit of a value one by one and tickling the wires that go in and out of a processor's port accordingly. Bit banging 本身通过一个一个地提取值的每一位并相应地对进出处理器端口的电线进行挠曲来工作。 The operators used here are >> to shift bits around and binary & to "select" particular bits.这里使用的运算符是>>来移动位和二进制&来“选择”特定位。

So for example when you see a statement like例如,当您看到类似的语句时

(v & (1<<x))

then what is does is checking if bit number x is set in the variable v .然后是检查是否在变量v设置了位号x You can also mask whole subsets of the bits in a variable, by masking (= applying the binary & operator – not to be confused with the unary "address of" operator that yields a pointer).您还可以通过屏蔽(= 应用二元&运算符 - 不要与产生指针的一元“地址”运算符混淆)来屏蔽变量中位的整个子集。

Similarly you can use the |同样,您可以使用| operator to "overlay" the bits of several variables onto each other.运算符将几个变量的位相互“叠加”。 Combined with the shift operators you can use this to build the contents of a variable bit-by-bit (with the bits coming in from a port).结合移位运算符,您可以使用它来逐位构建变量的内容(位来自端口)。

The target device is an I2C EEPROM, so the general form for writing is to send the destination address followed by some data.目标设备是一个 I2C EEPROM,所以写的一般形式是发送目标地址,然后是一些数据。 To read data from the EEPROM, you write the source address and then switch to a read mode to clock out the data.要从 EEPROM 读取数据,您需要写入源地址,然后切换到读取模式以时钟输出数据。

First of all, the line:首先,该行:

const uint8_t* p = (const uint8_t*)(const void*)&value;

is simply taking the templated type T and casting away its type, and converting it to a byte array ( uint8_t* ).只是采用模板化类型T并丢弃其类型,并将其转换为字节数组 ( uint8_t* )。 This pointer is used to advance one byte at a time through the memory containing value .该指针用于在包含value的内存中一次推进一个字节。

In the writeObjectSimple method, it first writes the 16-bit destination address (in big-endian format) followed by a data byte (where p is a data pointer into value ):writeObjectSimple方法中,它首先写入 16 位目标地址(采用大端格式),然后写入一个数据字节(其中p是指向value的数据指针):

Wire.write(*p++);

This writes the current byte from value and moves the pointer along one byte.这将从value写入当前字节并将指针沿一个字节移动。 It repeats this for however many bytes are in the type of T .无论T类型的字节有多少,它都会重复此操作。 After writing each byte, the destination address is also incremented, and it repeats.写入每个字节后,目标地址也递增,并重复。

When you code:编码时:

data myString;
writeObjectSimple(0x50,0,myString);

the templated writeObjectSimple will be instantiated over the data type, and will write its contents (one byte at a time) starting at address 0, to the device with address 0x50.模板化的writeObjectSimple将在data类型上实例化,并将其内容(一次一个字节)从地址 0 开始写入地址为 0x50 的设备。 It uses sizeof(data) to know how many bytes to iterate over.它使用sizeof(data)来知道要迭代多少字节。

The read operation works very much the same way, but writes the source address and then requests a read (which is implicit in the LSB of the I2C address) and reads one byte at a time back from the device.读取操作的工作方式非常相似,但写入源地址,然后请求读取(隐含在 I2C 地址的 LSB 中)并一次从设备读取一个字节。

the function i2c byte write detect some special character between each data type to store in the correct place?函数 i2c byte write 检测每种数据类型之间的一些特殊字符以存储在正确的位置?

Not really, each transaction simply contains the address followed by the data.并非如此,每笔交易只包含地址后跟数据。

[addr_hi] [addr_lo] [data]

Having explained all that, operating one byte at a time is a very inefficient way of achieving this.已经解释了所有这些,一次操作一个字节是实现这一目标的一种非常低效的方式。 The device is a 24LC256, and this 24LC family of EEPROMs support sequential writes (up to a page) in size in a single I2C transaction.该器件是 24LC256,该 24LC 系列 EEPROM 支持在单个 I2C 事务中按大小顺序写入(最多一页)。 So you can easily send the entire data structure in one transfer, and avoid having to retransmit the address (2 bytes for every byte of data).因此,您可以轻松地在一次传输中发送整个data结构,并避免重新传输地址(每个数据字节 2 个字节)。 Have a look in the datasheet for the full details.查看数据表了解完整的细节。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM