嵌入式程序示例

 

程序示例

 

    这里是一些程序示例和源码解释.一些相关程序代码会陆续补上.相关问题可参见硬件驱动

系统初始化(PowerPC汇编) VxWorks BSP文件配置及生成下载 
硬件中断 VxWorks网络驱动
FlashROM驱动(ARM汇编) ARM Boot Load程序(ARM汇编)
串口(UART)程序示例 PCI设备(网卡)初始化代码分析
文件系统  
LCD和触摸屏  
MODEM 拨号  
实时时钟 RTC  

 

系统初始化

系统初始化对不同的CPU,基本步骤是类似的.

系统初始化的主要步骤如下

  • 启动
  • 关闭中断
  • 放boot type到堆栈
  • 清空缓存

VxWorks 系统的 PowerPC BSP,系统开机后执行的第一个函数 romInit(),在ROM的起点,这里是使用的PowerPC汇编语言

/* 定义内部函数 internals */
.globl romInit /* start of system code */
.globl _romInit /* start of system code */

/* 定义外部函数 externals */
.extern romStart /* system initialization routine */

.text
.align 2

/*******************************************************************************
*
* romInit ( int startType /@ only used by 2nd entry point @/ )
*/

romInit:
_romInit:
bl cold    /* 冷启动 */
bl warm    /* 热启动 */

cold:
li p5, BOOT_COLD
bl start /* skip over next instruction */

warm:
or p5, p0, p0 /* startType to p5 */

start:        /* 此处是系统启动开始 */

/* 屏蔽MSR中CE,EE位,关闭所有的外部中断
/*
* Disable external interrupts
*/

mfmsr p0 /* p0 = msr */
INT_MASK (p0, p1) /* mask EE and CE bit */
ori p1,p1,_PPC_MSR_ME /* enable machine checks */
mtmsr p1 /* msr = p1 */
isync

/* 下面两步是按照硬件定义初始化一些SPR,DCR寄存器,置0或置1

/* SPR是特殊功能寄存器,DCR为设备控制寄存器,还有MSR机器状态寄存器,这些是PowerPC内核中很重要的寄存器

/* 初始化SPR,DCR寄存器置0
* Initalize registers that need to be set to zero.
*/

addi r4,r0,0x0000
mtspr SGR,r4 /* 解锁所有存储区域 SPR 中 SGR 位置0 */
mtspr ESR, r4 /* SPR中的错误状态位 ESR 清0 */
mtspr TCR, r4 /* 关闭所有的 timers */
mtspr PIT, r4 /* 清0 PIT timer */
mtdcr UICER, r4 /* 关闭中断控制器(UIC)中的所有中断 */
mtspr XER, r4 /* 清0 integer exception 寄存器 */

/* 初始化另一些SPR,DCR寄存器置1
* Initalize registers that need to be cleared with 0xFFFFFFFF.
*/

addis r4,r0,0xffff
ori r4,r4,0xffff
mtspr TSR, r4 /* timer  */
mtspr DBSR, r4 /* 调试状态位置1 */
mtdcr UICSR, r4 /* 清除中断控制器(UIC)中的所有 pending 中断 */
mtdcr dmasr, r4 /* DMA状态寄存器置1 */

/* PowerPC405用两个缓存,一个是16K指令缓存(ICU),一个是6K数据缓存(DCU),下面是清空着两个缓存,并根据硬件设置缓存 */

/* 清空指令缓存 */
/*BESR type regs ZZZZZZZZZZZZ
* Invalidate the entire instruction cache. This can be done
* with a single iccci instruction in the processor core.
*/

iccci r0, r0

/*清空数据缓存
* Invalidate the entire data cache.
* The 405 processor core in the 405GP has 128 congruence classes.
* Each cache line in the 405 processor is 32 bytes.
*/

