有关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
作为实现设备。
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
;
//************************************************************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;elseOLED_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;elseOLED_SDA=0;OLED_SCL=1;data <<=1;}OLED_CS=1; //锁存}
3.4 设置OLED
坐标
设置OLED
坐标需要使用到两个命令:
- 设置页地址,使用单字节指令:
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 清屏
清屏需要将所有的页数据清零;
//**************************************清屏*******************************
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 PA10STM32_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
节进行硬件接线,测试效果如下;
下图是使用逻辑分析器捕捉到的模拟SPI
信号;