drivers_day01

本文详细介绍了在嵌入式设备上移植Linux系统的全过程,包括安装系统、安装工具、搭建开发环境、移植uboot、内核、根文件系统,以及启动引导流程。重点阐述了移植过程中需要注意的细节,如确保硬件兼容性、配置交叉编译器、使用NFS网络文件系统等。同时,提供了实验步骤和实例指导,帮助开发者顺利进行嵌入式Linux系统的移植与开发。

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

三个要求:

1.安装linux系统

   ubuntu,fedora(u盘安装,硬盘安装)

   安装前备份重要的资料!

   手动分区!

2.安装工具和搭建开发环境

   tftp

   nfs

   vim + .vimrc + .vim(插件) /home/tarena/下)

   ctags / cscope :看源代码工具

   wine + SI

   openssh-server(远程登陆服务软件)

   kermit/minicom

   USB转串口:linux驱动无需安装(笔记本电脑没有串口,需要使用USB转串口线,先安装相应型号的USB转串口线驱动)

3.每天半小时笔试题,复习UC后续内容

********************************************************************************

嵌入式linux系统移植注意和开发内容:

明确:uboot,内核,根文件系统,代码都是官方提供的!

案例:某位同学上班,老板给一个TIARM开发板,需要一周时间把系统跑起来!

实施步骤:

1.查看是否有官方的操作文档

2.要明确开发板上的硬件信息,原理图,硬件说明书

   CPU

   内存(基地址,容量)

   闪存(nandflash,norflash,emmc,容量)

   串口

   网络

   USB

   接口(I2C,SPI,GPIO,CAN,UART(232,485,422))

3.获取交叉编译器(推荐使用官方提供的!)

   设置交叉编译器的环境变量!

   添加4.4.6交叉编译器的环境变量:

   1.vim /home/tarena/.bashrc

   将文件最后一行注释掉!

    保存退出!

   2. sudo vim /etc/environment 

   在PATH中添加4.4.6编译器的路径即可

  /home/tarena/workdir/toolchains/opt/S5PV210-crosstools/4.4.6/bin

   保存退出

   3.source /etc/environment   //重新生效新的环境变量

   4.arm-linux-gcc -v //查看编译器的版本

   5.which is arm-linux-gcc //查看编译器的路径是否正确

 

4.部署uboot(代码利用官方的)

   1.解压源码

   tar -jxvf / tar -zxvf / tar -jcvf /tar -zcvf

   2.清除源码中的配置文件和目标文件,上一次编译的结果

   make distclean

   3.针对具体的CPU和开发板进行配置源码

      make cw210_config  //cw210_config配置选项来自                   Makefile

   4.make all //编译,结果u-boot.bin

   5.烧写(JTAG,USB,SD,UART)

   6.涉及代码的修改:重点关注一个头文件:

   include/configs/cw210.h //全是开发板的硬件配置信息

 

5.kernel内核(使用官方的)

   1.解压内核源码

   2.清除源码的目标,配置信息

   make distclean //只做一遍,以后不用做!

   3.要针对CPU和开发板进行配置

   咱们使用的开发板的配置如下:

   参考实验步骤

   如果使用其他的开发板标准配置如下:

   make xxx_defconfig

   或者

   cp arch/arm/configs/xxx_defconfig .config

   如果不支持,不配置,默认会针对X86架构!

   4.make menuconfig //做三个检查

     检查是否支持ARM架构

     检查是否支持当前CPU

     检查是否支持当前开发板

   5.make zImage /make uImage

   6.cp arch/arm/boot/zImage /tftpboot

   7.内核移植或者驱动开发,都需要多多看内核的平台代码文件

    arch/arm/mach-s5pv210/mach-cw210.c

 

6.根文件系统rootfs(体力活)

   利用busybox进行制作

 

7.系统启动引导

   关键看uboot的两个参数:bootcmd,bootargs

   bootcmd:用于加载内核到内存,然后启动内核

   bootargs:内核启动以后,用于内核挂接根文件系统

 

   软件开发前期一般都使用NFS网络文件系统:

   setenv bootcmd tftp 20008000 zImage \;bootm                        20008000

   setenv bootargs root=/dev/nfsnfsroot=192.168.1.8:/opt/rootfsip=192.168.1.110:192.168.1.8:192.168.1.1:255.255.255.0::eth0:on init=/linuxrcconsole=ttySAC0,115200

  saveenv

 

