/ Cyclone V

FPGA/Linux co-design with Cyclone V - Hardware run [3/3]

This is the third and last part of a series of posts, so before start reading, check the second part here.

As the final objective of this guide, we'll build now an application that'll run in Linux user-space, reading an RTC that communicates through the I2C protocol. It'll be a simple demonstration of how we can develop a co-design with hardware and software in this amazing target.

Now it's time to generate the header file that contains the memory-mapped devices that we've been exporting in Qsys. This header contains the exported addresses that you set such as 4x DIP Switches, 7x LEDs and 2x Push buttons, so to generate the file enter the following commands:

cd ~/ex_codesign/sw/application
sopc-create-header-files ../../hw/ghrd_project/DE10_NANO_SoC_GHRD/soc_system.sopcinfo --single hps_0.h --module hps_0

You must have this addresses:

#define LED_PIO_COMPONENT_NAME led_pio
#define LED_PIO_BASE 0x3000
#define LED_PIO_SPAN 16
#define LED_PIO_END 0x300f
#define LED_PIO_BIT_CLEARING_EDGE_REGISTER 0
...
#define DIPSW_PIO_BASE 0x4000
#define DIPSW_PIO_SPAN 16
#define DIPSW_PIO_END 0x400f
#define DIPSW_PIO_IRQ 0
#define DIPSW_PIO_BIT_CLEARING_EDGE_REGISTER 1
...
#define BUTTON_PIO_BASE 0x5000
#define BUTTON_PIO_SPAN 16
#define BUTTON_PIO_END 0x500f
#define BUTTON_PIO_IRQ 1
#define BUTTON_PIO_BIT_CLEARING_EDGE_REGISTER 1
#define BUTTON_PIO_BIT_MODIFYING_OUTPUT_REGISTER 0
#define BUTTON_PIO_CAPTURE 1
#define BUTTON_PIO_DATA_WIDTH 2
...

Preparing the hardware

In my approach I'll use a RTC DS3231 (ZS-042 module) but you can use any I2C sensor that you have available. The slave address of this device is 0x68h and the address map of registers to read/write can be verified through this table:
Screenshot-from-2018-08-07-10-04-27
So, before your start working with your sensor, try to use the i2c-tools, that's a set of tools installed along with BuildRoot/BusyBox, that help us to get and set address in i2c devices through Linux shell.

Now, if you're using the DE10-Nano, check the schematics to confirm the pins of I2C1 SCL/SDA, according to the manual of rev. C of this board, the I2C1 pins are the 9/11 of the LTC connector.
Screenshot-from-2018-08-07-10-18-30
Then connect the correspondent wires into your sensor to start coding a program for it. I've also connected a logic analyzer to check if the protocol that I was sending out to the sensor was right.
IMG_20180807_102220390
IMG_20180807_102239161
IMG_20180807_102247665
The i2c pull-up resistors are inserted by the RTC that has both soldered in the sensor SCL/SDA lines. Check this code that makes usage of i2c-tools to write a program to read i2c addresses in the Linux and compile it in your host machine with make. You can transfer the compiled version by putting into any folder of the /root dir of the SD card in the Cyclone V or through SSH as in the next topic. Compiling it and running into the target, you should see something similar:
tty-1
As the callback you should see the last byte changing because it represents the seconds from 1.to..10. Then checking the logic analyzer we can see the protocol working as we expect:
Screenshot-from-2018-08-07-10-40-04
I modified a little the source code to add some libs that was missing so if you want to see check this link.

Connecting through ssh

Before start creating the Linux application, mount the partition /dev/sb2 (ext3 type) of the SD that we had created in the latest post. So, navigate to the partition and edit the open-ssh configurations to enable root login with:

cd /media/$USER/NAME_OF_PARTITION/etc/ssh
sudo nano sshd_config

