1.SPI总线是Motorola首先提出的全双工三线/四线同步串行总线
2.采用主从模式(Master Slave)架构;支持多slave模式应用
3.一般仅支持单Master,多从机
4.时钟由Master控制,在时钟移位脉 冲下,数据按位传输,高位在前,低位在后(MSB first) 5.SPI接口有2根单向数据线,为全双工 通信,目前应用中的数据速率可达几Mbps的水平
6.SPI总线被广泛地使用在FLASH、ADC、LCD等设备与MCU间,要求通讯速率较高的场合
7.本次实验:SOC<----SPI---->数码管
SPI硬件的连接有如下
1.SPI接口共有4根信号线,
????????分别是:设备选择线(片选线):
????????????????????????NSS nsc 时钟线:SCK CLK 串行输出数据线:
????????????????????????MOSI 串行输入数据线:
????????????????????????MISO
????????????????????????M:master主机? O:output输出? S:slave从机? I:input输入
.作用:
? (1)MOSI:主器件数据输出,从器件数据输入
? (2)MISO:主器件数据输入,从器件数据输出
? (3)SCLK :时钟信号,由主器件产生
? (4)/SS:从器件使能信号,由主器件控制(片选)
根据以上的分析可以得知SPI的框图r如下:
?头文件部分的内容如下
#ifndef __SPI_H__
#define __SPI_H__
#include "stm32mp1xx_gpio.h"
#include "stm32mp1xx_rcc.h"
// MOSI对应的引脚输出高低电平的信号
#define MOSI_OUTPUT_H() do{GPIOE->ODR |= (0x1 << 14);}while(0)
#define MOSI_OUTPUT_L() do{GPIOE->ODR &= (~(0x1 << 14));}while(0)
// 对应595芯片的锁存引脚输出高低电平
#define NSS_OUTPUT_H() do{GPIOE->ODR |= (0x1 << 11);}while(0)
#define NSS_OUTPUT_L() do{GPIOE->ODR &= (~(0x1 << 11));}while(0)
// 时钟信号对应的引脚输出高低电平
#define SCK_OUTPUT_H() do{GPIOE->ODR |= (0x1 << 12);}while(0)
#define SCK_OUTPUT_L() do{GPIOE->ODR &= (~(0x1 << 12));}while(0)
/*
* 函数功能: SPI初始化函数,推挽输出,高速,禁止上拉和下拉
* 函数参数:无
* 函数返回值:无
*/
void SPI_init(void);
/*
* 函数功能:SPI发送数据的函数
* 函数参数:dat : 要发送的数据
* 函数返回值:无
*
*/
void SPI_write(unsigned char dat);
#endif // __SPI_H__
然后是功能函数部分的代码
#include "spi.h"
// 通过GPIO引脚模拟SPI总线的通信协议
/* SPI4_NSS ----> PE11
* SPI4_SCK ----> PE12
* SPI4_MOSI ----> PE14
* SPI4_MISO ----> PE13
* */
/* 数码管的编码,不带小数点, 先发送低位,在发送高位
* A B C D E F G DP
* 1 1 1 1 1 1 0 0 0xFC 0
* 0 1 1 0 0 0 0 0 0x60 1
* 1 1 0 1 1 0 1 0 0xDA 2
* 1 1 1 1 0 0 1 0 0xF2 3
* 0 1 1 0 0 1 1 0 0x66 4
* 1 0 1 1 0 1 1 0 0xB6 5
* 1 0 1 1 1 1 1 0 0xBE 6
* 1 1 1 0 0 0 0 0 0xE0 7
* 1 1 1 1 1 1 1 0 0xFE 8
* 1 1 1 1 0 1 1 0 0xF6 9
* 0 0 0 1 1 0 1 0 0x1A
*
* 如果需要显示小数点,需要在以上数据的基础之上加1即可
* */
void delay_us1(unsigned int us)
{
int i,j;
for(i = 0; i < us;i++)
for (j = 0; j < 1;j++);
}
void SPI_init(void)
{
RCC->MP_AHB4ENSETR |= (0x1 << 4);
// MOSI PE14
GPIOE->MODER &= (~(0x3 << 28));
GPIOE->MODER |= (0x1 << 28);
GPIOE->OTYPER &= (~(0x1 << 14));
GPIOE->OSPEEDR &= (~(0x3 << 28));
// GPIOE->OSPEEDR |= (0x2 << 28);
GPIOE->PUPDR &= (~(0x3 << 28));
// MISO PE13
GPIOE->MODER &= (~(0x3 << 26));
GPIOE->OSPEEDR &= (~(0x3 << 26));
// GPIOE->OSPEEDR |= (0x2 << 26);
GPIOE->PUPDR &= (~(0x3 << 26));
// SCK PE12
GPIOE->MODER &= (~(0x3 << 24));
GPIOE->MODER |= (0x1 << 24);
GPIOE->OTYPER &= (~(0x1 << 12));
GPIOE->OSPEEDR &= (~(0x3 << 24));
// GPIOE->OSPEEDR |= (0x2 << 24);
GPIOE->PUPDR &= (~(0x3 << 24));
// NSS PE11
GPIOE->MODER &= (~(0x3 << 22));
GPIOE->MODER |= (0x1 << 22);
GPIOE->OTYPER &= (~(0x1 << 11));
GPIOE->OSPEEDR &= (~(0x3 << 22));
// GPIOE->OSPEEDR |= (0x2 << 22);
GPIOE->PUPDR &= (~(0x3 << 22));
NSS_OUTPUT_L(); // 595芯片的锁存引脚拉低
SCK_OUTPUT_L(); // SPI的时钟线拉低
}
void SPI_write(unsigned char dat)
{
unsigned char i;
for(i = 0; i < 8; i++)
{
if(dat & 0x01) // 由于数码管的编码格式,因此需要先发低位,再发高位。
{
MOSI_OUTPUT_H(); // MOSI线写高
} else {
MOSI_OUTPUT_L(); // MOSI线写低
}
dat >>= 1;
// 时钟线从低电平到高电平的变化时,MOSI数据线上的数据
// 被写到595芯片的移位寄存器中
SCK_OUTPUT_L(); // SCK拉低
delay_us1(10);
SCK_OUTPUT_H(); // SCK拉高
delay_us1(10);
}
//NSS_OUTPUT_L();
//NSS_OUTPUT_H();
}
最后是主函数的调用部分
#include "led.h"
#include "uart4.h"
#include "command.h"
#include "beep.h"
#include "fan.h"
#include "motor.h"
#include "interrupt.h"
#include "timer2_it.h"
#include "./include/si7006.h"
#include "./include/spi.h"
extern void printf(const char *fmt, ...);
void delay_ms(unsigned int ms)
{
int i, j;
for (i = 0; i < ms; i++)
for (j = 0; j < 1800; j++)
;
}
extern char buffer[LEN];
unsigned char num[10] = {0xFC, 0x60, 0xDA, 0xF2, 0x66, 0xB6, 0xBE, 0xE0, 0xFE, 0xF6};
int main()
{
unsigned int i;
si7006_init();
SPI_init();
while(1)
{
#if 0
// 案例1:让4位数码管都同时显示0-9,每隔1s中切换一次
for (i = 0; i < 10; i++) {
// 先发送数码管的位,在发送数码管的段
SPI_write(0xF0); // 发送数码管的位
SPI_write(num[i]); // 发送数码管的段
// 将移位寄存器中的数据存到索存寄存器中,让片选线产生一个上升沿
NSS_OUTPUT_L();
delay_ms(1);
NSS_OUTPUT_H();
delay_ms(1000); // 1s切换一次显示的数据
}
#endif
#if 1
// 案例2:让数码管显示不同的数字,比如显示1,2,3,4
// 主要利用的就是数码管的余辉效应:数码管从亮到灭或从灭到亮需要时间。
// 先让第一个亮,在第一个没有灭的时候,让第二个亮;
// 在第一个和第二个没有灭的时候让第三个亮;
// 在第一个,第二个和第三个没有灭的时候让第4个亮,
// 就可以让数码管显示不同的数字了,在此过程中不允许出现延时函数,
// 如果出现延时函数就会依次点亮不同的数码管。
for (i = 0; i < 4; i++) { // 选择位
SPI_write(0x80 >> i); // 依次发送数码管显示的位
SPI_write(num[i+1]); // 依次发送数码管的段,让数码管显示不同的数字
NSS_OUTPUT_L();
delay_ms(1);
NSS_OUTPUT_H();
delay_ms(500); // 不可以有延时
}
#endif
}
return 0;
}
运行结果的图
?
?
?
?
|