1. 程式人生 > >UCOSii專案在NIOSii上的移植

UCOSii專案在NIOSii上的移植

概覽

本次使用Altera公司的NIOS II軟核。

使用Quatus工具生成BSP並利用BSP打包工具生成UCOSII嵌入環境。

手動書寫LCD驅動與顯示函式,對UCOS II加入簡單影象顯示介面。

./
├── create-this-app
├── driver        #板子的具體驅動(非作業系統)
│   ├── init.h    #初始化
│   ├── irs.h     #中斷處理
│   ├── lcd.h     #LCD驅動
│   ├── sys.h     #系統驅動
│   └── tools.h   #工具
├── lib          #顯示庫
│ ├── ansii_lib.h │ ├── cn_lib.h │ ├── color.h │ └── values.h ├── Makefile ├── obj │ └── default │ ├── sys_kernel.d │ └── sys_kernel.o ├── readme.txt ├── sys_kernel.c #系統的主函式 ├── sys_user_interface.elf ├── sys_user_interface.map ├── sys_user_interface.objdump └── tasks #任務資料夾
├── task1.h └── task2.h

NIOS II軟核生成

由於這次沒有載入其餘IP核,這次的軟核非常簡單,沒有預留過多IP核PIO介面:

軟核工程中包含以下內容:

  • NIOS II processer
  • 軟核時鐘訊號
  • 外部儲存器介面
  • SPI匯流排介面
  • 一個控制LCD螢幕亮度的介面
  • 一個軟核版本控制器

若是有其餘IP核需要加入,則需要單獨的PIO進行互動,或者選擇其餘匯流排協議與IP核互動。

匯流排的速度非常慢,在軟核中推薦使用可程式設計佈線來進行互動。

具體軟核IP核互動可以參考我的部落格中的DES核與卷積核,這裡不做過多描述。

FPGA工程概覽

其中由於板載頻率問題,加入了PLL

然後右邊接了一個處理LCD的亮度的PWM模組也非常簡單,不做過多描述。

UCOS II環境配置

由於這裡使用的是Altera公司提供的NIOS II軟核,其有完整的BSP與系統移植。

這裡只需要根據選擇軟核生成對於此板子的BSP。

選擇一個UCOS的系統工程即可。(還提供了其他的RTOS)

LCD 驅動書寫

