單片機-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通信,以及計算機與嵌入式設備間的通信。

I2C 因其簡潔的連線和地址機制,適用于板級設備間的通信,如傳感器、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引腳。

4SPI的數據時序和對應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 */

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/81404.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/81404.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/81404.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Vue 3 動態 ref 的使用方式(表格)

一、問題描述 先給大家簡單介紹一下問題背景。我正在開發的項目中&#xff0c;有一個表格組件&#xff0c;其中一列是分鏡描述&#xff0c;需要支持視頻上傳功能。用戶可以為每一行的分鏡描述上傳對應的視頻示例。然而&#xff0c;在實現過程中&#xff0c;出現了一個嚴重的問…

構建 TypoView:一個富文本樣式預覽工具的全流程記錄

我正在參加CodeBuddy「首席試玩官」內容創作大賽&#xff0c;本文所使用的 CodeBuddy 免費下載鏈接&#xff1a;騰訊云代碼助手 CodeBuddy - AI 時代的智能編程伙伴 在一次和 CodeBuddy 的日常交流中&#xff0c;我提出了一個構想&#xff1a;能不能幫我從零構建一個富文本樣式…

AI:OpenAI論壇分享—《AI重塑未來:技術、經濟與戰略》

AI&#xff1a;OpenAI論壇分享—《AI重塑未來&#xff1a;技術、經濟與戰略》 導讀&#xff1a;2025年4月24日&#xff0c;OpenAI論壇全面探討了 AI 的發展趨勢、技術范式、地緣政治影響以及對經濟和社會的廣泛影響。強調了 AI 的通用性、可擴展性和高級推理能力&#xff0c;以…

Bash fork 炸彈 —— :(){ :|: };:

&#x1f9e0; 什么是 Fork 炸彈&#xff1f; Fork 炸彈是一種拒絕服務&#xff08;DoS&#xff09;攻擊技術&#xff0c;利用操作系統的 fork() 系統調用不斷創建新進程&#xff0c;直到系統資源&#xff08;如進程表、CPU、內存&#xff09;被耗盡&#xff0c;從而使系統無法…

<前端小白> 前端網頁知識點總結

HTML 標簽 1. 標題標簽 h1到h6 2. 段落標簽 p 3. 換行 br 水平線 hr 4. 加粗 strong 傾斜 em 下劃線 ins 刪除 del 5. 圖像標簽 img src-圖像的位置 alt- 圖片加載失敗顯示的文字 替換文本 title--- 鼠標放到圖片上顯示的文字 提示…

tomcat查看狀態頁及調優信息

準備工作 先準備一臺已經安裝好tomcat的虛擬機&#xff0c;tomcat默認是狀態頁是默認被禁用的 1.添加授權用戶 vim /usr/local/tomcat/conf/tomcat-users.xml22 <role rolename"manager-gui"/>23 <user username"admin" password"tomcat&q…

.NET NativeAOT 指南

目錄 1. 引言 2. 什么是 .NET NativeAOT&#xff1f; 2.1 NativeAOT 的定義 2.2 NativeAOT 與傳統 JIT 的對比 2.3 NativeAOT 的適用場景 3. NativeAOT 的核心優勢 3.1 性能提升 3.2 簡化部署 3.3 更小的應用體積 3.4 知識產權保護 4. NativeAOT 的基本用法 4.1 環境…

產品周圍的幾面墻

不能把排序&#xff0c;當單選題做。 2025年的杭州咖啡館&#xff0c;味道最濃的不是咖啡&#xff0c;是聊各種項目和創業的卷味。 在過去幾年&#xff0c;聊項目的也不少&#xff0c;那時候帶著更加濃烈的自信和松弛感&#xff0c;不過今年略帶幾分忐忑和試探的口吻。 看到網…

例舉3種強制類型轉換和2種隱式

1. 強制類型轉換 強制類型轉換是指程序員顯式地將一個數據類型的值轉換為另一種數據類型。這種轉換通常是通過使用特定的函數或運算符來完成的。 常用的強制類型轉換方法&#xff1a; 使用Number()函數 let value "123"; let num Number(value); // 強制轉換為數字…

UI-TARS本地部署

UI-TARS本地部署 UI-TARS本地部署 UI-TARS 論文&#xff08;arXiv&#xff09; UI-TARS 官方倉庫&#xff1a;包含部署指南、模型下載鏈接及示例代碼。 UI-TARS-Desktop 客戶端&#xff1a;支持本地桌面應用的交互控制。 模型部署框架&#xff1a;vLLM本地部署 1.下載項目…

