(题外话)为什么选择寄存器来实现,对于初学者而言我非常建议从寄存器配置开始,主要是因为搞单片机本来就是一项接近于底层硬件的工作,不要嫌麻烦。了解硬件外设工作原理和配置过程会对以后的调试有很大帮助。更容易理解库函数开发。
1.硬件资源描述
主控 | STM32F103RC |
---|
通讯方式 | 硬件SPI1 +DMA1(DMA可选) | 屏幕 | 0.96寸蓝色OLED屏幕 |
下面是屏幕图片 2.OLED屏幕驱动方法说明 屏幕的话可以在那啥宝上买到大概10块钱,现在应该还没涨价吧。至于买IIC协议的还是SPI协议的就看单片机使用习惯了,个人还是喜欢SPI的,相比IIC讲SPI传输速度更快点。至于NSS是接地还是硬件控制,看个人习惯,我选择的是硬件控制。
屏幕与单片机接线如下:
或者
D0
SCK : PA5
D1
MOSI : PA7
DC
PA1
RES
PA3
NSS
PA4
GND
注意:VDD和GND千万不要搞反了不然10块钱白给(我就烧坏了一个)😂
2.1:怎么和屏幕通讯并配置单片机的通讯
- 下面这张图是OLED屏幕驱动芯片手册提供的SPI时序CS# :通讯时一定要保持低电平。
D/C# :就是命令模式选择信号低电平时SDIN(D1)的数据就是命令控制字节。 SCLK :空闲电平高低无关,从这条时钟线可以看出屏幕是在上升沿锁存数据,下降沿允许数据变化。(这点很重要!) SDIN(D1) :8为数据高位在前。
(第三行的时序就是最后两行的组合)
- 单片机通讯配置
这张图来自STM32F1参考手册 stm32f1可以将SPI配置成4种通讯时序。对比我们的屏幕,只能选择上升沿采样的两种方式。
如下表:
序号 | 控制位 | 描述 |
---|
1 | CPHA=1 CPOL=1 | 空闲时高电平,第二个边沿采样(上升沿) | 2 | CPHA=0 CPOL=0 | 空闲时低电平,第一个边沿采样(上升沿) |
下面的例程使用的是上表中序号2。 stm32SPI1寄存器配置如下:
void SPI1_Init(void)
{
u16 spitest= 0;
RCC->APB2ENR |= 1<<2;
RCC->APB2ENR |= 1<<0;
RCC->APB2ENR |= 1<<12;
GPIOA->CRL &= 0x0F00FFFF;
GPIOA->CRL |= 0xB0BB0000;
spitest |= 1<<2;
spitest |= 1<<9;
spitest |= 1<<8;
spitest |= 1<<15;
spitest |= 1<<14;
spitest &=~(1<<1);
spitest &=~(1<<0);
spitest &=~(1<<11);
spitest &= 0xFFFFFFC7;
spitest &=~(1<<7);
SPI1->CR1 = spitest;
SPI1->CR2 |= 1<<1;
SPI1->CR1 |=1<<6;
}
2.2:DMA stm32的DMA可是32的一大特色,优点在于它不需要CPU干预,只要DMA被触发就能直接对数据进行搬运。所以使用它可以节省CPU的工作时间用来处理其他任务,在一些大项目中这点尤为突出。详细的配置介绍请参考STM32F1参考手册,这块我就不多言了。直接上代码。
(不想使用DMA的话可以将主函数注释掉的部分取消注释,并在屏幕初始化函数子函数内倒数第二行取消调用DMA函数)
inline static void DMA_OLED_Init(void)
{
RCC->AHBENR |= 1<<0;
DMA1_Channel3->CPAR = (u32)(&(SPI1->DR));
DMA1_Channel3->CMAR = (u32)(OLED_SRAM);
DMA1_Channel3->CCR |= 0<<14;
DMA1_Channel3->CCR |= 0<<6;
DMA1_Channel3->CCR |= 1<<7;
DMA1_Channel3->CCR |= 1<<5;
DMA1_Channel3->CCR |= 1<<4;
DMA1_Channel3->CNDTR= 0x400;
DMA1_Channel3->CCR |= 1<<0;
}
2.3:OLED屏幕的初始化及设置 相关的控制命令在屏幕手册中都有,下面要注意的一点就是DC控制要注意时间问题。DC是由GPIO控制的,所以反转速度很快,数据发送相比DC要慢得多。倒数第四行的 Wait_us(10); 就是在调试过程中遇到的问题,从代码上看数据已经发送完成了然后再改变DC命令选择,貌似没问题。但是屏幕一直没反应,用逻辑分析仪抓取时序图后发现该问题,下面附图。例程代码已纠正放心复制。 图中时序自上而下分别是SCK,MISO,DC。明显看出数据还未发送结束DC已经改变。
分析原因:子函数OLED_SendCmd(unsigned char)是判断发送区是否为空,然后条件式的装入。而硬件SPI则需等待上一个数据发送完成才会再处理刚送进来的数据,同样也需要时间。
代码部分如下:
void OLED_Init(void)
{
#define OLED_DC PA1
#define OLED_RES PA3
#define OLED_NSS PA4
#define OLED_D0 PA5
#define OLED_D1 PA7
GPIOA->CRL &= 0xFFFF0F0F;
GPIOA->CRL |= 0x00003030;
OLED_RES = 0;
Wait_us(100);
OLED_RES = 1;
OLED_DC = 0;
Wait_us(100);
OLED_SendCmd(0xAE);
OLED_SendCmd(0xD5);
OLED_SendCmd(0xF0);
OLED_SendCmd(0x81);
OLED_SendCmd(0x7F);
OLED_SendCmd(0x8D);
OLED_SendCmd(0x14);
OLED_SendCmd(0x20);
OLED_SendCmd(0x00);
OLED_SendCmd(0xD3);
OLED_SendCmd(0x2A);
OLED_SendCmd(0x21);
OLED_SendCmd(0x00);
OLED_SendCmd(0x7F);
OLED_SendCmd(0x22);
OLED_SendCmd(0x00);
OLED_SendCmd(0x07);
OLED_SendCmd(0xC8);
OLED_SendCmd(0xA1);
OLED_SendCmd(0xA4);
OLED_SendCmd(0xA6);
OLED_SendCmd(0xAF);
OLED_SendCmd(0x56);
Wait_us(10);
OLED_DC = 1;
DMA_OLED_Init();
Wait_us(10);
}
OLED_SendCmd(0xA4);//全局显示开启;0xa4正常,0xa5无视命令点亮全屏 这行代码在程序调试的时候可以将参数改为0xa5判断通讯是否正常。
这个是命令发送函数 OLED屏幕刷新一帧需要128*8个字节,每个字节的每一位控制屏幕的一个像素点(位的0或1表示亮灭),刚好是128 *64个位。
static void OLED_SendCmd(unsigned char ctrl_data)
{
unsigned char t=200;
while(! (SPI1->SR & 1<<1) )
{
t--;
if(t<=0)
break;
}
SPI1->DR = ctrl_data;
}
void OLED_Write(unsigned char ASII,unsigned char ye,unsigned char lie)
{
char i;
for(i=0;i<6;i++)
{
OLED_SRAM[ye][lie+i]=F6X8[(ASII-32)*6+i];
}
}
这个是ASII字库,网上能找到太多了。
const unsigned char F6X8[] =
{
0x00, 0x00, 0x00, 0x00, 0x00, 0x00 ,
0x00, 0x00, 0x00, 0x2f, 0x00, 0x00 ,
0x00, 0x00, 0x07, 0x00, 0x07, 0x00 ,
0x00, 0x14, 0x7f, 0x14, 0x7f, 0x14 ,
0x00, 0x24, 0x2a, 0x7f, 0x2a, 0x12 ,
0x00, 0x62, 0x64, 0x08, 0x13, 0x23 ,
0x00, 0x36, 0x49, 0x55, 0x22, 0x50 ,
0x00, 0x00, 0x05, 0x03, 0x00, 0x00 ,
0x00, 0x00, 0x1c, 0x22, 0x41, 0x00 ,
0x00, 0x00, 0x41, 0x22, 0x1c, 0x00 ,
0x00, 0x14, 0x08, 0x3E, 0x08, 0x14 ,
0x00, 0x08, 0x08, 0x3E, 0x08, 0x08 ,
0x00, 0x00, 0x00, 0xA0, 0x60, 0x00 ,
0x00, 0x08, 0x08, 0x08, 0x08, 0x08 ,
0x00, 0x00, 0x60, 0x60, 0x00, 0x00 ,
0x00, 0x20, 0x10, 0x08, 0x04, 0x02 ,
0x00, 0x3E, 0x51, 0x49, 0x45, 0x3E ,
0x00, 0x00, 0x42, 0x7F, 0x40, 0x00 ,
0x00, 0x42, 0x61, 0x51, 0x49, 0x46 ,
0x00, 0x21, 0x41, 0x45, 0x4B, 0x31 ,
0x00, 0x18, 0x14, 0x12, 0x7F, 0x10 ,
0x00, 0x27, 0x45, 0x45, 0x45, 0x39 ,
0x00, 0x3C, 0x4A, 0x49, 0x49, 0x30 ,
0x00, 0x01, 0x71, 0x09, 0x05, 0x03 ,
0x00, 0x36, 0x49, 0x49, 0x49, 0x36 ,
0x00, 0x06, 0x49, 0x49, 0x29, 0x1E ,
0x00, 0x00, 0x36, 0x36, 0x00, 0x00 ,
0x00, 0x00, 0x56, 0x36, 0x00, 0x00 ,
0x00, 0x08, 0x14, 0x22, 0x41, 0x00 ,
0x00, 0x14, 0x14, 0x14, 0x14, 0x14 ,
0x00, 0x00, 0x41, 0x22, 0x14, 0x08 ,
0x00, 0x02, 0x01, 0x51, 0x09, 0x06 ,
0x00, 0x32, 0x49, 0x59, 0x51, 0x3E ,
0x00, 0x7C, 0x12, 0x11, 0x12, 0x7C ,
0x00, 0x7F, 0x49, 0x49, 0x49, 0x36 ,
0x00, 0x3E, 0x41, 0x41, 0x41, 0x22 ,
0x00, 0x7F, 0x41, 0x41, 0x22, 0x1C ,
0x00, 0x7F, 0x49, 0x49, 0x49, 0x41 ,
0x00, 0x7F, 0x09, 0x09, 0x09, 0x01 ,
0x00, 0x3E, 0x41, 0x49, 0x49, 0x7A ,
0x00, 0x7F, 0x08, 0x08, 0x08, 0x7F ,
0x00, 0x00, 0x41, 0x7F, 0x41, 0x00 ,
0x00, 0x20, 0x40, 0x41, 0x3F, 0x01 ,
0x00, 0x7F, 0x08, 0x14, 0x22, 0x41 ,
0x00, 0x7F, 0x40, 0x40, 0x40, 0x40 ,
0x00, 0x7F, 0x02, 0x0C, 0x02, 0x7F ,
0x00, 0x7F, 0x04, 0x08, 0x10, 0x7F ,
0x00, 0x3E, 0x41, 0x41, 0x41, 0x3E ,
0x00, 0x7F, 0x09, 0x09, 0x09, 0x06 ,
0x00, 0x3E, 0x41, 0x51, 0x21, 0x5E ,
0x00, 0x7F, 0x09, 0x19, 0x29, 0x46 ,
0x00, 0x46, 0x49, 0x49, 0x49, 0x31 ,
0x00, 0x01, 0x01, 0x7F, 0x01, 0x01 ,
0x00, 0x3F, 0x40, 0x40, 0x40, 0x3F ,
0x00, 0x1F, 0x20, 0x40, 0x20, 0x1F ,
0x00, 0x3F, 0x40, 0x38, 0x40, 0x3F ,
0x00, 0x63, 0x14, 0x08, 0x14, 0x63 ,
0x00, 0x07, 0x08, 0x70, 0x08, 0x07 ,
0x00, 0x61, 0x51, 0x49, 0x45, 0x43 ,
0x00, 0x00, 0x7F, 0x41, 0x41, 0x00 ,
0x00, 0x55, 0x2A, 0x55, 0x2A, 0x55 ,
0x00, 0x00, 0x41, 0x41, 0x7F, 0x00 ,
0x00, 0x04, 0x02, 0x01, 0x02, 0x04 ,
0x00, 0x40, 0x40, 0x40, 0x40, 0x40 ,
0x00, 0x00, 0x01, 0x02, 0x04, 0x00 ,
0x00, 0x20, 0x54, 0x54, 0x54, 0x78 ,
0x00, 0x7F, 0x48, 0x44, 0x44, 0x38 ,
0x00, 0x38, 0x44, 0x44, 0x44, 0x20 ,
0x00, 0x38, 0x44, 0x44, 0x48, 0x7F ,
0x00, 0x38, 0x54, 0x54, 0x54, 0x18 ,
0x00, 0x08, 0x7E, 0x09, 0x01, 0x02 ,
0x00, 0x18, 0xA4, 0xA4, 0xA4, 0x7C ,
0x00, 0x7F, 0x08, 0x04, 0x04, 0x78 ,
0x00, 0x00, 0x44, 0x7D, 0x40, 0x00 ,
0x00, 0x40, 0x80, 0x84, 0x7D, 0x00 ,
0x00, 0x7F, 0x10, 0x28, 0x44, 0x00 ,
0x00, 0x00, 0x41, 0x7F, 0x40, 0x00 ,
0x00, 0x7C, 0x04, 0x18, 0x04, 0x78 ,
0x00, 0x7C, 0x08, 0x04, 0x04, 0x78 ,
0x00, 0x38, 0x44, 0x44, 0x44, 0x38 ,
0x00, 0xFC, 0x24, 0x24, 0x24, 0x18 ,
0x00, 0x18, 0x24, 0x24, 0x18, 0xFC ,
0x00, 0x7C, 0x08, 0x04, 0x04, 0x08 ,
0x00, 0x48, 0x54, 0x54, 0x54, 0x20 ,
0x00, 0x04, 0x3F, 0x44, 0x40, 0x20 ,
0x00, 0x3C, 0x40, 0x40, 0x20, 0x7C ,
0x00, 0x1C, 0x20, 0x40, 0x20, 0x1C ,
0x00, 0x3C, 0x40, 0x30, 0x40, 0x3C ,
0x00, 0x44, 0x28, 0x10, 0x28, 0x44 ,
0x00, 0x1C, 0xA0, 0xA0, 0xA0, 0x7C ,
0x00, 0x44, 0x64, 0x54, 0x4C, 0x44 ,
0x14, 0x14, 0x14, 0x14, 0x14, 0x14
};
这个是用到的延时初始化函数和ms级延时函数
inline void Wait_Init(void)
{
SysTick->CTRL &= (unsigned int)(~(1<<0));
SysTick->CTRL &= (unsigned int)(~(1<<2));
SysTick->CTRL &= (unsigned int)(~(1<<1));
}
void Wait_ms(unsigned int t)
{
#define ms_t 9000
SysTick->LOAD = ms_t;
SysTick->VAL = 0;
SysTick->CTRL |= 1<<0;
while(t)
{
if(SysTick->CTRL & 1<<16)
{t--;}
}
}
下面是主函数 注意:前面用到的子函数请做好声明
unsigned char OLED_SRAM[8][128];
extern const unsigned char F6X8[];
int main()
{
Wait_Init();
SPI1_Init();
OLED_Init();
OLED_Write('L',0,0);
OLED_Write('i',0,6);
OLED_Write('a',0,12);
OLED_Write('n',0,18);
OLED_Write('g',0,24);
while(1)
{
}
}
结果
; ; ; ;
如果有问题欢迎指正和讨论
|