顯示器驅動

  • LCD顯示屏初始化

    void LCD_init() {
        //************* Reset LCD Driver ****************//
        IOWR_ALTERA_AVALON_PIO_DATA(LCD_RESET_BASE, 1);
        delay_ms(200);
        IOWR_ALTERA_AVALON_PIO_DATA(LCD_RESET_BASE, 0);
        delay_ms(200);
        IOWR_ALTERA_AVALON_PIO_DATA(LCD_RESET_BASE, 1);
        delay_ms(10);
        //************* Start Initial Sequence **********//
        //剩餘引數配置見原始碼,這裡不予展示
    }

    LCD初始化可以直接利用晶片廠商提供的程式碼,或者參考晶片資料中的引數配置自行完成。

  • LCD顯示驅動

    查閱ILI9481晶片手冊,可以將傳送一次指令和內容打包成Indexcmd兩個函式。

    這兩個函式內容如下:

    void LCD_ILI9481_INDEX(unsigned int data) {
        IOWR_ALTERA_AVALON_PIO_DATA(LCD_RS_BASE, 1);
        IOWR_ALTERA_AVALON_PIO_DATA(LCD_WR_BASE, 0);
        IOWR_ALTERA_AVALON_PIO_DATA(LCD_DATA_BASE, data);
        IOWR_ALTERA_AVALON_PIO_DATA(LCD_WR_BASE, 1);
    }
    void LCD_ILI9481_CMD(unsigned int data) {
        IOWR_ALTERA_AVALON_PIO_DATA(LCD_RS_BASE, 0);
        IOWR_ALTERA_AVALON_PIO_DATA(LCD_WR_BASE, 0);
        IOWR_ALTERA_AVALON_PIO_DATA(LCD_DATA_BASE, data);
        IOWR_ALTERA_AVALON_PIO_DATA(LCD_WR_BASE, 1);
    }

    傳送一個指令的流程為先利用cmd函式傳送指令,再用INDEX函式傳送指令內容。

    再將LCD顯示一個畫素的影象打包為set_addrsend_data兩個步驟,先通過set_addr傳送畫素地址,再用send_data傳送畫素顏色資訊。

    send_addr的時序可以通過查閱晶片手冊來得知,其程式碼如下:

    void set_addr(unsigned int x, unsigned int y){
        LCD_ILI9481_CMD(0x002b);
        LCD_ILI9481_INDEX(x >> 8);
        LCD_ILI9481_INDEX(x & 0x00ff);
        LCD_ILI9481_INDEX(0x0001);
        LCD_ILI9481_INDEX(0x00df);
    
        LCD_ILI9481_CMD(0x002a);
        LCD_ILI9481_INDEX(y >> 8);
        LCD_ILI9481_INDEX(y & 0x00ff);
        LCD_ILI9481_INDEX(0x0001);
        LCD_ILI9481_INDEX(0x003f);
    
        LCD_ILI9481_CMD(0x002c);
    }

    同樣,可以寫出send_data如下:

    void send_data(unsigned int data) {
        IOWR_ALTERA_AVALON_PIO_DATA(LCD_RS_BASE, 1);
        IOWR_ALTERA_AVALON_PIO_DATA(LCD_WR_BASE, 0);
        IOWR_ALTERA_AVALON_PIO_DATA(LCD_DATA_BASE, data);
        IOWR_ALTERA_AVALON_PIO_DATA(LCD_WR_BASE, 1);
    }
  • LCD顯示工具

    ascii碼顯示工具(中文顯示工具原理相同),利用lcd_buffer傳引數,字母資料儲存在word_libc

      void display_ascii(unsigned int x, unsigned int y, unsigned int w_color,
            unsigned int b_color) {
        unsigned int i, j, k = 0;
        unsigned char str;
        unsigned int OffSet, z;
    
        while (1) {
            if (lcd_buffer[k] == 0) {
                set_addr(0, 0);
                return;
            }
            z = lcd_buffer[k];
            //每個字元在wordlib中用11
            OffSet = z * 11;
            //顯示一個字元 該字元的畫素大小為
            for (i = 0; i < 11; i++) {
                //讀取字元表示中的一個字元的一行
                str = word_lib[OffSet + i];
                for (j = 0; j < 8; j++) {
                    //設定顯示畫素點的相對左邊
                    set_addr(x + j, y - i);
                    //如果是要顯示的話,就顯示前景顏色
                    if (str & 0x80) {
                        send_data(w_color);
                    } else {
                        //如果是不顯示的話,顯示背景顏色
                        send_data(b_color);
                    }
                    str <<= 1;
                }
            }
            x += 8;
            k++;
        }
      }

    通過如下方式利用該工具

      sprintf((char * )lcd_buffer, " Test ");
      display_ascii(12, 16, 0x0000, MENU_FULL_COLOR);

    圖片顯示則利用如上所述的set_addrsend_data完成,歡迎介面顯示函式如下

    (影象利用工具轉換後放在welcome陣列之中)

