AVR单片机教程——LCD1602
本文隶属于AVR单片机教程系列。
显示屏
开发板套件里有两块屏幕,大的是LCD(液晶显示),小的是OLED(有机发光二极管)。正与你所想的相反,短小精悍的比较贵,而本讲的主题——LCD1602——功能较少,使用起来也简单很多。
这块屏幕的显示是以字符为单位的。每个字符都是8像素高,5像素宽。1602这个名字,来源于显示字符的数量,共2行,每行16个字符。出售1602的商家提供了一份文档:提取码8c1u。
硬件
一个典型的1602显示屏有16个引脚(还有些模块是用串行总线驱动的):
| 名称 | 功能 | 连接 |
|---|---|---|
| VSS | 电源地 | GND |
| VDD | 正电源 | VCC(5V) |
| VO | 对比度调整 | 左侧的电位器,其左端接GND,右端接VCC |
| RS | 数据/指令选择 | PB0 |
| R/W | 读/写选择 | PB1 |
| E | 使能 | PB2 |
| D0~D7 | 双向并行数据线 | 74HC595的输出,通过exout_write输出 |
| A | 背光LED正极 | NPN三极管的发射极,其集电极接VCC,基极接BAK |
| K | 背光LED负极 | GND |
很复杂吧?好在开发板已经处理好了这些,我们只需要关注2组线:RS、R/W、E这3根控制线,通过DDRB和PORTB来操控,和D0~D7这8跟数据线,通过exout_write来以字节为单位输出(在这第一期的最后一篇中,我终于成功地将“尽量减少接线”的原则在第一期中贯彻到底了)。其他的还有对比度CON接口,可以不外接,还有背光BAK接口,可以接5V或3.3V以开启背光。当然,如果你是非主流,你可以把CON接到DAC上,把BAK接到PWM上。
此外,还需要把扩展输出的最高位(标注Ext Out 7)接到一个单片机引脚上。关于为什么会有这种诡异的接法,这是设计时的失误(也可能是不得已吧,毕竟单片机的32个IO已经占满了),参见:一个低电平引发的思考。
协议
1602与单片机之间是通过并行总线通信的。AVR单片机硬件上不支持并行总线,需要通过软件模拟时序来实现。
写操作的时序如下:

