1 В избранное 0 Ответвления 0

OSCHINA-MIRROR/lanzhoo-tang-primer20k_-tutorial

Клонировать/Скачать
C01_software_design.md 12 КБ
Копировать Редактировать Web IDE Исходные данные Просмотреть построчно История
lanzhoo Отправлено 07.06.2024 16:44 6b518c4

[ 返回 ]

软件设计说明

外设

GPIO: LED

在硬件设计中,GPIO输出仅配置了6个LED。通过寄存器GPIO_ODR(地址为0x10000000)的低6比特控制。当对应比特值为1时,LED亮。

因此,直接访问寄存器的应用代码为:(例程sparrowrv_gpio01、sparrowrv_gpio02)

#define GPIO_ODR      *((volatile unsigned int *)0x10000000)

GPIO_ODR = 0x1;

GPIO_ODR = GPIO_ODR + 1;

执行应用逻辑代码和硬件相关代码分离的原则后,将硬件相关代码封装为HAL层函数:(例程sparrowrv_timer02、sparrowrv_uart02)

void setLedValue(unsigned int vValue) {
  GPIO_ODR = vValue;
}

unsigned int getLedValue() {
  return GPIO_ODR;
}

GPIO: Button

TangPrimer20kDock开发板上有5个按钮,S4用作Core Reset,S3用作JTAG Reset。留下3个按钮S0-S2用作GPIO输入。

通过寄存器GPIO_IDR(地址为0x10000004)的低3比特读入当前按键状态,按下时为0,松开时为1。

判断哪个按键按下松开的一个实现代码为:(例程sparrowrv_gpio02)

#define GPIO_IDR      *((volatile unsigned int *)0x10000004)

int buttonPushed(){
    unsigned int i, button;
    i = GPIO_IDR&0x07;
    if (i== 0x07)
        return 0;
    else if ((i&0x01)== 0)
        button=1;
    else if ((i&0x02)== 0)
        button= 2;
    else if ((i&0x04)== 0)
        button= 3;
    else
        button = 0;
    //wait for button released
    while ((GPIO_IDR&0x07)!= 0x07) {
    }
    //
    return button;
}

定时器

在硬件设计中,定时器只设计了一种循环自增模式:

  • 当启动后,计数器值在core_clk的触发下加1;
  • 启动状态下,在core_clk的触发时判断(加1前),若计数器值大于等于最大值时,计数器值归零(然后继续增加);
  • 当停止时,计数器值停下跳动,但不会归零;启动时,也不会归零从零开始;
  • 计数器值归零时,产生overflow标志,该标志在读overflow寄存器时清零;
  • overflow标志送intctrl模块,中断允许时可以触发中断;

定时器设计了如下几个I/O寄存器:

  • TIMER_COUNTER (地址为0x10000084):计数器的当前值,可以读写;
  • TIMER_TOP(地址为0x10000088):计数器的最大值;
  • TIMER_CTRL(地址为0x10000080):bit0=1时,启动定时器;
  • TIMER_OVERFLOW_FLAG(地址为0x1000008c):bit0=1表示overflow;

因此,直接访问寄存器、轮询方式的应用代码为:(例程sparrowrv_timer01)

#define TIMER_CTRL    *((volatile unsigned int *)0x10000080)
#define TIMER_COUNTER *((volatile unsigned int *)0x10000084)
#define TIMER_TOP     *((volatile unsigned int *)0x10000088)
#define TIMER_OVERFLOW_FLAG     *((volatile unsigned int *)0x1000008c)

以上声明寄存器。

    TIMER_TOP = 13500000;
    TIMER_CTRL = 0x01;

Core的工作时钟是13.5MHz,因此设置TIMER_TOP为13500000是延时1秒。第二句是启动定时器。

   while (1){
        GPIO_ODR = GPIO_ODR + 1;
        while (!TIMER_OVERFLOW_FLAG) {
            //wait for timeout
        }
   }

其中的“while (!TIMER_OVERFLOW_FLAG) ”就是等待定时器溢出。

执行应用逻辑代码和硬件相关代码分离的原则后,将硬件相关代码封装为HAL层函数:(例程sparrowrv_timer02)

void setTimerTopValue(unsigned int vValue) {
  TIMER_TOP = vValue;
}

void startTimer() {
  TIMER_CTRL = 0x01;
}

注:定时器中断应用在后面的小节介绍。

UART

在硬件设计中,UART的配置只固定为一种模式:

  • 波特率为115200;
  • 8位数据;
  • 无校验位;
  • 1位停止位;

