Cubieboard上使用U-boot通过定时器中断控制LED


继LED之后,学习使用A10中断,目的是学习U-boot中如何使用中断。


软件环境: 笔记本一台,安装WindowsXP sp3

                     XP下软件:Source Insight 3.5;SecureCRT;VMware Workstation7.0

                    虚拟机中安装ubuntu10.04

                    ubuntu中软件:Vim;编译工具链 arm-none-eabi- 版本4.7.2

硬件环境:cubieboard,淘宝自带的串口线和电源线

                    microSD卡一只,读卡器一个

准备工作:U-boot实现SD卡启动,请参考另一篇文章 http://blog.csdn.net/andy_wsj/article/details/8515197

                   CB的原理图

                   A10用户手册



一、代码分析进入点,厘清脉络
   从上次打开LED控制就发现了 http://blog.csdn.net/andy_wsj/article/details/8973818,文件status_led.c文件内的函数status_led_tick将会在定时器中断中被
调用,在工程内搜索status_led_tick,获得4个结果,两个在interrupts.c下。进入查看,阅读上下文,可以看出,这是在定时器中断中调用,实现定时LED闪烁控制。
    查看文件,希望获得中断处理部分代码。在任何平台下,使用中断之前都要初始化,因此在工程搜索这个文件里面的函数interrupt_init,获得很多结果。调用的地方暂时不理会,只看定义。A10是ARM平台,理所当然的查看ARM架构lib里面的定义:
Interrupts.c (d:\share\u-boot-sunxi-sunxi\arch\arm\lib):int interrupt_init (void)
可以看到,若要使用中断,需要定义预编译宏:
CONFIG_USE_IRQ


在include/configs/sunxi-common.h中定义之,然后编译u-boot,提示两个错误:
CONFIG_STACKSIZE_IRQ 未定义
CONFIG_STACKSIZE_FIQ 未定义
就是中断的栈大小没定义,先在include/configs/sunxi-common.h定义两个,不知到多大合适,暂时定义10K
#define CONFIG_STACKSIZE_IRQ (10 * 1024)
#define CONFIG_STACKSIZE_FIQ (10 * 1024)


再次编译u-boot,再次提示错误,start.S里面的do_irq没定义,打开
\u-boot-sunxi-sunxi\arch\arm\cpu\armv7\start.S,搜索do_irq,可以看到:


#ifdef CONFIG_USE_IRQ


.align 5
irq:
get_irq_stack
irq_save_user_regs
bl do_irq
irq_restore_user_regs


.align 5
fiq:
get_fiq_stack
/* someone ought to write a more effective fiq_save_user_regs */
irq_save_user_regs
bl do_fiq
irq_restore_user_regs


#else


.align 5
irq:
get_bad_stack
bad_save_user_regs
bl do_irq


.align 5
fiq:
get_bad_stack
bad_save_user_regs
bl do_fiq


#endif /* CONFIG_USE_IRQ */


很明显,这就是中断处理了。
继续看看标号irq,fiq,在文件start.S开头的地方:
.globl _start
_start: b reset
ldr pc, _undefined_instruction
ldr pc, _software_interrupt
ldr pc, _prefetch_abort
ldr pc, _data_abort
ldr pc, _not_used
ldr pc, _irq
ldr pc, _fiq
#ifdef CONFIG_SPL_BUILD
_undefined_instruction: .word _undefined_instruction
_software_interrupt: .word _software_interrupt
_prefetch_abort: .word _prefetch_abort
_data_abort: .word _data_abort
_not_used: .word _not_used
_irq: .word _irq
_fiq: .word _fiq
_pad: .word 0x12345678 /* now 16*4=64 */
#else
_undefined_instruction: .word undefined_instruction
_software_interrupt: .word software_interrupt
_prefetch_abort: .word prefetch_abort
_data_abort: .word data_abort
_not_used: .word not_used
_irq: .word irq
_fiq: .word fiq
_pad: .word 0x12345678 /* now 16*4=64 */
#endif /* CONFIG_SPL_BUILD */
我们已经来到整个u-boot开始的地方,即上电之后一条被执行的指令就是这里
_start: b reset


