17-CubeMx+Keil+Proteus仿真STM32 - SPI
本文例子参考《STM32单片机开发实例——基于Proteus虚拟仿真与HAL/LL库》
源代码:https://github.com/LanLinnet/STM32F103R6
项目要求
掌握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 CPHA=0)
- 模式1(CPOL=0 CPHA=1)
- 模式2(CPOL=1 CPHA=0)
- 模式3(CPOL=1 CPHA=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的温度值,虚拟终端的显示也会跟着改变。