Mini2440裸机开发之SPI(OLED SSD1306)
在通信协议-SPI小节,我们已经对SPI协议进行了详细的介绍,这里就不在重复赘述。
一、S3C2440上的SPI
1.1 SPI概述
SPI的使用位于S3C2440芯片手册的第23章。S3C2440包含了2个SPI,每个SPI都有2个分别分别用于发送和接收的8位串行移位寄存器。
一个SPI时钟周期,同时发送(串行移除)和接收(串行移入)一位数据,由相应控制寄存器设置指定8位串行数据的输出频率。
如果只希望发送数据,则接收数据可以保持伪位;如果只希望接收数据,则需要发送伪位'1'数据。
S3C2440 SPI特性:
- 支持2个通道SPI;
- 兼容SPI协议(2.11版本);
- 8位发送串行移位寄存器;
- 8位接收串行移位寄存器;
- 8位预分频逻辑;
- 查询、中断和DMA传输模式;
1.2 SPI方块图
S3C2440 SPI相关引脚定义:
SPI | SCLK | MOSI | MISO | SS |
SPI0 | GPE13 | GPE12 | GPE11 | GPG2 |
SPI1 | GPG7 | GPG6 | GPG5 | GPG3 |
二、SPI相关寄存器
2.1 SPI控制寄存器SPCONn
寄存器 | 地址 | R/W | 描述 | 复位值 |
SPCON0 | 0x59000000 | R/W | SPI通道0控制寄存器 | 0x00 |
SPCON1 | 0x59000020 | R/W | SPI通道1控制寄存器 | 0x00 |
寄存器位信息:
SPICONn | 位 | 描述 | 初始状态 |
SMOD | [6:5] |
SPI模式选择,决定如何读/写SPTDAT 00:查询模式 01:中断模式 10:DMA模式 11:保留 |
00 |
ENSCK | [4] |
SCK使能 0:禁止 1:使能 |
0 |
MSTR | [3] |
主/从机选择 0:从机 1:主机 |
0 |
CPOL | [2] |
时钟极性选择,决定时钟是高电平有效还是低电平有效 0:高电平有效 1:低电平有效 |
0 |
CPHA | [1] |
时钟相位选择,和CPOL一起决定采样时刻 0:第一个边沿 1:第二个边沿 |
0 |
TAGD | [0] |
仅接收模式控制 0:正常收发 1:仅接收(此时自动发送任意数据) 在正常模式,如果只想接收数据,需要发送0xFF |
0 |
2.2 SPI状态寄存器SPSTAn
寄存器 | 地址 | R/W | 描述 | 复位值 |
SPSTA0 | 0x59000004 | R | SPI通道0状态寄存器 | 0x01 |
SPSTA1 | 0x59000024 | R | SPI通道1状态寄存器 | 0x01 |
寄存器位信息:
SPSTAn | 位 | 描述 | 初始状态 |
保留 | [7:3] |
保留 |
- |
DCOL | [2] |
数据冲突错误标志。如果当传输正在进行中写了SPTDATn或读了SPRDATn此标志置位,并且可以通过读取SPSTAn清除 0:无错误 1:发生冲突错误 |
0 |
MULF | [1] |
多主SPI错误标志 0:无错误 1:多主SPI错误 |
0 |
REDY | [0] |
收发就绪标志,此位表示SPTDATn或SPRDATn准备好了发送或者接收 0:未就绪 1:Tx/Rx就绪 |
1 |
2.3 SPI引脚控制寄存器SPPINn
寄存器 | 地址 | R/W | 描述 | 复位值 |
SPPIN0 | 0x59000008 | R/W | SPI通道0引脚控制寄存器 | 0x02 |
SPPIN1 | 0x59000028 | R/W | SPI通道1引脚控制寄存器 | 0x02 |
寄存器位信息:
SPPINn | 位 | 描述 | 初始状态 |
保留 | [7:3] |
保留 |
- |
ENMUL | [2] |
多主机错误检测使能。当SPI系统为主机时,nSS引脚被用来作为输入检测多主机错误 0:禁止 1:使能 |
|
保留 | [1] |
保留 |
|
KEEP | [0] |
决定当1字节发送完成后MOSI的控制或释放(主机) 0:释放 1:保持之前的电平 |
2.4 SPI波特率预分频寄存器SPPREn
寄存器 | 地址 | R/W | 描述 | 复位值 |
SPPRE0 | 0x5900000C | R/W | SPI通道0波特率预分频寄存器 | 0x00 |
SPPRE1 | 0x5900002C | R/W | SPI通道1波特率预分频寄存器 | 0x00 |
寄存器位信息:
SPPREn | 位 | 描述 | 初始状态 |
保留 | [7:0] |
决定SPI时钟频率 波特率=PCLK/2/(预分频值+1) |
0xxx |
波特率应低于25MHz。
2.5 SPI发送数据寄存器SPTDATn
寄存器 | 地址 | R/W | 描述 | 复位值 |
SPTDAT0 |
0x59000010 | R/W | SPI通道0发送数据寄存器 | 0x00 |
SPTDAT1 | 0x59000030 | R/W | SPI通道1发送数据寄存器 | 0x00 |
寄存器位信息:
SPTDATn | 位 | 描述 | 初始状态 |
保留 | [7:0] |
包含通过SPI通道要发送的数据 |
0x00 |
2.6 SPI接收数据寄存器SPRDATn
寄存器 | 地址 | R/W | 描述 | 复位值 |
SPRDAT0 |
0x59000014 | R/W | SPI通道0接收数据寄存器 | 0xFF |
SPRDAT1 | 0x59000034 | R/W | SPI通道1接收数据寄存器 | 0xFF |
寄存器位信息:
SPRDATn | 位 | 描述 | 初始状态 |
保留 | [7:0] |
包含通过SPI通道接收到的数据 |
0xFF |
三、读写操作流程
3.1 初始化
3.1.1 IO引脚设置
设置SPIn引脚复用,这里我们以SPI通道1相关引脚为例:
GPGCON &= ~((3<<6) | (3<<10) | (3<<12) | (3 <<14)); /* 清零 */ GPGCON |= ((3<<6) | (3<<10) | (3 <<12) | (3 <<14)); /* 设置为SPI */
3.1.2 设置波特率预分频寄存器(SPPRE1)
由于我们PCLK=50HMz,因此当设置SPPRE1为4时,频率为
SPPRE1 = 4;
注意:设置波特率,需要根据威慑所能接收的范围来设置,比如查阅OLED的芯片手册得知其时钟最小值为100ns,即最大为10MHz。
3.1.3 配置SPCON1
配置查询模式;位[6:5]=00;
配置SCK输出使能;位[4]=1;
配置时钟极性、时钟相位;根据外接SPI设备来设置时钟极性,以及时钟相位,这里我们设置位[3:2]=00;
配置主机模式;位[1]=1;
配置正常收发模式;位[0]=0;
SPCON1 = 0<<5 | 1<<4 | 1<<3 | 0<<2 | 0<< 1 | 0<<0;
初始化代码如下,这里代码包含了通过SPI控制器实现SPI通信,以及通过GPIO模拟SPI通信两种方式:
/************************************************************* * * Function : spi0初始化,GPG7 ~ SCLK GPG6 ~ MOSI GPG5 ~ MISO GPG3 ~ SS * **************************************************************/ void spi_init() { #ifdef GPIO_SPI /* IO配置 */ GPGCON &= ~((3<<6) | (3<<10) | (3<<12) | (3 <<14)); /* 清零 */ GPGCON |= ((1<<6) | (0<<10) | (1<<12) | (1 <<14)); /* 设置GPG5输入 GPG3、6、7为输出 */ #else /* 1. IO配置 */ GPGCON &= ~((3<<6) | (3<<10) | (3<<12) | (3 <<14)); /* 清零 */ GPGCON |= ((1<<6) | (3<<10) | (3 <<12) | (3 <<14)); /* 设置GPG5、6、7为SPI、GPG3为输出 */ /* 2. 设置波特率预分频寄存器 */ SPPRE1 = 4; /* 3. 配置SPI 查询模式 SCK输出使能 时钟极性高电平有效 时钟相位第一个边沿 主机模式 正常收发 */ SPCON1 = (0<<5) | (1<<4) | (1<<3) | (0<<2) | (0<< 1) | (0<<0); #endif GPGDAT |= (1<<3) ; /* 取消片选 */ }
需要注意的是,当采用SPI控制器实现SPI通信时,片选引脚GPG3是需要自己控制片选/取消片选的,这里经过测试发现如果GPG3引脚复用成nSS1是没有效果的。
3.2 发送数据
检查状态寄存器SPSTA1发送就绪标志位(REDY=1),并接着写数据到SPTDAT1;
/************************************************************* * * Function : 主设备发送一个字节 * Input : data 字节数据 * **************************************************************/ void spi_write_byte(u8 data) { int i; /* 选中 */ spi_set_cs(0); #ifdef GPIO_SPI for (i = 0; i < 8; i++){ spi_set_clk(0); spi_set_mosi(data & 0x80); /* 上升沿采样 输出最高位 */ spi_set_clk(1); data <<= 1; } #else /* 等待发送或接收 ready */ while (!(SPSTA1 & 1)); SPTDAT1 = data; #endif /* 取消选中 */ spi_set_cs(1); }
GPIO模拟相关位操作代码:
/************************************************************* * * Function : 片选信号 GPG3引脚 * Input : val 0 选中 1 未选中 * **************************************************************/ void spi_set_cs(char val) { if (val) /* 1 未选中 */ GPGDAT |= (1<<3); else GPGDAT &= ~(1<<3); /* 0 选中 */ } /************************************************************* * * Function : GPIO模拟SPI时 设置时钟信号 * Input : val 0 低电平 1 高电平 * **************************************************************/ void spi_set_clk(u8 val){ if (val) /*1 高电平 */ GPGDAT |= (1<<7); else /*0 低电平 */ GPGDAT &= ~(1<<7); } /************************************************************* * * Function : GPIO模拟SPI时 设置MOSI * Input : val 0 低电平 1 高电平 * **************************************************************/ void spi_set_mosi(u8 val){ if (val) /*1 高电平 */ GPGDAT |= (1<<6); else /*0 低电平 */ GPGDAT &= ~(1<<6); }
3.3 接收数据
正常收发方式(同时收发,TAGD=0):向数据发送寄存器SPTDAT1写0xFF,查询并确认 REDY为1,然后从数据接收寄存器中读取数据;
/************************************************************* * * Function : 主设备接收一个字节 * Input : data 字节数据 * **************************************************************/ u8 spi_read_byte() { u8 val = 0; /* 选中 */ spi_set_cs(0); #ifdef GPIO_SPI /* todo 未实现*/ #else /* 向数据发送寄存器SPTDAT1写0xFF */ SPTDAT1 = 0xff; /* 等待发送或接收 ready */ while (!(SPSTA1 & 1)); val = SPRDAT1; #endif /* 取消选中 */ spi_set_cs(1); }
仅接收方式(TAGD=1):并确认REDY为1,然后从数据接收寄存器中读取数据。读取数据的同时启动一次发送。
四、OLED128x64(SSD1306)
由于Mini2440开发板并没有外接SPI设备,因此我们只能通过开发板引脚外接SPI设备。
这里我们外接一款支持SPI通信的OLED,显示屏尺寸为0.96寸、OLED屏幕内部驱动IC为SSD1306,像素为128*64。
OLED 有机发光二极管,相比于LCD区别在于LCD需要背光,而OLED不需要,因为它是自发光的。
4.1 SSD1606介绍
SD1306是一款带控制器的用于OLED点阵图形显示系统的单片CMOS OLED/PLED驱动器。它由128个SEG(列输出)和64个COM(行输出)组成。该芯片专为共阴极OLED面板设计。
SSD1306内置对比度控制器、显示RAM(GDDRAM)和振荡器,以此减少了外部元件的数量和功耗。
该芯片有256级亮度控制,数据或命令由通用微控制器通过硬件选择的6800/8000系通用并行接口、I2C接口或串行外围接口发送。
该芯片适用于许多小型便携式应用,如手机副显示屏、MP3播放器和计算器等。
使用该芯片可通过硬件电阻连接选中使用三线SPI、四线SPI或IIC,如下图所示:
至于为什么,我们可以在芯片datasheet上找到答案,SSD1306通过BS[2:0]引脚选择通信协议:
当选择不同的通信协议时,其数据引脚和控制引脚也略有差异:
4.2 引脚说明
当SSD1306选定4-wire serial interface接口方式,SPI引脚定义:
- CS:片选信号;连接S3C2440的GPG3引脚;
- DC::命令数据选择引脚;连接S3C2440的GPG10引脚;
- RES:模块复位引脚,低电平有效;连接S3C2440的GPG9引脚;
- D1:MOSI,SPI数据线,主设备输出从设备输入引脚;连接S3C2440的GPG6引脚;
- D0:SCLK,SPI时钟线;连接S3C2440的GPG7引脚;
- VCC:电源正极3.3~5V;
- GND:电源地;
我们需要按照Mini2440的电路原理图去将oled引脚和开发板GPIO引脚连接起来。
4.3 显示原理
SSD1306使用GDDRAM保存要显示的数据,GDDRAM是未映射静态RAM。RAM的大小为128 x 64位,RAM分为8页,从第0页到第7页,用于单色128x64点阵显示。
每个点(像素)使用1位来表示,为1则亮,为0则灭。
当一个数据字节写入GDDRAM时,列被填充(即,由列地址指针指示的整列(8位)被填充)。数据位D0被写入顶行,而数据位D7被写入底行。
4.4 GDDRAM寻址模式
GDDRAM有三种寻址模式,页寻址,水平寻址,垂直寻址。一般我们不修改其寻址模式,使用默认的页寻址,但当有数据更合适其它寻址方式时,可以更换寻址方式。
4.4.1 页寻址模式
页寻址模式是器件默认选择的GDDRAM寻址模式,通过“20H,02H”命令可以设置寻址模式为页寻址。
在页寻址模式下,寻址只在一页(PAGEn)内进行,地址指针不会跳到其它页。每次向GDDRAM写入1字节显示数据后,列指针会自动+1。
当128列都寻址完之后,列指针会重新指向SEG0而页指针仍然保持不变。通过页寻址模式我们可以方便地对一个小区域内数据进行修改。
4.4.2 水平寻址
水平寻址模式可以通过指令“20H,00H”来设置。
水平寻址模式下,每次向GDDRAM写入1字节数据后,列地址指针自动+1。
列指针到达结束列之后会被重置到起始列,而页指针将会+1。
页地址指针达到结束页之后,将会自动重置到起始页。水平寻址模式适用于大面积数据写入,例如一帧画面刷新。
4.4.3 垂直寻址
垂直寻址模式可以通过指令“20H,01H”来设置。
垂直寻址模式下,每次向GDDRAM写入1字节数据之后,页地址指针将会自动+1。
页指针到达结束页之后会被重置到0,而列指针将会+1。
列地址指针达到结束页之后,将会自动重置到起始列。
4.5 常用命令
4.5.1 设置对比度
双字节指令:0x81H + A[7:0]
此命令设置显示器的对比度设置。该芯片具有从0x00H到0xFFh的256个对比度步长。这个段输出电流随着对比度阶跃值的增加而增加。
4.5.2 设置正常/反转显示
单字节指令:0xA6H / 0xA7H (正常/反转)
正常为1亮0灭,反转为1灭0亮。
4.5.3 设置寻址方式
单字节指令:0x20H + A[1:0]
A[1:0]为寻址方式,00为水平寻址,01为垂直寻址,02为页寻址。默认为02。
4.5.4 设置起始/结束列地址(21H)
三字节指令:0x21H + A[6:0] + B[6:0] (起始 + 终止)
A、B为需要设置的起始和结束坐标,最高位为无效位,即最高设置坐标为127。
4.5.5 设置起始/结束页地址
三字节指令:0x22H + A[2:0] + B[2:0](起始 + 终止)
A、B为需要设置的起始和结束坐标,仅低三位有效,即最高设置页为7。
注意:该三字节命令指定显示数据RAM的页起始地址和结束地址,该指令仅在垂直寻址和水平寻址模式下才有效。
4.5.6 设置列地址
单字节指令:0x00H / 0x10H (低/高)+ A[3:0]
设置列地址需要发送两次命令,一次是设置列地址低4位,一次是设置列地址高四位。
A为需要设置列的坐标的低/高四位,(00h~0Fh)。
注意:该指令仅在设置页寻址模式下才有效
4.5.7 设置页地址
单字节指令: 0xB0H + A[3:0]
A为需要设置的页,最高为7。
注意:该指令仅在设置页寻址模式下才有效
亲爱的读者和支持者们,自动博客加入了打赏功能,陆陆续续收到了各位老铁的打赏。在此,我想由衷地感谢每一位对我们博客的支持和打赏。你们的慷慨与支持,是我们前行的动力与源泉。
日期 | 姓名 | 金额 |
---|---|---|
2023-09-06 | *源 | 19 |
2023-09-11 | *朝科 | 88 |
2023-09-21 | *号 | 5 |
2023-09-16 | *真 | 60 |
2023-10-26 | *通 | 9.9 |
2023-11-04 | *慎 | 0.66 |
2023-11-24 | *恩 | 0.01 |
2023-12-30 | I*B | 1 |
2024-01-28 | *兴 | 20 |
2024-02-01 | QYing | 20 |
2024-02-11 | *督 | 6 |
2024-02-18 | 一*x | 1 |
2024-02-20 | c*l | 18.88 |
2024-01-01 | *I | 5 |
2024-04-08 | *程 | 150 |
2024-04-18 | *超 | 20 |
2024-04-26 | .*V | 30 |
2024-05-08 | D*W | 5 |
2024-05-29 | *辉 | 20 |
2024-05-30 | *雄 | 10 |
2024-06-08 | *: | 10 |
2024-06-23 | 小狮子 | 666 |
2024-06-28 | *s | 6.66 |
2024-06-29 | *炼 | 1 |
2024-06-30 | *! | 1 |
2024-07-08 | *方 | 20 |
2024-07-18 | A*1 | 6.66 |
2024-07-31 | *北 | 12 |
2024-08-13 | *基 | 1 |
2024-08-23 | n*s | 2 |
2024-09-02 | *源 | 50 |
2024-09-04 | *J | 2 |
2024-09-06 | *强 | 8.8 |
2024-09-09 | *波 | 1 |
2024-09-10 | *口 | 1 |
2024-09-10 | *波 | 1 |
2024-09-12 | *波 | 10 |
2024-09-18 | *明 | 1.68 |
2024-09-26 | B*h | 10 |
2024-09-30 | 岁 | 10 |
2024-10-02 | M*i | 1 |
2024-10-14 | *朋 | 10 |
2024-10-22 | *海 | 10 |
2024-10-23 | *南 | 10 |
2024-10-26 | *节 | 6.66 |
2024-10-27 | *o | 5 |
2024-10-28 | W*F | 6.66 |
2024-10-29 | R*n | 6.66 |
2024-11-02 | *球 | 6 |
2024-11-021 | *鑫 | 6.66 |
2024-11-25 | *沙 | 5 |
2024-11-29 | C*n | 2.88 |

【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了