STM32IIC与SPI详解

news2024/11/14 17:30:15

单片机里的通信协议其实蛮多的,IIC;SPI;MQTT;CAN;包括串口也是一种通信协议。而串口通信虽然实现了全双工,但需要至少三根线,为了节省这一根线的成本,于是IIC诞生了。

目录

一.IIC协议

1.IIC的结构

2.IIC的特点

3.IIC的通信时序

4.具体配置(32HAL库版)

二.SPI协议

1.SPI的结构

2.SPI的特点

3.具体配置

三:联合配置

1.OLED屏幕的配置

OLED的数据格式

OLED的写入模式

Oled的命令

代码:

W25Q128配置

W25Q128的命令

W25Q128的寄存器

代码:

最终配置:

祝你看完就会


一.IIC协议

IIC协议其实就是一种标准外设协议,其实所谓协议,本质上就是各种时序电路的组合。IIC也不例外,它的最大特点就是特别的轻量级。

1.IIC的结构

IIC的简便和轻量级就在于它只有两条线,一条是时钟线SCL,一条是数据线SDA。说白了,就是在SCL的控制下在SDA上传输命令/数据。

在这张图上可以看到同时有很多设备连接在这两条线上,它们之间的关系一般是一主多从。

2.IIC的特点

1.IIC是半双工通信,因为SCL不负责传输数据,只有一条SDA数据线,无法发的同时接收信息。
2.如果你使用的是STM32芯片,那么如果没有配置上拉电阻的话GPIO口必须配置为开漏输出。因为假设时序出现错误两个设备一个讲SDA拉低一个将SDA拉高,那么将导致短路。
3.同步传输:数据的传输是严格按照时钟线来进行的。

3.IIC的通信时序

IIC的通信主要依靠四个信号:起始信号,应答信号,停止信号;读写信号;
START:

  
STOP:


ASK:


读写:

        其中ASK就是在在吧SCL拉高的期间读取SDA的的电平。读取完成后立即拉低。读取到的SDA假设为1则表示有应答,若为0则表示没有应答。
        读写信号则是在起始信号后将SCL拉低的期间向SDA上放或者读取数据。
        START和STOP比较简单不多赘述。 

4.具体配置(32HAL库版)

头文件:

#ifndef __IIC_H__
#define __IIC_H__

#include "sys.h"

#define SDA_PULL_UP()			HAL_GPIO_WritePin(GPIO_POTT, SDA, GPIO_PIN_SET)
#define SDA_PULL_DOWN()			HAL_GPIO_WritePin(GPIO_POTT, SDA, GPIO_PIN_RESET)
#define SCL_PULL_UP()			HAL_GPIO_WritePin(GPIO_POTT, SCL, GPIO_PIN_SET)
#define SCL_PULL_DOWN()			HAL_GPIO_WritePin(GPIO_POTT, SCL, GPIO_PIN_RESET)

void iic_Stop(GPIO_TypeDef *GPIO_POTT,uint16_t SDA,uint16_t SCL);
void iic_Start(GPIO_TypeDef *GPIO_POTT,uint16_t SDA,uint16_t SCL);
void iic_Ask(GPIO_TypeDef *GPIO_POTT,uint16_t SDA,uint16_t SCL);
void iic_Sendbyte(GPIO_TypeDef *GPIO_POTT,uint16_t SDA,uint16_t SCL,uint8_t DATA);

#endif

首先我这里用了大量的宏定义了很多宏函数来方便控制对应线的电平高低。另外如果你看的细致的话会发现我这里宏函数中的参数也是宏,然而你却在这儿找不到这个宏。其实它并不是宏,我用了一种稍微怪的方式来简便我在IIC定义里的工作量,但加大了一点调用的工作量。 
源文件:

#include "IIC.h"

void iic_Stop(GPIO_TypeDef *GPIO_POTT,uint16_t SDA,uint16_t SCL){
	
	SCL_PULL_UP();
	SDA_PULL_DOWN();			//先拉高SCL再拉高SDA
	SDA_PULL_UP();
	
}