/*
* Turn the instruction cache on for faster boot-up.
* Also, the icache is needed to help initialize Bank 0
* of the EBC to speed up accesses to flash.
* address space 0x00000000-0x07ffffff is cached
* address space 0xf8000000-0xffffffff is cached
*/

lis p0, HIADJ(_PPC403_ICCR_DEFAULT_VAL)
addi p0, p0, LO(_PPC403_ICCR_DEFAULT_VAL)
mtspr _PPC403_ICCR, p0
isync

/* 初始化外部总线控制器(EBC),跳转指令BL,执行extBusCntlrInit
* /

bl extBusCntlrInit

/*
* Now that the EBC Bank 0 has been set up, turn the I-cache off if
* i-cache was not specified in config.h. It is also invalidated
* again.
*/

#ifndef USER_I_CACHE_ENABLE
li p0, 0 /* clear p0 */
mtspr _PPC403_ICCR, p0 /* turn off i-cache */
isync
iccci r0, r0 /* invalidate the I-cache again */
#endif

/* 初始化和SDRAM相关的IIC(inter-integrated circut)寄存器IIC0,
* Initialize IIC0 for use in automatic SDRAM configuration
*/

#ifdef LOCAL_MEM_AUTOSIZE
bl iic0Init
#endif

/* 初始化SDRAM,BL跳转执行sdramInit
* Configure the SDRAM controller only if this is a cold boot.
* If the SDRAM controller is reinitialized on a warm boot, the
* boot line will get wiped out because of the ECC SDRAM memory
* initialization.
*/

li p0, BOOT_COLD
and. p0, p0, p5 /* p5 is saved at the entry of romInit */
beq skip

bl sdramInit

skip:

/*
* Clear the CPU reservation bit
*/

li r0, 0
lwarx p0, r0, r0
stwcx. p0, r0, r0

#ifdef PPC405GP_REVA
/* 设置中断向量表到0x0000 */
li p0, 0x2100/4
mtctr p0
lis p0, WALNUT_EVPR_VAL
li p1, 0x0000
zeroOut:
stw p1,0x0(p0)
addi p0, p0, 4
bdnz zeroOut
#endif


/* 初始化堆栈
/* Initialize the stack pointer (r1)  */

lis sp, HIADJ(STACK_ADRS)
addi sp, sp, LO(STACK_ADRS)

#if FALSE /* SDA not supported */
/* initialize r2 and r13 according to EABI standard */

lis r2, HIADJ(_SDA2_BASE_)
addi r2, r2, LO(_SDA2_BASE_)
lis r13, HIADJ(_SDA_BASE_)
addi r13, r13, LO(_SDA_BASE_)
#endif

/* 得到C程序romStart()在ROM中的地址,保证romInit执行结束后,系统跳转执行romStart()

/* calculate C entry point: routine - entry point + ROM base */

lis p1, HIADJ(romStart) /* p1 = romstart */
addi p1, p1, LO(romStart)

lis p2, HIADJ(romInit) /* p2 = romInit */
addi p2, p2, LO(romInit)

/* ROM_TEXT_ADRS为ROM的入口地址,在文件makefile定义,为0xfff80100
lis p3, HIADJ(ROM_TEXT_ADRS) /* p3 = ROM_TEXT_ADRS */   
addi p3, p3, LO(ROM_TEXT_ADRS)

subf p1, p2, p1 /* p1 = p1 - p2 */
add p1, p1, p3 /* p1 = p1 + p3 */
/* p1中是romStart()的地址,这里把这个地址放到连接寄存器LR中.         mtlr p1 /* link register = C entry point */    

or p0, p5, p5 /* p0 = startType */
addi sp, sp, -FRAMEBASESZ /* get frame stack */

/* 跳转到LR中romStart()的地址,执行romStart()
blr /* branch to link register */            

 

返回页首

 

硬件中断

中断的产生和VxWorks系统的中断操作:

一般中断的产生是由硬件定义的,如串口中断的定义:

