stm32入门学习10-软件I2C和陀螺仪模块

news2024/11/23 15:52:32

(一)I2C通信

(1)通信方式

I2C是一种同步半双工的通信方式,同步指的是通信双方时钟为一个时钟,半双工指的是在同一时间只能进行接收数据或发送数据,其有一条时钟线(SCL)和一条数据线(SDA),使用I2C通信要遵守一定的规定

其收发数据有几种模式

(1)开启传输:在时钟线(SCL)为1时将数据线(SDA)由1转为0;

(2)结束传输:在时钟线(SCL)为1时将数据线(SDA)由0转为1;

(3)传输数据:在时钟线(SCL)为0时在数据线(SDA)中写入数据,将时钟线(SCL)由0变为1发送;

(4)接收数据:先释放数据线(SDA置1),后将时钟线(SCL)由0变为1后读取数据线(SDA);

(5)接收响应:I2C在主机传输8位数据时会给主机响应,为1则接收失败,为0则接收成功,主机需要接收响应,接收方式和接收数据相同;

(6)发送响应:I2C在主机接收8位数据时要给从机响应,给1则从机停止继续向主机发送,给0则继续向主机发送数据

这里可以注意到,I2C通信中只有开始和结束时在时钟线(SCL)为1时操作的数据线(SDA),其余都是在时钟线为0时操作数据线;

通信过程中其有一定的协议

(1)发送数据:(1)开启传输;(2)写入外设地址写模式;(3)接收从机响应;(4)写入外设寄存器的地址;(5)接收响应;(6)写入数据;(7)接收响应;(8)结束传输

(2)接收数据:(1)开启传输;(2)写入外设地址写模式;(3)接收从机响应;(4)写入外设寄存器的地址;(5)接收响应;(6)重新开启传输;(7)写入外设地址读模式;(8)接收响应;(9)读取数据;(10)发送响应;(11)结束传输

(2)软件模拟

这样我们就可以用代码来模拟I2C通信,我们选择时钟线(SCL)接在PA0口上,数据线(SDA)接在PA1口上

#define SCL GPIO_Pin_0
#define SDA GPIO_Pin_1

(1)时钟打开和初始化

这里要注意的是I2C是采用开漏输出的,即默认为高电平,我们这里端口输出模式也要选择开漏输出

void i2c_init()
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	
	GPIO_InitTypeDef gpio_init;
	gpio_init.GPIO_Mode = GPIO_Mode_Out_OD;
	gpio_init.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1;
	gpio_init.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &gpio_init);
	
	GPIO_SetBits(GPIOA, GPIO_Pin_0 | GPIO_Pin_1);
}

(2)置高低电平和读取数据

为了方便给SCL和SDA高电平或低电平,封装几个函数方便后面调用,分别为置某个端口高电平、置某个端口低电平、读取某个端口的值

void set(uint16_t io)
{
	GPIO_SetBits(GPIOA, io);
	Delay_us(10);
}

void reset(uint16_t io)
{
	GPIO_ResetBits(GPIOA, io);
	Delay_us(10);
}

unsigned char read(uint16_t io)
{
	unsigned char result;
	result = GPIO_ReadInputDataBit(GPIOA, io);
	return result;
}

(3)开启传输

和前面讲的一样,我们要在SCL高电平的情况下把SDA由高电平拉到低电平,然后拉低SCL,为后面的传输数据做准备

void i2c_start()
{
	set(SDA);
	set(SCL);
	reset(SDA);
	reset(SCL);
}

由于我们不知道在开始前数据线和时钟线是否一定为高电平,因此我们先把两者置高电平后再拉低

(4)结束传输

我们要在SCL为高电平的情况下把SDA由低电平上拉为高电平

void i2c_end()
{
	reset(SDA);
	set(SCL);
	set(SDA);
}

我们不知道在要结束的时候SDA是否为低电平,因此我们先拉低SDA,为后面的上拉做准备,至于我们的时钟线SCL,我们确保其在除了结束传输这一步外每一步结束时都为低电平,可以注意一下其他步骤的代码

