写在前面
在学习了两天STM32 FSMC后,总算是拿这个东西做了第一个应用,关于FSMC是什么东西怎么用,CSDN中有很多介绍,但是,估计新手刚看都是一头雾水(我就是),不过,你仍必须反反复复地看,看了很多很多次后,你最终就能理解它。视频方面,不推荐看正点原子的FSMC的视频,而野火的FSMC讲得很好,推荐看用FSMC读写SRAM那一节。
请记住,如果你完全不懂FSMC,那么你高概率看不懂后面的代码。
FSMC驱动12864lcd简介
12864lcd液晶屏
这个液晶屏是16脚的,看到那么多引脚,可以猜测到这东西大概率是并口通讯,液晶屏的图片和资料见这篇https://blog.csdn.net/kabuto_hui/article/details/46911055
FSMC驱动原理
简单来说就是利用FSMC控制SRAM的时序和并口控制12864液晶屏类似这个特性。关于RS引脚可用地址线A0(对应STM32F103的PF0引脚)模拟。当你定义完一个0x60000000的指针p1,如果你向p1所指向的单元中写入一个数据,此时,A0引脚会拉低,代表写命令;如果你定义完一个0x60000001的指针p2,如果你向p2所指向的单元中写入一个数据,此时,A0引脚会拉高,代表写数据。
引脚对应关系
液晶屏引脚——FSMC引脚——IO口引脚
RS——A0——PF0
RD——FSMC_NOE——PD4
WR——FSMC_NWE——PD5
D0——FSMC_D0——PD14
D1——FSMC_D1——PD15
D2——FSMC_D2——PD0
D3——FSMC_D3——PD1
D4——FSMC_D4——PE7
D5——FSMC_D5——PE8
D6——FSMC_D6——PE9
D7——FSMC_D7——PE10
CS——FSMC_NE1——PD7
RESET——PG9
代码
包含的一些delay.h、usart.h、sys.h头文件是正点原子的(事实上只用了sys.h),lcd初始化设置及测试代码用的是普中科技的代码(主要是懒),另外,我的C语言写得也很烂,有问题就别吐槽了。
文件LCD12864.c
#include "stm32f10x_gpio.h"
#include "stm32f10x_fsmc.h"
#include "stm32f10x_rcc.h"
#include "LCD12864.h"
#include "charcode.h"
/*******************************************************
* 函 数 名:void lcd_gpio_init(void)
* 功能描述:初始化lcd12864需要使用io口,引脚对应关系(lcd——FSMC功能引脚——GPIO口)
* RS——A0——PF0 RD——FSMC_NOE——PD4
* WR——FSMC_NWE——PD5 D0——FSMC_D0——PD14
* D1——FSMC_D1——PD15 D2——FSMC_D2——PD0
* D3——FSMC_D3——PD1 D4——FSMC_D4——PE7
* D5——FSMC_D5——PE8 D6——FSMC_D6——PE9
* D7——FSMC_D7——PE10 CS——FSMC_NE1——PD7
* RESET——PG9
* 调用函数:无
* 全局变量:无
* 输入参数:无
* 返 回 值:无
* 设 计 者:sddfsAv 日期:24/07/2019
*******************************************************/
void lcd_gpio_init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
/*GPIOF、D、E、G时钟使能*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOF|RCC_APB2Periph_GPIOD|RCC_APB2Periph_GPIOE|RCC_APB2Periph_GPIOG, ENABLE);
/*RS推挽复用输出*/
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(GPIOF, &GPIO_InitStructure);
/*RD、WR、D0、D1、D2、D3、CS推挽复用输出*/
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4|GPIO_Pin_5|GPIO_Pin_14|GPIO_Pin_15|
GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_7;
GPIO_Init(GPIOD, &GPIO_InitStructure);
/*D4、D5、D6、D7推挽复用输出*/
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7|GPIO_Pin_8|GPIO_Pin_9|GPIO_Pin_10;
GPIO_Init(GPIOE, &GPIO_InitStructure);
/*RESET推挽输出*/
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_Init(GPIOG, &GPIO_InitStructure);
}
/*******************************************************
* 函 数 名:void lcd_fsmc_init(void)
* 功能描述:用fsmc初始化配置
* 调用函数:无
* 全局变量:无
* 输入参数:无
* 返 回 值:无
* 设 计 者:sddfsAv 日期:24/07/2019
*******************************************************/
void lcd_fsmc_init(void)
{
FSMC_NORSRAMInitTypeDef FSMC_NORSRAMInitStructure;
FSMC_NORSRAMTimingInitTypeDef readWriteTiming;
FSMC_NORSRAMTimingInitTypeDef writeTiming;
/*FSMC时钟使能*/
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_FSMC,ENABLE);
/*读时序设置*/
readWriteTiming.FSMC_AddressSetupTime = 0x01; //地址建立时间(ADDSET)为2个HCLK 1/36M=27ns
readWriteTiming.FSMC_AddressHoldTime = 0x00; //地址保持时间(ADDHLD)模式A未用到
readWriteTiming.FSMC_DataSetupTime = 0x0f; //数据保存时间为16个HCLK
readWriteTiming.FSMC_BusTurnAroundDuration = 0x00;
readWriteTiming.FSMC_CLKDivision = 0x00;
readWriteTiming.FSMC_DataLatency = 0x00;
readWriteTiming.FSMC_AccessMode = FSMC_AccessMode_A; //模式A
/*写时序设置*/
writeTiming.FSMC_AddressSetupTime = 0x00; //地址建立时间(ADDSET)为1个HCLK
writeTiming.FSMC_AddressHoldTime = 0x00; //地址保持时间(A
writeTiming.FSMC_DataSetupTime = 0x06; //数据保存时间为6个HCLK
writeTiming.FSMC_BusTurnAroundDuration = 0x00;
writeTiming.FSMC_CLKDivision = 0x00;
writeTiming.FSMC_DataLatency = 0x00;
writeTiming.FSMC_AccessMode = FSMC_AccessMode_A; //模式A
FSMC_NORSRAMInitStructure.FSMC_Bank = FSMC_Bank1_NORSRAM1;//使用NE1
FSMC_NORSRAMInitStructure.FSMC_DataAddressMux = FSMC_DataAddressMux_Disable; //不复用数据地址
FSMC_NORSRAMInitStructure.FSMC_MemoryType =FSMC_MemoryType_SRAM;// FSMC_MemoryType_SRAM; //SRAM
FSMC_NORSRAMInitStructure.FSMC_MemoryDataWidth = FSMC_MemoryDataWidth_8b;//存储器数据宽度为8bit
FSMC_NORSRAMInitStructure.FSMC_BurstAccessMode =FSMC_BurstAccessMode_Disable;// FSMC_BurstAccessMode_Disable;
FSMC_NORSRAMInitStructure.FSMC_WaitSignalPolarity = FSMC_WaitSignalPolarity_Low;
FSMC_NORSRAMInitStructure.FSMC_AsynchronousWait=FSMC_AsynchronousWait_Disable;
FSMC_NORSRAMInitStructure.FSMC_WrapMode = FSMC_WrapMode_Disable;
FSMC_NORSRAMInitStructure.FSMC_WaitSignalActive = FSMC_WaitSignalActive_BeforeWaitState;
FSMC_NORSRAMInitStructure.FSMC_WriteOperation = FSMC_WriteOperation_Enable; // 存储器写使能
FSMC_NORSRAMInitStructure.FSMC_WaitSignal = FSMC_WaitSignal_Disable;
FSMC_NORSRAMInitStructure.FSMC_ExtendedMode = FSMC_ExtendedMode_Enable; // 读写使用不同的时序
FSMC_NORSRAMInitStructure.FSMC_WriteBurst = FSMC_WriteBurst_Disable;
FSMC_NORSRAMInitStructure.FSMC_ReadWriteTimingStruct = &readWriteTiming; //读写时序
FSMC_NORSRAMInitStructure.FSMC_WriteTimingStruct = &writeTiming; //写时序
FSMC_NORSRAMInit(&FSMC_NORSRAMInitStructure); //初始化FSMC配置
FSMC_NORSRAMCmd(FSMC_Bank1_NORSRAM1, ENABLE); // 使能BANK1
}
/*******************************************************
* 函 数 名:void LcdSt7565_WriteCmd(void)
* 功能描述:写一个命令到12864
* 调用函数:无
* 全局变量:无
* 输入参数:u8 cmd, 8位命令
* 返 回 值:无
* 设 计 者:sddfsAv 日期:25/07/2019
*******************************************************/
void LcdSt7565_WriteCmd(u8 cmd)
{
WRITECOMMAND=cmd;
}
/*******************************************************
* 函 数 名:void LcdSt7565_WriteCmd(void)
* 功能描述:写一个数据到12864
* 调用函数:无
* 全局变量:无
* 输入参数:u8 dat, 8位命令
* 返 回 值:无
* 设 计 者:sddfsAv 日期:25/07/2019
*******************************************************/
void LcdSt7565_WriteData(u8 dat)
{
WRITEDATA=dat;
}
/*******************************************************
* 函 数 名:void Lcd12864_Init(void)
* 功能描述:写一个数据到12864
* 调用函数:lcd_gpio_init();lcd_fsmc_init()
* 全局变量:无
* 输入参数:无
* 返 回 值:无
* 设 计 者:sddfsAv 日期:25/07/2019
*******************************************************/
void Lcd12864_Init(void)
{
u8 i;
lcd_gpio_init();
lcd_fsmc_init();
LCD12864_RSET = 0;
for (i=0; i<100; i++);
LCD12864_CS = 0;
LCD12864_RSET = 1;
//----------------Star Initial Sequence-------//
//------程序初始化设置,具体命令可以看文件夹下---//
//--软件初始化--//
LcdSt7565_WriteCmd(0xE2); //reset
for (i=0; i<100; i++); //延时一下
//--表格第8个命令,0xA0段(左右)方向选择正常方向(0xA1为反方向)--//
LcdSt7565_WriteCmd(0xA0); //ADC select segment direction
//--表格第15个命令,0xC8普通(上下)方向选择选择反向,0xC0为正常方向--//
LcdSt7565_WriteCmd(0xC8); //Common direction
//--表格第9个命令,0xA6为设置字体为黑色,背景为白色---//
//--0xA7为设置字体为白色,背景为黑色---//
LcdSt7565_WriteCmd(0xA7); //reverse display
//--表格第10个命令,0xA4像素正常显示,0xA5像素全开--//
LcdSt7565_WriteCmd(0xA4); //normal display
//--表格第11个命令,0xA3偏压为1/7,0xA2偏压为1/9--//
LcdSt7565_WriteCmd(0xA2); //bias set 1/9
//--表格第19个命令,这个是个双字节的命令,0xF800选择增压为4X;--//
//--0xF801,选择增压为5X,其实效果差不多--//
LcdSt7565_WriteCmd(0xF8); //Boost ratio set
LcdSt7565_WriteCmd(0x01); //x4
//--表格第18个命令,这个是个双字节命令,高字节为0X81,低字节可以--//
//--选择从0x00到0X3F。用来设置背景光对比度。---/
LcdSt7565_WriteCmd(0x81); //V0 a set
LcdSt7565_WriteCmd(0x23);
//--表格第17个命令,选择调节电阻率--//
LcdSt7565_WriteCmd(0x25); //Ra/Rb set
//--表格第16个命令,电源设置。--//
LcdSt7565_WriteCmd(0x2F);
for (i=0; i<100; i++);
//--表格第2个命令,设置显示开始位置--//
LcdSt7565_WriteCmd(0x40); //start line
//--表格第1个命令,开启显示--//
LcdSt7565_WriteCmd(0xAF); // display on
for (i=0; i<100; i++);
}
/*******************************************************************************
* 函 数 名 : LCD12864_ClearScreen
* 函数功能 : 清屏12864
* 输 入 : 无
* 输 出 : 无
*******************************************************************************/
void Lcd12864_ClearScreen(void)
{
u8 i, j;
for(i=0; i<8; i++)
{
//--表格第3个命令,设置Y的坐标--//
//--Y轴有64个,一个坐标8位,也就是有8个坐标--//
//所以一般我们使用的也就是从0xB0到0x07,就够了--//
LcdSt7565_WriteCmd(0xB0+i);
//--表格第4个命令,设置X坐标--//
//--当你的段初始化为0xA1时,X坐标从0x10,0x04到0x18,0x04,一共128位--//
//--当你的段初始化为0xA0时,X坐标从0x10,0x00到0x18,0x00,一共128位--//
//--在写入数据之后X坐标的坐标是会自动加1的,我们初始化使用0xA0所以--//
//--我们的X坐标从0x10,0x00开始---//
LcdSt7565_WriteCmd(0x10);
LcdSt7565_WriteCmd(0x04);
//--X轴有128位,就一共刷128次,X坐标会自动加1,所以我们不用再设置坐标--//
for(j=0; j<128; j++)
{
LcdSt7565_WriteData(0x00); //如果设置背景为白色时,清屏选择0XFF
}
}
}
/*******************************************************************************
* 函 数 名 : LCD12864_Write16CnCHAR
* 函数功能 : 在12864上面书写16X16的汉字
* 输 入 : Page, Column, cn
*******************************************************************************/
u8 Lcd12864_Write16CnCHAR(u8 Page, u8 Column, u8 *cn)
{
u8 j, x1, x2, wordNum;
if(Page > 7) return 0; //页坐标只能从0到7,大于则直接返回
if(Column > 128) return 0; //列的坐标只能从0到127,大于则直接返回
Page += 0xB0; //求取页坐标的值 不能放在while里面
while ( *cn != '\0') //在C语言中字符串结束以‘\0’结尾
{
LcdSt7565_WriteCmd(Page); //设置页坐标
x1 = (Column >> 4)& 0x0F; //先取出高4位 注释1
x2 = Column & 0x0F; //取低四位
LcdSt7565_WriteCmd(0x10 + x1); //设置列坐标 高4
LcdSt7565_WriteCmd(0x00 + x2); // 低4
for (wordNum=0; wordNum<50; wordNum++)
{
//--查询要写的字在字库中的位置--//
if ((CN16CHAR[wordNum].Index[0] == *cn) && (CN16CHAR[wordNum].Index[1] == *(cn+1)))
{
for (j=0; j<32; j++) //写一个字
{
if (j == 16) //16X16用到两个页坐标,当大于等于16时,切换页坐标
{
LcdSt7565_WriteCmd(Page + 1); //设置页坐标
LcdSt7565_WriteCmd(0x10 + x1); //高4位 设置列坐标
LcdSt7565_WriteCmd(0x00 + x2); //低4位
}
LcdSt7565_WriteData(CN16CHAR[wordNum].Msk[j]);
}
Column += 16; //下一个字的新的列地址
break; //我们理解:一旦字库中找到字符,余下字库不用再找
}
}
cn += 2;
}
return 1;
}
LCD12864.h
#ifndef __LCD12864_H
#define __LCD12864_H
#include "stm32f10x.h"
#include "sys.h"
//---包含字库头文件
#define CHAR_CODE
#define WRITECOMMAND *((volatile u8 *)0x60000000) //在A0~A15全部低电平,代表地址0x0000
#define WRITEDATA *((volatile u8 *)0x60000001) //仅在A0上为高电平,代表地址0x0001
#define LCD12864_CS PDout(7)
#define LCD12864_RSET PGout(9)
void lcd_gpio_init(void);
void lcd_fsmc_init(void);
void Lcd12864_Init(void);
void Lcd12864_ClearScreen(void);
void LcdSt7565_WriteData(u8 dat);
void LcdSt7565_WriteCmd(u8 cmd);
u8 Lcd12864_Write16CnCHAR(u8 Page, u8 Column, u8 *cn);
#endif
charcode.h
#ifndef __CHARCODE_H
#define __CHARCODE_H
// ------------------ 汉字字模的数据结构定义 ------------------------ //
struct Cn16CharTypeDef // 汉字字模数据结构
{
unsigned char Index[2]; // 汉字内码索引,一个汉字占两个字节
unsigned char Msk[32]; // 点阵码数据(16*16有32个数据)
};
struct Cn16CharTypeDef CN16CHAR[]=
{
/*-- 文字: 液 --*/
/*-- 宋体12; 此字体下对应的点阵为:宽x高=16x16 --*/
"液",0x10,0x60,0x02,0x8C,0x00,0x84,0xE4,0x1C,0x05,0xC6,0xBC,0x24,0x24,0xE4,0x04,0x00,
0x04,0x04,0x7E,0x01,0x00,0x00,0xFF,0x82,0x41,0x26,0x18,0x29,0x46,0x81,0x80,0x00,
/*-- 文字: 晶 --*/
/*-- 宋体12; 此字体下对应的点阵为:宽x高=16x16 --*/
"晶",0x00,0x00,0x00,0x00,0x7F,0x49,0x49,0x49,0x49,0x49,0x7F,0x00,0x00,0x00,0x00,0x00,
0x00,0xFF,0x49,0x49,0x49,0x49,0xFF,0x00,0xFF,0x49,0x49,0x49,0x49,0xFF,0x00,0x00,
/*-- 文字: 显 --*/
/*-- 宋体12; 此字体下对应的点阵为:宽x高=16x16 --*/
"显",0x00,0x00,0x00,0xFE,0x92,0x92,0x92,0x92,0x92,0x92,0x92,0xFE,0x00,0x00,0x00,0x00,
0x40,0x42,0x44,0x58,0x40,0x7F,0x40,0x40,0x40,0x7F,0x40,0x50,0x48,0x46,0x40,0x00,
/*-- 文字: 示 --*/
/*-- 宋体12; 此字体下对应的点阵为:宽x高=16x16 --*/
"示",0x40,0x40,0x42,0x42,0x42,0x42,0x42,0xC2,0x42,0x42,0x42,0x42,0x42,0x40,0x40,0x00,
0x20,0x10,0x08,0x06,0x00,0x40,0x80,0x7F,0x00,0x00,0x00,0x02,0x04,0x08,0x30,0x00,
/*-- 文字: 文 --*/
/*-- 宋体12; 此字体下对应的点阵为:宽x高=16x16 --*/
"文",0x08,0x08,0x08,0x38,0xC8,0x08,0x09,0x0E,0x08,0x08,0xC8,0x38,0x08,0x08,0x08,0x00,
0x80,0x80,0x40,0x40,0x20,0x11,0x0A,0x04,0x0A,0x11,0x20,0x40,0x40,0x80,0x80,0x00,
/*-- 文字: 字 --*/
/*-- 宋体12; 此字体下对应的点阵为:宽x高=16x16 --*/
"字",0x10,0x0C,0x04,0x24,0x24,0x24,0x25,0x26,0xA4,0x64,0x24,0x04,0x04,0x14,0x0C,0x00,
0x02,0x02,0x02,0x02,0x02,0x42,0x82,0x7F,0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x00,
/*-- 文字: 测 --*/
/*-- 宋体12; 此字体下对应的点阵为:宽x高=16x16 --*/
"测",0x10,0x60,0x02,0x8C,0x00,0xFE,0x02,0xF2,0x02,0xFE,0x00,0xF8,0x00,0xFF,0x00,0x00,
0x04,0x04,0x7E,0x01,0x80,0x47,0x30,0x0F,0x10,0x27,0x00,0x47,0x80,0x7F,0x00,0x00,
/*-- 文字: 试 --*/
/*-- 宋体12; 此字体下对应的点阵为:宽x高=16x16 --*/
"试",0x40,0x40,0x42,0xCC,0x00,0x90,0x90,0x90,0x90,0x90,0xFF,0x10,0x11,0x16,0x10,0x00,
0x00,0x00,0x00,0x3F,0x10,0x28,0x60,0x3F,0x10,0x10,0x01,0x0E,0x30,0x40,0xF0,0x00,
};
#endif
main.c
#include "delay.h"
#include "usart.h"
#include "stm32f10x.h"
#include "stm32f10x_conf.h"
#include "LCD12864.h"
int main(void)
{
delay_init();
Lcd12864_Init();
Lcd12864_ClearScreen();
while(1)
{
//页地址 列地址 显示文字
Lcd12864_Write16CnCHAR(0, 0, "液晶显示文字测试");
}
}
/******************* (C) COPYRIGHT 2019 sddfsAv*****文件结束****/
结果
看上去显示不是很完美,不过至少说明FSMC基本正常工作。
好吧,个人觉得浪费10几个IO口来驱动这样一个LCD完全不划算。