1.接收中断:当接收中断使能,接收数据存储器 RxData 存在有效数据,则产生中断.
2.发送中断:当发送中断使能,发送数据存储器 TxData 为空,则产生中断.

硬件发送中断产生逻辑示意

硬件接收中断产生逻辑示意

 

所以要产生一个串口中断,主要有两步:

1.使能这两个串口中断,RX Enable,TX Enable,函数intEnable().
2.用intConnect()登记中断号,和相应的中断例程ISR.

    程序示例

在VxWorks系统上登记,使能串口中断

    intConnect((VOIDFUNCPTR *)5,ComISR,0);         //登记中断服务程序ComISR()到外部中断号5,

    intEnable((VOIDFUNCPTR *)5);                   //使能外部中断5

   
//enable 使能 UART1 这里直接用32位地址表示了
    *(unsigned long *)0x20000014 &= ~0x01000100;  //INT_FORCE     use pending
    *(unsigned long *)0x20000010 = 0x01000100;     //INT_PENDING    clear
    *(unsigned long *)0x20000018 = 0x01000100;    //INT_MASK only enable UART1 RX


//control register 使能 RX TX
    *(unsigned long *)0x20000068 = 0x00070007;    //RX and TX ENABLE

//divider register,baut rate 19200    设置波特率
    *(unsigned long *)0x2000006c = 59;   

//完成, 这样当串口有中断发生时,硬件系统会自动跳转到中断号5的地址0x18,调用程序ComISP().

注意:中断程序不能单步执行,或跟踪调试,中断服务程序中与函数库或系统有关的函数不可用如print()等,因为中断调用时,所有其它的任务都被挂起停止运行.

   

返回页首

 

VxWorks系统的网络驱动(END)

VxWorks网络配置参见VxWorks网络驱动配置及分析

    VxWorks系统网络驱动在BSP中完成,写驱动时应参考BSP develop kit,在VxWorks中叫做END( Enhanced Network Driver),编写程序使用由VxWorks定义的MUX接口

MUX是数据链路层和网络协议层之间的接口

主要调用过程和步骤如下:

VxWorks系统执行的第一个任务target\config\all\usrConfig.c文件中 usrRoot()=======>>

target\src\config\usrNetwork.c文件(该文件初始化TCP/IP)中 usrNetInit(BOOT_LINE_ADRS)(该函数作用是添加MUX END)========>>

pcooki = pCookie = muxDevLoad(pDevTbl->unit,.....)其中pDevTbl在BSP网络配置文件configNet.h中定义.END_TBL_ENTRY endDevTbl[]={...},该表定义了网络设备的具体参数,在这里调用了网络驱动

END_TBL_ENTRY endDevTbl [] =
{
{0, IBM_EMAC_LOAD_FUNC, IBM_EMAC_LOAD_STR_0, TRUE, NULL, FALSE},
{0, END_TBL_END, NULL, 0, NULL, FALSE},
};
其中IBM_EMAC_LOAD_FUNC就是 ibmEmacEndLoad()

========>>muxDevStart(pcooki)==========>>ibmEmacEndLoad()

 

ibmEmacEndLoad()初始化系统为网络驱动运行做准备

MUX调用ibmEmacStart()

ibmEmacStart() 登记中断服务程序ibmEmacInit(),启动设备运行在中断模式下.

LOCAL STATUS ibmEmacStart ( EMAC_DRV_CTRL * pDrvCtrl )
{
int rc;

SYS_INT_CONNECT (pDrvCtrl, ibmEmacInt, pDrvCtrl, &rc);
SYS_OUT_LONG(pDrvCtrl, EMAC_ISR, 0xFFFFFFFF);
SYS_INT_ENABLE ();

/* Allow MAL EOB and Descriptor error interrupts */

malChannelIntMaskSet(MAL_TX_TYPE, pDrvCtrl->txChn0MalChannel,
MAL_EOB_INT_EN | MAL_DE_INT_EN | MAL_SERR_INT_EN);

malChannelIntMaskSet(MAL_RX_TYPE, pDrvCtrl->rxChn0MalChannel,
MAL_EOB_INT_EN | MAL_DE_INT_EN | MAL_SERR_INT_EN);

return (OK);
}

