本篇文章由 VeriMake 旧版论坛中备份出的原帖的 Markdown 源码生成
原帖标题为:以 CH549 为例的 51 教程 | 图文 6. 通信:UART 和 SPI(下)
原帖网址为:https://verimake.com/topics/261 (旧版论坛网址,已失效)
原帖作者为:Xy(旧版论坛 id = 73,注册于 2020-10-15 13:03:26)
原帖由作者初次发表于 2021-08-19 15:40:18
📇 点此回到教程主索引
目录
SPI 介绍
连线
SPI 驱动的 OLED 屏幕具体代码解析
SPI 通信小实验
SPI 介绍
SPI 是串行外设接口的缩写,是一种高速的,全双工的,同步的,串行通讯总线。
高速指的是传输速率普遍大于i2c和uart,
全双工指的是同时可以进行发送和接受,
同步指的是spi中有时钟线,无论输入输出都需要根据其中的时钟信号,
SPI与uart相比,最大的区别是spi是以主从的方式工作的,通常情况下拥有一个主设备和一个或多个从设备,至少需要四根线来完成spi通讯(单向传输的话最少可以三根)

MOSI - 主设备数据输出,从设备数据输入
MISO - 主设备数据输入,从设备数据输出
SCLK - 时钟信号,由主设备产生,正是上文所说的spi的同步特性。
CS - 片选使能信号,由主设备产生,只有使能了一个从机获得的cs,从机才可以从主机那接受信息,或给主机发送信息。

举个例子,当cs1使能的时候则从机1则可以从MOSI那里接受数据或可以通过MISO发送数据。
连线
根据数据手册和屏幕上各个引脚对应关系,将屏幕与开发板在面包板上连接起来。

基础薄弱的同学可以像这样抄下作业:

SPI 驱动的 OLED 屏幕具体代码解析
整段代码调用ch549官方提供的spi库并借鉴了oled屏幕厂家提供的stc89c51的例程
main.c
/**
******************************************************************
* @file main.c
* @author 甜鱼酱,xy
* @version V1.0
* @date 2021-5-19
* @brief SPI
******************************************************************
* @attention
* verimake 用于ch549例程使用SPI驱动oled屏幕
* oled引脚接线表
* CS P1_4 //CS片选
* RST P3_5 //LED复位
* DC P2_7 //数据/命令控制
* D0 P1_7 //SCLK时钟信号
* D1 P1_5 //MOSI数据
******************************************************************
*/
#include <CH549_sdcc.h> //ch549的头文件,其中定义了单片机的一些特殊功能寄存器
#include <CH549_OLED.h> //其中有驱动屏幕使用的函数
#include <CH549_BMP.h> //用于显示图片的头文件
#include <CH549_DEBUG.h> //CH549官方提供库的头文件,定义了一些关于主频,延时,串口设置,看门口,赋值设置等基础函数
#include <CH549_SPI.h> //CH549官方提供库的头文件,定义了一些关于SPI初始化,传输数据等函数
/********************************************************************
* TIPS:
* oled引脚接线表
* CS P1_4 //CS片选
* RST P3_5 //LED复位
* DC P2_7 //数据/命令控制
* D0 P1_7 //SCLK时钟信号
* D1 P1_5 //MOSI数据
*********************************************************************/
int main(void)
{
CfgFsys( );
mDelaymS(20); //调整主频,建议稍加延时等待内部时钟稳定
SPIMasterModeSet(3); //SPI主机模式设置,模式3
SPI_CK_SET(12); //设置spi sclk 时钟信号为12分频
OLED_Init(); //初始化OLED
OLED_Clear(); //将oled屏幕上内容清除
setFontSize(16); //设置文字大小
OLED_ShowString(0,2,"Please follow Verimake!");
while(1);
}
更改OLED屏幕上的内容步骤:
1.使用OLED_Clear()函数清楚原oled屏幕上的内容。
2.使用setFontSize()函数设置字符的大小。
3.使用OLED_ShowString()函数设置字符的位置与内容
CH549_SPI.c
/********************************** (C) COPYRIGHT *******************************
* File Name : 官方spi库
* Author : WCH
* Version : V1.0
* Date : 2018/08/23
* Description : CH549 SPI主、从模式接口函数
注:片选有效时,从机会自动加载SPI0_S_PRE的预置值到发送移位缓冲区,所以最好可以在片选
有效前向SPI0_S_PRE寄存器写入预发值,或者在主机端丢弃首个接收字节,发送时注意主机会先
取走SPI0_S_PRE里面的值产生一个S0_IF_BYTE中断。
如果片选从无效到有效,从机首先进行发送的话,最好把输出的首字节放到SPI0_S_PRE寄存器中;
如果已经处于片选有效的话,数据数据使用SPI0_DATA就可以
*******************************************************************************/
#include <CH549_SPI.h>
#pragma NOAREGS
/*******************************************************************************
* Function Name : SPIMasterModeSet( UINT8 mode )
* Description : SPI主机模式初始化
* Input : UINT8 mode
* Output : None
* Return : None
*******************************************************************************/
void SPIMasterModeSet(UINT8 mode)
{
SCS = 1;
P1_MOD_OC &= ~(bSCS|bMOSI|bSCK);
P1_DIR_PU |= (bSCS|bMOSI|bSCK); //SCS,MOSI,SCK设推挽输出
P1_MOD_OC |= bMISO; //MISO 上拉输入
P1_DIR_PU |= bMISO;
SPI0_SETUP = 0; //Master模式,高位在前
if(mode == 0)
{
SPI0_CTRL = (bS0_MOSI_OE|bS0_SCK_OE); //模式0
}
else if(mode == 3)
{
SPI0_CTRL = (bS0_MOSI_OE|bS0_SCK_OE|bS0_MST_CLK); //模式3
}
}
/*******************************************************************************
* Function Name : CH549SPIMasterWrite(UINT8 dat)
* Description : CH549硬件SPI写数据,主机模式
* Input : UINT8 dat 数据
* Output : None
* Return : None
*******************************************************************************/
void CH549SPIMasterWrite(UINT8 dat)
{
SPI0_DATA = dat;
while(S0_FREE == 0)
{
; //等待传输完成
}
}
/*******************************************************************************
* Function Name : CH549SPIMasterRead( )
* Description : CH549硬件SPI0读数据,主机模式
* Input : None
* Output : None
* Return : UINT8 ret
*******************************************************************************/
UINT8 CH549SPIMasterRead()
{
SPI0_DATA = 0xff;
while(S0_FREE == 0)
{
;
}
return SPI0_DATA;
}
/*******************************************************************************
* Function Name : SPISlvModeSet( )
* Description : SPI从机模式初始化
* Input : None
* Output : None
* Return : None
*******************************************************************************/
void SPISlvModeSet( )
{
P1_MOD_OC &= ~(bSCS|bMOSI|bSCK); //SCS,MOSI,SCK 浮空输入
P1_DIR_PU &= ~(bSCS|bMOSI|bSCK);
P1_MOD_OC &= ~bMISO; //MISO推挽输出
P1_DIR_PU |= bMISO;
SPI0_S_PRE = 0x66; //预置值,任意值
SPI0_SETUP = bS0_MODE_SLV; //Slv模式,高位在前
SPI0_CTRL = bS0_MISO_OE; //MISO 输出使能
#ifdef SPI_INTERRUPT
SPI0_SETUP |= bS0_IE_FIRST | bS0_IE_BYTE;
IE_SPI0 = 1;
EA = 1;
#endif
}
/*******************************************************************************
* Function Name : CH549SPISlvWrite(UINT8 dat)
* Description : CH549硬件SPI写数据,从机模式
* Input : UINT8 dat 数据
* Output : None
* Return : None
*******************************************************************************/
void CH549SPISlvWrite(UINT8 dat)
{
SPI0_DATA = dat;
while(S0_FREE==0)
{
;
}
}
/*******************************************************************************
* Function Name : CH549SPISlvRead( )
* Description : CH549硬件SPI0读数据,从机模式
* Input : None
* Output : None
* Return : UINT8 ret
*******************************************************************************/
UINT8 CH549SPISlvRead()
{
while(S0_FREE == 0)
{
;
}
return SPI0_DATA;
}
#ifdef SPI_INTERRUPT
/*******************************************************************************
* Function Name : SPIInterrupt(void)
* Description : SPI 中断服务程序
*******************************************************************************/
void SPIInterrupt( void ) interrupt INT_NO_SPI0 using 1 //SPI中断服务程序,使用寄存器组1
{
UINT8 dat;
dat = CH549SPISlvRead();
CH549SPISlvWrite(dat^0xFF);
#if DE_PRINTF
printf("Read#%02x\n",(UINT16)dat);
#endif
}
#endif
以上为沁恒官方提供的spi库,以下为相比较为重要的函数:

CH549SPIMasterWrite(UINT8 dat) 用于ch549作为主机使用mosi输出数据给从机。
CH549SPIMasterRead() 用于ch549作为主机使用miso读取从机发送数据。
同样ch549也可以作为spi的从机,库中也有相应的函数。
CH549单片机使用spi读取数据和uart一样可以通过中断来读取的,并不会影响其他代码的运行。

