FPGA学习[2]——致敬Mbed!使用C++开发NIOS II



前言

在上一篇文章中博主介绍了如何让nios ii跑在板子自带的SDRAM里,这样大家伙就不用担心小的可怜的FPGA内部的memory(当然用着几万块开发板的土豪除外)。对于要用FPGA做开发的盆友来说,花费少量的资源跑一个简单的软核还是非常方便的,特别是对于一些速度要求不是很高的处理,像是跑个小型的GUI之类的,nios ii无疑是个很棒的选择。

在很多的教程中,开发NIOS II总是给人一个非常不好的印象:晦涩难懂的函数,复杂的调用,还有一些莫名其妙的问题,这无疑给开发带来了很大的难度。

这十年来,开源硬件越来越流行,从最初的Arduino到后来的树莓派,都是以一种相对平易近人的姿态面向电子爱好者和广大学生或是从业者们,其简易的开发方式大大缩短了编程难度,让我们真正的拜托硬件平台的限制,把目光从如何使用一款硬件转移到如何开发一套优良的算法。这几年ARM公司的Mbed给了Cortex-M系列处理器前所未有的支持,从STM32到LPC一系列微控制器都允许使用Mbed开发,进一步降低了设计难度。由于Mbed良好的移植特性,我们可以轻易地将官方未宣布支持的Cortex-M控制器一直上Mbed,经过简易的测试就可以顺利跑起来。

Mbed是基于C++语言开发的平台,其优雅的代码形式还是很受博主欣赏的呢!

#include "mbed.h"
DigitalOut myled(LED1);
int main()
{
    // check that myled object is initialized and connected to a pin
    if(myled.is_connected()) {
        printf("myled is initialized and connected!\n\r");
    }
    // Blink LED
    while(1) {
        myled = 1;          // set LED1 pin to high
        printf("myled = %d \n\r", (uint8_t)myled );
        wait(0.5);
        myled.write(0);     // set LED1 pin to low
        printf("myled = %d \n\r",myled.read() );
        wait(0.5);
    }
}

这是官网给出来的一段blinking led的代码样例,使用DigitalOut,在这篇博客中,博主希望能够以一种这样的方式开发NIOS II。

SunZhenyu,

于 台州学院 电力电子实验室


NIOS II工程搭建

接着上一次的教程,创建完成一个nios ii软核与pll核,正确分配引脚并编译成功以后,下载到目标板上后,FPGA内部就已经有一个可用的nios处理器了。

注意:如果生成的sof文件后缀为time limited,应该考虑一下是否购买了正版的Quartus^_ ^或者正确地 破解 您的软件??

博主使用Quartus Prime 18.0,这款软件有一个lite版本可以尝尝鲜,是不需要license的,但是使用nios ii会有时间限制(限制为无限时间),也就是time limited…因此不可以转化为jic文件烧录到配置芯片中,不过学学nios应该也够用了,学会了记得忽悠导师买正版啊!

在这里插入图片描述
选择Nios ii Software Build Tool for Eclipse
注意NIOS ii的开发平台是基于大名鼎鼎的Eclipse的,博主希望充分发挥Eclipse的优势。
在这里插入图片描述
选择软件工程存放位置
在这里插入图片描述
载入SOPC文件,注意SOPC文件的路径
工程模板我们就选择一个空模板就好了。
在这里插入图片描述
点击Finish以后就可以载入一个工程。
在这里插入图片描述
注意nios_on_sdram这个是我们的工程,而nios_on_sdram_bsp则是系统自动建立的bsp模板。

新建一个源代码文件
在这里插入图片描述
在这里插入图片描述
注意文件的扩展名!
因为博主希望使用C++开发,因此后缀选择了CPP
至此项目框架搭建完毕!


Hello World on Nios ii

使用C++语言的优势就在于可以方便而灵活的使用面向对象的特点,利用类简化一些操作。 为了测试SOPC是否顺利跑起来,我们还是写一个Hello World试一下。
/*
 * main.cpp
 *
 *  Created on: Sep 23, 2019
 *      Author: Sunzh
 */