UART设计了如下几个I/O寄存器:

  • UART_TXD (地址为0x10000040):在发送空闲时,写该寄存器则发送写入值的bit7-0;
  • UART_TX_BUSY(地址为0x10000048):bit0=1时,表示发送进行中;
  • UART_RXD(地址为0x10000040):UART_RX_VALID时,读该寄存器返回收到的字节;
  • UART_RX_VALID(地址为0x10000044):bit0=1表示收到数据,读UART_RXD时清零;

因此,直接访问寄存器、轮询方式的应用代码为:(例程sparrowrv_uart01)

#define UART_TXD      *((volatile unsigned int *)0x10000040)
#define UART_RXD      *((volatile unsigned int *)0x10000040)
#define UART_RX_VALID   *((volatile unsigned int *)0x10000044)
#define UART_TX_BUSY   *((volatile unsigned int *)0x10000048)

以上声明寄存器。

UART_TXD = gValue;

上句在不判断发送状态下发送数据。

if (UART_RX_VALID){
  UART_TXD = UART_RXD;
}

上句在不判断发送状态下转发收到的数据。

执行应用逻辑代码和硬件相关代码分离的原则后,将硬件相关代码封装为HAL层函数:(例程sparrowrv_uart02)

void _putchar(char character){
    while (UART_TX_BUSY){
    }
    UART_TXD = character;

}

void uartSend(unsigned int vData) {
  _putchar(vData);
}

//blocking function: return until receive byte
unsigned int uartReceive() {
    while (!UART_RX_VALID){
    }
    return UART_RXD;
}

中断

硬件设计中,在peripheral模块中包含了一个intctrl模块,负责外部中断屏蔽管理。其中定义了如下I/O寄存器:

  • PERI_EFFECT_INT_REQ(地址为0x10003fc0):通过了中断屏蔽的中断请求信号;
  • PERI_INT_MASK(地址为0x10003fc4):某位值为1,表示允许中断;
  • PERI_PENDING_INT_REQ(地址为0x10003fc8):原始中断请求信号;

定时器overflow中断请求在bit0;

UART收到数据中断请求在bit1。

在RISCV的规范中,定义了一系列的CSR寄存器用于中断管理。其中包括:

  • mie:内部定时中断、软中断、外部中断允许;
  • mstatus:bit3为全局中断允许;
  • mtvec:中断矢量表入口地址;
  • mcause:中断原因;
  • mepc:断点位置;

startup.S和trap_entry.S中的相关设置

## set interrupt vector
    la t0, trap_entry
    csrw	mtvec,t0

以上设置中断矢量表入口为trap_entry,且所有中断类型均指向同一个入口地址。

trap_entry在trap_entry.S中定义,先保护现场,然后调用trap_handler(传入mcause和mepc)执行实际的中断服务程序,然后恢复现场,执行mret中断返回。

    .section      .text.entry
    .align 2
    .global trap_entry

trap_entry:
## enable interrupt
    li t0, 0x888
    csrw mie,t0

以上允许外部中断。

hal_basic.c中的相关设置

开全局中断和开定时器中断、UART接收中断的封装函数代码:

#define PERI_EFFECT_INT_REQ     *((volatile unsigned int *)0x10003fc0)
#define PERI_INT_MASK     *((volatile unsigned int *)0x10003fc4)
#define PERI_PENDING_INT_REQ     *((volatile unsigned int *)0x10003fc8)

#define PERI_INT_MASK_VALUE_TIMER 0x01
#define PERI_INT_MASK_VALUE_UART  0x02
void enableGlobalInt() {
  __asm__ __volatile__("csrsi  mstatus,0x08");
}

void enableTimerInt() {
  PERI_INT_MASK |= PERI_INT_MASK_VALUE_TIMER;
}

void enableUartRxvalidInt() {
  PERI_INT_MASK |= PERI_INT_MASK_VALUE_UART;
}

分发中断事件处理的代码:

//external interrupt handler
void handler_interrupt_ex() {
  unsigned int request;
  request = PERI_EFFECT_INT_REQ;
  if (request & PERI_INT_MASK_VALUE_TIMER) {
    handler_timer_overflow();
  }
  if (request & PERI_INT_MASK_VALUE_UART) {
    handler_uart_rxvalid();
  }

}

基于中断方式的定时器例程(sparrowrv_timer02)

hal_basic.c中,定时器中断的处理函数,设置gTimeoutFlag标志:

unsigned int gTimeoutFlag;

__attribute__((weak)) void handler_timer_overflow() {
    gTimeoutFlag = TIMER_OVERFLOW_FLAG;
}