void iic_Start(GPIO_TypeDef *GPIO_POTT,uint16_t SDA,uint16_t SCL){
	SCL_PULL_UP();			//SCL高电平期间SDA下降沿
	SDA_PULL_UP();
	SDA_PULL_DOWN();
	SCL_PULL_DOWN();
}

void iic_Ask(GPIO_TypeDef *GPIO_POTT,uint16_t SDA,uint16_t SCL){
	SCL_PULL_UP();		
	SCL_PULL_DOWN();
}

void iic_Sendbyte(GPIO_TypeDef *GPIO_POTT,uint16_t SDA,uint16_t SCL,uint8_t DATA){
	uint8_t i,tmp = DATA;
										//主机在SCL低电平期间在SDA上放数据
	for(i = 0; i<8; i++){
		if((tmp & 0x80) == 0x80)		//DATA &= 1000 0000 
			SDA_PULL_UP();
		else
			SDA_PULL_DOWN();
		
		SCL_PULL_UP();				//从机在SCL高电平期间读取这一位		
		SCL_PULL_DOWN();
		tmp = tmp << 1;
	}
}

想必如果你由上面的疑问看了源文件也就理解 ,我定义的宏函数里的参数并不是宏而是调用它的函数里的参数。所以我增加的调用方的工作量就是传参很多,IIC这里就非常简介易懂了。

另外一个点就是数据传输函数中是一位一位放的,不断地放不断地左移知道8次轮回完成一字节的传输则结束。 

二.SPI协议

SPI的全称是Serial Peripheral Interface。有基础的人估计一眼就看见Serial(串口)了。没错,其实这东西它使用非常像串口。不仅如此,配置起来也非常像串口。

1.SPI的结构

SPI一共由四条线组成:SCK;MISO;MOSI;NSS(CS)

其中,MISO和MOSI分别是:Master Input Slave Output

这里某些同学不要想歪了哈,Master这里就是主机,Slave就是从机,所以这两根线就很好理解了,简单说就是RX和TX一样。

然而:虽然这里也是两条线也是全双工,但是SPI的传输数据方式却是非常的特殊的

图中画圈的地方都是SPI的重点,其中特殊就在于它的位移寄存器 。

2.SPI的特点

1.SPI最大的特点就来自于它的位移寄存器,它每发送一字节的数据就必须收一字节的数据,同样的,它要收一字节的数据就必须发出一字节数据
2.看了特点一你会认为SPI是强制全双工的,但其实不是,它完全可以配置为半双工或者只有一条数据线。
3.SPI的工作模式比较特别,它的工作模式取决于时钟极性时钟相位

第三点展开来说:
时钟极性控制SPI数据线上没有数据时SCL的电平状态:
CPOL为0则空闲时为低电平,反之则为高电平。
CPHA为0则每一个奇数边缘采样数据(第一个),反之则为偶数边缘采样。

你要是看不懂也没关系,总之就是CPOL和CPHA排列组合一共有四种工作模式。

3.具体配置

SPI的配置一般是依靠板子上确定的外设的,因为它不像IIC那样的轻量级。它有很多的东西需要配,比如:分频数;时钟启动;工作模式;是否半双工等等。

#include "SPI.h"
SPI_HandleTypeDef spi_handle = {0};

void SPI_INIT(void){
	
	spi_handle.Instance = SPI1;
	spi_handle.Init.Mode = SPI_MODE_MASTER; 							//配置主从模式
	spi_handle.Init.Direction = SPI_DIRECTION_2LINES;					//半双工全双工选择
	spi_handle.Init.DataSize = SPI_DATASIZE_8BIT;
	spi_handle.Init.CLKPolarity = SPI_POLARITY_LOW;					//低电平有效
	spi_handle.Init.CLKPhase = SPI_PHASE_1EDGE;						//奇数取值
	spi_handle.Init.NSS = SPI_NSS_SOFT;								//软件调控NSS
	spi_handle.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_256;	//分频数选择
	spi_handle.Init.FirstBit = SPI_FIRSTBIT_MSB;	
	spi_handle.Init.TIMode = SPI_TIMODE_DISABLE;
    spi_handle.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
    spi_handle.Init.CRCPolynomial = 7;	
	HAL_SPI_Init(&spi_handle);

}