#include <iostream>

int main()
{
	std::cout<<"Hello World on Nios ii"<<std::endl;
	return 0;
}


在这里插入图片描述
如果没毛病的话,经过短暂的构建编译以后就可以看到控制台输出了一段

Hello World on Nios ii

这证明我们的程序跑起来了!
那么这个小小的Hello World有多大呢?我们看一下控制台的下载过程

Using cable "USB-Blaster [USB-0]", device 1, instance 0x00
Pausing target processor: OK
Initializing CPU cache (if present)
OK

Downloading 02000000 ( 0%)
Downloading 02010000 ( 8%)
Downloading 02020000 (16%)
Downloading 02030000 (24%)
Downloading 02040000 (32%)
Downloading 02050000 (40%)
Downloading 02060000 (48%)
Downloading 02070000 (56%)
Downloading 02080000 (65%)
Downloading 02090000 (73%)
Downloading 020A0000 (81%)
Downloading 020B0000 (89%)
Downloading 020C0000 (97%)
Downloading 020C4AE4 (99%)
Downloaded 787KB in 7.8s (100.8KB/s)

Verifying 02000000 ( 0%)
Verifying 02010000 ( 8%)
Verifying 02020000 (16%)
Verifying 02030000 (24%)
Verifying 02040000 (32%)
Verifying 02050000 (40%)
Verifying 02060000 (48%)
Verifying 02070000 (56%)
Verifying 02080000 (65%)
Verifying 02090000 (73%)
Verifying 020A0000 (81%)
Verifying 020B0000 (89%)
Verifying 020C0000 (97%)
Verifying 020C4AE4 (99%)
Verified OK                         
Starting processor at address 0x02000244

神特么的一个小小的Hello World有787KB?!
其实不用这么紧张,如果我们不用C++的输入输出流,这个代码可能很短。

点亮LED

在设计SOPC的时候,博主加上一个PIO的模块,这个模块被映射为一个真实的IO口,与开发板上的一个LED相连。

在这里插入图片描述

在system.h中我们可以看到IO口的基地址。在BSP中Altera为我们编写好了一系列的HAL库供我们调用,就像STM32开发一样。当然这些库有的晦涩难懂,需要翻阅手册才能使用。这里博主简单介绍一个函数。
直接HAL库操作IO的方法是IOWR(),声明在io.h中

#define IOWR(BASE, REGNUM, DATA) \
  __builtin_stwio (__IO_CALC_ADDRESS_NATIVE ((BASE), (REGNUM)), (DATA))

这是一个带参数的宏定义,其包含三个参数BASE,REGNUM,DATA
BASE参数就是我们PIO模块的基地址,在博主搭建的SOPC上是0x4001010,第二个参数是寄存器号
通常我们使用如下这两个
寄存器0:数据寄存器,为PIO写入或读取的数据
寄存器1:为方向寄存器,定义0为输入1为输出
DATA是要写入的内容
例如我们要使一个16位的PIO中第5管脚设置为输出,其余管脚设置为输入,只需要对1寄存器写入0x0010

IOWR(PIO_BASE, 1, 0x0010);
//0x00=(0000 0000 0001 0000)b

要是第五管脚输出一个高电平,只需要对0寄存器写入0x0010

IOWR(PIO_BASE, 0, 0x0010);
//0x00=(0000 0000 0001 0000)b

注意对方向寄存器设置为输入的管脚写入数据是无效的。
利用这个函数就足够让小灯闪烁了。
博主的PIO为16位,其中小灯接在第2个IO。

/*
 * main.cpp
 *
 *  Created on: Sep 23, 2019
 *      Author: Sunzh
 */

#include <io.h>
#include <unistd.h>
#include <system.h>
int main()
{
	IOWR(PIO_BASE,1,0x0002);
	while(true)
	{
		IOWR(PIO_BASE,0,0x0002);  
		usleep(500000);  //延时函数,包含在unistd.h中
		IOWR(PIO_BASE,0,0x0000);
		usleep(500000);
	}
	return 0;
}


当控制台闪过这样一段文字的时候