main函数中,检查gTimeoutFlag标志触发动作:

    gTimeoutFlag = 0;
    setTimerTopValue(13500000);
    enableTimerInt();
    enableGlobalInt();
    startTimer();

    while(1){
        if (gTimeoutFlag) {
            setLedValue(getLedValue()+1);
            printf("timeout...\n");
            gTimeoutFlag = 0;
        }
    }

基于中断方式的UART接收例程(sparrowrv_uart02)

hal_basic.c中,UART接收中断的处理函数,读入数据,设置gUartRxDataFlag标志:

unsigned int gUartRxData;
unsigned int gUartRxDataFlag;

__attribute__((weak)) void handler_uart_rxvalid() {
  gUartRxData = UART_RXD;
  gUartRxDataFlag = 1;
}

main函数中,检查gUartRxDataFlag标志触发动作:

    enableUartRxvalidInt();
    enableGlobalInt();

    while(1){

        if (gUartRxDataFlag) {
            setLedValue(gUartRxData);
            printf("rx: %x\n", gUartRxData);
            gUartRxDataFlag = 0;
        }

    }

启动

RESET入口

I_ROM分配的地址是0x0000xxxx,RESET后执行的第一条指令从地址0x0000_0000获取。

在应用软件中对应的定义在startup.S和link.lds。

startup.S中的相关代码:

	.section .init; /*声明此处段名为.init*/
	.globl _start; /*声明_start是全局的*/
	.type _start,@function  /*声明_start是函数*/

_start:

_start在.init段。

link.lds中的相关代码:

ENTRY(_start) 

声明_start为程序入口。

_iram_base_addr = 0x00000000;
_sram_base_addr = 0x20000000;
MEMORY
{
  iram (rx) : ORIGIN = _iram_base_addr, LENGTH = _iram_app_size 
  sram (rw) : ORIGIN = _sram_base_addr, LENGTH = _cpu_sram_size 
}

声明iram输出段的位置在0x00000000。

SECTIONS
{
   __stack_size = DEFINED(__stack_size) ? __stack_size : _cpu_stack_size; /*堆栈区大小*/

  .init           :
  {
    KEEP (*(SORT_NONE(.init)))
  } >iram AT>iram 

  .text           :
  {
    *(.text.unlikely .text.unlikely.*)
    *(.text.startup .text.startup.*)
    *(.text .text.*)
    *(.gnu.linkonce.t.*)
  } >iram AT>iram 

最先把.init段的内容放在iram输出段中,然后才放.text段的内容(一般代码放.text段)。

以上保证了_start函数出现在0x0000_0000的的位置。

复制静态数据

将静态数据从I_ROM区域复制到D_RAM区。(代码来自“小麻雀处理器”仓例程)

		/*加载data段,存储需要初始化的全局变量和静态变量*/
	la a0, _data_lma /*程序存储器的data段起始地址 加载至 a0*/
	la a1, _data     /*数据存储器的data段起始地址 加载至 a1*/
	la a2, _edata    /*数据存储器的data段结束地址 加载至 a2*/
	bgeu a1, a2, 2f  /*a1大于等于a2,跳转至往下第一个2标签;否则向下执行*/
1:
	lw t0, (a0)      /*a0指向的地址 写入 t0*/
	sw t0, (a1)      /*t0的数据 写入 a1指向的地址 */
	addi a0, a0, 4   /*a0+4*/
	addi a1, a1, 4   /*a1+4*/
	bltu a1, a2, 1b  /*a1小于a2,跳转至往上第一个1标签;否则向下执行*/
	/*加载data段*/
2:

	/*清空bss段,存储不用初始化的全局变量和静态变量*/
	la a0, __bss_start /*bss段起始地址 加载至 a0*/
	la a1, _end        /*bss段结束地址 加载至 a1*/
	bgeu a0, a1, 2f    /*a0大于等于a1,跳转至往下第一个2标签;否则向下执行*/
1:
	sw zero, (a0)      /*a0指向的地址 写入 0*/
	addi a0, a0, 4     /*a0+4*/
	bltu a0, a1, 1b    /*a0小于a1,跳转至往上第一个1标签;否则向下执行*/
	/*清空bss段*/
2:

其中相关参数的位置在link.lds中定义。

[ 返回 ]

Опубликовать ( 0 )

Вы можете оставить комментарий после Вход в систему

1
https://api.gitlife.ru/oschina-mirror/lanzhoo-tang-primer20k_-tutorial.git
git@api.gitlife.ru:oschina-mirror/lanzhoo-tang-primer20k_-tutorial.git
oschina-mirror
lanzhoo-tang-primer20k_-tutorial
lanzhoo-tang-primer20k_-tutorial
master