• SoC中有4个串口,但开发板上只引出了两个(uart0,uart2),默认通过uart2输出,尝试通过修改代码把它改uart0输出.

    SoC中有4个串口,但开发板上只引出了两个(uart0,uart2),默认通过uart2输出,

    尝试通过修改代码把它改到uart0输出.

    在 "\u-boot-samsung-dev\include\configs" 路径下的 smdkv210single.h 中,修改146行 


    这是根据 "\u-boot-samsung-dev\include" 下的 s5pc110.h 中的 uart 描述得来的,如下图



    其它数据也可以按自己需要改一下,如下图:





    删除iNand中的ENV分区信息

    mmc  write 0  30000000  11#  32                          

     注 0 代表 内部的iNand    1 代表 外部SD卡  30000000 代表内存里的地址 11#代表扇区号 32代表该扇区的大小

    表示将DDR的0x30000000开头的一段内存中的内容写入iNand中的11#扇区,写32个扇区的大小


    展开全文
  • 最近,因为项目的需要,我们购买了广州致远电子有限公司的M6708-T工控板(预装Linux系统),准备将sqlite3移植本嵌入式开发板上。笔者参考了网上的很多文章,均无法达到成功移植的目的。在多方摸索之下,终于采用了...

    最近,因为项目的需要,我们购买了广州致远电子有限公司的M6708-T工控板(预装Linux系统),准备将sqlite3移植到本嵌入式开发板上。笔者参考了网上的很多文章,均无法达到成功移植的目的。在多方摸索之下,终于采用了一种简单的方法成功实现了移植。本文介绍如何成功地将sqlite3移植到M6708-T工控板上,可供相关的开发人员参考。

    准备工作
    在本机上安装好Linux系统(一般都是安装在虚拟机上面的),并按照工控板的用户手册在Linux系统上安装交叉开发的工具链。
    将工控板上电,能够通过串口或网络等方式登录到Linux系统中。这里建议为工控板设置静态的ip地址,这样以后上传程序比较方便。
    登录到sqlite的官网(https://www.sqlite.org/)上去下载最新的sqlite源代码,如下截图所示:
    这里写图片描述

    移植步骤
    第一步:将sqlite源代码拷贝到虚拟机的Linux系统目录中,并使用命令tar xvf sqlite-autoconf-3200100.tar.gz解压,生成sqlite-autoconf-3200100目录,转到该目录中,可看到文件列表如下截图所示:
    这里写图片描述
    第二步:使用交叉编译工具编译代码,生成sqlite的可执行文件。在编译之前,可以用echo $CC命令查看系统上的编译工具是否是交叉编译工具而非系统自带的gcc,执行该命令后的结果如下截图所示:
    这里写图片描述
    确认交叉编译工具正确之后,便可执行命令$CC sqlite3.c shell.c -o sqlite3 -lpthread –ldl生成可执行文件sqlite3,执行该命令后的结果如下截图所示:
    这里写图片描述
    第三步:将生成的可执行文件sqlite3从虚拟机拷贝到本机目录下,并利用FTP工具将之上传到工控板的目录下。这里,笔者使用了FileZilla软件将sqlite3上传到/usr/local/DT/sqlite目录下,如下截图所示:
    这里写图片描述
    第四步:将上传之后的sqlite3文件移动到/usr/bin目录下,并赋予可执行的权限。执行mv sqlite3 /usr/bin/命令移动文件,并使用命令chmod 777 sqlite3修改权限。命令执行过程的截图如下所示:
    这里写图片描述
    第五步:执行命令sqlite3即可成功启动数据库。数据库启动之后的截图如下所示:
    这里写图片描述
    通过以上五个步骤,sqlite就被成功移植到了工控板上,接下来便可以进行建表等操作了,也可以编写代码调用数据库的API实现对数据的存储及读取操作了。

    总结
    在很多参考文章中,都是通过运行configure和make命令来生成sqlite3可执行文件的,笔者最开始也使用了这种方法,但生成的可执行文件在工控板上无法运行。笔者重复试了很多次,也没有找到无法成功移植的原因。因此采用了在本文中描述的方法,轻松实现了sqlite数据库的移植。
    当然,相比于PC版的Linux来说,嵌入式开发板上的Linux的功能被裁剪了很多,这也导致了很多命令是没有的。我们在将一些程序移植到嵌入式开发板上的时候,一定要意识到它与普通的操作系统之间的差别。


    这里写图片描述

    展开全文
  • 华恒ColdFire系列嵌入式Linux开发板常见问题解答 ——基于公用核心板HHCF5249/5272 (华恒科技版权所有) 1、关于宿主机Linux的安装 建议使用REDHAT系列:建议选择完全安装,即选择Custom...
    华恒ColdFire系列嵌入式Linux开发板常见问题解答
    ——基于公用核心板HHCF5249/5272
    (华恒科技版权所有)
    1、关于宿主机Linux的安装
    建议使用REDHAT系列:建议选择完全安装,即选择Custom,然后在Package中选择everything。
    【以下配置详见最新PDF手册下载(例如:HHCO5272-R1的手册)】

    宿主机的网络配置
    主要是要安装好以太网卡,对于一般常见的RTL8139网卡,REDHAT7.2可以自动识别并自动安装好,完全不要用户参与,因此建议使用该网卡。然后配置宿主机IP:
    ifconfig eth0 192.168.2.32
    【注意】
    对于REDHAT7.2,它默认的是打开了防火墙,因此对于外来的IP访问它全部拒绝,这样其它网络设备根本无法访问它,即无法用NFS mount它,无法通过TFTP从它下载,无法telnet,ftp它等。因此网络安装完毕后,应立即关闭防火墙。操作如下:
    运行setup,选择Firewall configuration,选中No firewall。然后到上一层菜单选择System services,去掉ipchains和iptables两项服务。最后退出setup。
    其实,在安装REDHAT7.2/3时,就要求选择Custom定制安装,其中由一项就是要求选择No Firewall,这样的话,启动后,就不需要执行setup来设置防火墙了。还有,REDHAT在这里有个BUG,即无论上次你进入Firewall configuration选择什么,每次再次进入时它都显示High,这是REDHAT显示的BUG,其实防火墙已经关闭了。

    配置NFS
    运行linuxconf,在config选项下选Server tasks,选中Exported File systems(NFS),然后选择Add Directory,加入根目录/,然后Accept。系统就会输出根目录允许NFS mount。
    下一步再选择Control项下面Control panel下的Control Service activity,然后选择nfs enabled,然后start。配置好后的界面显示其中nfs必须为: Automatic Running。
    【注意】
    这里建议把ipchains和iptables都取消其自动启动的状态。
    最后,在Control项下面Control panel下选择Activate configuration,则弹出界面,提示系统配置的改动,选择"Do it",最后退出时则完成NFS配置。
    配置完成后,可用如下办法简单测试一下NFS是否配置好了:
    在宿主机上自己mount自己,看是否成功就可以判断NFS是否配好了。例如在宿主机/目录下执行:
    mount 192.168.2.32:/ /mnt
    然后到/mnt/目录下看是否可以列出/目录下的所有文件和目录,可以则说明mount成功,NFS配置成功。 

    配置TFTP服务器 
    参见下面第二点介绍。
    【注意】
    安装完华恒uClinux软件光盘后,不要make xconfig,直接make即可。加入驱动等修改内核的操作都不必make xconfig,除非要更改处理器平台,例如移植到MCF5407等才需要make xconfig。其实这个操作不过就是修改linux/.config和linux/include/linux/autoconfig.h中的宏设置。完全可以由手工完成。

    2、关于gdbtftpflash烧写
    【注意】
    烧写必须接百兆以太网接口,对于多以太网板的10M口是不能用来烧写的。
    在一个Linux TTY终端执行
    ./flash
    然后立刻切换到另一个TTY终端启动的minicom下去查看信息
    Reading image.bin from 192.168.2.46 to 0x00100000
    TFTP download successful
    或者信息如下:
    ICMP: Port unreachable
    ICMP: Port unreachable
    ICMP: Port unreachable
    TFTP could not make connection to server
    Errors in TFTP download

    Read 1004740 bytes (1963 blocks)【这才是关键所在,前面的信息都无用,只要这里读到的字节数和宿主机上/tftpboot/目录下的image.bin文件的大小一致就表明TFTP下载成功了!】
    >>>>>>>>>> Init mflash
    >>>>>>>>>> Init mflash Successfully
    ********** File size : 0xF54C4 bytes
    ********** Address base : 0x0
    ********** Manufacturer ID 1
    ********** Device ID 2249
    ********** Sector 0 [FFC00000] (0-unprotect, 1 protect):0
    ********** Sector 1 [FFC04000] (0-unprotect, 1 protect):0
    ********** Sector 2 [FFC06000] (0-unprotect, 1 protect):0
    ********** Sector 3 [FFC08000] (0-unprotect, 1 protect):0
    ********** Sector 4 [FFC10000] (0-unprotect, 1 protect):0
    ********** Sector 5 [FFC20000] (0-unprotect, 1 protect):0
    ********** Sector 6 [FFC30000] (0-unprotect, 1 protect):0
    ********** Sector 7 [FFC40000] (0-unprotect, 1 protect):0
    ********** Sector 8 [FFC50000] (0-unprotect, 1 protect):0
    ********** Sector 9 [FFC60000] (0-unprotect, 1 protect):0
    ********** Sector 10 [FFC70000] (0-unprotect, 1 protect):0
    ********** Sector 11 [FFC80000] (0-unprotect, 1 protect):0
    ********** Sector 12 [FFC90000] (0-unprotect, 1 protect):0
    ********** Sector 13 [FFCA0000] (0-unprotect, 1 protect):0
    ********** Sector 14 [FFCB0000] (0-unprotect, 1 protect):0
    ********** Sector 15 [FFCC0000] (0-unprotect, 1 protect):0
    ********** Sector 16 [FFCD0000] (0-unprotect, 1 protect):0
    ********** Sector 17 [FFCE0000] (0-unprotect, 1 protect):0
    ********** Sector 18 [FFCF0000] (0-unprotect, 1 protect):0
    ********** Sector 19 [FFD00000] (0-unprotect, 1 protect):0
    ********** Sector 20 [FFD10000] (0-unprotect, 1 protect):0
    ********** Sector 21 [FFD20000] (0-unprotect, 1 protect):0
    ********** Sector 22 [FFD30000] (0-unprotect, 1 protect):0
    ********** Sector 23 [FFD40000] (0-unprotect, 1 protect):0
    ********** Sector 24 [FFD50000] (0-unprotect, 1 protect):0
    ********** Sector 25 [FFD60000] (0-unprotect, 1 protect):0
    ********** Sector 26 [FFD70000] (0-unprotect, 1 protect):0
    ********** Sector 27 [FFD80000] (0-unprotect, 1 protect):0
    ********** Sector 28 [FFD90000] (0-unprotect, 1 protect):0
    ********** Sector 29 [FFDA0000] (0-unprotect, 1 protect):0
    ********** Sector 30 [FFDB0000] (0-unprotect, 1 protect):0
    ********** Sector 31 [FFDC0000] (0-unprotect, 1 protect):0
    ********** Sector 32 [FFDD0000] (0-unprotect, 1 protect):0
    ********** Sector 33 [FFDE0000] (0-unprotect, 1 protect):0
    ********** Sector 34 [FFDF0000] (0-unprotect, 1 protect):0
    xxxxxxxxxx Program sector 0 : Read Fill Erase Program End
    xxxxxxxxxx Program sector 1 : Read Fill Erase Program End
    xxxxxxxxxx Program sector 2 : Read Fill Erase Program End
    xxxxxxxxxx Program sector 3 : Read Fill Erase Program End
    xxxxxxxxxx Program sector 4 : Read Fill Erase Program End
    xxxxxxxxxx Program sector 5 : Read Fill Erase Program End
    xxxxxxxxxx Program sector 6 : Read Fill Erase Program End
    xxxxxxxxxx Program sector 7 : Read Fill Erase Program End
    xxxxxxxxxx Program sector 8 : Read Fill Erase Program End
    xxxxxxxxxx Program sector 9 : Read Fill Erase Program End
    xxxxxxxxxx Program sector 10 : Read Fill Erase Program End
    xxxxxxxxxx Program sector 11 : Read Fill Erase Program End
    xxxxxxxxxx Program sector 12 : Read Fill Erase Program End
    xxxxxxxxxx Program sector 13 : Read Fill Erase Program End
    xxxxxxxxxx Program sector 14 : Read Fill Erase Program End
    xxxxxxxxxx Program sector 15 : Read Fill Erase Program End
    xxxxxxxxxx Program sector 16 : Read Fill Erase Program End
    xxxxxxxxxx Program sector 17 : Read Fill Erase Program End
    xxxxxxxxxx Program sector 18 : Read Fill Erase Program End
    ********** Verify Complete
    下面板子开始重启。
    【注意】
    一定要看到这些Read Fill Erase Program End信息才表示烧写进去了,前面
    ********** Sector 0 [FFC00000] (0-unprotect, 1 protect):0等只是显示FLASH的扇区分布,
    并没有进行烧写,只有看到Read Fill Erase Program End信息才表示在进行烧写。 
    在minicom中用ctrl+a,然后按b,接着用上下箭头,可以翻看前面的信息,从而查看出了什么错误。例如,TFTP连接失败的信息就要前翻来查看,信息如下:
    Unable to locate 192.168.1.46
    Errors in TFTP download.
    Read 0 bytes (0 blocks)
    这就要查看宿主机(即TFTP服务器配置是否成功)。

    工作机制:
    ./flash调用hhco.gdb脚本对CPU进行初始化,(sys-init)
    然后指定
    target bdm /dev/bdmcf0
    load 即下载一个小软件(tftp.elf,约80K)到板子的RAM中,并用c(continue)命令让它跑起来,这时CPU完全由这个小软件接管。这个小软件跑起来后初始化以太网及TFTP协议栈,然后它运行TFTP客户端,从内存中0x00200004(2M+4处)读取gdb脚本放在此处的TFTP服务器IP地址,并从该IP的TFTP服务器PC下载image.bin到板子内存1M地址处,再将其烧写到FLASH上,重启板子,这时引导起来的就是LINUX了。

    问:烧写完毕后最后会报如下错误:
    Program received signal SIGBUS, Bus error.
    0x488 in asm_exception_handler ()
    1: x/i $pc 0x488 <asm_exception_handler+4>: orib #84,%d0
    注意上述信息并非错误,烧写完退出时都要显示这个信息,并不能表示烧写是否成功。烧写成功与否完全要看minicom端的显示信息来判断。

    问:运行./flash时报错,内容如下:
    ./gdb:error while loading shared libraries:libncurses.so.4:cannot load shared object file:No such file or directory.
    这是怎么回事呢?
    答:这是由于宿主机REDHAT安装时不完全,不支持DEVELOPMENT工具,如gcc,gdb等工具,导致系统没有安装运行这些工具所需的共享库。建议重新完全安装系统。

    3、 关于TFTP服务器设置

    其实TFTP服务器可以是和板子相连的局域网内任意一台开通了TFTP服务的LINUX PC机,即板子可以从任意一台TFTP服务器下载IMAGE文件并进行烧写,当然首先要检查IP是否匹配及连线是否正确。但若将LINUX宿主机(即用串口线连接的那一台LINUX PC)同时开通TFTP服务,这样就不必占用多台机器。默认完全安装后的REDHAT的TFTP服务是没有开通的,要自己手工开通。
    开通宿主机上的TFTP服务,对于REDHAT6.x,可以在宿主机上:
    vim /etc/inetd.conf
    查找tftp,若发现前面有#就表示这一行被注释掉了,即服务没有打开,去掉#就打开了TFTP服务,然后重启宿主机即可。
    对于REDHAT7.2,则在宿主机上执行setup,选择System services,将其中的tftp一项选中(出现 [*]表示选中),并去掉ipchains和iptables两项服务(即去掉它们前面的*号)。然后还要选择Firewall configuration,选中No firewall。最后,退出setup,执行如下命令以启动TFTP服务:
    service xinetd restart
    配置完成后,建议简单测试一下TFTP服务器是否可用,即自己tftp自己,例如在宿主机上执行:
    cd /
    cp /HHCO5272-R1/image/image.bin /tftpboot/
    tftp 192.168.2.32
    tftp>get image.bin
    若出现如下信息:
    Received 634732 bytes in 0.7 seconds
    就表示TFTP服务器配置成功了。若弹出信息说:Timed out,则表明未成功,需要按照上述步骤重新检查一遍。
    或者用如下命令查看tftp服务是否开通:
    netstat -a|grep tftp
    完成上述配置工作后,就可以开始使用该以太网烧写工具了。

    4、BDM与重启问题
    安装bdm驱动模块,以HHCO5272-R1为例:
    1. cd HHCO5272-R1/bdm
    2. insmod linux-bdm.o 【可将此句写入/etc/rc.d/rc.local文件中,如:
    /sbin/insmod /HHCO5272-R1/bdm/linux-bdm.o,这样每次PC启动都会自动执行,否则每次启动宿主机都要执行这一句】
    3. ./MAKEDEV 【在/dev/下创建设备bdmcf0,只要执行一次】
    【注意】
    在insmod时有可能出现.o文件与内核版本不匹配现象,这时只要重编该模块驱动即可。具体请参阅下面第31条。
    还有,BDM线的长度不能加长,否则使用时会报"bdm not open"。

    问:板子刚拿到,一加电就不启动,运行指示灯不闪,minicom什么信息也没有,怎么回事?
    答:这是因为插了BDM卡,若不插BDM卡,板子就可以正常启动了。
    从软件使用者的角度来说,插了BDM后,核心板的reset键就失效,所以板子加电后没有收到reset信号,所以板子不能启动。这时要板子让板子上的软件系统启动运行,可以先执行一下chk,然后立刻退出则板子就会自动重启。
    cd chk
    ./chk
    >>>x
    当板子启动后,可以通过minicom接收命令,则要重启就直接在minicom下键入reboot即可重启,这时按reset键无效。若不插BDM则板子的reset按键是有效的。
    有时,,执行chk时报错:
    device busy表示有其它程序在使用BDM
    用ps -A
    看看是否有其它程序在使用BDM,例如gdb(即gdbtftpflash下的./flash)或者chk,
    若提示device not found,则表明没有安装驱动linux-bdm.o

    5、板子死了?
    判断板子是否死机的标准不是minicom是否可以输入等,最可信的标准就是看核心板上的运行指示灯是否还正常的闪烁,若不再闪了,必死无疑。有时串口可以打印输出信息,但用户却无法输入命令,这经常是由于操作者手上静电太高导致串口工作异常。这时建议给板子断电,把核心板和底板以及串口线等都重新拔插一下再加电重启试试;或者换一台PC,或者用telnet使用网络终端。

    6、TFTP下载与RAM版本问题

    用bootloader下载RAM内核,总是报错:
    KERNEL: Bad trap from supervisor state, vector=4
    CURRENT PROCESS: 
    COMM=swapper PID=1 
    TEXT=00100000-00156e6c DATA=00000000-0016685c BSS=0016685c-0017d7f0 
    USER-STACK=00000000 KERNEL-STACK=0021d000
    ......
    答:
    因为TFTP采用的是udp传输的,
    而我们的板子是通过一个小HUB跟局域网联在一起的,
    可能在传输中有些包丢失了,或者是在传输中有些
    数据出现错误,因此出现RAM版的不能用。后来单独把板子跟一台计算机的网卡相连,
    或者都接到同一个HUB上,就不会有问题了。
    有时烧写完后板子启动时回出现如下错误:
    Unable to open an initial console.
    这种现象有多种原因可能导致出现,其中就有可能是这里所提的烧写错误所致。

    7、BUSS ERROR的问题
    记住出错时打印出的PC指针值,将linux/linux
    用m68k-elf-objdump -D linux >tt
    vim tt
    反汇编后查看该PC地址处在什么函数调用内,
    从而作进一步定位。
    用chk单步跟踪,(fp命令)
    建议阅读chk源代码,可以根据自己的需要定制自己的调试命令。
    所有可用的BDM操作都在bdm/下的一个bdm.h文件中。

    8、硬件扩展
    问:CN5的复用脚p19,p23,p25是系统宽度总线控制信号,留给 用户,系统没有用吗?那么复位期间如何确定总线宽度?
    回答:上电的时候作为系统宽度总线控制信号;但是上电结束开始运行之后,就变成SPI信号线了,您可以使用。问:普通usb口只用d+ d_两条信号线,而5272增加了转出去的信号线:usb-rp,usb-tp....(pa{6:0}),用户能否用其第一功能?
    回答:5272本身带有usb收发器,5272可以让您选择是否选择外部usb收发器(通过配置内部寄存器来选择)。建议选用外部USB收发器。注意!是收发器,不是控制器!USB控制器是在5272里面的。
    【注】cn5,cn6只是做简单的扩展,而你可以通过自己设计底板(华恒底板都已经在手册里面有原理图了!),从而在cn1,,cn2,cn3使用信号。cn1,cn2,cn3上的管脚对应的信号原理图上都有。其中地址线在cn1的1~45(奇数脚),数据线在cn2的2~64(偶数脚)。ddat[3:0]和DTEA都在cn3上,是用于接BDM口的。具体使用请参考5272处理器手册。
    USB[3:1]管脚是从一个外接的USB收发芯片pdiusb11上接出的。而TX_P~RX_N是以太网隔离变压器的输出线。cpuclk的频率为66Mhz,无驱动。

    关于系统I/O:
    MCF5272共提供48个通用IO引脚,它们可以分为3个16位PORT,即PORTA、PORTB、PORTC。其中PORTA,PORTB对应的控制寄存器来实现与其它信号引脚的复用。Port C没有PCCNT寄存器,它只能在工作为外总线16位模式时作为16位的I/O口线使用,即它与D[0~15]复用,只有16位外总线模式时,因为只用高16位D[16~31],这时D[0~15]就为PC[0~15]。另外的一个端口Port D不能被用做I/O线。
    系统剩余GPIO资源(以HHCO5272-R1为例):
    PA7~PA14(8根引脚);PB4~PB7(4根引脚);
    若去掉USB接口,则PA[6:0]可用(7根引脚)。
    所以一般的去掉USB,通用IO引脚可用的有8+4+7=19根。
    串口0所占用的PB[3:0](4根引脚),其中PB0/1为URT0_TxD/URT0RxD,PB2/3为CTS/RTS。
    若去掉串口0的CTS/RTS,又多2根,即可19+2=21根。
    若整个去掉串口0,又多两根,即最多21+2=23根。

    9、串口2检测
    将板子的串口2和宿主机的串口1连接起来,在宿主机的一个TTY启动minicom,另一个TTY执行chk
    ./chk
    >>>fr colilo2.bin e00000 【下载到RAM中0x00e00000地址处】
    >>>fg xx e00400 【从0x00e00400地址处开始执行,加上0x400是要跳过前面1024个字节的ramvect】
    代码下到板子RAM中,然后用fg将其激活跑起来后,CPU就由那个代码控制了,这时就不要在chk中输入任何命令了。不要在chk中执行退出(x),否则板子立即reset,下载到RAM中的内容就丢了。【注意】没有fg之前,核心板上运行指示灯(LEDK1)是不亮不闪的。一旦fg xx e00400后就应该亮闪起来。这是判断下载软件是否跑起来的唯一标准,与串口是否打印完全无关。若minicom没有打印信息,就只能说明串口2是坏的。

    10、开发/使用注意
    ◇无论编译内核还是自己的某一个应用程序都必须在uClinux目录下进行编译make,在其它目录下编译都会报错。◇当板子插了网线时,核心板上的百兆以太网连接和碰撞指示灯(LEDL2)就会亮起来,若这时板子ping一台PC机或者一台PC机ping板子的话,LEDL2灯就会闪烁起来。若LEDL2不亮,可能是以太网插口接触不好所致,用力插紧即可。
    ◇HHCO5272-R1提供了串口2可用于作MODEM拨号,需要把uClinux/user/pppd/、chat/和diald/三个目录加入编译,并编写正确的拨号脚本和配置文件。华恒提供完备的PPP拨号软件包。
    ◇HHCO5272-R1板上采用的是ROMFS的文件系统,它是只读的。可以扩展支持JFFS/JFFS2文件系统,它可读可写。华恒提供完备的JFFS/JFFS2支持软件包。
    ◇在用ddd-5272调试应用程序时,若看不到代码,是因为没有加-g的编译参数;若代码能看到,但是是在run的时候提示BDM not Open! 这是run命令的问题,运行时不要用run,而用continue命令,即c。
    ◇MCF5272不支持外设的DMA,它只有内部DMA,为FEC所用。
    ◇要在系统启动时自动执行用户自己的应用程序,就必须修改uClinux/romfs/etc/rc文件,这是个启动的脚本文件,它的作用就相当于WINDOWS98下的autoexec.bat,这个脚本文件中的每一句都是在shell下可直接执行的命令。例如用户移植了WEB SERVER boa,要启动就执行boa,就在这个rc文件的最后加一句boa&,然后到uClinux下执行make,然后./flash烧写板子即可。
    ◇板子IP的修改:
    可以用ifconfig/route(2.4内核不需要route命令)命令来修改板子启动后的IP,但这个修改在重启后无法保存。因为板子IP的指定是在其ROMFS文件系统的/etc/rc文件中指定的,如下:
    ifconfig eth0 192.168.2.112
    route add -net 192.168.2.0
    要永久的改变板子的IP,就要在宿主机上修改uClinux/romfs/etc/rc文件中对应的IP设置内容,然后在uClinux下重新编译,然后重新烧写。但这样做的问题在于无法做到动态修改IP并保存。要实现这种功能,就必须在板子上实现文件存储功能。实现的机制有许多,如flatfsd、JFFS/JFFS2文件系统等。对于板子上的存储机制,华恒提供完整的JFFS/JFFS2软件包销售,可大大加快用户的开发进度及产品上市时间。

    11、启动失败
    客户问:HHCF5272-LCD-IDE-R1套件中:把image.bin写到flash后,应该可以从板子启动进入uClinux中,但是它会出现一个错误是kernel panic attempted to kill init!
    答复:都是usb_init惹的祸
    这样修改:
    vim uClinux/linux-2.4.x/drivers/char/mem.c
    去掉usb_init一行,然后重新编译烧写就没有问题了。

    12、串口2问题 
    ◇客户问:
    一个简单测试程序,向/dev/ttyS0发送数据,波特率设为38400,在PC机下顺利接收到。利用5272开发板上的串口1发送,另一端在minicom下,设为相同的波特率,能接收到数据,但是相同的程序,只修改为/dev/ttyS1确收不到任何数据,
    答复:经次测试,发现是缺少了对串口2占用的PD口的初始化。
    对于HHCF5272-R1,更新的文件sysinit.c,
    将它覆盖到uClinux/linux/arch/m68knommu/platform/5272/目录下,
    重新编译烧写即可。
    对于HHCF5272-LCD-IDE-R1 ,将附件colilo.bin覆盖到uClinux/colilo/目录下,重新编译烧写即可。

    13、扩大RAM盘的方法
    有客户问到增大HHCF5272-R1板子上的/var/目录大小的问题,
    这个目录是RAM盘,可读可写。
    它的大小是在uClinux/romfs.mk中指定的:
    例如要指定为2M大小的RAM盘:
    则加入如下一句:
    RAMFSy = $(USER)/ramimage/ramfs2048.img
    即可。
    【注意】
    RAM盘太大好像会引起系统死机。

    14、关于编译重烧启动时出现“unable to open an initial console”问题
    解决办法:
    修改uClinux/vendor/HHtech/M5272/Makefile,
    将其中的genromfs改为/usr/local/bin/genromfs即可。

    15、关于BDM驱动“ kernel-module version mismatch”问题
    现象:在bdm/driver/linux下执行make后,insmod linux-bdm.o时出现如下错误:
    linux-bdm.o: kernel-module version mismatch
    linux-bdm.o was compiled for kernel version 2.4.17
    while this kernel is version 2.4.5. 
    解决办法:
    1、要注释MODVERSIONS=-DMODVERSIONS
    即:#MODVERSIONS=-DMODVERSIONS
    2、修改bdm/driver/linux/Makefile中的CFLAGS一行为如下:
    CFLAGS = -Wall -g -O -pipe -I/usr/src/linux-2.4/include -I.. -DMODULE -D__KERNEL__ $(MODVERSIONS)
    这样编译就不会有内核版本不匹配的问题了

    16、关于系统启动时数据总线宽度的问题
    5272复用脚L5 QSPICLK/BUSW1 和M5 SPI_CS0/BUSW0
    在HHCO5272-R1中用作 QSPICLK 和 SPI_CS0 功能(CN1 
    pin93 ,CN1 pin95) , 而只有在CPU复位时它们才作为BUSW1:BUSW0组合决定CS0内存数据宽度。这个是不需要配置的。
    具体参见MCF5272手册:P14-2页,即P14.3.1节介绍。
    华恒板子的BUSW1:BUSW0组合为[1:0],即16位模式,因此刚加电时接CS0的FLASH0的数据总线宽度为16位模式。
    同理,N4 QSPI_DOUT/WSEL 在HHCO5272-R1中用作 
    QSPI_DOUT 功能(CN1 pin89 ), 
    WSEL也只是CPU复位时生效,华恒板子此脚接下拉电阻,表示为0,因此为32位模式。此后都作为QSPI_DOUT信号使用,不需配置。

    17、关于REDHAT7.3/8.0/9.0等高版本宿主机上NFS配置的问题
    目前主要的问题在于完全安装的这些REDHAT版本没有提供linuxconf工具软件,因此无法配置NFS服务器。不过这个问题很容易解决,这需要到http://ftp.boe.tcc.edu.tw/tnc/firewall/下载一个名为:linuxconf-1.25r7-3.i386.rpm的RPM包,然后在REDHAT LINUX机器上执行:
    rpm -i linuxconf-1.25r7-3.i386.rpm (6,995KB)
    这个安装比较耗时,安装完毕后,立即就可以使用linuxconf工具了

    18、关于定时器使用的问题
    对于秒级以上的应用程序:
    alarm(sec)
    在sec秒以后就会产生一个sigalrm信号。
    注册一个自己的处理函数来处理这个信号就可以实现。
    对于ms级的定时,usleep为us级的延时,但无法提供中断。
    MCF5272提供四个定时器,uClinux只使用了其中一个:
    uClinux/linux/arch/m68knommu/platform/5272/config.c中:
    void coldfire_timer_init(void (*handler)(int, void *, struct pt_regs *))
    {
    volatile unsigned short *timerp;
    volatile unsigned long *icrp;
    /* Set up TIMER 1 as poll clock */
    timerp = (volatile unsigned short *) (MCF_MBAR + MCFTIMER_BASE1);
    timerp[MCFTIMER_TMR] = MCFTIMER_TMR_DISABLE;
    timerp[MCFTIMER_TRR] = (unsigned short) ((MCF_CLK / 16) / HZ);
    timerp[MCFTIMER_TMR] = MCFTIMER_TMR_ENORI | MCFTIMER_TMR_CLK16 |
    MCFTIMER_TMR_RESTART | MCFTIMER_TMR_ENABLE;
    icrp = (volatile unsigned long *) (MCF_MBAR + MCFSIM_ICR1);
    *icrp = 0x0000d000; /* TMR1 with priority 5 */
    request_irq(69, handler, SA_INTERRUPT, "ColdFire Timer", NULL);
    }
    void config_BSP(char *commandp, int size)
    {
    memset(commandp, 0, size);
    mach_sched_init = coldfire_timer_init;
    mach_tick = coldfire_tick;
    mach_trap_init = coldfire_trap_init;
    }
    uClinux/linux/arch/m68knommu/kernel/time.c:
    void time_init(void)
    {
    unsigned int year, mon, day, hour, min, sec;
    extern void arch_gettod(int *year, int *mon, int *day, int *hour,
    int *min, int *sec);
    arch_gettod (&year, &mon, &day, &hour, &min, &sec);
    if ((year += 1900) < 1970)
    year += 100;
    xtime.tv_sec = mktime(year, mon, day, hour, min, sec);
    xtime.tv_usec = 0;
    if (mach_sched_init)
    mach_sched_init(timer_interrupt);
    }
    /*
    * timer_interrupt() needs to keep up the real-time clock,
    * as well as call the "do_timer()" routine every clocktick
    */
    void timer_interrupt(int irq, void *dummy, struct pt_regs * regs)
    {
    /* last time the cmos clock got updated */
    static long last_rtc_update=0;
    /* may need to kick the hardware timer */
    if (mach_tick)
    mach_tick();
    do_timer(regs);
    /*
    * If we have an externally synchronized Linux clock, then update
    * CMOS clock accordingly every ~11 minutes. Set_rtc_mmss() has to be
    * called as close as possible to 500 ms before the new second starts.
    */
    if (time_state != TIME_BAD && xtime.tv_sec > last_rtc_update + 660 &&
    xtime.tv_usec > 500000 - (tick >> 1) &&
    xtime.tv_usec < 500000 + (tick >> 1)) {
    if (set_rtc_mmss(xtime.tv_sec) == 0)
    last_rtc_update = xtime.tv_sec;
    else
    last_rtc_update = xtime.tv_sec - 600; /* do it again in 60 s */
    }
    }
    add_timer函数在uClinux/linux/kernel/sched.c中定义
    void add_timer(struct timer_list *timer)
    {
    unsigned long flags;
    save_flags(flags);
    cli();
    #if SLOW_BUT_DEBUGGING_TIMERS
    if (timer->next || timer->prev) {
    printk("add_timer() called with non-zero list from %p\n",
    __builtin_return_address(0));
    goto out;
    }
    #endif
    internal_add_timer(timer);
    #if SLOW_BUT_DEBUGGING_TIMERS
    out:
    #endif
    restore_flags(flags);
    }
    static inline void internal_add_timer(struct timer_list *timer)
    {
    /*
    * must be cli-ed when calling this
    */
    unsigned long expires = timer->expires;
    unsigned long idx = expires - timer_jiffies;
    if (idx < TVR_SIZE) {
    int i = expires & TVR_MASK;
    insert_timer(timer, tv1.vec, i);
    } else if (idx < 1 << (TVR_BITS + TVN_BITS)) {
    int i = (expires >> TVR_BITS) & TVN_MASK;
    insert_timer(timer, tv2.vec, i);
    } else if (idx < 1 << (TVR_BITS + 2 * TVN_BITS)) {
    int i = (expires >> (TVR_BITS + TVN_BITS)) & TVN_MASK;
    insert_timer(timer, tv3.vec, i);
    } else if (idx < 1 << (TVR_BITS + 3 * TVN_BITS)) {
    int i = (expires >> (TVR_BITS + 2 * TVN_BITS)) & TVN_MASK;
    insert_timer(timer, tv4.vec, i);
    } else if (expires < timer_jiffies) {
    /* can happen if you add a timer with expires == jiffies,
    * or you set a timer to go off in the past
    */
    insert_timer(timer, tv1.vec, tv1.index);
    } else if (idx < 0xffffffffUL) {
    int i = (expires >> (TVR_BITS + 3 * TVN_BITS)) & TVN_MASK;
    insert_timer(timer, tv5.vec, i);
    } else {
    /* Can only get here on architectures with 64-bit jiffies */
    timer->next = timer->prev = timer;
    }
    }
    在uClinux/linux/arch/m68knommu/platform/5272/config.c中的coldfire_timer_init中增加对TMR2初始化设置和中断申请。然后自己写TMR2的定时器中断服务程序,
    这个代码可放在config.c中,也可加到OS代码中:
    uClinux/linux/arch/m68knommu/kernel/time.c:
    这个文件中有LINUX使用TMR1的定时器中断服务程序代码:
    void timer_interrupt(int irq, void *dummy, struct pt_regs * regs)
    它是用来给LINUX OS提供时钟的,自己的定时中断就不必做这个工作了,让它10ms进一次中断,在自己的TMR2中断程序中做自己的工作就可以了。当然这里的处理一定要快。

    19、关于C++支持
    华恒HHCF系列开发套件光盘提供的软件系统支持C++代码,下面给出一个编译参数的例子:
    m68k-elf-g++ -m5307 -msep-data -Wl,-elf2flt -o test test.cpp -lstdc++ -lc -lgcc 
    【注意】
    这里没有加入库的路径和头文件的路径:-L和-I,请用户在编译时自己指定绝对路径。
    下面介绍一下将C++代码统一到user目录下像普通应用程序一样参与编译:
    C++程序在5272上的编译,首先要修改uclinux目录下的config.arch文件,将其中的
    CXX = $(CROSS_COMPILE)g++ 
    改为:
    CXX = $(CROSS_COMPILE)g++ $(CPUFLAGS) -DCONFIG_COLDFIRE
    再将其中的
    CXXLIBS = $(LDPATH) $(LIBSTDCPP) $(LIBIOSTREAM) $(LIBIO) $(LIBIBERTY) \
    $(LIBC) $(LIBGCC)
    改为:
    CXXLIBS = $(LDPATH) $(LIBSTDCPP) $(LIBIBERTY) \
    $(LIBC) $(LIBGCC)
    然后模仿以下的Makefile书写:
    EXEC = test
    OBJS = test.o
    all: $(EXEC)
    $(EXEC): $(OBJS)
    $(CXX) $(LDFLAGS) -o $@ $(OBJS) $(CXXLIBS)
    romfs:
    $(ROMFSINST) /bin/$(EXEC)
    clean:
    -rm -f $(EXEC) *.elf *.gdb *.o
    即可。
    说明:uclinux(uclibc)现在对iostream的支持还不够,不能使用iostream库。
    【注】
    华恒客户可发信到support@hhcn.com索取测试代码。

    20. 关于进程间通信IPC的问题
    uClinux下system V的进程间通信IPC机制不可用!
    (现在已经可以用了,在make menuconfig中内核配置菜单中选择General setup --->,选中:
    [*] System V IPC)
    1、信号:但要求知道另一个进程的pid号:
    进程间用信号通信时,用kill发送信号,但需要指定进程号,
    可以这样作:
    接收信号的进程把自己的进程号写到RAM盘(如/tmp目录)下的一个文件中,发送信号的进程从这个文件中读取这个进程号即可成功的实现信号发送。
    这里是直接指定内存地址(当然做好是在靠后的未用地址),然后用一个进程启动另一个进程,这两个进程之间的进程号恰好相差1,这样就可以在两个进程之间使用信号了。
    2、共享文件:两个进程访问同一个文件,用文件锁实现互斥访问。
    要保护/tmp/a文件,
    在一个进程中每次要fopen前,先判断是否有a.lock文件锁,
    若有就等待。
    若没有,则创建该文件锁用open(O_CREAT|0_EXECL,
    S_IRUSR|S_IWUSR)创建。
    另一方法:在RAM中建立共享区(相当于文件)
    在一个进程里面分配一个内存快,然后将内存快的地址传给
    由vfork和exclp创建的另一个进程,这样这两个进程就可以对这块内存进行读写了,对共享内存的互锁是通过发信号实现的。
    一共有4个进程,分别一个启动一个,这样他们的进称号恰好是连续的,一就是说一个进程知道任意一个它想发信号的进程号。这样就可以在所有进程间发信号了,共享内存是先分配内存块,然后传递已经有操作系统分配好的共享内存地址,管道使用的pipe创建,然后使用vfork创建另一个进程,并传递管道的读、写fd,就可以读写管道了。
    3、消息队列
    含消息队列的程序编译程序时会提示没有定义符号--"msgctl,msgsnd,msgget,msgrcv";
    在/uClinux/linux/ipc/msg.c里有系统调用的源代码
    -"sys_msgget,sys_msgctl,sys_msgsnd,sys_msgrcv";
    再查看系统uClinux\linux\arch\m68knommu\kernel/sys_m68k.c里ipc系统调用里调用--"sys_msgget,sys_msgctl,sys_msgsnd,sys_msgrcv"内核函数;
    所以推断是档案libc.a里没包含"msgctl,msgsnd,msgget,msgrcv"原形函数调用;
    做法:
    1)make xconfig;选取SYSTEM V IPC
    2)在/uClinux/lib/libc/生成目录./msg
    3)添加文件msgctl.c,msgsnd.c,msgget.c,msgrcv.c,写相应的makefile;
    4)修改上一级目录里makefile,添加msg目录;->加函数到libc.a里;
    5)make dep;
    6)make;
    注意:在系统机上发消息队列的长度和接收消息队列的长度可以不等长;华恒内核中长度好象必须相等,(最大不要超过BUFSIZ=1024)。
    消息队列可以对其进行编址,使得各个进程各取所需,并且可以使消息队列的个数变少。但是如果在消息队列被删除后还有N个要发送或检索的进程,会发生问题 
    信号量的做法一样!

    21、关于应用程序中使用GPIO的问题
    关于GPIO,要设置三个寄存器,分别是:以PB口为例
    1)PBCNT,绝对各个引脚的复用信号是否为IO
    2)方向寄存器,每个引脚是输入还是输出。
    3)数据寄存器,进行IO的输入输出。
    给个例子代码:(注意:这是在用户应用程序中的代码,不是内核态代码)
    *(volatile unsigned long *)0x10000080 &=0xff003fff; // PACNT 14-23 bit is cleared,use PA7-11
    *((volatile unsigned short *)0x10000084 |= 0x0f80; // PADDR 7-11 bit is setted,use as output
    *(volatile unsigned short *)0x10000086 = 0x0080; // PA7为0,PA8为0
    *(volatile unsigned short *)0x10000086 = 0x0180; // PA7为0,PA8为1

    22、关于管道的使用
    下面是一个例子,在一个进程中通过管道启动另一个进程test。
    #include <sys/wait.h>
    #include <stdio.h>
    int main(void)
    {
    FILE *fd;
    int waitstat;
    char buf[20];
    if((fd=popen("/bin/test","r"))==NULL)
    printf("popen error! \n");
    wait(&waitstat);
    //pclose(fd);
    if(fgets(buf,15,fd)==NULL)
    printf("fgets error! \n");
    printf("%s",buf);
    return 0;
    }
    代码移植到uC下时应在popen后加入,pclose(fd)或wait(&waitstat)。因为uClibc中popen的实现与X86下的不同,它使用了vfork(PC上是用fork)必须等子进程可靠结束关闭文件后才执行对文件的下一步读写。

    23. 关于ColdFire系列开发板的uClinux内核版本
    华恒ColdFire系列开发板提供的嵌入式uClinux内核版本目前主要有两种:2.0.38和2.4.x,
    其中只有HHCF5272-R1还保留了2.0.38的内核版本,其它套件全部为2.4内核。目前HHCF5272-R1也可以提供2.4的内核。
    华恒版本的uClinux和从网上uClinux.org down下来的版本的区别在于:
    1)网上开源的版本只提供了RAM版内核,不提供ROM版本,呵呵,这样做我想可能就是为了限制商业化用户吧,因为RAM版本还需要bootloader来引导(但行业内,bootloader基本上大家都不提供源代码),不能直接启动,根本无法作为商业产品使用,因此也就只能给爱好者折腾着玩玩而已。华恒替客户完成了这部分ROM化的工作。
    2)华恒以本地化器件提供的硬件板卡,因此要针对自己的硬件修改系统启动代码、硬件设备驱动BSP,并相应的提供下载、烧写等工具软件,这些都要自己移植,修改或者完全自己定制。没有这些辅助工具,空有uClinux的tar包,是根本无法进行开发调试的。
    3)本地化全中文技术手册及相关的技术支持(论坛)。

    24. 关于不同内核版本间应用程序移植的问题
    应用程序的移植一般的和内核版本是没有任何关系的,当然有的应用程序是需要内核支持的,例如pppd等。在2.0.38内核和2.4内核之间互移应用程序,唯一要注意的就是Makefile的写法:
    其实区别就一句话,就是2.4下面,它把elf2flt作为gcc的一个参数一步完成了,而2.0.38还要分为两步完成,体现在Makefile上就如下:
    对于2.0.38:
    $(LD) $(LDFLAGS) -o $@.elf $(OBJS) $(LDLIBS)
    $(CONVERT)
    对于2.4.x:
    $(CC) $(LDFLAGS) -o $@ $(OBJS) $(LDLIBS)

    25、关于外设中断的问题
    MCF5272处理器提供了6路外部中断以供外设控制芯片使用。在MCF5272的启动代码中(sysinit.c代码中)屏蔽了所有的内部和外部中断,因为用户在扩展使用自己的中断源设备时,在该设备的驱动初始化代码(如open函数中)中就必须手工打开允许这个外部中断源。屏蔽和允许一个中断源都是通过设置ICRn寄存器来实现的。
    在对应的PI位写1,而后面的三位IPL位都为0就表示屏蔽该中断源。
    在对应的PI位写1,后面的三位IPL位写001~111就表明打开运行该中断源,并设置其中断优先级为1到7级,级别越高,优先级越高。
    switch(irq)
    {
    case 1:
    *(volatile unsigned long *)(0x10000020) |= 0xb0000000;
    break;
    case 3:
    *(volatile unsigned long *)(0x10000020) |= 0x00b00000;
    break;
    case 4:
    *(volatile unsigned long *)(0x10000020) |= 0x000b0000;
    break;
    case 5:
    *(volatile unsigned long *)(0x1000002c) |= 0x0b000000;
    break;
    }

    26、关于中断的问题
    在每个使用中断的设备的驱动初始化代码中,都首先要打开中断,因为启动代码把所有中断都禁止(mask)了,根据CPU手册,在ICRn对应的PI位写1,而后面的三位IPL位都为0就表示屏蔽该中断源。即将对应ICR中对应的字节设置为8。例如启动时,ICR1就被设置为:0x88888888。
    打开中断时,则在ICRn对应的PI位写1,后面的三位IPL位写001~111就表明打开运行该中断源,并设置其中断优先级为1到7级,级别越高,优先级越高。例如MCF5272的几个内部模块所使用的内部中断:
    1)TIMER:在ICR1对应位设置0xd,即priority == 5(2.0.38用的是TMR1,2.4内核用的是TMR4)
    2)FEC:在ICR3对应位设置0xd,即priority == 5
    3)FEC要工作的同时还要使用MII(占用INT2),在ICR1对应位设置0xd,即priority == 5
    4)UART:在ICR2对应位设置0xe,即priority == 6
    样例代码如下:
    FEC:fec.c中int __init fec_enet_init(struct net_device *dev)
    volatile unsigned long *icrp;
    icrp = (volatile unsigned long *) (MCF_MBAR + MCFSIM_ICR3);//FEC
    *icrp = 0x00000ddd;
    icrp = (volatile unsigned long *) (MCF_MBAR + MCFSIM_ICR1);//MII
    *icrp = (*icrp & 0x70777777) | 0x0d000000;
    TMR4:在uClinux/linux-2.4.x/arch/m68knommu/platform/5272/config.c中对TIMER的初始化如下:
    void coldfire_timer_init(void (*handler)(int, void *, struct pt_regs *))函数中有如下代码:
    icrp = (volatile unsigned long *) (MCF_MBAR + MCFSIM_ICR1);
    *icrp = 0x0000000d; /* TMR4 with priority 5 */
    request_irq(72, handler, SA_INTERRUPT, "ColdFire Timer", NULL); 
    UART1/UART2:在uClinux/linux-2.4.x/drivers/char/mcfserial.c中,有如下代码对UART进行中断初始化:
    static void mcfrs_irqinit(struct mcf_serial *info)
    volatile unsigned long *icrp;
    volatile unsigned long *portp;
    icrp = (volatile unsigned long *) (MCF_MBAR + MCFSIM_ICR2);
    switch (info->line) {
    case 0:
    *icrp = 0xe0000000;
    break;
    case 1:
    *icrp = 0x0e000000;
    break;
    此外,MCF5272相应外部中断后,并不会自动的清楚该中断的pending位,这样用户就必须在自己的中断处理函数的末尾自己来替处理器完成这一工作。例如:对于外部中断3
    *(volatile unsigned long*)(MCF_MBAR+0x20) |= 0x00800000;

    27、应用程序中如何定时产生信号?
    这个问题就相当于实现WINDOWS下的WM_TIMER功能,定时给应用程序发送消息
    例如:要求每一秒产生一个信号,去打印一句test。本来signal/alarm只能一次性工作,现在要让它循环起来,每一秒都产生信号,就要再其信号处理函数中对其进行重置。
    void test()
    {
    signal(SIGALRM,test);//重置
    alarm(1);
    printf("test\n");
    }
    main()
    {
    signal(SIGALRM,test);
    alarm(1);
    for(;;)
    {/*主函数处理部分*/
    }
    }

    28、应用程序的编译参数观察
    用户在编译自己的应用程序时,也是要在uClinux下执行make,屏幕一滚就过去了,SHIFT+PAGEUP也翻不了那末多,其实自己的应用怎么编译的也不清楚。
    在uClinux下执行make >&t,则自动生成临时文件t,它记录了整个编译过程及每步编译的详细参数,对于理解编译工具的使用及其参数非常有帮助。(整个的编译过程详细分析请参见手册2.1.3节),举例而言,以应用程序uClinux/user/inetd和ping为例:
    对于uClinux-2.0.38下的写法如下:
    /HHCF5272-R1/uClinux/tools/m68k-elf-gcc -m5200 -Wa,-m5200 -DCONFIG_COLDFIRE -Dl
    inux -D__linux__ -Dunix -DEMBED -O2 -msoft-float 
    -I/HHCF5272-R1/uClinux/tools/gcc-include -I/HHCF5272-R1/uClinux/lib/libc/include 
    -I/HHCF5272-R1/uClinux/lib/libm 
    -I/HHCF5272-R1/uClinux/vendors/include -fno-builtin -DSERVICES=\"/etc/services
    \" -DINETD_CONF=\"/etc/inetd.conf\" -c -o inetd.o inetd.c
    对于uClinux-2.4.17下的编译全部以gcc为前台,连ld也被隐藏到后台了:
    m68k-elf-gcc -m5307 -DCONFIG_COLDFIRE -Os -g -fomit-frame-pointer -Dlinux 
    -D__linux__ -Dunix -D__uClinux__ -DEMBED 
    -I/HHCF5272-R1/uClinux/lib/libc/include -I/HHCF5272-R1/uClinux/lib/libm 
    -I/HHCF5272-R1/uClinux -I/HHCF5272-R1/uClinux/linux-2.4.x/include 
    -fno-builtin -msep-data -c -o ping.o ping.c
    m68k-elf-gcc -m5307 -DCONFIG_COLDFIRE -Os -g -fomit-frame-pointer 
    -Dlinux -D__linux__ -Dunix -D__uClinux__ -DEMBED 
    -I/HHCF5272-R1/uClinux/lib/libc/include -I/HHCF5272-R1/uClinux/lib/libm 
    -I/HHCF5272-R1/uClinux -I/HHCF5272-R1/uClinux/linux-2.4.x/include 
    -fno-builtin -msep-data -Wl,-elf2flt -o ping ping.o 
    -L/HHCF5272-R1/uClinux/lib/libc/. -L/HHCF5272-R1/uClinux/lib/libc/lib 
    -L/HHCF5272-R1/uClinux/lib/libm -L/HHCF5272-R1/uClinux/lib/libnet 
    -L/HHCF5272-R1/uClinux/lib/libdes -L/HHCF5272-R1/uClinux/lib/libpcap 
    -L/HHCF5272-R1/uClinux/lib/libssl -lc
    -L表明后面为所要链接的libc库的路径。库路径可以有多个(对于一个应用程序而言,很多库并没有用),因此可以看到有多个-L参数;
    -I参数表明后面所跟的为C语言INCLUDE头文件的路径,这个路径可以有多个,因此可以看到有多个-I参数;
    -m5200(-m5307)为处理器相关编译参数,2.4下采用了-m5307,这其实对于应用程序而言没有任何区别;
    -c后面为所要编译的C文件;
    -o后面为这行编译操作的目的,即这行编译完毕后要生成的文件;
    -Wall -Wstrict-prototypes -O2 -fomit-frame-pointer -fno-strength-reduce:这些都是编译参数;

    29、FLASH上数据保存 
    鉴于目前发行的开发板中的uClinux均采用ROMFS作为其根文件系统,因此其目录大多是不可写的。只有/var,/tmp是RAM盘可写,但板子一掉电里面的内容就丢失了,因此只能作临时文件保存,无法永久的保存数据,例如配置文件等。下面大概介绍一下几种FLASH上保存配置的方法:
    1、对于简单的数据,小的配置文件等,而且不是非常频繁的(例如一分钟写10次)写入的,可以直接自己在FLASH的空余处(例如第二片FLASH上)划出一块区域,以自己定义的方式写入,并在板子启动时自动读出。华恒提供了这样的样例代码,即user/memtools/,其详细介绍在手册第二章“FLASH扇区保存IP地址”一节。这样作最大的好处是用户的控制程度最大,形式最灵活,保存的可以是自定义的数据,可以不是文件的形式。而下面的几种方式都是要求文件的形式保存,无法处理自定义的数据保存。
    2、对于比较多的配置文件,一般的都先写在RAM盘中,然后选择保存,一次性写入FLASH的某几个扇区。这时就可使用flatfsd软件,它的使用需要内核的配合支持,即要在blkmem.c中为其指定保存数据的几个扇区的起始/结束地址。这中方式也不能适应非常频繁的写入。
    使用说明:将要保存的文件放在板子的RAM盘目录/etc/config目录下,因为uClinux的根目录/是建于FLASH上的romfs文件系统,是不可写的,只有几个RAM盘才可写,可用mount命令查看:
    /etc> mount
    rootfs on / type rootfs (rw)
    /dev/rom0 on / type romfs (ro)
    /proc on /proc type proc (rw)
    /dev/ram0 on /var type ext2 (rw)
    /dev/ram1 on /etc/config type ext2 (rw)
    /dev/mtdblock0 on /sbin type jffs2 (rw)
    要保存的时候,就在minicom里面执行
    killall -10 flatfsd
    或者在C程序代码里面用vfork+execlp执行这句命令行即可.
    这样就会自动将RAM盘中/etc/config目录里面的文件全部保存写入到板子FLASH的扇区里面(具体哪几个扇区是由linux-2.4.x/drivers/block/blkmem.c中指定的,可在板子启动信息里面看到:)
    Blkmem copyright 1998 Kenneth Albanowski
    Blkmem 2 disk images:
    0: FFC77362-FFCCE761 [VIRTUAL FFC77362-FFCCE761] (RO) /*romfs*/
    1: FFFC0000-FFFFFFFF [VIRTUAL FFFC0000-FFFFFFFF] (RW) /*flatfsd*/
    能够保存的机制是由如下文件的一行保证的:
    /etc> cat inittab
    inet:unknown:/bin/inetd
    flt:unknown:/bin/flatfsd 
    这样就保证系统启动后系统进程里面有flatfsd进程永远在运行,才能保证接收兵处理用户发来的数值为10的信号SIGNAL:
    /etc> ps
    PID PORT STAT SIZE SHARED %CPU COMMAND
    1 S 37K 0K 0.0 init
    2 S 0K 0K 0.0 keventd
    3 R 0K 0K 0.2 ksoftirqd_CPU0
    4 R 0K 0K 22.7 kswapd
    5 S 0K 0K 0.0 bdflush
    6 S 0K 0K 0.0 kupdated
    7 S 0K 0K 0.0 mtdblockd
    16 S 0K 0K 0.0 jffs2_gcd_mtd0
    30 S0 S 85K 0K 0.0 /bin/sh
    31 S 38K 0K 0.0 /bin/inetd
    32 S 53K 0K 0.0 /bin/flatfsd
    33 S 190K 0K 2.2 /bin/diald -f /etc/config/diald.pppoe1
    34 S 207K 0K 0.0 /bin/dhcpd -d -cf /etc/config/dhcpd.conf eth0
    1220 S 149K 0K 0.0 /bin/pppd -detach mtu 1454 mru 1454 file /etc/config/pppoe1
    1221 S 69K 0K 7.4 /bin/pppoe -I eth0 -T 3000 -m 1412 -p /var/log/pppoe.pid
    1241 S 49K 0K 0.0 /bin/telnetd
    1242 p2 R 88K 0K 0.0 sh 
    而板子启动时通过执行/etc/rc里面的flatfsd –r就可以将FLASH上的保存的文件恢复到RAM盘目录/etc/config/下。
    /> cd /etc/
    /etc> cat rc
    hostname mp3server
    /bin/expand /etc/ramfs.img /dev/ram0
    /bin/expand /etc/ramfs.img /dev/ram1
    mount -t proc proc /proc
    mount -t ext2 /dev/ram0 /var
    mount -t ext2 /dev/ram1 /etc/config
    mount -t jffs2 /dev/mtdblock0 /sbin
    mkdir /var/tmp
    mkdir /var/log
    mkdir /var/run
    mkdir /var/lock
    /bin/flatfsd -r
    touch /etc/config/dhcpd.leases
    ifconfig lo 127.0.0.1
    ifconfig eth0 192.168.2.254 netmask 255.255.255.0
    ifconfig eth0:1 192.168.1.2 netmask 255.255.255.0
    /bin/ena -e
    iptables -t nat -A POSTROUTING -o ppp0 -j MASQUERADE
    注意:若想修改板子启动后/etc/config里面的文件的内容,例如 板子启动后的IP地址设置文件/etc/config/start文件,需要如下操作:
    修改uClinux源代码uClinux-dist/romfs/etc/default/start文件内容,然后重新编译生成image.bin,烧写板子,板子启动后,在minicom里面执行
    cp /etc/default/start /etc/config
    killall -10 flatfsd
    这样下次启动就会自动生效。
    3、对于比较频繁的数据保存,就要在板子上建立额外的日志型文件系统JFFS/JFFS2,或者干脆就用JFFS/JFFS2取代ROMFS作根文件系统。这样板子的目录就是可写的,就像硬盘一样,不需要额外的工具来负责将数据写入FLASH。JFFS为2.0.38内核所支持,它不支持JFFS2,JFFS2到2.4内核才被支持,它采用了成熟稳定的MTD技术,因此要比JFFS稳定。这两种文件系统要在uClinux上实现支持并不复杂,但它的实用还需要一些额外的工作,例如烧写工具的配合,新型image.bin编译生成,因为真正产品化的软件是不能允许每次启动后都还要进行许多的手工操作,例如加载文件系统等,板子出厂烧写也要一次完成,而不能还要分多次烧写等等,这些工作都是比较繁杂的,而且没有烧写工具的源代码是无法完成的。华恒提供JFFS/JFFS2整套软件技术。

    30、关于2.4内核版本下的RAM版内核的编译 
    对于2.0.38内核,华恒提供了完备的ram.ld和crt0_ram.S,它们和华恒提供的bootloader可配合使用。 对于2.4.17的内核,从网上直接DOWN的uClinux-Coldfire版本(例如最新版本:uClinux-dist-20020701.tar.gz)是可以直接在华恒bootloader提示符HHCN>下执行readàgo 100000跑起来的。而若客户要从华恒2.4系列开发套件软件系统的基础上改动跑RAM版则需要做一些改动才可以的,例如HHCF5272-LCD-IDE-R1、HHCF5272-2ETH-R2等都是采用的2.4内核。因为华恒板子烧在FLASH上的就是一个RAM版本的内核,经由华恒的bootloader解压复制到RAM中才激活执行的,它的执行方式和readàgo的方式是完全不同的,虽然都是RAM版。要做到这一点,华恒是做了一些改动的,而现在实际上就是要恢复成从网上直接DOWN下来的状态。
    1、首先是crt0_ram.S和直接下载的uClinux-Coldfire版本的crt0_ram.S有区别,是经过华恒修改的代码,若直接使用必然无法通过bootloaderàreadàgo 100000而跑起来。因此这时就必须使用网上下载的原始未经改动的crt0_ram.S,其实两个文件的区别很小,仅在于华恒的版本注释了复制romfs的部分代码:
    只要把uClinux/linux-2.4.x/arch/m68knommu/platform/5272/HHTECH/crt0_ram.S中的一个#if 0 改为#if 1
    下面贴出华恒代码:
    _start:
    nop /* Filler */
    move.w #0x2700, %sr /* No interrupts */
    /*
    * Setup VBR here, otherwise buserror remap will not work.
    * if dBug was active before (on my SBC with dBug 1.1 of Dec 16 1996)
    *
    * bkr@cut.de 19990306
    *
    * Note: this is because dBUG points VBR to ROM, making vectors read
    * only, so the bus trap can't be changed. (RS)
    */
    move.l #VBR_BASE, %a7 /* Note VBR can't be read */
    movec %a7, %VBR
    move.l %a7, _ramvec /* Set up vector addr */
    move.l %a7, _rambase /* Set up base RAM addr */
    /*
    * Set to 4 meg for the Cadre III board (m5206e).
    */
    move.l #MEM_SIZE, %a0
    move.l %a0, %d0 /* Mem end addr is in a0 */
    move.l %d0, %sp /* Set up initial stack ptr */
    move.l %d0, _ramend /* Set end ram addr */
    #if 1
    /*
    * Enable CPU internal cache.
    */
    move.l #0x01000000, %d0 /* Invalidate cache cmd */
    movec %d0, %CACR /* Invalidate cache */
    move.l #0x80000100, %d0 /* Setup cache mask */
    movec %d0, %CACR /* Enable cache */
    #endif
    /*
    * Move ROM filesystem above bss :-)
    */
    lea.l _ebss, %a1 /* Set up destination */
    move.l %a1, _ramstart /* Set start of ram */
    #if 1【这里原来是#if 0,即在华恒版本中被注释掉了】
    lea.l _sbss, %a0 /* Get start of bss */
    lea.l _ebss, %a1 /* Set up destination */
    move.l %a0, %a2 /* Copy of bss start */
    move.l 8(%a0), %d0 /* Get size of ROMFS */
    addq.l #8, %d0 /* Allow for rounding */
    and.l #0xfffffffc, %d0 /* Whole words */
    add.l %d0, %a0 /* Copy from end */
    add.l %d0, %a1 /* Copy from end */
    move.l %a1, _ramstart /* Set start of ram */
    _copy_romfs:
    move.l -(%a0), %d0 /* Copy dword */
    move.l %d0, -(%a1)
    cmp.l %a0, %a2 /* Check if at end */
    bne _copy_romfs
    #endif
    /*
    * Zero out the bss region.
    */
    lea.l _sbss, %a0 /* Get start of bss */
    lea.l _ebss, %a1 /* Get end of bss */
    clr.l %d0 /* Set value */
    _clear_bss:
    move.l %d0, (%a0)+ /* Clear each word */
    cmp.l %a0, %a1 /* Check if at end */
    bne _clear_bss
    /*
    * load the current task pointer and stack
    */
    lea init_task_union, %a0
    movel %a0, _current_task
    lea 0x2000(%a0), %sp
    /*
    * Assember start up done, start code proper.
    */
    jsr start_kernel /* Start Linux kernel */
    _exit:
    jmp _exit /* Should never get here */
    2、ram.ld也要有修改,
    MEMORY {
    ram : ORIGIN = 0x100000, LENGTH = 0x6e0000
    }
    从网上直接DOWN下来默认的ORIGIN = 0x20000,这样用我们的bootloader下载下去当然是跑不起来的。以下的SECTIONS 节不需要任何改动。
    3、修改vendors/HHTECH/M5272/Makefile
    image:
    [ -d $(IMAGEDIR) ] || mkdir -p $(IMAGEDIR)
    /usr/local/bin/genromfs -v -V "ROMdisk" -f $(ROMFSIMG) -d $(ROMFSDIR)
    m68k-elf-objcopy -O binary $(ROOTDIR)/$(LINUXDIR)/linux \
    $(IMAGEDIR)/linux.bin
    #gzip -f $(IMAGEDIR)/linux.bin 【注释掉】
    #cat $(IMAGEDIR)/linux.bin.gz $(ROMFSIMG) > $(IMAGE) 【注释掉】
    cat $(IMAGEDIR)/linux.bin $(ROMFSIMG) > $(IMAGE)
    $(ROOTDIR)/tools/cksum -b -o 2 $(IMAGE) >> $(IMAGE)
    [ -n "$(NO_BUILD_INTO_TFTPBOOT)" ] || cp $(IMAGE) /tftpboot
    #$(MAKE) -C ../../../colilo 【注释掉】
    BSS=`m68k-elf-objdump --headers $(ROOTDIR)/$(LINUXDIR)/linux | \
    grep .bss` ; \
    ADDR=`set -- $${BSS} ; echo 0x$${4}` ; \
    m68k-elf-objcopy --add-section=.romfs=$(ROMFSIMG) \
    --adjust-section-vma=.romfs=$${ADDR} --no-adjust-warnings \
    --set-section-flags=.romfs=alloc,load,data \
    $(ROOTDIR)/$(LINUXDIR)/linux $(ELFIMAGE) 2> /dev/null
    4、修改linux-2.4.x/drivers/block/blkmem.c的第125行开始的几句,注释掉defined(CONFIG_HHTECH),修改后代码如下:
    #ifdef CONFIG_COLDFIRE
    #ifdef CONFIG_TELOS
    #undef CONFIG_CARDE3
    #define CAT_ROMARRAY
    ......
    其实关键就是去掉CAT_ROMARRAY的定义。

    31、2.4 Linux内核大小 
    uClinux/linux-2.4.x/目录下编译出来的linux文件有5M多,这是ELF格式的文件,它转换成image.bin时还要用m68k-elf-objcopy工具转换成binary格式的linux.bin,这时它就只有800k了,再压缩一下就变成300k了。这个工作具体可参见uClinux/vendors/HHTECH/M5272/Makefile。

    32、从SDRAM中划出一块不让Linux访问,供自己使用,可作共享内存用
    对于2.4内核修改uClinux/linux-2.4.x/arch/m68knommu/platform/5272/HHTECH/crt0_ram.S(对于2.0ROM版,为uClinux/linux/arch/m68knommu/platform/5272/MOTOROLA/crt0_rom.S),只要修改一处:即文件最前面定义的MEM_SIZE宏,华恒提供的为0x01000000,即16M。用户若想划出8M为自己管理使用,只要将这个值改为0x00800000即可。这样作就是不让LINUX知道还有这8M地址,这样OS在分配内存的时候就不会用到这些地址空间,相当于从OS那里偷内存给自己使用。例如可用于内核驱动和应用程序的通信用共享内存等。

    33、关于通过NFS mount宿主机硬盘调试应用程序的问题
    嵌入式LINUX调试应用程序最主要的方式就是通过NFS mount宿主机硬盘上的应用程序来执行,通过观察其在串口终端打印的信息来达到调试的目的。
    在这个过程中,存在一个权限的问题,即板子mount宿主机硬盘后,这个NFS mount的操作默认的不是以root的权限执行的,因此一般的板子没有权限执行宿主机硬盘上的程序,这时就要在宿主机上执行chmod 777 app,其中app为应用程序可执行文件的名字。其实这种现象还是比较容易为开发人员解决的,因为当执行应用时,minicom就会报错,permision denied,或者unknown error 4。但对于有的情况就不一定这么容易看出是权限的问题:
    例如:调试WEB管理软件cgi代码时,我们把宿主机上/cgi-bin/通过NFS mount到板子的CGI工作目录/home/httpd/cgi-bin/上,这时通过浏览器IE执行CGI操作时,就会报错403,这里也是一个权限的问题,即CGI要求其工作目录可写,这时就必须在宿主机上执行:chmod 777 /cgi-bin,这时浏览器里立刻就可以工作了。

    34、关于uClinux下线程的使用
    使用线程是要求uClibc里加入pThread库编译。
    这样就要求make menuconfig时选择uClibc,而不是华恒默认选择的uC-libc。
    在uClibc中选择pThread库,可以按照以下操作:
    1.cd uClibc
    2.make menuconfig CROSS=m68k-elf-
    3.选上posix thread support选项
    4.cd ..
    5.make clean
    6.make
    库选择好之后,先不必自己编制应用程序,uClinux提供了完备的测试程序:即uClinux/user/threaddemos
    你make menuconfig在应用程序中选择这个程序加入编译,看是否可以编译通过,通过后执行是否正确即可。
    若编译有错误,可以直接到user/threaddemos目录下,执行:
    m68k-elf-gcc -m5307 -msep-data -Wl,-elf2flt -o bcdm bcdm.c -lpthread -lc
    可直接生成可执行文件bcdm,可直接mount到板子上执行。
    【注意】
    这里要求使用20030314的elf-tools 。
    华恒客户可发信到support@hhcn.com索取相关测试代码。

    35、关于支持模块动态加载(Loadable Module)的问题
    在make menuconfig中配置内核时选择:Loadable module support --->
    [*] Enable loadable module support 
    [] Set version information on all module symbols (NEW)
    [*] Kernel module loader (NEW)
    并在应用程序配置中选择busybox下的insmod/rmmod/lsmod,
    并选中: [*] Post 2.1 kernel modules。
    关于驱动模块的.c(例如mydriver.c)可这样编译,不需要单独写Makefile,就直接放到uClinux/linux2.4.x里面参与内核编译即可:
    cp mydriver.c uClinux-dist/linux-2.4.x/drivers/char
    然后修改uClinux-dist/linux-2.4.x/drivers/char/Makefile,
    obj-y += mem.o tty_io.o n_tty.o tty_ioctl.o raw.o pty.o misc.o random.o mydriver.o
    在最后加入mydriver.o
    这样内核就会用正确的参数编译出mydriver.o。
    然后把这个mydriver.o单独复制出来到uClinux-dist/romfs/usr下面,然后再把uClinux-dist/linux-2.4.x/drivers/char/Makefile恢复回去,然后再重编内核,烧写,启动后执行insmod /usr/mydriver.o即可成功。再lsmod看看。

    36、关于支持应用程序动态链接uClibc的问题
    早期的uClinux下的应用程序都是采用静态链接的方式链接uClibc/uC-libc,现在uClinux早就已经可以支持动态链接uClibc库了。动态链接的好处是可以实现多个应用程序实现代码共享,从而可以节省内存消耗,就相当于WINDOWS下的DLL所引入的共享优势。
    动态链接在NOMMU处理器上的实现采用了XIP(execute in place)代码。
    详细分析参见:
    http://mailman.uclinux.org/pipermail/uclinux-dev/2002-April/007786.html

    37、关于最新的20030314 elf toolschain
    参见:http://www.uclinux.org/pub/uClinux/m68k-elf-tools/
    下载m68k-elf-tools-20030314.sh到LINUX PC机上,chmod 777 m68k-elf-tools-20030314.sh,然后./m68k-elf-tools-20030314.sh则直接将最新的m68k-elf-xxx工具集合安装到/usr/local/下面,可以直接使用。

    38、执行我的应用程序时报错:BINFMT_FLAT: bad magic/rev (0x1010100, need 0x4)
    例如:mount宿主机上的nmbd,在minicom下执行/mnt/nmbd&,出现如下错误:
    BINFMT_FLAT: bad magic/rev (0x1010100, need 0x4)
    BINFMT_FLAT: bad magic/rev (0x1010100, need 0x4)
    /mnt/nmbd: Exec format error
    这是因为这个应用程序没有使用交叉编译工具编译,而是用了PC Linux下的gcc编译出来的。造成这种问题的经常是由于客户不是在uClinux目录下执行make统一进行编译,而是直接到该应用程序目录下执行make所致。这是由于uClinux/user/下每个应用程序的Makefile中使用了许多宏,列入CC等,这些宏都是统一在uClinux下的一个目录下的文件中定义的,若用户直接到user/下的应用程序目录下执行make,则这些宏就无法获取uClinux为其设定的值,而是自动采用PC Linux系统默认的,例如CC若用户不指定,默认的就是gcc,所以应用程序就没有用m68k-elf-gcc编译,而是用PC Linux下的gcc编译的,这样编出来的可执行文件在板子上当然是无法执行的了。

    39、关于关闭shell,使能串口通信
    鉴于我们提供的版本不断的升级,所以针对不同版本的方法略有区别,下面一共提供两种方法:
    方法一
    1.修改uClinux/linux-2.4.x/init/main.c
    - if (open("/dev/ttyS0", O_RDWR, 0) < 0)
    + if (open("/dev/null", O_RDWR, 0) < 0)
    修改以后启动过程中printk内容仍会打印,printf内容如rc内步骤不会打印。
    2.同时最好在user/init/simpleinit.c中不要启动/bin/sh,具体操作是:
    /* Fake an inittab entry if boot console defined */
    #ifdef CONFIG_USER_INIT_CONSOLE_SH
    #if LINUX_VERSION_CODE < 0x020100
    if (console_device && strcmp(console_device, "/dev/null"))
    #else
    if (have_console)
    #endif
    /*{ 【就是注释掉这一段代码!!!!】
    struct initline *p;
    p = inittab + numcmd++;
    init_itab(p);
    strcpy(p->fullline, "console");
    strcpy(p->tty, "console");
    strcpy(p->termcap, "linux");
    p->toks[0] = "/bin/sh";
    } */
    #endif
    **************************************************************************************
    方法二
    【这是最新的测试结果】
    通常不能使用COM1进行串口通信的原因是它已被重定向为标准输入输出,系统的shell进程将对其监视并处理从此处发送进来的数据,而不能被用户程序所得到。
    解决办法我认为有两种,一是不把Com1口重定向为标准输入输出,二是在标准输入输出上不启动shell。第一种没有试过,第二种可以按下面方法修改。
    ü uC的版本很多,好像有一种是在uClinux/linux-2.4.x/init/main.c中启动shell,该代码在do_shell函数中,检查main.c文件,如果有该函数,把
    if(open("/dev/ttyS0",O_RDWR,0)<0)
    改为
    if(open("/dev/null",O_RDWR,0)<0)
    没有就算了。
    在uClinux/user/init/simpleinit.c中注掉read_inittab函数中下面HHTECH部分的内容:
    void read_inittab(void)
    {
    numcmd = 0;
    /* Fake an inittab entry if boot console defined */
    #ifdef CONFIG_USER_INIT_CONSOLE_SH
    #if LINUX_VERSION_CODE < 0x020100
    if (console_device && strcmp(console_device, "/dev/null"))
    #else
    if (have_console)
    #endif
    /*HHTECH
    {
    struct initline *p;
    p = inittab + numcmd++;
    init_itab(p);
    strcpy(p->fullline, "console");
    strcpy(p->tty, "console");
    strcpy(p->termcap, "linux");
    strcpy(p->termcap, "linux");
    p->toks[0] = "/bin/sh";
    }
    */
    #endif
    read_initfile(_PATH_INITTAB);
    #ifdef CONFIG_USER_FLATFSD_FLATFSD
    read_initfile(_PATH_CONFIGTAB);
    #endif
    if (numcmd == 0)
    _exit(1);
    }
    注意:
    这样并不是说就永远不会启动shell了,只是在标准输出上不启动shell,但若用户telnet板子还是仍然会在p0口上启动shell的,否则用户就永远无法向板子发布command命令了。
    方法三
    make menuconfig
    Kernel hacking --->
    [*] Compiled-in Kernel Boot Parameter
    Kernel Boot Parameter: "CONSOLE=/dev/null" 
    这样选择最彻底,minicom里面没有任何内核启动的信息,但可以telnet板子操作。

    40、关于malloc使用问题
    在HHCF5272-R2的uClinux下测试, malloc最大空间约为2,048,000
    设为2,100,000时,出现:
    kernel panic : BUG!
    另:realloc结果同malloc一样,大小上没有区别;在内核中使用kmalloc,大小亦相同。

    41、MFC5272-16com 开发板串口波特率设置?
    #define SETBAUDRATE 0xe021
    #define SETBUFFER 0xe022
    #define SETFORMAT 0xe023
    对串口波特率修改为
    ioctl(spfd,SETBAUDRATE,4);
    波特率表
    char hh_mcf5272_baud_table[10][2] = {
    { 0x80, 0x01 }, /* 1200 300 *///0
    { 0xc0, 0x00 }, /* 2400 600 *///1
    { 0x60, 0x00 }, /* 4800 1200 *///2
    { 0x30, 0x00 }, /* 9600 2400 *///3
    { 0x18, 0x00 }, /* 19.2K 4800 *///4
    { 0x0c, 0x00 }, /* 38.4K 9600 *///5
    { 0x06, 0x00 }, /* 76.8K 19K *///6
    { 0x03, 0x00 }, /* 153.6K 38k *///7
    { 0x02, 0x00 }, /* 230.4K 56k *///8
    { 0x01, 0x00 } /* 460.8K 115k *///9
    };
    4就对应上表的19200,默认串口波特率为19200
    对串口传输格式修改
    ioctl(spfd,SETFORMAT,"8n1");
    默认为8n1

    42、关于CGI
    CGI是一种WEB SERVER端扩展代码。例如IIS端的ISAPI等开发的DLL代码都是WINDOWS上的WEB SERVER端的扩展,用于处理用户通过WEB浏览器的表单输入等静态页面以外的智能输入处理。
    CGI可用于WINDOWS和LINUX上的WEB SERVER端扩展,可用各种脚本如PHP等实现,最原始的就是用C代码实现的,具体用什么代码要看你的WEB SERVER是否支持。对于嵌入式LINUX采用的boa这个WEB SERVER而言,就不支持任何的脚本,只支持C代码的CGI程序,每连接一个客户端浏览器连接就在SERVER端(板子上)启动一个CGI进程的COPY。

    43、在REDHAT 9.0环境下使用BDM时,提示“DEVICE BUSY”,是并口被占用?
    请在您的REDHAT9机器上执行如下命令
    rmmod lp
    rmmod parport_pc
    rmmod parport
    这时您再执行./chk就没有问题了。

    44、xxx函数或者结构体是在哪里定义的等类似的问题。
    在LINUX这种开源的环境下工作,最重要的不是你己经掌握了多少知识,而是要掌握获取信息的手段,源代码都有,所有的知识都在那个uClinux的目录下,不需要任何的书籍,只有有搜索和查找的工具就可以获取任何需要的知识,搞开发就像学语言,模仿是非常有效的手段,看系统中运行的代码是怎么写的,仿照着做就是了。
    搜索和查找的手段就两个:
    一个是在所有文件中搜索字符串:
    grep xxx * -r
    另一个是搜索文件:
    find -name xxx.c
    还有,可在uClinux目录下(其实任何目录都可以,只是作用范围小些)用:
    ctags -R .
    这样在vi中阅读代码时可用ctrl+]跳转到想要查询的函数/结构定义处;用ctrl+T返回。

    45、关于uClinux浮点支持
    1.从/uClinux/lib/libm复制gcvt.c和mathf.h到/uClinux/lib/libc/stdio2
    2.修改/uClinux/lib/libc/stidio2/Makefile
    2a.增加gcvt.o编译项到
    OBJ= $(AOBJ) $(POBJ) $(SOBJ) dputs.o snprintf.o getdelim.o getline.o 
    gcvt.o
    ~~~~~~添加项
    2b.添加编译参数-DFLOATS=1到all: $(LIBC) $(OBJ)前面
    3.可增加make clean相关项
    Makefile文件如下:
    # Copyright (C) 1995,1996 Robert de Bath 
    # This file is part of the Linux-8086 C library and is distributed
    # under the GNU Library General Public License.
    LIBC=../libc.a
    ASRC=stdio.c
    AOBJ=_stdio_init.o fputc.o fgetc.o fflush.o fgets.o gets.o fputs.o \
    puts.o fread.o fwrite.o fopen.o fclose.o fseek.o rewind.o ftell.o \
    setbuffer.o setvbuf.o ungetc.o
    PSRC=printf.c
    POBJ=printf.o sprintf.o fprintf.o snprintf.o vprintf.o vsprintf.o \
    vfprintf.o vfnprintf.o vsnprintf.o
    SSRC=scanf.c 
    SOBJ=scanf.o sscanf.o fscanf.o vscanf.o vsscanf.o vfscanf.o
    OBJ= $(AOBJ) $(POBJ) $(SOBJ) dputs.o getdelim.o getline.o gcvt.o
    CFLAGS += -DFLOATS=1
    all: $(LIBC) $(OBJ)
    #@$(RM) $(OBJ)
    $(LIBC): $(LIBC)($(OBJ))
    $(LIBC)($(AOBJ)): $(ASRC)
    $(CC) $(CFLAGS) -DL_$* $< -c -o $*.o
    $(AR) $(ARFLAGS) $@ $*.o
    $(LIBC)($(POBJ)): $(PSRC)
    $(CC) $(CFLAGS) -DL_$* $< -c -o $*.o
    $(AR) $(ARFLAGS) $@ $*.o
    $(LIBC)($(SOBJ)): $(SSRC)
    $(CC) $(CFLAGS) -DL_$* $< -c -o $*.o
    $(AR) $(ARFLAGS) $@ $*.o
    transfer:
    -@rm -f ../include/stdio.h
    cp -p stdio.h ../include/.
    clean:
    rm -f *.o libc.a
    $(LIBC)($(OBJ)): stdio.h 

    46、关于用vfork实现多进程
    N多客户不停的在问vfork是否真正的多进程,要不就是认为父进程被挂起了。
    这里给个明确的答复,vfork+exec是多进程,而且父子进程同时并行执行,下面是个标准的例子代码:
    if ((pid=vfork())<0){
    ;
    }
    else if (pid==0){//child
    //execl("/bin/sh","/bin/sh","/tmp/tmp1",0);
    execl("/bin/sh","/bin/sh","/tmp/tmp1",(char*)0);
    exit(0);
    }

    47、在RH7.2上无法make的问题
    1.uClinux-dist/linux-2.4.x/scripts
    mv mkdep mkdep.bak
    gcc -o mkdep mkdep.c
    mv split-include split-include.bak
    gcc -o split-include split-include.c
    2.uClinux-dist/config/scripts
    download lxdialog-OK-rh7.2.tgz from HHTech to / 
    cd uClinux-dist/config/scripts
    mv lxdialog lxdialog.bak
    tar xzf /lxdialog-OK-rh7.2.tgz
    cp lxdialog/lxdialog /
    cd uClinux-dist/linux-2.4.x/scripts/lxdialog
    mv lxdialog lxdialog.bak
    cp /lxdialog .
    then 
    make dep
    make menuconfig
    works fine!!
    Happy hacking:-(.
    注意:上面的lxdialog-OK-rh7.2.tgz在华恒目前提供的软件光盘上都有提供。

    48、关于uClinux下执行make menuconfig的说明
    一般的,在LINUX下执行make menuconfig只是对LINUX内核进行配置。但uClinux将linux-2.4.x内核和应用程序user都整合到uClinux目录下,并将对应用程序的选择也集成到make menuconfig里面,下面给出简单的使用说明:
    在字符界面下,不要进入XWindows,
    cd uClinux-dist
    make menuconfig
    出现对话框,内容如下:(注意:下面有许多│ │,这是因为终端软件贴图的缘故)
    Target Platform Selection ---> │ │
    │ │ --- │ │
    │ │ Load an Alternate Configuration File │ │
    │ │ Save Configuration to an Alternate File │ │

    选择Target Platform Selection --->回车,出现如下界面:
    --- Choose a Vendor/Product combination. │ │
    │ │ (HHtech/M5249) Vendor/Product │ │
    │ │ --- Kernel is linux-2.4.x │ │
    │ │ (uC-libc) Libc Version │ │
    │ │ [ ] Default all settings (lose changes) │ │
    │ │ [*] Customize Kernel Settings │ │
    │ │ [*] Customize Vendor/User Settings (NEW) │ │
    │ │ [*] Update Default Vendor Settings 
    选择(即 [*] )第二项:[*] Customize Kernel Settings就是要对内核进行裁减配置;选择第三项:[*] Customize Vendor/User Settings (NEW)就是要挑选应用程序,至于第四项可选可不选,选了是当你的这个uClinux版本作为发布版本时,用户选择Default all settings (lose changes)就完全使用你的出厂配置。当然这个对于普通用户而言是没有什么用的。
    选择完毕后,选择下面的< Exit > 逐层退出,最后按照提示保存配置,则会分别出现内核和应用程序的选配菜单:
    下面是内核配置菜单:
    Linux Kernel v2.4.20-uc0 Configuration
    Code maturity level options ---> │ │
    │ │ Loadable module support ---> │ │
    │ │ Processor type and features ---> │ │
    │ │ General setup ---> │ │
    │ │ Memory Technology Devices (MTD) ---> │ │
    │ │ Parallel port support ---> │ │
    │ │ Plug and Play configuration ---> │ │
    │ │ Block devices ---> │ │
    │ │ Networking options ---> │ │
    │ │ Telephony Support ---> │ │
    │ │ ATA/IDE/MFM/RLL support ---> │ │
    │ │ SCSI support ---> │ │
    │ │ I2O device support ---> │ │
    │ │ Network device support ---> │ │
    │ │ Amateur Radio support ---> │ │
    │ │ IrDA (infrared) support ---> │ │
    │ │ ISDN subsystem ---> │ │
    │ │ Old CD-ROM drivers (not SCSI, not IDE) ---> │ │
    │ │ Character devices ---> │ │
    │ │ File systems --->
    ......
    随便选择一项,回车即可进去配置。配置完内核后保存配置,< Exit >退出则进入应用程序选配菜单:
    下面是应用程序配置菜单:
    uClinux v1.3.4 Configuration
    Core Applications ---> │ │
    │ │ Library Configuration ---> │ │
    │ │ Flash Tools ---> │ │
    │ │ Filesystem Applications ---> │ │
    │ │ Network Applications ---> │ │
    │ │ Miscellaneous Applications ---> │ │
    │ │ BusyBox ---> │ │
    │ │ Tinylogin ---> │ │
    │ │ MicroWindows ---> │ │
    │ │ Games ---> │ │
    │ │ Miscellaneous Configuration ---> │ │
    │ │ Debug Builds ---> │ │
    │ │ --- │ │
    │ │ Load an Alternate Configuration File │ │
    │ │ Save Configuration to an Alternate File 
    选择其中一项即可进行选择,例如:要加入一些网络类程序,例如DHCP服务器/客户端,SNMP,pppd/pppoe等都是选择Network Applications --->,回车后进入如下界面:打上*号即表示选中。
    [ ] arp │ │
    │ │ [*] boa │ │
    │ │ [ ] boa uses SSL (NEW) │ │
    │ │ [ ] bpalogin │ │
    │ │ [ ] br2684ctl │ │
    │ │ [ ] brcfg │ │
    │ │ [ ] bridge utils │ │
    │ │ [ ] dhcpcd │ │
    │ │ [ ] dhcpcd-new (2.0/2.4) │ │
    │ │ [ ] dhcpd │ │
    │ │ [ ] dhcpd(ISC) │ │
    │ │ [ ] dhclient(ISC) │ │
    │ │ [ ] dhcrelay (ISC) │ │
    │ │ [ ] diald │ │
    │ │ [*] discard │ │
    │ │ [ ] dnsmasq │ │
    │ │ [ ] ethattach │ │
    │ │ [ ] ez-ipupdate │ │
    │ │ [*] ftp │ │
    │ │ [ ] ftpd 
    还有常用的就是busybox的选配:
    BusyBox --->
    [*] BusyBox │ │
    │ │ --- Applets │ │
    │ │ [ ] adjtimex │ │
    │ │ [ ] ar │ │
    │ │ [ ] basename │ │
    │ │ [*] cat │ │
    │ │ [ ] chgrp │ │
    │ │ [ ] chmod │ │
    │ │ [ ] chown │ │
    │ │ [*] chroot │ │
    │ │ [ ] clear │ │
    │ │ [*] cmp │ │
    │ │ [ ] cp │ │
    │ │ [*] cut │ │
    │ │ [*] date │ │
    │ │ [ ] dc │ │
    │ │ [ ] dd │ │
    │ │ [*] df │ │
    │ │ [ ] dirname │ │
    │ │ [ ] dmesg 
    ......
    还有就是编译自己在uClinux/user下添加的应用程序时,一定要返回在uClinux目录下执行make
    不能直接在这个应用程序目录下执行make,这样做也不会慢的,因为内核和uC-libc库及其它user下的应用程序都已经编译过了,编译过程只是简单的检查一下STAMP,看它们都已经编译过了,就不再理睬,而直接找到你刚改动的应用程序目录进行编译,比单独编译这个应用程序慢一点点而已。
    对于armlinux和ppclinux而言,则应用程序就要逐个单独编译了,有人觉得这样方便,但也有人喜欢前者,呵呵,萝卜青菜各有所爱:-)

    49、关于uClibc和pThread库的一个BUG(感谢杭州客户李渊先生提供资料) 
    uCLibc/libc/stdlib/malloc/free.c里面有个小错误,这个错误只有在大程序,大数据并且使用了pthread库的时候才会出现,
    现象是malloc会死锁不动,改正的方法是在 __free_to_heap函数最后加一句 "__heap_unlock(heap);" 就可以了。

    50、关于目前uClinux for MCF52XX中断处理的一个BUG纠正
    就是要修改原来清除中断的方式由 |= 改为 = 即可。
    以HHCF5249的IDE驱动为例:uClinux-dist/linux-2.4.x/include/asm/ide.h
    修改的地方仅一行,参见下面加红加粗代码:
    static IDE_INLINE void
    ide_get_lock(
    int *ide_lock,
    void (*handler)(int, void *, struct pt_regs *),
    void *data)
    {
    #ifdef CONFIG_M5249C3
    /* Clear interrupts for GPIO5 */
    *((volatile unsigned long *) 0x800000c0) = 0x00002020; 
    #endif
    }
    再看以太网驱动:
    uClinux-dist/linux-2.4.x/drivers/net/dm9000x.c修改如下:
    static void dmfe_interrupt(int irq, void *dev_id, struct pt_regs *regs) 函数中:
    *((volatile unsigned long *) (MCF_MBAR2+MCFSIM2_GPIOINTCLEAR)) = 0x00004040; 

    51、关于HHCF5249读写CS2~CS6死机的问题
    发现怪现象,无法将CSOR1的数值改为0x3d80,怎么改都还是0x3c80,这样一读就死机。这时,用MEMORY读写工具(user/memtools)mt来手工读写这些CPU寄存器地址:
    /> mt
    Please input your command :>>>
    gl 10000090 1
    00FF0021
    Please input your command :>>>
    gw 10000096 1
    3C80
    Please input your command :>>>
    pw 10000096 3d80
    Please input your command :>>>
    gw 10000096 1
    3C80
    Please input your command :>>>
    gw 10000096 1
    3C80
    Please input your command :>>>
    pw 10000096 3d80
    Please input your command :>>>
    gw 10000096 1
    3C80
    经查,在uClinux-dist/linux-2.4.x/arch/m68knommu/platform/5249/HHTECH/crt0_ram.S里面设置的CSOR1还是正确的0x3d80:
    /*
    * Setup CS1 for ethernet controller.
    * (Setup as per M5249C3 doco).
    */
    move.l #0x30000000, %d0 /* CS1 mapped at 0x30000000 */
    move.l %d0, 0x8c(%a0)
    move.l #0x00ff0021, %d0 /* CS1 size of 16Mb */
    move.l %d0, 0x90(%a0)
    move.w #0x3d80, %d0 /* CS1 = 16bit port, AA */
    move.w %d0, 0x96(%a0)
    但在LINUX内核启动过程中就不知哪里自动给变成0x3c80了,最后确认是在以太网DM9000驱动中给改掉了,见下面红字标注的代码:
    #define NEEDCHANGE 1
    ......
    inline void outw(unsigned short value, unsigned long addr)
    {
    cli();
    #if NEEDCHANGE
    *(volatile unsigned short*)0x10000096 |=0x0100;
    #endif
     
    // udelay(10);
    *(volatile unsigned short*)addr = value;
    #if NEEDCHANGE
    *(volatile unsigned short*)0x10000096 &=0xfeff;
    #endif
    sti();
    }
    将uClinux-dist/linux-2.4.x/drivers/net/dm9000x.c 文件前面的一行改为:(原来是1,改为下面的0)
    #define NEEDCHANGE 0
    然后重新编译,重烧即可。
    【注意】
    并不是所有的版本的dm9000x.c都是这个样子的,有的就没有上面的问题。

    52、关于init: /bin/boa respawning too fast问题
    /etc/inittab文件:由init进程监护执行,保证inittab文件中列出的服务进程任何时候都有一份拷贝在内存中运行。它的作用就在于维护一些系统中非常关键的后台服务进程,使之永远有一份在运行,即使发生致命错误导致进程死掉退出,init进程会负责自动再重新启动一个。下面给出/etc/inittab文件的内容:
    inet:unknown:/bin/inetd 【启动inet服务】
    有时我们在板子启动后,可看到minicom里不停打印类似下面的信息:
    init: /bin/boa respawning too fast
    这就是因为在板子的/etc/inittab文件里面有这样一行:
    boa:unknown:/bin/boa 
    而你make menuconfig选择应用程序时又没有选中boa,导致烧到板子上的/bin目录里面没有boa这个程序,init进程就会不停的试图启动boa,但又没有,它就不停的尝试,所以才会有这样的信息出来。所以,要么你就把boa编进去,要么就把/etc/inittab里面这一行用#号注释掉,重编重烧后就没有问题了。

    展开全文
  • 是通过Android开发板上的串口进行数据的读取操作,一块android开发板外接一个Arduino,再接一个传感器,当传感器上返回数据后在Arduino上进行编程处理,转换成Android程序想要的数据,再通过串口通信输入Android...

    Android开发板之串口开发

    简介

    首先描述一下我的应用项目,它是一个简单的智能盒子,主要内容:是通过Android开发板上的串口进行数据的读取操作,一块android开发板外接一个Arduino,再接一个传感器,当传感器上返回数据后在Arduino上进行编程处理,转换成Android程序想要的数据,再通过串口通信输入到Android程序中进行响应操作。

    那说完用途,接下来说说这个具体的Android开发,Android的串口编程,在网上我们可以找到开源项目android-serialport-api,这个在github下载到:https://github.com/cepr/android-serialport-api(GITHUB的地址),有兴趣的可以下载源码做做研究。

    由于android-serialport-api的代码的复杂,对于初学者不是很适合,所以我将串口开发的几个主要涉及到的知识点抽取出来,使大家易懂容易上手。

    精华集锦:关于Android串口,简单的总结出主要的四部曲:打开串口,串口输入,串口输出,关闭串口。而串口有五个重要的参数:串口设备名,波特率,检验位,数据位,停止位,其中检验位一般默认位NONE,数据位一般默认为8,停止位默认为1,校验位是为了减少误差的会根据奇、偶进行补位操作,下面具体的和我一起来分解我的项目:

    一、项目配置

    首先,看一下项目结构:创建了一个jni和jniLibs的两个包文件夹,在AndroidStudio 下SO文件需要放在jniLibs下,将MK和C一些文件放在jni下。如图:

    项目结构图

    再就是,在main/Java下,创建android_serialport_api包,放入SerialPort.java文件,这个包名必须的是和jni包下的SerialPort.c中的函数名同步的,(这样做是为了保持与C函数中的方法名同步,如果不这样做你需要根据你自己建立的包的名字来更改SerialPort.c中的函数名,其实无所谓了)。

    二、代码讲解

    1、程序的入口activity类:
    在这个类中,通过设置按钮配置,打开按钮打开串口,发送按钮点击向串口输出数据,实现了在串口TX和RX短接的情况下,模拟串口的读写数据。

    package com.xkdx.serial_test;
    
    
    import android.app.Activity;
    import android.os.Bundle;
    import android.os.Handler;
    import android.text.TextUtils;
    import android.util.Log;
    import android.view.View;
    import android.view.View.OnClickListener;
    import android.widget.Button;
    import android.widget.EditText;
    import android.widget.TextView;
    
    import java.io.File;
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.OutputStream;
    
    import android_serialport_api.SerialPort;
    
    public class MainActivity extends Activity {
        protected SerialPort mSerialPort;
        protected InputStream mInputStream;
        protected OutputStream mOutputStream;
        private TextView text;
        private String prot = "ttyS0";//串口号(具体的根据自己的串口号来配置)
        private int baudrate = 9600;//波特率(可自行设定)
        private static int i = 0;
        private StringBuilder sb;
    
        Handler handler = new Handler() {
            public void handleMessage(android.os.Message msg) {
                if (msg.what == 1) {
                    text.setText(text.getText().toString().trim()+sb.toString());
                }
            }
        };
        private Thread receiveThread;
        private Thread sendThread;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            sb = new StringBuilder();
            text = (TextView) findViewById(R.id.text_receive);
            //设置按钮事件
            Button btn_set = (Button) findViewById(R.id.btn_set);
            btn_set.setOnClickListener(new OnClickListener() {
    
                @Override
                public void onClick(View v) {
                    EditText et_prot = (EditText) findViewById(R.id.et_prot);
                    EditText et_num = (EditText) findViewById(R.id.et_num);
                    prot = TextUtils.isEmpty(et_prot.getText().toString().trim()) ? "ttyS0"
                            : et_prot.getText().toString().trim();
                    baudrate = Integer.parseInt(TextUtils.isEmpty(et_num.getText()
                            .toString().trim()) ? "9600" : et_num.getText()
                            .toString().trim());
                }
            });
    
            //打开按钮事件
            Button btn_open = (Button) findViewById(R.id.btn_open);
            btn_open.setOnClickListener(new OnClickListener() {
    
                @Override
                public void onClick(View v) {
                    // 配置并打开串口
                    try {
                        mSerialPort = new SerialPort(new File("/dev/" + prot), baudrate,
                                0);
                        mInputStream = mSerialPort.getInputStream();
                        mOutputStream = mSerialPort.getOutputStream();
                        receiveThread();
                    } catch (SecurityException e) {
                        e.printStackTrace();
                    } catch (IOException e) {
                        Log.i("test", "打开失败");
                        e.printStackTrace();
                    }
                }
            });
    
            //发送按钮事件
            Button btn_send = (Button) findViewById(R.id.btn_send);
            btn_send.setOnClickListener(new OnClickListener() {
    
                @Override
                public void onClick(View v) {
                    // 发送串口信息
                    sendThread = new Thread() {
                        @Override
                        public void run() {
                            while (true) {
                                try {
                                    i++;
                                    mOutputStream.write(("1").getBytes());
                                    Log.i("test", "发送成功:1" + i);
                                    Thread.sleep(1000);
                                } catch (Exception e) {
                                    Log.i("test", "发送失败");
                                    e.printStackTrace();
                                }
                            }
                        }
                    };
                    sendThread.start();
                }
            });
    
            //关闭按钮事件
            Button btn_close = (Button) findViewById(R.id.btn_receive);
            btn_receive.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View v) {
                    closeSerialPort();
                }
            });
    
        }
    
        private void receiveThread() {
            // 接收串口信息
            receiveThread = new Thread() {
                @Override
                public void run() {
                    while (true) {
                        int size;
                        try {
                            byte[] buffer = new byte[1024];
                            if (mInputStream == null)
                                return;
                            size = mInputStream.read(buffer);
                            if (size > 0) {
                                String recinfo = new String(buffer, 0,
                                        size);
                                Log.i("test", "接收到串口信息:" + recinfo);
                                sb.append(recinfo).append(",");
                                handler.sendEmptyMessage(1);
                            }
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }
            };
            receiveThread.start();
        }
    
        /**
         * 关闭串口
         */
        public void closeSerialPort() {
    
            if (mSerialPort != null) {
                mSerialPort.close();
            }
            if (mInputStream != null) {
                try {
                    mInputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (mOutputStream != null) {
                try {
                    mOutputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
    
        }
    
    }
    
        2、在整个项目中SerialPort.Java是最重要的类,在这个类中完成了对串口权限的修改,通过jni调用底层C函数,实现串口的一系列操作。代码:
    
    package android_serialport_api;
    
    import android.util.Log;
    import java.io.File;
    import java.io.FileDescriptor;
    import java.io.FileInputStream;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.OutputStream;
    
    public class SerialPort {
    
        private static final String TAG = "SerialPort";
        private FileDescriptor mFd;
        private FileInputStream mFileInputStream;
        private FileOutputStream mFileOutputStream;
    
        public SerialPort(File device, int baudrate, int flags) throws SecurityException, IOException {
    
            //检查访问权限,如果没有读写权限,进行文件操作,修改文件访问权限
            if (!device.canRead() || !device.canWrite()) {
                try {
                    //通过挂在到linux的方式,修改文件的操作权限
                    Process su = Runtime.getRuntime().exec("/system/bin/su");
                    //一般的都是/system/bin/su路径,有的也是/system/xbin/su
                    String cmd = "chmod 777 " + device.getAbsolutePath() + "\n" + "exit\n";
                    su.getOutputStream().write(cmd.getBytes());
    
                    if ((su.waitFor() != 0) || !device.canRead() || !device.canWrite()) {
                        throw new SecurityException();
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                    throw new SecurityException();
                }
            }
    
            mFd = open(device.getAbsolutePath(), baudrate, flags);
    
            if (mFd == null) {
                Log.e(TAG, "native open returns null");
                throw new IOException();
            }
    
            mFileInputStream = new FileInputStream(mFd);
            mFileOutputStream = new FileOutputStream(mFd);
        }
    
        // Getters and setters
        public InputStream getInputStream() {
            return mFileInputStream;
        }
    
        public OutputStream getOutputStream() {
            return mFileOutputStream;
        }
    
        // JNI(调用java本地接口,实现串口的打开和关闭)
        /**
         * @param path     串口设备的据对路径
         * @param baudrate 波特率
         * @param flags    校验位
         */
        private native static FileDescriptor open(String path, int baudrate, int flags);
    
        public native void close();
    
        static {//加载jni下的C文件库
            System.loadLibrary("serial_port");
        }
    }

    3、再来看看SerialPort.c文件,在这个C文件中,主要实现了Open()和Close()方法的实现。

    /*
     * Copyright 2009-2011 Cedric Priscal
     *
     * Licensed under the Apache License, Version 2.0 (the "License");
     * you may not use this file except in compliance with the License.
     * You may obtain a copy of the License at
     *
     * http://www.apache.org/licenses/LICENSE-2.0
     *
     * Unless required by applicable law or agreed to in writing, software
     * distributed under the License is distributed on an "AS IS" BASIS,
     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     * See the License for the specific language governing permissions and
     * limitations under the License.
     */
    
    #include <termios.h>
    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <string.h>
    #include <jni.h>
    
    #include "SerialPort.h"
    
    #include "android/log.h"
    static const char *TAG="serial_port";
    #define LOGI(fmt, args...) __android_log_print(ANDROID_LOG_INFO,  TAG, fmt, ##args)
    #define LOGD(fmt, args...) __android_log_print(ANDROID_LOG_DEBUG, TAG, fmt, ##args)
    #define LOGE(fmt, args...) __android_log_print(ANDROID_LOG_ERROR, TAG, fmt, ##args)
    
    static speed_t getBaudrate(jint baudrate)
    {
        switch(baudrate) {
        case 0: return B0;
        case 50: return B50;
        case 75: return B75;
        case 110: return B110;
        case 134: return B134;
        case 150: return B150;
        case 200: return B200;
        case 300: return B300;
        case 600: return B600;
        case 1200: return B1200;
        case 1800: return B1800;
        case 2400: return B2400;
        case 4800: return B4800;
        case 9600: return B9600;
        case 19200: return B19200;
        case 38400: return B38400;
        case 57600: return B57600;
        case 115200: return B115200;
        case 230400: return B230400;
        case 460800: return B460800;
        case 500000: return B500000;
        case 576000: return B576000;
        case 921600: return B921600;
        case 1000000: return B1000000;
        case 1152000: return B1152000;
        case 1500000: return B1500000;
        case 2000000: return B2000000;
        case 2500000: return B2500000;
        case 3000000: return B3000000;
        case 3500000: return B3500000;
        case 4000000: return B4000000;
        default: return -1;
        }
    }
    
    /*
     * Class:     android_serialport_SerialPort
     * Method:    open
     * Signature: (Ljava/lang/String;II)Ljava/io/FileDescriptor;
     */
    JNIEXPORT jobject JNICALL Java_android_1serialport_1api_SerialPort_open
      (JNIEnv *env, jclass thiz, jstring path, jint baudrate, jint flags)
    {
        int fd;
        speed_t speed;
        jobject mFileDescriptor;
    
        /* Check arguments */
        {
            speed = getBaudrate(baudrate);
            if (speed == -1) {
                /* TODO: throw an exception */
                LOGE("Invalid baudrate");
                return NULL;
            }
        }
    
        /* Opening device */
        {
            jboolean iscopy;
            const char *path_utf = (*env)->GetStringUTFChars(env, path, &iscopy);
            LOGD("Opening serial port %s with flags 0x%x", path_utf, O_RDWR | flags);
            fd = open(path_utf, O_RDWR | flags);
            LOGD("open() fd = %d", fd);
            (*env)->ReleaseStringUTFChars(env, path, path_utf);
            if (fd == -1)
            {
                /* Throw an exception */
                LOGE("Cannot open port");
                /* TODO: throw an exception */
                return NULL;
            }
        }
    
        /* Configure device */
        {
            struct termios cfg;
            LOGD("Configuring serial port");
            if (tcgetattr(fd, &cfg))
            {
                LOGE("tcgetattr() failed");
                close(fd);
                /* TODO: throw an exception */
                return NULL;
            }
    
            cfmakeraw(&cfg);
            cfsetispeed(&cfg, speed);
            cfsetospeed(&cfg, speed);
    
            if (tcsetattr(fd, TCSANOW, &cfg))
            {
                LOGE("tcsetattr() failed");
                close(fd);
                /* TODO: throw an exception */
                return NULL;
            }
        }
    
        /* Create a corresponding file descriptor */
        {
            jclass cFileDescriptor = (*env)->FindClass(env, "java/io/FileDescriptor");
            jmethodID iFileDescriptor = (*env)->GetMethodID(env, cFileDescriptor, "<init>", "()V");
            jfieldID descriptorID = (*env)->GetFieldID(env, cFileDescriptor, "descriptor", "I");
            mFileDescriptor = (*env)->NewObject(env, cFileDescriptor, iFileDescriptor);
            (*env)->SetIntField(env, mFileDescriptor, descriptorID, (jint)fd);
        }
    
        return mFileDescriptor;
    }
    
    /*
     * Class:     cedric_serial_SerialPort
     * Method:    close
     * Signature: ()V
     */
    JNIEXPORT void JNICALL Java_android_1serialport_1api_SerialPort_close
      (JNIEnv *env, jobject thiz)
    {
        jclass SerialPortClass = (*env)->GetObjectClass(env, thiz);
        jclass FileDescriptorClass = (*env)->FindClass(env, "java/io/FileDescriptor");
    
        jfieldID mFdID = (*env)->GetFieldID(env, SerialPortClass, "mFd", "Ljava/io/FileDescriptor;");
        jfieldID descriptorID = (*env)->GetFieldID(env, FileDescriptorClass, "descriptor", "I");
    
        jobject mFd = (*env)->GetObjectField(env, thiz, mFdID);
        jint descriptor = (*env)->GetIntField(env, mFd, descriptorID);
    
        LOGD("close(fd = %d)", descriptor);
        close(descriptor);
    }
    

    4、Android.mk

    LOCAL_PATH := $(call my-dir)
    
    include $(CLEAR_VARS)
    TARGET_PLATFORM := android-3
    LOCAL_MODULE    := serial_port
    LOCAL_SRC_FILES := SerialPort.c
    LOCAL_LDLIBS    := -llog
    
    include $(BUILD_SHARED_LIBRARY)
        如果你要修改so文件的名称,请修改LOCAL_MODULE:= serial_port,主要的就是这些,还有其他的类就不一 一说了,打开一看就明白。
    

    调试和总结:

    在调试中如果大家没有串口硬件的话可以使用PC机+模拟器完成调试实验。具体操作方法:

    1、打开cmd进入到android开发的sdk目录下的tools文件夹下;

    2、执行命令emulator @(你自己的模拟器的名字) -qemu -serial COM3(COM3是你的PC的一个串口通过命令挂载到你的模拟器上,当然你也可以是COM1跟你电脑对应);例:我的模拟器叫123,我给模拟器挂载的电脑上的串口是COM3,则执行:emulator @123 -qemu -serial COM3

    这样你会看到模拟器已经启动,这时候你将程序部署上运行即可。
    如果用程序打开串口,提示没有读写权限。应该在命令提示符下用linux命令赋予读写的权限: 进入shell:adb shell 进入设备目录:#cd dev 修改权限:#chmod 777 ttyS2即可。(这个我已经写入程序内,无需在添加,如果LOG日志打印显示无权限,那就只能你手动在执行一边,再解说以下这个进入shell,这个是adb shell,你的命令执行的目录下必须有adb shell.exe多的不解释了)

    Demo截图:

    这里写图片描述

    下载地址:seriail_test串口测试代码

    Demo运行操作说明:由于只是为测试程序的串口收发通过性,所以将串口的TX和DX短接,然后实现自发自收的功能。主要操作:首先你将串口的TX和DX短接后,在APP里面输入端口号和波特率,点击设置按钮,如果不进行设置,则默认端口为ttyS0,波特率为9600设置完后,点击打开按钮,去设置和打开串口,再去点击发送按钮,程序就进行自己发送信息了,默认内容为1;最后点击关闭则关闭串口,释放流对象。(Demo只是为测试代码的通过性,如果按照我描述的步骤走,不会有BUG,测试已通过)。

    希望我的文章对你有一定的帮助,要是有实在解决不了的问题,请留言给我!如果你有什么好的分享也请留言给我!

    展开全文
  • 在嵌入式开发中,我们都是使用串口进行...需要一种方法把串口所有输出重定向telnet。  主要是利用tty的ioctl重定向方法来实现重定向,下面直接贴代码: /************************************************

    嵌入式开发中,我们都是使用串口进行调试定位问题。然而在成品调试或者远程调试时,没有串口,只能telnet进去,少了很多应用进程的打印,这样就不利于我们发现问题。需要一种方法把串口所有输出重定向到telnet。

      主要是利用tty的ioctl重定向方法来实现重定向,下面直接贴代码:

    /************************************************************
    Copyright (C), 2017, Leon, All Rights Reserved.
    FileName: console_redirect.c
    Description: console输出重定向
    Author: Leon
    Version: 1.0
    Date: 2017-2-6 15:33:12
    Function:
    History:
    <author>    <time>  <version>   <description>
     Leon
    ************************************************************/
    
    /*
        内核的打印不能重定向过来,应用层打印可以重定向打印过来
        查看内核的打印,cat /proc/kmsg,在输出完缓冲区内容后,会阻塞卡住,内核有新的输出时会继续输出。
        如果要把内核打印到telnet,那么需要修改printk.c。
        kernel和user空间下都有一个console,关系到kernel下printk的方向和user下printf的方向,实现差别很大。
        kernel下的console是输入输出设备driver中实现的简单的输出console,只实现write函数,并且是直接输出到设备。
        user空间下的console,实际就是tty的一个特殊实现,大多数操作函数都继承tty,所以对于console的读写,都是由kernel的tty层来最终发送到设备。
    */
    
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <fcntl.h>
    #include <sys/ioctl.h>
    #include <unistd.h>
    
    int main(int argc, char *argv[])
    {
        int tty = -1;
        char *tty_name = NULL;
    
        if(argc < 2)
        {
            printf("miss argument\n");
            return 0;
        }
    
        /* 获取当前tty名称 */
        tty_name = ttyname(STDOUT_FILENO);
        printf("tty_name: %s\n", tty_name);
    
        if(!strcmp(argv[1], "on"))
        {
            /* 重定向console到当前tty */
            tty = open(tty_name, O_RDONLY | O_WRONLY);
            ioctl(tty, TIOCCONS);
            perror("ioctl TIOCCONS");
        }
        else if(!strcmp(argv[1], "off"))
        {
            /* 恢复console */
            tty = open("/dev/console", O_RDONLY | O_WRONLY);
            ioctl(tty, TIOCCONS);
            perror("ioctl TIOCCONS");
        }
        else
        {
            printf("error argument\n");
            return 0;
        }
    
        close(tty);
        return 0;
    }
    • 使用
      交叉编译tftp进板子,然后telnet进去,用on参数执行这个程序就好了

    • 内核打印到telnet的思考
      内核的打印没有走tty的中间层,直接在printk哪儿调用了注册设备的write方法。如果要把printk打印到telnet,那么就需要分析下printk实现代码。自己改了下在printk输出时把数据重新写到/dev/console的这种方法,因为对printk逻辑不清楚,锁处理会产生问题,造成死锁或者系统挂掉。

    展开全文
  • 如果设备具有4G或者上网功能,通过编写一个应用程序,利用网络把设备的串口信息重定向你自己电脑的TCP工具,就可以通过TCP工具远程调试设备了。 一、嵌入式设备可以上网 要想把设备的串口数据传你电脑,首先...
  • 在基于嵌入式ARM开发产品的时候,有时候由于ARM处理器UART数量的限制,我们需要将系统默认的调试串口打印信息关闭或者转移其他串口上面去,本文即演示更改uboot和linux kernel 调试串口的过程示例。   本文所...
  • 只需要设置好端口(Serial Line),Linux中默认接上串口后,端口为ttyS0,或者ttyUSB0(对应USB转串口),如果再多接一个,就会显示ttyS1或者ttyUSB1(Window中,普通串口对应的是COM1、COM2,USB对应COM3, 需要安装...
  • 文章目录原文Linux中重定向 原文 ...在嵌入式Linux系统中,有时通过远程(telnet或者ssh)登录现场设备,想看程序的实时打印的调试信息,需要将输出...下面是实现的代码,可以将输出到串口日志信息,重定向当前的tel
  • 有时候,在linux 环境下,使用printf 无法打印信息,导致调试很不方便。 2 原因分析 2.1 没有刷新缓冲区 默认Linux 设置了打印缓冲功能,当打印缓冲区未满情况下,不打印。如果需要打印,增加fflush(stdout) ...
  • 一.背景 LS1021ATWR开发板运行...2.1 linux相关日志 root@OpenWrt:/# reboot 重启 root@OpenWrt:/# [ 2324.325719] device eth0 left promiscuous mode 网卡eth0离开混杂模式 [ 2324.330172] br-lan: port 1(et...
  • 嵌入式开发输出调试信息的几种方法 嵌入式开发输出调试信息的几种方法 1、输出log信息SRAM 2、通过SWO输出log 2.1 通过IDE输出 2.2 通过STM32 ST-LINK Utility输出 3、通过串口输出log 4、使用IO模拟串口...
  • 经过多次PCB打样和全面调试,在2014年4月初已经完成了cc2540 cc2541的开发板的批量贴片工作, 就此硬件相关的工作告一段落, 接下来是全面完善软件方面的工作, 考虑软件上的继承性,一些别的开发者已经完成的例程...
  • 在笔者装环境之前,先将笔者的开发板配置总结一下: SCP 1G封装 4.3寸电阻屏 RGB-LCD 没有WIFI模块(不打算用这个) win10环境,显寸8G(玩arm绝对够用) PC环境安装 参考配套教程,windows下安装如下工具: ...
  • 有时调试内核模块,打印信息太多了,可以通过修改/proc/sys/kernel/...该文件有四个数字值,它们根据日志记录消息的重要性,定义将其发送何处。关于不同日志级别的更多信息,请查阅syslog(2)联机帮助。上面显示的4...
  • linux查看新增串口、USB设备,Minicom + Usb转串口注意:虚拟机环境下的ubuntu默认情况下是不能自动识别的,需要在虚拟机窗口右下角点击"Prolific USB-Serial Controller",然后选择"Connect...
  • 1. 首先在Linux里面装上tftp server,详见上一篇转载的日志。基本上没问题的。 2. Linux上网使用桥接,设置windows,linux开发板的IP分别是192.168.1.x1,192.168.1.x2,192.168.1.x3。这一点很重要!一定得是...
  • 版权声明:本文为博主原创文章,未经博主允许不得转载。...只需要设置好端口(Serial Line),Linux中默认接上串口后,端口为ttyS0,或者ttyUSB0(对应USB转串口),如果再多接一个,就会显示tt...
  • 嵌入式调试printf与分配rintf 除了人工的分析之外,最简单最直接的调试方法要算printf了。不过,我们这里推荐使用的并不是初学C语言时使用的函数int printf(const char *format, ...),而是稍微复杂一点的fprintf()...
1 2 3 4 5 ... 20
收藏数 1,010
精华内容 404