Using cable "USB-Blaster [USB-0]", device 1, instance 0x00
Pausing target processor: OK
Initializing CPU cache (if present)
OK

Downloading 02000000 ( 0%)
Downloading 02003EB8 (64%)
Downloaded 16KB in 0.1s        

Verifying 02000000 ( 0%)
Verifying 02003EB8 (64%)
Verified OK                         
Starting processor at address 0x02000244

值得注意的是这段代码仅有16KB大小。
是的,现在小灯已经在以1秒钟1次的速度闪烁了。
但是现在有一个不小的问题,每一次这么操作实在是太麻烦了,点亮一个LED尚可,若点亮一块SPI接口的OLED屏幕呢?
算了算了还是静静吧。


快速开发:使用C++的类

如果我们将每一个IO封装为一个类,这个类通过IOWR的方式操作PIO,这样估计就能一劳永逸了。 Mbed为我们提供了一个绝佳的典范!

博主定义了一个DigitalOut类

#ifndef DRIVERS_DIGITALOUT_H_
#define DRIVERS_DIGITALOUT_H_
#include "inc.h"
class DigitalOut
{
private:
    PinName pin;
    bool data;

public:
    DigitalOut(PinName);
    void operator=(const bool);
    operator int();
};
#endif

实现如下

#include "inc.h"

extern uint16_t PIO_DATA;
extern uint16_t PIO_DIR;

void DigitalOut::operator=(bool data)
{
    this->data = data;
    if(((PIO_DIR&(1<<pin))!=0)!=_DigitalOutDir)
    {
    	PIO_DIR |= (1 << pin);
    	IOWR(_DigitalOut_IO_BASE, DIR_REG, PIO_DIR);
    }
    PIO_DATA &= (~(1 << pin));
    PIO_DATA |= (data << pin);
    IOWR(_DigitalOut_IO_BASE, DATA_REG, PIO_DATA);
}
DigitalOut::DigitalOut(PinName Name)
{
    pin = Name;
    operator=(0);
}
DigitalOut::operator int()
{
	return data;
}

博主这里使用了几个重载,尤其注意转换函数int()重载用于实现类似a=!a的操作
其中PinName是一个枚举类型,定义如下

#ifndef DRIVERS_PINNAME_H_
#define DRIVERS_PINNAME_H_
enum PinName
{
    D0 = 0,
    D1,
    D2,
    D3,
    D4,
    D5,
    D6,
    D7,
    D8,
    D9,
    D10,
    D11,
    D12,
    D13,
    D14,
    D15
};
#endif

同时博主对usleep也做了一点小小的改动,以求更加方便

#ifndef _WAIT_H_
#define _WAIT_H_
#define wait_ms(x) usleep((x)*1000)
#define wait(x) usleep((x)*1000000)
#define wait_us usleep((x))

#endif

现在是否能更加优雅的点灯了呢?

/*
 * main.cpp
 *
 *  Created on: Sep 23, 2019
 *      Author: Sunzh
 */

#include "Drivers/inc.h"
int main()
{
	DigitalOut led(D1);
	while(true)
	{
		led=!led;
		wait_ms(500);
	}
	return 0;
}


我们还可以定义一个DigitalIn类型,完成按键的输入,就像这个样子

/*
 * main.cpp
 *
 *  Created on: Sep 23, 2019
 *      Author: Sunzh
 */

#include "Drivers/inc.h"
int main()
{
	DigitalOut led(D1);
	DigitalIn key(D0);
	while(true)
	{
		if(key==0)
		{
			wait_ms(25);
			while(key==0);
			led=!led;
		}
	}
	return 0;
}

至此,所有关于IO的基本操作我们都实现了,利用DigitalIn,DigitalOut还有wait,我们能完成几乎所有的I2C SPI等的访问,驱动各种传感器,代码也会简洁地令人耳目一新。

稍后博主会放上一些代码和模板工程。
博主会利用学业的空闲时间一点一点地将Mbed-NIOS完善起来,加油!
在这里插入图片描述

发布了12 篇原创文章 · 获赞 27 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/little_cats/article/details/101224078
ii