中斷處理

  • 時鐘中斷在此版本中暫時用於處理背光,中斷的申請與定義可見各NIOS教程,處理函式如下

    void timer(void* context) {
    IOWR_ALTERA_AVALON_TIMER_STATUS(TIMER_BASE, 0);
    IOWR_ALTERA_AVALON_TIMER_CONTROL(TIMER_BASE, 0x0b);
    
    //控制LCD的背光燈
    if (LED_PWM_DATA <= 130) {
        LED_PWM_DATA += 1;
        IOWR_ALTERA_AVALON_PIO_DATA(PWM_LED_BASE, LED_PWM_DATA);
    }
    IOWR_ALTERA_AVALON_TIMER_CONTROL(TIMER_BASE, 0x07);
    }
  • 按鍵中斷則是SPI匯流排來讀取按鍵值

    void KeyListener(void* context) {
    KEY_IS_DOWN = !KEY_IS_DOWN;
    unsigned char i = 0;
    for (i = 0; i < 8; i++) {
        send_KEY(((0xfeff << i) >> 8) & 0xff);
        if (IORD_ALTERA_AVALON_PIO_DATA(KEY_PORT_BASE) != 0x03) {
            if (IORD_ALTERA_AVALON_PIO_DATA(KEY_PORT_BASE) == 1) {
                switch (i) {
                case 0:
                    KEY_DATA = 8;
                    break;
                //...
                  //見原始碼 省去
                }
            } else if (IORD_ALTERA_AVALON_PIO_DATA(KEY_PORT_BASE) == 2) {
                switch (i) {
                case 0:
                    KEY_DATA = 0;
                    break;
                //...
                  //見原始碼 省去
                }
            }
            break;
        }
    }
    send_KEY(0x00);
    if(KEY_IS_DOWN == 0){
        sprintf(lcd_buffer,"keytest");
        display_ascii(417,95,0x0000,0xEF78);
        sprintf(lcd_buffer,"%2d",KEY_DATA);
        display_ascii(417,80,0x0000,0xEF78);
    }
    IOWR_ALTERA_AVALON_PIO_EDGE_CAP(KEY_PORT_BASE, 0x0000);
    }

    SPI匯流排的驅動send_key見下

    void send_KEY(unsigned char data) {
    unsigned char u;
    IOWR_ALTERA_AVALON_PIO_DATA(SPI_LE_K_BASE, 0);
    //序列傳送j的資料,通過移位傳送j的資料
    for (u = 0; u < 8; u++) {
        if (data & 0x80) {
            IOWR_ALTERA_AVALON_PIO_DATA(SPI_DATA_BASE, 1);
        } else {
            IOWR_ALTERA_AVALON_PIO_DATA(SPI_DATA_BASE, 0);
        }
    
        IOWR_ALTERA_AVALON_PIO_DATA(SPI_CLK_BASE, 1);
    
        IOWR_ALTERA_AVALON_PIO_DATA(SPI_CLK_BASE, 0);
    
        data <<= 1;
        //左移傳送訊號
    }
    IOWR_ALTERA_AVALON_PIO_DATA(SPI_LE_K_BASE, 1);
    }

工具類

工具類主要提供了delay如下

void delay_ms(unsigned int i) {
    unsigned int j, k;
    for (j = 0; j < i; j++)
        for (k = 0; k < 1000; k++);
}
void delay_us(unsigned int i) {
    unsigned int j;
    for (j = 0; j < i; j++);
}

系統初始化

cvoid SYS_init() {
    LCD_init();
    Init_background();
    print_screen("============================================");
    print_screen("=                                          =");
    print_screen("=   Welcome to UCOS II based on NIOS II    =");
    print_screen("=                                          =");
    print_screen("=       NIOS II Version : Liu Nian         =");
    print_screen("=                                          =");
    print_screen("============================================");
    print_screen("Initial UCOS II");
    sprintf((char *)lcd_buffer,"Initial interrupt ...");
    display_ascii(1, LINE_Y, 0xffff, 0x0000);
    //初始化中斷服務
    alt_irq_init (ALT_IRQ_BASE);
    sprintf((char *)lcd_buffer,"DONE");
    display_ascii(DONE_X, LINE_Y, DONE_COLOR, 0x0000);
    LINE_Y = LINE_Y - 10;

    sprintf((char *)lcd_buffer,"Initial Key Listener ...");
    display_ascii(1, LINE_Y, 0xffff, 0x0000);
    KEY_init(); //初始化按鍵中斷
    sprintf((char *)lcd_buffer,"DONE");
    display_ascii(DONE_X, LINE_Y, DONE_COLOR, 0x0000);
    LINE_Y = LINE_Y - 10;
}

初始化用於測試顯示驅動,與初始化中斷處理。

int main (void){
    SYS_init();
    OSInit();                                              /* Initialize uC/OS-II                      */
    OSTaskCreate(TaskStart, (void *)0, &TaskStartStk[TASK_STK_SIZE - 1], 0);
    /*Create semaphore*/
    mutex = OSSemCreate(1);
    full = OSSemCreate(0);
    empty = OSSemCreate(10);
    OSStart();                                             /* Start multitasking                       */
    return 0;
}

初始化系統也是直接呼叫sysinit(),然後交給Taskstart進行處理。

貪吃蛇移植

有了顯示驅動,有了系統框架,移植貪吃蛇也就很快了。

#include <stdio.h>
#include "includes.h"
#include "driver/lcd.h"
#include "driver/init.h"
#include "driver/sys.h"
#include "tasks/task1.h"
#include "tasks/task2.h"