接下来几句就是异常处理
ldr pc, _undefined_instruction
ldr pc, _software_interrupt
ldr pc, _prefetch_abort
ldr pc, _data_abort
ldr pc, _not_used
ldr pc, _irq
ldr pc, _fiq
发生什么类型的异常就是跳到对应的位置进行处理,再看看
_irq: .word irq
_fiq: .word fiq


由此可见,假设发生一个irq,那么首先执行
ldr pc, _irq
_irq就是irq
(_irq==irq)->do_irq,就跳到中断处理函数去了,如何实现do_irq呢?在工程内搜索之。结果不多,逐个查看,发现就是各种实现方式,对于A10,我觉得自己修改一种方式有利于学习,
最后挑了Interrupts.c (d:\share\u-boot-sunxi-sunxi\arch\arm\cpu\arm720t):void do_irq (struct pt_regs *pt_regs)
我决定把这一个文件修改成能在当前平台下使用,并将这个文件放在\u-boot-sunxi-sunxi\arch\arm\cpu\armv7\sunxi\下,
修改该目录Makefile文件,增加如下几句:
ifdef CONFIG_USE_IRQ
COBJS   += interrupts.o
endif


再增加一个文件Interrupts.h,用来定义A10的中断寄存器地址和中断源,放在\u-boot-sunxi-sunxi\arch\arm\include\asm\arch-sunxi目录下。
目的是同过timer0中断控制LED闪烁,其他中断暂时都不会打开,但是会定义,为进一步学习其他模块做准备。


二、了解中断机制,修改代码
准备写代码了,应该看一看A10用户手册,仔细阅读中断和定时器相关的章节
在“intterrupt controller”这一章,所有中断都在这里描述
在“timer controller”这一章,也描述了与定时器相关的寄存器


1、代码编写和修改。
修改\u-boot-sunxi-sunxi\arch\arm\cpu\armv7\sunxi\timmer.c里面的timmer_init函数,使之符合中断要求。
/*
 * (C) Copyright 2007-2011
 * Allwinner Technology Co., Ltd. <www.allwinnertech.com>
 * Tom Cubie <[email protected]>
 *
 * See file CREDITS for list of people who contributed to this
 * project.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of
 * the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
 * MA 02111-1307 USA
 */


#include <common.h>
#include <asm/io.h>
#include <asm/arch/timer.h>
#include <asm/arch/interrupts.h>


DECLARE_GLOBAL_DATA_PTR;


#define TIMER_MODE   (0 << 7)   /* continuous mode */
#define TIMER_DIV    (0 << 4)   /* pre scale 1 */
#define TIMER_SRC    (1 << 2)   /* osc24m */
#define TIMER_RELOAD (1 << 1)   /* reload internal value */
#define TIMER_EN     (1 << 0)   /* enable timer */


#define TIMER_CLOCK       (24 * 1000 * 1000)
#define COUNT_TO_USEC(x) ((x) / 24)
#define USEC_TO_COUNT(x) ((x) * 24)
#define TICKS_PER_HZ (TIMER_CLOCK / CONFIG_SYS_HZ)
#define TICKS_TO_HZ(x) ((x) / TICKS_PER_HZ)


#define TIMER_LOAD_VAL     TICKS_PER_HZ


#define TIMER_NUM    (0)        /* we use timer 0 */


static struct sunxi_timer *timer_base =
&((struct sunxi_timer_reg *)SUNXI_TIMER_BASE)->timer[TIMER_NUM];


/* macro to read the 32 bit timer: since it decrements, we invert read value */
#define READ_TIMER() (~readl(&timer_base->val))


/* init timer register */
int timer_init(void)
{
unsigned int val;


writel(TIMER_LOAD_VAL, &timer_base->inter);
writel(TIMER_MODE | TIMER_DIV | TIMER_SRC | TIMER_RELOAD | TIMER_EN, &timer_base->ctl);


#if defined(CONFIG_USE_IRQ)
        val = readl(SUNXI_TIMER_BASE + 0x04);
        val |= (0x1 << 0);
writel( val, SUNXI_TIMER_BASE + 0x04); /* clear timer0 irq pending  */


/* enable timer0 interrupt*/
        val = readl(SUNXI_TIMER_BASE);
        val |= (0x1 << 0);
writel( val, SUNXI_TIMER_BASE); /* Enable timer0 irq */


        writel( (1 << (INT_TIM0 % 32)), INTC_EN_REG0 + (INT_TIM0 >> 5));

#if defined(TIMER_DEBUG)
printf("Timer init done.\n");
#endif
#endif
return 0;
}


