当前位置: 首页 > news >正文

单片机-STM32部分:14、SPI

飞书文档https://x509p6c8to.feishu.cn/wiki/VYYnwOc9Zi6ibFk36lYcPQdRnlf

什么是SPI

SPI 是英语Serial Peripheral interface的缩写,顾名思义就是串行外围设备接口。是Motorola(摩托罗拉)首先在其MC68HCXX系列处理器上定义的。

SPI,是一种高速的,全双工,同步的通信总线。

高速的:通常可以达到几百kHz到几十MHz的范围。
全双工:支持全双工,可以同时收发
总线:支持一个主设备,一个或多个从设备。

SPI主从模式

SPI分为主、从两种模式,一个SPI通讯系统需要包含一个(且只能是一个)主设备,一个或多个从设备。提供时钟的为主设备(Master),接收时钟的设备为从设备(Slave),SPI接口的读写操作,都是由主设备发起。当存在多个从设备时,通过各自的片选信号进行管理。

SPI信号线

SPI接口一般使用四条信号线通信

MOSI(Master Output Slave Input): 主设备输出/从设备输入引脚。该引脚在主模式下发送数据,在从模式下接收数据。

MISO(Master Input Slave Output): 主设备输入/从设备输出引脚。该引脚在从模式下发送数据,在主模式下接收数据。

SCLK:串行时钟信号,由主设备产生。

CS/SS:从设备片选信号,由主设备控制。它的功能是用来作为“片选引脚”,也就是选择指定的从设备,让主设备可以单独地与特定从设备通讯,避免数据线上的冲突。

UART、IIC、SPI的对比

UART

IIC

SPI

通讯方式

异步

同步

同步

通讯线

TXD 发送

RXD 接收

GND 地

SDA 数据

SCL 时钟

MOSI 主发从收

MISO 主收从发

SCK 时钟

CS 片选

设备从属

一对一

总线

总线

通讯速率

从几十Kbps到几Mbps

标准模式下可达100kbps,快速模式下可达400kbps,高速模式下可达3.4Mbps

几十Mbps甚至上百Mbps

场景

UART 常用于串行通信,如RS-232、RS-485通信,以及计算机与嵌入式设备间的通信。

I²C 因其简洁的连线和地址机制,适用于板级设备间的通信,如传感器、EEPROM等。

SPI 适用于短距离、高速数据传输,常见于传感器、屏幕、存储器(如Flash)与MCU之间的通信。

SPI参数说明

SPI1设置为全双工主模式,硬件NSS关闭

模式设置:全双工主机模式

  • 有主机模式全双工/半双工
  • 从机模式全双工/半双工
  • 只接收主机模式/只接收从机模式
  • 只发送主机模式

硬件NSS(片选信号):Disable

可以选择使能,也可以使用其他IO口接到芯片的NSS上进行代替,如果只连接了一个从设备,可以不用开启片选。

其中SIP1的片选NSS : SPI1_NSS(PA4)

其中SIP2的片选NSS : SPI2_NSS(PB12)

如果片选引脚没有连接 SPI1_NSS(PA4)或者SPI2_NSS(PB12),则需要选择软件片选。

在stm32中,每个spi控制器的NSS信号引脚都具有两种功能,即输入和输出。所谓的输入就是NSS管脚的信号给自己。所谓的输出就是将NSS的信号送出去,给从机。

SPI设置帧格式为摩托罗拉格式,数据长度8BitsMSB高位先传输,时钟分频64CPOLLowCPHA为第一个边沿,关闭CRC,软件NSS

帧格式:Motorla格式(摩托罗拉格式)目前只提供该格式,SPI标准协议就是由摩托罗拉设计的。

数据长度:8Bits

8Bits或16Bits,如果为16Bits,每次可以发送2Byte数据。

FirstBitMSB先输出

MSB/LSB,通信中先传高位还是低位,和传输协议有关,主机从机保持一致即可。

时钟分频:分频为64分频

可见:SPI1是在挂APB2上的,SPI2是挂在APB1上的

当PCLK2为72M时,SPI速率为72/64=1.125M,在保证稳定情况下,STM32F1建议SPI不超过18M。

采样模式设置:

时钟极性CPOL是指SPI通讯设备处于空闲状态时,SCK信号线的电平,也就是通讯开始时SCK的电平。

时钟相位CPHA是指数据的采样的时刻。

CPOL和CPHA的设置,决定SPI在什么时候进行采样,会影响读取到的数据。
根据时钟极性(CPOL)及相位(CPHA)不同,SPI有四种工作模式.
用的比较多的是模式:CPOL=0,CPHA=0,主机从机保持一致即可

时钟极性(CPOL)定义了时钟空闲状态电平:
CPOL=0为时钟空闲时为低电平
CPOL=1为时钟空闲时为高电平

时钟相位(CPHA)定义数据的采集时间。
CPHA=0:在时钟的第一个跳变沿(上升沿或下降沿)进行数据采样。
CPHA=1:在时钟的第二个跳变沿(上升沿或下降沿)进行数据采样。

不开启CRC检验

NSS为软件控制

我们用得更多的是由软件控制某些 GPIO引脚单独作为SS信号,这个GPIO引脚可以随便选择,像板卡中只有一个从设备,我们可以不使用片选引脚。如果我们把 NSS引脚配置为硬件自动控制,SPI模块能够自动判别它能否成为SPI的主机,或自动进入SPI从机模式。

生成代码

轮询:最基本的发送接收函数,就是正常的发送数据和接收数据
HAL_StatusTypeDef HAL_SPI_Transmit(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size, uint32_t Timeout);
*hspi: 选择SPI1/2,比如&hspi1,&hspi2
*pData : 需要发送的数据,如果设置为16bit,可以设置为数组
Size: 发送数据的字节数,1 就是发送一个字节数据
Timeout: 超时时间,就是执行发送函数最长的时间,超过该时间自动退出发送函数
成功返回HAL_OK

