UEFI学习——读取CMOS信息在Shell上显示时间并一直刷新

本文介绍了如何在UEFI环境下使用C语言编写一个应用程序,从CMOS中读取时间信息并以特定格式显示在屏幕上。程序通过端口映射I/O来交互,同时使用了TimerLib库的延时功能。作者在实现过程中遇到了找不到TimerLib的问题,通过修改项目配置解决了此问题。最终代码实现了每秒更新屏幕时间,同时增加了按键退出功能,提高了在物理机上的运行效果。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

       提取CMOS中的时间信息在Shell中以固定格式显示并一直刷新

       x86架构中有两种类型的IO,一种是内存映射的IO(即MMIO,Memory-mapped IO),另一种是端口映射IO(Port-mapped IO)。读取CMOS信息就是使用的端口映射IO,其中,端口0x70是用来设置CMOS中的数据地址,端口0x71是用来读取端口0x70设置的CMOS数据地址中的数据。
       简单的说,0x70端口写入的是数据地址,从0x71端口读取的是数据,是从0x70端口写入的地址中读取的数据。
       下面是从CMOS中读取系统时间,并按年/月/日 周几 时:分:秒显示,并一直刷新屏幕。
       .c文件:

#include <Uefi.h>  
#include <Library/UefiLib.h>
#include <Library/TimerLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/ShellCEntryLib.h>  
#include <Library/IoLib.h> 

 
#define CMOS_INDEX    0x70
#define CMOS_DATA     0x71 
 
INTN
EFIAPI
ShellAppMain (
  IN UINTN Argc,
  IN CHAR16 **Argv
  ) 
{
  UINT32 flag = 0;
//定义秒、分、时、周几、日、月、年并初始化
  UINT8 second = 0;
  UINT8 minute = 0;
  UINT8 hour = 0;
  UINT8 weekday = 0;
  UINT8 date = 0;
  UINT8 month = 0;
  UINT8 year = 0;
  
  while (flag < 100) { 
  //写入数据地址并读取,将结果返回给定义好的变量
    IoWrite8 (CMOS_INDEX, 0x00); 
    second = IoRead8 (CMOS_DATA);    
    IoWrite8 (CMOS_INDEX, 0x02); 
    minute = IoRead8 (CMOS_DATA);
    IoWrite8 (CMOS_INDEX, 0x04); 
    hour = IoRead8 (CMOS_DATA);
    IoWrite8 (CMOS_INDEX, 0x06); 
    weekday = IoRead8 (CMOS_DATA);
    IoWrite8 (CMOS_INDEX, 0x07); 
    date = IoRead8 (CMOS_DATA);
    IoWrite8 (CMOS_INDEX, 0x08); 
    month = IoRead8 (CMOS_DATA);
    IoWrite8 (CMOS_INDEX, 0x09); 
    year = IoRead8 (CMOS_DATA); 
    Print (L"%02x/%02x/%02x %02x %02x:%02x:%02x\n", year,month,date,weekday,hour,minute,second);  //用十六进制显示,之前用其他格式显示老出错
    MicroSecondDelay(1000*1000);  //每隔一秒显示一次
    gST->ConOut->ClearScreen(gST->ConOut);  //清屏
    flag++;
  } 
 
  return EFI_SUCCESS;
	
} 

       .inf文件:

[Defines]  
  INF_VERSION             = 0x00010005  
  BASE_NAME               = Cmoswls 
  FILE_GUID               = E4F3B912-F453-4AE8-B732-B8737229AB7A  
  MODULE_TYPE             = UEFI_APPLICATION  
  VERSION_STRING          = 1.0  
  ENTRY_POINT             = ShellCEntryLib  

[Sources]  
  CMOSwls.c  


[Packages]  
  MdePkg/MdePkg.dec  
  ShellPkg/ShellPkg.dec

[LibraryClasses]
  UefiLib
  IoLib
  TimerLib 
  ShellCEntryLib   
  UefiBootServicesTableLib

       先说写代码时遇到的错误,读取CMOS信息并显示没有问题,出问题的是调用延时函数MicroSecondDelay时老是提示找不到对应的库。
       错误信息如下:
