在很多的教程中,开发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,
于 台州学院 电力电子实验室
注意:如果生成的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
至此项目框架搭建完毕!
/*
* main.cpp
*
* Created on: Sep 23, 2019
* Author: Sunzh
*/
#include
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++的输入输出流,这个代码可能很短。
在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
#include
#include
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屏幕呢?
算了算了还是静静吧。
博主定义了一个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等的访问,驱动各种传感器,代码也会简洁地令人耳目一新。