编译下载进开发板,即可看到:

SPI 通信小实验
- 将按键连接引脚p1.0和gnd。

- 将main例程进行以下修改:
/**
******************************************************************
* @file main.c
* @author 甜鱼酱,xy
* @version V1.0
* @date 2021-5-19
* @brief SPI
******************************************************************
* @attention
* verimake 用于ch549例程使用SPI驱动oled屏幕
* oled引脚接线表
* CS P1_4 //CS片选
* RST P3_5 //LED复位
* DC P2_7 //数据/命令控制
* D0 P1_7 //SCLK时钟信号
* D1 P1_5 //MOSI数据
******************************************************************
*/
#include <CH549_sdcc.h> //ch549的头文件,其中定义了单片机的一些特殊功能寄存器
#include <CH549_OLED.h> //其中有驱动屏幕使用的函数
#include <CH549_BMP.h> //用于显示图片的头文件
#include <CH549_DEBUG.h> //CH549官方提供库的头文件,定义了一些关于主频,延时,串口设置,看门口,赋值设置等基础函数
#include <CH549_SPI.h> //CH549官方提供库的头文件,定义了一些关于SPI初始化,传输数据等函数
/********************************************************************
* TIPS:
* oled引脚接线表
* CS P1_4 //CS片选
* RST P3_5 //LED复位
* DC P2_7 //数据/命令控制
* D0 P1_7 //SCLK时钟信号
* D1 P1_5 //MOSI数据
*********************************************************************/
#define key1 P1_0 //将单片机的P1.0端口定义为key1
int main(void)
{
CfgFsys( );
mDelaymS(20); //调整主频,建议稍加延时等待内部时钟稳定
SPIMasterModeSet(3); //SPI主机模式设置,模式3
SPI_CK_SET(12); //设置spi sclk 时钟信号为12分频
OLED_Init(); //初始化OLED
OLED_Clear(); //将oled屏幕上内容清除
setFontSize(16); //设置文字大小
OLED_ShowString(0,2,"Please follow Verimake!");
while(1)
{
if(key1==0)
{
OLED_DC = 0;//OLED_DC即p2.7,可选择对oled屏幕传输的数据是用于显示在屏幕上or控制屏幕,赋值为0为选择控制屏幕,赋值为1为选择传输显示在屏幕上的数据
OLED_CS = 0;//OLED_CS即p1.4,片选控制位,赋值为0则使能此从机,赋值为1则不使能此从机
CH549SPIMasterWrite(0xAE); //将0xAE作为命令控制通过spi传输给oled,0xAE对应的命令为清空屏幕内容
}
}
//OLED_DC = 0;
//OLED_CS = 0;
//CH549SPIMasterWrite(0xAE);
}
具体修改为:
- 添加按键端口的定义
- 在while(1)中添加
if(key1==0)
{
OLED_DC = 0;//OLED_DC即p2.7,可选择对oled屏幕传输的数据是用于显示在屏幕上or控制屏幕,赋值为0为选择控制屏幕,赋值为1为选择传输显示在屏幕上的数据
OLED_CS = 0;//OLED_CS即p1.4,片选控制位,赋值为0则使能此从机,赋值为1则不使能此从机
CH549SPIMasterWrite(0xAE); //将0xAE作为命令控制通过spi传输给oled,0xAE对应的命令为清空屏幕内容
}
含义为如果按下按键则会执行三句代码:
OLED_DC = 0;//OLED_DC即p2.7,可选择对oled屏幕传输的数据是用于显示在屏幕上or控制屏幕,赋值为0为选择控制屏幕,赋值为1为选择传输显示在屏幕上的数据
第一句将oled_DC赋值为0,oled_DC即p2.7,此变量赋值的意义在于选择对oled屏幕传输的数据是用于显示在屏幕上or控制屏幕,赋值为0为选择控制屏幕,赋值为1为选择传输显示在屏幕上的数据,此处为控制屏幕。
OLED_CS = 0;//OLED_CS即p1.4,片选控制位,赋值为0则使能此从机,赋值为1则不使能此从机。
第二句 将OLED_CS赋值为0,OLED_CS即p1.4,即片选控制位,赋值为0则使能此从机,赋值为1则不使能此从机,此处为使能从机。
CH549SPIMasterWrite(0xAE); //将0xAE作为命令控制通过spi传输给oled,0xAE对应的命令为清空屏幕内容。
第三句则是将0xAE作为命令控制通过spi传输给oled屏幕,0xAE对应的命令为清空屏幕内容。
3.将改好的代码编译下载,按下按键之后屏幕上原有的字样消失了,表明我们成功的使用ch549完成了SPI通讯。