build.py…
d:\myworkspace\edk2\ShellPkg\ShellPkg.dsc(…): error 4000: Instance of library class [TimerLib] is not found
in [d:\myworkspace\edk2\ShellPkg\WLS\CMOS\CMOSwls.inf] [X64]
consumed by module [d:\myworkspace\edk2\ShellPkg\WLS\CMOS\CMOSwls.inf]
       之前一直以为调用某个函数只要写好头文件,并在.inf文件中的[LibraryClasses]下加上Lib就好了,但有的Lib还需要在包含你.c和.inf的Pkg下的.dsc文件里加上你需要的Lib信息,我的.c和.inf是在ShellPkg下,所以在ShellPkg.dsc里的[LibraryClasses.common]中加入了TimerLib | MdePkg\Library\SecPeiDxeTimerLibCpu\SecPeiDxeTimerLibCpu.inf这一句。TimerLib 是要用到的库,MdePkg\Library\SecPeiDxeTimerLibCpu\SecPeiDxeTimerLibCpu.inf是和TimerLib 对应的.inf文件,SecPeiDxeTimerLibCpu.inf里的.c文件DxeCoreTimerLib.c中包含了MicroSecondDelay函数。
       在虚拟机上运行可以成功,在windows的模拟环境中会卡退,在物理机中加刷新屏幕函数就会很卡,体验不到屏幕刷新的效果。

       这是在虚拟机下拍的运行结果:

Cmos

       技术含量不高,后续会加上一些其他功能。

2021/8/26更新

       这次更新解决了在物理机中刷新屏幕卡顿的问题,新加了检测到任意按键程序退出功能。原先每秒的延时功能由MicroSecondDelay改为定时器事件,当一秒内未检测到按键则刷新屏幕。
       代码:

#include <Uefi.h>  
#include <Library/UefiLib.h>
#include <Library/TimerLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/ShellCEntryLib.h>  
#include <Library/IoLib.h> 

 
#define CMOS_INDEX    0x70
#define CMOS_DATA     0x71 
 
INTN
EFIAPI
ShellAppMain (
  IN UINTN Argc,
  IN CHAR16 **Argv
  ) 
{
  EFI_STATUS Status;
  EFI_EVENT Events[2] = {0}; 
  UINTN EventIndex = 0;

  UINT8 second = 0;
  UINT8 minute = 0;
  UINT8 hour = 0;
  UINT8 weekday = 0;
  UINT8 date = 0;
  UINT8 month = 0;
  UINT8 year = 0;



  //Events数组的0号事件为键盘事件
  Events[0] = gST->ConIn->WaitForKey;
  //生成定时器事件,并将事件对象赋值给Events数组的1号元素
  Status = gBS->CreateEvent(EVT_TIMER  , TPL_CALLBACK, (EFI_EVENT_NOTIFY)NULL, (VOID*)NULL, &Events[1]);
  //设置定时器属性,1秒(10 * 1000 * 1000 * 100ns = 1s)钟后到期
  Status = gBS->SetTimer(Events[1],TimerPeriodic , 10 * 1000 * 1000);
  
  while (1) { 
    IoWrite8 (CMOS_INDEX, 0x00); 
    second = IoRead8 (CMOS_DATA);    
    IoWrite8 (CMOS_INDEX, 0x02); 
    minute = IoRead8 (CMOS_DATA);
    IoWrite8 (CMOS_INDEX, 0x04); 
    hour = IoRead8 (CMOS_DATA);
    IoWrite8 (CMOS_INDEX, 0x06); 
    weekday = IoRead8 (CMOS_DATA);
    IoWrite8 (CMOS_INDEX, 0x07); 
    date = IoRead8 (CMOS_DATA);
    IoWrite8 (CMOS_INDEX, 0x08); 
    month = IoRead8 (CMOS_DATA);
    IoWrite8 (CMOS_INDEX, 0x09); 
    year = IoRead8 (CMOS_DATA); 
    Print (L"%02x/%02x/%02x %02x %02x:%02x:%02x\n", year,month,date,weekday,hour,minute,second);

    //等待Events数组钟任一事件发生,EventIndex返回触发的事件在Events中的位置
    Status = gBS->WaitForEvent (2, Events, &EventIndex);
    if(EFI_SUCCESS == Status)
    {
        if(EventIndex == 0){
            //键盘有输入
            break;
        }else if(EventIndex == 1){
            // 1 秒内无键盘操作则刷新屏幕
            gST->ConOut->ClearScreen(gST->ConOut);
            continue;
        }
    }
  }
  Status = gBS->CloseEvent(Events[1]);
 
  return Status;
	
} 

       这次的测试结果是在物理机上运行的(用U盘做的UEFI Shell环境,2021/8/27下午录制):

Cmos加按键定时器

评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值