/* timer without interrupts */
ulong get_timer(ulong base)
{
return get_timer_masked() - base;
}


ulong get_timer_masked(void)
{
/* current tick value */
ulong now = TICKS_TO_HZ(READ_TIMER());


if (now >= gd->lastinc) /* normal (non rollover) */
gd->tbl += (now - gd->lastinc);
else /* rollover */
gd->tbl += (TICKS_TO_HZ(TIMER_LOAD_VAL) - gd->lastinc) + now;
gd->lastinc = now;
return gd->tbl;
}


/* delay x useconds */
void __udelay(unsigned long usec)
{
long tmo = usec * (TIMER_CLOCK / 1000) / 1000;
ulong now, last = READ_TIMER();


while (tmo > 0) {
now = READ_TIMER();
if (now > last) /* normal (non rollover) */
tmo -= now - last;
else /* rollover */
tmo -= TIMER_LOAD_VAL - last + now;
last = now;
}
}


/*
 * This function is derived from PowerPC code (read timebase as long long).
 * On ARM it just returns the timer value.
 */
unsigned long long get_ticks(void)
{
return get_timer(0);
}


/*
 * This function is derived from PowerPC code (timebase clock frequency).
 * On ARM it returns the number of timer ticks per second.
 */
ulong get_tbclk(void)
{
ulong tbclk;
tbclk = CONFIG_SYS_HZ;
return tbclk;
}


增加\u-boot-sunxi-sunxi\arch\arm\cpu\armv7\sunxi\interrupts.c,这个是修改过来的。修改Makefile,使之可编译到u-boot。
将status_led_tick增加到timer_isr调用中,若正常,板上的led将闪烁。
/*
* for sun4i A10 
* by Andy 2013.5
*/


#include <common.h>
#include <asm/io.h>
#include <asm/arch/interrupts.h>
#include <asm/arch/timer.h>




#if defined(CONFIG_USE_IRQ) && defined(CONFIG_SUN4I)
static struct _irq_handler IRQ_HANDLER[N_IRQS];


static void default_isr( void *data) {
printf ("default_isr():  called for IRQ %d\n", (int)data);
}


static void timer_isr( void *data) {
unsigned int *pTime = (unsigned int *)data;
unsigned int val;


(*pTime)++;
status_led_tick ((unsigned long)(*pTime));


#if defined(TIMER_DEBUG)
printf("Timer_isr running.\n");
#endif


        val = readl(SUNXI_TIMER_BASE + 0x04);
        val |= (0x1 << 0);
writel( val, SUNXI_TIMER_BASE + 0x04); /* clear timer0 irq pending  */
}


void do_irq (struct pt_regs *pt_regs)
{
unsigned int pending;
unsigned int vector;
unsigned int int_source;

vector  = readl( INTC_VECTOR_REG);
if( (int_source = (vector >> 2)) < N_IRQS )  
{
  switch(int_source >> 5 ){
     case  0:
                            pending = INTC_IRQ_PEND_REG0;
         break;
     case  1:
                            pending = INTC_IRQ_PEND_REG1;
         break;
     case  2:
         break;
                            pending = INTC_IRQ_PEND_REG2;
     default:
                            pending = INTC_IRQ_PEND_REG0;
         break;
  }
       if( (readl(pending) >> ( int_source % 32 )) & 0x01 ){


IRQ_HANDLER[int_source].m_func( IRQ_HANDLER[int_source].m_data);

/* clear pending interrupt */
writel( ~(1 << (int_source % 32)), pending);
}
    
}else{

default_isr(&vector);
}
}




