芯片:STM32f103c8t6
f103型号大同小异,其他芯片请根据情况修改接口和配置
使用的是7针的0.96寸OLED屏幕,黑白两色显示
以下是实物图
首先你要了解STM32上的AFIO(复用功能),DMA,SPI 和OLED上的SSD1306驱动的原理和命令,还有C语言的指针,如果其中任何一项不熟悉的话,请先学习一遍
相关资料:
DMA原理
SPI的基本原理(库函数版)
学习笔记 端口复用&重映射
一文彻底了解SSD1306驱动0.96寸OLED
通过学习后相信你会更好明白这篇文章以及对STM32的了解
DMA每次会将8bit的数据从SRAM中传输到SPI的缓存区中
SPI启用DMA功能后会自动响应DMA传输完成的请求,将8bit数据写入发送缓存区,此款屏幕总共有128 * 64个像素,所以要传输128*64/8=1024次
当发送寄存器为空时,发送缓存区会将将数据写入到发送寄存器
当8bit数据写满时,发送寄存器通过MOSI口输出数据
OLED屏幕有三种刷新方式分别为页地址模式, 水平地址模式和垂直地址模式,。
水平地址模式和垂直地址模式可以在一页(一列)写完后自动换页(列)
所以在水平地址寻址或者垂直地址寻址模式下,只要源源不断的发送数据即可
水平地址模式
垂直地址模式
参考资料
https://blog.csdn.net/gengyuchao/article/details/86608037
SRAM->DMA传输->SPI响应DMA请求->写入缓存区->写入寄存器->自动写入发送寄存器->发送数据->OLED接收数据并写入显存->显示
这个过程是不需要STM32响应中断的,全程走DMA
缺点是需要一次性写入整个OLED_SRAM
但是DMA和硬件SPI拥有很快的传输速度,所以这个缺点也被克服了
经过测试,帧率有大约30帧左右
1.GND 电源地
2.VCC 电源正(3~5.5V)
3.D0(SCL) SCK管脚
4.D1(SDA) MOSI管脚
5.RES(RST) 用来复位(低电平复位)
6.DC(D/C) 数据和命令控制管脚 1表示数据 0表示命令
7.CS(NSS) 片选管脚
通过查阅STM32C8T6的原理图(其他型号芯片可以看自己的原理图),可以发现有两个SPI
我用的是SPI1
因为在SPI启用了主机信号由软件控制,并且4线SPI的0.96寸OLED模块没有发送数据的针脚
所以将PA4作为RST功能使用,将PA6作为DC功能使用
#ifndef __oled_spi_dma_H
#define __oled_spi_dma_H
#include "sys.h"
#define S6X8 0
#define S8X16 1
#define OLED_SCL_CLR() GPIO_ResetBits(GPIOA,GPIO_Pin_5) //时钟
#define OLED_SCL_SET() GPIO_SetBits(GPIOA,GPIO_Pin_5)
#define OLED_SDA_LOW() GPIO_ResetBits(GPIOA,GPIO_Pin_7) //MOSI主设备输出
#define OLED_SDA_HIGH() GPIO_SetBits(GPIOA,GPIO_Pin_7)
#define OLED_RST_OFF() GPIO_ResetBits(GPIOA,GPIO_Pin_4) //接低电平复位
#define OLED_RST_ON() GPIO_SetBits(GPIOA,GPIO_Pin_4)
#define OLED_DC_CMD() GPIO_ResetBits(GPIOA,GPIO_Pin_6) //模式
#define OLED_DC_DAT() GPIO_SetBits(GPIOA,GPIO_Pin_6)
void GPIO_Configuration(void); //GPIO和SPI初始化
void OLED_Write(u8 ye,u8 lie,u8* ascii,u8 size); //写入ASCII文字
void OLED_SendCmd(u8 TxData); //发送命令
void OLED_Init(void); //OLED初始化
void DMA_OLED_Init(void); //DMA初始化
#endif
1.单线只发送或者双线全双工
2.主机模式
3.一次发送8位数据
4.空闲时间为低电平时
5.第一个上升沿采样
6.主机片选信号(CS)由软件控制
7.预分频 256
8.数据传输从 MSB 高位开始 低位为LSB
9.CRC 值计算的多项式设置为大于1即可
10.SPI1->CR2=1 << 1; //允许DMA往缓冲区内发送
(这是最重要的一步即启用SPI的DMA响应功能)
以下是代码
void GPIO_Configuration(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
SPI_InitTypeDef SPI_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO | RCC_APB2Periph_GPIOA | RCC_APB2Periph_SPI1 , ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5|GPIO_Pin_7;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//速度50MHz
GPIO_Init(GPIOA, &GPIO_InitStructure); //初始化GPIOA5,7
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4|GPIO_Pin_6;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//速度50MHz
GPIO_Init(GPIOA, &GPIO_InitStructure); //初始化GPIOA4,6
GPIO_SetBits(GPIOA,GPIO_Pin_5|GPIO_Pin_7); //PA5/PA7上拉
SPI_InitStructure.SPI_Direction = SPI_Direction_1Line_Tx; //设置 SPI 单线只发送
SPI_InitStructure.SPI_Mode = SPI_Mode_Master; //主 SPI
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; // SPI 发送接收 8 位帧结构
SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;//串行同步时钟的空闲状态为低电平
SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;//第1个跳变沿数据被采样
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; //NSS 主机片选信号(CS)由软件控制
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256; //预分频 256
//SPI 速度设置函数(调整传输速度快慢 只有4个分频可选)
//SPI_BaudRatePrescaler_2 2 分频 (SPI 36M@sys 72M)
//SPI_BaudRatePrescaler_8 8 分频 (SPI 9M@sys 72M)
//SPI_BaudRatePrescaler_16 16 分频 (SPI 4.5M@sys 72M)
//SPI_BaudRatePrescaler_256 256 分频 (SPI 281.25K@sys 72M)
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; //数据传输从 MSB 高位开始 低位为LSB
SPI_InitStructure.SPI_CRCPolynomial = 7; //CRC 值计算的多项式
SPI_Init(SPI1, &SPI_InitStructure); //根据指定的参数初始化外设 SPIx 寄存器
SPI1->CR2=1 << 1; //允许DMA往缓冲区内发送
SPI_Cmd(SPI1, ENABLE); //使能 SPI 外设
} ;
通过查阅STM32参考手册9.3.3
数据从SRAM传输到SPI1的缓存区
SPI1_TX在DMA1的CH3通道
1.外设基地址为缓存区 即&SPI1-DR 并且强制转换成u32类型
2.内存基地址为SRAM 即OLED_SRAM 并且强制转换成u32类型
3.方向是从储存器读取发送到外设
4.通道缓存的大小 一个画面要传输1024次
5.外设地址偏移不变,因为缓存区的地址不变
6.内存地址要递增,因为OLED_SRAM是个数组,数据存在里面,指针地址要递增
7.传输数据大小为8bit
8.内存数据大小也为8bit
9.工作在循环模式(此模式不需要中断,次数结束后会自动重载为1024次)
10.不是非内存到内存的传输
代码如下
void DMA_OLED_Init(void)
{
DMA_InitTypeDef DMA_InitStructure;
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); //使能 DMA 时钟
DMA_DeInit(DMA1_Channel3);
DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)&SPI1->DR; //DMA 外设 ADC 基地址
DMA_InitStructure.DMA_MemoryBaseAddr = (u32)OLED_SRAM; //DMA 内存基地址
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST; //从储存器读取发送到外设
DMA_InitStructure.DMA_BufferSize = 1024; //DMA 通道的 DMA 缓存的大小
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//外设地址不变
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //内存地址递增
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; //8 位
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; // 8 位
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; //工作在循环传输模式
DMA_InitStructure.DMA_Priority = DMA_Priority_Medium; //DMA 通道 x 拥有中优先级
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; //非内存到内存传输
DMA_Init(DMA1_Channel3, &DMA_InitStructure); //根据指定的参数初始化
//DMA_Cmd(DMA1_Channel3, DISABLE); //不使能DMA1 CH3所指示的通道
DMA_Cmd(DMA1_Channel3, ENABLE); //使能DMA1 CH3所指示的通道
}
DC脚低电平时写入命令,高电平时写入数据
SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE)函数获取缓存区是否为空
SPI_I2S_SendData(SPI1, TxData)函数发送发送寄存器中的数据
(这两个函数是官方标准库中自带的
可以在stm32f10x_spi.c和stm32f10x_spi.h找到使用方法)
void OLED_SendCmd(u8 TxData) //发送命令
{
u8 retry=0;
OLED_DC_CMD(); //命令模式
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET) //检查指定的 SPI标志位设置与否:发送缓存空标志位
{
retry++;
if(retry>200) return ;
}
delay_ms(100);
SPI_I2S_SendData(SPI1, TxData); //通过外设 SPIx 发送一个数据
retry=0;
OLED_DC_DAT(); //数据模式
}
关于OLED初始化
以下是需要注意的
void OLED_Init(void) //初始化函数
{
GPIO_Configuration();//端口初始化
delay_ms(1000);
OLED_RST_OFF();
delay_ms(100);
OLED_RST_ON();
OLED_SendCmd(0xae);//关闭显示
OLED_SendCmd(0xd5);//设置时钟分频因子,震荡频率
OLED_SendCmd(0x80);//[3:0],分频因子;[7:4],震荡频率
OLED_SendCmd(0x81);//设置对比度
OLED_SendCmd(0x7f);//128
OLED_SendCmd(0x8d);//设置电荷泵开关
OLED_SendCmd(0x14);//开
OLED_SendCmd(0x20);//设置模式
OLED_SendCmd(0x00);//设置为水平地址模式
OLED_SendCmd(0x21);//设置列地址的起始和结束的位置
OLED_SendCmd(0x00);//0
OLED_SendCmd(0x7f);//127
OLED_SendCmd(0x22);//设置页地址的起始和结束的位置
OLED_SendCmd(0x00);//0
OLED_SendCmd(0x07);//7
OLED_SendCmd(0xc9);//0xc9上下反置 0xc8正常
OLED_SendCmd(0xa1);//0xa0左右反置 0xa1正常
OLED_SendCmd(0xa4);//全局显示开启;0xa4正常,0xa5无视命令点亮全屏
OLED_SendCmd(0xa6);//设置显示方式;bit0:1,反相显示;0,正常显示
OLED_SendCmd(0xaf);//开启显示
OLED_SendCmd(0x56);
DMA_OLED_Init();//DMA初始化
}
通过之前的配置,STM32已经能够写入OLED的显存啦!
因为此款OLED没有自带字库,所以在STM32中写入字库
const u8 F6X8[] =
{
0x00, 0x00, 0x00, 0x00, 0x00, 0x00 , // sp
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 , // 0
0x00, 0x00, 0x42, 0x7F, 0x40, 0x00 , // 1
0x00, 0x42, 0x61, 0x51, 0x49, 0x46 , // 2
0x00, 0x21, 0x41, 0x45, 0x4B, 0x31 , // 3
0x00, 0x18, 0x14, 0x12, 0x7F, 0x10 , // 4
0x00, 0x27, 0x45, 0x45, 0x45, 0x39 , // 5
0x00, 0x3C, 0x4A, 0x49, 0x49, 0x30 , // 6
0x00, 0x01, 0x71, 0x09, 0x05, 0x03 , // 7
0x00, 0x36, 0x49, 0x49, 0x49, 0x36 , // 8
0x00, 0x06, 0x49, 0x49, 0x29, 0x1E , // 9
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 , // A
0x00, 0x7F, 0x49, 0x49, 0x49, 0x36 , // B
0x00, 0x3E, 0x41, 0x41, 0x41, 0x22 , // C
0x00, 0x7F, 0x41, 0x41, 0x22, 0x1C , // D
0x00, 0x7F, 0x49, 0x49, 0x49, 0x41 , // E
0x00, 0x7F, 0x09, 0x09, 0x09, 0x01 , // F
0x00, 0x3E, 0x41, 0x49, 0x49, 0x7A , // G
0x00, 0x7F, 0x08, 0x08, 0x08, 0x7F , // H
0x00, 0x00, 0x41, 0x7F, 0x41, 0x00 , // I
0x00, 0x20, 0x40, 0x41, 0x3F, 0x01 , // J
0x00, 0x7F, 0x08, 0x14, 0x22, 0x41 , // K
0x00, 0x7F, 0x40, 0x40, 0x40, 0x40 , // L
0x00, 0x7F, 0x02, 0x0C, 0x02, 0x7F , // M
0x00, 0x7F, 0x04, 0x08, 0x10, 0x7F , // N
0x00, 0x3E, 0x41, 0x41, 0x41, 0x3E , // O
0x00, 0x7F, 0x09, 0x09, 0x09, 0x06 , // P
0x00, 0x3E, 0x41, 0x51, 0x21, 0x5E , // Q
0x00, 0x7F, 0x09, 0x19, 0x29, 0x46 , // R
0x00, 0x46, 0x49, 0x49, 0x49, 0x31 , // S
0x00, 0x01, 0x01, 0x7F, 0x01, 0x01 , // T
0x00, 0x3F, 0x40, 0x40, 0x40, 0x3F , // U
0x00, 0x1F, 0x20, 0x40, 0x20, 0x1F , // V
0x00, 0x3F, 0x40, 0x38, 0x40, 0x3F , // W
0x00, 0x63, 0x14, 0x08, 0x14, 0x63 , // X
0x00, 0x07, 0x08, 0x70, 0x08, 0x07 , // Y
0x00, 0x61, 0x51, 0x49, 0x45, 0x43 , // Z
0x00, 0x00, 0x7F, 0x41, 0x41, 0x00 , // [
0x00, 0x55, 0x2A, 0x55, 0x2A, 0x55 , // 55
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 , // a
0x00, 0x7F, 0x48, 0x44, 0x44, 0x38 , // b
0x00, 0x38, 0x44, 0x44, 0x44, 0x20 , // c
0x00, 0x38, 0x44, 0x44, 0x48, 0x7F , // d
0x00, 0x38, 0x54, 0x54, 0x54, 0x18 , // e
0x00, 0x08, 0x7E, 0x09, 0x01, 0x02 , // f
0x00, 0x18, 0xA4, 0xA4, 0xA4, 0x7C , // g
0x00, 0x7F, 0x08, 0x04, 0x04, 0x78 , // h
0x00, 0x00, 0x44, 0x7D, 0x40, 0x00 , // i
0x00, 0x40, 0x80, 0x84, 0x7D, 0x00 , // j
0x00, 0x7F, 0x10, 0x28, 0x44, 0x00 , // k
0x00, 0x00, 0x41, 0x7F, 0x40, 0x00 , // l
0x00, 0x7C, 0x04, 0x18, 0x04, 0x78 , // m
0x00, 0x7C, 0x08, 0x04, 0x04, 0x78 , // n
0x00, 0x38, 0x44, 0x44, 0x44, 0x38 , // o
0x00, 0xFC, 0x24, 0x24, 0x24, 0x18 , // p
0x00, 0x18, 0x24, 0x24, 0x18, 0xFC , // q
0x00, 0x7C, 0x08, 0x04, 0x04, 0x08 , // r
0x00, 0x48, 0x54, 0x54, 0x54, 0x20 , // s
0x00, 0x04, 0x3F, 0x44, 0x40, 0x20 , // t
0x00, 0x3C, 0x40, 0x40, 0x20, 0x7C , // u
0x00, 0x1C, 0x20, 0x40, 0x20, 0x1C , // v
0x00, 0x3C, 0x40, 0x30, 0x40, 0x3C , // w
0x00, 0x44, 0x28, 0x10, 0x28, 0x44 , // x
0x00, 0x1C, 0xA0, 0xA0, 0xA0, 0x7C , // y
0x00, 0x44, 0x64, 0x54, 0x4C, 0x44 , // z
0x14, 0x14, 0x14, 0x14, 0x14, 0x14 // horiz lines
};
下面是我自己写的OLED_Write的函数,调用即可
OLED_Write(页,列,文字,大小) 暂时只支持S6X8,S8X16大小暂不可用
void OLED_Write(u8 ye,u8 lie,u8* ascii,u8 size)
{
u8 i=0,j=0,c=*ascii;
if (size==S6X8)
{
for (i=0;i<6;i++)
{
OLED_SRAM[ye][lie+i]=F6X8[(c-32)*6+1+i];
}
}
else if(size==S8X16)
{
for (j=0;j<2;j++)
{
for (i=0;i<8;i++)
{
OLED_SRAM[ye+j][lie+i]=F8X16[(c-32)*8+1+i];
}
}
}
在[0,1]的位置显示A
在[1,1]的位置显示B
在[3,50]的位置显示C
#include "oled_spi_dma.h"
#include "delay.h"
int main()
{
uint8_t OLED_SRAM[8][128]; //图像储存在SRAM里
delay_init(); //delay函数不初始化要出大问题!!!!!!!!!
OLED_Init();
OLED_Write(0,1,"A",0);
OLED_Write(2,1,"B",0);
OLED_Write(3,50,"C",0);
}