(5)传输一个字节

我们在时钟线SCL为低电平的时候把数据放在数据线SDA上,然后把SCL拉高为高电平即完成传输,循环8次,传输一个字节

void i2c_send_byte(unsigned char message)
{
	unsigned char i;
	for(i = 0; i < 8; i++)
	{
		if ((message & (0x80>>i)) == 0)
			reset(SDA);
		else
			set(SDA);
		set(SCL);
		reset(SCL);
	}
}

这里的数据传输为高位先行,先传输高位,我们使用“与”的方法依次提取从高位到低位的八位bit,将数据message和1000 0000 的右移i位相与;

(6)接收一个字节

先释放数据线SDA(将其置1),后在SCL置1后读取SDA,循环八次,读取一个字节

unsigned char i2c_receive_byte()
{
	unsigned char i;
	unsigned char message = 0x00;
	set(SDA);
	for (i = 0; i < 8; i++)
	{
		set(SCL);
		if (read(SDA) == 1)
			message |= (0x80>>i);
		reset(SCL);
	}
	return message;
}

这里使用“或”的方式来接收数据,如果接收到i位数据为1,则将message与1000 0000 右移i位相或,第i位置1,其余位保持不变;

(7)发送应答

发送应答和发送单个bit做法相同,需要在SCL低电平期间将应答置于SDA中,再SCL上拉发送

void i2c_send_ack(unsigned char ack)
{
	if (ack == 0)
		reset(SDA);
	else 
		set(SDA);
	set(SCL);
	reset(SCL);
}

(8)接收应答

接收应答和接收单个bit做法相同,需要先释放数据线SDA(置1),在SCL置高电平时读取数据线的值

unsigned char i2c_receive_ack()
{
	unsigned char ack;
	set(SDA);
	set(SCL);
	ack = read(SDA);
	reset(SCL);
	return ack;
}

(3)封装

这样I2C的几种基本通信方式就写好了,我们只需要把其封装,再按照发送接收的规定,就可以读取改写外设寄存器,最后.c和.h文件可以这样

#include "stm32f10x.h"                  // Device header
#include "Delay.h"


#define SCL GPIO_Pin_0
#define SDA GPIO_Pin_1

void i2c_init()
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	
	GPIO_InitTypeDef gpio_init;
	gpio_init.GPIO_Mode = GPIO_Mode_Out_OD;
	gpio_init.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1;
	gpio_init.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &gpio_init);
	
	GPIO_SetBits(GPIOA, GPIO_Pin_0 | GPIO_Pin_1);
}

void set(uint16_t io)
{
	GPIO_SetBits(GPIOA, io);
	Delay_us(10);
}

void reset(uint16_t io)
{
	GPIO_ResetBits(GPIOA, io);
	Delay_us(10);
}

unsigned char read(uint16_t io)
{
	unsigned char result;
	result = GPIO_ReadInputDataBit(GPIOA, io);
	return result;
}

void i2c_start()
{
	set(SDA);
	set(SCL);
	reset(SDA);
	reset(SCL);
}

void i2c_end()
{
	reset(SDA);
	set(SCL);
	set(SDA);
}

void i2c_send_byte(unsigned char message)
{
	unsigned char i;
	for(i = 0; i < 8; i++)
	{
		if ((message & (0x80>>i)) == 0)
			reset(SDA);
		else
			set(SDA);
		set(SCL);
		reset(SCL);
	}
}

unsigned char i2c_receive_byte()
{
	unsigned char i;
	unsigned char message = 0x00;
	set(SDA);
	for (i = 0; i < 8; i++)
	{
		set(SCL);
		if (read(SDA) == 1)
			message |= (0x80>>i);
		reset(SCL);
	}
	return message;
}

void i2c_send_ack(unsigned char ack)
{
	if (ack == 0)
		reset(SDA);
	else 
		set(SDA);
	set(SCL);
	reset(SCL);
}