/*
 *********************************************************************************************************
 *                                               CONSTANTS
 *********************************************************************************************************
 */

#define  TASK_STK_SIZE                 512       /* Size of each task's stacks (# of WORDs)            */
#define          TASK_0_ID           0
#define          TASK_1_ID           1
#define          TASK_PRIO           1
#define          TASK_0_PRIO         3
#define          TASK_1_PRIO         2


#define         LEFT                0
#define         FRONT               1
#define         BACK                2
#define         RIGHT               3

#define snake_maxlen 20
#define true 1
#define false 0

/*
 *********************************************************************************************************
 *                                               VARIABLES
 *********************************************************************************************************
 */

OS_STK        TaskStartStk[TASK_STK_SIZE];            /* Task Start task stack                         */
OS_STK        Task0Stk[TASK_STK_SIZE];                /* Task #0    task stack                         */
OS_STK        Task1Stk[TASK_STK_SIZE];                /* Task #1    task stack                         */

/*
 *********************************************************************************************************
 *                                           FUNCTION PROTOTYPES
 *********************************************************************************************************
 */

void  Task0(void *data);                       /* Function prototypes of tasks                  */
void  Task1 (void *pdata);
void  TaskStart(void *data);                  /* Function prototypes of Startup task           */
static  void  TaskStartCreateTasks(void);
static  void  TaskStartDispInit(void);
static  void  TaskStartDisp(void);


struct snake_node{
    int node_x;
    int node_y;
} snake[snake_maxlen];


/*$PAGE*/
/*
 *********************************************************************************************************
 *                                                MAIN
 *********************************************************************************************************
 */

/* Semaphores */
OS_EVENT *mutex;
OS_EVENT *full;
OS_EVENT *empty;

int main (void){
    SYS_init();
    OSInit();                                              /* Initialize uC/OS-II                      */
    OSTaskCreate(TaskStart, (void *)0, &TaskStartStk[TASK_STK_SIZE - 1], 0);
    /*Create semaphore*/
    mutex = OSSemCreate(1);
    full = OSSemCreate(0);
    empty = OSSemCreate(10);
    OSStart();                                             /* Start multitasking                       */
    return 0;
}


/*
 *********************************************************************************************************
 *                                              STARTUP TASK
 *********************************************************************************************************
 */
OS_EVENT *mail1;
OS_EVENT *mail2;

void  TaskStart (void *pdata){
    int        need_new = 1;
    TaskStartDispInit();
    OSStatInit();                                          /* Initialize uC/OS-II's statistics         */
    TaskStartCreateTasks();                                /* Create all the application tasks         */
    mail1 = OSMboxCreate(0);
    mail2 = OSMboxCreate(0);
    OSMboxPost(mail2,&need_new);
    TaskStartDisp();
    while(1) {
        OSCtxSwCtr = 0;                                    /* Clear context switch counter             */
        OSTimeDlyHMSM(0, 0, 1, 0);                         /* Wait one second                          */
    }
}


/*$PAGE*/
/*
 *********************************************************************************************************
 *                                        INITIALIZE THE DISPLAY
 *********************************************************************************************************
 */

static  void  TaskStartDispInit (void){
    Init_background();
}

static  void  TaskStartDisp (void){
}


static  void  TaskStartCreateTasks (void){
    OSTaskCreateExt(Task0,   /*蛇*/
                    (void *)0,
                    &Task0Stk[TASK_STK_SIZE - 1],
                    TASK_0_PRIO,
                    TASK_0_ID,
                    &Task0Stk[0],
                    TASK_STK_SIZE,
                    (void *)0, /*無擴充套件*/
                    OS_TASK_OPT_STK_CHK | OS_TASK_OPT_STK_CLR);//堆疊檢查,堆疊清空
        OSTaskCreateExt(Task1,   /*豆子*/
                        (void *)0,
                        &Task1Stk[TASK_STK_SIZE - 1],
                        TASK_1_PRIO,
                        TASK_1_ID,
                        &Task1Stk[0],
                        TASK_STK_SIZE,
                        (void *)0, /*無擴充套件*/
                        OS_TASK_OPT_STK_CHK | OS_TASK_OPT_STK_CLR);//堆疊檢查,堆疊清空
}




/*
 *********************************************************************************************************
 *                                             My  TASKS
 *********************************************************************************************************
 */

