stm32cubemx+freertos+中断实现IIC从机
最近做一个项目需要将stm32配置为iic的从机模式来响应总线的读写需求,看了网上的大部分资料讲解的都不是很全面,因此这里做一个小分享。
iic通信流程
要编写iic从机模式的代码,就得对iic得整个通信流程足够熟悉,下面是流程的介绍讲解
- 主机发送数据(从机接收数据)
- 起始信号(START)
主机在检测到总线为“空闲状态”(即 SDA、SCL 线均为高电平)时,发送一个启动信号“S”,开始一次通信。 - 发送从机地址和写命令
主机接着发送一个命令字节。该字节由 7 位的外围器件地址和 1 位读写控制位 R/W 组成(此时 R/W=0 表示写操作)。 - 从机应答
相对应的从机收到命令字节后向主机回馈应答信号 ACK(ACK=0),表示地址匹配并准备好接收数据。 - 发送数据字节
主机收到从机的应答信号后开始发送第一个字节的数据。从机收到数据后返回一个应答信号 ACK。主机收到应答信号后再发送下一个数据字节。 - 结束通信
当主机发送最后一个数据字节并收到从机的 ACK 后,通过向从机发送一个停止信号 P 结束本次通信并释放总线。从机收到 P 信号后也退出与主机之间的通信。
流程示意图:
┌───────┐ ┌─────────────┐ ┌───────┐ ┌──────────┐ ┌───────┐ ┌────────┐
│ START │ → │ 地址+写命令 │ → │ ACK │ → │ 数据字节 │ → │ ACK │ → ... → │ STOP │
└───────┘ └─────────────┘ └───────┘ └──────────┘ └───────┘ └────────┘
- 主机读取数据(从机发送数据)
起始信号(START)
主机拉低SDA线(在SCL高电平期间产生下降沿),表示通信开始。发送从机地址+写命令
- 主机发送7位从机地址,后跟1位方向控制位(R/W) ,此处为
0
(写模式)。 - 从机返回应答信号(ACK) (SDA拉低)确认地址匹配。
- 主机发送7位从机地址,后跟1位方向控制位(R/W) ,此处为
发送寄存器地址
- 主机发送8位寄存器地址,指定需要读取的从机内部寄存器位置。
- 从机再次返回ACK确认接收成功。
重复起始信号(Repeated START)
主机在未发送停止信号的情况下,再次产生起始信号,切换到读模式。发送从机地址+读命令
- 重新发送7位从机地址,方向控制位改为
1
(读模式)。 - 从机返回ACK,准备发送数据。
- 重新发送7位从机地址,方向控制位改为
接收数据并应答
从机发送数据:从机在SCL的每个上升沿将数据位输出到SDA线,高位优先。
主机应答
:每接收完8位数据,主机在第9个时钟周期:
- 若需继续读取:发送ACK(SDA拉低)。
- 若结束读取:发送NACK(SDA保持高电平)。
停止信号(STOP)
主机在SCL高电平期间拉高SDA线(产生上升沿),结束通信。
流程示意图:
START → 地址+写 → ACK → 寄存器地址 → ACK → 重复START → 地址+读 → ACK → 接收数据 → (ACK/NACK) → STOP
这种“先写后读”的设计源于IIC协议的寄存器寻址机制,主要原因如下:
- 指定目标寄存器位置
从机通常包含多个寄存器,直接读取时无法确定目标地址。通过先发送写命令+寄存器地址,明确告知从机后续需要读取的寄存器位置。例如,读取EEPROM的第2个存储单元时,需先写入地址0x02
。- 避免数据冲突
若直接发送读命令,从机可能默认从某个固定地址(如最近访问地址)返回数据,导致主机无法精确控制数据来源。先写后读确保操作原子性。- 协议复合格式支持
IIC支持重复起始信号(Repeated START) ,允许在不释放总线的情况下切换读写模式。这种机制减少了总线占用时间,提升效率。- 从机状态初始化
部分从机需要先接收控制命令(如传感器配置寄存器地址),才能切换到数据输出模式。例如,读取温度传感器数据前需先指定数据寄存器的地址。
实战
了解完iic通信的整个流程后,下面就了解一下具体是如何实现的
cubemx设置
常规的时钟和调试口(SWD)等的设置这里就不说了,就说IIC
和freertos
的设置,如下:
此处只需要配置一下对应的时钟和地址即可,此处地址是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从机的更多相关文章
- 硬件IIC主从机中断代码注释解析
目录 硬件IIC的主从中断在582的最新EVT中已支持. 对于IIC从机中断,例程中已封装好中断处理过程,用户调用app_i2c时,初始化中需要配置回调函数. 初始化的配置如下. struct i2c ...
- FreeRTOS - 中断使用注意
原文地址:http://www.cnblogs.com/god-of-death/p/6886823.html 注意点: 1.首先要将中断的嵌套全部设置为抢占优先级. 2.将freertos系统内核中 ...
- STM32CubeMX+FreeRTOS 定时器os_timer的使用
转载:https://blog.csdn.net/jacklondonjia/article/details/78497120在STM32CubeMX的FreeRTOS配置中,使能FreeRTOS的S ...
- FreeRTOS——中断管理
1. 只有以“FromISR”或"FROM_ISR"结束的API函数或宏才可以在中断服务函数中使用. 2. 除互斥信号量外,所有类型的信号量都可以调用 xSemaphoreTake ...
- 安装卡巴 OFFICE链接 出现这个过程被中断,由于本机的限制
今天 安装了卡巴后 office 超链接功能不能使用了,一点击超链接,就会发出警报,说”由于本机的限制,此操作已被取消,请与系统管理员联系“ 解决办法:1打开注册表2到这个位置:HKEY_CURREN ...
- STM32运行FreeRTOS出现prvTaskExitError错误死机
文件port.c prvTaskExitError();任务退出错误,一个可能在任务里面写了return,另一个可能任务切换退出问题,入栈和出栈的时候出了问题. static void prvTask ...
- FreeRTOS中断测试
configMAX_SYSCALL_INTERRUPT_PRIORITY 高于此优先级的中断,不能被禁止 #ifdef __NVIC_PRIO_BITS #define configPRIO_BITS ...
- FreeRTOS 中断配置和临界段
中断屏蔽寄存器 PRIMASK.FAULTMASK和BASEPRI 1.PRIMASK:这是个只有1个位的寄存器.当它置1时, 就关掉所有可屏蔽的异常,只剩下 NMI和硬fault可以响应.它的缺省值 ...
- STM32CubeMX FreeRTOS定时器的使用
配置STM32CubeMX如下 生成的Keil代码的创建启动定时器如下 /* Create the timer(s) */ /* definition and creation of myTimer0 ...
- STM32CubeMX FreeRTOS no definition for "osThreadGetState" 解决办法
用STM32CubuMX默认加入的FreeRTOS默认配置eTaskGetState是禁止的 把该功能设为Enabled编译就不会出错了 IAR的编译器要勾选Allow VLA
随机推荐
- Django实战项目-学习任务系统-用户管理
接着上期代码框架,开发第6个功能,用户管理,查看用户信息和学生用户属性值,尤其是总积分值,还可以查看积分流水明细,完成任务奖励积分,兑换物品消耗积分. 第一步:编写第6个功能-用户管理 1,编辑模型文 ...
- Flask快速入门2
六,Flask HTTP方法 Http协议是万维网中数据通信的基础.在该协议中定义了从指定URL检索数据的不同方法. 下表总结了不同的http方法: 序号 方法 描述 1 GET 以未加密的形式将数据 ...
- Dicom纯js的三维重建影像浏览器
主要功能介绍 实现通过浏览器浏览Dicom影像阅片.主要功能: 支持标准DIcom影像的2D浏览,预设窗位,伪彩,序列间,序列内多种布局方式. 影像处理,提供影像翻图.缩放.移动.透镜.反相.旋转.截 ...
- nginx下增加https端口的方法
一.进入根目录我是使用xshell进行远程连接服务器的,连接到服务器首先输入cd /进入到根目录在这里插入图片描述二.配置nginx.conf文件首先输入cd etc/nginx进入到nginx目录在 ...
- Docker 容器跨主机多网段通信解决方案
一.MacVlan实现Docker的跨主机网络通信的方案有很多,如之前博文中写到的通过部署 Consul服务实现Docker容器跨主机通信 Macvlan工作原理: Macvlan是Lin ...
- 响应式编程之Project Reactor
Project Reactor作为响应式编程范式的核心实现框架,严格遵循Reactive Streams规范体系,其架构设计完整包含了规范定义的四个核心组件:Publisher(数据源).Subscr ...
- CSS布局——左右固定中间填满
小小例子,注意中间的div应该写在最后,留爪. 先上个高清无码图 源码实现 <!DOCTYPE html> <html lang="en" xmlns=" ...
- FireDAC 下FDMEMTable的的字段自动获取
用clientdataset可以在设计时获取表结构.带来了不少方便.那么在FireDAC下如何处理? TSQLConnect继续provider的方法是没有问题的.而FireDAC不支持Provide ...
- JMeter+Grafana+Influxdb可视化性能监控平台搭建总结
说明:此次搭建基于unbuntu16.04系统搭建 1.安装docker 打开终端依次输入如下命令: 卸载旧版本 sudo apt-get remove docker docker-engine do ...
- eolinker请求预处理:配置全局环境变量后,步骤内去掉请求头信息
特别注意:需要使用全局变量或者预处理前务必阅读本链接https://www.cnblogs.com/becks/p/13713278.html 1.描述,用例配置环境变量后会在请求前自动加上域名和请求 ...