11. STM32-SPI Nokia 5110 LCD 实作

Nokia 5110 LCD 介绍

刚好手边有块Nokia 5110 LCD 就拿它来做测试吧~虽然这块LCD年份久远了,但还是很适合来做些小东西玩玩!
https://ithelp.ithome.com.tw/upload/images/20220316/20146325Px76bqYoBu.png
先介绍一下这块LCD上面的接脚 :

脚位 说明 连接脚位
RST LCD 重置 PB15
CS 选择脚位 PB6
D/C 资料或命令切换 PB13
DIN 资料输入 PA7
CLK 系统时钟 PA5
VCC 电源 3.3v
BLC 背光控制 PB14
GND 接地 GND

Nokia 5110 是使用SPI协议但没有MISO只有MOSI,所以MISO透过程序模拟就可以了。

上方表是各个脚位的连接,在这边我使用SPI1所以主要使用是PA5-7,CS透过软件选择则是PB6,除了电源与接地外其余的脚位可以依照个人去更改。

接下来看看LCD的指令有哪些~
https://ithelp.ithome.com.tw/upload/images/20220316/20146325LoOBH4ZG0p.png
可以看到其中功能设置、写指令、设置RAM XY位置等等,可以先将会使用到的函数先写出来,方便之後去使用。

  1. Function Set (功能设置) : 在D/C设为0,後续填入00100 PD V H,PD为0时表示选中1表示失能,V为0则代表水平寻址1则代表垂直寻址,H为0是使用标准指令及1则是扩展指令及。
  2. Write data (写指令) : 在D/C设为1,後续填入要写入的8个bit,用途为写资料到RAM上。
  3. Display control (显示控制) : D/C设置为0,後续填入 00001 D 0 E,用途为控制显示模式
D E Mode
0 0 显示空白
1 0 普通模式
0 1 打开所有显示
1 1 反转
  1. Set Y address of RAM (设置RAM的Y位置) : D/C设置为0,後续填入01000 Y2 Y1 Y0,同时(0≤Y≤5)。
  2. Set Y address of RAM (设置RAM的Y位置) : D/C设置为0,後续填入1 X6 X5 X4 X3 X2 X1 X0,同时(0≤X≤83)。

上方功能中的RAM是什麽?可以把它想成显示pixel的地址,这一块是48x84的LCD也就是说共有4032个pixel,每一个Pixel都会像下图一样排好各有各的地址。
https://ithelp.ithome.com.tw/upload/images/20220316/20146325avncHUVZzp.png
水平寻址於垂直寻址又是什麽呢?下方是整个LCD的RAM格式寻址,在垂直的部分为6x8 = 48,而x的部分则为0-83共84刚好对应到了48x84的LCD。
https://ithelp.ithome.com.tw/upload/images/20220316/20146325zChlARGDyp.png

  1. 垂直寻址 : 对应上方的六大格编号为0-5垂直往下接着换下一行接续,所以上方才会说Y必须小於等於5 (0b00000101)
    https://ithelp.ithome.com.tw/upload/images/20220316/20146325hpR8pwvv3G.png
  2. 水平寻址 : 由纵向改为横向地址编排, 0-83接着换下一列继续接续,也就是X小於等於83 (0b01010011)
    https://ithelp.ithome.com.tw/upload/images/20220316/20146325v74f4w8B0R.png
    上述图片来源:手册

IOC档设置

  1. Ioc档设置:先点选左侧的Connectivity可以在里面找到SPI1、SPI2、SPI3,会看到SPI1是禁止的,原因是因为板载LED将PA5设置为Output,取消PA5就可以选择了选择最上方的Reset_State。
    https://ithelp.ithome.com.tw/upload/images/20220316/201463250QFZdae0PV.png
  2. 接着在上方模式的部分选择Full-Duplex Master(全双工Master),让STM32做为Master。下方NSS的部分选择Disable,因为我透过软件方式去控制CS脚位。
    https://ithelp.ithome.com.tw/upload/images/20220316/20146325rf4DfvFhC3.png
  3. 下方详细配置中可以点选GPIO Settings ,会清楚地看到MISO MOSI SCK脚位在哪,将LCD对应的接上去即可,但由於刚刚选择透过软件控制CS,所以我们要将原先的CS(PB6)脚位选择为OutPut。
    https://ithelp.ithome.com.tw/upload/images/20220316/20146325W2pdLO0jfq.png
    https://ithelp.ithome.com.tw/upload/images/20220316/20146325315uN5p6uO.png
  4. 接着点选回Parameter Settings,当中将Data Size改为8bit。下方预分频系数可以依照传输鲍率去做选择,这边我选为Prescaler 64 Baud Rate则是1.25MBits/s。再往下会看到CPOL与CPHA设置的选项,这部分可以参考前一篇介绍去做设置。如果要使用中断方式的话记得要到NVIC当中将SPI中断打开!
    https://ithelp.ithome.com.tw/upload/images/20220316/201463252FvcqHSW58.png
  5. 接下来已经将LCD上剩下RST、BLC、DIN我们还没有接上,这部分可以依照个人选择脚位,如果有其他设备不要占用到功能脚位即可,这边我选择PB13-15去做这三条线的连接。
    https://ithelp.ithome.com.tw/upload/images/20220316/20146325KEWgOyr1VP.png

实作程序码

  1. 定义SPI指令
