有关SPI
通信协议我们在《通信协议-SPI
》已经进行了详细的介绍,因此这一节不再重复介绍。
一、软件/硬件SPI
想要控制STM32
产生SPI
方式的通讯,可以采用软件模拟或硬件SPI
这两种方式。
1.1 软件模拟
所谓软件模拟,即直接使用CPU
内核按照SPI
协议的要求控制GPIO
输出高低电平。
1.2 硬件SPI
硬件SPI
是指直接利用STM32
芯片中的硬件SPI
外设,该硬件SPI
外设跟USART
串口外设类似,只要配置好对应的寄存器, 外设就会产生标准串口协议的时序。
使用它的SPI
外设则可以方便地通过外设寄存器产生SPI
协议方式的通讯,如初始化好SPI
外设后, 只需要把某寄存器位置1
,那么外设就会控制对应的SCK
及MOSI/MISO
线自动进行SPI
数据传输,而不需要内核直接控制引脚的电平。
相对来说,硬件SPI
直接使用外设来控制引脚,可以减轻CPU
的负担。不过使用硬件SPI
时必须使用某些固定的引脚作为SCL
和MOSI/MISO
, 软件模拟SPI
则可以使用任意GPIO
引脚,相对比较灵活。
在本开发板中,由于STM32F103RCT6
芯片引脚较少,资源比较紧张, 在设计硬件时不方便使用硬件SPI
指定的引脚连接外部设备,所以在控制程序上这里使用软件模拟SPI
的方式。
二、OLED128x64
(SSD1306
)
2.1 回顾
有关OLED128x64(SSD1306)
可以参考《Mini2440
裸机开发之SPI(OLED SSD1306)
》小节中的介绍;