unsigned char i2c_receive_ack()
{
	unsigned char ack;
	set(SDA);
	set(SCL);
	ack = read(SDA);
	reset(SCL);
	return ack;
}
#ifndef __I2C_H__
#define __I2C_H__

void i2c_init(void);
void i2c_start(void);
void i2c_end(void);
void i2c_send_byte(unsigned char message);
unsigned char i2c_receive_byte(void);
void i2c_send_ack(unsigned char ack);
unsigned char i2c_receive_ack(void);

#endif

(二)MPU-6050

MPU-6050是一个可以测量加速度和角速度的陀螺仪加速度计,其外设写地址为0xD0,外设的读地址为0xD1,其测量的加速度和角速度存在其内部寄存器中,我们通过I2C访问其内部寄存器来读取测量值

经过我们前面的程序,我们已经有了这几个函数:(1)开启传输函数;(2)结束传输函数;(3)发送一个字节函数;(4)接收一个字节函数;(5)发送应答函数;(6)接收应答函数;通过这些函数我们就可以操作寄存器了

(1)写某个位置的一个字节

按照我们之前的说法,我们要写某个寄存器,我们先要开启传输,发送外设写地址,接收应答,发送寄存器地址,接收应答,写入数据,接收应答,结束传输,对应下面的每一行代码

void mpu_write(unsigned char address, unsigned char message)
{
	i2c_start();
	i2c_send_byte(mpu6050_address);
	ack = i2c_receive_ack();
	i2c_send_byte(address);
	ack = i2c_receive_ack();
	i2c_send_byte(message);
	ack = i2c_receive_ack();
	i2c_end();
	Delay_us(10);
}

(2)读某个位置的一个字节

和前面说的一样,要读取某个外设的寄存器,我们需要开启传输,发送外设写地址,接收应答,发送寄存器地址,接收应答,重新开启传输,发送外设读地址,接收应答,读取数据,发送应答,结束传输,对应代码为

uint8_t mpu_read(unsigned char address)
{
	uint8_t message = 0x00;
	i2c_start();
	i2c_send_byte(mpu6050_address);
	ack = i2c_receive_ack();
	i2c_send_byte(address);
	ack = i2c_receive_ack();
	
	i2c_start();
	i2c_send_byte(mpu6050_address | 0x01);
	ack = i2c_receive_ack();
	message = i2c_receive_byte();
	i2c_send_ack(1);
	i2c_end();

	return message;
}

(3)初始化MPU-6050

MPU-6050默认为睡眠模式,如果不初始化不会进行数据转换,这里直接操作寄存器转换如下,主要为停止睡眠模式、选择时钟等,顺便把I2C也在此初始化

void mpu_init()
{
	i2c_init();
	
	mpu_write(0x6B, 0x01);	//PWR_MGMT_1 -> 0000 0001
	mpu_write(0x6C, 0x00);	//PWR_MGMT_2 -> 0000 0000
	mpu_write(0x19, 0x09);	//SMPLRT_DIV -> 0000 1001
	mpu_write(0x1A, 0x06);	//CONFIG -> 0000 0110
	mpu_write(0x1B, 0x18);	//GYRO_CONFIG -> 0001 1000
	mpu_write(0x1C, 0x18);	//ACCEL_CONFIG -> 0001 1000
}

(4)传输数据

MPU-6050中有六个数据,分别为x、y、z轴加速度,x、y、z轴的角速度,我们需要一次返回六个变量,可以用数组,但这里用一个结构体来返回六个变量

typedef struct inf
{
	int x_acceleration;
	int y_acceleration;
	int z_acceleration;
	
	int x_angular_velocity;
	int y_angular_velocity;
	int z_angular_velocity;
} information;

这六个变量都是16位数据,其高八位和低八位存在不同的寄存器中

我们可以通过把高位左移8位“或”低位的方式来读取其16位寄存器

