ARM Architecture and C Language (Wei Dongshan) Study Notes (6) - Bit Operations, Structures and Pointers of C Language


1. Bit operations in C language

The following are some common bit operations:
Bitwise AND (&): Perform an AND operation on each bit of two binary numbers. Only when the corresponding bits of the two numbers are 1, the result is 1, otherwise it is 0.
Bitwise OR (|): Perform OR operation on each bit of two binary numbers, as long as one of the corresponding bits of the two numbers is 1, the result is 1, otherwise it is 0.
Bitwise XOR (^): XOR each bit of two binary numbers, if the corresponding bits of the two numbers are different, the result is 1, otherwise it is 0.
Bitwise inversion (~): Invert each bit of the binary number, that is, 0 is changed to 1, and 1 is changed to 0.
Left shift (<<): Shift each bit of the binary number to the left by the specified number of bits, and fill the vacant bits with 0.
Right shift (>>): Shift each bit of the binary number to the right by the specified number of bits, and fill the vacated bits with 0 or 1, depending on the value of the highest bit before the shift.

Example 1:

#define MPU_ADDR				0X68
void MPU_IIC_Send_Byte(u8 txd)
{
    
                            
    u8 t;   
	MPU_SDA_OUT(); 	    
    MPU_IIC_SCL=0;//拉低时钟开始数据传输
    for(t=0;t<8;t++)
    {
    
                  
        MPU_IIC_SDA=(txd&0x80)>>7;
        txd<<=1; 	  
		    MPU_IIC_SCL=1;
		    MPU_IIC_Delay(); 
		    MPU_IIC_SCL=0;	
		    MPU_IIC_Delay();
    }	 
} 	    
MPU_IIC_Send_Byte((MPU_ADDR<<1)|0);//发送器件地址+写命令	

From an embedded point of view, this is an operation of operating the MPU6050 through IIC.
First, a constant MPU_ADDR is defined to indicate the address of the MPU device. Then a function MPU_IIC_Send_Byte is defined to send a single byte of data. The function receives one parameter txd which is the bytes to be transmitted.
Next, set the SDA pin as an output pin using the MPU_SDA_OUT() function. Then pull the SCL pin low to start the data transfer. Next, use a for loop to traverse the 8-bit data, starting with the highest bit (bit 7), and shifting the data one bit to the left with each iteration. For each bit, the function sets the SDA pin to the value of the current bit (0 or 1), then pulls the SCL pin high and waits for a short time (using the MPU_IIC_Delay() function). Then pull the SCL pin low again and wait another short time.
The function returns when all 8 bits of data have been transferred. Finally, call the MPU_IIC_Send_Byte function to shift the address of the MPU device one bit to the left and set the lowest bit to 0, which means writing data. >

(1) What is (MPU_ADDR<<1)|0?
Answer: In the macro definition, 0x68 is expressed as 01101000, which becomes 11010000 after being shifted one bit to the left, and then bitwise ORed with 0 to get: 11010000 . What is the point of this step? In the I2C protocol, the address of the device is a 7-bit binary number, the highest bit of which must be 0 or 1, indicating read and write operations. The lowest bit is used to indicate that the device will perform a read or write operation, a value of 1 indicates a read operation, and a value of 0 indicates a write operation . Therefore, when sending the device address onto the I2C bus, the lowest bit needs to be set to 0 to ensure a write operation, not a read operation.
(2) From the perspective of C language, what does MPU_IIC_SDA=(txd&0x80)>>7; mean?
Answer: txd&0x80 in the brackets is a bitwise AND operation, and 0x80 in hexadecimal is 128 in decimal, which is 10000000. This operation can get the highest bit in the txd binary representation (that is, the 7th bit), and then shift it to the right by 7 bits, and a value of 0 or 1 will be obtained. This value will be assigned to the MPU_IIC_SDA variable , which is the output value of the SDA pin. This operation is equivalent to extracting the highest bit of txd and writing it to the SDA pin.
(3) What does txd<<=1; mean?
Answer: Move txd one bit to the left. It can be seen from (1) that the function parameter txd=(MPU_ADDR<<1)|0, in this code, each cycle will move txd one bit to the left, so that each bit of txd can be processed bit by bit.
When it is passed in for the first time, txd==11010000, one bit shifted to the left becomes 10100000, at this time, the second time MPU_IIC_SDA=(txd&0x80)>>7; transmits the second highest bit of the data, let txd shift left one bit again, and so on.
After processing the highest bit of txd, the second highest bit becomes the highest bit through a left shift operation, so that it can be processed in the next cycle.
(4) Therefore. What does this code do?
Answer: In the MPU_IIC_Send_Byte function, first set the SDA port to output mode, and then pull the clock line SCL low to start data transmission. Next, send each bit of the data txd to be transmitted through the SDA line in turn. After each transmission of a bit, the clock line SCL is pulled high once, and then pulled low for the transmission of the next bit. After 8 bits have been transferred, the data transfer ends. Setting the lowest bit to 0 is equivalent to converting it to a write command.

Example 2:

//得到加速度值(原始值)
//gx,gy,gz:陀螺仪x,y,z轴的原始读数(带符号)
//返回值:0,成功
//    其他,错误代码
u8 MPU_Get_Accelerometer(short *ax,short *ay,short *az)
{
    
    
    u8 buf[6],res;  
	res=MPU_Read_Len(MPU_ADDR,MPU_ACCEL_XOUTH_REG,6,buf);
	if(res==0)
	{
    
    
		*ax=((u16)buf[0]<<8)|buf[1];  
		*ay=((u16)buf[2]<<8)|buf[3];  
		*az=((u16)buf[4]<<8)|buf[5];
	} 	
    return res;;
}

(1) What does MPU_Read_Len(MPU_ADDR,MPU_ACCEL_XOUTH_REG,6,buf) mean?
Answer: The function of the function is to read multiple bytes of data starting from the specified address from the MPU6050 sensor, store them in the specified array buf, and return the result of the read operation. The four parameters are:
addr: Indicates the address of the I2C device to be read. In the MPU6050 sensor, both the accelerometer and the gyroscope address are 0x68 (01101000 in binary).

reg: Indicates the address of the register to be read. In the MPU6050 sensor, the register addresses of the accelerometer and gyroscope are different, the register address of the accelerometer starts from 0x3B, and the register address of the gyroscope starts from 0x43.

len: Indicates the number of bytes to read. In the MPU6050 sensor, the data of the accelerometer and the gyroscope are 16 bits, so the data of each axis needs to read 2 bytes.

buf: Indicates the array used to store the read data. In the MPU_Get_Accelerometer function, the length of buf is 6, which is used to store the acceleration data of 3 axes, and each axis occupies 2 bytes.
(2) *ax=((u16)buf[0]<<8)|buf[1]; *ay=
((u16)buf[2]<<8)|buf[3];
*az=((u16)buf[4]<<8)|buf[5];
what is this operation?
Answer: The function of this line of code is to combine the two-byte data of buf[0] and buf[1] into a signed short type, and store it in the address pointed to by the pointer variable ax. Specifically, shift the value of buf[0] to the left by 8 bits (that is, multiply by 256), and then add the value of buf[1] to obtain a 16-bit unsigned integer. Then, cast this unsigned integer to type short and use the pointer variable *ax to store the value. For example, before storing 0x1a and 0x2b in buf[0 and buf[1], it needs to be merged into a 16-bit data, and 0x1a is forced to be converted into 16-bit data and shifted to the left by 8 bits to make it into the upper 8 bits. After bitwise ORing with buf[1], the lower eight bits of the data == buf[1], thus successfully merging.

Since the accelerometer data of the MPU6050 sensor is a 16-bit signed integer, it is necessary to convert the combined result of buf[0] and buf[1] into a signed short type. A cast is used here to convert an unsigned integer to a signed integer.

What is the role of bit operations in embedded?

Answer: Bit operations in Embedded can be used to process and manipulate binary data, for example:

Compressed data: Multiple data can be compressed into a binary number by operations such as displacement, bitwise AND, and bitwise OR, thereby reducing storage space and transmission bandwidth.

Improve efficiency: Bit operations can replace operations such as multiplication, division, and modulus, making the code more concise and efficient.

Control hardware: The bit in the register can be set and cleared by bit operation, so as to realize the control and configuration of the hardware.

Bit flags: Bit operations can be used to set and clear flag bits for state management and control

2. What is the structure?

(1) Definition and examples

C language structure (struct) is a user-defined data type that can be used to describe a group of related data items. Structures can contain data items of different types, such as integers, floats, chars, pointers, etc. Each data item in a structure is called a structure member, which can be accessed through the member access operator (.). The definition of the structure is usually placed outside the function, and can be instantiated and used inside the function.

Here is an example of a simple structure definition:

struct student {
    
    
    char name[20];
    int age;
    float score;
};

In the above example, we defined a structure named student, which contains 3 members: name, age and score. Among them, name is a character array, age is an integer, and score is a floating point number. We can create a variable of this structure type in the program to store student information:

struct student stu1;

Next, we can use the member access operator (.) to access the structure members:

strcpy(stu1.name, "Tom");
stu1.age = 18;
stu1.score = 90.5;

The code above assigns the student's name, age, and score as Tom, 18, and 90.5, respectively. We can access struct members using member access operators as follows:

#include <stdio.h>

struct student {
    
    
    char name[20];
    int age;
    float score;
};

int main() {
    
    
    struct student stu1;
    strcpy(stu1.name,"TOM");
    stu1.age = 18;
    stu1.score = 90.5;
    printf("Name: %s\n", stu1.name);
    printf("Age: %d\n", stu1.age);
    printf("Score: %.1f\n", stu1.score);
    return 0;
}

The above code will output the student's name, age, and marks.

(2) Take an example as an introduction when STM32 operates peripherals

1. GPIO initialization function of STM32

code show as below:

void Beep_Init(void)
{
    
    
	GPIO_InitTypeDef gpio_initstruct;
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);//打开GPIOB的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
    GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);//禁止JTAG功能
	gpio_initstruct.GPIO_Mode = GPIO_Mode_Out_PP;			//设置为输出
	gpio_initstruct.GPIO_Pin = GPIO_Pin_3;				//将初始化的Pin脚
	gpio_initstruct.GPIO_Speed = GPIO_Speed_50MHz;		//可承载的最大频率
	GPIO_Init(GPIOB, &gpio_initstruct);					//初始化GPIO
	Beep_Set(BEEP_OFF);		//初始化完成后,关闭蜂鸣器

}