HAL_StatusTypeDef HAL_SPI_Receive(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size, uint32_t Timeout);
*hspi: 选择SPI1/2,比如&hspi1,&hspi2
*pData : 接收发送过来的数据的数组
Size: 接收数据的字节数,1 就是接收一个字节数据
Timeout: 超时时间,就是执行接收函数最长的时间,超过该时间自动退出接收函数
成功返回HAL_OK

HAL_StatusTypeDef HAL_SPI_TransmitReceive(SPI_HandleTypeDef *hspi, uint8_t *pTxData, uint8_t *pRxData, uint16_t Size,
                                          uint32_t Timeout);
SPI在发送数据的同时接收指定长度的数据
*hspi: 选择SPI1/2,比如&hspi1,&hspi2
pTxData:接收数据缓冲区首地址
pRxData:接收数据缓冲区首地址
size:要发送/接收数据的字节数
成功返回HAL_OK



中断相关接口:                                       
HAL_StatusTypeDef HAL_SPI_Transmit_IT(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size);
HAL_StatusTypeDef HAL_SPI_Receive_IT(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size);
HAL_StatusTypeDef HAL_SPI_TransmitReceive_IT(SPI_HandleTypeDef *hspi, uint8_t *pTxData, uint8_t *pRxData,
                                             uint16_t Size);


DMA相关接口:                                         
HAL_StatusTypeDef HAL_SPI_Transmit_DMA(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size);
HAL_StatusTypeDef HAL_SPI_Receive_DMA(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size);
HAL_StatusTypeDef HAL_SPI_TransmitReceive_DMA(SPI_HandleTypeDef *hspi, uint8_t *pTxData, uint8_t *pRxData,
                                              uint16_t Size);

中断方式

中断模式和串口比较类似,但是SPI是总线通讯,一般由主机先在main.c中发送数据开启中断接收,并在中断回调函数中接收数据处理,处理完成后重新启动中断接收即可

//main里启动中断
uint8_t sendData[2] = {1,2};
uint8_t receiveData[2];
HAL_SPI_TransmitReceive_IT(&hspi1, sendData, receiveData, 2);//中断回调函数
void HAL_SPI_TxRxCpltCallback(SPI_HandleTypeDef *hspi)
{// 数据发送完成回调函数if (hspi == &hspi1){HAL_SPI_TransmitReceive_IT(&hspi1, sendData, receiveData, 2);}
}

DMA方式

添加SPI1的两个DMA通道,分别设置为Circular模式,传输的数据宽度要和SPI的数据位数相对应(spi是8位传输,这里就改为BYTE),设置DMA后,会默认开启SPI通道DMA中断。


 


DMA频繁发送时,需检测是否传输完成
void DMA1_Channel3_IRQHandler(void)
{/* USER CODE BEGIN DMA1_Channel3_IRQn 0 */if(__HAL_DMA_GET_FLAG(&hdma_spi1_tx, DMA_FLAG_TC1)){}/* USER CODE END DMA1_Channel3_IRQn 0 */HAL_DMA_IRQHandler(&hdma_spi1_tx);/* USER CODE BEGIN DMA1_Channel3_IRQn 1 *//* USER CODE END DMA1_Channel3_IRQn 1 */
}接收同理,可以在DMA接收传输完成后,才读出
void DMA1_Channel2_IRQHandler(void)
{/* USER CODE BEGIN DMA1_Channel2_IRQn 0 */if(__HAL_DMA_GET_FLAG(&hdma_spi1_rx, DMA_FLAG_TC1)){}/* USER CODE END DMA1_Channel2_IRQn 0 */HAL_DMA_IRQHandler(&hdma_spi1_rx);/* USER CODE BEGIN DMA1_Channel2_IRQn 1 *//* USER CODE END DMA1_Channel2_IRQn 1 */
}同时,也可以结合SPI中断,完成各种特殊功能开发
void SPI1_IRQHandler(void)
{/* USER CODE BEGIN SPI1_IRQn 0 */if(__HAL_DMA_GET_FLAG(&hspi1, SPI_FLAG_TXE) == SET){发送缓冲区为空时,执行   }if(__HAL_DMA_GET_FLAG(&hspi1, SPI_FLAG_BSY) == RESET){SPI总线不忙时,执行}if(__HAL_DMA_GET_FLAG(&hspi1, SPI_SR_RXNE == SET){接收缓冲区不为空时,执行}/* USER CODE END SPI1_IRQn 0 */HAL_SPI_IRQHandler(&hspi1);/* USER CODE BEGIN SPI1_IRQn 1 *//* USER CODE END SPI1_IRQn 1 */
}


DMA发送模式:
先调用HAL_SPI_Transmit_DMA(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size)发送
等待TXE=1 BSY=0时才能重新调用HAL_SPI_Transmit_DMA

当然,发送时也会产生中断void SPI1_IRQHandler()
发送完后会进入发送完成回调函数void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi)
可以DMA发送再写进这里这样也可以实现循环发送,进入该回调函数就可以认为本次发送已经完成。

/**
  * @brief  Tx Transfer completed callback.
  * @param  hspi pointer to a SPI_HandleTypeDef structure that contains
  *               the configuration information for SPI module.
  * @retval None
  */
__weak void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi)

DMA接收模式:
先调用HAL_SPI_Receive_DMA(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size)
检测到TCIF=1 DMA传输结束标志时,可以读取数据进行处理

接收时会产生中断void SPI1_IRQHandler()
接收完后会进入发送完成回调函数void HAL_SPI_RxCpltCallback(SPI_HandleTypeDef *hspi)
/**
  * @brief  Rx Transfer completed callback.
  * @param  hspi pointer to a SPI_HandleTypeDef structure that contains
  *               the configuration information for SPI module.
  * @retval None
  */
