最近做一个项目需要将stm32配置为iic的从机模式来响应总线的读写需求,看了网上的大部分资料讲解的都不是很全面,因此这里做一个小分享。

iic通信流程

要编写iic从机模式的代码,就得对iic得整个通信流程足够熟悉,下面是流程的介绍讲解

  • 主机发送数据(从机接收数据)
  1. 起始信号(START)

    主机在检测到总线为“空闲状态”(即 SDA、SCL 线均为高电平)时,发送一个启动信号“S”,开始一次通信。
  2. 发送从机地址和写命令

    主机接着发送一个命令字节。该字节由 7 位的外围器件地址和 1 位读写控制位 R/W 组成(此时 R/W=0 表示写操作)。
  3. 从机应答

    相对应的从机收到命令字节后向主机回馈应答信号 ACK(ACK=0),表示地址匹配并准备好接收数据。
  4. 发送数据字节

    主机收到从机的应答信号后开始发送第一个字节的数据。从机收到数据后返回一个应答信号 ACK。主机收到应答信号后再发送下一个数据字节。
  5. 结束通信

    当主机发送最后一个数据字节并收到从机的 ACK 后,通过向从机发送一个停止信号 P 结束本次通信并释放总线。从机收到 P 信号后也退出与主机之间的通信。
流程示意图:
┌───────┐ ┌─────────────┐ ┌───────┐ ┌──────────┐ ┌───────┐ ┌────────┐
│ START │ → │ 地址+写命令 │ → │ ACK │ → │ 数据字节 │ → │ ACK │ → ... → │ STOP │
└───────┘ └─────────────┘ └───────┘ └──────────┘ └───────┘ └────────┘

  • 主机读取数据(从机发送数据)
  1. 起始信号(START)

    主机拉低SDA线(在SCL高电平期间产生下降沿),表示通信开始。

  2. 发送从机地址+写命令

    • 主机发送7位从机地址,后跟1位方向控制位(R/W) ,此处为0(写模式)。
    • 从机返回应答信号(ACK) (SDA拉低)确认地址匹配。
  3. 发送寄存器地址

    • 主机发送8位寄存器地址,指定需要读取的从机内部寄存器位置。
    • 从机再次返回ACK确认接收成功。
  4. 重复起始信号(Repeated START)

    主机在未发送停止信号的情况下,再次产生起始信号,切换到读模式。

  5. 发送从机地址+读命令

    • 重新发送7位从机地址,方向控制位改为1(读模式)。
    • 从机返回ACK,准备发送数据。
  6. 接收数据并应答

    • 从机发送数据:从机在SCL的每个上升沿将数据位输出到SDA线,高位优先。

    • 主机应答

      :每接收完8位数据,主机在第9个时钟周期:

      • 若需继续读取:发送ACK(SDA拉低)。
      • 若结束读取:发送NACK(SDA保持高电平)。
  7. 停止信号(STOP)

    主机在SCL高电平期间拉高SDA线(产生上升沿),结束通信。

流程示意图:
START → 地址+写 → ACK → 寄存器地址 → ACK → 重复START → 地址+读 → ACK → 接收数据 → (ACK/NACK) → STOP

这种“先写后读”的设计源于IIC协议的寄存器寻址机制,主要原因如下:

  1. 指定目标寄存器位置

    从机通常包含多个寄存器,直接读取时无法确定目标地址。通过先发送写命令+寄存器地址,明确告知从机后续需要读取的寄存器位置。例如,读取EEPROM的第2个存储单元时,需先写入地址0x02
  2. 避免数据冲突

    若直接发送读命令,从机可能默认从某个固定地址(如最近访问地址)返回数据,导致主机无法精确控制数据来源。先写后读确保操作原子性。
  3. 协议复合格式支持

    IIC支持重复起始信号(Repeated START) ,允许在不释放总线的情况下切换读写模式。这种机制减少了总线占用时间,提升效率。
  4. 从机状态初始化

    部分从机需要先接收控制命令(如传感器配置寄存器地址),才能切换到数据输出模式。例如,读取温度传感器数据前需先指定数据寄存器的地址。