GPIO_InitTypeDef here is a structure type used to initialize and configure the GPIO pins of the STM32 chip.

typedef struct
{
    
    
  uint16_t GPIO_Pin;           
  GPIOSpeed_TypeDef GPIO_Speed;  
  GPIOMode_TypeDef GPIO_Mode;    
  
}GPIO_InitTypeDef;

Pay attention here, why is the writing of the structure here different from the writing in the C language example above?

struct student {
    
    
    char name[20];
    int age;
    float score;
};


typedef struct
{
    
    
  uint16_t GPIO_Pin;           
  GPIOSpeed_TypeDef GPIO_Speed;  
  GPIOMode_TypeDef GPIO_Mode;    
  
}GPIO_InitTypeDef;

A keyword is involved here: typedef .

2. The difference between struct and typedef structure structure

(1) Both struct and typedef can be used to construct structures.
(2) The difference between struct and typedef to construct a structure is:
struct is used to define the name and member variables of the structure;
typedef is a keyword used to define the type alias, which can define an alias for the structure while defining the structure.
(3) When using this structure, you can directly use the name defined after the typedef without reinitializing a struct xx, which improves the simplicity and readability of the code.

3. In-depth understanding and application of structure

1.

#include <stdio.h>

struct student {
    
    
    char name[10];
    int age;
    float score;
};

int main() {
    
    
    struct student stu1[10];//构建一个10个成员的结构体变量
    strcpy(stu1[0].name,"c");
    stu1[0].age = 18;
    stu1[0].score = 90.5;
    stu1[1].age=19;
    printf("Name: %s\n", stu1[0].name);
    printf("Age: %d\n", stu1[0].age);
    printf("Score: %.1f\n", stu1[0].score);
    printf("Name addr: %p\n", &stu1[0].name);
    printf("Age addr: %p\n", &stu1[0].age);
    printf("Score addr: %p\n", &stu1[0].score);
    return 0;
}

The running result is:
insert image description here
the structure is a continuous memory space in the memory, which is used to store the value of each member variable in the structure. Specifically, the memory layout of the structure is as follows:

① Each member variable in the structure is arranged in sequence according to the order of its definition, and there is no interval between each member variable.
② If the member variables in the structure are basic data types, their size and alignment are the same as ordinary variables. For example, a member variable of type int usually needs to occupy 4 bytes of memory space, and its address is usually a multiple of 4.
③If the member variables in the structure are array types, their memory layout is the same as that of ordinary arrays, that is, each element in the array is arranged in sequence, and there is no gap between adjacent elements.
④ If the member variables in the structure are pointer types, their size is usually 4 or 8 bytes, depending on the number of bits of the operating system and compiler. The value of the pointer variable itself is stored as a memory address, pointing to the actual data storage area.
⑤If the member variables in the structure are of structure type, their memory layout is the same as that of ordinary variables, that is, they are arranged in sequence according to the order of structure definition, and there is no gap between each member variable.

2. In the development of stm32 library functions, why can the structure represent the address of the peripheral?

In an embedded system, a structure is usually used to represent the register map (Register Map) of the peripheral . This is because peripherals usually interact with the CPU through memory mapping (Memory-Mapped I/O), that is of peripherals are mapped to a specific memory address space. By accessing these memory addresses, the CPU can read and write the registers of the peripherals, thereby controlling the behavior of the peripherals.
In order to facilitate access to these registers, the register mapping table is defined as a structure, and each member variable in the structure corresponds to a peripheral register . In this way, the registers of the peripherals can be accessed through the structure variables in the program without manually calculating the addresses of the registers.

3. Operators -> and .

(1). The operator is used to access the member variable of the structure type variable, such as struct_name.member_name, where struct_name is the name of the structure type variable, and member_name is the name of the member variable in the structure type.

(2) The -> operator is used to access the member variable of the structure type pointer variable, such as struct_pointer->member_name, where struct_pointer is the name of the structure type pointer variable, and member_name is the name of the member variable in the structure type.

struct person {
    
    
    char name[20];
    int age;
};

struct person p1 = {
    
    "Alice", 20};//初始化成员变量name=alice和age=20
struct person *p2 = &p1;//定义了一个名为p2的指向person类型结构体的指针变量,并将其初始化为指向p1结构体变量的地址
printf("%s %d\n", p1.name, p1.age);         // 使用.运算符访问结构体变量p1中的成员变量
printf("%s %d\n", p2->name, p2->age);       // 使用->运算符访问结构体指针变量p2中的成员变量

Here you can see the difference between the two operators.

Guess you like

Origin blog.csdn.net/qq_53092944/article/details/131178100