概览
本次使用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
芯片手册,可以将发送一次指令和内容打包成Index
与cmd
两个函数。这两个函数内容如下:
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_addr
和send_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_addr
与send_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);
}