在《Mini2440
裸机开发之SPI(OLED SSD1306)
》我们介绍了SSD1306
的常用命令,以及SPI
通信方式。
后续我们又在《linux
驱动移植-SPI
驱动移植(OLED SSD1306
)》中介绍了SPI
设备驱动的编写,并以OLED SSD1306
作为学习SPI
的测试设备。
2.2 硬件接线
当SSD1306
选定SPI
接口方式,SPI
引脚定义:
CS
:片选信号;连接是STM32F103
的PC0
引脚;DC
:命令数据选择引脚;连接STM32F103
的PC1
引脚;0
:读写命令;1
:读写数据;
RES
:模块复位引脚,低电平有效;连接STM32F103
的PC2
引脚;D1
:MOSI
引脚,SPI
数据线,主设备输出从设备输入引脚;连接STM32F103
的PC3
引脚;D0
:SCLK
引脚,SPI
时钟线;连接STM32F103
的PC4
引脚;VCC
:电源正极3.3~5V
,连接STM32F103
的PC5
引脚;GND
:电源地,连接STM32F103
的PC6
引脚。
三、OLED
源码实现
3.1 GPIO
初始化
配置PC0~PC6
引脚,均配置为通用推挽输出,最大速度2MHZ
;
/*******************************************************************************************
*4线SPI使用说明:
*VBT 供内部DC-DC电压,3.3~4.3V,如果使用5V电压,为保险起见串一个100~500欧的电阻
*VCC 供内部逻辑电压 1.8~6V
*GND 地
*BS0 低电平
*BS1 低电平
*BS2 低电平
*CS 片选管脚
*DC 命令数据选择管脚
*RES 模块复位管脚
*D0(SCLK) ,时钟脚,由MCU控制
*D1(MOSI) ,主输出从输入数据脚,由MCU控制
*D2 悬空
*D3-D7 , 低电平 , 也可悬空,但最好设为低电平
*RD 低电平 ,也可悬空,但最好设为低电平
*RW 低电平 ,也可悬空,但最好设为低电平
*RD 低电平 ,也可悬空,但最好设为低电平
************************************************************************************************/
//************************************************************12864端口定义*********************************
#define OLED_GND PCout(6) //GND
#define OLED_VCC PCout(5) //VCC
#define OLED_SCL PCout(4) //串行时钟线
#define OLED_SDA PCout(3) //串行数据线
#define OLED_RST PCout(2) //硬复位
#define OLED_DC PCout(1) //命令/数据标志 0:读写命令 1:读写数据
#define OLED_CS PCout(0) //片选信号
/*******************************************************************************
*
* Description: 初始化所用到的引脚
*
******************************************************************************/
void OLED12864_GPIO_Init(void) //初始化所用到的引脚
{
gpio_init(PC0,GPO_PUSH_PULL_2,HIGH);
gpio_init(PC1,GPO_PUSH_PULL_2,HIGH);
gpio_init(PC2,GPO_PUSH_PULL_2,HIGH);
gpio_init(PC3,GPO_PUSH_PULL_2,HIGH);
gpio_init(PC4,GPO_PUSH_PULL_2,HIGH);
gpio_init(PC5,GPO_PUSH_PULL_2,HIGH);
gpio_init(PC6,GPO_PUSH_PULL_2,LOW);
};
3.2 写命令
写命令需要将DC
设置为低电平,然后发送一个字节的命令即可;
/*******************************************************************************
*
* Description: 写命令 DC=0 CS=0 CLK上升沿数据传递
* 数据从高位开始写入
*
******************************************************************************/
void OLED_Wcmd(u8 cmd) //写命令
{
u8 i;
OLED_CS=0; //片选
OLED_DC=0; //写命令
for(i=0;i<8;i++)
{
OLED_SCL=0;
if(cmd&0x80)
OLED_SDA=1;
else
OLED_SDA=0;
OLED_SCL=1;
cmd <<=1;
}
OLED_CS=1; //锁存
}
3.3 写数据
写数据需要将DC
设置为高电平,然后发送一个字节的数据即可;
/*******************************************************************************
*
* Description: 写数据 DC=1 CS=0 CLK上升沿数据传递
* 数据从高位开始写入
*
******************************************************************************/
void OLED_Wdata(u8 data) //写数据
{
u8 i;
OLED_CS=0; //片选
OLED_DC=1; //写数据
for(i=0;i<8;i++)
{
OLED_SCL=0;
if(data&0x80)
OLED_SDA=1;
else
OLED_SDA=0;
OLED_SCL=1;
data <<=1;
}
OLED_CS=1; //锁存
}
3.4 设置OLED
坐标
设置R
坐标需要使用到两个命令:
- 设置页地址,使用单字节指令:
0xB0H + A[3:0]
; - 设置列地址,使用单字节指令:
0x00H / 0x10H (低/高)+ A[3:0]
;
实现如下:
/***************************************************************************************************
*
* Description: OLED12864设置坐标 一个ASCII字符字符占用6*8 6列8行
* x : 设置列地址0~0X7F
* y : 设置页地址0~7
*
**************************************************************************************************/
void OLED_Pos(u8 x,u8 y) //坐标设定
{
OLED_Wcmd(0xB0+y);
OLED_Wcmd(((x&0xF0)>>4)|0x10); //设置列地址高四位
OLED_Wcmd((x&0x0F)|0x01); //设置列地址低四位
}
3.5 清屏
清屏需要将OLED
所有的页数据清零;
//**************************************清屏*******************************
void OLED_Clear(void)
{
u8 x;
u8 y;
for(y=0;y<8;y++)
{
OLED_Wcmd(0xB0+y); //选择页
OLED_Wcmd(0x01);
OLED_Wcmd(0x10);
for(x=0;x<0x80;x++)
OLED_Wdata(0x00); //每次清1列
}
}
3.6 OLED
初始化
初始化代码如下所示,这里我们就不深究每个命令的含义了,具体查看数据手册;
/***************************************************************************************************
*
* Description: OLED12864初始化
*
*
**************************************************************************************************/
void OLED12864_Init(void) //初始化配置
{
OLED_SCL=1;
OLED_CS=0;
OLED_RST=0;
delay_ms(50); //复位
OLED_RST=1;
OLED_Wcmd(0xAE); //显示关
OLED_Wcmd(0x00) ; //设置列低位地址
OLED_Wcmd(0x10); //设置列高位地址
OLED_Wcmd(0x40); //--set start line address Set Mapping RAM Display Start Line (0x00~0x3F)
OLED_Wcmd(0X81); //设置对比度
OLED_Wcmd(0xCF); //值越大 越亮
OLED_Wcmd(0xA1); //设置列左右反置 0xa0左右反置 0xa1正常
OLED_Wcmd(0xC8); //设置行上下反置 0xc0上下反置 0xc8正常
OLED_Wcmd(0xA6); //设置正常显示
OLED_Wcmd(0x20); //设置页地址模式 (0x00/0x01/0x02)
OLED_Wcmd(0x02);
OLED_Wcmd(0x8D); //设置电荷磊开关
OLED_Wcmd(0x14); //电荷磊开
OLED_Wcmd(0xA4); //字符显示开关 0xA4:开 0xA5:关
OLED_Wcmd(0xA6); // 背景色显示开关 0xA6:关 0xA7:开
OLED_Wcmd(0xAF); //显示开
OLED_Clear(); //初始清屏
OLED_Pos(0,0);
}
3.7 显示字符
关于字符的显示原理这里不重复介绍了,具体可以查看《Mini2440
裸机开发之LCD
编程(GB2312
、ASCII
字库制作)》。
3.7.1 1
个ASCII
字符8
行6
列
/*******************************************************************************************************
*
* Functon: OLED_P6x8Str(u8 x,u8 y,u8 *str)
* Description: 写入一组标准ASCII字符串
* Parameter : 显示的位置(x,y),y为页范围0~7,要显示的字符串
* x : 设置列地址0~0X7F
* y : 设置页地址0~7
* 一个字节占8行6列
*
*********************************************************************************************************/
void OLED_P6x8Str(u8 x,u8 y,u8 *str) //6列8行 一列8位
{
u8 i=0;
u8 j=0;
u8 k=0;
while (str[j]!='\0')
{
while((str[j]<0x20)||(str[j]>0x80)) //当写入的没有对应的点阵时 显示空格
str[j]=32;
k =str[j]-32;
if(x>121)
{
x=0;
y++;
}
OLED_Pos(x,y); //选中坐标
for(i=0;i<6;i++)
OLED_Wdata(ASCII6x8[k][i]); //写入一个字节
x+=6;
j++;
}
}
5.7.2 1
个ASCII
字符16
行8
列
/*******************************************************************************************************
*
* Functon: OLED_P16x8Str(u8 x,u8 y,u8 *str)
* Description: 写入一组标准ASCII字符串
* Parameter : 显示的位置(x,y),y为页范围0~7,要显示的字符串
* x : 设置列地址0~0X7F
* y : 设置页地址0~7
* 一个字节占16行8列
*
*********************************************************************************************************/
void OLED_P16x8Str(u8 x,u8 y,u8 *str)
{
u8 i=0;
u8 j=0;
u8 k=0;
while(str[j]!='\0')
{
while((str[j]<0x20)||(str[j]>0x80)) //当写入的没有对应的点阵时 显示空格
str[j]=32;
k=str[j]-32;
if(x>120) //列溢出 写入下一页
{
x=0;
y++;
}
OLED_Pos(x,y); //选中页和列坐标
for(i=0;i<8;i++) //写入上八行
{
OLED_Wdata(ASCII16x8[k][i]);
}
OLED_Pos(x,y+1);
for(i=0;i<8;i++) //写入下八行
{
OLED_Wdata(ASCII16x8[k][i+8]);
}
x+=8; //x坐标右移8位 准备写入下一个字节
j++; //下一个字符
}
}
3.7.3 1
个汉字8
行16
列
/*******************************************************************************************************
*
* Functon: OLED_P8x16Chi(u8 x,u8 y,u8 *str)
* Description: 写入一组汉字
* Parameter : 显示的位置(x,y),y为页范围0~7,要显示的字符串
* x : 设置列地址0~0X7F
* y : 设置页地址0~7
* 一个字占8行16列
*
*********************************************************************************************************/
void OLED_P8x16Chi(u8 x,u8 y,u8 *str)
{
u8 i=0;
u8 j=0;
u8 k=0;
while(str[j]!='\0')
{
if(x>120) //列溢出 写入下一页
{
x=0;
y++;
}
OLED_Pos(x,y); //选中页和列坐标
for(i=0;i<16;i++) //写入字
{
OLED_Wdata(CHINESE8x16[k][i]);
}
x+=16; //x坐标右移8位 准备写入下一个字节
j+=2; //下一个字
k++;
}
}
3.8 实现功能
这里我们通过OLED
实时输出RTC
时间。
3.8.1 main
函数实现
int main()
{
u8 *time;
STM32_Clock_Init(9); //系统时钟初始化
STM32_NVIC_Init(2,USART1_IRQn,0,1); //串口中断优先级初始化,其中包括中断使能
usart_init(USART_1,115200); //串口1初始化,波特率115200 映射到PA9 PA10
STM32_NVIC_Init(2,RTC_IRQn,0,1); //RTC中断优先级初始化,其中包括中断使能
while(RTC_Init()); //RTC初始化
time = RTCTime();
OLED12864_GPIO_Init(); //GPIO初始化
OLED12864_Init(); //OLED初始化
OLED_P16x8Str(45,0,"OLED"); //调用LCD_P8x16Str字符串显示函数,在第0页即第一行的第40列开始,显示字符串“OLED"
OLED_P8x16Chi(16,6,"安徽理工大学");
while(1)
{
time = RTCTime();
OLED_P6x8Str(8,4,time); //显示当前时间
delay_ms(1000);
}
}
3.8.2 测试
编译程序并下载测试,这里我们需要按照2.2
节进行硬件接线,OLED
测试效果如下;
下图是使用逻辑分析器捕捉到的模拟SPI
信号;

四、源码下载
源码下载路径:stm32f103
。