Then edit the line which has the PermitRootLogin option and set to yes (if it's commented, uncomment it).
Screenshot-from-2018-08-06-21-44-30
Then save it and put the SD back to the target to boot it again. Once it boots, check the IPv4 that you router has been attributed to the board and copy your ssh key with this command:

ssh-copy-id root@YOUR_TARGET_IP
#Type the password that you've configured to the system and now you'll not be asked about the password once up again

Then create a simple folder where we'll mount a folder with our application to transfer to the target:

mkdir tmp
#Now mount the root into this folder
sshfs root@YOUR_TARGET_IP:/root tmp

If you don't know what's such command above, they will mount remotely the folder root of your Cyclone V into your filesystem so it'll be easy to transfer the compiled sources. What's so much helpful, even more when you're testing the code. =)
Now, check the connections of your target to see where is the pins of the I2C SDA/SCL and then connect it to the target.

Source code

Now create some sources to build our application in your host computer. These files are the source code and the Makefile that we need to build our application into the Linux.

TARGET = rtc_read

ALT_DEVICE_FAMILY ?= soc_cv_av

CROSS_COMPILE = arm-linux-gnueabihf-

SOCEDS_DEST_ROOT = /home/anderson/intelFPGA/18.0/embedded

HWLIBS_ROOT = $(SOCEDS_DEST_ROOT)/ip/altera/hps/altera_hps/hwlib

CFLAGS = -g -Wall -Werror -I$(HWLIBS_ROOT)/include -I$(HWLIBS_ROOT)/include/$(ALT_DEVICE_FAMILY) -D$(ALT_DEVICE_FAMILY)
LDFLAGS = -g -Wall -Werror

CROSS_COMPILE = arm-linux-gnueabihf-
CC = $(CROSS_COMPILE)gcc
LD = $(CROSS_COMPILE)gcc
ARCH = arm

.PHONY: build
build: $(TARGET)

$(TARGET): main.o
	$(LD) $(LDFLAGS) $^ -o $@
	rm main.o
    cp rtc_read tmp

.PHONY: clean
clean:
	rm -f $(TARGET) *.a *.o *~

And the main.c code:

/**
  Licensed to the Apache Software Foundation (ASF) under one
  or more contributor license agreements.  See the NOTICE file
  distributed with this work for additional information
  regarding copyright ownership.  The ASF licenses this file
  to you under the Apache License, Version 2.0 (the
  "License"); you may not use this file except in compliance
  with the License.  You may obtain a copy of the License at
    http://www.apache.org/licenses/LICENSE-2.0
  Unless required by applicable law or agreed to in writing,
  software distributed under the License is distributed on an
  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  KIND, either express or implied.  See the License for the
  specific language governing permissions and limitations
  under the License.
 *******************************************************************************
 * @license License under APACHE 2.0.
 * @file main.c
 * @author Ânderson Ignacio da Silva
 * @date 08 Ago 2018
 * @brief Program that runs in user-linux space to read RTC through i2c /dev/i2c-1
 * @see https://blog.aignacio.com
 * @target Cyclone V - 5CSEBA6U23I7
 * @todo
**/
#include <errno.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <unistd.h>
#include <linux/types.h>
#include <linux/i2c.h>
#include <linux/i2c-dev.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <fcntl.h>
#include "hwlib.h"
#include "hps_0.h"

#include "alt_dma.h"
#include "alt_globaltmr.h"
#include "socal/hps.h"
#include "socal/socal.h"
#include "alt_clock_manager.h"
#include "alt_generalpurpose_io.h"
#include "alt_globaltmr.h"
#include "hwlib.h"
#include "socal/alt_gpio.h"

// LED PIO Macros
#define HPS_TO_FPGA_LW_BASE 0xFF200000
#define HPS_TO_FPGA_LW_SPAN 0x0020000

// I2C Macros
#define RTC_I2C_ADDR 0x68

#define TEMP_REG_1 0x11
#define TEMP_REG_2 0x12
#define SECO_REG   0x00
#define MINU_REG   0x01
#define HOUR_REG   0x02
#define DOW_REG    0x03
#define DAY_REG    0x04
#define MONTH_REG  0x05
#define YEAR_REG   0x06

#define PATH_I2C "/dev/i2c-1" //We're using the channel 1

static inline __s32 i2c_smbus_access(int file, char read_write, __u8 command,
                                     int size, union i2c_smbus_data *data){
	struct i2c_smbus_ioctl_data args;

	args.read_write = read_write;
	args.command = command;
	args.size = size;
	args.data = data;
	return ioctl(file,I2C_SMBUS,&args);
}

static inline __s32 i2c_smbus_read_byte_data(int file, __u8 command){
	union i2c_smbus_data data;
	if (i2c_smbus_access(file,I2C_SMBUS_READ,command,
	                     I2C_SMBUS_BYTE_DATA,&data))
		return -1;
	else
		return 0x0FF & data.byte;
}

uint8_t bcdtodec(const uint8_t val){
    return ((val / 16 * 10) + (val % 16));
}

uint8_t get_data(int fp, uint8_t reg){
 uint8_t data, status;
 status = ioctl(fp, I2C_SLAVE, RTC_I2C_ADDR);
 if (status < 0)
  printf("\nI2C Address not available...0x%x", RTC_I2C_ADDR);// printf("\nI2C Address correct...0x%x", RTC_I2C_SLAVE_ADDRESS);
 data = i2c_smbus_read_byte_data(fp, reg);
 return bcdtodec(data);
}

char* day_of_week(uint8_t dow){
  switch (dow) {
   case 1:
    return "Monday";
   break;
   case 2:
    return "Tuesday";
   break;
   case 3:
    return "Wednesday";
   break;
   case 4:
    return "Thursday";
   break;
   case 5:
    return "Friday";
   break;
   case 6:
    return "Saturday";
   break;
   default:
    return "Sunday";
   break;
  }
}

int main(int argc, char *argv[]){
 uint8_t temp[2],
         seconds,
         minutes,
         hour,
         day,
         month,
         dow;
 int year;

 int file_i2c, devmem_fd = 0;
 bool toggle = 0;

 void * lw_bridge_map = 0;
 uint32_t * led_pio_map = 0;

 // LEDs PIO
 devmem_fd = open("/dev/mem", O_RDWR | O_SYNC);
 if(devmem_fd < 0) {
     perror("devmem open");
     exit(EXIT_FAILURE);
 }

 lw_bridge_map = (uint32_t*)mmap(NULL, HPS_TO_FPGA_LW_SPAN, PROT_READ|PROT_WRITE, MAP_SHARED, devmem_fd, HPS_TO_FPGA_LW_BASE);
 if(lw_bridge_map == MAP_FAILED) {
     perror("devmem mmap");
     close(devmem_fd);
     exit(EXIT_FAILURE);
 }
 led_pio_map = (uint32_t*)(lw_bridge_map + LED_PIO_BASE);
 *led_pio_map = 0xFF;

 // I2C device file
 file_i2c = open(PATH_I2C, O_RDWR);
 file_i2c < 0 ? printf("\nError opening I2C1, path=%s",PATH_I2C) : printf("I2C1 Path ok");

 while (true) {
  *led_pio_map = 0x00;
  temp[0] = get_data(file_i2c,TEMP_REG_1);
  temp[1] = get_data(file_i2c,TEMP_REG_2);
  seconds = get_data(file_i2c,SECO_REG);
  minutes = get_data(file_i2c,MINU_REG);
  hour = get_data(file_i2c,HOUR_REG);
  day = get_data(file_i2c,DAY_REG);
  month = get_data(file_i2c,MONTH_REG);
  year = get_data(file_i2c,YEAR_REG)+2000;
  dow = get_data(file_i2c,DOW_REG);

  printf("\nTemp:%d.%dC Time now: %02d:%02d:%02d Date: %02d/%02d/%02d - %s",\
  temp[0],temp[1]&(0x7f),\
  hour,minutes,seconds,\
  day,month,year, day_of_week(dow));
  if (toggle)
   *led_pio_map = 0xFF;
  else
   *led_pio_map = 0x00;
  toggle = !toggle;
  usleep(100000);
  // sleep(0.5);
 }
 return 0;
}

Compile and load into the hardware to see the temperature, date and time provided by the RTC and 7 LEDs blinking into a higher freq. than the 2Hz of the FPGA Side. This code will be toggling the 7x LEDs through the lightweight HPS to FPGA bridge.
tty-2
That's it, please share/subscribe to have more posts like this and comment below if you have any questions.
SS49QfB

References
FPGA/Linux co-design with Cyclone V - Hardware run [3/3]
Share this

Subscribe to @aignacio's