__weak void HAL_SPI_RxCpltCallback(SPI_HandleTypeDef *hspi)

DMA使用注意事项

  1. 1、要确保从机启动完成后,才开启主机DMA发送或查询,否则会出现丢帧
  2. 2、如果手动开关片选,要在DMA发送完成后才切换,否则会出现丢帧
  3. 3、如果用杜邦线连接,注意SPI速率高时,可能会丢包

SPI驱动1.3OLED SH1106

https://www.waveshare.net/wiki/1.3inch_SH1106_OLED

注意:当选通 SPI 串口或者 I2C 接口,建议把 D7~D2 连接到 VDD1 或者 VSS。也允许把 D7~D2 悬空。

3线SPI和4线SPI,这里的4线SPI的意思是,在SPI的CS MOSI MISO CLK的基础上,多了一个D/C,指令/数据切换引脚,由于屏幕只需要接收主机发送的数据,主机不需要读取屏幕的数据,所以4线SPI的引脚为CS MOSI CLK D/C,3线则是CS MOSI CLK,少了一个D/C引脚。

4线SPI的数据时序和对应IO

从图中,我们可以知道IO部分分配如下:
CS:片选信号
SI(D1):MOSI数据引脚
SCL(D0):时钟线
A0:指令/数据切换引脚
为什么没有MISO?
因为屏幕显示的时候,只需要从主机发送数据给屏幕,不需要读取屏幕的内容。

SPI模式部分参数如下:
MSB高位在前,CPOL=1 CPHA=1
时钟极性CPOL=1为时钟空闲时为高电平 CPOL=HIGH
时钟相位CPHA=1:在时钟的第二个跳变沿(上升沿或下降沿)进行数据采样。CPHA=2Edge


每 8 个时钟周期,A0 会被采样一次,移位寄存器的数据字节会写入到显示数据的 RAM(A0=1)或者命令寄存器(A0=0)中。
设置A0(DC)引脚为高时,写入显示数据
设置A0(DC)引脚为低时,写入指令数据(用于设置芯片显示模式、参数等)

查看原理图

可以知道驱动屏幕的主要是4个IO,分别是

OLED_RES-复位信号-低电平复位-对应PB8

OLED_DC-数据/命令发送切换信号-对应PB4

OLED_SCLK-时钟信号-对应PB3

OLED_SDIN-数据信号-对应PB5

打开SPI3,模式为主机模式半双工,因为屏幕驱动只需要发送数据给屏幕显示,不需要读取屏幕信息。

因为SPI3的时钟和数据刚好对应PB3和PB5,这是板卡设计时选择好的。

此处需更新GPOLHightGPHA2Edge
 

修改IO的标签名称为OLED_CLK、OLED_DATA

设置PB8、PB4为输出模式

OLED_RES-复位信号-低电平复位-对应PB8
OLED_DC-数据/命令发送切换信号-对应PB4

修改标签名为OLED_DC、OLED_RES

最终添加USART1作为日志串口,方便调试

初始化屏幕

然后设置对应寄存器,这部分一般参考厂家的示例或手册。

/* USER CODE BEGIN 0 */
static void sh1106_reset()
{//复位屏幕HAL_GPIO_WritePin(GPIOB, OLED_RES_Pin, GPIO_PIN_RESET);  //RES resetHAL_Delay(1000);//拉高复位引脚,进入正常工作模式HAL_GPIO_WritePin(GPIOB, OLED_RES_Pin, GPIO_PIN_SET);  //RES set
}
//发送指令
static void sh1106_write_cmd(uint8_t chData)
{HAL_GPIO_WritePin(GPIOB, OLED_DC_Pin, GPIO_PIN_RESET);//拉低DC,发送指令 HAL_SPI_Transmit(&hspi3, &chData, 1, 0xff);//发送
}      
//发送数据
static void sh1106_write_data(uint8_t chData)
{HAL_GPIO_WritePin(GPIOB, OLED_DC_Pin, GPIO_PIN_SET);  //拉高DC,发送数据 HAL_SPI_Transmit(&hspi3, &chData, 1, 0xff);//发送
}   //初始化
void sh1106_init(void)
{      sh1106_reset();sh1106_write_cmd(0xAE);//--turn off oled panelsh1106_write_cmd(0x00);//---set low column address 00->02sh1106_write_cmd(0x10);//---set high column addresssh1106_write_cmd(0x40);//--set start line address  Set Mapping RAM Display Start Line (0x00~0x3F)sh1106_write_cmd(0x81);//--set contrast control registersh1106_write_cmd(0xCF);// Set SEG Output Current Brightnesssh1106_write_cmd(0xA1);//--Set SEG/Column Mapping    sh1106_write_cmd(0xC0);//Set COM/Row Scan Direction  sh1106_write_cmd(0xA6);//--set normal displaysh1106_write_cmd(0xA8);//--set multiplex ratio(1 to 64)sh1106_write_cmd(0x3f);//--1/64 dutysh1106_write_cmd(0xD3);//-set display offset Shift Mapping RAM Counter (0x00~0x3F)sh1106_write_cmd(0x00);//-not offsetsh1106_write_cmd(0xd5);//--set display clock divide ratio/oscillator frequencysh1106_write_cmd(0x80);//--set divide ratio, Set Clock as 100 Frames/Secsh1106_write_cmd(0xD9);//--set pre-charge periodsh1106_write_cmd(0xF1);//Set Pre-Charge as 15 Clocks & Discharge as 1 Clocksh1106_write_cmd(0xDA);//--set com pins hardware configurationsh1106_write_cmd(0x12);sh1106_write_cmd(0xDB);//--set vcomhsh1106_write_cmd(0x40);//Set VCOM Deselect Levelsh1106_write_cmd(0x20);//-Set Page Addressing Mode (0x00/0x01/0x02)sh1106_write_cmd(0x02);//sh1106_write_cmd(0x8D);//--set Charge Pump enable/disablesh1106_write_cmd(0x14);//--set(0x10) disablesh1106_write_cmd(0xA4);// Disable Entire Display On (0xa4/0xa5)sh1106_write_cmd(0xA6);// Disable Inverse Display On (0xa6/a7)sh1106_write_cmd(0xAF);//--turn on oled panel
}

