FPGA nios软核编写液晶屏LCD12864驱动程序源码以及注意事项,本人亲自踩坑,重要!!!

版权声明:本文为博主原创文章,未经博主允许不得转载。欢迎联系我qq2488890051 https://blog.csdn.net/kangkanglhb88008/article/details/84027198

LCD12864引脚如下:

FPGA开发板得提供,3.3v电压,5v电压,普通io都是3.3v电压

DB:数据脚,得用双向io,因为程序里面需要读取液晶的应答(普通io3.3v可以)

E: 输出引脚即可,普通io3.3v可以

RW: 输出引脚即可,普通io3.3v可以

RS: 输出引脚即可,普通io3.3v可以

PSB:串行还是并行,接+5v为并行,0v为串行,这个不能悬空,而且一定要接+5v(接3.3v行不行,没有测试,不敢乱说)

V0:这个是控制屏幕显示对比度的,接5v为最高对比度,0v为最低对比度,但是这样根本看不出来屏幕有显示,很坑,还以为是驱动程序没写对,所以最好直接接+5v,先保证屏幕一些正常,把程序调试正确,最后再来调一下对比度即可,即调这个引脚的电压值。其实这个引脚悬空也行,因为lcd12864背后自带了一个电位器,可以调,拧紧的方向为增强对比度,而且很灵敏,调一点点即可,对比度高,屏幕明显看起来有紫红色,对比度太低甚至不显示的程度,屏幕看起来是淡蓝色

VCC:+5v,记住:不能接3.3v,只能5v(这个问题坑了我一天时间,我以为跟数据脚用一样的3.3v就可以,其实不行)

GND:0V但是我发现接不接好像都可以,挺奇怪的,但是最好接上吧

BLA:背光板的正极,+5v,3.3v都可以,只是亮度不同,不影响时序以及应答,不接也行,就没有背光了而已

BLK:背光板负极,0v

首先保证以上引脚电压都接正确,否则程序正确了也没有显示,而且不会应答,程序陷入死循环!!!比如,我就是把vcc接了+3.3v,被这个问题整整搞了大半天,心累!!!手头也没有万用表,示波器等高端设备,自己也只有通过一根线连接这个引脚和一个led来测试哪个引脚电平的高低,一点点尝试,怀疑每一个地方,第二天中午12.30,失败了156次后,正准备放弃的时候,稀里糊涂的换了一下VCC引脚为5V,结果程序竟然跑起来了,显示也正确了,你知道我当时有多开心吗,比中了一个亿还开心,真想告诉全世界!!!那种无数次失败后成功的喜悦,真的是太好了!所以这里想告诉大家,失败是成功之母,经历了无数次失败后,你已经积累了大量的知识,阅历和运气,只要你肯坚持下去,你一定会成功的!

下面是我的FPGA nios 驱动12864的程序:(我为什么不上传呢,因为这样你下载可能就需要积分或者啥的,我感觉不方便,既然大家都是追求技术,我也能体会大家现在的心情,我写这个的目的也不是为了赚积分,或者图什么利益,我就是想记录下自己踩过的坑,分享给大家,希望大家少走弯路。所以我直接写在博客里面,这样你可以直接复制)

注:这个是LCD12864的驱动程序,至于为什么偶尔出现1602的字样,是因为这个驱动程序我是从LCD1602驱动程序移植过来的,因为这两个屏幕的驱动程序很相似,甚至不用改一行程序也能显示少数的字符,所以大家知道这么回事就行

LCD12864.h

/*
 * lcd12864.h
 *
 *  Created on: 2018-11-5
 *      Author: 西电某计算机研究生,嘻嘻
 */

#if 1   // 调试时候方便自己决定这部分是否编译,总比自己一句句注释掉方便

#ifndef LCD12864_H_
#define LCD12864_H_

void InitLcd1602(void);
void LcdShowStr(unsigned char row, unsigned char column, unsigned char *str);
void LcdWriteCmd(unsigned char cmd);


#endif /* LCD12864_H_ */

#endif

LCD12864.c

/*
 * lcd12864.c
 *
 *  Created on: 2018-11-5
 *      Author: 西电某计算机研究生,嘻嘻
 */

#if 1
#include <stdio.h>
#include "system.h" //系统头文件
#include "alt_types.h" //数据类型头文件
#include "altera_avalon_pio_regs.h"//pio 寄存器头文件

