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);
}

這裏寫圖片描述

發佈了74 篇原創文章 · 獲贊 366 · 訪問量 18萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章