这里记录下常见的错误:如果你在读取某些有符号数据时其逼近但不超过最大值,但是却从来没有为负数,这可能是因为在位数小的数据强行转换为位数大的数据中出现的错误,其会在位数小数据的前面自动补0,而众所周知我们负数的补码是要在前面补1的,比如8位有符号数据1111 1101为-3,若将其强行转化为16位有符号数据则默认为0000 0000 1111 1101,这就是一个很大的正数了,我们要的16位-3应该为1111 1111 1111 1101

information mpu_get_inf()
{
	uint8_t inf_L;
	uint8_t inf_H;
	information infor;
	inf_H = mpu_read(0x3B);
	inf_L = mpu_read(0x3C);
	infor.x_acceleration = (inf_H<<8) | inf_L;
	inf_H = mpu_read(0x3D);
	inf_L = mpu_read(0x3E);
	infor.y_acceleration = (inf_H<<8) | inf_L;
	inf_H = mpu_read(0x3F);
	inf_L = mpu_read(0x40);
	infor.z_acceleration = (inf_H<<8) | inf_L;
	
	inf_H = mpu_read(0x43);
	inf_L = mpu_read(0x44);
	infor.x_angular_velocity = (inf_H<<8) | inf_L;
	inf_H = mpu_read(0x45);               
	inf_L = mpu_read(0x46);               
	infor.y_angular_velocity = (inf_H<<8) | inf_L;
	inf_H = mpu_read(0x47);               
	inf_L = mpu_read(0x48);               
	infor.z_angular_velocity = (inf_H<<8) | inf_L;
	
	if (infor.x_acceleration & 0x8000)
	{
		infor.x_acceleration |= 0xFFFF0000;
	}
	if (infor.y_acceleration & 0x8000)
	{
		infor.y_acceleration |= 0xFFFF0000;
	}
	if (infor.z_acceleration & 0x8000)
	{
		infor.z_acceleration |= 0xFFFF0000;
	}
	if (infor.x_angular_velocity & 0x8000)
	{
		infor.x_angular_velocity |= 0xFFFF0000;
	}
	if (infor.y_angular_velocity & 0x8000)
	{
		infor.y_angular_velocity |= 0xFFFF0000;
	}
	if (infor.z_angular_velocity & 0x8000)
	{
		infor.z_angular_velocity |= 0xFFFF0000;
	}
	
	return infor;
}

最后的一连串if就是来解决类型转化间的错误的

这样我们就成功读到了寄存器内的数据并返回一个包含所有数据的结构体

(5)封装与声明

最后的.c 和 .h代码如下

#include "stm32f10x.h"                  // Device header
#include "i2c.h"
#include "Delay.h"

#define mpu6050_address 0xD0
unsigned char ack;

void mpu_write(unsigned char address, unsigned char message)
{
	i2c_start();
	i2c_send_byte(mpu6050_address);
	ack = i2c_receive_ack();
	i2c_send_byte(address);
	ack = i2c_receive_ack();
	i2c_send_byte(message);
	ack = i2c_receive_ack();
	i2c_end();
	Delay_us(10);
}

uint8_t mpu_read(unsigned char address)
{
	uint8_t message = 0x00;
	i2c_start();
	i2c_send_byte(mpu6050_address);
	ack = i2c_receive_ack();
	i2c_send_byte(address);
	ack = i2c_receive_ack();
	
	i2c_start();
	i2c_send_byte(mpu6050_address | 0x01);
	ack = i2c_receive_ack();
	message = i2c_receive_byte();
	i2c_send_ack(1);
	i2c_end();

	return message;
}

void mpu_init()
{
	i2c_init();
	
	mpu_write(0x6B, 0x01);	//PWR_MGMT_1 -> 0000 0001
	mpu_write(0x6C, 0x00);	//PWR_MGMT_2 -> 0000 0000
	mpu_write(0x19, 0x09);	//SMPLRT_DIV -> 0000 1001
	mpu_write(0x1A, 0x06);	//CONFIG -> 0000 0110
	mpu_write(0x1B, 0x18);	//GYRO_CONFIG -> 0001 1000
	mpu_write(0x1C, 0x18);	//ACCEL_CONFIG -> 0001 1000
}