#include "LCD12864.h"
#include "delay.h"
// 本程序把对应的io的单独位控制以及读取写入等函数写成了库函数,放在这个文件里面,大家也可以参考一下我这种写法,个人感觉美观,而且方便实现代码复用
// pio_bid_db是双向的,需要控制方向
void pio_bid_db_write8bits(unsigned char value)
{
  IOWR (PIO_BID_DB_BASE,1,0Xff);        // 设置这个双向io的方向为全部io输出
  IOWR_ALTERA_AVALON_PIO_DATA (PIO_BID_DB_BASE, value);
}
unsigned char pio_bid_db_read8bits()
{
  IOWR (PIO_BID_DB_BASE,1,0X00);
  return IORD_ALTERA_AVALON_PIO_DATA (PIO_BID_DB_BASE);
}
void pio_bid_db_write1bit(unsigned char position,unsigned char value)
{
  unsigned char tmp;
  tmp = pio_bid_db_read8bits();
  tmp &= ~(1<<position); //将tmp的第pos位设置为0
  tmp |= value<<position;
  pio_bid_db_write8bits(tmp);
}
// 0-7
unsigned char pio_bid_db_read1bit(unsigned char position)
{
  unsigned char tmp;
  tmp = pio_bid_db_read8bits();
  if((tmp & (1 << position)) > 0)
    return 1;
  else
    return 0;
}
///////////////////////////////////////////////////////////////////////////////////////////////
// pio_bid_en是双向的,需要控制方向
void pio_bid_en_write8bits(unsigned char value)
{
  IOWR (PIO_BID_EN_BASE,1,0Xff);
  IOWR_ALTERA_AVALON_PIO_DATA (PIO_BID_EN_BASE, value);

}
unsigned char pio_bid_en_read8bits()
{
  IOWR (PIO_BID_EN_BASE,1,0X00);
  return IORD_ALTERA_AVALON_PIO_DATA (PIO_BID_EN_BASE);
}
void pio_bid_en_write1bit(unsigned char position,unsigned char value)
{
  unsigned char tmp;
  tmp = pio_bid_en_read8bits();
  tmp &= ~(1<<position); //将tmp的第pos位设置为0
  tmp |= value<<position;
  pio_bid_en_write8bits(tmp);
}
unsigned char pio_bid_en_read1bit(unsigned char position)
{
  unsigned char tmp;
  tmp = pio_bid_en_read8bits();
  if((tmp & (1 << position)) > 0)
    return 1;
  else
    return 0;
}
// LCD1602_RS:     pio_bid_en[5]
// LCD1602_RW:     pio_bid_en[6]
// LCD1602_E:        pio_bid_en[7]
// LCD1602_DB:     pio_bid_db

/* 等待液晶准备好 */
void LcdWaitReady()
{
  unsigned char sta;
  delay_us(1);
  pio_bid_db_write8bits(0xff);
  //LCD1602_RS = 0;
  pio_bid_en_write1bit(5,0);
  delay_us(1);
  //LCD1602_RW = 1;
  pio_bid_en_write1bit(6,1);
  delay_us(1);
  do {
      //LCD1602_E = 1;
      pio_bid_en_write1bit(7,1);
      delay_us(1);
      sta = pio_bid_db_read8bits();
      delay_us(1);

      //LCD1602_E = 0;
      pio_bid_en_write1bit(7,0);
      delay_us(1);
  } while (sta & 0x80); //bit7等于1表示液晶正忙,重复检测直到其等于0为止
}
/* 向LCD1602液晶写入一字节命令,cmd-待写入命令值 */
void LcdWriteCmd(unsigned char cmd)
{
  delay_us(1);
  LcdWaitReady();
  delay_us(1);
  //LCD1602_RS = 0;       FPGA板子的 D/I引脚
  pio_bid_en_write1bit(5,0);
  delay_us(1);
  //LCD1602_RW = 0;
  pio_bid_en_write1bit(6,0);
  delay_us(1);
  //write_LCD1602_DB(cmd);
  pio_bid_db_write8bits(cmd);
  delay_us(1);
  //LCD1602_E = 1;
  pio_bid_en_write1bit(7,1);
  delay_us(1);
  //LCD1602_E = 0;
  pio_bid_en_write1bit(7,0);
  delay_us(1);
}

