今天看了下LCD驱动开发的代码,发现思路是这样的。
如以下任务,需要用主控芯片Cortext M3架构的MCU,驱动字符液晶模组tc1602。另外,配以串并转换芯片74HC595。给你以上这样3个器件,你要怎么让LCD动态显示一串字符和时间?
1.首先,为了节省IO口资源,MCU一般会使用SSI(同步串行传输接口)传指令和数据。而SSI的协议有多种,其中最著名的就是SPI接口。所以,你必须得懂得SSI的驱动怎么写。
2.因为字符液晶模组一般是并口的,可以是4位或者8位或者更多,所以MCU和LCD之间的数据传输,要有串并转换芯片来实现,如74HC595。
3.串口芯片有什么特征?首先,串口输入部分是用移位寄存器,而输出部分是并口存储器,很明显,输出比较慢,要等待串行输入结束才可以输出。所以输出和输入是两个时钟,在SSI编程时,会用两个不同的延时MCU端口来发时钟给串并转换芯片,以同步输入和输出端的数据(如下图的SCK和RCK)。
4.那么MCU发送数据给LCD的流程是怎样的呢?如下:
(1)首先,MCU把数据串行地发给串并转换芯片。这时,要根据串并转换芯片的要求,允许一些数据操作端口。
(2)其次,根据串并转换芯片的输出端的时钟,允许串并转换芯片的数据从并口输出。但在这个操作之前,MCU要在相应的端口提醒LCD接收数据
5.这有点需要注意的地方,那就是MCU既有控制LCD读取的端口,也有控制串并转换芯片读写的端口。如下图的R/W RS OE
总结:可见,给LCD发数据,主要是给串并转换芯片发数据,在写函数的时候,其实是环环嵌套的,打个比方:
函数嵌套如下:
LCD字符串send->调用LCD字节SEND->调用SSI_SEND->调用纯字节send
SSI_SEND和纯字节SEND函数有什么区别呢?区别就是纯send,只是把数据写入寄存器,而协议SSI_SEND需要把相应的位OE拉高或者拉低,还要判断芯片是否busy等。LCD_SEND也是这样,需要在SSI_SEND的基础上给控制端口拉高或拉低电平。
可见,驱动开发=写命令和数据寄存器+写控制端口+准确设计延时。而上面提到的嵌套,就是因为普通的写数据寄存器都依赖写控制端口+写命令寄存器+时延函数,所以会出现很多调用。但如上所述,原理其实很简单。
以上说的参考代码如下(《CortexM3内核微控制器 快速入门与应用》):
//*********************************************************************************
//博圆周立功单片机&嵌入式系统培训[博圆单片机初学之路]
//Email: bymcupx@126.com
//文件名: TC1602_lcd.h
//功能: TC1602液晶驱动程序
//开发者:刘同法
//日期: 2008年9月15日
//---------------------------------------------------------------------------------
#include "hw_memmap.h"
#include "hw_types.h"
#include "ssi.h"
#include "gpio.h"
#include "sysctl.h"
#include "systick.h"
#define uchar unsigned char //映射uchar 为无符号字符
#define uint unsigned int //映射uint 为无符号整数
#define RS GPIO_PIN_0 //PA0 用于命令与数据选择RS=0选择发送命令,RS=1选择发送数据
#define RW GPIO_PIN_1 //PA1 读/写数据选择脚 RW=0选择命令或数据写,Rw=1选择命令或数据读
#define E GPIO_PIN_6 //PB6 使能线
#define GCS GPIO_PIN_3 //SPI 片选 PA3
#define GDIO GPIO_PIN_5 //SPIDAT[MOSI] 数据 PA5
#define GSCLK GPIO_PIN_2 //SPICLK 时钟 PA2
uchar uchBuff;
void GSEND_DATA8(uchar chSDAT);
void GSEND_COM8(uchar chSDAT);
/****************************************************************************
* 名称:Delay50uS
* 功能:50uS软件延时
* 说明:用户根据自已的系统相应更改
****************************************************************************/
void Delay_uS(uint nNum2)
{
unsigned long i;
i=SysCtlClockGet()/1000000; //获取系统1us的时间*1得出1个us
i=i*nNum2;
while(i--);
}
//-------------------------------------------------------------------------------
//延时函数
//说明:每执行一次大约100us
//------------------------------------------------------------------------------
void DelayDS(uint nNum)
{
unsigned long i;
i=SysCtlClockGet()/1000000*100; //获取系统1us的时间*100得出100个us
i=i*nNum;
while(i--);
}
//------------------------------------------------------------------------------
// 函数名称 GPio_Initi_PA
// 函数功能 启动外设GPIO输入输出
// 输入参数 无。
// 输出参数 无。
//-----------------------------------------------------------------------------
void GPio_Initi_PA(void)
{
// 使能GPIO B口外设。用于指示灯
SysCtlPeripheralEnable( SYSCTL_PERIPH_GPIOA | SYSCTL_PERIPH_GPIOB );
GPIODirModeSet(GPIO_PORTA_BASE, RS | RW | E,GPIO_DIR_MODE_OUT);
GPIODirModeSet(GPIO_PORTB_BASE, E,GPIO_DIR_MODE_OUT);
// 设定 GPIO A 2~5 引脚为使用外设功能 GPIO_DIR_MODE_HW由硬件进行控制
GPIODirModeSet(GPIO_PORTA_BASE, (GPIO_PIN_2 | GPIO_PIN_3 |
GPIO_PIN_5), GPIO_DIR_MODE_OUT); //HW[]
}
//-----------------------------------------------------------------------------
//函数名称: Write_Command()
//函数功能: 向TC1602写入命令[将ACC寄存器命令内容发送到P1口]
//入口参数:chComm[送递要发送的命令]
//出口参数:无
//编程序: 刘同法
//编程序日期:2008年9月15日
//------------------------------------------------------------------------------
void Write_Command(uchar chComm)
{
//RS=0; //CLR RS 命令与数据选择脚 RS=0选择发送命令,RS=1选择发送数据
GPIOPinWrite( GPIO_PORTA_BASE, RS,~RS );
//RW=0; //CLR RW 读/写数据选择脚 RW=0选择命令或数据写,Rw=1选择命令或数据读
GPIOPinWrite( GPIO_PORTA_BASE, RW,~RW );
// E=1; //SETB E 开启数据锁存脚[数据锁存允许]
GPIOPinWrite( GPIO_PORTB_BASE, E,E );
GSEND_COM8(chComm);
//E=0; //CLR E 关闭数据锁存脚并向对方锁存数据[告诉对方数据已发送请接收]
GPIOPinWrite( GPIO_PORTB_BASE, E,~E );
DelayDS(10); //延时1ms 100*10=1000us=1ms
}
//--------------------------------------------------------------
//函数名称: Write_Data()
//函数功能: 向TC1602写入数据[将ACC寄存器数据内容发送到P1口]
//入口参数:chData[送递要发送的数据]
//出口参数:无
//编程序: 刘同法
//编程序日期:2008年9月15日
//----------------------------------------------------------------
void Write_Data(uchar chData)
{
//RS=1; //SETB RS 命令与数据选择脚 RS=0选择发送命令,RS=1选择发送数据
GPIOPinWrite( GPIO_PORTA_BASE, RS,RS );
//RW=0; //CLR RW 读/写数据选择脚 RW=0选择命令或数据写,Rw=1选择命令或数据读
GPIOPinWrite( GPIO_PORTA_BASE, RW,~RW );
//E=1; //SETB E 开启数据锁存脚[数据锁存允许]
GPIOPinWrite( GPIO_PORTB_BASE, E,E );
GSEND_COM8(chData);
//E=0; //CLR E 关闭数据锁存脚并向对方锁存数据[告诉对方数据已发送请接收]
GPIOPinWrite( GPIO_PORTB_BASE, E,~E );
DelayDS(5); //延时0.5ms 5*100=500us=0.5ms
}
//--------------------------------------
//函数名称:Busy_tc1602()
//函数功能:判忙子程序[用于判断LCD是否在忙于写入,如LCD在忙于别的事情,那就等LCD忙完后才操作]
//入出参数:无
//编程序: 刘同法
//编程序日期:2008年9月15日
//-----------------------------------------
void Busy_tc1602()
{
uchar ACC;
// RS=0; //CLR RS
GPIOPinWrite( GPIO_PORTA_BASE, RS,~RS );
//RW=1; //SETB RW 设为从TC1602中读取数据
GPIOPinWrite( GPIO_PORTA_BASE, RW,RW );
do
{
//E=1; //SETB E
GPIOPinWrite( GPIO_PORTB_BASE, E,E );
//ACC=P1; //MOV A,P1 读取P1口数据
//ACC=Lm101_Ssi_Rcv();
//E=0; //CLR E 锁存数据
GPIOPinWrite( GPIO_PORTB_BASE, E,~E );
ACC=ACC&0x80; //ANL A,#80H
}while(ACC); //JNZ TT0
//POP ACC
}
//-------------------------------------------------------------
//函数名称: Delay_1602()
//函数功能: 用于毫秒级延时
//入口参数:nDTime[用于传递延时间单位为ms,如果nDTime=1即为1ms]
//出口参数: 无
//编程序: 刘同法
//编程序日期:2008年9月15日
//---------------------------------------------------------------
void Delay_1602(uint nDTime)
{
uint a;
for(a=0;a<nDTime;a++)
DelayDS(10); //取每循环一次为1ms
}
//------------------------------------------------------------
//下面是TC1602外用程序
//-------------------------------------------------------------
//函数名称: Init_TC1602()(外部调用)
//函数功能: 初始化TC1602液晶显示屏[TC1602必须要初使化才能使用]
//入出参数:无
//编程序: 刘同法
//编程序日期:2008年9月15日
//-----------------------------------------------------------
void Init_TC1602()
{
//初始化SPI
GPio_Initi_PA();
//共延时15ms
Delay_1602(15);
//发送命令
Write_Command(0x38);
Delay_1602(5); //LCALL TME0 ;延时5ms
//重发一次
Write_Command(0x38);
Delay_1602(5); //LCALL TME0 ;延时5ms
Write_Command(0x38); //设置为8总线16*2 5*7点阵
Write_Command(0x01); //发送清屏命令
Write_Command(0x06); //设读写字符时地址加1,且整屏显示不移动
Write_Command(0x0F); //开显示,开光标显示,光标和光标所在的字符闪烁
Delay_1602(5); //LCALL TME0 ;延时5ms
}
//------------------------------------------------------
//函数名称: Cls()
//函数功能: 用于清屏
//入出参数:元
//编程序: 刘同法
//编程序日期:2008年9月15日
//--------------------------------------------------------
void Cls()
{
Write_Command(0x01); //发送清屏命令
}
//--------------------------------------------------------
//下面是应用部分
//--------------------------------------------------------
//函数名称: Send_String_1602()
//函数功能: 用于向TC1602发送字符串
//入口参数:chCom[传送命令行列] lpDat[传送数据串不要超个16个字符] nCount[传送发送数据的个数]
//出口参数: 无
//编程序: 刘同法
//编程序日期:2008年9月15日
//---------------------------------------------------------
void Send_String_1602(uchar chCom,uchar *lpDat,uint nCount)
{
uint i=0;
Write_Command(chCom); //发送起始行列号
Delay_1602(10);
for(i=0;i<nCount;i++)
{
Write_Data(*lpDat); //发送数据
lpDat++; //让指针向前进1[加1]读取下一个字符
}
Delay_1602(20);
}
//--------------------------------------------------------
//函数名称: Send_Data_1602()
//函数功能: 用于向TC1602发送整型数
//入口参数:chCom[传送命令行列] nDat[传送整型数据] nCount[传送发送数据的个数]
//出口参数: 无
//编程序: 刘同法
//编程序日期:2008年9月15日
//---------------------------------------------------------
void Send_Data_1602(uchar chCom,uint nData,uint nCount)
{
uint nInt,nInt1,nInt2; //用来存放数据
uchar chC[5];
if(nCount>4)return ; //判断是否大于4个,如果大于4个就反回
//控制5个不准显示
if(nCount==1)
{ chC[0]=nData%10;
chC[0]|=0x30; //使用逻辑或加入显示字符因为tc1602使用的是ASCII码作显示
Write_Command(chCom); //发送起始行列号
Write_Data(chC[0]); //发送数据
}
else if(nCount==2)
{
nInt=nData%100;
chC[0]=nInt/10;
chC[0]|=0x30; //使用逻辑或加入显示字符因为tc1602使用的是ASCII码作显示
chC[1]=nInt%10;
chC[1]|=0x30; //同chC[1]=chC[1]|0x30; 逻辑或运算,但是千万不能用加法运算,否则得出来的数是乱码
Write_Command(chCom); //发送起始行列号
Write_Data(chC[0]); //发送数据
Write_Data(chC[1]); //发送数据
}
else if(nCount==3)
{
nInt=nData%1000;
chC[0]=nInt/100;
chC[0]|=0x30; //逻辑或运算变为ASCII美国国家标准信息码用于显示
nInt=nInt%100;
chC[1]=nInt/10;
chC[1]|=0x30;
chC[2]=nInt%10;
chC[2]|=0x30;
Write_Command(chCom); //发送起始行列号
Write_Data(chC[0]); //发送数据
Write_Data(chC[1]); //发送数据
Write_Data(chC[2]); //发送数据
}
else if(nCount==4)
{
nInt=nData%10000;
nInt1=nInt/100; //取商
nInt2=nInt%100; //取余
chC[0]=nInt1/10;
chC[0]|=0x30;
chC[1]=nInt1%10;
chC[1]|=0x30;
chC[2]=nInt2/10;
chC[2]|=0x30;
chC[3]=nInt2%10;
chC[3]|=0x30;
Write_Command(chCom); //发送起始行列号
Write_Data(chC[0]); //发送数据
Write_Data(chC[1]); //发送数据
Write_Data(chC[2]); //发送数据
Write_Data(chC[3]); //发送数据
}else;
return ;
}
//------------------------------------------------------------------------------------
//为自编写字模用WRCGRAM子程序写入1602LCD夜晶显示器CGRAM存储器
uchar ZhiMou[]={0x08,0x0F,0x12,0x0F,0x0A,0x1F,0x02,0x02, //年
0x0F,0x09,0x0F,0x09,0x0F,0x09,0x11,0x00, // ;月
0x0F,0x09,0x09,0x0F,0x09,0x09,0x0F,0x00}; //;日
uchar chTB1[6]={0x42,0x59,0x50,0x58,0x42}; // ;BYPXB
uchar chTB2[14]={0x6C,0x74,0x66,0x32,0x30,0x30,0x35,0x00,0x31,0x30,0x01,0x31,0x02};
// l t f 2 0 0 5 年 1 0 月 1 日
uchar chTB4[8]={0x62,0x79,0x6D,0x63,0x75,0x70,0x78}; //bymcupx
uchar chTB5[8]={0x32,0x30,0x30,0x35,0x31,0x30,0x39}; //2005109
uchar chTB6[10]={'L','i','u','T','o','n','g','F','a'};
//-------------------------------------------------------------------------------
//写入用户汉字字模数据子程序
//------------------------------------------------------------------------------
//函数名称:Write_WRCGRAM()
//功能:[创建用户字模地址从00~07共8个,且只能创建8个]把要建立的汉字字模数据写入用户字模存储器[CGRAM]
//入出参数:无
//编程序: 刘同法
//编程序日期:2008年9月15日
//------------------------------------------------------------------------------
void Write_WRCGRAM()
{
uint i=0;
Write_Command(0x40); //发送命令从00H地址开始存放字模
for(i=0;i<24;i++) //8*8*8=24
Write_Data(ZhiMou[i]); //发送字模数据
}
//----------------------------------------------------------------------------
//下面是74HC595驱动程序
//用于向74HC595发送数据
//-----------------------------------------------------------------------------
//程序名称:GSEND_DATA8()
//程序功能:用于发送8位数据[单字节发送子程序]
//入口参数: chSDAT[传送要发送的数据]
//出口参数:无
//编程:刘同法
//时间:2008年6月10日
//说明:ZLG7289使用的是SPI同步串行通信,本子程序已是串行通信程序,
// 再加上CS片选动作即可实施SPI同步串行通信
// 数据发送高位在前
//---------------------------------------------------------------------------------
void GSEND_DATA8(uchar chSDAT)
{
GPIODirModeSet(GPIO_PORTA_BASE, GPIO_PIN_5 , GPIO_DIR_MODE_OUT);
uint JSQ1=8; //准备发送8次
Delay_uS(50); //调用50us延时子程序
uchBuff=chSDAT;
SD:
//先将高7位发送出去
if(uchBuff&0x80)
GPIOPinWrite( GPIO_PORTA_BASE, GDIO, GDIO ); //如果是1就发1
else
GPIOPinWrite( GPIO_PORTA_BASE, GDIO, 0x00 ); // 如果是0就发0
//将高6位移到高7位准备下一次发送
uchBuff=uchBuff<<1; //然后向左移一位准备下一次的发送
Delay_uS(2);
GPIOPinWrite( GPIO_PORTA_BASE, GSCLK, GSCLK ); //准备数据锁存 =1
Delay_uS(8); //12微秒延时
GPIOPinWrite( GPIO_PORTA_BASE, GSCLK, 0x00 ); //在脉冲的下沿锁存数据
Delay_uS(8); //12微秒延时锁存数据
if(--JSQ1)goto SD;
GPIOPinWrite( GPIO_PORTA_BASE, GDIO, 0x00 );//清零数据线
return;
}
//----------------------------------------------------
//程序名称:GSEND_COM8()
//程序功能:单字节命令发送子程序[发送纯指令的子程序]
//入口参数: chSDAT[传送要发送的数据]
//出口参数:无
//编程:刘同法
//时间:2008年6月10日
//----------------------------------------------------
void GSEND_COM8(uchar chSDAT)
{
GPIOPinWrite( GPIO_PORTA_BASE, GCS, 0x00 );//GCS=0;
GPIOPinWrite( GPIO_PORTA_BASE, GSCLK, 0x00 ); // GSCLK=0;
GSEND_DATA8(chSDAT);
GPIOPinWrite( GPIO_PORTA_BASE, GCS, GCS ); // GCS=1;
}
//-----------------------------------------------------------------------------
//******************************************************************************