UCOSii项目在NIOSii上的移植

时间:2021-09-01 05:00:13

概览

本次使用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接口:

UCOSii项目在NIOSii上的移植

软核工程中包含以下内容:

  • NIOS II processer
  • 软核时钟信号
  • 外部存储器接口
  • SPI总线接口
  • 一个控制LCD屏幕亮度的接口
  • 一个软核版本控制器

若是有其余IP核需要加入,则需要单独的PIO进行交互,或者选择其余总线协议与IP核交互。

总线的速度非常慢,在软核中推荐使用可编程布线来进行交互。

具体软核IP核交互可以参考我的博客中的DES核与卷积核,这里不做过多描述。

FPGA工程概览

UCOSii项目在NIOSii上的移植

其中由于板载频率问题,加入了PLL

UCOSii项目在NIOSii上的移植

然后右边接了一个处理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初始化可以直接利用芯片厂商提供的代码,或者参考芯片资料中的参数配置自行完成。

UCOSii项目在NIOSii上的移植

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

UCOSii项目在NIOSii上的移植