实战

了解完iic通信的整个流程后,下面就了解一下具体是如何实现的

cubemx设置

常规的时钟和调试口(SWD)等的设置这里就不说了,就说IICfreertos的设置,如下:

此处只需要配置一下对应的时钟和地址即可,此处地址是7位的,即用主机对该设备进行寻址和读写操作时,需要用该地址加上(0/1)读写标志位再进行后续的操作。

IIC的中断也需要打开一下。

freertos的配置更简单,我这里开的是CMSIS_V1,其他的配置默认即可

生成代码后打开,进行以下操作,如下所示:

首先启动监听:

HAL_I2C_EnableListen_IT(&hi2c1);  // 使能I2C1的侦听中断

注意:理论上随时都可以开启这个,但是一般在初始化的时候开启,同时得注意后面如果还有其他初始化的东西且比较耗时的话,先将中断关闭!

__disable_irq();

// 中间放其他的初始化代码!

__enable_irq();

重写以下几个函数,并在里面实现具体的通信逻辑:

// I2C设备地址回调函数(地址匹配上以后会进入该函数)
void HAL_I2C_AddrCallback(I2C_HandleTypeDef *hi2c, uint8_t TransferDirection, uint16_t AddrMatchCode);
// I2C数据接收回调函数(在I2C完成一次接收时会关闭中断并调用该函数,因此在处理完成后需要手动重新打开中断)
void HAL_I2C_SlaveRxCpltCallback(I2C_HandleTypeDef *hi2c);
// I2C数据发送回调函数(在I2C完成一次发送后会关闭中断并调用该函数,因此在处理完成后需要手动重新打开中断)
void HAL_I2C_SlaveTxCpltCallback(I2C_HandleTypeDef *hi2c);
// 错误回调函数
void HAL_I2C_ErrorCallback(I2C_HandleTypeDef *hi2c);
// 侦听完成回调函数(完成一次完整的i2c通信以后会进入该函数)
void HAL_I2C_ListenCpltCallback(I2C_HandleTypeDef *hi2c);

注意,本教程是没有使用DMA来实现IIC从机的,如果使用了DMA可以方便很多,编程难度也会下降非常多

具体实现逻辑下面给一份实测可以使用的示例代码,注意此处代码只能做参考,需要略微修改按照才能移植使用