中断服务程序ibmEmacInit() 处理EMAC控制器的中断,主要是 TX,RX状态错误

LOCAL void ibmEmacInt ( EMAC_DRV_CTRL * pDrvCtrl )
{
UINT isrReg;

/* Read the EMAC interrupt status register */

SYS_IN_LONG(pDrvCtrl, EMAC_ISR, isrReg);
pDrvCtrl->errorEmac = isrReg;

/*
* Check to see if there was a TX error. If there was, the Dead bit
* will be set. Clear the status bits for the TX error, and clear the dead
* bit. Keep count of these errors in the main device structure.
*/

if (isrReg & EMAC_ISR_TX_INTS)
{
pDrvCtrl->intErrorTX++;
SYS_OUT_LONG(pDrvCtrl, EMAC_ISR, EMAC_ISR_TX_INTS);
}

/*
* Check to see if there was a RX error. Clear the status bits for the RX
* error. Keep count of these errors in the main device structure.
*/

if (isrReg & EMAC_ISR_RX_INTS)
{
pDrvCtrl->intErrorRX++;
SYS_OUT_LONG(pDrvCtrl, EMAC_ISR, EMAC_ISR_RX_INTS);
}

return;
}

           (未完)     

 

Cillus网卡CS8900A Linux驱动

下面是我为一个网友解释的CS8900A网卡驱动文件中的部分函数,操作系统为ucLinux,CPU是国内常用的Motorola龙珠系列MC68EZ328(16M),相比PowerPC和ARM来说,它的结构简单,不带MMU,较易理解

CS8900A是一个16位网卡,支持ISA总线,10-BastT.

static inline void outw(unsigned short value,unsigned int addr)
{            
unsigned short newvalue;
unsigned char *_src=(unsigned char*)(&value),*_dest=(unsigned char*)(&newvalue);

//这里为什么要交换一下?

////++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//这里不是交换,而是赋值,将16位分成两个8位的数组,分别赋值,16位寄存器是只有后8位可读写,
//前8位是寄存器ID,所以这里吧后8位放在前面"_dest[0]=_src[1];"
//然后通过I/O口,赋给相应的存储器,即"*(volatile unsigned short*)addr=newvalue;"
///+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

_dest[0]=_src[1];   
_dest[1]=_src[0];   
*(volatile unsigned short*)addr=newvalue;
}