void HAL_SPI_MspInit(SPI_HandleTypeDef *spi_handle){
    if(spi_handle->Instance == SPI1)
    {
        GPIO_InitTypeDef gpio_initstruct;
        __HAL_RCC_GPIOA_CLK_ENABLE();                           
        __HAL_RCC_SPI1_CLK_ENABLE();

        gpio_initstruct.Pin = GPIO_PIN_4;          
        gpio_initstruct.Mode = GPIO_MODE_OUTPUT_PP;           
        gpio_initstruct.Pull = GPIO_PULLUP;                    
        gpio_initstruct.Speed = GPIO_SPEED_FREQ_HIGH;          
        HAL_GPIO_Init(GPIOA, &gpio_initstruct);
        
        gpio_initstruct.Pin = GPIO_PIN_5 | GPIO_PIN_7;          
        gpio_initstruct.Mode = GPIO_MODE_AF_PP;           
        HAL_GPIO_Init(GPIOA, &gpio_initstruct);
        
        gpio_initstruct.Pin = GPIO_PIN_6;          
        gpio_initstruct.Mode = GPIO_MODE_INPUT;           
        HAL_GPIO_Init(GPIOA, &gpio_initstruct);
    }
	
}

uint8_t SPI_swap_byte(uint8_t data){
	uint8_t rev_data = 0;
	HAL_SPI_TransmitReceive(&spi_handle,&data,&rev_data,1,1000);
	return rev_data;
}

这里对于Instance的配置还是挺繁琐的,但是也只是繁琐并没有难度。剩下的就是时钟;GPIO配置;然后可以看到在发送的部分它的函数叫HAL_SPI_TransmitReceive,对应上了我们说的接收的同时必须发送。

三:联合配置

两个协议都学会了,如何应用呢?IIC的入门外设还是非常经典的--OLED屏幕。那SPI呢?可以采用一块存储块进行配合读写。我这里就用W25Q128的存储模块。

1.OLED屏幕的配置

Oled的配置核心其实就是显示东西嘛,所以其实总结成一句话就是:

                        告诉屏幕在哪里显示什么东西。

OLED是只可以选择亮或者灭的,所以显示什么东西其实最终说白了是各种点阵,这个东西其实没啥技术含量。另外OLED模块的初始化也是不需要学的,你就照着把一堆命令直接复制过来用就行了。所以主要的配置重点在于:如何告诉他

那么这个时候我们就需要读一下OLED的手册了。

OLED的数据格式


以上为手册的原图,可以看到它的发送全部都是 Control byte + Data byte。那么这是什么意思呢?

说白了,就是Control byte 用来让Oled判断接下来所接收的数据到底是命令还是显示数据
可以看到,整个数据帧的格式就是:

        Start信号--写入数据模式--ASK--Control byte--Data byte--ASK--Stop信号

那么此时的问题就变成了写入模式是什么?以及Control byte是什么?

Control byte看右下角,D/C的后一位写0则表示接下来的数据是Command,写1则表示接下来的数据Data。

其实自己看手册可绝望了,不信? 给:

OLED的写入模式

一共有四种:

         

总而言之呢,其实你只记得住第一个就行,因为我们用哪一个都ok,第一个在不配置的情况下是被默认选定的。只需要知道它在写完后会自动向右偏移一位。当写道最右边后会返回来这一行的最左边。那么这里就需要稍微知道一下屏幕的大小了

整个屏幕是128*64的,每一个字节的八位是竖着排列的,逻辑为1的就亮为0就灭。
64 / 8 = 8,这也是为什么是8个page。

Oled的命令


Oled其实内置的是一块芯片,所以它的命令其实还是蛮多的。但是可以看到这里我给你截下来的命令都是关于位置设定和写入模式的设定的。

其实这里主要的就是Page和Column 的设定,Page的很简单B0~B7分别表示Page0到Page7.
主要就是Column需要给两次,因为有 128位嘛,所以需要两个字节。这就在编程的方面稍微有点小麻烦。