注意事项:

1.init=/linuxrc信息可以不用写,如果不用写,启动的第一个进程为/sbin/init进程,如果/sbin/init没有,启动第一个进程/bin/init,如果它也没有,就执行/bin/sh(如果这行/bin/sh以后,会造成rcS和profile无法得到执行,所以千万要注意init=/bin/sh这种写法)

2.给内核传递启动参数,不仅仅可以通过uboot的bootargs,还可以通过内核给自己传递参数!

    cd /opt/kernel

    make menuconfig

     Boot options  --->

    (console=ttySAC2,115200) Default kernelcommand                         string

     //光标移动到这个位置,然后按回车键进行编辑,例如采用NFS网络文件系统启动:

    root=/dev/nfs nfsroot=...

 

 [ ]  Always use the default kernel command string  //如果这个选项选择为*,内核使用自己的参数信息,否则使用uboot的bootargs参数

 

   查看内核的启动参数信息:

   cat /proc/cmdline

 

实验步骤:

1.从ftp下载官方内核源码:

   Kernel_2.6.35.7_CW210_for_Linux_v1.0.tar.gz

2.修改/opt/的用户和组

   sudo chown tarena /opt -R

   sudo chgrp tarena /opt -R

  把源码包放置在/opt/目录

 

3.解压内核源码

  tar -xvfKernel_2.6.35.7_CW210_for_Linux_v1.0.tar.gz

  mv cw210... kernel //重命名为kernel目录

 

4.cd /opt/kernel

   make distclean  //清除

   cp config_CW210_linux_V1.0 .config //进行配置

5.makemenuconfig  //三个检查

   System type->

      ARM system(SAMSUNG S5PV210...)

      ...

                  board ... (smdkv210) //开发板

6.make zImage

   cp arch/arm/boot/zImage /tftpboot

 

7.根文件系统一律是/opt/rootfs

   cp 做好的rootfs /opt/ -frd

   sudo vim /etc/exports/  添加/opt/rootfs

   sudo /etc/init.d/nfs-kernel-server restart

 

8.uboot设置启动参数

setenv bootcmdtftp 20008000 zImage \; bootm                      20008000

   setenv bootargs root=/dev/nfsnfsroot=192.168.1.8:/opt/rootfsip=192.168.1.110:192.168.1.8:192.168.1.1:255.255.255.0::eth0:on init=/linuxrcconsole=ttySAC0,115200

  saveenv

 

9.结果:使用官方内核启动NFS网络文件系统

 

注意:嵌入式linux开发,玩的是硬件,如果软件没有问题,一定要注意硬件是否存在问题,通过更换硬件对比测试!

 

问题:

Root-NFS: Serverreturned error -13 while mounting /opt/rootfs :原因是PC的NFS网络服务没有开启,并没有指定共享目录

 

问题:如果PC的网络服务都启动,并且bootargs也指定了,如果还出现无法挂接NFS    ,要注意内核需要添加NFS网络文件系统的支持:

make menuconfig

File system->

      networking file system->

      <*>NFS client support

      <*>Root file system on NFS (重要)

                  <*>NFS server support

问题: 

/dev/console:不允许操作

/dev/null:不允许操作

答案:甭搭理

 

***********************************************************************************

linux系统分为用户空间和内核空间:

用户空间:包含用户编写的应用软件,C

      整个4G虚拟地址空间,用户空间包含了0~3G

      每个用户进程都有自己独立的3G空间

 

内核空间:包含驱动,文件系统,平台代码等等,内核空间包含了剩余1G(3G~4G),内核1G空间对于所有的进程都是共享

 

用户空间和内核空间的划分本质上要依赖ARM的工作模式,

用户空间的软件在运行时,CPU处于USR模式,内核空间的软件运行时,CPU处于SVC模式,这种模式划分,决定了用户空间的软件和内核空间的软件在访问资源时,具有的权限是不一样的!

 

注意事项:

1.用户空间的软件不能直接访问内核空间的地址包括代码和数据,例如一个应用程序不能调用驱动的某个函数!

2.用户空间不能直接访问硬件资源,例如,一个应用程序不能直接访问CPU的GPIO寄存器!

3.用户空间的软件和内核空间软件进行交互,必须通过唯一的途径--系统调用(open,close,read,write,ioctl,mmap,fork,exit...)

