SPI总线概念
SPI总线是Motorola首先提出的全双工三线/四线同步串行总线,采用主从模式(Master Slave)架构;支持多从机(slave)模式应用,一般仅支持单主机,多从机。
时钟由主机控制,在时钟移位脉冲下,数据按位传输,高位在前,低位在后(MSB first)
SPI接口有2根单向数据线,为全双工 通信,目前应用中的数据速率可达几Mbps的水平,所以,SPI总线被广泛地使用在FLASH、ADC、LCD等设备与MCU间,要求通讯速率较高的场合。
SPI硬件连接
1.SPI接口共有4根信号线,
分别是:设备选择线(片选线):NSS nsc
时钟线:SCK CLK 串行输出数据线:MOSI
串行输入数据线:MISO
M:master主机 O:output输出 S:slave从机 I:input输入
2.作用:
• (1)MOSI:主器件数据输出,从器件数据输入(M out S in)
• (2)MISO:主器件数据输入,从器件数据输出(M in S out)
• (3)SCLK :时钟信号,由主器件产生
• (4)/SS:从器件使能信号,由主器件控制(片选)
三线制
四线制
通信协议
起始信号: NSS信号由高到低
结束信号:NSS信号由低变高
数据传输:SPI使用MOSI及MISO信号线来传输数据,使用SCK信号线同步数据。MOSI以及
MISO数据线在SCK每一个时钟周期内传输一位数据,且数据输入输出是同时进行
的。SPI每次数据传输可以8或16位为单位,每次传输的单位数不受限制。
通信模式
在SPI操作中,最重要的两项设置是时钟极性(CPOL)以及时钟相位(CPHA)。这两项是主从设备间数据采样的约定方式。
• 时钟极性CPOL:设置时钟空闲时的电平
当CPOL = 0时,SCK引脚在空闲状态保持低电平
当CPOL = 1时,SCK引脚在空闲状态保持高电平
• 时钟相位CPHA:设置数据采样时的时钟沿
当CPHA = 0时,MOSI或MISO数据线上的信号将会在SCK时钟线的奇数边沿被采样
当CPHA = 1时,MOSI或MISO数据线上的信号将会在SCK时钟线的偶数边沿被采样
相对应的,在CPOL和CPHA的变化组合中,诞生了2×2种通信模式
通信模式
SPI模式 | CPOL | CPHA | 空闲时SCK时钟 | 采样时刻 |
0(00) | 0 | 0 | 低电平 | 奇数边沿 |
1(01) | 0 | 1 | 低电平 | 偶数边沿 |
2(10) | 1 | 1 | 高电平 | 奇数边沿 |
3(11) | 1 | 0 | 高电平 | 偶数边沿 |
分析电路
由此可知:
是GPIO章节
* SPI4_NSS ----> PE11
* SPI4_SCK ----> PE12
* SPI4_MOSI ----> PE14
* SPI4_MISO ----> PE13
数码管驱动原理
一个a~g分别控制一根数码管的亮灭字母组合控制数码管亮起,比如,数字2,就是abged。将它们转化为二进制,即使我们需要按位调整的代码。
分析芯片手册
翻看芯片手册《M74HC595》
引脚描述
工作原理
数码管真值表
《STM32MP157-datasheet》的GPIO章节
首先需要RCC给GPIOE使能
这个是RCC章节的
其次设置成通用输出模式
然后设置成推挽输出模式
接着再是低速模式
最后设置为禁止上下拉
代码实现
数码管数字对应值,最终的结果为:四个数码管会同时从0~9循环
/* 数码管的编码, 先发送低位,再发送高位
* 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
* */
头文件
#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__
先根据GPIO章节的分析,写好初始化部分
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();
}