代码:
#include "oled.h"
#include "delay.h"
#include "front.h"

void OLED_INIT(){
    GPIO_INIT();
    
    delay_ms(100);    
    Oled_Write_Cmd(0xAE);   
    Oled_Write_Cmd(0xD5);   
    Oled_Write_Cmd(0x80);   
    Oled_Write_Cmd(0xA8);   
    Oled_Write_Cmd(0x3F);   
    Oled_Write_Cmd(0xD3);   
    Oled_Write_Cmd(0x00);   
    Oled_Write_Cmd(0x40);   
    Oled_Write_Cmd(0xA1);   
    Oled_Write_Cmd(0xC8);   
    Oled_Write_Cmd(0xDA);   
    Oled_Write_Cmd(0x12);
    Oled_Write_Cmd(0x81);   
    Oled_Write_Cmd(0xCF);   
    Oled_Write_Cmd(0xD9);   
    Oled_Write_Cmd(0xF1);
    Oled_Write_Cmd(0xDB);   
    Oled_Write_Cmd(0x30);
    Oled_Write_Cmd(0xA4);   
    Oled_Write_Cmd(0xA6);   
    Oled_Write_Cmd(0x8D);   
    Oled_Write_Cmd(0x14);
    Oled_Write_Cmd(0xAF);   
	
}

void GPIO_INIT(){
	GPIO_InitTypeDef gpio_init;
	
	gpio_init.Mode = GPIO_MODE_OUTPUT_PP;
	gpio_init.Pin = SDA_PIN|SCL_PIN;
	gpio_init.Pull = GPIO_PULLUP;
	gpio_init.Speed = GPIO_SPEED_FREQ_HIGH;
	
	__HAL_RCC_GPIOB_CLK_ENABLE();
	HAL_GPIO_Init(GPIOB, &gpio_init);
}

void Oled_Write_Cmd(uint8_t cmd){
	
	iic_Start(SDA_PORT,SDA_PIN,SCL_PIN);
	iic_Sendbyte(SDA_PORT,SDA_PIN,SCL_PIN,0x78);
	iic_Ask(SDA_PORT,SDA_PIN,SCL_PIN);
	iic_Sendbyte(SDA_PORT,SDA_PIN,SCL_PIN,0x00);
	iic_Ask(SDA_PORT,SDA_PIN,SCL_PIN);
	iic_Sendbyte(SDA_PORT,SDA_PIN,SCL_PIN,cmd);
	iic_Ask(SDA_PORT,SDA_PIN,SCL_PIN);
	iic_Stop(SDA_PORT,SDA_PIN,SCL_PIN);
}

void Oled_Write_Data(uint8_t data){
	iic_Start(SDA_PORT,SDA_PIN,SCL_PIN);
	iic_Sendbyte(SDA_PORT,SDA_PIN,SCL_PIN,0x78);
	iic_Ask(SDA_PORT,SDA_PIN,SCL_PIN);
	iic_Sendbyte(SDA_PORT,SDA_PIN,SCL_PIN,0x40);
	iic_Ask(SDA_PORT,SDA_PIN,SCL_PIN);
	iic_Sendbyte(SDA_PORT,SDA_PIN,SCL_PIN,data);
	iic_Ask(SDA_PORT,SDA_PIN,SCL_PIN);
	iic_Stop(SDA_PORT,SDA_PIN,SCL_PIN);
}

void Oled_Set_Position(uint8_t page,uint8_t column){
	Oled_Write_Cmd(0xB0 + page);						//选择页数
	Oled_Write_Cmd(column & 0x0F);						//低四位
	Oled_Write_Cmd(((column & 0xF0) >> 4) | 0x10);		//高四位

}

void Oled_clear(void){
	uint8_t i,j;	
	for(i = 0;i < 8;i++){			
		Oled_Set_Position(i,0);
		for(j = 0;j<128;j++){
			Oled_Write_Data(0);
		}	
	}
	
}