4.用户软件利用系统调用进入内核空间,内核会给这个进程重新分配一个内核栈(8K,在内核空间编程时,要注意局部变量的大小!

5.由于空间的划分依赖ARM的工作模式,也就决定了他们访问资源的权限不同,起到操作系统的保护。例如,如果在应用程序访问NULL指针,操作系统有权干掉这个应用程序。如果在内核编程时,访问NULL指针,操作系统崩溃!

6.类似printf,malloc这些都是标准库函数,read,write等都是系统调用函数,这些函数的实现都是在C库中。

案例:向终端打印输出hello,world

          prinf("hello,world");最终调用write

          write(1, "hello,world",strlen("hello,world"));

查看应用程序运行的过程:strace ./a.out

 

linux内核(zImage)包含7大子系统

1.设备驱动

2.内存管理

3.网络协议栈

4.进程管理

5.系统调用

6.平台代码

7.文件系统

 

linux内核配置编译过程:

回顾:* m Kconfig Makefile

案例:LM77温度传感器,实现内核这个传感器驱动。

cd /opt/kernel

make menucofig

按键"/"搜索关键字"LM77",提示信息如下:

│ Symbol: SENSORS_LM77 [=n]                                  │ 

  │ Prompt: National Semiconductor LM77                        │ 

  │   Defined atdrivers/hwmon/Kconfig:509                    │ 

  │   Depends on: HWMON [=y]&& I2C [=y]                       │ 

  │   Location:                                               │ 

  │     -> Device Drivers                                      │ 

  │       -> HardwareMonitoring support (HWMON [=y])

 

以上信息说明,内核支持这个驱动!

Symbol:SENSORS_LM77-》CONFIG_SENSORS_LM77

  Location:                                               │ 

  │     -> Device Drivers                                      │ 

  │       -> HardwareMonitoring support (HWMON [=y])

代表这个配置选项的位置,只需选中为*

 

问:通过以上的配置信息,如果找到源代码?

zhangsan.c

Kconfig:

config ZHANGSAN->CONFIG_ZHANGSAN

      ......

Makefile

obj-$(CONFIG_ZHANGSAN)+= zhangsan.o

 

如果选中为*->CONFIG_ZHANGSAN=y -》

      obj-y +=zhangsan.o //静态编译

如果选择为M-》CONFIG_ZHANGSAN=m

      obj-m +=zhangsan.o //模块化编译

 

通过CONFIG_SENSORS_LM77,在Makefile中进行搜索,找到对应的源码

 

问:如果找到源码,如何找到对应的配置选项信息?

***********************************************************************************

一些常用命令:       

1.搜索匹配字符串命令:

grep "zhangsan"  ./*(指定目录下的所有文件) -R

在指定目录下的所有文件中(.表示当前目录,*表示所有文件)查找指定的字符串,-R表示递归到子目录查找

2.在指定的目录下查找文件:

find  .(指定目录) -name  filename

在指定目录下找文件filename

3.vim分屏:

(1)左右分屏:进入vim的命令行模式,键入vs 文件名,

例如:在已经用vi打开的hello.c下的命令行下,输入vs swap.c

(2)上下分屏:进入vim的命令行模式,进入sp 文件名

例如:在已经用vi打开的hello.c下的命令行下,输入sp swap.c

(3)屏幕切换:ctrl + ww

在分屏模式下,用ctrl+ww 来进行屏的切换

vim自动补全:ctrl + n

行选:shift +v

列选:ctrl + v

方向键:hjkl

4.复制、删除

命令行下:

n,m coh 将从n行到m行的内容复制到第h行之后,例如:5,10 co 12

nm del 将从n行到m行的内容删除,例如:5,10 del

不在命令行,也不在编辑模式下,而在一般模式下

n yy  从光标所在行开始,复制n行

p将yy复制的内容粘贴到光标所在行的下一行

n dd 从光标所在行开始,删除n行

-------------------------------------------------------------------------------------------------------------------------------------------------------                

总结:

在一个新的板子上跑操作系统

(1)电脑装linux系统

(2)装vim 配置,插件

(3)装tftp、nfs服务,并配置

(4)装交叉编译器arm-linux-gcc

(5)编译开发板配套u-boot

(6)编译开发板配套kernel

(7)制作根文件系统,利用busybox


#include <avr/io.h> #include <avr/interrupt.h> #include <util/delay.h> #include <avr/eeprom.h> #include <string.h> #define delay_ms(x) _delay_ms(x) // LCD 相关引脚定义 #define LCD_RS PC0 #define LCD_RW PC1 #define LCD_E PC2 #define LCD_D4 PA4 #define LCD_D5 PA5 #define LCD_D6 PA6 #define LCD_D7 PA7 // 按键引脚定义(使用PORTD) #define SET_BUTTON PIND0 #define ADD_BUTTON PIND1 #define SUB_BUTTON PIND2 // LED 引脚定义 #define LED_PIN PB3 // EEPROM存储地址 #define EEPROM_TIME_HOUR_ADDR 0x00 #define EEPROM_TIME_MINUTE_ADDR 0x01 #define EEPROM_DATE_YEAR_ADDR 0x02 #define EEPROM_DATE_MONTH_ADDR 0x03 #define EEPROM_DATE_DAY_ADDR 0x04 // 全局变量 volatile uint8_t hour = 12; // 小时(0-23) volatile uint8_t minute = 0; // 分钟(0-59) volatile uint8_t second = 0; // 秒(0-59) volatile uint8_t year = 23; // 年份(后两位) volatile uint8_t month = 1; // 月份(1-12) volatile uint8_t day = 1; // 日期(1-31) volatile uint8_t set_mode = 0; // 0:正常显示, 1:设置小时, 2:设置分钟, 3:设置年, 4:设置月, 5:设置日 volatile uint8_t blink_state = 0; // 闪烁状态 volatile uint8_t timer1_counter = 0; // 定时器计数器 // 闹钟结构 typedef struct { uint8_t hour; uint8_t minute; uint8_t enabled; } Alarm; Alarm alarm1 = {8, 0, 0}; // 闹钟1,默认8:00,关闭 Alarm alarm2 = {12, 0, 0}; // 闹钟2,默认12:00,关闭 // LCD初始化 void LCD_Init() { // 设置数据端口为输出 DDRA |= (1<<LCD_D4) | (1<<LCD_D5) | (1<<LCD_D6) | (1<<LCD_D7); // 设置控制端口为输出 DDRC |= (1<<LCD_RS) | (1<<LCD_RW) | (1<<LCD_E); // 初始化序列 delay_ms(50); // 等待LCD上电稳定 // 4位模式初始化 LCD_Write_Command(0x33); LCD_Write_Command(0x32); LCD_Write_Command(0x28); // 4位模式,2行显示,5x8点阵 LCD_Write_Command(0x0C); // 显示开,光标关,闪烁关 LCD_Write_Command(0x06); // 增量模式,不移位 LCD_Write_Command(0x01); // 清屏 delay_ms(2); } // 向LCD发送命令 void LCD_Write_Command(unsigned char cmd) { PORTC &= ~(1<<LCD_RS); // RS=0 命令模式 PORTC &= ~(1<<LCD_RW); // RW=0 写入 // 发送高4位 PORTA = (PORTA & 0x0F) | (cmd & 0xF0); PORTC |= (1<<LCD_E); // E=1 delay_ms(1); PORTC &= ~(1<<LCD_E); // E=0 // 发送低4位 PORTA = (PORTA & 0x0F) | ((cmd << 4) & 0xF0); PORTC |= (1<<LCD_E); // E=1 delay_ms(1); PORTC &= ~(1<<LCD_E); // E=0 delay_ms(1); } // 向LCD发送数据 void LCD_Write_Data(unsigned char data) { PORTC |= (1<<LCD_RS); // RS=1 数据模式 PORTC &= ~(1<<LCD_RW); // RW=0 写入 // 发送高4位 PORTA = (PORTA & 0x0F) | (data & 0xF0); PORTC |= (1<<LCD_E); // E=1 delay_ms(1); PORTC &= ~(1<<LCD_E); // E=0 // 发送低4位 PORTA = (PORTA & 0x0F) | ((data << 4) & 0xF0); PORTC |= (1<<LCD_E); // E=1 delay_ms(1); PORTC &= ~(1<<LCD_E); // E=0 delay_ms(1); } // 在LCD上显示字符串 void LCD_Display_String(char *str) { while (*str) { LCD_Write_Data(*str++); } } // 设置LCD光标位置 void LCD_Set_Cursor(uint8_t row, uint8_t col) { uint8_t address; if (row == 0) { address = 0x80 + col; } else { address = 0xC0 + col; } LCD_Write_Command(address); } // 显示时间 void Display_Time() { char buffer[16]; // 第一行显示时间 LCD_Set_Cursor(0, 4); sprintf(buffer, "%02d:%02d:%02d", hour, minute, second); LCD_Display_String(buffer); // 第二行显示日期 LCD_Set_Cursor(1, 4); sprintf(buffer, "20%02d-%02d-%02d", year, month, day); LCD_Display_String(buffer); } // 显示设置模式 void Display_Set_Mode() { char buffer[16]; LCD_Set_Cursor(0, 0); LCD_Display_String("Set:"); switch(set_mode) { case 1: // 设置小时 if(blink_state) { sprintf(buffer, "Hour: %02d", hour); } else { sprintf(buffer, "Hour: "); } break; case 2: // 设置分钟 if(blink_state) { sprintf(buffer, "Minute:%02d", minute); } else { sprintf(buffer, "Minute: "); } break; case 3: // 设置年 if(blink_state) { sprintf(buffer, "Year: 20%02d", year); } else { sprintf(buffer, "Year: "); } break; case 4: // 设置月 if(blink_state) { sprintf(buffer, "Month: %02d", month); } else { sprintf(buffer, "Month: "); } break; case 5: // 设置日 if(blink_state) { sprintf(buffer, "Day: %02d", day); } else { sprintf(buffer, "Day: "); } break; default: sprintf(buffer, "Normal Mode "); } LCD_Set_Cursor(0, 5); LCD_Display_String(buffer); // 仍然显示时间日期 LCD_Set_Cursor(1, 0); sprintf(buffer, "Time:%02d:%02d:%02d", hour, minute, second); LCD_Display_String(buffer); } // 从EEPROM加载时间日期 void Load_Time_From_EEPROM() { hour = eeprom_read_byte((uint8_t*)EEPROM_TIME_HOUR_ADDR); minute = eeprom_read_byte((uint8_t*)EEPROM_TIME_MINUTE_ADDR); year = eeprom_read_byte((uint8_t*)EEPROM_DATE_YEAR_ADDR); month = eeprom_read_byte((uint8_t*)EEPROM_DATE_MONTH_ADDR); day = eeprom_read_byte((uint8_t*)EEPROM_DATE_DAY_ADDR); // 检查读取的值是否有效 if(hour > 23) hour = 0; if(minute > 59) minute = 0; if(year > 99) year = 23; if(month == 0 || month > 12) month = 1; if(day == 0 || day > 31) day = 1; } // 保存时间日期到EEPROM void Save_Time_To_EEPROM() { eeprom_update_byte((uint8_t*)EEPROM_TIME_HOUR_ADDR, hour); eeprom_update_byte((uint8_t*)EEPROM_TIME_MINUTE_ADDR, minute); eeprom_update_byte((uint8_t*)EEPROM_DATE_YEAR_ADDR, year); eeprom_update_byte((uint8_t*)EEPROM_DATE_MONTH_ADDR, month); eeprom_update_byte((uint8_t*)EEPROM_DATE_DAY_ADDR, day); } // 初始化定时器1 (1秒中断) void Timer1_Init() { // 设置定时器1为CTC模式 TCCR1B |= (1 << WGM12); // 设置预分频为1024 TCCR1B |= (1 << CS12) | (1 << CS10); // 设置比较值 (16MHz/1024 = 15625 ticks/sec, 15625 ticks = 1秒) OCR1A = 15625; // 启用比较匹配中断 TIMSK |= (1 << OCIE1A); } // 初始化按键引脚 void Buttons_Init() { // 设置按键引脚为输入,启用上拉电阻 DDRD &= ~((1<<SET_BUTTON) | (1<<ADD_BUTTON) | (1<<SUB_BUTTON)); PORTD |= (1<<SET_BUTTON) | (1<<ADD_BUTTON) | (1<<SUB_BUTTON); } // 按键扫描 void Key_Scan() { static uint8_t last_set_state = 1; static uint8_t last_add_state = 1; static uint8_t last_sub_state = 1; uint8_t current_set_state = PIND & (1<<SET_BUTTON); uint8_t current_add_state = PIND & (1<<ADD_BUTTON); uint8_t current_sub_state = PIND & (1<<SUB_BUTTON); // 检测设置按键按下 if(last_set_state && !current_set_state) { _delay_ms(20); // 消抖 if(!(PIND & (1<<SET_BUTTON))) { set_mode++; if(set_mode > 5) set_mode = 0; if(set_mode == 0) { // 退出设置模式,保存时间到EEPROM Save_Time_To_EEPROM(); } } } // 只在设置模式下检测加减按键 if(set_mode > 0) { // 检测增加按键按下 if(last_add_state && !current_add_state) { _delay_ms(20); // 消抖 if(!(PIND & (1<<ADD_BUTTON))) { switch(set_mode) { case 1: // 增加小时 hour = (hour + 1) % 24; break; case 2: // 增加分钟 minute = (minute + 1) % 60; break; case 3: // 增加年 year = (year + 1) % 100; break; case 4: // 增加月 month = (month % 12) + 1; break; case 5: // 增加日 day = (day % 31) + 1; break; } } } // 检测减少按键按下 if(last_sub_state && !current_sub_state) { _delay_ms(20); // 消抖 if(!(PIND & (1<<SUB_BUTTON))) { switch(set_mode) { case 1: // 减少小时 hour = (hour == 0) ? 23 : hour - 1; break; case 2: // 减少分钟 minute = (minute == 0) ? 59 : minute - 1; break; case 3: // 减少年 year = (year == 0) ? 99 : year - 1; break; case 4: // 减少月 month = (month == 1) ? 12 : month - 1; break; case 5: // 减少日 day = (day == 1) ? 31 : day - 1; break; } } } } last_set_state = current_set_state; last_add_state = current_add_state; last_sub_state = current_sub_state; } // 检查闹钟 void Check_Alarms() { static uint8_t alarm1_triggered = 0; static uint8_t alarm2_triggered = 0; // 检查闹钟1 if(alarm1.enabled && !alarm1_triggered && hour == alarm1.hour && minute == alarm1.minute && second == 0) { alarm1_triggered = 1; PORTB |= (1<<LED_PIN); // 打开LED } // 检查闹钟2 if(alarm2.enabled && !alarm2_triggered && hour == alarm2.hour && minute == alarm2.minute && second == 0) { alarm2_triggered = 1; PORTB |= (1<<LED_PIN); // 打开LED } // 每分钟重置闹钟触发标志 if(second == 0) { alarm1_triggered = 0; alarm2_triggered = 0; } // 如果LED亮起,检测按键关闭 if(PORTB & (1<<LED_PIN)) { if(!(PIND & (1<<SET_BUTTON)) || !(PIND & (1<<ADD_BUTTON)) || !(PIND & (1<<SUB_BUTTON))) { PORTB &= ~(1<<LED_PIN); // 关闭LED } } } // 定时器1比较匹配中断服务程序 ISR(TIMER1_COMPA_vect) { // 更新时间 second++; if(second >= 60) { second = 0; minute++; if(minute >= 60) { minute = 0; hour++; if(hour >= 24) { hour = 0; // 日期增加逻辑 day++; uint8_t max_day = 31; // 处理不同月份的天数 if(month == 4 || month == 6 || month == 9 || month == 11) { max_day = 30; } else if(month == 2) { // 简单处理2月天数(不考虑闰年) max_day = 28; } if(day > max_day) { day = 1; month++; if(month > 12) { month = 1; year++; if(year > 99) year = 0; } } } } } // 更新闪烁状态 (0.5秒周期) timer1_counter++; if(timer1_counter >= 5) { // 10次 = 1秒 (0.1秒中断) timer1_counter = 0; blink_state = !blink_state; } // 检查闹钟 Check_Alarms(); } int main(void) { // 初始化端口 DDRB |= (1<<LED_PIN); // LED引脚为输出 PORTB &= ~(1<<LED_PIN); // 初始关闭LED // 初始化按键 Buttons_Init(); // 初始化LCD LCD_Init(); // 从EEPROM加载时间 Load_Time_From_EEPROM(); // 初始化定时器 Timer1_Init(); // 启用全局中断 sei(); // 清屏并显示欢迎信息 LCD_Write_Command(0x01); LCD_Set_Cursor(0, 3); LCD_Display_String("AVR Clock"); LCD_Set_Cursor(1, 2); LCD_Display_String("Initializing..."); delay_ms(1000); LCD_Write_Command(0x01); while(1) { // 扫描按键 Key_Scan(); // 更新显示 if(set_mode == 0) { Display_Time(); } else { Display_Set_Mode(); } // 短暂延迟 delay_ms(100); } return 0; }使用的atmega128单片机,不改动引脚的使用
最新发布
05-27
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值