typedef struct inf
{
	int x_acceleration;
	int y_acceleration;
	int z_acceleration;
	
	int x_angular_velocity;
	int y_angular_velocity;
	int z_angular_velocity;
} information;

information mpu_get_inf()
{
	uint8_t inf_L;
	uint8_t inf_H;
	information infor;
	inf_H = mpu_read(0x3B);
	inf_L = mpu_read(0x3C);
	infor.x_acceleration = (inf_H<<8) | inf_L;
	inf_H = mpu_read(0x3D);
	inf_L = mpu_read(0x3E);
	infor.y_acceleration = (inf_H<<8) | inf_L;
	inf_H = mpu_read(0x3F);
	inf_L = mpu_read(0x40);
	infor.z_acceleration = (inf_H<<8) | inf_L;
	
	inf_H = mpu_read(0x43);
	inf_L = mpu_read(0x44);
	infor.x_angular_velocity = (inf_H<<8) | inf_L;
	inf_H = mpu_read(0x45);               
	inf_L = mpu_read(0x46);               
	infor.y_angular_velocity = (inf_H<<8) | inf_L;
	inf_H = mpu_read(0x47);               
	inf_L = mpu_read(0x48);               
	infor.z_angular_velocity = (inf_H<<8) | inf_L;
	
	if (infor.x_acceleration & 0x8000)
	{
		infor.x_acceleration |= 0xFFFF0000;
	}
	if (infor.y_acceleration & 0x8000)
	{
		infor.y_acceleration |= 0xFFFF0000;
	}
	if (infor.z_acceleration & 0x8000)
	{
		infor.z_acceleration |= 0xFFFF0000;
	}
	if (infor.x_angular_velocity & 0x8000)
	{
		infor.x_angular_velocity |= 0xFFFF0000;
	}
	if (infor.y_angular_velocity & 0x8000)
	{
		infor.y_angular_velocity |= 0xFFFF0000;
	}
	if (infor.z_angular_velocity & 0x8000)
	{
		infor.z_angular_velocity |= 0xFFFF0000;
	}
	
	return infor;
}
#ifndef __MPU_H__
#define __MPU_H__

typedef struct inf
{
	int x_acceleration;
	int y_acceleration;
	int z_acceleration;

	int x_angular_velocity;
	int y_angular_velocity;
	int z_angular_velocity;
} information;

//extern struct information;
void mpu_init(void);
information mpu_get_inf(void);

#endif

(三)主函数调用

由于我们的MPU-6050初始化中已经包含了I2C的初始化,我们只要引用MPU头文件即可,我们在第1列显示加速度,在第9列显示角速度

#include "stm32f10x.h"                  // Device header
#include "mpu6050.h"
#include "OLED.h"
#include "Delay.h"

int main()
{
	information num;
	mpu_init();
	OLED_Init();
	while(1)
	{
		num = mpu_get_inf();
		OLED_ShowSignedNum(1, 1, num.x_acceleration, 5);
		OLED_ShowSignedNum(2, 1, num.y_acceleration, 5);
		OLED_ShowSignedNum(3, 1, num.z_acceleration, 5);
		
		OLED_ShowSignedNum(1, 9, num.x_angular_velocity, 5);
		OLED_ShowSignedNum(2, 9, num.y_angular_velocity, 5);
		OLED_ShowSignedNum(3, 9, num.z_angular_velocity, 5);
		
		Delay_ms(500);
	}
	return 0;
}

(三)总结

通过读取加速度和角速度,我们通过I2C通信协议读写MPU-6050,了解了I2C的工作原理和手动模拟I2C的工作流程,解决了一些遇到的错误,积累了错误处理的经验

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1987329.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

MyBatis补充

控制类和dao层接口以及mapper中的xml是怎样的关联的&#xff1f; 在Mybatis中&#xff0c;控制类和dao层接口是通过mapper的xml文件进行连接的。 控制类调用dao层接口中的方法&#xff0c;通过接口实现进行访问数据库操作。dao层接口定义数据库操作的方法&#xff0c;提供给控制…