void Oled_show_picture(uint8_t hight,uint8_t wide,uint8_t *picture){
	uint8_t page,i,j;
	page = hight / 8;
	
	for(i = 0; i<page; i++){
		Oled_Set_Position(i,0);
		for(j = 0; j<wide; j++){
			Oled_Write_Data(picture[wide * i + j]);
		}
	}
	
}

void Oled_show_char(uint8_t Ocolumn, uint8_t Opage, uint8_t num, uint8_t size)
{
    uint8_t i, j, page;
    
    num = num - ' ';
    page = size / 8;
    if(size % 8)
        page++;
    
    for(j = 0; j < page; j++)
    {
        Oled_Set_Position(Opage + j,Ocolumn);
        for(i = size / 2 * j; i < size /2 * (j + 1); i++)
        {
            if(size == 12)
                Oled_Write_Data(ascii_6X12[num][i]);
            else if(size == 16)
                Oled_Write_Data(ascii_8X16[num][i]);
            else if(size == 24)
                Oled_Write_Data(ascii_12X24[num][i]);
                
        }
    }
}

void Oled_show_string(uint8_t column, uint8_t page, char *p, uint8_t size)
{
    while(*p != '\0')
    {
        Oled_show_char(column, page, *p, size);
        column += size/2;
        p++;
    }
}

W25Q128配置

对于它其实主要就是配置 读;写;等待空闲;这就是一个FLASH储存器。
FLASH的唯一特性就是:它只能写0不能写1.

并且该模块不需要初始化,命令同样很多但是我们用的很少。

W25Q128的命令
0x06写使能写入数据/擦除之前,必须先发送该指令
0x05读 SR1判定 FLASH 是否处于空闲状态,擦除用
0x03读数据读取数据
0x02页写写入数据,最多写256字节
0x20扇区擦除扇区擦除指令,最小擦除单位

这里主要讲一些要点:
1.在做任何通讯的操作之前,必须要拉低CS也就是拉低片选。这也是它和IIC不同的地方,IIC是进行寻址,而它通过拉低片选。操作完成后必须再拉高
2.在进行读/写操作是需要发送地址,这个地址是三字节的而发送时每次发送的是一个字节,所以就需要位操作。

void send_addr(uint32_t address){
	
	SPI_swap_byte((uint8_t)address >> 16);		//右移的同时进行强转,强转保留低位,所以这里是发送高位
	SPI_swap_byte((uint8_t)address >> 8);		//中间8位
	SPI_swap_byte((uint8_t)address);			//低8位
}

3.任何和写相关的操作比如:马上要写,刚刚写完;必须进行等待空闲。

W25Q128的寄存器

这里就介绍这个一个,就是为了等待空闲操作使用。
在编程过程中,步骤也很简单:

拉低片选----向芯片发送读取Busy的命令----发送的同时接收----while来阻塞知道接收到BusyFlag为0----拉高片选。

void wait_busy(void){
	W25Q128_CS(0);
	SPI_swap_byte(FLASH_ReadStatusReg1);		//读取状态寄存器指令
	SPI_swap_byte(FLASH_DummyByte);				//接收状态寄存器flag	
	while((SPI_swap_byte(0xFF) & 0x01) == 1);	//等待知道寄存器busy位变为0	
	W25Q128_CS(1);
}
代码:
 
#include "w25q128.h"

uint16_t w25q128_config(void){
    uint16_t device_id = 0;
    W25Q128_CS(0);
    
    SPI_swap_byte(FLASH_ManufactDeviceID);
    SPI_swap_byte(0x00);
    SPI_swap_byte(0x00);
    SPI_swap_byte(0x00);
    device_id = SPI_swap_byte(FLASH_DummyByte) << 8;
    device_id |= SPI_swap_byte(FLASH_DummyByte);
    
    W25Q128_CS(1);
    return device_id;	
}

void wait_busy(void){
	W25Q128_CS(0);
	SPI_swap_byte(FLASH_ReadStatusReg1);		//读取状态寄存器指令
	SPI_swap_byte(FLASH_DummyByte);				//接收状态寄存器flag	
	while((SPI_swap_byte(0xFF) & 0x01) == 1);	//等待知道寄存器busy位变为0	
	W25Q128_CS(1);
}