int direction;
int count = 0;



int find_way(int target_x,int target_y,int cur_x,int cur_y){
    printf("FIND %D %D\n\n",target_x,target_y);
    if(cur_x < target_x) return RIGHT;
    else if(cur_x > target_x) return LEFT;
    else if(cur_y < target_y) return FRONT;
    else if(cur_y > target_y) return BACK;
    return BACK;

}


int need_new = true;

int target_x = 0;
int target_y = 0;

void  Task0 (void *pdata){
    INT8U *err;
    char s[40];
    int length = 4;
    int i = 0;
    int need_grow = false;

    snake[0].node_x = 10;
    snake[0].node_y = 10;
    snake[1].node_x = 9;
    snake[1].node_y = 10;
    snake[2].node_x = 9;
    snake[2].node_y = 9;
    snake[3].node_x = 9;
    snake[3].node_y = 8;

    length = 4;
    for(i = 0; i< length ; i++){
        print_xy(snake[i].node_x, snake[i].node_y);
    }
    while(1){
        if(need_grow){
            length ++;
            need_grow = false;
            for(i = length - 1; i > 0;i--){
                snake[i].node_x = snake[i-1].node_x;
                snake[i].node_y = snake[i-1].node_y;
            }
            switch (direction) {
                case LEFT:
                    snake[0].node_x = snake[0].node_x - 1;
                    break;
                case RIGHT:
                    snake[0].node_x = snake[0].node_x + 1;
                    break;
                case FRONT:
                    snake[0].node_y = snake[0].node_y + 1;
                    break;
                case BACK:
                    snake[0].node_y = snake[0].node_y - 1;
                    break;
                default:
                    snake[0].node_x = snake[0].node_x - 1;
                    break;
            }


        }
        else{
            if(target_x == snake[0].node_x && target_y == snake[0].node_y){
                need_grow = true;
                need_new = true;
            }
            direction = find_way(target_x,target_y,snake[0].node_x,snake[1].node_y);

            print_xy_t(snake[length - 1].node_x, snake[length - 1].node_y);
            for(i = length - 1; i > 0;i--){
                snake[i].node_x = snake[i-1].node_x;
                snake[i].node_y = snake[i-1].node_y;
            }



            switch (direction) {
                case LEFT:
                    snake[0].node_x = snake[0].node_x - 1;
                    break;
                case RIGHT:
                    snake[0].node_x = snake[0].node_x + 1;
                    break;
                case FRONT:
                    snake[0].node_y = snake[0].node_y + 1;
                    break;
                case BACK:
                    snake[0].node_y = snake[0].node_y - 1;
                    break;
                default:
                    snake[0].node_x = snake[0].node_x - 1;
                    break;
            }
        }

        //顯示
        sprintf((char*)lcd_buffer,"*");
        print_xy(snake[0].node_x, snake[0].node_y);
        sprintf(s,"tail.x = %d tail.y = %d direction:%d",snake[length - 1].node_x, snake[length - 1].node_y,direction);
        printf("%s",s);
        sprintf(s,"head.x = %d head.y = %d",snake[0].node_x, snake[0].node_y);
        printf("%s\n",s);
        OSTimeDly(10);
    }
}

void  Task1 (void *pdata){
    INT8U *err;
    char s[40];

    while(1){
        if(need_new==true){
            target_x = rand()% 20 + 3;
            target_y = rand()% 15 + 2;
            printf("target_x = %d target_y = %d",target_x, target_y);
            print_xy(target_x, target_y);
            need_new = false;
        }
        OSTimeDly(10);
    }
}

擴充套件:VGA模組的加入

VGA視訊記憶體的設計可以看我的另一篇部落格

同樣是移植貪吃蛇,只需要更改printxy即可

void print_xy(int x, int y) {
    int addr;
    IOWR_ALTERA_AVALON_PIO_DATA(WREN_BASE, 0);
    addr = y * 64 + x;
    IOWR_ALTERA_AVALON_PIO_DATA(WRITEADDR_BASE, addr);
    IOWR_ALTERA_AVALON_PIO_DATA(WRITEDATA_BASE, 0x7491);
    IOWR_ALTERA_AVALON_PIO_DATA(WREN_BASE, 1);
}

這裡寫圖片描述