#define NOP 0x00
#define FunSetVE 0x23 //垂直寻址 扩展模式
#define FunSetHE 0x21 //水平寻址 扩展模式
#define FunSetVS 0x22 //垂直寻址 标准模式
#define FunSetHS 0x20 //水平寻址 标准模式
#define DisplayBlack 0x08//空白模式
#define DisplayNormalk 0x0C //正常模式
#define DisplayALL 0x09 //显示段全开
#define DisplayInverse 0x0D //反转模式
  1. 定义CS_LOW与CS_High来做位时序的启动於停止
//CS_LOW
static void StartSPI(void)
{
    HAL_GPIO_WritePin(GPIOB,GPIO_PIN_6,GPIO_PIN_RESET);
}
//CS_High
static void StopSPI(void)
{
    HAL_GPIO_WritePin(GPIOB,GPIO_PIN_6,GPIO_PIN_SET);
}
  1. 接着我们可以将HAL_SPI的库写成一次传送与接收1个byte,方便之後使用~
//传送
static void SPI_Tx(uint8_t data)
{
  HAL_SPI_Transmit(&hspi1, &data, 1, 10);
}
//接收
//回传值为接收到的资料
static uint8_t SPI_Rx(void)
{
  uint8_t retVal;
  HAL_SPI_Receive(&hspi1, &retVal, 1, 10);
  return retVal;
}
  1. 对LCD写入资料
void LCD_Writebyte(unsigned char data, unsigned char dc)
{
	StartSPI();
	if(dc==0)
		HAL_GPIO_WritePin(GPIOB,GPIO_PIN_13,RESET); //命令
	else
		HAL_GPIO_WritePin(GPIOB,GPIO_PIN_13,SET); //资料
	SPI_Tx(data);
	StopSPI();
}
  1. 初始化LCD
void LCD_Init(void)
{
	//Reset
	HAL_GPIO_WritePin(GPIOB,GPIO_PIN_15,RESET); 
	HAL_Delay(1);
	HAL_GPIO_WritePin(GPIOB,GPIO_PIN_15,SET); 
	
	StartSPI();
	HAL_Delay(1);
	StopSPI();
	HAL_Delay(1);

	LCD_Writebyte(FunSetHE, 0); 
	LCD_Writebyte(0xBD,0); 
	LCD_Writebyte(0x13,0);
	LCD_Writebyte(0x13,0);
	LCD_Writebyte(FunSetHS, 0);
	LCD_Writebyte(DisplayNormalk, 0);
}
  1. 设置Xy起始位置
void LCD_SetPosition(uint8_t X, uint8_t Y)
{
	LCD_Writebyte(0x40 | Y, 0);		// column
	LCD_Writebyte(0x80 | X, 0);    // row
}
  1. 清除LCD
void LCD_Clear(void)
{
	uint16_t i;

	LCD_Writebyte(0x0c, 0);
	LCD_Writebyte(0x80, 0);

	for (i = 0; i < 504; i ++)
	{
		LCD_Writebyte(0, 1);
	}
}

看了那麽多函式可能还不太懂如何去将字体显示在LCD上方,下方这张图可以搭配看可能会比较好理解,假设希望产生下方英文:
https://ithelp.ithome.com.tw/upload/images/20220316/20146325n3Vue0D1QF.png

while (1)
{
	HAL_GPIO
	LCD_Clear();
	LCD_Writebyte(0x02, 1);
	LCD_Writebyte(0x02, 1);
	LCD_Writebyte(0x02, 1);
	LCD_Writebyte(0x02, 1);
	LCD_Writebyte(0xFE, 1);
	LCD_SetPosition(8,0);
	LCD_Writebyte(0x82, 1);
	LCD_Writebyte(0x82, 1);
	LCD_Writebyte(0xFE, 1);
	LCD_Writebyte(0x82, 1);
	LCD_Writebyte(0x82, 1);
	LCD_SetPosition(15,0);
	LCD_Writebyte(0xF0, 1);
	LCD_Writebyte(0x48, 1);
	LCD_Writebyte(0x44, 1);
	LCD_Writebyte(0x48, 1);
	LCD_Writebyte(0xF0, 1);
	HAL_Delay(500);
}

https://ithelp.ithome.com.tw/upload/images/20220316/20146325SInrIhmwmK.jpg
上方只是为了好理解如何将文字显在在LCD上,网路上有些软件可以直接提取对应的位置,就不用这麽麻烦一个一个去写,可以直接透过[]数组的方式去显示就可以了~


<<:  Python & Celery 学习笔记_删除任务

>>:  Emotet 社交工程邮件,寄件者或是内容中包含贵公司的员工资料或是签名档

Gulp 基础介绍 gulp-postcss 与 autoprefixer DAY81

这里要来介绍如何优化 css 有时候我们因为要加前缀词(有的旧浏览器不支援,所以需要加) 但这会浪费...

实战练习 - 使用 RxJS 实作 Flux Pattern

使用 React 作为前端架构的朋友对於 Flux 应该都不陌生,React 也内建了 Flux 让...

Day24:Hot flow - State Flow (part II)

前一篇文章中,我们介绍了 State Flow 以及它的使用方式,本篇将继续讨论 State Flo...

.net framework 4.5.2 升级到 .net framework 4.6.2 笔记

Step 1 修改专案属性 专案->属性 修改目标framework的值 点选"是&...

DAY21-EXCEL统计分析:单因子完全随机集区实例

某间面包店的面包师傅想研究不同配方做出的面包所销售出的差异,但依照部烤箱的不同温度又会有所不同,故想...