// iic.h
#ifndef ORIN_IIC_H
#define ORIN_IIC_H #include "stm32f1xx_hal.h"
#include "i2c.h"
#include "FreeRTOS.h"
#include "cmsis_os.h"
#include "task.h"
#include "crc8.h"
#include "power_adc.h"
#include "string.h"
#include "board_led.h"
#include "floodlight.h"
#include "gimbal.h" #define SLAVE_ADDRESS 0x40 // 设置的从机地址为0x40
#define GPIO_PIN_SCL GPIO_PIN_6
#define GPIO_PIN_SDA GPIO_PIN_7
#define I2C_GPIO_PORT GPIOB
// 电源数据的位置和长度
#define SEND_POWER_OFFSET 0
#define SEND_POWEER_LEN 4 typedef enum {
STATE_WAIT_CMD, // 等待命令
STATE_WAIT_LENGTH, // 等待数据长度
STATE_WAIT_DATA, // 等待数据
STATE_WAIT_CHECKSUM // 等待校验
} ProtocolState; void Orin_IIC_Init(void); // orin_iic的初始化操作
void HAL_I2C_ListenCpltCallback(I2C_HandleTypeDef *hi2c);
void HAL_I2C_AddrCallback(I2C_HandleTypeDef *hi2c, uint8_t TransferDirection, uint16_t AddrMatchCode);
void HAL_I2C_SlaveRxCpltCallback(I2C_HandleTypeDef *hi2c);
void HAL_I2C_SlaveTxCpltCallback(I2C_HandleTypeDef *hi2c);
void HAL_I2C_ErrorCallback(I2C_HandleTypeDef *hi2c); // 错误回调函数
void Orin_Flash_Data(void); // 刷新要发送的数据
void Orin_IIC_Data_Parse(void); // 处理接收的数据 #endif // ORIN_IIC_H
// iic.c
#include "orin_iic.h" extern I2C_HandleTypeDef hi2c1;
extern TIM_HandleTypeDef htim2;
extern TIM_HandleTypeDef htim3;
static ProtocolState protocol_state = STATE_WAIT_CMD; // 接收数据的各种状态
static uint8_t data_receive_buff[64]; // 接收数据的缓存数组
static uint8_t data_reveive_temp[64]; // 接收数据的暂存中心,具体处理都是用这个来处理的
const int data_receive_buff_len = sizeof(data_reveive_temp); // 接收的数据的长度
static volatile uint8_t finish_receive = 0; // 1代表完成接收,有数据要处理,0代表没数据
static uint8_t data_send_buff[64]; // 数据发送缓存数组
static uint8_t data_send_temp[64]; // 数据发送暂存中心
const int data_send_buff_len = sizeof(data_send_temp); // 要发送的数据的长度
static uint8_t send_offset = 0; // 要发送数据的偏移量
static uint8_t send_data_len = 0; // 要发送数据的长度
static uint8_t send_data_count = 0; // 发送的数据记录长度
static uint8_t data_counter = 0; // 记录接收了多少个数据
const uint8_t CMD_READ_POWER = 0x01; // 读电压值
const uint8_t CMD_CONTROL_LIGHT = 0x41; // 控制4个灯
const uint8_t CMD_CONTRIL_GIMBAL = 0x42; // 控制云台
static void float_to_uint8_array(uint8_t *array, float value); // float转为uint8_t
static HAL_StatusTypeDef current_mode;
static uint8_t tiaoshi1 = 0;
extern volatile uint8_t beer_ring_mode; // 控制蜂鸣器叫的函数 static void float_to_uint8_array(uint8_t *array, float value)
{
// 使用指针访问 float 的字节表示
uint8_t *float_bytes = (uint8_t *)&value;
// 保证小端字节序
for (int i = 0; i < sizeof(float); i++) {
array[i] = float_bytes[i];
}
} void Orin_IIC_Init(void)
{
HAL_I2C_EnableListen_IT(&hi2c1); // 使能I2C1的侦听中断
} // 侦听完成回调函数(完成一次完整的i2c通信以后会进入该函数)
void HAL_I2C_ListenCpltCallback(I2C_HandleTypeDef *hi2c)
{
protocol_state = STATE_WAIT_CMD;
send_offset = 0;
send_data_len = 0;
data_counter = 0;
send_data_count = 0;
HAL_I2C_EnableListen_IT(hi2c);
} // I2C设备地址回调函数(地址匹配上以后会进入该函数)
void HAL_I2C_AddrCallback(I2C_HandleTypeDef *hi2c, uint8_t TransferDirection, uint16_t AddrMatchCode)
{
// 主机写,接收数据
if (TransferDirection == I2C_DIRECTION_TRANSMIT){
// 接收第一个命令字,主机发送的情况下该函数只会进入一次
// 接收命令的状态
if (protocol_state == STATE_WAIT_CMD) {
// 即当前的协议是啥样的
HAL_I2C_Slave_Seq_Receive_IT(hi2c, &data_receive_buff[0], 1, I2C_NEXT_FRAME);
}
// 主机读数据,从机发送数据
} else {
switch (data_receive_buff[0]) {
case CMD_READ_POWER:
{
// 复制一份副本数据,防止在发送数据的过程中数据被修改导致出错
memcpy(data_send_temp, data_send_buff, data_send_buff_len);
send_offset = SEND_POWER_OFFSET;
send_data_len = SEND_POWEER_LEN;
beer_ring_mode = 1;
break;
}
default:
{
break;
}
}
if(send_data_len == 1){
HAL_I2C_Slave_Seq_Transmit_IT(hi2c, &data_send_temp[send_offset], 1, I2C_LAST_FRAME);
}else if(send_data_len <= 0){
// 会进入到这里说明收到的数据有问题,程序会自动检测问题并解决!
// 主机请求数据时,如果有如何问题,会从此处跳出去自动恢复
}else{
HAL_I2C_Slave_Seq_Transmit_IT(hi2c, &data_send_temp[send_offset], 1, I2C_NEXT_FRAME);
}
}
} // I2C数据接收回调函数(在I2C完成一次接收时会关闭中断并调用该函数,因此在处理完成后需要手动重新打开中断)
void HAL_I2C_SlaveRxCpltCallback(I2C_HandleTypeDef *hi2c)
{
switch (protocol_state) {
case STATE_WAIT_CMD:
{
while(!data_receive_buff[0]);
// 此判断意味着为主机读数据,从机写数据
if((data_receive_buff[0] == CMD_CONTROL_LIGHT) || (data_receive_buff[0] == CMD_CONTRIL_GIMBAL)){
protocol_state = STATE_WAIT_LENGTH;
// 接收数据长度
HAL_I2C_Slave_Seq_Receive_IT(hi2c, &data_receive_buff[1], 1, I2C_NEXT_FRAME);
}else if(data_receive_buff[0] == CMD_READ_POWER)
{
// 发送数据指令排除
}else{
// 其他没用的指令
}
break;
}
case STATE_WAIT_LENGTH:
{
protocol_state = STATE_WAIT_DATA;
data_counter = 0;
// 准备接收数据
HAL_I2C_Slave_Seq_Receive_IT(hi2c, &data_receive_buff[2], 1, I2C_NEXT_FRAME);
break;
}
case STATE_WAIT_DATA:
{
data_counter++;
if (data_counter >= data_receive_buff[1]) {
protocol_state = STATE_WAIT_CHECKSUM;
// 接收校验字节
HAL_I2C_Slave_Seq_Receive_IT(hi2c, &data_receive_buff[data_receive_buff[1]+2], 1, I2C_LAST_FRAME);
}else
{
HAL_I2C_Slave_Seq_Receive_IT(hi2c, &data_receive_buff[2+data_counter], 1, I2C_NEXT_FRAME);
}
break;
}
case STATE_WAIT_CHECKSUM:
{
if(data_receive_buff[data_receive_buff[1]+2] == do_crc_table(data_receive_buff, data_receive_buff[1]+2))
{
memcpy(data_reveive_temp, data_receive_buff, data_receive_buff_len);
finish_receive = 1;
}
else
{
// 校验有误,初始化为0,同时蜂鸣器响一下
memset(data_receive_buff, 0, sizeof(data_receive_buff));
// 蜂鸣器响
beer_ring_mode = 2;
}
protocol_state = STATE_WAIT_CMD; // 复位状态
break;
}
}
} // I2C数据发送回调函数(在I2C完成一次发送后会关闭中断并调用该函数,因此在处理完成后需要手动重新打开中断)
void HAL_I2C_SlaveTxCpltCallback(I2C_HandleTypeDef *hi2c)
{
send_data_count ++;
// 判断数据传输完了没有
if(send_data_len != 0)
{
if(send_data_count < send_data_len - 1)
{
HAL_I2C_Slave_Seq_Transmit_IT(hi2c, &data_send_temp[send_offset + send_data_count], 1, I2C_NEXT_FRAME);
}else if(send_data_count == send_data_len - 1){
HAL_I2C_Slave_Seq_Transmit_IT(hi2c, &data_send_temp[send_offset + send_data_count], 1, I2C_LAST_FRAME);
}
}else
{
beer_ring_mode = 2;
}
} // 错误回调函数
void HAL_I2C_ErrorCallback(I2C_HandleTypeDef *hi2c)
{
// 获取错误类型
uint32_t errors = HAL_I2C_GetError(hi2c);
if (errors & (HAL_I2C_ERROR_BERR | HAL_I2C_ERROR_ARLO | HAL_I2C_ERROR_AF)) {
// 重置 I2C 外设
HAL_I2C_DeInit(hi2c);
MX_I2C1_Init(); // 重新初始化
Orin_IIC_Init();
}
} // 刷新数据缓存区的数据
void Orin_Flash_Data(void)
{
// 刷新电源电压数据
float power_temp = Get_Power_ADC_Value();
float_to_uint8_array(data_send_buff, power_temp);
} // 处理接收的数据
void Orin_IIC_Data_Parse(void)
{
if(finish_receive){
switch(data_reveive_temp[0]) {
case CMD_CONTROL_LIGHT:
{
// 1 2 3 4
// 0x41 0x04 0x64 0x00 0x32 0x25 0xB8
// TIM3 TIM_CHANNEL_3对应板子上的灯1
// TIM3 TIM_CHANNEL_4对应板子上的灯2
// TIM2 TIM_CHANNEL_1对应板子上的灯3
// TIM2 TIM_CHANNEL_2对应板子上的灯4
// ReverseLedState();
beer_ring_mode = 1;
if(data_reveive_temp[2] > 100) data_reveive_temp[2] = 100;
if(data_reveive_temp[3] > 100) data_reveive_temp[3] = 100;
if(data_reveive_temp[4] > 100) data_reveive_temp[4] = 100;
if(data_reveive_temp[5] > 100) data_reveive_temp[5] = 100;
FloodLightPWMSetDutyRatio((float)data_reveive_temp[2]/100, &htim3, TIM_CHANNEL_3);
FloodLightPWMSetDutyRatio((float)data_reveive_temp[3]/100, &htim3, TIM_CHANNEL_4);
FloodLightPWMSetDutyRatio((float)data_reveive_temp[4]/100, &htim2, TIM_CHANNEL_1);
FloodLightPWMSetDutyRatio((float)data_reveive_temp[5]/100, &htim2, TIM_CHANNEL_2);
break; }
case CMD_CONTRIL_GIMBAL:
{
// ReverseLedState();
// beer_ring_mode = 1;
// // 0x42 0x02 0x00 0x00 0x07(大端模式)
// int16_t pitch_angle = 0xFF;
// pitch_angle &= (data_reveive_temp[2] << 8);
// pitch_angle &= data_reveive_temp[3];
// Set_Gimbal_Pitch(pitch_angle);
break;
}
default:
{
beer_ring_mode = 2; // 嘀嘀嘀三声,表示没有此写指令
break;
}
}
finish_receive = 0;
} }

