- 打卡等级:偶尔看看III
- 打卡总天数:30
- 最近打卡:2025-12-16 16:50:01
注册会员
- 积分
- 83
|
书接上回https://www.stcaimcu.com/thread-21629-1-1.html
实验测试
连线:
用 8P 杜邦线将 JP27 P0-P7 连接 JP18 QA-QH
JP26 P9-P16 连接 JP20 QA-QH
JP10-P12 连接 JP16 SER3,SRCLK,RCLK
JP14-P16 连接 JP28 SER1,SRCLK,RCLK
JP30-P33 连接 JP74 DK0-DK3
JP19 SER4 连接 QH-3
实验现象:
按下 DK1 键 点阵显示字母 T,按下 DK2 键 点阵显示水平向右滚动的“特种兵”;
按下 DK3 键 点阵显示向下移动的箭头,按下 DK4 键 点阵显示向上移动的箭头。
代码分析
SN74HC595 的使用
74HC595 串出一个 8 位的数据,其具体程序如下:
/*******************************************************
* 函数名称:HC595_send_byte_r()
* 函数功能:行方向上的HC595串出一个字节,从高位到低位串
* 入口参数:dat: 串入的字节
* 返回参数:无
********************************************************/
void HC595_send_byte_r(uchar dat)
{
uchar i;
for (i = 0; i < 8; i++)
{
HC595_data_r = dat & 0x80; // 从高位到低位串出
dat <<= 1;
HC595_clk_r = 0; // HC595_clk_r上升沿将SER数据逐步传入到移位寄存器中
HC595_clk_r = 1;
}
}
16*16 点阵扫描底层驱动就是不考虑显示缓冲区是什么内容,当定时器 1ms 一
到,就将当前缓冲区中相应列的段码显示出来。这种设计可以将底层驱动与上层程
序控制分开,更好的实现模块化,调试起来也比较方便。部分程序如下所示:
/*******************************************************
//函数名称:void display_io(void)
//函数功能:16 * 16点阵扫描底层驱动,1ms扫描一列
//入口参数:无
//返回参数:无
********************************************************/
void display_io(void)
{
static uchar scan_line; //16列扫描计数
union display_data line; //扫描位选值
uchar temp1;
uchar temp2;
temp1 = ~display_buff[scan_line].data_8[0]; //扫描到当前列,显示缓冲区高8位
temp2 = ~display_buff[scan_line].data_8[1]; //扫描到当前列,显示缓冲区低8位
line.data_16 = scan_line_array[scan_line]; //给当前列即位选赋值
HC595_send_byte_c(line.data_8[0]); //串出列上的数据,位选
HC595_send_byte_c(line.data_8[1]);
HC595_send_byte_r(temp1); //串出行上的数据,段值
HC595_send_byte_r(temp2);
HC595_latch_r = 0; //行方向,HC595_latch_r上升沿将并出的数据锁存到存储寄存器中
HC595_latch_r = 1;
HC595_latch_c = 0; //列方向,HC595_latch_c上升沿将并出的数据锁存到存储寄存器中
HC595_latch_c = 1;
if (++scan_line >= 16) scan_line = 0; //扫描完16列,回到第一列
}
在本段代码中,用到了 union 数据结构,它的定义在 hal.h 文件中,这个数据
结构可以将 16 位数据的高八位和低八位直接取出使用,而不用通过左移操作实现,
更加简洁方便。
按键扫描底层驱动
按键设计依然采用状态机的思想,现在有四个按键,所以需要确认那个键按下,
在 read_key()函数中,在 state1 按键确认状态时还需要判断哪个键按下,并将返
回值赋给key_return,这样方便在主程序调用read_key()后就可将返回值赋给flag
标志位,从而方便进行相应的处理。部分程序如下所示:
/*******************************************************
//函数名称: uchar read_key(void)
//函数功能:按键扫描底层驱动,10ms扫描一次,利用状态机进行消抖
//入口参数:无
//返回参数:返回当前按键值
********************************************************/
uchar read_key(void)
{
static uchar key_state = 0;
uchar key_press, key_return = 0;
P3 = 0x7f | P3; //读按键I/O电平,先写1
key_press = P3 | 0xf0; //将P3的高4位屏蔽掉,由低4位判断键值
switch (key_state)
{
case key_state0: //按键初试态
if (key_press != key_none_value)
{
key_state = key_state1; //按键被按下状态转换到确认态
}
break;
case key_state1: //按键确认态
//按键仍按下状态转换成释放态
if (key_press != key_none_value)
{
key_state = key_state2;
switch (key_press) //返回按键的值
{
case key1_value:
key_return = 1; //DK1键按下
break;
case key2_value:
key_return = 2; //DK2键按下
break;
case key3_value:
key_return = 3; //DK3键按下
break;
case key4_value:
key_return = 4; //DK4键按下
break;
}
}
else
key_state = key_state0; //否则按键在抖动,转换到按键初始态
break;
case key_state2: //按键释放态
P3 = 0x7f | P3; //读按键I/O电平,先写1
key_press = P3 | 0xf0;
if (key_press == key_none_value)
{
key_state = key_state0; //按键已释放,转换到按键初始态
}
break;
}
return key_return;
}
主程序流程图
如图 7-9 所示,为 main 函数的程序流程图,本次实验软件设计由 3 个文件组
成,main.c、key_read.c、display_io.c。
在 main.c 中进行系统的初始化和中断服务程序处理,选用定时器 0 进行 1ms
定时,在定时器 0 中断服务程序中,设置 1ms,10ms,150ms 标志位,用于 16*16
点阵扫描,驱动按键扫描与消抖以及更新显示缓冲区内容。
需要注意的是,由于本实验全部采用列扫描方式,所以箭头上升与下降在更新
缓冲区内容时需要进行如下处理:
for (k0 = 0; k0 <= 15; k0++)
{
tmp1 = display_buff[k0].data_8[1]; //行数据的低八位
display_buff[k0].data_16 >>= 1; //将16位数据右移一位
if (tmp1 & 0x01)
{
display_buff[k0].data_16 |= 0x8000; //低八位数据最后一位移位到高八位最高位
}
}
程序相关宏文件:
#define INT_GLOBAL_ENABLE(on) EA = (!!on) //开全局中断
/********************************************************
// 功能:定时器运行操作控制
// 参数:num 计数器号(0-4) ;on 运行控制,1:启动,0停止
*********************************************************/
#define TIMER_RUN(num, on) \
st( \
if (num == 0) \
TR0 = on; \
else if (num == 1) \
TR1 = on; \
else if (num == 2) \
(on) ? (AUXR |= BIT4) : (AUXR &= ~BIT4); \
else if (num == 3) \
(on) ? (T4T3M |= BIT3) : (T4T3M &= ~BIT3); \
else if (num == 4) \
(on) ? (T4T3M |= BIT4) : (T4T3M &= ~BIT4); \
)
/********************************************************
// 功能:定时器定时操作控制
// 参数:num 计数器号(0-4)
*********************************************************/
#define TIMER_TIME(num) \
st( \
if (num == 0) \
TMOD |= ~BIT2; \
else if (num == 1) \
TMOD |= ~BIT6; \
else if (num == 2) \
AUXR |= ~BIT3; \
else if (num == 3) \
T4T3M |= ~BIT2; \
else if (num == 4) \
T4T3M |= ~BIT6; \
)
/********************************************************
// 功能:定时器中断使能操作控制
// 参数:num 计数器号(0-4) ;on 使能控制,1:允许,0禁止 //1溢出中断允许
*********************************************************/
#define TIMER_INT_EN(num, on) \
if (num == 0) \
ET0 = on; \
else if (num == 1) \
ET1 = on; \
else if (num == 2) \
(on) ? (IE2 |= BIT3) : (IE2 &= ~BIT3); \
else if (num == 3) \
(on) ? (IE2 |= BIT5) : (IE2 &= ~BIT5); \
else if (num == 4) \
(on) ? (IE2 |= BIT6) : (IE2 &= ~BIT6);
/********************************************************
// 功能:定时器工作模式选择
// 参数:num 计数器号(0-1) ;sel模式选择(0-3)
0:16位重载 1:16位计数器 2:8位重载 3:计数器无效
*********************************************************/
#define TIMER_MODE(num, sel) \
if (sel < 4) \
{ \
if (num == 0) \
{ \
TMOD &= ~0x03; \
TMOD |= sel; \
} \
else if (num == 1) \
{ \
TMOD &= ~0x30; \
TMOD |= sel << 3; \
} \
}
/********************************************************
// 功能:定时器定时时间分频选择
// 参数:num 计数器号(0-4); sel分频选择; 1:1分频,0-12分频
*********************************************************/
#define TIMER_CLK_DIV(num, sel) \
st( \
if (num == 0) \
(sel == 1) ? (AUXR |= BIT7) : (AUXR &= ~BIT7); \
else if (num == 1) \
(sel == 1) ? (AUXR |= BIT6) : (AUXR &= ~BIT6); \
else if (num == 2) \
(sel == 1) ? (AUXR |= BIT2) : (AUXR &= ~BIT2); \
else if (num == 3) \
(sel == 1) ? (T4T3M |= BIT1) : (T4T3M &= ~BIT1); \
else if (num == 4) \
(sel == 1) ? (T4T3M |= BIT5) : (T4T3M &= ~BIT5); \
)
/*************************************
// 功能:清除定时器中断标志 //清除溢出中断
// 参数:num 计数器号(0-1)
*********************************************************/
#define CLR_TIMER_FLAG(num) \
st( \
if (num == 0) \
TF0 = 0; \
else if (num == 1) \
TF0 = 0; \
)
|
|