4. 串行设备接口SPI
4. 串行设备接口SPI
4.1 SPI结构及寄存器说明
4.2 SPI设计实例
4.2.1 SPI基本功能程序设计
4.2.2 SPI环回程序设计
4.2.3 用SPI控制LCD
4.1 SPI结构及寄存器说明
- 串行设备接口(Serial peripheral interface:SPI)是工业标准串行协议,通常用于嵌入式系统,将微处理器连接到各种片外传感器、转换器、存储器和控制设备
- SPI可以实现主设备或从设备协议,当配置为主设备时,SPI可以连接多达16个独立的从设备,发送数据和接收数据寄存器的宽度可配置为8位或16位
- SPI使用2根数据线、1根控制线和1根时钟线实现串行通信
主入从出(MISO) 主出从入(MOSI)
串行时钟(SCK) 从设备选择(NSS)
- 时钟极性和时钟相位组合
- 时钟极性为0时初始电平为低,为1时初始电平为高时钟相位为0时第1个边沿采样,为1时第2个边沿采样
- SPI由收发数据和收发控制两部分组成
- 收发数据部分包括发送缓冲区、接收缓存区和移位寄存器
- 收发控制部分包括控制状态寄存器、通信电路、主控制电路和波特率发生器
- NSS是一个可选的引脚功能是用作“片选引脚”用来选择从设备,通常配成通用I/O引脚
- 当SPI连接多个从设备时MOSI、MISO和SCK连接所有的从设备,但每个从设备的NSS引脚必须连接到主设备的一个通用I/O引脚
- SPI使用的GPIO引脚
注:(1)括号中的引脚为复用功能重映射引脚
- SPI通过7个寄存器进行操作
- SPI控制寄存器1(CR1)
- SPI控制寄存器2(CR2)
- SPI状态寄存器(SR)
4.2 SPI设计实例
4.2.1 SPI基本功能程序设计
- SPI初始化子程序
// SPI1初始化子程序(主模式)
void Spi1_Init(void)
{
RCC->APB2ENR |= 1<<2; //开启GPIOA时钟
RCC->APB2ENR |= 1<<12; //开启SPI1时钟
GPIOA->CRL &= 0x0000ffff;
GPIOA->CRL &= 0xb4bb0000; //PA.07(MOSI)复用推挽输出、PA.06(MISO)浮空输入
//PA.05(SCK)和PA.04(NSS)复用推挽输出
SPI1->CR1 |= 1<<2; //主模式
SPI1->CR1 |= 1<<6; //SPI允许
}
- SPI发送/接收子程序
//SPI1发送子程序
//入口参数:data-发送数据
//出口参数:返回发送数据
short Spi1_Txd(short data)
{
SPI1->DR =data; //发送数据
while(!(SPI1->SR&2)); //等待TxE=1(发送数据寄存器空)
return data; //返回数据
}
//SPI1接收子程序
//出口参数:接收数据(接收成功)/0(接收不成功)
short Spi1_Rxd(void)
{
if(SPI1->SR&1) //RXNE=1(接收数据寄存器不空)
return SPI1->DR; //返回接收数据
else
return 0; //返回0
}
4.2.2 SPI环回程序设计
int mian(void)
{
SysTick_Init(); //系统定时器初始化
Key_Init(); //按键接口初始化
Led_Init(); //LED接口初始化
Lcd_Init(); //LCD初始化
USART1_Init(); //USART1初始化
Spi_Init(); //SPI1初始化
while(1)
{
SysTick_Proc(); //定时处理
Key_Proc(); //按键处理
Led_Proc(); //LED处理
Lcd_Proc(min,sec,(sec&1)<<3); //LCD显示分秒
if(sec != sec2) //1s时间到
{
sec2 = sec;
printf("%02x:",min); //分值直接通过USART发送
Spi1_Txd(sec); //秒值通过SPI发送
}
Rxd_Time(); //设置分秒值
if(SPI1->SR&1) //RXNE=1(接收数据寄存器不空)
printf("%02x \r\n",SPI->DR);
}
}
4.2.3 用SPI控制LCD
- 对比LCM046写操作时序和SPI时钟极性和时钟相位组合图可以看出:LCM046写操作时序和SPI时钟极性=1、时钟相位=1的信号波形相同,因此可以用SPI控制LCM046
- 实验证明:SPI时钟极性=0、时钟相位=0时也可以控制LCM046正常工作,这是liyongSPI控制外设的典型方法
- SPI初始化子程序
//SPI1初始化子程序(主模式)
void Spi1_Init(void)
{
RCC->APB2ENR |= 1<<2; //开启GPIOA时钟
RCC->APB2ENR |= 1<<12; //开启SPI1时钟
GPIOA->CRL &= 0x0000ffff;
GPIOA->CRL |= 0xb4b70000; //PA.07(MOSI)和PA.05(SCK)复用推挽输出
//PA.04(NSS)通用开漏输出
GPIOA->ODR |= 1<<4; //PA.04(NSS)输出高电平
SPI1->CR1 |= 0x0b6f; //16位、SPI允许、PCLK/64
//主模式、CPOL=1、CPHA=1
}
- SPI发送子程序
//SSPI1发送子程序
//入口参数:data-发送数据
//出口参数:返回发送数据
short Spi1_Txd(short data)
{
GPIOA->BRR = 1<<4; //PA.04(NSS)=0
SPI1->DR = data; //发送数据
while(!(SPI1->SR&2)); //等待
while(SPI1->SR&1<<7); //等待BSY=0(忙)
GPIOA->BSRR = 1<<4; //PA.04(NSS)=1
return data; //返回发送数据
}
- LCM046初始化子程序
//LCM046初始化子程序
void Lcd_Init(void)
{
Spi1_Init(); //初始化SPI1
Lcd_Code_Write(0x29); //初始化模块
Lcd_Code_Write(0x18); //内部振荡器
Lcd_code_Write(0x01); //开振荡器
Lcd_Code_Write(0x03); //开显示器
}
- LCM046写命令和写数据子程序
//LCM046写命令子程序
//入口参数:code-8位命令代码
void Lcd_Code_Write(char code)
{
short data = (4<<9)+(code<<1);
Spi1_Txd(data<<4); //数据左对齐
}
//LCM046写数据子程序
//入口参数:addr-6位地址,data-4位数据
void Lcd_Data_Write(char addr,char data)
{
short data1 = (5<<10)+(addr<<4)+data;
Spi1_Txd(data1<<3); //数据左对齐
}