iic出错后自愈代码

不使用DMA时,若代码接收到错误协议发过来的数据时有时候会遇到一些意想不到的情况导致总线一直被占用,此时可以在freertos中添加一个任务监视总线的情况,如果有问题可以进行总线的释放,来恢复iic通信,如下:

// freertos_task.c
void ReleaseBus(void const * argument)
{
/* USER CODE BEGIN ReleaseBus */
/* Infinite loop */
// 每20ms检测一次总线是否有问题,若连续检测出5次则重新初始化!
for(;;)
{
if (HAL_GPIO_ReadPin(I2C_GPIO_PORT, GPIO_PIN_SCL) == GPIO_PIN_RESET ||
HAL_GPIO_ReadPin(I2C_GPIO_PORT, GPIO_PIN_SDA) == GPIO_PIN_RESET) {
count_iic_bus_error ++;
}else {
count_iic_bus_error = 0;
}
if (count_iic_bus_error >= 5)
{
I2C_BusRecover();
}
}
/* USER CODE END ReleaseBus */
}
// release.c
void I2C_BusRecover(void) {
// 1. 临时配置 SCL/SDA 为开漏输出
GPIO_InitTypeDef GPIO_InitStruct = {0};
GPIO_InitStruct.Pin = GPIO_PIN_SCL | GPIO_PIN_SDA;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD; // 开漏输出
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(I2C_GPIO_PORT, &GPIO_InitStruct); // 2. 尝试释放 SDA 线
HAL_GPIO_WritePin(I2C_GPIO_PORT, GPIO_PIN_SCL, GPIO_PIN_SET); // SCL 高
HAL_GPIO_WritePin(I2C_GPIO_PORT, GPIO_PIN_SDA, GPIO_PIN_SET); // SDA 高
osDelay(1); // 3. 生成 9 个 SCL 脉冲(I2C 协议要求)
for (int i = 0; i < 9; i++) {
// SCL 拉低
HAL_GPIO_WritePin(I2C_GPIO_PORT, GPIO_PIN_SCL, GPIO_PIN_RESET);
osDelay(1);
// SCL 拉高,等待 SDA 被释放
HAL_GPIO_WritePin(I2C_GPIO_PORT, GPIO_PIN_SCL, GPIO_PIN_SET);
osDelay(1);
// 检查 SDA 是否变为高电平
if (HAL_GPIO_ReadPin(I2C_GPIO_PORT, GPIO_PIN_SDA) == GPIO_PIN_SET) {
break; // SDA 已释放,退出循环
}
} // 4. 发送 STOP 条件(可选)
HAL_GPIO_WritePin(I2C_GPIO_PORT, GPIO_PIN_SDA, GPIO_PIN_RESET);
osDelay(1);
HAL_GPIO_WritePin(I2C_GPIO_PORT, GPIO_PIN_SDA, GPIO_PIN_SET);
osDelay(1); // 5. 恢复 GPIO 为 I2C 复用功能
GPIO_InitStruct.Mode = GPIO_MODE_AF_OD; // 复用开漏
HAL_GPIO_Init(I2C_GPIO_PORT, &GPIO_InitStruct); // 6. 重新初始化 I2C 外设
HAL_I2C_DeInit(&hi2c1);
MX_I2C1_Init(); // 重新调用初始化函数
Orin_IIC_Init(); // 重新启动监听iic中断
} void Orin_IIC_Init(void)
{
HAL_I2C_EnableListen_IT(&hi2c1); // 使能I2C1的侦听中断
}