static int cs89x0_probe1(struct device *dev, int ioaddr)
{
    struct net_local *lp;
    static unsigned version_printed = 0;
    int i;
    unsigned rev_type = 0;

    irq2dev_map[0] = dev;

    /* set up the chip select */
   

    //下面这一段该怎么理解?看不懂阿
   
////++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//这里是对CPU进行操作,对cs89进行初始化,我没有见到你的硬件原理图,我只能按CPU
//的结构定义来解释一下,你要想搞清楚,只有看硬件原理图,还要参考CPU的硬件手册
////++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++    
   
   

//连通PORT F I/O功能管脚1,SEL1(-IRQ5/PF1),
    *(volatile unsigned char *)0xfffff42b |= 0x02; /* output /sleep */
//设置PORT F 管脚0为输出,输出高电平
    *(volatile unsigned short *)0xfffff428 |= 0x0101; /* not sleeping */

//连通PORT F 中断功能管脚1,SEL1(-IRQ5/PF1)
    *(volatile unsigned char *)0xfffff42b &= ~0x02; /* input irq5 */
//PORT F 管脚1,SEL1(-IRQ5/PF1) 为输入方式,置低电平有效   
    *(volatile unsigned short *)0xfffff428 &= ~0x0202; /* irq5 fcn on */
   
//在寄存器CSGBB中定义片选及cs89的基地址为:0x10000000.++++非常重要+++++++    
    *(volatile unsigned short *)0xfffff102 = 0x8000; /* 0x04000000 */
//在寄存器CSB中,片选使能,cs89地址空间大小(1M),数据线宽16位,6个等待周期,FLASH...,
//非保护存储空间(128K)......       
    *(volatile unsigned short *)0xfffff112 = 0x01e7; /* 128k, 2ws, FLASH, en */
   
//对PORT G操作........   
    *(volatile unsigned int *)0xfffff430 = 0x023c3d0a;
   
////++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//这是I/O模式的主要的两个PORT口,一个是地址口,一个是数据口,这里对0x22操作,
//中断IRQ0
///+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++    
    *(volatile unsigned short*)0x1000030a=0x2200;
    *(volatile unsigned short*)0x1000030c=0;
   

    //*(volatile unsigned short *)0xfffff302 |= 0x0080;
   
    ......................

    .....................

void
reset_chip(struct device *dev)
{
    int reset_start_time;
    writereg(dev, PP_SelfCTL, readreg(dev, PP_SelfCTL) | POWER_ON_RESET);

    /* wait 30 ms */
    current->state = TASK_INTERRUPTIBLE;
    current->timeout = jiffies + 3;
    schedule();

    /* Wait until the chip is reset */
   
    //这里jiffies说是timestamp,时间标记,这个变量和current
    //都在哪里定义?
    //jiffies主要起什么作用?
////+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//这里是个延时等待,reset以后必须有等待,以便reset彻底完成,在reset过程中所有寄存器是关闭的
//不但对cs89,对其他芯片也是一样的
///++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++  reset_start_time = jiffies;
    while( (readreg(dev, PP_SelfST) & INIT_DONE) == 0 && jiffies - reset_start_time< 2);
}

static int
net_open(struct device *dev)
{
    struct net_local *lp = (struct net_local *)dev->priv;
    int result = 0;
    int i;

    write_irq(dev, lp->chip_type, 0);

    irq2dev_map[/* FIXME */ 0] = dev;
    writereg(dev, PP_BusCTL, 0); /* ints off! */

//这里和上面那个一样,怎么看懂?
////++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//这里也是CPU对CS89操作,我也根据CPU硬件手册给你大概说一下
///+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

//中断信号,正电平触发(POL5)
    *(volatile unsigned short *)0xfffff302 |= 0x0080; /* +ve pol irq */

//调整PORT G的输入输出状态,及设置管脚相应的电平信号    
    *(volatile unsigned int *)0xfffff430 = 0x023c3d0a;    /* low -> high */
    *(volatile unsigned int *)0xfffff430 = 0x023e3d0a;
    *(volatile unsigned int *)0xfffff430 = 0x023c3d0a;    /* high -> low */

////++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//这是I/O模式的主要的两个PORT口,一个是地址口,一个是数据口,这里对0x22操作,
//中断IRQ0
///+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++    
    *(volatile unsigned short *)0x1000030a = 0x2200;    /* index window, REG index : 0022h */
    *(volatile unsigned short *)0x1000030c = 0x0000;    /* data window, REG data : 0000h,irq0 */
   
.........................

.........................

static void
net_rx(struct device *dev)
{
    struct net_local *lp = (struct net_local *)dev->priv;
    int ioaddr = dev->base_addr;
    struct sk_buff *skb;
    int status, length;

//下面两行怎么右边是一样的?
//是作者写错了吗?
////++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++|
//不是,这是一个包中前后不同的两个数据,前一个数据是该数据包的状态,后一个是数据包的长度
////++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    status = inw(ioaddr + RX_FRAME_PORT);
    length = inw(ioaddr + RX_FRAME_PORT);
    if ((status & RX_OK) == 0) {
        lp->stats.rx_errors++;
        if (status & RX_RUNT) lp->stats.rx_length_errors++;
        if (status & RX_EXTRA_DATA) lp->stats.rx_length_errors++;
        if (status & RX_CRC_ERROR) if (!(status & (RX_EXTRA_DATA|RX_RUNT)))
            /* per str 172 */
            lp->stats.rx_crc_errors++;
        if (status & RX_DRIBBLE) lp->stats.rx_frame_errors++;
        return;
    }

......................

static int
set_mac_address(struct device *dev, void *addr)
{
    int i;
    if (dev->start)
        return -EBUSY;
   
    if(get_arena_addr())
        {
            memcpy(dev->dev_addr,0x1100000,6);
        }
    else
    {
    //这里的00hhcnl和前面
//出现的00hhcn是什么意思?
////+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//注意:在这里他更改了网卡的MAC值,随便给了一个值"0x00,0x00,'h',h','c','n'",
//不过这个48位值必须是世界唯一的,或者用网卡自带的也可以.我不能确定是否eeprom中有信息,
//你可以用示波器量一下EEDI管脚,若为高电平,则EEPROM用到了,否则,他们根本没有用EEPROM.
///++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
        unsigned char mac[]={0,0,'h','h','c','n'};
        memcpy(dev->dev_addr,mac,6);
    }
    printk("%s: Setting MAC address to ", dev->name);
    for (i = 0; i < 6; i++)
        printk(" %2.2x", dev->dev_addr[i] = ((unsigned char *)addr)[i]);
    printk(".\n");
    /* set the Ethernet address */
    for (i=0; i < ETH_ALEN/2; i++)
        writereg(dev, PP_IA+i*2, dev->dev_addr[i*2] | (dev->dev_addr[i*2+1] << 8));

    return 0;
}

 

返回页首

 

LCD 与触摸屏

返回页首

 

Modem拨号

Modem拨号程序步骤如下


1. off hook,发出摘机命令(ATH1\r),
   等待modem返回状态,如果是"OK",则表示成功.进行第二步.
2. on hook 发出挂机命令(ATH\r),
   等待modem返回状态,如果是"OK",则表示成功.进行第三步.
3. off hook,dialing,摘机拨号(ATDT....\r),
   等待modem返回状态,如果是"CONNECT",则表示成功.进行第四步.
4. send data,发送数据.

最好按上述步骤一步一步调,并要等待正确的返回值后再进行下一步.

具体程序例子如下,拨号:

// 清空接收FIFO
    REG(FCR, nsChan) |=FCR_RXCLR;
// 等待,直到清空为止
    while((REG(FCR, nsChan) & FCR_RXCLR) != 0x00);
// 拨号    
    while(atdt1[i]!=0)
    {
// 检查发送FIFO是否准备好,为空
     while((REG(LSR, nsChan) & LSR_THRE) == 0x00);
// 在发送FIFO中放入数据
REG(THR, nsChan) = atdt1[i];
i++;
    }
    
// 等待是否有数据到达,即modem返回数据.
while((REG(LSR, nsChan) & LSR_DR) == 0x00);
// 接收数据并判断返回值
response[i-19]=REG(RBR,nsChan);
    if(response[0]!=0x43)
        ..................
    else
        ..................    
// 如果返回值是"connect",    
    while(*data!=0)
    {
     while((REG(LSR, nsChan) & LSR_THRE) == 0x00);
REG(THR, nsChan) = *data++;
    }
其他步骤基本一样.


 这是在ARM7上做的modem驱动,硬件设计方法是modem集成在主板上,没有用串口,modem是用片选地址直接对硬件操作的.

 

返回页首

 

RTC

返回页首

 

http://www.embhelp.com/drew/mypage/sourcecode.htm

你可能感兴趣的:(嵌入式程序示例)