新電腦軟件配置三 pycharm

快捷鍵放大和縮小字體 按住ctrl鼠標滾輪向上 縮小同理

華為OD機試真題——考勤信息(2025A卷:100分)Java/python/JavaScript/C/C++/GO最佳實現

2025 A卷 100分 題型 本專欄內全部題目均提供Java、python、JavaScript、C、C++、GO六種語言的最佳實現方式; 并且每種語言均涵蓋詳細的問題分析、解題思路、代碼實現、代碼詳解、3個測試用例以及綜合分析; 本文收錄于專欄:《2025華為OD真題目錄+全流程解析+備考攻略+經驗分…

Python語法規則:縮進、代碼塊與空格規范

在眾多編程語言中&#xff0c;Python 以其“簡潔而優雅”的語法風格獨樹一幟。然而&#xff0c;這種“簡潔”并非輕率隨意&#xff0c;而是建立在一套嚴謹的語法哲學之上。縮進、代碼塊與空格規范&#xff0c;不僅是 Python 的語法基礎&#xff0c;更是它傳達代碼意圖、塑造開發…

Baklib智能知識管理增效方案

Baklib智能知識管理核心優勢 基于Baklib構建的知識中臺&#xff0c;通過多維度結構化處理與智能語義引擎&#xff0c;重構了企業知識管理范式。該系統支持文檔、表格、音視頻等多格式內容聚合&#xff0c;利用自然語言處理技術實現知識資產的自動化分類與標簽匹配&#xff0c;…

【導航信號模擬器】【MATLAB APP】MATLAB AppDesigner基本使用教程

MATLAB AppDesigner基本使用教程 作者&#xff1a;齊花Guyc(CAUC) 文章目錄 MATLAB AppDesigner基本使用教程一、創建項目二、編寫回調函數1. 按鈕——獲取選擇文件路徑2. 按鈕——保存文件路徑3. 單選按鈕組4. 復選框5. 文本框顯示 三、打包APP 一、創建項目 建立空文件夾—…

ImgShrink:攝影暗房里的在線圖片壓縮工具開發記

我正在參加CodeBuddy「首席試玩官」內容創作大賽&#xff0c;本文所使用的 CodeBuddy 免費下載鏈接&#xff1a;騰訊云代碼助手 CodeBuddy - AI 時代的智能編程伙伴 在一次 CodeBuddy 的項目試玩官活動中&#xff0c;我決定構建一個實用又不失視覺特色的小工具——ImgShrink。它…

利用systemd啟動部署在服務器上的web應用

0.背景 系統環境&#xff1a; Ubuntu 22.04 web應用情況&#xff1a; 前后端分類&#xff0c;前端采用react&#xff0c;后端采用fastapi 1.具體配置 1.1 前端配置 開發態運行&#xff08;啟動命令是npm run dev&#xff09;,創建systemd服務文件 sudo nano /etc/systemd/…

在vue3中使用Cesium的保姆教程

1. 軟件下載與安裝 1. node安裝 Vue.js 的開發依賴于 Node.js 環境&#xff0c;因此我們首先需要安裝 Node.js。Node.js 是一個基于 Chrome V8 引擎的 JavaScript 運行環境&#xff0c;它允許你在服務器端運行 JavaScript 代碼&#xff0c;同時也為前端開發提供了強大的工具支…

基于LabVIEW的雙音多頻系統設計

目錄 1 系統設計概述 雙音多頻(Dual-Tone Multi-Frequency, DTMF)信號是一種廣泛應用于電話系統中的音頻信號,通過不同的頻率組合表示不同的按鍵。每個按鍵對應兩個頻率,一個低頻和一個高頻,共同組成獨特的信號。在虛擬儀器技術快速發展的背景下,利用LabVIEW等圖形化編程…

【筆記】端口轉發

echo off :loop ssh -N -L 13306:192.168.0.3:23306 -o ServerAliveInterval60 admin192.168.0.2 timeout /t 5 goto loop 代碼功能剖析 1、基礎設置&#xff1a; echo off 此命令的作用是讓批處理腳本在執行過程中不顯示命令行&#xff0c;從而使輸出更為簡潔。 2、定義循環…