stm32cubemx+freertos+中断实现IIC从机的更多相关文章

  1. 硬件IIC主从机中断代码注释解析

    目录 硬件IIC的主从中断在582的最新EVT中已支持. 对于IIC从机中断,例程中已封装好中断处理过程,用户调用app_i2c时,初始化中需要配置回调函数. 初始化的配置如下. struct i2c ...

  2. FreeRTOS - 中断使用注意

    原文地址:http://www.cnblogs.com/god-of-death/p/6886823.html 注意点: 1.首先要将中断的嵌套全部设置为抢占优先级. 2.将freertos系统内核中 ...

  3. STM32CubeMX+FreeRTOS 定时器os_timer的使用

    转载:https://blog.csdn.net/jacklondonjia/article/details/78497120在STM32CubeMX的FreeRTOS配置中,使能FreeRTOS的S ...

  4. FreeRTOS——中断管理

    1. 只有以“FromISR”或"FROM_ISR"结束的API函数或宏才可以在中断服务函数中使用. 2. 除互斥信号量外,所有类型的信号量都可以调用 xSemaphoreTake ...

  5. 安装卡巴 OFFICE链接 出现这个过程被中断,由于本机的限制

    今天 安装了卡巴后 office 超链接功能不能使用了,一点击超链接,就会发出警报,说”由于本机的限制,此操作已被取消,请与系统管理员联系“ 解决办法:1打开注册表2到这个位置:HKEY_CURREN ...

  6. STM32运行FreeRTOS出现prvTaskExitError错误死机

    文件port.c prvTaskExitError();任务退出错误,一个可能在任务里面写了return,另一个可能任务切换退出问题,入栈和出栈的时候出了问题. static void prvTask ...

  7. FreeRTOS中断测试

    configMAX_SYSCALL_INTERRUPT_PRIORITY 高于此优先级的中断,不能被禁止 #ifdef __NVIC_PRIO_BITS #define configPRIO_BITS ...

  8. FreeRTOS 中断配置和临界段

    中断屏蔽寄存器 PRIMASK.FAULTMASK和BASEPRI 1.PRIMASK:这是个只有1个位的寄存器.当它置1时, 就关掉所有可屏蔽的异常,只剩下 NMI和硬fault可以响应.它的缺省值 ...

  9. STM32CubeMX FreeRTOS定时器的使用

    配置STM32CubeMX如下 生成的Keil代码的创建启动定时器如下 /* Create the timer(s) */ /* definition and creation of myTimer0 ...

  10. STM32CubeMX FreeRTOS no definition for "osThreadGetState" 解决办法

    用STM32CubuMX默认加入的FreeRTOS默认配置eTaskGetState是禁止的 把该功能设为Enabled编译就不会出错了 IAR的编译器要勾选Allow VLA

随机推荐

  1. Netty基础—2.网络编程基础二

    大纲 1.网络编程简介 2.BIO网络编程 3.AIO网络编程 4.NIO网络编程之Buffer 5.NIO网络编程之实战 6.NIO网络编程之Reactor模式 1.网络编程简介 既然是通信,那么肯 ...

  2. Java List和Array之间的转换

    import java.util.Arrays; import java.util.List; class Test { //Object数组向List的转换 public static List&l ...

  3. 入门Dify平台:如何根据需求选择与创建最合适的应用

    今天我们将继续深入讲解Dify,重点介绍如何创建应用.具体来说,我们将探讨如何根据不同的需求来决定选择什么类型的应用最为合适,帮助大家更好地理解在Dify平台上构建应用的最佳实践. 创建空白应用 首先 ...

  4. Netty源码—7.ByteBuf原理二

    大纲 9.Netty的内存规格 10.缓存数据结构 11.命中缓存的分配流程 12.Netty里有关内存分配的重要概念 13.Page级别的内存分配 14.SubPage级别的内存分配 15.Byte ...

  5. LazyAdmin打靶笔记

    参考视频:https://www.bilibili.com/video/BV16Tc8eCEKZ/?spm_id_from=333.1387.homepage.video_card.click Nma ...

  6. Python复制单个文件为多个脚本

    编写背景: 由于线上用户反馈媒体添加页加载时间很长,猜测是由于本地视频/图片数量过多引起,于是编写此脚本以便快速生成大量测试视频 代码如下: # coding=utf-8 import os impo ...

  7. jmeter返回数据重新编码的方法

    下图内容为请求后的返回值,红色箭头内容是需要正则处理传参给后面的接口使用 其中==后面的\U0026为未编码内容 而实际能够提交的链接为下图"&" 所以,图1请求后需要先转 ...

  8. thinkphp mysql 使用IN 条件

    今天使用thinkphp  whrere  in条件查询 数据库是 ,我需要搜索入参  110000  一个字段 ,但是thinkphp 为了效率直接把  in条件转成  = 解决方法 FIND_IN ...

  9. Windows 提权指南

    男儿若遂平生志,五经勤向窗前读. 导航 壹 - Se 特权 贰 - RunAs 叁 - 弱服务 肆 - Windows 内核 伍 - 密码搜寻 陆 - 杂项 AlwaysInstallElevated ...

  10. 使用 AOT 编译保护 .NET 核心逻辑,同时支持第三方扩展

    引言 在开发大型ERP .NET 应用程序时,我面临一个挑战:如何创建一个可供第三方引用的组件(DLL)以便二次开发,但同时保护核心逻辑不被轻易反编译,还要支持反射机制(包括私有字段访问),并且坚持使 ...