本文例子參考《STM32單片機開發實例——基於Proteus虛擬模擬與HAL/LL庫》 源代碼:https://github.com/LanLinnet/STM33F103R6 項目要求 掌握SPI匯流排通信規則,使用單片機每隔1s讀取一次溫度感測器TC72的溫度值,並通過串口將讀取的溫度值發送出去。 ...
本文例子參考《STM32單片機開發實例——基於Proteus虛擬模擬與HAL/LL庫》
源代碼:https://github.com/LanLinnet/STM33F103R6
項目要求
掌握SPI匯流排通信規則,使用單片機每隔1s讀取一次溫度感測器TC72的溫度值,並通過串口將讀取的溫度值發送出去。串口通信參數:波特率為19200bits/s,無校驗。
硬體設計
-
在第一節的基礎上,在Proteus中添加電路如下圖所示。其中我們添加了一個串列溫度感測器
TC72
。
此外,我們還添加了兩個虛擬儀錶:一個虛擬終端VIRTUAL TERMINAL
和一個SPI匯流排調試工具SPI DEBUGGER
。
虛擬終端VIRTUAL TERMINAL
的設置如下。
-
SPI:
1)簡介:SPI(Serial Peripheral Interface, 串列外設介面)是由美國Motorola公司推出的一種同步串列通信介面,用於串列連接微處理器與外圍晶元。SPI採用主從通信模式,通常為一主多從結構,通信時鐘由主機控制,在時鐘信號的作用下,數據先傳送高位,再傳送低位。
2)介面:SPI通信至少需要以下4根線。- SCLK:時鐘線,用於提供通信所需的時鐘基準信號。
- MOSI:主出從入數據線,對於主機而言是數據輸出匯流排,對從機是數據輸入匯流排。
- MISO:主入從出數據線,對於主機而言是數據輸入匯流排,對從機是數據輸出匯流排。
- \(\overline{CS}\):片選信號,低電平有效。但是對於本次項目所用的TC72,有效電平為高電平。
3)通信時序:SPI通信的工作時序有4種,具體由CPHA(Clock Phase,時鐘相位)和CPOL(Clock Polarity,時鐘極性)決定。SPI的4種通信模式如下表,時序圖如下分別列出。
- 模式0(CPOL=0 CPOL=0)
- 模式1(CPOL=0 CPOL=1)
- 模式2(CPOL=1 CPOL=0)
- 模式3(CPOL=1 CPOL=1)
-
TC72:
1)簡介:TC72是由美國Microchip公司出品的串列溫度感測器,相容SPI通信協議,溫度測量範圍為-55℃-+125℃,解析度為10位(0.25℃/bit)。
2)引腳:TC72的引腳功能如下表所示。
3)工作模式:TC72的工作模式有以下兩種:- 連續轉換模式(Continuous Conversion Mode):每隔150ms進行1次溫度轉換。
- 單次轉換模式(One-Shot Mode):轉換1次後就進入省電模式。
TC72的溫度轉換結果採用左對齊數據存儲格式:高位元組存放溫度值轉換結果的整數部分,最高位T9為符號位;低位元組高2位存放溫度值轉換結果的小數部分,數據以補碼形式存放。其寄存器地址如下表所示。
-
打開CubeMX,建立工程。STM32F103R6單片機自帶一個SPI模塊,但是為了便於移植,本項目中採用GPIO引腳模擬SPI時序。設置PA4、PA5、PA7均為
GPIO_Output
,PA6均為GPIO_Input
。點擊“Categories”中的“GPIO”,修改GPIO各參數如下圖所示。
隨後進行串口設置,如下圖所示,這裡就不贅述了,具體可以參考第13節。
-
點擊“Generator Code”生成Keil工程。
軟體編寫
-
考慮到代碼的可移植性,這裡將SPI和TC72的驅動代碼全部封裝成函數並分別歸入頭文件“vSPI.h”和“TC72.h”中。我們可以先在
...\Core\Src
文件夾中建立這兩個頭文件,此時Keil可能找不到對應文件,可以直接將文件拽入Keil中進行編輯,然後再在“main.c”文件中進行include。 -
點擊“Open Project”在Keil中打開工程,打開“vSPI.h”,添加代碼如下。
#ifndef INC_VSPI_H_ #define INC_VSPI_H_ #include "main.h" //軟體延時函數,單位為微秒 void delay_us(uint16_t n) { uint16_t i = n * 8; while(i--); } //SPI匯流排使能 void vSPI_En() { HAL_GPIO_WritePin(GPIOA, vCE_Pin, GPIO_PIN_SET); HAL_GPIO_WritePin(GPIOA, vSCK_Pin, GPIO_PIN_RESET); delay_us(4); } //SPI匯流排禁止 void vSPI_Dis() { HAL_GPIO_WritePin(GPIOA, vSCK_Pin, GPIO_PIN_SET); HAL_GPIO_WritePin(GPIOA, vCE_Pin, GPIO_PIN_RESET); } //SPI主站發送1位元組 void vSPI_SndByte(uint8_t dat) //dat表示發送的位元組 { uint8_t i; for(i=0; i<8; i++) { HAL_GPIO_WritePin(GPIOA, vSCK_Pin, GPIO_PIN_RESET); delay_us(4); if(dat & 0x80) { HAL_GPIO_WritePin(GPIOA, vMOSI_Pin, GPIO_PIN_SET); } else HAL_GPIO_WritePin(GPIOA, vMOSI_Pin, GPIO_PIN_RESET); dat<<=1; //上升沿 HAL_GPIO_WritePin(GPIOA, vSCK_Pin, GPIO_PIN_SET); delay_us(4); } } //SPI主站接收1位元組數據 uint8_t vSPI_RcvByte() { uint8_t i, dat=0; for(i=0;i<8;i++) { delay_us(4); dat<<=1; HAL_GPIO_WritePin(GPIOA, vSCK_Pin, GPIO_PIN_RESET); if(HAL_GPIO_ReadPin(GPIOA, vMISO_Pin) == GPIO_PIN_SET) { dat |= 0x01; } else dat &= 0xfe; HAL_GPIO_WritePin(GPIOA, vSCK_Pin, GPIO_PIN_SET); } return dat; //返回1位元組數據 } #endif /* INC_VSPI_H_ */
打開“TC72.h”,添加代碼如下。
#ifndef INC_TC72_H_ #define INC_TC72_H_ #include "main.h" #include "vSPI.h" //巨集定義 #define _TC72_CTRL_R 0x00 //控制寄存器地址(讀) #define _TC72_CTRL_W 0x80 //控制寄存器地址(寫) #define _TC72_Dat_LSB 0x01 //溫度低位元組地址(讀) #define _TC72_Dat_MSB 0x02 //溫度高位元組地址(讀) #define _TC72_ID 0x03 //製造商ID(讀) #define _TC72_OnceCnv 0x15 //單次轉化指令 #define _TC72_ContinueCnv 0x05 //連續轉化指令 //發送轉化指令 void TC72_Convert(uint8_t Instr) //Instr為指令 { vSPI_En(); //SPI匯流排使能 vSPI_SndByte(_TC72_CTRL_W); //發送控制寄存器地址(寫) vSPI_SndByte(Instr); //發送轉化指令 vSPI_Dis(); //SPI匯流排禁止 } //讀溫度 float TC72_TemperatureRd() { uint8_t DatL, DatM; //高低位元組 int16_t Dat; //最終接收數據 float t; //轉化溫度 vSPI_En(); //SPI匯流排使能 vSPI_SndByte(_TC72_Dat_MSB); //發送溫度高位元組地址(讀) DatM = vSPI_RcvByte(); //SPI主站接收1位元組(高) DatL = vSPI_RcvByte(); //SPI主站接收1位元組(低) vSPI_Dis(); //SPI匯流排禁止 Dat = DatM; Dat <<= 8; Dat += DatL; //組合高低位元組 t = ((float)(Dat))/256; //轉化溫度 return t; //返回溫度值 } #endif /* INC_TC72_H_ */
-
隨後我們需要在main.c文件中的最前面引入我們自定義的頭文件
/* Private includes ----------------------------------------------------------*/ /* USER CODE BEGIN Includes */ #include "stdio.h" //引入輸入輸出標準庫 #include "vSPI.h" //引入自定義頭文件 #include "TC72.h" /* USER CODE END Includes */
在main函數中定義需要通過串口發送的字元串
/* USER CODE BEGIN 1 */ float t; char str1[] = "Temperature:"; char str2[10]; //存放溫度字元串 /* USER CODE END 1 */
最後,在while(1)中調用我們自定義的函數對TC72和串口進行操作
/* USER CODE BEGIN WHILE */ while (1) { HAL_UART_Transmit(&huart1, (uint8_t *)str1, 12, 12); //串口發送str1 TC72_Convert(_TC72_OnceCnv); //單次轉化指令 HAL_Delay(100); t = TC72_TemperatureRd(); //讀感測器溫度 sprintf(str2, "%f", t); //將溫度t由浮點型轉化為字元串並存入數組str2 HAL_UART_Transmit(&huart1, (uint8_t *)str2, 7, 7); //串口發送str2 HAL_UART_Transmit(&huart1, (uint8_t *)&"\n\r", 2, 2); HAL_Delay(900); /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ } /* USER CODE END 3 */
聯合調試
- 點擊運行,生成HEX文件。
- 在Proteus中載入相應HEX文件,點擊運行。可以看到虛擬終端“VIRTUAL TERMINAL”每隔1秒都會顯示一次TC72的溫度,調節TC72的溫度值,虛擬終端的顯示也會跟著改變。