static ulong timestamp;
static ulong lastdec;
int arch_interrupt_init (void)
{
int i;


/* install default interrupt handlers */
for ( i = 0; i < N_IRQS; i++) {
IRQ_HANDLER[i].m_data = (void *)i;
IRQ_HANDLER[i].m_func = default_isr;
}


/* configure interrupts for IRQ mode */
writel( 0x0, INTC_IRQ_TYPE_SEL0);
writel( 0x0, INTC_IRQ_TYPE_SEL1);
writel( 0x0, INTC_IRQ_TYPE_SEL2);

/* clear any pending interrupts */
writel( 0x0, INTC_IRQ_PEND_REG0);
writel( 0x0, INTC_IRQ_PEND_REG1);
writel( 0x0, INTC_IRQ_PEND_REG2);

writel( 0x0, INTC_FIQ_PEND_REG0);
writel( 0x0, INTC_FIQ_PEND_REG1);
writel( 0x0, INTC_FIQ_PEND_REG2);

lastdec   = 0;
timestamp = 0;
/* install interrupt handler for timer */
IRQ_HANDLER[INT_TIM0].m_data = (void *)&timestamp;
IRQ_HANDLER[INT_TIM0].m_func = timer_isr;

#if defined(TIMER_DEBUG)
printf("Interrupts initialize done.\n");
#endif


return 0;
}


#else
#error do_irq no define for this CPU type
#endif


增加\u-boot-sunxi-sunxi\arch\arm\include\asm\arch-sunxi\interrupts.h,用来定义A10的中断寄存器地址,中断源和中断数据结构。
/************************************/
/*     A10 interrupt register list  */
/************************************/
#define INTC_REG_BASE         0x01C20400
#define INTC_VECTOR_REG      (INTC_REG_BASE + 0x0000)
#define INTC_BASE_ADDR_REG   (INTC_REG_BASE + 0x0004)
#define INTC_INTCTRL_REG     (INTC_REG_BASE + 0x000C)
#define INTC_IRQ_PEND_REG0   (INTC_REG_BASE + 0x0010)
#define INTC_IRQ_PEND_REG1   (INTC_REG_BASE + 0x0014)
#define INTC_IRQ_PEND_REG2   (INTC_REG_BASE + 0x0018)
#define INTC_FIQ_PEND_REG0   (INTC_REG_BASE + 0x0020)
#define INTC_FIQ_PEND_REG1   (INTC_REG_BASE + 0x0024)
#define INTC_FIQ_PEND_REG2   (INTC_REG_BASE + 0x0028)
#define INTC_IRQ_TYPE_SEL0   (INTC_REG_BASE + 0x0030)
#define INTC_IRQ_TYPE_SEL1   (INTC_REG_BASE + 0x0034)
#define INTC_IRQ_TYPE_SEL2   (INTC_REG_BASE + 0x0038)
#define INTC_EN_REG0         (INTC_REG_BASE + 0x0040)
#define INTC_EN_REG1         (INTC_REG_BASE + 0x0044)
#define INTC_EN_REG2         (INTC_REG_BASE + 0x0048)
#define INTC_MASK_REG0       (INTC_REG_BASE + 0x0050)
#define INTC_MASK_REG1       (INTC_REG_BASE + 0x0054)
#define INTC_MASK_REG2       (INTC_REG_BASE + 0x0058)
#define INTC_RESP_REG0       (INTC_REG_BASE + 0x0060)
#define INTC_RESP_REG1       (INTC_REG_BASE + 0x0064)
#define INTC_RESP_REG2       (INTC_REG_BASE + 0x0068)
#define INTC_FF_REG0         (INTC_REG_BASE + 0x0070)
#define INTC_FF_REG1         (INTC_REG_BASE + 0x0074)
#define INTC_FF_REG2         (INTC_REG_BASE + 0x0078)


#define INTC_PRIO_REG0       (INTC_REG_BASE + 0x0080)
#define INTC_PRIO_REG1       (INTC_REG_BASE + 0x0084)
#define INTC_PRIO_REG2       (INTC_REG_BASE + 0x0088)
#define INTC_PRIO_REG3       (INTC_REG_BASE + 0x008C)
#define INTC_PRIO_REG4       (INTC_REG_BASE + 0x0090)