发送清屏指令

OLED屏幕就是一个个小的有机自发光二极管组成的阵列,作为例子的屏幕的分辨率是128*64,即每行有128个发光二极管,一共有64行,如果我们需要显示一个图案,可以按图案的坐标点亮对应位置的发光二极管即可。

为了让你写代码可以更快找到需要点亮的发光二极管的位置,SH1106芯片提供了页寻址的方式。

注意:SH1106最高支持点亮132*64分辨率的屏幕,而我们的屏幕分辨率是128*64,所以前2列和后2列是不需要用的,写入显示数据时,要注意设置起始列地址。

页是SH1106芯片设计者为了方便将同一列的8个点阵编成一组,用一个8bit数表示,这样132组个8bit被称为1页,这样一共有64/8=8页。

页寻找的方式

Column 0

Column 2

...

Column 130

Column131

Page0

-->

-->

-->

-->

-->

Page1

-->

-->

-->

-->

-->

...

Page6

Page7

选择对应的页:发送指令0xB0-0xB7

我们可以看到D7-D4是固定的,D3-D0由对应页决定,总共有8页,分别是B0-B7。

sh1106_write_cmd(0xB0); //设置页码为0xB0
sh1106_write_cmd(0xB1); //设置页码为0xB1
sh1106_write_cmd(0xB2); //设置页码为0xB2

设置列起始行地址

列地址由两个字节分别管理高低四位。D7 D6 D5 D4是固定的

发送起始列地址:发送指令0x00-0x0F,0x10-0x1F,下方是转换方法:

列地址由两个字节分别管理高低四位。
高四位:0b0001 A7 A6 A5 A4
低四位:0b0000 A3 A2 A1 A0
SH1106最高支持点亮132*64分辨率的屏幕,而我们的屏幕分辨率是128*64,所以前2列和后2列是不需要用的,写入显示数据时,要注意设置起始列地址。
所以起始地址为第二列: 0x02
A7 A6 A5 A4 A3 A2 A1 A0
0  0  0  0  0  0  1  0

高四位:0b00010000 = 0x10
低四位:0b00000010 = 0x02

sh1106_write_cmd(0x02); //列起始地址低四位
sh1106_write_cmd(0x10); //列起始地址高四位

从设定的页和列开始发送数据,列地址自动累加,页地址不会更新,如果超出范围则超出部分无效。