第6章>>实验7:PS(ARM)端Linux RT与PL端FPGA之间(通过Memory存储器进行通信和交互)《LabVIEW ZYNQ FPGA宝典》

1、实验内容 上一节实验里面介绍的Reg寄存器通道比较适合在PS端和PL端之间传递标量数据&#xff0c;也就是单个元素&#xff0c;如果要传递多个元素的数组或者连续数据流的话&#xff0c;Reg寄存器通道就不是很合适了。 本节实验我们向大家讲解如何借助Memory存储器通道在PS&am…

【Mind+】掌控板入门教程06 多彩呼吸灯

呼吸灯是指模仿动物一呼一吸使灯光由亮到暗逐渐变化&#xff0c;给人以安静沉稳的感觉。电子产品中经常会使用不同色彩的呼吸灯&#xff0c;起到很好的视觉提醒效果。 这个项目中我们将带领大家用掌控板制作一个颜色和亮度一起变化的多彩呼吸灯&#xff01; 项目示例 …

编程深水区之并发④:Web多线程

Node的灵感来源于Chrome&#xff0c;更是移植了V8引擎。在Node中能够实现的多线程&#xff0c;在Web环境中自然也可以。 一、浏览器是多进程和多线程的复杂应用 在本系列的第二章节&#xff0c;有提到现代浏览器是一个多进程和多线程的复杂应用。浏览器主进程统管全局&#xf…

vue动态规则

vue动态规则 在Vue中&#xff0c;可以使用动态规则来实现灵活的表单验证和输入限制。动态规则允许你根据特定条件或动态数据来定义验证规则。 以下是一个示例&#xff0c;展示如何在Vue中使用动态规则&#xff1a; <template><div><input v-model"inputVa…

LVS--DR模式

目录 1 DR模式原理 2 DR模式请求回复过程 3 实验环境 4 开始实验 4.1 配置实验环境 4.2 Router 配置路由转发 4.3 LVS 设置转发规则 4.4 解决vip响应问题 4.5 Web1配置 4.6 Web2配置 5 测试效果 1 DR模式原理 当用户向负载均衡调度器&#xff08;Director Server&#xff09;发…

如何用数字便签管理工作任务?

在快节奏的工作环境中&#xff0c;我们每天都需要处理大量的工作任务。如果仅仅依靠个人的记忆力和精力&#xff0c;很容易导致任务遗漏或者延误。随着数字化技术的发展&#xff0c;选择一款功能强大的数字便签软件已经成为我们管理工作任务的更好选择。 在众多的数字便签软件…

Java零基础之多线程篇:讲解并发集合

哈喽&#xff0c;各位小伙伴们&#xff0c;你们好呀&#xff0c;我是喵手。运营社区&#xff1a;C站/掘金/腾讯云&#xff1b;欢迎大家常来逛逛 今天我要给大家分享一些自己日常学习到的一些知识点&#xff0c;并以文字的形式跟大家一起交流&#xff0c;互相学习&#xff0c;一…

c语言11天笔记

函数的概述 函数&#xff1a;实现一定功能的&#xff0c;独立的代码模块。我们的函数一定是先定义&#xff0c;后使用。 使用函数的优势&#xff1a; 1. 我们可以通过函数提供功能给别人使用。当然我们也可以使用别人提供的函数&#xff0c;减少代码量。 2. 借助函数可以减…

4.6.长短期记忆网络(LSTM)

长短期记忆网络(LSTM) ​ 长短期记忆网络的设计灵感来自于计算机的逻辑门。 长短期记忆网络引入了记忆元&#xff08;memory cell&#xff09;&#xff0c;或简称为单元&#xff08;cell&#xff09;。 有些文献认为记忆元是隐状态的一种特殊类型&#xff0c; 它们与隐状态具有…

萱仔求职系列——1.1 机器学习基础知识复习

