CubeMx笔记 -- IIC(位带操作实现)+ IO拓展
文章目录
- 1、理论基础
- 1.1、物理层特点
- 1.2、通信过程
- 1.3、IIC架构
- 2、工程建立(硬件iic)
- 2.1、配置步骤
- 2.2、cubemx具体配置
- 3、应用(软件iic)
- 3.1、原理图
- 3.2、端口模式+位带操作
- 3.3、iic头文件
- 3.4、iic具体实现
- 3.5、AT24C02读写驱动
- 4、利用iic拓展io
- 4.1、原理图
- 4.2、代码
IO拓展件:PCF8574T
开发板:STM32F429
参考:
1、[正点原子]《STM32F429 开发指南(HAL 库版)》
2、STM32CubeMX使用之I2C通讯
1、理论基础
1.1、物理层特点
- “总线”指多个设备共用的信号线。在I2C 通讯总线中,支持多个主机及多个从机
- 一个 I2C 有两条总线, 数据线 (SDA) 用来表示数据,时钟线 (SCL)用于数据收发同步。
- 每个连接到总线的设备都有一个独立的地址
从机地址可以是 7位或 10 位 - 总线通过上拉电阻接到电源。
当 I2C 设备空闲时,会输出高阻态,当所有设备都空闲时,由上拉电阻把总线拉成高电平 - 具有仲裁机制 (开漏输出:线与,低电平有效)
多个主机同时使用总线时,为了防止数据冲突,会利用仲裁方式决定由哪个设备占用总线 - 三种传输模式
标准模式传输速率为 100kbit/s ,快速模式为 400kbit/s ,高速模式下可达 3.4Mbit/s - 连接到相同总线的 IC 数量受到总线的最大电容 400pF 限制
- 主机Master 从机Slave
1.2、通信过程
1.2.1、通讯起始和停止信号
- 空闲时 sda和scl都是高电平
- 起始位s 停止位p
1.2.2、有效数据
- scl为高电平时数据有效
- 每次数据传输都以字节为单位,每次传输的字节数不受限制
- scl 1 sda 1 表示1
- scl 1 sda 0 表示0
1.2.3、读写过程
- 主机写数据到从机 (应答ACK)
DATA数据包的大小为 8 位
- 从机写数据到主机
主机等待从机的应答
1.3、IIC架构
- 数据寄存器 DR
- 地址寄存器OAR1
- 第二地址寄存器OAR2
- 校验寄存器PEC
- 控制寄存器CR1/2
- 状态寄存器SR1/2
- 控制寄存器CCR
2、工程建立(硬件iic)
ST公司实现了硬件的iic,但据说用着不方便,可以参考第3节软件实现
2.1、配置步骤
- RCC
设置外部晶振、PLL、主频 - 调试口
选择调试模式,选择调试IO口 - IO分配
打开相应外设开关 - IIC参数
设置速率、地址、主从、时序
2.2、cubemx具体配置
- 选择开漏模式(一般只有这个选项)
- 参数设置
3、应用(软件iic)
- 利用EEPROM 24C02 测试
- 这里先用标准库
3.1、原理图
3.2、端口模式+位带操作
端口模式设置
GPIO 端口模式寄存器 (GPIOx_MODER) (x = A…I)
- 3(二进制11),将11左移10位,取反再与,使MODER5的10、11位寄存器清零
0左移10位,使配置位10为0;即配置端口5为输入模式
1左移10位,即配置位10为1;即配置端口5为输出模式
#define SDA_IN() {GPIOH->MODER&=~(3<<(5*2));GPIOH->MODER|=0<<5*2;} //PH5输入模式
#define SDA_OUT() {GPIOH->MODER&=~(3<<(5*2));GPIOH->MODER|=1<<5*2;} //PH5输出模式
位带操作
stm32之bit-band(位带)操作
3.3、iic头文件
- 代码均来自正点原子,只是改了一点点
#ifndef _MYIIC_H
#define _MYIIC_H#include "main.h"
#include "stm32f4xx_hal_gpio.h"//IO口位带操作
#define BITBAND(addr, bitnum) ((addr & 0xF0000000)+0x2000000+((addr &0xFFFFF)<<5)+(bitnum<<2))
#define MEM_ADDR(addr) *((volatile unsigned long *)(addr))
#define BIT_ADDR(addr, bitnum) MEM_ADDR(BITBAND(addr, bitnum)) //IO方向设置
#define SDA_IN() {GPIOH->MODER&=~(3<<(5*2));GPIOH->MODER|=0<<5*2;} //PH5输入模式
#define SDA_OUT() {GPIOH->MODER&=~(3<<(5*2));GPIOH->MODER|=1<<5*2;} //PH5输出模式//IO口地址映射
#define GPIOH_ODR_Addr (GPIOH_BASE+20) //0x40021C14
#define GPIOH_IDR_Addr (GPIOH_BASE+16) //0x40021C10 //IO操作
#define PHout(n) BIT_ADDR(GPIOH_ODR_Addr,n) //输出 gpio_writepin
#define PHin(n) BIT_ADDR(GPIOH_IDR_Addr,n) //输入 gpio_readpin#define IIC_SCL PHout(4) //SCL
#define IIC_SDA PHout(5) //SDA
#define READ_SDA PHin(5) //输入SDA #define CPU_FREQUENCY_MHZ 180 // STM32时钟主频 用于微秒级延时typedef unsigned char u8;
typedef unsigned short int u16;
typedef unsigned int u32;//IIC所有操作函数
void IIC_Init(void); //初始化IIC的IO口
void IIC_Start(void); //发送IIC开始信号
void IIC_Stop(void); //发送IIC停止信号
void IIC_Send_Byte(u8 txd); //IIC发送一个字节
u8 IIC_Read_Byte(unsigned char ack);//IIC读取一个字节
u8 IIC_Wait_Ack(void); //IIC等待ACK信号
void IIC_Ack(void); //IIC发送ACK信号
void IIC_NAck(void); //IIC不发送ACK信号void delay_us(uint32_t delay); //hal库 没有微秒延时,需要自己写
void delay_ms(uint32_t ms);#endif
3.4、iic具体实现
- 为了配合AT24C02的使用,具体的设置可能和其他教程有所差别
#include "myiic.h"//IIC初始化
void IIC_Init(void)
{GPIO_InitTypeDef GPIO_Initure;__HAL_RCC_GPIOH_CLK_ENABLE(); //使能GPIOH时钟//PH4,5初始化设置GPIO_Initure.Pin=GPIO_PIN_4|GPIO_PIN_5;GPIO_Initure.Mode=GPIO_MODE_OUTPUT_PP; //推挽输出GPIO_Initure.Pull=GPIO_PULLUP; //上拉GPIO_Initure.Speed=GPIO_SPEED_FREQ_HIGH; //快速HAL_GPIO_Init(GPIOH,&GPIO_Initure);//都拉高表示iic空闲IIC_SDA=1;IIC_SCL=1;
}//产生IIC起始信号:scl 拉高 sda产生下降沿
void IIC_Start(void)
{SDA_OUT(); //配置sda线为输出模式IIC_SDA=1; IIC_SCL=1;delay_us(4);IIC_SDA=0;//产生下降沿delay_us(4);IIC_SCL=0;//钳住I2C总线,准备发送或接收数据
}//产生IIC停止信号:scl 拉高 sda产生上升沿
void IIC_Stop(void)
{SDA_OUT();//sda线输出IIC_SCL=0;//拉低用于sda高低电平变换IIC_SDA=0;delay_us(4);IIC_SCL=1; delay_us(4); IIC_SDA=1;//产生上升沿}//等待应答信号到来
//返回值:1,接收应答失败
// 0,接收应答成功
u8 IIC_Wait_Ack(void)
{u8 ucErrTime=0;SDA_IN(); //SDA设置为输入 IIC_SDA=1;delay_us(1); IIC_SCL=1;delay_us(1); while(READ_SDA)//sda为低表示应答信号{ucErrTime++;if(ucErrTime>250){IIC_Stop();return 1;}}IIC_SCL=0; return 0;
} //产生ACK应答(这里我还没弄清楚)
void IIC_Ack(void)
{IIC_SCL=0;SDA_OUT();IIC_SDA=0;delay_us(2);IIC_SCL=1;delay_us(2);IIC_SCL=0;
}//不产生ACK应答
void IIC_NAck(void)
{IIC_SCL=0;SDA_OUT();IIC_SDA=1;delay_us(2);IIC_SCL=1;delay_us(2);IIC_SCL=0;
} //IIC发送一个字节
//返回从机有无应答
//1,有应答
//0,无应答
void IIC_Send_Byte(u8 txd)
{ u8 t; SDA_OUT(); IIC_SCL=0;//拉低时钟开始数据传输for(t=0;t<8;t++)//一位一位的发{ IIC_SDA=(txd&0x80)>>7;//发送txd的最高位txd<<=1; delay_us(2); //对TEA5767这三个延时都是必须的IIC_SCL=1;delay_us(2); IIC_SCL=0; //拉低时钟,变化sdadelay_us(2);}
} //读1个字节,ack=1时,发送ACK,ack=0,发送nACK
u8 IIC_Read_Byte(unsigned char ack)
{unsigned char i,receive=0;//uint8_tSDA_IN();//SDA设置为输入for(i=0;i<8;i++ ){IIC_SCL=0; delay_us(2);IIC_SCL=1;receive<<=1;if(READ_SDA){receive++; }delay_us(1); } if (!ack)IIC_NAck();//发送nACKelseIIC_Ack(); //发送ACK return receive;
}void delay_us(uint32_t delay)
{int last, curr, val;int temp;while (delay != 0){temp = delay > 900 ? 900 : delay;last = SysTick->VAL;curr = last - CPU_FREQUENCY_MHZ * temp;if (curr >= 0){do{val = SysTick->VAL;}while ((val < last) && (val >= curr));}else{curr += CPU_FREQUENCY_MHZ * 1000;do{val = SysTick->VAL;}while ((val <= last) || (val > curr));}delay -= temp;}
}void delay_ms(uint32_t ms){for(int i=0;i<ms;i++){delay_us(1000);}
}
3.5、AT24C02读写驱动
- 头文件
#ifndef _24CXX_H
#define _24CXX_H
#include "myiic.h"#define AT24C01 127
#define AT24C02 255
#define AT24C04 511
#define AT24C08 1023
#define AT24C16 2047
#define AT24C32 4095
#define AT24C64 8191
#define AT24C128 16383
#define AT24C256 32767
//STM32F429开发板使用的是24c02,所以定义EE_TYPE为AT24C02
#define EE_TYPE AT24C02u8 AT24CXX_ReadOneByte(u16 ReadAddr); //指定地址读取一个字节
void AT24CXX_WriteOneByte(u16 WriteAddr,u8 DataToWrite); //指定地址写入一个字节
void AT24CXX_WriteLenByte(u16 WriteAddr,u32 DataToWrite,u8 Len);//指定地址开始写入指定长度的数据
u32 AT24CXX_ReadLenByte(u16 ReadAddr,u8 Len); //指定地址开始读取指定长度数据
void AT24CXX_Write(u16 WriteAddr,u8 *pBuffer,u16 NumToWrite); //从指定地址开始写入指定长度的数据
void AT24CXX_Read(u16 ReadAddr,u8 *pBuffer,u16 NumToRead); //从指定地址开始读出指定长度的数据u8 AT24CXX_Check(void); //检查器件
void AT24CXX_Init(void); //初始化IIC
#endif
- 具体实现
#include "24cxx.h"//初始化IIC接口
void AT24CXX_Init(void)
{IIC_Init();//IIC初始化
}
//在AT24CXX指定地址读出一个数据
//ReadAddr:开始读数的地址
//返回值 :读到的数据
u8 AT24CXX_ReadOneByte(u16 ReadAddr)
{ u8 temp=0; IIC_Start(); if(EE_TYPE>AT24C16){IIC_Send_Byte(0XA0); //发送写命令IIC_Wait_Ack();IIC_Send_Byte(ReadAddr>>8);//发送高地址 }else IIC_Send_Byte(0XA0+((ReadAddr/256)<<1)); //发送器件地址0XA0,写数据 IIC_Wait_Ack(); IIC_Send_Byte(ReadAddr%256); //发送低地址IIC_Wait_Ack(); IIC_Start(); IIC_Send_Byte(0XA1); //进入接收模式 IIC_Wait_Ack(); temp=IIC_Read_Byte(0); IIC_Stop();//产生一个停止条件 return temp;
}
//在AT24CXX指定地址写入一个数据
//WriteAddr :写入数据的目的地址
//DataToWrite:要写入的数据
void AT24CXX_WriteOneByte(u16 WriteAddr,u8 DataToWrite)
{ IIC_Start(); if(EE_TYPE>AT24C16){IIC_Send_Byte(0XA0); //发送写命令IIC_Wait_Ack();IIC_Send_Byte(WriteAddr>>8);//发送高地址 }else IIC_Send_Byte(0xA0+((WriteAddr/256)<<1)); //发送器件地址0XA0,写数据 IIC_Wait_Ack(); IIC_Send_Byte(WriteAddr%256); //发送低地址IIC_Wait_Ack(); IIC_Send_Byte(DataToWrite); //发送字节 IIC_Wait_Ack(); IIC_Stop();//产生一个停止条件 delay_ms(10);
}//在AT24CXX里面的指定地址开始写入长度为Len的数据
//该函数用于写入16bit或者32bit的数据.
//WriteAddr :开始写入的地址
//DataToWrite:数据数组首地址
//Len :要写入数据的长度2,4
void AT24CXX_WriteLenByte(u16 WriteAddr,u32 DataToWrite,u8 Len)
{ u8 t;for(t=0;t<Len;t++){AT24CXX_WriteOneByte(WriteAddr+t,(DataToWrite>>(8*t))&0xff);}
}//在AT24CXX里面的指定地址开始读出长度为Len的数据
//该函数用于读出16bit或者32bit的数据.
//ReadAddr :开始读出的地址
//返回值 :数据
//Len :要读出数据的长度2,4
u32 AT24CXX_ReadLenByte(u16 ReadAddr,u8 Len)
{ u8 t;u32 temp=0;for(t=0;t<Len;t++){temp<<=8;temp+=AT24CXX_ReadOneByte(ReadAddr+Len-t-1); }return temp;
}//检查AT24CXX是否正常
//这里用了24XX的最后一个地址(255)来存储标志字.
//如果用其他24C系列,这个地址要修改
//返回1:检测失败
//返回0:检测成功
u8 AT24CXX_Check(void)
{u8 temp;
temp = AT24CXX_ReadOneByte(255);//避免每次开机都写AT24CXX if(temp==0X55)return 0; else//排除第一次初始化的情况{AT24CXX_WriteOneByte(255,0X55);temp=AT24CXX_ReadOneByte(255); if(temp==0X55)return 0;}return 1;
}//在AT24CXX里面的指定地址开始读出指定个数的数据
//ReadAddr :开始读出的地址 对24c02为0~255
//pBuffer :数据数组首地址
//NumToRead:要读出数据的个数
void AT24CXX_Read(u16 ReadAddr,u8 *pBuffer,u16 NumToRead)
{while(NumToRead){*pBuffer++=AT24CXX_ReadOneByte(ReadAddr++); NumToRead--;}
} //在AT24CXX里面的指定地址开始写入指定个数的数据
//WriteAddr :开始写入的地址 对24c02为0~255
//pBuffer :数据数组首地址
//NumToWrite:要写入数据的个数
void AT24CXX_Write(u16 WriteAddr,u8 *pBuffer,u16 NumToWrite)
{while(NumToWrite--){AT24CXX_WriteOneByte(WriteAddr,*pBuffer);WriteAddr++;pBuffer++;}
}
- 部分测试代码
const u8 TEXT_Buffer[]={"IIC AT24c02 测试"};
#define SIZE sizeof(TEXT_Buffer)
//需要自行配置串口
#define u1_printf(...) HAL_UART_Transmit((UART_HandleTypeDef *)&huart1,\(const uint8_t *)u1_data,\(uint16_t)sprintf((char *)u1_data,__VA_ARGS__),\(uint32_t)0xffff)uint8_t u1_data[2048];//设为全局
u8 datatemp[SIZE] = {0};AT24CXX_Init(); //初始化IIC
while(AT24CXX_Check());//检测不到24c02
u1_printf("AT24C02 OK\r\n");AT24CXX_Write(0,(u8*)TEXT_Buffer,SIZE);
u1_printf("write ok\r\n");
AT24CXX_Read(0,datatemp,SIZE);
u1_printf("%s\r\n",datatemp)
4、利用iic拓展io
4.1、原理图
INT为拓展件的中断线 低电平有效
就是mcu用scl 和 sda 两根线 来 读写 p0 ~ p7 的引脚电平值
- mcu往拓展件写数据
- mcu从拓展件读数据
注意:一旦中断有效后,必须对 PCF8574T 进行一次读取/写入操作,复位中断,
才可以输出下一次中断,否则中断将一直保持(无法输出下一次输入信号变化所产生的中断)。
4.2、代码
代码就直接照搬正点原子的源码了
- 头文件
#ifndef __PCF8574_H
#define __PCF8574_H
#include "sys.h"
#include "myiic.h"#define PCF8574_INT PBin(12) //PCF8574 INT脚#define PCF8574_ADDR 0X40 //PCF8574地址(左移了一位)//PCF8574各个IO的功能
#define BEEP_IO 0 //蜂鸣器控制引脚 P0
#define AP_INT_IO 1 //AP3216C中断引脚 P1
#define DCMI_PWDN_IO 2 //DCMI的电源控制引脚 P2
#define USB_PWR_IO 3 //USB电源控制引脚 P3
#define EX_IO 4 //扩展IO,自定义使用 P4
#define MPU_INT_IO 5 //MPU9250中断引脚 P5
#define RS485_RE_IO 6 //RS485_RE引脚 P6
#define ETH_RESET_IO 7 //以太网复位引脚 P7u8 PCF8574_Init(void); //初始化
u8 PCF8574_ReadOneByte(void);
void PCF8574_WriteOneByte(u8 DataToWrite);
void PCF8574_WriteBit(u8 bit,u8 sta);//写入数据到指定引脚
u8 PCF8574_ReadBit(u8 bit);
#endif
- 具体实现
#include "pcf8574.h"
#include "delay.h"//初始化PCF8574
u8 PCF8574_Init(void)
{u8 temp=0;GPIO_InitTypeDef GPIO_Initure;__HAL_RCC_GPIOB_CLK_ENABLE(); //使能GPIOB时钟GPIO_Initure.Pin=GPIO_PIN_12; //PB12GPIO_Initure.Mode=GPIO_MODE_INPUT; //输入GPIO_Initure.Pull=GPIO_PULLUP; //上拉GPIO_Initure.Speed=GPIO_SPEED_HIGH; //高速HAL_GPIO_Init(GPIOB,&GPIO_Initure); //初始化IIC_Init(); //IIC初始化
//检查PCF8574是否在位IIC_Start(); IIC_Send_Byte(PCF8574_ADDR); //写地址 temp=IIC_Wait_Ack(); //等待应答,通过判断是否有ACK应答,来判断PCF8574的状态IIC_Stop(); //产生一个停止条件PCF8574_WriteOneByte(0XFF); //默认情况下所有IO输出高电平return temp;
}//读取PCF8574的8位IO值
//返回值:读到的数据
u8 PCF8574_ReadOneByte(void)
{ u8 temp=0; IIC_Start(); IIC_Send_Byte(PCF8574_ADDR|0X01); //进入接收模式 IIC_Wait_Ack(); temp=IIC_Read_Byte(0); IIC_Stop(); //产生一个停止条件 return temp;
}//向PCF8574写入8位IO值
//DataToWrite:要写入的数据
void PCF8574_WriteOneByte(u8 DataToWrite)
{ IIC_Start(); IIC_Send_Byte(PCF8574_ADDR|0X00); //发送器件地址0X40,写数据 IIC_Wait_Ack(); IIC_Send_Byte(DataToWrite); //发送字节 IIC_Wait_Ack(); IIC_Stop(); //产生一个停止条件 delay_ms(10);
}//设置PCF8574某个IO的高低电平
//bit:要设置的IO编号,0~7
//sta:IO的状态;0或1
void PCF8574_WriteBit(u8 bit,u8 sta)
{u8 data;data=PCF8574_ReadOneByte(); //先读出原来的设置if(sta==0){data&=~(1<<bit); //把bit位的置零 }else {data|=1<<bit;}PCF8574_WriteOneByte(data); //写入新的数据
}//读取PCF8574的某个IO的值
//bit:要读取的IO编号,0~7
//返回值:此IO的值,0或1
u8 PCF8574_ReadBit(u8 bit)
{u8 data;data=PCF8574_ReadOneByte(); //先读取这个8位IO的值 if(data&(1<<bit)){return 1;}else return 0;
}
CubeMx笔记 -- IIC(位带操作实现)+ IO拓展相关推荐
- 秉火429笔记之七位带操作
目录 位带概况 位带概况 位操作,简单的理解,可以单独的对一个比特位读和写,51单片机非常常见.类如,51单片机通过关键字sbit来实现位定义,STM32并没有这个关键字,而是通过访问位带别名区(Bi ...
- Cortex‐M3和Cortex‐M0是否都能位带操作
最近在进行stm32l011f4的编程,想到stm32有位带操作,便想试一试,但是事与愿违,先贴下位带操作代码 Cortex‐M0的位带操作代码 //IO输出方向设置 #define SDA_IN() ...
- STM32笔记 GPIO介绍及IO口操作 STM32F0 利用C语言位域实现仿位带操作
文章目录 GPIO简介 工作方式 相关寄存器介绍 IO 操作步骤 IO口三种操作细解 附录:C语言相关 偏移地址与绝对地址 GPIO简介 GPIO:每个连接到I/O总线上的设备都有自己的I/O地址集, ...
- STM32 进阶教程 8 - 位带操作
前言 有过51单片机开发经历的朋友应该都对51的IO口或一些特殊寄存器可以直接按位操作的方式不陌生吧,那么在stm32中有没有类似的操作呢,答案是肯定的,本节将给大家介绍如何在STM32中实现位带操作 ...
- 【STM32】标准库与HAL库对照学习教程六--位带操作
[STM32]标准库与HAL库对照学习教程六--位带操作 一.前言 二.准备工作 三.位带介绍 1.位带操作 2.STM32位带及位带别名区域 四.位带区与位带别名区地址转换 五.GPIO的位带操作 ...
- STM32(五)------GPIO位带操作
GPIO位带操作 介绍 位带简介 外设位带区 SRAM位带区 位带区和位带别名区地址转换 GPIO 位带操作 代码解析 GPIO 寄存器映射 GPIO位操作 主函数 介绍 位带简介 位操作就是可以单独 ...
- 3. GD32F103C8T6 GPIO的位带操作
属于Contex M3 M4的内核有1M个区域的RAM和一个1M区域的外设地址可以实现位带操作 1.Contex M3 权威手册的说明 支持位带操作的两个内存区的范围是: 0x2000_0000‐0x ...
- 位带操作全解释,个人觉得不错就转过来理解下
还记得51独有位操作,以一位(BIT)为数据对象的操作?可以简单的将P1口的第2位独立操作.P1.2=0;P1.2=1 ; 既可以把P1口的第三个脚(BIT2)置0置1. 而现在STM32的位段.位带 ...
- STM32的位带操作
1. 什么是位带操作? 学习 51 单片机的时候就使用过位操作,通过关键字 sbit 对单片机IO口进行位定义.但STM32没有这样的关键字,于是便要通过访问位带别名区的方式来实现.即:将每一位膨胀成 ...
最新文章
- JS如何深度复制对象和数组,避免指针变量引用修改值
- C++ 一个例子彻底搞清楚拷贝构造函数和赋值运算符重载的区别
- WPF:仿WIN7窗体打开关闭效果
- Java 创建、填充PDF表单域
- WEB通用网关接口:CGI简单教程
- DOM-2 document对象、获取元素、节点、遍历树
- iOS- 关于AVAudioSession的使用——后台播放音乐
- 类与方法java讲解_Java中方法使用的深入讲解
- 奇异值与主成分分析(PCA)
- CISCO路由器的备份与还原(1)
- 电脑屏幕蓝光过滤护眼工具:f.lux for Mac
- 直通车点击率、点击率、创意图、关键词、出价卡位,提升直通车点击率的技巧和方法
- 二叉树——推荐一些神奇的网站
- 操作系统实践-BIOS
- twitter推文采集案例
- 嵌入式学习用什么编程语言
- import 模块大小写问题
- 素数筛(筛选法求素数)
- Linux中断——request_irq
- 日语语法准备一:日语词性的分类
热门文章
- 【githubshare】Linux sed 命令行常用汇总:useful-sed,收集了 sed 命令行的诸多常见用法,可用于快速处理文本文件
- FatMouse's Speed(LIS+路径记录)
- 人类为什么应当使用bilibili
- 从微博个性图标里学Android动态更换
- 《程序猿技术大咖》微信交流群
- 33_ue4进阶末日生存游戏开发[拾取面板UI]
- Unity(十七) 在Unity中Android使用FTP进行上传、下载、文件创建(客户端部分)
- 开源多端合一小程序源码系统+前端+后端+搭建教程
- 【经验】Chrome网络代理插件SwitchyOmega安装
- 甲骨文oracle测试面试记录