/************************************/
/*     A10 interrupt source         */
/************************************/
#define INT_EXTNMI    0
#define INT_UART0     1
#define INT_UART1     2
#define INT_UART2     3
#define INT_UART3     4
#define INT_IR0       5
#define INT_IR1       6
#define INT_TWI0      7
#define INT_TWI1      8
#define INT_TWI2      9
#define INT_SPI0      10
#define INT_SPI1      11
#define INT_SPI2      12
#define INT_NC        13
#define INT_AC97      14
#define INT_TS        15
#define INT_IIS       16
#define INT_UART4     17
#define INT_UART5     18
#define INT_UART6     19
#define INT_UART7     20
#define INT_KEYPAD    21
#define INT_TIM0      22
#define INT_TIM1      23
#define INT_TIM2_ALARM_WD    24
#define INT_TIM3      25
#define INT_CAN       26
#define INT_DMA       27
#define INT_PIO       28
#define INT_TOUCHPANEL       29
#define INT_AUDIOCODEC       30
#define INT_LRADC     31
#define INT_SD_MMC0   32
#define INT_SD_MMC1   33
#define INT_SD_MMC2   34
#define INT_SD_MMC3   35
#define INT_RSVD1     36
#define INT_NAND      37
#define INT_USB0      38
#define INT_USB1      39
#define INT_USB2      40
#define INT_SCR       41
#define INT_CSI0      42
#define INT_CSI1      43
#define INT_LCD_CON0  44
#define INT_LCD_CON1  45
#define INT_MP        46
#define INT_DE_FE0_BE0       47
#define INT_DE_FE1_BE1       48
#define INT_PMU       49
#define INT_SPI3      50
#define INT_TZASC     51
#define INT_PATA      52
#define INT_VE        53
#define INT_SS        54
#define INT_EMAC      55
#define INT_RSVD2     56
#define INT_RSVD3     57
#define INT_HDMI      58
#define INT_TVE0_1    59
#define INT_ACE       60
#define INT_TVD       61
#define INT_PS2_0     62
#define INT_PS2_1     63
#define INT_USB3      64
#define INT_USB4      65
#define INT_PLE_PREFMU      66
#define INT_TIM4      67
#define INT_TIM5      68
#define INT_GPU_GP    69
#define INT_GPU_GPMMU       70
#define INT_GPU_PP0   71
#define INT_GPU_PPMMU0      72
#define INT_GPU_PMU   73
#define INT_GPU_RSV0  74
#define INT_GPU_RSV1  75
#define INT_GPU_RSV2  76
#define INT_GPU_RSV3  77
#define INT_GPU_RSV4  78
#define INT_GPU_RSV5  79
#define INT_GPU_RSV6  80


#define N_IRQS        (80)


#ifndef __ASSEMBLER__
struct _irq_handler {
void                *m_data;
void (*m_func)( void *data);
};
#endif 




2、了解调用关系,使编译之后的代码能正常运行
只要timmer0初始化和中断初始化正常,那么就可以产生timer0中断。
那么timer_init被谁调用了呢?工程中搜索timer_init,结果很多,逐个观察,可以肯定初始化在board.c里面,A10为ARM,所以一定在ARM平台的代码内:
Board.c (d:\share\u-boot-sunxi-sunxi\arch\arm\lib): timer_init, /* initialize timer */
阅读上下文,可以看到,在函数board_init_f内通过一个转移表init_sequence调用了timer_init。


再来看看,中断初始化在哪调用的,搜索interrupt_init,跟timer_init在同一个文件里
Board.c (d:\share\u-boot-sunxi-sunxi\arch\arm\lib): interrupt_init();
这个interrupt_init就是这篇文章开始收搜索的函数,在文件:Interrupts.c (d:\share\u-boot-sunxi-sunxi\arch\arm\lib):int interrupt_init (void)
它调用了arch_interrupt_init函数,不用看,这就在我们自己写的文件\u-boot-sunxi-sunxi\arch\arm\cpu\armv7\sunxi\interrupts.c里面
确定调用完整。


3、写到SD卡内,测试之。
中断周期太大,采用24M的外部时钟,而CONFIG_SYS_HZ的值为1000,即中断1000次为1秒钟,每次中断为1ms,因此timer0的重载值为  24M/1000 
修改:
#define TIMER_LOAD_VAL     TICKS_PER_HZ

这个宏之后,可以获得正确的CONFIG_SYS_HZ时间值

设置CONFIG_SYS_HZ时,LED的闪烁可以做到1秒一次

你可能感兴趣的:(定时器,中断,cubieboard,led,u-boot)