/* USER CODE BEGIN 0 */
//优化前
void sh1106_clear_screen() 
{uint8_t Buffer1[128];sh1106_write_cmd(0xB0); //页码sh1106_write_cmd(0x02); //列起始地址低四位sh1106_write_cmd(0x10); //列起始地址高四位for (j = 0; j < 128; j ++) {Buffer1[j] = 0; //填充0sh1106_write_data(Buffer1[j]); //发送数据,每次发送1byte,8bit}uint8_t Buffer2[128];sh1106_write_cmd(0xB1); //页码sh1106_write_cmd(0x02); //列起始地址低四位sh1106_write_cmd(0x10); //列起始地址高四位for (j = 0; j < 128; j ++) {Buffer2[j] = 0; //填充0sh1106_write_data(Buffer2[j]); //发送数据,每次发送1byte,8bit}xxx
}//优化后uint8_t s_chDispalyBuffer[128][8]; //8*8bit=64void sh1106_clear_screen() 
{uint8_t i, j;for (i = 0; i < 8; i ++) {sh1106_write_cmd(0xB0 + i); //设置页码从0xB0开始到0xB7sh1106_write_cmd(0x02); //列起始地址低四位sh1106_write_cmd(0x10); //列起始地址高四位//8*128个点,全部清零for (j = 0; j < 128; j ++) {s_chDispalyBuffer[j][i] = 0; //填充0sh1106_write_data(s_chDispalyBuffer[j][i]); //发送数据}}
}

画点函数


/* USER CODE BEGIN 0 */
/**把需要点亮的点转换为显示数组s_chDispalyBuffer的一个bit状态chXpos: 绘制点的x坐标 0<= x <=127chYpos: 绘制点的y坐标 0<= 7 <=63chPoint: 0: 熄灭  1: 点亮
**/
void sh1106_draw_point(uint8_t chXpos, uint8_t chYpos, uint8_t chPoint)
{uint8_t chPos, chBx, chTemp = 0;if (chXpos > 127 || chYpos > 63) {return;}//chYpos坐标转换,因为我们用8个字节管理了64个bit,所以需要把y坐标转换到对应的字节bit位置chPos = 7 - chYpos / 8;   //找出那一页chBx = chYpos % 8;        //找出哪一位chTemp = 1 << (7 - chBx); //把对应位置1if (chPoint) {s_chDispalyBuffer[chXpos][chPos] |= chTemp;} else {s_chDispalyBuffer[chXpos][chPos] &= ~chTemp;}sh1106_refresh_gram();
}/**
所有页更新到屏幕显示
把显示数组s_chDispalyBuffer发送到屏幕显示
**/
void sh1106_refresh_gram(void)
{uint8_t i, j;for (i = 0; i < 8; i ++) { sh1106_write_cmd(0xB0 + i); //设置页码从0xB0开始到0xB7  sh1106_write_cmd(0x02); //列起始地址低四位sh1106_write_cmd(0x10); //列起始地址高四位     for (j = 0; j < 128; j ++) {sh1106_write_data(s_chDispalyBuffer[j][i]);}}  
}

显示图像

下方图像数组,我们通过取模软件可以生成

字库取模

中文取模时,需要一个一个字取,顺向取模,根据生成的.c内容记录字体大小,通过画图的方式绘制到屏幕。

图片取模


const uint8_t c_chSingal816[16] = //mobie singal 16*8
{0xFE,0x02,0x92,0x0A,0x54,0x2A,0x38,0xAA,0x12,0xAA,0x12,0xAA,0x12,0xAA,0x12,0xAA
};const uint8_t c_chMsg816[16] =  //message 16*8
{0x1F,0xF8,0x10,0x08,0x18,0x18,0x14,0x28,0x13,0xC8,0x10,0x08,0x10,0x08,0x1F,0xF8
};const uint8_t c_chBat816[16] = //batery 16*8
{0x0F,0xFE,0x30,0x02,0x26,0xDA,0x26,0xDA,0x26,0xDA,0x26,0xDA,0x30,0x02,0x0F,0xFE
};void sh1106_draw_bitmap(uint8_t chXpos, uint8_t chYpos, const uint8_t *pchBmp, uint8_t chWidth, uint8_t chHeight)
{uint16_t i, j, byteWidth = (chWidth + 7) / 8;//遍历图片的宽高,取出每一点,判断为1的位,为需要点亮的点,通过画点函数绘制到屏幕for(j = 0; j < chHeight; j ++){for(i = 0; i < chWidth; i ++ ) {if(*(pchBmp + j * byteWidth + i / 8) & (128 >> (i & 7))) {sh1106_draw_point(chXpos + i, chYpos + j, 1);}}}
}

最终在main函数中使用


/* USER CODE BEGIN Includes */
#include <stdio.h>int main(void)
{HAL_Init();SystemClock_Config();MX_GPIO_Init();MX_SPI3_Init();MX_USART1_UART_Init();/* USER CODE BEGIN 2 */printf("app init \n");sh1106_init();/* USER CODE END 2 */while (1){/* USER CODE BEGIN 3 */sh1106_clear_screen();sh1106_draw_point(10,10,1);HAL_Delay(1000);sh1106_clear_screen();//起始坐标(0,2) 绘制的图标数组c_chSingal816, 图标的宽高(18,8)sh1106_draw_bitmap(0, 2, c_chSingal816, 16, 8);HAL_Delay(1000);}/* USER CODE END 3 */
}/* USER CODE BEGIN 4 */
int fputc(int ch, FILE *f)
{HAL_UART_Transmit(&huart1 , (uint8_t *)&ch, 1, 0xFFFF);return ch;
}
/* USER CODE END 4 */

相关文章:

单片机-STM32部分:14、SPI

飞书文档https://x509p6c8to.feishu.cn/wiki/VYYnwOc9Zi6ibFk36lYcPQdRnlf 什么是SPI SPI 是英语Serial Peripheral interface的缩写&#xff0c;顾名思义就是串行外围设备接口。是Motorola(摩托罗拉)首先在其MC68HCXX系列处理器上定义的。 SPI&#xff0c;是一种高速的&…...

Selenium-Java版(操作元素)

选择和操控元素的基本方法 前言 选择元素 选择元素的方法 根据 id属性选择元素 根据class属性选择元素 根据tag名选择元素 通过WebElement对象选择元素 示例 等待界面元素出现 原因 解决 操控元素 点击元素 输入框 获取元素信息 获取元素文本内容 获取…...

20052012世界银行中国企业调查数据-社科数据

2005&2012世界银行中国企业调查数据-社科数据https://download.csdn.net/download/paofuluolijiang/90623828https://download.csdn.net/download/paofuluolijiang/90623828 世界银行中国企业调查数据&#xff08;World Bank Enterprise Surveys Data&#xff09;是国际金…...

学习黑客NFC技术详解

NFC技术详解&#xff1a;近距离通信的无线桥梁 &#x1f4f1;&#x1f4b3; 学习目标&#xff1a;了解NFC技术的基本原理、应用场景及安全注意事项&#xff0c;掌握这一日益普及的近场通信技术 1. NFC的概念与基础 &#x1f4e1; NFC&#xff08;Near Field Communication&…...

Java问题排查常用命令行工具速查表

Java问题排查常用命令行工具速查表 工具典型用途常用命令示例说明/场景jps列出本机所有Java进程jps -l获取Java进程PID和主类名&#xff0c;配合其它工具使用jcmd动态诊断、堆heap dump、线程dump等jcmd helpjcmd VM.flagsjcmd GC.heap_infojcmd Thread.print功能最全&#xf…...

近期搬了个家,停更了几天,明天继续哈~

近期搬了个家&#xff0c;停更了几天&#xff0c;明天继续哈&#xff5e; 近期搬家比较离谱&#xff0c;第一天下暴雨&#xff0c;冰雹&#xff0c;停电&#xff0c;第二天又停电两小时&#xff0c;截止14号晚上11:30终于完工 了&#xff0c;从西二的20 号楼到西三的19号楼&am…...

C#高级编程:IO和序列化

在 C# 编程中,输入输出(IO)和序列化是两个至关重要的概念,它们为数据的存储、读取以及在不同环境间的传输提供了强大的支持。无论是开发小型应用程序,还是构建复杂的企业级系统,深入理解并熟练运用 IO 和序列化技术都是必不可少的。​ 一、C# 中的 IO 基础​ 1、文件流…...

PyQt5完整指南:从入门到实践

引言 PyQt5是Python编程语言的一个GUI&#xff08;图形用户界面&#xff09;工具包&#xff0c;它是Qt5应用程序框架的Python绑定。Qt是一个跨平台的C应用程序开发框架&#xff0c;被广泛用于开发GUI程序和非GUI程序。PyQt5让Python开发者能够使用Python语言享受到Qt框架的强大…...

C#高级编程:加密解密

在数字化时代,数据安全是每个应用程序都必须重视的环节。无论是用户的个人信息、敏感的商业数据,还是重要的系统配置,都需要得到妥善的保护。C# 作为一种广泛应用的编程语言,提供了丰富且强大的加密解密功能,帮助开发者构建安全可靠的应用。本文将深入探讨 C# 高级编程中的…...

银行卡真伪验证助力金融合规-银行卡实名认证接口

在数字化时代&#xff0c;金融交易日益频繁&#xff0c;用户身份与银行卡信息的真实性核验成为保障资金安全、防止欺诈行为的关键环节。无论是在线支付、网络借贷、电商平台&#xff0c;还是社交软件、金融服务APP&#xff0c;均需对用户的银行卡进行严格的实名认证。为满足企业…...

html5+css3实现傅里叶变换的动态展示效果(仅供参考)

<!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>傅里叶变换的动态展示效果</title><sty…...

SysConfig修改后`ti_msp_dl_config`文件未更新问题的解决方法(已解决)

SysConfig修改后ti_msp_dl_config文件未更新问题的解决方法 在使用SysConfig工具配置TI MSPM0系列MCU时&#xff0c;有时会遇到一个令人困惑的问题&#xff1a;在SysConfig中修改配置后&#xff0c;生成的ti_msp_dl_config.c文件内容却没有更新。这可能会导致工程无法正确编译…...

深入浅出 IPFS 在 DApps 和 NFT 中的应用:以 Pinata 实战为例

目录 IPFS背景什么是 IPFS?IPFS 在 DApps 与 NFT 中的作用什么是 Pinata?为什么使用它?使用原生IPFS上传下载文件(HTML + JavaScript 示例)使用Pinata上传下载文件(HTML + JavaScript 示例)注册并创建APIKey使用 Pinata 上传文件和JSON(HTML + JavaScript 示例)总结IP…...

深度剖析LLM的“大脑”:单层Transformer的思考模式探索

简单说一下哈 —— 咱们打算训练一个单层 Transformer 加上稀疏自编码器的小型百万参数大型语言模型&#xff08;LLM&#xff09;&#xff0c;然后去调试它的思考过程&#xff0c;看看这个 LLM 的思考和人类思考到底有多像。 LLMs 是怎么思考的呢&#xff1f; 开源 LLM 出现之后…...

(4)python开发经验

文章目录 1 使用ctypes库调用2 使用pybind11 更多精彩内容&#x1f449;内容导航 &#x1f448;&#x1f449;Qt开发 &#x1f448;&#x1f449;python开发 &#x1f448; 1 使用ctypes库调用 说明&#xff1a;ctypes是一个Python内置的库&#xff0c;可以提供C兼容的数据类型…...

卷积神经网络全连接层详解:特征汇总、FCN替代与性能影响分析

【内容摘要】 本文聚焦卷积神经网络&#xff08;CNN&#xff09;的全连接层&#xff0c;详细介绍其将二维特征图转化为一维向量的过程&#xff0c;阐述全卷积网络&#xff08;FCN&#xff09;如何通过转置卷积替代全连接层以实现像素级分类&#xff0c;并分析全连接层对图像分类…...

通义千问-langchain使用构建(一)

目录 序言通义千问1获取通义千问api_key2Conda构建下本地环境3 构建一下多轮对话 LangChain1使用Langchain调用通义千问接口实现翻译 结论 序言 25年5月&#xff0c;现在基本每个大厂都有涉及大模型(Large Language Model)&#xff0c;然后在大模型基础上构建应用框架。 参考…...

六西格玛觉醒:一场数据思维的启蒙运动​

​ 当生产线上的不良品率曲线第一次在我眼前具象化为统计波动图时&#xff0c;我意识到自己正站在新旧认知的断层带上。从对着MINITAB界面手足无措的菜鸟&#xff0c;到能独立完成过程能力分析的绿带学员&#xff0c;这段学习旅程不仅重塑了我的问题解决逻辑&#xff0c;更让我…...

BitMart合约交易体验 BitMart滑点全赔的底层逻辑

美国新泽西州泽西市&#xff0c;2025年5月13日 – BitMart&#xff0c;全球领先的数字资产交易平台&#xff0c;推出了其开创性的滑点保护计划&#xff0c;旨在解决加密市场中最具挑战性且常常被忽视的风险之一&#xff1a;滑点。该计划为交易者提供了在 USDT 保证金永续合约交…...

HCIP(BFD)

一、前言 随着网络应用的广泛部署,网络发生故障极大可能导致业务异常。为了减小链路、设备故障对业 务的影响,提高网络的可靠性,网络设备需要尽快检测到与相邻设备间的通信故障,以便及时采取措施,保证业务正常进行。BFD(Bidirectional Forwarding Detection,双向转发检测)提供…...

json-server的用法-基于 RESTful API 的本地 mock 服务

json-server 是一个非常方便的工具&#xff0c;用于快速搭建基于 RESTful API 的本地 mock 服务&#xff0c;特别适合前端开发阶段模拟后端数据接口。 &#x1f9e9; 一、安装 npm install -g json-server&#x1f680; 二、快速启动 创建一个 db.json 文件&#xff08;模拟数…...

化工单元操作试验装置系列产品JG-SX211计算机过程控制板框过滤操作实训装置

化工单元操作试验装置系列产品JG-SX211计算机过程控制板框过滤操作实训装置 一、装置功能 板框过滤岗位技能&#xff1a;板框过滤机的构造和操作方法&#xff1b;板框压滤机的操作&#xff08;装合、过滤、洗涤、卸渣、整理&#xff09;&#xff1b;洗涤速率与最终过滤速率的关…...

Linux 内核 IPv4 协议栈中的协议注册机制解析

1. 引言 在 Linux 内核的 IPv4 协议栈中,inetsw 是一个核心数据结构,负责管理不同套接字类型(如 SOCK_STREAM、SOCK_DGRAM)的协议实现。本文结合代码分析,深入探讨其设计原理、动态协议注册机制及并发安全实现。 2. inetsw 的结构与作用 2.1 定义与初始化 static struc…...

【PostgreSQL数据分析实战:从数据清洗到可视化全流程】附录-C. 常用SQL脚本模板

&#x1f449; 点击关注不迷路 &#x1f449; 点击关注不迷路 &#x1f449; 点击关注不迷路 文章大纲 附录C. 常用SQL脚本模板速查表一、数据清洗与预处理模板二、数据聚合与分析模板三、窗口函数应用模板四、性能优化与监控模板五、数据备份与恢复模板六、权限管理与安全模板…...

Linux操作系统实战:中断源码的性能分析(转)

Linux中断是指在Linux操作系统中&#xff0c;当硬件设备或软件触发某个事件时&#xff0c;CPU会中断正在执行的任务&#xff0c;并立即处理这个事件。它是实现实时响应和处理外部事件的重要机制&#xff0c;Linux中断可以分为两种类型&#xff1a;硬件中断和软件中断&#xff0…...

Windows系统Anaconda/Miniconda的安装、配置、基础使用、清理缓存空间和Pycharm/VSCode配置指南

本文同步发布在个人博客&#xff1a; Windows系统Anaconda/Miniconda的安装、配置、基础使用、清理缓存空间和Pycharm/VSCode配置指南 - 萑澈的寒舍Conda 是一个开源的跨平台包管理与环境管理工具&#xff0c;广泛应用于数据科学、机器学习及 Python 开发领域。它不仅能帮助用…...

用HBuilder运行小程序到微信开发者工具

首先在HBuilder里配置微信开发者工具安装路径 “运行”--“运行到小程序模拟器”--“运行设置”--“微信开发者工具路径”...

基于网关实现不同网段S7-1200 CPU的通信方法

在工业自动化场景中&#xff0c;不同网段的S7-1200 PLC之间需要进行数据交换时&#xff0c;通常需要借助网关或路由设备实现跨网段通信。以下是几种常见的实现方法及详细配置步骤。 一、通信需求分析 当两个或多个S7-1200 PLC位于不同子网&#xff08;如192.168.1.0/24和192.1…...

微信小程序学习之轮播图swiper

轮播图是小程序的重要组件&#xff0c;我们还是好好学滴。 1、上代码&#xff0c;直接布局一个轮播图组件(index.wxml)&#xff1a; <swiper class"swiper" indicator-active-color"#fa2c19" indicator-color"#fff" duration"{{durati…...

零基础用 Hexo + Matery 搭建博客|Github Pages 免费部署教程

文章目录 一、Hexo1.1 依赖1.2 快速使用1.3 目录说明1.4 命令说明1.4.1 常规命令1.4.2 全局选项 二、主题安装2.1 安装 Matery 主题2.1.1 下载2.1.2 配置2.1.2.1 基础配置2.1.2.2 新建页面类型2.1.2.3 其他配置 2.2 其他主题推荐 三、部署3.1 部署到 Github Pages 四、总结 一、…...

Large-Scale Language Models: In-Depth Principles and Pioneering Innovations

大规模语言模型(Large-Scale Language Models, LLMs)是人工智能领域的璀璨明珠,深刻重塑了自然语言处理(NLP)并推动多模态应用的蓬勃发展。从BERT的语义洞察到GPT系列的生成奇迹,再到Grok、LLaMA等模型的跨界创新,LLMs在智能对话、代码生成、科学探索等领域展现出近乎人…...

微信小程序智能商城系统(uniapp+Springboot后端+vue管理端)

一、系统介绍 本智能商城系统是基于当今主流技术栈开发的一款多端商城解决方案&#xff0c;主要包括微信小程序前端、SpringBoot 后端服务以及 Vue 管理后台三大部分。系统融合了线上商城的核心功能&#xff0c;支持商品浏览、下单、支付、订单管理等操作&#xff0c;适用于中小…...

命令行快速上传文件到SFTP服务器(附参考示例)

一、SFTP基础命令格式 更新参数后的标准命令格式为&#xff1a; sftp -P [端口号] [用户名][服务器IP]:[远程路径] <<< $put [本地文件路径]二、新参数实例解析 使用新连接参数的完整命令示例&#xff1a; sftp -P 30033 test_jigou_sftp121.199.64.216:/download…...

【Linux】第十六章 分析和存储日志

1. RHEL 日志文件保存在哪个目录中&#xff1f; 一般存储在 /var/log 目录中。 2. 什么是syslog消息和非syslog消息&#xff1f; syslog消息是一种标准的日志记录协议和格式&#xff0c;用于系统和应用程序记录日志信息。它规定了日志消息的结构和内容&#xff0c;包括消息的…...

vue2+ThinkPHP5实现简单大文件切片上传

使用 Vue 2 和 ThinkPHP 5 实现大文件切片上传功能 文章目录 一、前端(Vue 2)安装依赖文件上传并切片全部代码二、后端(ThinkPHP 5)完整代码一、前端(Vue 2) 安装依赖 安装spark-md5依赖 用于生成文件哈希,以便验证文件的完整性。 npm install spark-md5文件上传并切…...

phpstudy的Apache添加AddType application/x-httpd-php .php .php5配置无效的处理方式

前言 最近在学习安全竞赛ctf相关的内容&#xff0c;使用phpstudy作为服务端&#xff0c;研究图片上传相关漏洞的靶场upload-labs。其中遇到后缀名过滤&#xff0c;会过滤后缀名php。按照网上的处理方式&#xff0c;只需要在Apache服务器的配置文件中增加“AddType application…...

2025年Flutter项目管理技能要求

在2025年&#xff0c;随着Flutter技术的广泛应用和项目复杂度的提升&#xff0c;项目管理的重要性愈发凸显。Flutter项目管理不仅需要技术能力&#xff0c;还需要良好的沟通、协调、规划和执行能力。本文将详细探讨2025年Flutter项目管理应具备的技能要求&#xff0c;帮助项目管…...

Step1

项目 SchedulerSim 已搭建完成 ✅ ⸻ ✅ 你现在拥有的&#xff1a; • &#x1f527; 两种调度器&#xff08;Round Robin SJF&#xff09; • &#x1f4e6; 模拟进程类 Process • &#x1f9f1; 清晰结构&#xff1a;OOP 风格 便于扩展 • ✍️ 主函数已演示调度器运行效…...

MCP(一)——QuickStart

目录 1. MCP简介2. MCP的优势3. MCP核心4. QuickStart For Server Developers(仅具参考)4.1 MCP核心概念4.2 构建MCP服务器的代码4.2.1 设置MCP服务器实例4.2.2 辅助函数4.2.3 实现工具执行4.2.4 在Cherry-Studio中添加MCP服务器4.2.5 演示4.2.5.1 测试工具get_alerts4.2.5.2 测…...

NLP的基本流程概述

自然语言处理&#xff08;Natural Language Processing, NLP&#xff09;是计算机科学与人工智能领域中的一个重要分支&#xff0c;旨在使计算机能够理解、分析、生成和处理人类语言。NLP的基本流程通常包括以下几个关键步骤&#xff1a; 1. 文本预处理 (Text Preprocessing) …...

【Java学习笔记】==运算符

运算符 是一个比较运算符 既可以判断基本类型&#xff0c;又可以判断引用类型 如果判断基本类型&#xff0c;判断的是值是否相等&#xff0c;示例: int i 10; double d 10.0&#xff08;底层会发生自动类型转换&#xff09; 如果判断引用类型&#xff0c;判断的是地址是否相…...

移动网页调试工具实战:从 Chrome 到 WebDebugX 的效率演进

前端开发的日常&#xff0c;说白了就是构建、预览、调试的不断循环。如果是桌面浏览器&#xff0c;调试体验已经极致成熟&#xff1b;但一旦牵涉到移动端&#xff0c;尤其是 WebView 环境&#xff0c;一切都变得复杂。 过去几年里&#xff0c;我陆续试用了多个调试工具&#x…...

Vue 图片预览功能(含缩略图)

众所周知&#xff0c;常见的组件库如Element、Ant Design&#xff0c;自带的图片预览功能都没有缩略图&#xff0c;所以 需要单独封装一个图片预览的服务。 第三方库&#xff1a;v-viewer 安装&#xff1a; npm install v-viewer viewerjs 若使用报错&#xff0c;可安装指定…...

RK3588 串行解串板,支持8路GMSL相机

RK3588 支持的 GMSL 相机接入数量取决于所使用的解串板型号及配置方案&#xff1a; ‌xcDeserializer3.0 解串板‌ 可接入最多 ‌8 路 2M GMSL2 相机‌1。 ‌xcDeserializer4.0 解串板‌ 支持 ‌4 路 2M GMSL2 相机‌1。 ‌边缘计算盒解决方案‌ 部分商用方案可实现 ‌4 或 8…...

数据库字段唯一性修复指南:从设计缺陷到规范实现

数据库字段唯一性修复指南&#xff1a;从设计缺陷到规范实现 一、问题背景 表结构设计缺陷&#xff1a; sys_user 表未对 dingtalk_user_id&#xff08;钉钉用户ID&#xff09;字段设置唯一性约束&#xff0c;导致数据重复&#xff0c;引发以下问题&#xff1a; 系统稳定性风…...

深度学习Dropout实现

深度学习中的 Dropout 技术在代码层面上的实现通常非常直接。其核心思想是在训练过程中&#xff0c;对于网络中的每个神经元&#xff08;或者更精确地说&#xff0c;是每个神经元的输出&#xff09;&#xff0c;以一定的概率 p 随机将其输出置为 0。在反向传播时&#xff0c;这…...

IIS服务器URL重写配置完整教程

1.下载URL Rewrite Module 2.1 https://www.iis.net/downloads/microsoft/url-rewrite https://download.microsoft.com/download/1/2/8/128E2E22-C1B9-44A4-BE2A-5859ED1D4592/rewrite_amd64_zh-CN.msi 2.安装...

前后端分离博客 Weblog 项目实战

前后端分离博客 Weblog 项目实战&#xff1a;专栏介绍 - 犬小哈专栏 原文作者 http://116.62.199.48/ 功能模块 技术栈 必备环境 后端环境&#xff1a; JDK 1.8 版本&#xff08;此版本是目前企业中使用最广泛的&#xff09;;MySQL 5.7 版本 (或者 8.x 版本都可以&#…...

stm32 ADC单通道转换

stm32c8t6仅有12位分辨率 1、单次转换 非扫描 1、初始化 void Ad_Init() {RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);//配置ADCCLK时钟分频,ADC的输入时钟不得超过14MHzRCC_ADCCLKConfig(RCC_PCLK2_Div6);G…...

万文c++继承

1、继承的概念与定义 1.1继承的概念 继承&#xff1a;是c代码复用的手段&#xff0c;允许在原有的基础上扩展&#xff0c;在此之前都是函数层次的复用&#xff0c;继承是类设计层次的复用。 下面有两个类Student和Teacher都有姓名/地址/电话/年龄等成员变量。都有identity身…...