void send_addr(uint32_t address){
	
	SPI_swap_byte((uint8_t)address >> 16);		//右移的同时进行强转,强转保留低位,所以这里是发送高位
	SPI_swap_byte((uint8_t)address >> 8);		//中间8位
	SPI_swap_byte((uint8_t)address);			//低8位
}

void w25q128_writ_enable(void)
{
    W25Q128_CS(0);
    SPI_swap_byte(FLASH_WriteEnable);
    W25Q128_CS(1);
}

void read_data(uint32_t address, uint8_t *rev_data, uint8_t size){
	
	W25Q128_CS(0);
	
	SPI_swap_byte(FLASH_ReadData);
	send_addr(address);
	
	uint8_t i = 0;	
	for(i = 0; i < size; i++){
		rev_data[i] = SPI_swap_byte(0xFF);			//直接根据指针写进去了所以不需要返回值
	}
	W25Q128_CS(1);
}


void write_page_data(uint32_t address, uint8_t *write_data, uint8_t size){	
	
	w25q128_writ_enable();
	wait_busy();
	
	W25Q128_CS(0);
	SPI_swap_byte(FLASH_PageProgram);
	send_addr(address);
	
	uint16_t i = 0;
	for(i = 0; i< size; i++){
		SPI_swap_byte(write_data[i]);
	}
	W25Q128_CS(1);
	wait_busy();
	
}

void erase_page(uint32_t address){

    w25q128_writ_enable();

    wait_busy();

    W25Q128_CS(0);

    SPI_swap_byte(FLASH_SectorErase);

    send_addr(address);

    W25Q128_CS(1);

    wait_busy();

}

最终配置:

把文件合并,接线合并,初始化后在Main函数更改写法让读写操作显示在屏幕上:

#include "sys.h"
#include "uart1.h"
#include "delay.h"
#include "led.h"
#include "SPI.h"
#include "w25q128.h"
#include "oled.h"
#include "IIC.h"
#include "string.h"

uint8_t data_write[4] = {0xAA, 0xBB, 0xCC, 0xDD};
uint8_t data_read[4] = {0};
int main(void)
{
    HAL_Init(); 
	stm32_clock_init(RCC_PLL_MUL9); 	
	SPI_INIT();
	uart1_init(115200);	
	OLED_INIT();
	Oled_clear();	
	
	Oled_show_string(1,1,"HELLO WKX",24);	
	delay_ms(1000);	
	Oled_clear();
	
	char show[8];
	uint16_t number = w25q128_config();
	sprintf(show,"device id :%X",number);
	Oled_show_string(1,1,"Loading...",24);	
	delay_ms(1000);	
	Oled_clear();
	
	Oled_show_string(1,1,show,16);
	delay_ms(1000);
	Oled_clear();	
	
	write_page_data(0x000000, data_write, 4);
	Oled_show_string(1,1,"Writed succes",16);
	delay_ms(500);
	Oled_clear();
	
	Oled_show_string(1,1,"Reading data",16);
	delay_ms(500);
	Oled_clear();
	
    read_data(0x000000, data_read, 4);
	memset(show,0,8);
	sprintf(show,"%X",data_read[0]);
	Oled_show_string(1,1,show,16);
	sprintf(show,"%X",data_read[1]);
	Oled_show_string(40,1,show,16);
	sprintf(show,"%X",data_read[2]);
	Oled_show_string(80,1,show,16);
    while(1)
    { 
		
    }
}


好了给看看效果:

OLED&W25&Q128

祝你看完就会
 

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

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

相关文章

【产业前沿】树莓集团如何以数字媒体产业园为引擎,加速产业升级?

在数字化转型的浪潮中&#xff0c;树莓集团以敏锐的洞察力和前瞻性的战略眼光&#xff0c;将数字媒体产业园打造成为产业升级的强劲引擎。这一创新举措不仅为传统行业插上了数字的翅膀&#xff0c;更为整个产业链注入了新的活力与可能。 树莓集团深知&#xff0c;数字媒体产业园…