由于我最近拿到offer还是想再找找更好的机会&#xff0c;目前有很多的面试&#xff0c;面试的时候很多面试官会问一些机器学习的基础知识&#xff0c;由于我上一段实习的时候主要是机器学习和部分深度学习的内容&#xff0c;为了避免在面试的时候想不起来自己学习的内容&#x…

MPU6050的STM32数据读取

目录 1. 概述2. STM32G030对MPU6050的读取3. STM32F1xx对MPU6050的读取 1. 概述 项目中&#xff0c;往往需要根据不同的环境使用不同的芯片处理某些数据&#xff0c;当使用不同的芯片对六轴陀螺仪芯片MPU6050进行数据处理中&#xff0c;硬件的连接、I/O口的设置往往需要根据相…

【HarmonyOS NEXT星河版开发学习】小型测试案例05-得物列表项

个人主页→VON 收录专栏→鸿蒙开发小型案例总结​​​​​ 基础语法部分会发布于github 和 gitee上面&#xff08;暂未发布&#xff09; 前言 鸿蒙操作系统通过其先进的分布式架构和开发工具&#xff0c;以及灵活的界面布局和样式控制&#xff0c;为开发者提供了丰富的开发资源…

设计模式- 数据源架构模式

活动记录&#xff08;Active Record&#xff09; 一个对象&#xff0c;它包装数据库表或视图中的某一行&#xff0c;封装数据库访问&#xff0c;并在这些数据上增加了领域逻辑 对象中既有数据又有行为。这些数据大多是持久数据、并且需要保存到数据库。 运行机制 活动记录的…

Iris for mac 好用的录屏软件

Iris 是一款高性能屏幕录像机&#xff0c;可录制到 h.264。Iris 在可用时利用板载 GPU 加速。它可以选择包括来自摄像头和最多两个麦克风的视频。 兼容性 所有功能在macOS 11.0-14上完全支持&#xff0c;包括macOS Sonoma。 简单编码 直接录制为h.264、h.265、ProRes或Motion…

WPF学习(10)-Label标签+TextBlock文字块+TextBox文本框+RichTextBox富文本框

Label标签 Label控件继承于ContentControl控件&#xff0c;它是一个文本标签&#xff0c;如果您想修改它的标签内容&#xff0c;请设置Content属性。我们曾提过ContentControl的Content属性是object类型&#xff0c;意味着Label的Content也是可以设置为任意的引用类型的。 案…

游戏ID统一管理器DEMO

一般游戏的角色ID、名字&#xff0c;工会ID、名字&#xff0c;等最好统一创建&#xff0c;方便合服处理&#xff0c;可以以此基础&#xff0c;动态配置生成ID 这个也可以用openresty 作个&#xff0c;可能更专业点&#xff0c; 1&#xff1a;go1.20 最后一版支持win7的 mongod…

微信小程序乡村医疗系统,源码、部署+讲解

目录 摘 要 Abstract 1 绪论 1.1 研究背景及意义 1.2 研究现状 1.3 研究内容 2 相关技术介绍 2.1 Java 语言 2.2 MySQL 数据库 2.3 Spring Boot 框架 2.4 B/S 结构 2.5 微信小程序 3 系统分析 3.1 可行性分析 3.1.1 经济可行性 3.1.2 技术可行性…

4.MySQL数据类型

目录 数据类型 ​编辑数值类型 tinyint类型 bit类型 float类型 decimal类型 字符串类型 char类型 varchar varchar和char的区别 日期和时间类型 数据类型 数值类型 说明一下&#xff1a;MySQL本身是不支持bool类型的&#xff0c;当把一个数据设置成bool类型时&#x…

【ThreadLocal总结】

文章目录 为什么使用ThreadLocalThreadLocal核心ThreadLocal内部结构ThreadLocal内存泄漏解决内存泄漏 为什么使用ThreadLocal 在并发编程中&#xff0c;多个线程同时访问和修改共享变量是一个常见的场景。这种情况下&#xff0c;可能会出现线程安全问题&#xff0c;即多个线程…