进行一个写操作,需要先让RS根据写的类型设置电平,R/W输出低电平,D0`D7`输出要发送的数据,然后在`E`的上升沿数据被对方读取,并保持`R/W`与`D0`D7电平不变,直到E的下降沿之后。两次E的上升沿之间至少需要400us时间间隔。
1602共有8条指令,都是一字节长度的。从高位到低位,每一条指令都由若干个0、一个1和有效指令组成,使得没有两条指令会有相同的二进制表示。这种编码方式成为前缀码,UTF-8也用了类似的编码。
| 前导0个数 | 功能 |
|---|---|
| 0 | 设置DDRAM(显示存储器)地址 |
| 1 | 设置CGRAM(字符发生存储器)地址 |
| 2 | 设置总线宽度、字符行数、字符大小 |
| 3 | 设置滚动对象(光标或画面)、滚动方向 |
| 4 | 设置画面、光标、闪烁的开关 |
| 5 | 设置AC(地址计数器)是否变化与变化方向 |
| 6 | 将AC清零 |
| 7 | 清屏 |
1602插上开发板后只有第一行有黑色背景。我们先把它设置为2行输出,这是最简单的有视觉效果的指令。
#include <avr/io.h>
#include <ee1/lcd.h>
inline void short_delay()
{
asm("nop");
asm("nop");
asm("nop");
asm("nop");
}
int main()
{
DDRB |= 0b111; // PB2:0 for LCD control: E, R/W, RS
exout_init(); // Ext Out 7:0 for LCD instructions/data
#define LCD_CONTROL(x) (PORTB = (PORTB & ~0b111) | (x))
LCD_CONTROL(0b000);
short_delay();
LCD_CONTROL(0b100);
short_delay();
exout_write(0b00111000); // 8-bit, 2 lines, 5x7 font
short_delay();
LCD_CONTROL(0b000);
}
在这段代码中,asm("nop")是一句汇编语句nop,代表这个CPU周期内不执行操作,可以用来短时间而精确地延时。在控制信号与数据信号之间加入短暂的延时,使时序符合1602的要求。
其他指令见数据手册,这份数据手册本身就是很好的教程。需要提醒的是,显示屏内的控制器执行指令需要一定时间,程序在发送下一指令之前,应通过RS为低电平的读取操作来获取其状态,并等待直到状态为空闲。
软件
开发板的库不仅包装了这些硬件操作,还添加了对字符串的支持,包括对\b、\t、\n和\r等不可打印字符的处理。
下面我们用1602显示屏来完成一个小项目,在之前用按键控制蜂鸣器的基础上,加入LCD显示与背光动态效果。
#include <string.h>
#include <ee1/lcd.h>
#include <ee1/pwm.h>
#include <ee1/button.h>
#include <ee1/tone.h>
#include <ee1/delay.h>
int main()
{
lcd_init(PIN_3);
lcd_set_status(true, false, false);
button_init(PIN_6, PIN_7);
wave_mode(WAVE_0, WAVE_MODE_PWM);
wave_mode(WAVE_1, WAVE_MODE_TONE);
char display[6] = {[0] = '\r', [5] = '\0'}; // 1. 为什么要开6字节字符数组?
char* notes = display + 1; // 2. display数组是如何使用的?
uint8_t brightness; // 3. 这一行有什么问题?
uint16_t frequency[] = {262, 330, 392, 523};
uint16_t buzzer = 0, tone;
while (1)
{
memset(notes, ' ', 4); // 4. memset是什么函数?
tone = 0;
for (uint8_t i = 0; i != 4; ++i)
if (button_down(i))
{
notes[i] = 0xFF; // 5. 0xFF是什么字符?
brightness = 255;
tone = frequency[i]; // 6. 如果有多个按键按下,蜂鸣器会响什么音?
}
lcd_print_string(display);
pwm_set(WAVE_0, brightness * brightness >> 8); // 7. 为什么第二个参数不直接写brightness?
if (tone != buzzer) // 8. 为什么必须引入buzzer变量并在这里作判断?
{
buzzer = tone;
tone_set(WAVE_1, buzzer);
}
if (brightness)
brightness -= 5;
delay(10);
}
}
在这AVR单片机教程第一期最后一讲中,我在代码中留了8个问题。如果你能全部答上来,说明你对C语言已经有一些了解,熟悉了单片机开发中的部分细节,并具备初步的工程素养了。
作业
思考题:
明明可以选择4位总线来节省IO,为什么开发板上还是8位连接?
实现读取1602空闲状态。
在1602上画一颗心。
修改例程,使1602以滚动的横线指示按下的按键与发出的音符(类似于你玩过或看过的音游)。
扩展阅读:
AVR单片机教程——LCD1602的更多相关文章
- AVR单片机教程——第三期导语
背景(一) 寒假里做了一个灯带控制器: 理想情况下我应该在一个星期内完成这个项目,但实际上它耗费了我几乎一整个寒假,因为涉及到很多未曾尝试的方案.在这种不是很赶时间的.可以自定目标.自由发挥的项目中, ...
- AVR单片机教程——旋转编码器
好久没写这个系列了.今天讲讲旋转编码器. 旋转编码器好像不是单片机玩家很常用的器件,但是我们的开发板上有,原因如下: 旋转编码器挺好用的.电位器能旋转的角度有限,旋转编码器可以无限圈旋转:旋转时不连续 ...
- AVR单片机教程——数码管
先解答之前一个思考题:如果不把引脚配置为输出而写高电平,连接LED会怎样? 实验结果是,LED会亮,但相比于输出高电平的情况,亮度很低.这是为什么呢? 通过上一篇教程我们知道,引脚输入输出模式是由寄存 ...
- AVR单片机教程——数字输出
从上一篇教程中我们了解到,按键与开关的输入本质上就是数字信号的读取.这一篇教程要讲的是,控制LED的原理是数字信号的输出.数字IO是单片机编程之有别于桌面编程的各项内容中最简单.最基础的. 在讲数字信 ...
- AVR单片机教程——数字输入
我们已经学习了如何使用按键和拨动开关,不知你有没有好奇 button_down 和 switch_status 等函数是如何实现的.本篇教程带你一探究竟,让我们从按键的原理开始. 在原理图中,按键的符 ...
- AVR单片机教程——拨动开关
在按键的上方有4个拨动开关.开关与按键,在原理和使用方法上都是很类似的,但有不同的用途——按键按下后松开就会弹起,而开关可以保存其状态. <switch.h> 定义了与开关相关的函数.sw ...
- AVR单片机教程——按键动作
上一篇教程中我们学习了如何读取按键状态.而按键的动作,比如单击,至少需要两个状态才能判定,长按.双击的判定更加复杂.今天我们来学习如何使用库函数判断按键单击,以及其实现原理. 我们要实现的是:当一个按 ...
- AVR单片机教程——按键状态
好久没更新了,今天开始继续,争取日更. 今天我们来讲按键.开发板的右下角有4个按键,按下会有明显的“咔嗒”声.如何检测按键是否被按下呢?首先要把按键或直接或间接地连接到单片机上.与之前使用的4个LED ...
- AVR单片机教程——随机点亮LED
之前我们做的闪烁LED和流水灯,灯效都是循环的.这次我们来尝试一些不一样的——每一次随机选择一个LED并点亮. 要实现随机的效果,我们要用C语言标准库中的相关设施: #define RAND_MAX ...
随机推荐
- java 合并流(SequenceInputStream)
需要两个源文件,还有输出的目标文件 SequenceInputStream: 将两个文件的内容合并成一个文件 该类提供的方法: SequenceInputStream(InputStream s1, ...
- UVA 1025 "A Spy in the Metro " (DAG上的动态规划?? or 背包问题??)
传送门 参考资料: [1]:算法竞赛入门经典:第九章 DAG上的动态规划 题意: Algorithm城市的地铁有 n 个站台,编号为 1~n,共有 M1+M2 辆列车驶过: 其中 M1 辆列车从 1 ...
- P1088 上台阶
题目描述 楼梯有 \(n(1 \le n \le 50)\) 阶台阶,上楼时可以一步上 \(1\) 阶,也可以一步上 \(2\) 阶,也可以一步上 \(3\) 阶,编程计算共有多少种不同的走法. 输入 ...
- spring security (BCryptPasswordEncoder)加密及判断密码是否相同
通过BCryptPasswordEncoder的加密的相同字符串的结果是不同的,如果需要判断是否是原来的密码,需要用它自带的方法. 加密: BCryptPasswordEncoder encode = ...
- java 内省 了解JavaBean
JavaBean是一种特殊的Java类,主要用于传递数据信息,这种java类中的方法主要用于访问私有的字段,且方法名符合某种命名规则. 如果要在两个模块之间传递多个信息,可以将这些信息封装到一个Jav ...
- ESB总线的核心架构
根据近期对开源ESB产品的研究,已经对Oracle和Tibco的ESB总线产品的实施经验积累,对ESB总线的核心产品架构有了进一步的清晰认识,将ESB的核心架构整理为上图,上图中看到的内容也是做为一款 ...
- 微信小程序之在线试题(1)
最近在做一套公司的市场化培训项目,涉及到手机端在线答题的设计,首先摒弃app的模式,那就只剩下微信公众号和小程序,而公众号是可以关联小程序,所以我们只需要做好一套小程序. 因为篇幅问题,下面只讲解在线 ...
- linux 每-CPU 的变量
每-CPU 变量是一个有趣的 2.6 内核的特性. 当你创建一个每-CPU 变量, 系统中每个处理 器获得它自己的这个变量拷贝. 这个可能象一个想做的奇怪的事情, 但是它有自己的优点. 存取每-CPU ...
- 让Word Add-in For MediaWiki支持Word 2013
1.下载并安装Word Add-in For MediaWiki https://www.microsoft.com/en-us/download/details.aspx?id=12298 2.在“ ...
- cmake安装mysql及多实例配置方法
一.安装mysql 1. 生产环境如何选择MySQL版本 1. 选择社区版的稳定GA版本2. 可以选择5.1或5.5.互联网公司主流5.5, 其次是5.1和5.63. 选择发布后6个月以上的GA版4. ...