【人工智能】AI最终会取代程序员吗?

1. 前言 到 2030 年&#xff0c;40% 的编程任务将实现自动化。这个令人难以置信的统计数据凸显了人工智能在软件工程中日益增长的影响力&#xff0c;并引发了一个问题&#xff1a;人工智能会彻底接管软件工程吗&#xff1f; 人工智能技术正在蓬勃发展&#xff0c;有望实现大量…

【实战】Spring Security Oauth2自定义授权模式接入手机验证

文章目录 前言技术积累Oauth2简介Oauth2的四种模式授权码模式简化模式密码模式客户端模式自定义模式 实战演示1、mavan依赖引入2、自定义手机用户3、自定义手机用户信息获取服务4、自定义认证令牌5、自定义授权模式6、自定义实际认证提供者7、认证服务配置8、Oauth2配置9、资源…

C语言程序设计-[11] 循环结构嵌套

1、循环结构嵌套形式 上面三种循环语句结构可以相互嵌套&#xff0c;组合非常灵活。循环嵌套需要记住最重要的一点&#xff1a;”外循环执行一次&#xff0c;内循环要完整执行一遍”&#xff0c;要通过实例加深对这一句话的理解。 注1&#xff1a;一个循环结构由四个要素构成&…

Java设计模式-建造者模式-一次性理解透

1. 建造者模式简介 今天我们将研究 Java 中的建造者模式&#xff08;Builder 模式&#xff09;。Builder 设计模式是一种创建型设计模式&#xff0c;也被称为生成器模式&#xff0c;类似于工厂模式和抽象工厂模式。 该模式用于创建复杂对象&#xff0c;允许用户创建不同类型的…

【Python】PyWebIO 初体验:用 Python 写网页

目录 前言1 使用方法1.1 安装 Pywebio1.2 输出内容1.3 输入内容 2 示例程序2.1 BMI 计算器2.2 Markdown 编辑器2.3 聊天室2.4 五子棋 前言 前两天正在逛 Github&#xff0c;偶然看到一个很有意思的项目&#xff1a;PyWebIo。 这是一个 Python 第三方库&#xff0c;可以只用 P…

100 Exercises To Learn Rust 挑战!准备篇

公司内部的学习会非常活跃&#xff01;我也参与了Rust学习会&#xff0c;并且一直在研究rustlings。最近&#xff0c;我发现了一个类似于rustlings的新教程网站&#xff1a;Welcome - 100 Exercises To Learn Rust。 rustlings是基于Rust的权威官方文档《The Rust Programming…

汽车免拆诊断案例 | 2010款劳斯莱斯古斯特车中央信息显示屏提示传动系统故障

故障现象  一辆2010款劳斯莱斯古斯特车&#xff0c;搭载N74发动机&#xff0c;累计行驶里程约为11万km。车主反映&#xff0c;起动发动机后组合仪表和中央信息显示屏均提示传动系统故障。用故障检测仪检测&#xff0c;发现发动机控制模块2&#xff08;DME2&#xff09;中存储…

SmartBI拓展包二开入门开发

前言 新接到一个项目拓展包三开的需求&#xff0c;没有相关经验&#xff0c;学习开发&#xff0c;本文尝试通过简单的定位以及指导&#xff0c;确定修改点 SmartBI帮助文档-拓展包开发 登录 http://localhost:18080/smartbi/vision/index.jsp后台配置 上传拓展包&#xff0…

MySQL和Redis的数据一致性

MySQL和Redis的数据一致性 多线程环境下的涉及读写的缓存才会存在MySQL和Redis的数据不一致问题 先删除缓存再更新数据库再延时删除缓存 线程一删除缓存线程一更新数据线程二开始查数据如果第二步线程一更新数据延时&#xff0c;那么线程二会重新从数据库加载数据&#xff0…

超好用的windows系统工具PowerToys