/* 向LCD1602液晶写入一字节数据,dat-待写入数据值 */
void LcdWriteDat(unsigned char dat)
{
  delay_us(1);
  LcdWaitReady();
  delay_us(1);
  //LCD1602_RS = 1;       FPGA板子的 D/I引脚
  pio_bid_en_write1bit(5,1);
  delay_us(1);
  //LCD1602_RW = 0;
  pio_bid_en_write1bit(6,0);
  delay_us(1);
  //write_LCD1602_DB(dat);
  pio_bid_db_write8bits(dat);
  delay_us(1);
  //LCD1602_E = 1;
  pio_bid_en_write1bit(7,1);
  delay_us(1);
  //LCD1602_E = 0;
  pio_bid_en_write1bit(7,0);
  delay_us(1);
}
/* 设置显示RAM起始地址,亦即光标位置,(x,y)-对应屏幕上的字符坐标 */
void LcdSetCursor(unsigned char x, unsigned char y)
{
  unsigned char addr;

  if (y >= 2)        //说明我们想显示在右半屏
    {
      y -= 2;
      x += 8;         //那么x就得往右挪动8个汉字了
    }
  addr = y*16 + x;//现在这个是以一个汉字为单位,这里所有所说的汉字为单位的意思是16*16
  //由起始DDRAM地址连续写入字符串                               //addr的值是所有的汉字后面的地址,也是汉字为单位,即ddram也是以汉字为单位的
  LcdWriteCmd(0x30); //启动DDRAM操作
  LcdWriteCmd(0x80|addr);                                 //设置ram地址
}
/* 在液晶上显示字符串,(row,column)-对应屏幕上的汉字坐标,str-字符串指针 */
// 这个函数搞得如此复杂的原因是:这样可以实现自动换行,而不会显示跳行等问题,会用就行
void LcdShowStr(unsigned char row, unsigned char column, unsigned char *str)
{
  unsigned char n=0,a;
  unsigned char *fore_str;
  LcdSetCursor(column, row);   //设置起始地址
  while (*str != '\0')  //连续写入字符串数据,直到检测到结束符
    {

      if((n+1)%2==0)  //奇数地址
        {
          fore_str=str-1;
          if(*fore_str>0x80)
            {
              LcdWriteDat(*str++);
            }
          else if(*str>0x80)
            LcdWriteDat(' ');
          else if(*str<=0x80)
            LcdWriteDat(*str++);
        }
      else
        LcdWriteDat(*str++);  //先取str指向的数据,然后str自加1
      n++;

      if((n+2*column)%16==0)
        {
          a=(n+2*column)/16;
          if(row==0)
            {
              if(a==1)
                LcdWriteCmd(0x90);
              if(a==2)
                LcdWriteCmd(0x88);
              if(a==3)
                LcdWriteCmd(0x98);
              if(a==4)
                LcdWriteCmd(0x80);
            }

          if(row==1)
            {
              if(a==1)
                LcdWriteCmd(0x88);
              if(a==2)
                LcdWriteCmd(0x98);
              if(a==3)
                LcdWriteCmd(0x80);
              if(a==4)
                LcdWriteCmd(0x90);
            }

          if(row==2)
            {
              if(a==1)
                LcdWriteCmd(0x98);
              if(a==2)
                LcdWriteCmd(0x80);
              if(a==3)
                LcdWriteCmd(0x90);
              if(a==4)
                LcdWriteCmd(0x88);
            }

          if(row==3)
            {
              if(a==1)
                LcdWriteCmd(0x80);
              if(a==2)
                LcdWriteCmd(0x90);
              if(a==3)
                LcdWriteCmd(0x88);
              if(a==4)
                LcdWriteCmd(0x98);
            }
        }
    }
}/* 初始化1602液晶 */
void InitLcd1602()
{
  LcdWriteCmd(0x38);  //16*2显示,5*7点阵,8位数据接口
  LcdWriteCmd(0x0C);  //显示器开,光标关闭
  LcdWriteCmd(0x06);  //文字不动,地址自动+1
  LcdWriteCmd(0x01);  //清屏
}

#endif

从上面的代码我们可以看出,因为FPGA nios我用的是100MHZ主频,速度太快(实际上一点也不快,我测试过了,一条a++自加指令都要差不多30个时钟周期,即整个nios只相当于3MHZ主频左右的单片机,算非常慢的cpu了),我们需要在指令之间插入延时1us比较好,才能匹配液晶屏需要的时序,这里我用的是自己写的延时函数,当然你也可以用库函数,ussleep(int us);我自己写的delay延时函数如下:

/*
 * delay.c
 *
 *  Created on: 2018-11-5
 *      Author: 西电某计算机研究生,嘻嘻
 */

void delayOneUs()
{
	unsigned char i = 0;
	for(i=0;i<3;i++);	// delay 3 period is one us,though nios 100MHZ freq
}
void delay_us(unsigned long us)
{
	while(us--)
	{
		delayOneUs();
	}
}
void delay_ms(unsigned long ms)
{
	while(ms--)
	{
		delay_us(1000);
	}
}

最后在主函数里面这样调用,lcd12864液晶屏幕即可显示:

#include <stdio.h>
#include "system.h" //系统头文件
#include "alt_types.h" //数据类型头文件
#include "altera_avalon_pio_regs.h"//pio 寄存器头文件
#include "lcd12864.h"

int main(void)
{

  InitLcd1602();
  LcdShowStr(0,0,"I love 华南理工大学");

  LcdShowStr(2,0,"I love 西安电子科技大学");

  return(0);
}

经过测试,整个程序完全o98k,放心用吧!!!运行效果如下:

猜你喜欢

转载自blog.csdn.net/kangkanglhb88008/article/details/84027198