# c51_course_design **Repository Path**: starlight-flow/c51_course_design ## Basic Information - **Project Name**: c51_course_design - **Description**: c51课程设计 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2024-05-22 - **Last Updated**: 2024-12-26 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # c51_course_design c51课程设计 ## 系统框架 系统按模块划分为:C51最小系统和上拉电阻、LED阵列模块、LED行驱动控制模块、LED行字码输出模块、字码输出锁存器组控制模块、模式切换按钮模块。 LED阵列使用32*16的规格,横向排布,可同时显示2个字。 LED行驱动控制模块使用38译码器,由于38译码器输出的选中信号是低电平,因此适合控制阵列阴极。 LED行字码输出模块使用8位锁存器,控制一行LED的字码输出,单次传输8位数据。 字码输出锁存器组控制模块使用24译码器实现8位锁存器的片选功能,由于译码器输出的选中信号是低电平,而锁存器的直通状态是锁存引脚为高电平,因此需在锁存器的锁存使能引脚前串接非门。 按当前阵列规格,使用2个74HC138译码器实现逐行扫描,4个74HC573锁存器实现2个字的字码输出。使用P23和P24引脚实现对74HC138的片选功能, 使用P20-P22引脚对38译码器传送2进制的LED行字符编码。使用74HC139实现对4个74HC573锁存器的片选控制,使用P10和P11引脚传送锁存器片选编码,使用P0口对4个8位锁存器传送8位字码数据。 ## 原理 ### LED阵列 LED点阵屏通过LED发光二极管组成,以灯珠亮灭来显示文字、图片、动画、视频等,是各部分组件都模块化的显示器件,通常由显示模块、控制系统及电源系统组成。LED点阵显示屏制作简单,安装方便,被广泛应用于各种公共场合,如汽车报站器、广告屏以及公告牌等。 8x8阵列单侧有8个引脚,一个引脚连接一行的行内LED的阳极,另一侧 的8 个引脚中,每个引脚连接一列的LED的阴极。亮起一个LED需要其所在阳极行引脚为高电平,阴极列引脚为低电平。 为了给单个中文字符显示区域提供足够的面积,将8x8规格扩展为16x16规格,根据8x8阵列工作原理,将2个纵向位置关系的LED阵列的阴极引脚按位串联为LED#引脚,2个横向位置关系的LED阵列的阳极引脚按位串联为DA#或DB#引脚。设计图中32x16的LED阵列整体由2个16x16的LED阵列横向拼接而成。 字码生成: 一个字符的显示区域是16行16列。当前方案中,显示方式为行扫描,一行16位的二进制数可转换为2个字节的16进制数。可先根据图形设定2进制数,再转换为16进制。 也可使用字模软件快速获取字码。 ### 38译码器 74HC138 是一款高速 CMOS 器件,74HC138 引脚兼容低功耗肖特基TTL(LSTTL)系列。 74HC138 译码器可接受 3 位二进制加权地址输入(A, B 和 C),并当使能时,提供 8 个互斥的低有效输出(Y0 至 Y7)。74HC138 特有 3 个使能输入端: 两个低有效(E1 和 E2)和一个高有效(E3)。除非 E1 和 E2 置低且 E3 置高, 否则 74HC138 将保持所有输出为高。利用这种复合使能特性,仅需 4 片 74HC138 芯片和 1 个反相器,即可轻松实现并行扩展,组合成为一个 1-32(5 线到 32 线) 译码器。任选一个低有效使能输入端作为数据输入,而把其余的使能输入端作为 选通端,则 74HC138 亦可充当一个 8 输出多路分配器,未使用的使能输入端必须保持绑定在各自合适的高有效或低有效状态。 根据工作原理,将E1和E2接地保持低电平,通过E3引脚传输高电平控制信号实现对译码器的片选控制。 译码器输出信号通过A,B,C引脚传输的二进制编码信号控制。 ### 24译码器 74HC139 包含两个 2 线至 4 线解码器,具有一个低电平有效的输出选通 G。当一个通道的输出受到选通输入控制时,所有输出强制进入高电平状态。当选通输入未禁用输出时,只有选定输出为低电平,而所有其他输出为高电平。 根据工作原理,将选通引脚G接地保持低电平使译码器正常工作,通过A,B引脚传输二进制编码信号。 ### 锁存器 74HC573是一款高速CMOS器件,74HC573引脚兼容低功耗肖特基TTL(LSTTL)系列。 74HC573包含八路D型透明锁存器,每个锁存器具有独立的D型输入,以及适用于面向总线的应用的三态输出。所有锁存器共用一个锁存使能(LE)端和一个输出使能(OE)端。 在锁存器使能 (LE) 输入为高电平时,Q 输出将跟随数据 (D) 输入。当 LE 为低电平时,Q 输出被锁存在 D输入端的逻辑电平。 当OE为低时,8个锁存器的内容可被正常输出;当OE为高时,输出进入高阻态。OE端的操作不会影响锁存器的状态。 根据工作原理,将74HC573输出引脚和LED阳极引脚连接,输入引脚和单片机P0接口连接,LE引脚串联非门后和24译码器连接,而OE引脚接地保持正常工作。 ## 系统软件设计 初始化声明 ```c #define u8 unsigned char #define u16 unsigned int u8 code T_zhang[32] = { 0x01,0x00,0xF9,0x08,0x09,0x08,0x09,0x10,0x09,0x20,0x79,0x40,0x41,0x00,0x47,0xFE,0x41,0x40,0x79,0x20,     0x09,0x20,0x09,0x10,0x09,0x08,0x09,0x44,0x51,0x82,0x21,0x00 }; //行扫描 阴码 高位在前 u8 code T_yun[32] = { 0x10,0x00,0x10,0xFC,0x10,0x00,0xFE,0x00,0x10,0x00,0x7C,0x00,0x11,0xFE,0xFE,0x20,0x10,0x20,0x38,0x40,     0x34,0x48,0x54,0x84,0x50,0x84,0x91,0xFE,0x10,0x82,0x10,0x02 }; u8 code T_xuan[32] = { 0x00,0x40,0x00,0x20,0xFB,0xFE,0x22,0x02,0x24,0x04,0x21,0xFC,0x20,0x00,0xF9,0xFC,0x21,0x04,0x21,0xFC, 0x21,0x04,0x39,0xFC,0xE1,0x04,0x40,0x00,0x03,0xFE,0x00,0x00 }; // 封装成函数后字码需存放在ROM,若存放在RAM会出现字符的地址顺序和指针数组中的位置顺序相反的情况。即zhang的起始地址比yun高 u8 TAB_length = 3; // 字符个数 u8 *TAB_addr[] = {&T_zhang,&T_yun,&T_xuan}; // 存放字符阴码行数组地址(单向) u8 mode=0; // 显示模式 0横向左移 1纵向上移 2闪烁交替 3静态交替 int stay_ms = 750;   //内容驻留时长(ms) 可调范围250-1000 (位移间隔时长与闪烁文字交替时长) bit STAY_CHANGE = 0; //驻留时长改变标识 int scan_delay =3;   // 扫描间隔(ms) ``` 在ROM区存放字码数组,设置TAB_addr变量存放字码数组的地址,供程序调用字码,并给字符个数变量赋相应的值,标明内容的长度。 初始化模式变量和内容驻留时长变量,行扫描间隔设定为3毫秒。 延时函数: ```c void delay_ms(u16 x) {// 延时     u8 t;     while(x--){         for(t=0;t<100;t++);     } } ``` 延时x毫秒时长。 主程序 主程序流程图: ```c void main(){     IE = 0x85; //启用中断INT0和INT1 1000 0101     TCON = 0x05; //选择跳变触发方式 0000 0101          while (1){         if(mode==0){             left_move();         }else if(mode==1){             double_up_move();         }else if(mode==2){             flicker_display();         }else if(mode==3){             static_switch();         }     } } ``` 对中断允许寄存器和中断控制寄存器初始化,启用外部中断0和1并设为跳变触发,设置无限循环,循环中对模式变量mode判断后调用对应显示模式。 代码: ``` void left_move(){ // 逐列左移  模式0     u8 out_side;     int i;     int num;     int offset;     int time;     int stay_length = stay_ms / (scan_delay * 16); //内容驻留循环次数(分子值单位ms)     u8 l_high_char_half,l_high_char_filled,l_low_char_half,l_low_char_filled,r_high_char_half,r_high_char_filled,r_low_char_half,r_low_char_filled;     for(num=0;num>(8-offset)); // 左移后补充低位的移位部分                     l_low_char_half = *(TAB_addr[num]+2*i+1) << offset; //低位左移                     l_low_char_filled = l_low_char_half + (*(TAB_addr[num+1]+2*i) >>(8-offset)); // 左移后补充下个字符的高位移位部分                     r_high_char_half = *(TAB_addr[num+1]+2*i) << offset; //阵列2高位左移                     r_high_char_filled = r_high_char_half + (*(TAB_addr[num+1]+2*i+1) >>(8-offset)); // 左移后补充阵列2低位的移位部分                     r_low_char_half = *(TAB_addr[num+1]+2*i+1) << offset; //低位左移                     r_low_char_filled = r_low_char_half + (*(TAB_addr[num+2]+2*i) >>(8-offset)); // 左移后补充下个字符的高位移位部分                     if(num == (TAB_length-2)){                         // 阵列2打印倒数字时不显示补位                         r_low_char_filled = r_low_char_half; // 阵列2低位不使用补位部分(没有下个字符了)                     }                     if(num == (TAB_length-1)){                         // 打印最后字时不显示补位                         l_low_char_filled = l_low_char_half; // 低位不使用补位部分(没有下个字符了)                         r_high_char_filled = r_low_char_filled = 0x00;  // 阵列2段码已移动至阵列1,不输出                     }                                          row_data_send_byte(l_high_char_filled,l_low_char_filled,                         r_high_char_filled,r_low_char_filled,out_side);  //阳极,行(高位在左)                     out_side++;                     delay_ms(scan_delay);                 }             }             P2=0x00; // 消隐             delay_ms(5);         }         for(offset=0;offset<8;offset++){ // 列偏移量循环 单字后8位             for(time=0;time>(8-offset)); // 左移后补充低位的移位部分                     l_low_char_half = *(TAB_addr[num+1]+2*i) << offset; //低位左移                     l_low_char_filled = l_low_char_half + (*(TAB_addr[num+1]+2*i+1) >>(8-offset)); // 左移后补充下个字符的高位移位部分                                          r_high_char_half = *(TAB_addr[num+1]+2*i+1) << offset; //阵列2高位左移                     r_high_char_filled = r_high_char_half + (*(TAB_addr[num+2]+2*i) >>(8-offset)); // 左移后补充阵列2低位的移位部分                     r_low_char_half = *(TAB_addr[num+2]+2*i) << offset; //低位左移                     r_low_char_filled = r_low_char_half + (*(TAB_addr[num+2]+2*i+1) >>(8-offset)); // 左移后补充下个字符的高位移位部分                     if(num == (TAB_length-2)){                         // 阵列2打印倒数字时不显示补位                         r_high_char_filled = r_high_char_half; // 阵列2高位不使用补位部分(输出的段码已经是低位段码了)                         r_low_char_filled = 0x00; // 阵列2低位不使用补位部分(没有下个字符了)                     }                                          if(num == (TAB_length-1)){                         // 打印最后字时不显示补位                         l_high_char_filled = l_high_char_half; // 高位不使用补位部分(输出的段码已经是低位段码了)                         l_low_char_filled = 0x00;               // 字符的低位段码已移动至高位区,低位不输出                         r_high_char_filled = r_low_char_filled = 0x00;  // 阵列2段码已移动至阵列1,不输出                     }                     row_data_send_byte(l_high_char_filled,l_low_char_filled,                         r_high_char_filled,r_low_char_filled,out_side); //阳极,行(高位在左)                     out_side++;                     delay_ms(scan_delay);                 }             }             P2=0x00; // 消隐             delay_ms(5);         }     } } ``` 声明字码数组地址指针num,用于指向字符编码的起始位置。 声明out_side变量用于阴极码的运算。 声明offset偏移量,用于移位处理。 在每一轮扫描开始时,判断模式mode是否有改变,如果mode不是当前模式就跳出函数。判断内容停滞时长是否有改变,当时长有改变时重新计算停滞循环次数。 将一个文字的16行字码逐行循环显示,循环时长达到内容停滞时间后,偏移量offset+1。在下一轮显示中,每行的字码左移offset位,并读取下一个文字的移位部分填补空缺。 由于一个单位的行字码长度是8位,即字符编码长度16位的一半,因此offset达到8位后,开始新的一轮循环,显示剩下的半个字符的移位过程。下半个字符循环中,读取时的字码指针需要在上半个字符循环的基础上整体+1,以读取字符编码的低8位部分。 当下半个字符的循环结束后,字码数组地址指针+2,指向下一行的内容。 当显示内容是末尾几个字时,需要将读取了超出有效内容范围的补位数据丢弃,否则显示内容会包含无效的随机图案。 P2控制的译码器中,低3位是选通二进制编码,P2.3高电平选用阴极码低8位的译码器,P2.4高电平选用阴极码高8位的译码器,两个译码器的控制可通过进位实现。因此初始的传输值为0000 1000,扫描一行后自增1次,自增8次后值为0001 0000,刚好启用高8位译码器并关闭低8位译码器。 显示完一段内容后,进行消隐处理,防止干扰下一段内容的显示。 文字上移模式函数: 上移流程图: 代码: ```c void double_up_move(){ // 阵列整行上移    模式1     u8 out_side;         int i;     int TAB_index;     int offset;     int time;        int stay_length = stay_ms / (scan_delay * 16); //内容驻留循环次数(分子值单位ms)     u8 l_high_char,l_low_char,r_high_char,r_low_char;     for(TAB_index=0;TAB_index= (15 - offset)){                         out_side=0x00; // 打印最后字时不显示超出行                     }                                          l_high_char = *(TAB_addr[TAB_index]+2*(offset+i));                     l_low_char = *(TAB_addr[TAB_index]+2*(offset+i)+1);                     r_high_char = *(TAB_addr[TAB_index+1]+2*(offset+i));                     r_low_char = *(TAB_addr[TAB_index+1]+2*(offset+i)+1);                  if(offset>0 && i > (15 - offset)){                       // 单阵列显示补位段码时跳过一个字                         l_high_char = *(TAB_addr[TAB_index+1]+2*(offset+i)); //相对偏移量=一个字的行数(字符指针偏移一个单位)                         l_low_char = *(TAB_addr[TAB_index+1]+2*(offset+i)+1);                         r_high_char = *(TAB_addr[TAB_index+2]+2*(offset+i));                         r_low_char = *(TAB_addr[TAB_index+2]+2*(offset+i)+1);                         if(TAB_index+4>TAB_length && i >= (15 - offset)){ //(TAB_index+3)>(TAB_length-1)                             // 右阵列显示倒数第二个字时,不显示补位段码(没有下一行的字了)                                 r_high_char = r_low_char = 0x00;                         }                         if(TAB_index+3>TAB_length && i >= (15 - offset)){ //(TAB_index+2)>(TAB_length-1)                             // 左阵列显示倒数第二个字时,不显示补位段码(没有下一行的字了)                                 l_high_char = l_low_char = 0x00;                         }                  }                   if(TAB_index+2>TAB_length){ //(TAB_index+1)>(TAB_length-1)                         // 最后一个字不在右阵列时,右阵列不显示                         r_high_char = r_low_char = 0x00;                     }                     row_data_send_byte(l_high_char,l_low_char,                         r_high_char,r_low_char,out_side); //阳极,行(高位在左)                     out_side++;                     delay_ms(scan_delay);                 }             }             P2=0x00; // 消隐         }     } } ``` 声明字码数组地址指针TAB_index,用于指向字符编码的起始位置。 声明out_side变量用于阴极码的运算。 声明偏移量offset,用于移位处理。 将一个文字的16行字码逐行循环显示,循环时长达到内容停滞时间后,偏移量offset+1。由于字码是连续存放的,因此在下一轮显示中,只需要将指针加上行偏移量即可读取下一个文字的头几行,用于填补移位空缺。 当下半个字符的循环结束后,字码数组地址指针+2,指向下一行的内容。 当显示内容是末尾几个字时,需要将读取了超出有效内容范围的补位数据丢弃,否则显示内容会包含无效的随机图案。 显示完一段内容后,进行消隐处理,防止干扰下一段内容的显示。 文字闪烁模式函数: 闪烁流程图: 代码: ```c void flicker_display(){ // 闪烁    模式2     u8 out_side;         int i;     int num;     int time;     int display_ms = 200;    //文字闪烁间隔时长(ms)     int display_length = display_ms / (scan_delay * 16); // 文字闪烁循环次数     int stay_length = stay_ms / display_ms / 2 + 2; //内容驻留循环次数(分子值单位ms)          int show_i;      //文字闪烁循环计数变量     u8 l_high_char,l_low_char,r_high_char,r_low_char;     for(num=0;numTAB_length){                         // 最后一个字不在右阵列时,右阵列不显示                         r_high_char = r_low_char = 0x00;                     }                          row_data_send_byte(l_high_char,l_low_char,                         r_high_char,r_low_char,out_side); //阳极,行(高位在左)                     out_side++;                     delay_ms(scan_delay);                 }                }             for(show_i=0;show_iTAB_length){                     // 最后一个字不在右阵列时,右阵列不显示                     r_high_char = r_low_char = 0x00;                 }                 row_data_send_byte(l_high_char,l_low_char,                     r_high_char,r_low_char,out_side); //阳极,行(高位在左)                 out_side++;                 delay_ms(scan_delay);             }         }         P2=0x00; // 消隐              } } ``` 声明字码数组地址指针num,用于指向字符编码的起始位置。 声明out_side变量用于阴极码的运算。 将一个文字的16行字码逐行循环显示,循环时长达到内容停滞时间后显示下一行内容。 当下半个字符的循环结束后,字码数组地址指针+2,指向下一行的内容。 当右阵列显示内容超出有效范围时,将数据置零,不显示内容。 显示完一段内容后,进行消隐处理,防止干扰下一段内容的显示。 锁存器数据传输函数: ``` void row_data_send_byte(u8 datl,u8 datr,u8 datl2,u8 datr2,u8 out_side){ // 锁存器数据传输 分路控制 8位 // datl: 左阵列高4位 // datr: 左阵列低4位 // datl2: 右阵列高4位 // datr2: 右阵列低4位 // out_side: 扫描列的位码     P2=0x00;     // 消隐     P1=0x00;     //启用锁存器1     P0=datr;     // 传入低字节     P1=0x01;     //启用锁存器2     _nop_();_nop_();     P0=datl; // 传入高字节     P1=0x02;     //启用锁存器3     _nop_();_nop_();     P0=datr2;    // 传入低字节     P1=0x03;     //启用锁存器4     _nop_();_nop_();     P0=datl2;    // 传入高字节     P2=out_side; } ``` P2口先清空,对行消隐。然后从锁存器1开始使用P0口依次传输8位数据,并让其锁存。 中断函数: ```c void INT0_change_print_mode() interrupt 0 {// 切换模式     mode++;     if(mode>3) mode=0; } void INT1_change_print_mode() interrupt 2 {// 切换内容停留时长     if(stay_ms>1000) stay_ms=250;     stay_ms = stay_ms + 250;    STAY_CHANGE = 1; } ``` 外部中断0接口用于切换显示模式,按下一次后mode自增,当mode自增完超出最大范围后将mode重置为初始值0。 外部中断1接口用于切换内容停滞时长,按下一次后stay_ms增加250毫秒,当stay_ms超出最大范围后将其重置为初始值250毫秒。