文章目录 Github地址基本介绍使用 Github地址 PowerToys 基本介绍 是windows官方好用的工具箱&#xff0c;包括各种工具 使用 要带上win键 此工具安装后每次运行电脑自启动&#xff0c;桌面没有快捷方式&#xff0c;只能右下角 窗口在上效果演示&#xff0c;会被蓝线框到…

基于GeoTools使用JavaFx进行矢量数据可视化实战

目录 前言 一、JavaFx展示原理说明 二、GeoTools的Maven依赖问题 三、引入Geotools相关的资源包 四、创建JavaFx的Canvas实例 五、JavaFx的Scene和Node的绑定 六、总结 前言 众所周知&#xff0c;JavaFx是Java继Swing之后的又一款用于桌面应用的开发利器。当然&#xff0…

江科大/江协科技 STM32学习笔记P22

文章目录 AD单通道&AD多通道ADC基本结构和ADC有关的库函数AD单通道AD.cmain.c连续转换&#xff0c;非扫描模式的AD.c AD多通道AD.cmain.c AD单通道&AD多通道 ADC基本结构 第一步&#xff0c;开启RCC时钟&#xff0c;包括ADC和GPIO的时钟&#xff0c;ADCCLK的分频器也需…

openvidu私有化部署

openvidu私有化部署 简介 OpenVidu 是一个允许您实施实时应用程序的平台。您可以从头开始构建全新的 OpenVidu 应用程序&#xff0c;但将 OpenVidu 集成到您现有的应用程序中也非常容易。 OpenVidu 基于 WebRTC 技术&#xff0c;允许开发您可以想象的任何类型的用例&#xf…

回归预测|基于黏菌优化LightGBM的数据回归预测Matlab程序SMA-LightGBM 多特征输入单输出

回归预测|基于黏菌优化LightGBM的数据回归预测Matlab程序SMA-LightGBM 多特征输入单输出 文章目录 前言回归预测|基于黏菌优化LightGBM的数据回归预测Matlab程序 多特征输入单输出 SMA-LightGBM 一、SMA-LightGBM模型1. **LightGBM**2. **黏菌智能优化算法&#xff08;SMA&…

知识中台是什么?它如何实现高效知识管理?

引言 在信息化浪潮席卷全球的今天&#xff0c;企业面临的不仅是市场的激烈竞争&#xff0c;更是知识爆炸带来的管理挑战。如何在浩瀚的信息海洋中提炼出有价值的知识&#xff0c;并将其快速转化为企业的核心竞争力&#xff0c;成为了每个企业必须深思的问题。在此背景下&#…

二叉树的重要概念

前言&#xff1a; 二叉树是树形结构的一个重要类型&#xff0c;一般的树也可以转化成二叉树来解决问题。在数据结构的系统中&#xff0c;树形结构也是信息存储和遍历的重要实现&#xff0c;二叉树的最大特点就是一个根包含着左右子树的形式&#xff0c;许多具有层次关系的问题…

单元测试注解:@ContextConfiguration

ContextConfiguration注解 ContextConfiguration注解主要用于在‌Spring框架中加载和配置Spring上下文&#xff0c;特别是在测试场景中。 它允许开发者指定要加载的配置文件或配置类的位置&#xff0c;以便在运行时或测试时能够正确地构建和初始化Spring上下文。 基本用途和工…

【开源社区】Elasticsearch(ES)中空值字段 null_value 及通过exists查找非空文档

文章目录 0、声明1、问题描述2、问题剖析2.1 NULL或者空值类型有哪些2.2 案例讲解&#xff1a;尝试检索值为 null 的字段2.3 解决思路 3、使用 null_value 的诸多坑&#xff08;避免生产事故&#xff09;3.1 null_value 替换的是索引&#xff0c;并不会直接替换源数据3.2 不支持…

LVS(Linux Virtual Server)详解

LVS&#xff08;Linux Virtual Server&#xff09;是一个用于负载均衡的开源软件项目&#xff0c;旨在通过集群技术实现高性能、高可用的服务器系统。它运行在Linux操作系统上&#xff0c;并且可以利用内核级的资源来提高性能和稳定性。 思维导图 LVS的工作原理 LVS主要基于Ne…