Introdução
Gostaria de me apresentar e explicar o que devem esperar das postagens que farei aqui.
Meu nome é Rafael, sou um engenheiro de controle e automação que tem como passa tempo sistemas embarcados.
Recentemente adquiri uma DE10 Nano e decidi documentar meu aprendizado na esperança de ajudar outras pessoas que estejam tentando utilizar um kit de desenvolvimento igual ou semelhante, além de garantir que eu mesmo não esqueça como fiz para resolver um problema, tenho certeza que muita gente já teve alguma experiência parecida, onde você resolve um problema e depois de algum tempo esbarra nele novamente, você sabe que já resolveu isso antes mas não lembra como foi.
Agora que já falei um pouco sobre mim, vamos falar da placa.
DE10 Nano
A DE10 Nano é uma placa produzida pela Terasic baseada no SOC Cyclone® V SE 5CSEBA6U23I7 da Intel, ela veio para substituir a DE0 Nano SOC e atualizar o SOC utilizado para um com maior capacidade.
Numa comparação rápida entre as duas placas, a principal diferença está na adição de um conector HDMI na DE10 Nano e a quantidade de elementos lógicos que na DE10 Nano é de 110 mil e na DE0 Nano SOC é de 40 mil. Fora esses detalhes, as duas placas possuem características muito semelhantes, contando com Ethernet Gigabit, conector USB-OTG, acelerômetro conectado diretamente ao HPS, conversor A/D, 2 conectores de 40 pinos para expansão (GPIO), conector estilo Arduino, conversor USB-Serial integrado e USB-Blaster II integrado.
Para quem quiser conferir as especificações completas das placas, vou disponibilizar o link direto para o site do fabricante:
DE0 Nano SOC
DE10 Nano
Como criar um cartão SD com Ubuntu
Existem 2 grandes campos para trabalhar nessa plataforma SOC, o FPGA e o HPS. Decidi começar pelo HPS por ser uma estrutura já definida e que será utilizada posteriormente.
Será necessária a utilização de um computador ou máquina virtual com um sistema operacional Linux, eu optei pela distribuição Ubuntu e os passos apresentados serão baseados nessa distribuição. Caso prefira outra distribuição, os passos devem ser basicamente os mesmos, mas alguns comandos podem ser ligeiramente diferentes dependendo da distribuição escolhida, como por exemplo o gerenciador de pacotes utilizado para instalar os pacotes necessários para o procedimento que realizaremos.
Inicialmente não é necessário possuir um cartão SD, ele só será necessário após compilarmos o bootloader, kernel e módulos.
O primeiro passo é baixar os seguintes pacotes que serão necessários durante o processo:
1: sudo apt install git
2: sudo apt install make
3: sudo apt install make-guile
4: sudo apt install build-essential
5: sudo apt install bison
6: sudo apt install flex
7: sudo apt install libncurses5-dev libncursesw5-dev
8: sudo apt install libssl-dev
9: sudo apt install lzop
10: sudo apt install qtdeclarative5-dev
11: sudo apt install pkg-config
Com todos os pacotes instalados devemos criar uma pasta de trabalho, por conveniência chamarei a pasta de DE0_NANO_DEV.
1: mkdir DE0_NANO_SOC
2: cd DE0_NANO_SOC
Após criar e entrar na pasta de trabalho faremos o download do toolchain linaro e do código fonte para o bootloader e para o Kernel. Note que os arquivos são grandes e o download poderá demorar.
1: wget http://releases.linaro.org/components/toolchain/binaries/7.3-2018.05/arm-linux-gnueabihf/gcc-linaro-7.3.1-2018.05-x86_64_arm-linux-gnueabihf.tar.xz
2: git clone https://github.com/torvalds/linux.git
3: git clone https://github.com/u-boot/u-boot
4: wget -c https://rcn-ee.com/rootfs/eewiki/minfs/ubuntu-18.04.1-minimal-armhf-2018-07-30.tar.xz
Nota importante, a versão do toolchain linaro 7.3.1-2018-5 é a mais recente na data de publicação deste post, é recomendável verificar se existe uma nova versão estável e baixa-la, substituindo a versão mais antiga.
Outro ponto importante é que não utilizarei patches para alterar/criar arquivos, minha intenção com isso é apresentar as alterações, fazendo com que os passos possam ser replicados em diversas versões do kernel e do bootloader.
Com todos os arquivos necessários baixados, devemos configurar o ambiente de desenvolvimento.
1: tar -xvf gcc-linaro-7.3.1-2018.05-x86_64_arm-linux-gnueabihf.tar.xz
2: export CC=${PWD}/gcc-linaro-7.3.1-2018.05-x86_64_arm-linux-gnueabihf/bin/arm-linux-gnueabihf-
Apenas por garantia, é recomendado executar o comando abaixo para verificar se o compilador será acessado corretamente
1: ${CC}gcc –version
Caso esteja funcionando a saída será parecida com:
1: arm-linux-gnueabihf-gcc (Linaro GCC 7.3-2018.05) 7.3.1 20180425 [linaro-7.3-2018.05 revision d29120a424ecfbc167ef90065c0eeb7f91977701]
2: Copyright (C) 2017 Free Software Foundation, Inc.
3: This is free software; see the source for copying conditions. There is NO
4: warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE
5:
Compilando o bootloader
O bootloader utilizado será o U-Boot, porém não utilizaremos a versão distribuída pela Intel/Altera pois é muito desatualizada (2013.01.01).
Como o repositório do U-Boot já foi clonado, devemos entrar na pasta e selecionar uma versão recente com os comandos abaixo:
1: cd u-boot
2: git checkout v2018.09 -b tmp
Utilizaremos um branch temporário por comodidade. Para configurar o U-boot basta executar os comandos:
1: make ARCH=arm CROSS_COMPILE=${CC} distclean
2: make ARCH=arm CROSS_COMPILE=${CC} socfpga_de10_nano_defconfig
3:
Caso deseje alterar alguma configuração utilizando um menu ou interface gráfica o comando opcional abaixo pode ser utilizado:
1: make ARCH=arm CROSS_COMPILE=${CC} menuconfig
2: make ARCH=arm CROSS_COMPILE=${CC} xconfig
Para compilar o U-Boot execute o comando:
1: make ARCH=arm CROSS_COMPILE=${CC} u-boot-with-spl.sfp
Agora que compilamos o bootloader acho importante dar algumas explicações. Não utilizaremos o padrão demonstrado pela Intel/Altera ao criar nosso cartão SD pois utilizaremos uma imagem do U-Boot que já contém o SPL (Preloader do U-Boot) e que é capaz de carregar arquivos de uma partição ext4, portanto nosso cartão SD terá apenas duas partições e o arquivo de configuração de boot ficará na própria partição ext4.
Compilando o Kernel
O procedimento para compilar o Kernel é bem parecido com o que utilizamos para compilar o bootloader, a grande diferença é que aqui faremos algumas modificações no código e adicionaremos alguns arquivos.
Outro ponto importante é que estamos utilizando o kernel do repositório oficial do Linux, a Intel/Altera possui um repositório próprio com algumas alterações voltadas para seus produtos. O procedimento para compilar o kernel não muda, mas em um outro post compilarei o kernel RT do repositório da Intel/Altera.
Primeiramente vamos entrar no diretório do Kernel e selecionar a versão mais recente para trabalharmos.
1: cd ../linux
2: git checkout v4.18 -b tmp
Agora que a versão mais recente do kernel foi selecionada, criaremos a pasta “deploy”, utilizada para simplificar os passos envolvendo a criação do cartão SD.
1: mkdir deploy
O primeiro passo após criar a pasta será a criação de 2 novos arquivos, um “Device Tree” e outro de configuração do kernel. Depois editaremos um Makefile para utilizar os arquivos criados.
Vamos começar pelo “Device Tree”. Utilizando um editor de texto crie o arquivo “arch/arm/boot/dts/socfpga_cyclone5_de10_nano.dts”, para facilitar o tutorial utilizarei o gedit.
1: gedit arch/arm/boot/dts/socfpga_cyclone5_de10_nano.dts
Em seguida preencha o documento com:
1: /*
2: * Copyright Intel Corporation (C) 2017. All rights reserved.
3: *
4: * This program is free software; you can redistribute it and/or modify
5: * it under the terms and conditions of the GNU General Public License,
6: * version 2, as published by the Free Software Foundation.
7: *
8: * This program is distributed in the hope it will be useful, but WITHOUT
9: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
10: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
11: * more details.
12: *
13: * You should have received a copy of the GNU General Public License along with
14: * this program. If not, see <http://www.gnu.org/licenses/>.
15: */
16:
17: #include "socfpga_cyclone5.dtsi"
18:
19: / {
20: model = "Terasic DE10-Nano";
21: compatible = "altr,socfpga-cyclone5", "altr,socfpga";
22:
23: chosen {
24: bootargs = "earlyprintk";
25: stdout-path = "serial0:115200n8";
26: };
27:
28: memory {
29: name = "memory";
30: device_type = "memory";
31: reg = <0x0 0x40000000>; /* 1GB */
32: };
33:
34: aliases {
35: ethernet0 = &gmac1;
36: };
37:
38: regulator_3_3v: 3-3-v-regulator {
39: compatible = "regulator-fixed";
40: regulator-name = "3.3V";
41: regulator-min-microvolt = <3300000>;
42: regulator-max-microvolt = <3300000>;
43: };
44:
45: leds {
46: compatible = "gpio-leds";
47: hps0 {
48: label = "hps_led0";
49: gpios = <&portb 24 0>;
50: linux,default-trigger = "heartbeat";
51: };
52: };
53:
54: keys {
55: compatible = "gpio-keys";
56: hps0 {
57: label = "hps_key0";
58: gpios = <&portb 25 0>;
59: linux,code = <63>;
60: debounce-interval = <50>;
61: };
62: };
63: };
64:
65: &gmac1 {
66: status = "okay";
67: phy-mode = "rgmii";
68:
69: txd0-skew-ps = <0>; /* -420ps */
70: txd1-skew-ps = <0>; /* -420ps */
71: txd2-skew-ps = <0>; /* -420ps */
72: txd3-skew-ps = <0>; /* -420ps */
73: rxd0-skew-ps = <420>; /* 0ps */
74: rxd1-skew-ps = <420>; /* 0ps */
75: rxd2-skew-ps = <420>; /* 0ps */
76: rxd3-skew-ps = <420>; /* 0ps */
77: txen-skew-ps = <0>; /* -420ps */
78: txc-skew-ps = <1860>; /* 960ps */
79: rxdv-skew-ps = <420>; /* 0ps */
80: rxc-skew-ps = <1680>; /* 780ps */
81:
82: max-frame-size = <3800>;
83: };
84:
85: &gpio0 {
86: status = "okay";
87: };
88:
89: &gpio1 {
90: status = "okay";
91: };
92:
93: &gpio2 {
94: status = "okay";
95: };
96:
97: &i2c0 {
98: status = "okay";
99: speed-mode = <0>;
100:
101: adxl345: adxl345@0 {
102: compatible = "adi,adxl34x";
103: reg = <0x53>;
104:
105: interrupt-parent = <&portc>;
106: interrupts = <3 2>;
107: };
108: };
109:
110: &mmc0 {
111: vmmc-supply = <®ulator_3_3v>;
112: vqmmc-supply = <®ulator_3_3v>;
113: status = "okay";
114: };
115:
116: &uart0 {
117: status = "okay";
118: };
119:
120: &usb1 {
121: status = "okay";
122: };
Salve o arquivo.
Devemos criar um novo arquivo socfpga_de10_nano_defconfig na pasta “arch/arm/configs/”, como o conteúdo arquivo é muito extenso, decidi colocar um link para
download. Basta colocar o arquivo baixado na pasta.
Agora editaremos o arquivo “arch/arm/boot/dts/Makefile”
1: gedit arch/arm/boot/dts/Makefile
No arquivo procure por “socfpga”, deverá encontrar um texto semelhante ao exibido abaixo:
1: dtb-$(CONFIG_ARCH_SOCFPGA) += \
2: socfpga_arria5_socdk.dtb \
3: socfpga_arria10_socdk_nand.dtb \
4: socfpga_arria10_socdk_qspi.dtb \
5: socfpga_arria10_socdk_sdmmc.dtb \
6: socfpga_cyclone5_mcvevk.dtb \
7: socfpga_cyclone5_socdk.dtb \
8: socfpga_cyclone5_de0_sockit.dtb \
9: socfpga_cyclone5_sockit.dtb \
10: socfpga_cyclone5_socrates.dtb \
11: socfpga_cyclone5_sodia.dtb \
12: socfpga_cyclone5_vining_fpga.dtb \
13: socfpga_vt.dtb
Edite o texto adicionado a linha “socfpga_cyclone5_de10_nano.dtb \”, fazendo com que o texto fique da seguinte maneira:
1: dtb-$(CONFIG_ARCH_SOCFPGA) += \
2: socfpga_arria5_socdk.dtb \
3: socfpga_arria10_socdk_nand.dtb \
4: socfpga_arria10_socdk_qspi.dtb \
5: socfpga_arria10_socdk_sdmmc.dtb \
6: socfpga_cyclone5_mcvevk.dtb \
7: socfpga_cyclone5_socdk.dtb \
8: socfpga_cyclone5_de0_sockit.dtb \
9: socfpga_cyclone5_sockit.dtb \
10: socfpga_cyclone5_socrates.dtb \
11: socfpga_cyclone5_sodia.dtb \
12: socfpga_cyclone5_vining_fpga.dtb \
13: socfpga_cyclone5_de10_nano.dtb \
14: socfpga_vt.dtb
Salve o arquivo.
Essa alteração adiciona o arquivo socfpga_cyclone5_de10_nano.dts na lista de arquivos que serão compilados. Esse passo garante que teremos o arquivo socfpga_cyclone5_de10_nano.dtb ao final do processo de compilação.
Após as alterações execute os seguintes comandos para configurar o kernel:
1: make ARCH=arm CROSS_COMPILE=${CC} distclean
2: make ARCH=arm CROSS_COMPILE=${CC} socfpga_de10_nano_defconfig
Em seguida execute o comando
1: make ARCH=arm CROSS_COMPILE=${CC} menuconfig
Ou caso prefira interface gráfica
1: make ARCH=arm CROSS_COMPILE=${CC} xconfig
Aqui você poderá alterar configurações do kernel e adicionar ou remover funcionalidades. Os comandos executados anteriormente já configuraram o kernel corretamente, caso não deseje fazer alterações basta sair e prosseguir.
Execute o comando abaixo para compilar o kernel:
1: make -j$(getconf _NPROCESSORS_ONLN) ARCH=arm LOCALVERSION= CROSS_COMPILE=${CC} zImage modules
Aguarde enquanto o kernel é compilado, o processo deve demorar alguns minutos.
Após terminar de complilar o kernel execute o comando abaixo para criar uma variável de ambiente com a versão do kernel:
1: export kernel_version=$(cat "include/generated/utsrelease.h" | awk '{print $3}' | sed 's/\"//g' )
Copie a imagem do kernel para o diretório deploy utilizando o comando:
1: cp arch/arm/boot/zImage deploy/${kernel_version}.zImage
Agora que terminamos de compilar o kernel, devemos copiar os módulos para a pasta deploy:
1: make -s ARCH=arm LOCALVERSION= CROSS_COMPILE="${CC}" modules_install INSTALL_MOD_PATH="deploy/tmp"
2: cd deploy/tmp
3: tar --create --gzip --file ../${kernel_version}-modules.tar.gz ./*
4: cd ..
5: rm -rf tmp
6: cd ..
A última etapa é compilar os DTBs, que são os arquivos que descrevem a placa para o kernel, seria necessário compilar apenas o arquivo para a DE10-Nano, podendo inclusive pular esse passo caso já possua um arquivo, mas como é possível fazer este passo de maneira genérica, criando os arquivos para diversas placas, decidi inclui-lo.
1: make ARCH=arm LOCALVERSION= CROSS_COMPILE="${CC}" dtbs
2: make -s ARCH=arm LOCALVERSION= CROSS_COMPILE="${CC}" dtbs_install INSTALL_DTBS_PATH="deploy/tmp"
3: cd deploy/tmp
4: tar --create --gzip --file ../${kernel_version}-dtbs.tar.gz ./*
5: cd ..
6: rm -rf tmp
7: cd ..
Cartão SD
Agora todos os arquivos necessários para criar o cartão SD foram criados e resta apenas formatar o cartão e copiar os arquivos.
O processo para formatar o cartão SD é bem direto, a única coisa importante é que você deverá verificar qual o dispositivo a ser formatado. No tutorial utilizarei o dispositivo /dev/sdX, você deverá utilizar o dispositivo correto para seu cartão SD.
1: export DISK=/dev/sdx
2: sudo dd if=/dev/zero of=${DISK} bs=1M count=64
3: sudo sfdisk ${DISK} <<-__EOF__
4: 1M,1M,0xA2,
5: 2M,,,*
6: __EOF__
Pronto, o cartão SD já está com as duas partições criadas, o próximo passo é copiar o bootloader para a primeira partição usando os comandos:
1: cd ..
2: sudo dd if=./u-boot/u-boot-with-spl.sfp of=${DISK}1
Com o bootloader copiado para o cartão SD, formate a segunda partição com o comando:
1: sudo mkfs.ext4 -L rootfs ${DISK}2
Monte a partição com os comandos:
1: sudo mkdir -p /media/rootfs/
2: sudo mount ${DISK}2 /media/rootfs/
Primeiramente vamos copiar o ubuntu para o cartão SD:
1: sudo tar xfvp ./*-*-*-armhf-*/armhf-rootfs-*.tar -C /media/rootfs/
2: sync
3: sudo chown root:root /media/rootfs/
4: sudo chmod 755 /media/rootfs/
Em seguida criaremos um arquivo de configuração de boot:
1: sudo mkdir -p /media/rootfs/boot/extlinux/
2: sudo sh -c "echo 'label Linux ${kernel_version}' > /media/rootfs/boot/extlinux/extlinux.conf"
3: sudo sh -c "echo ' kernel /boot/vmlinuz-${kernel_version}' >> /media/rootfs/boot/extlinux/extlinux.conf"
4: sudo sh -c "echo ' append root=/dev/mmcblk0p2 ro rootfstype=ext4 rootwait quiet' >> /media/rootfs/boot/extlinux/extlinux.conf"
5: sudo sh -c "echo ' fdtdir /boot/dtbs/${kernel_version}/' >> /media/rootfs/boot/extlinux/extlinux.conf"
Por fim copiaremos o kernel, módulos e dtbs para o cartão sd.
1: cd linux
2: sudo cp -v ./deploy/${kernel_version}.zImage /media/rootfs/boot/vmlinuz-${kernel_version}
3: sudo mkdir -p /media/rootfs/boot/dtbs/${kernel_version}/
4: sudo tar xfv ./deploy/${kernel_version}-dtbs.tar.gz -C /media/rootfs/boot/dtbs/${kernel_version}/
5: sudo tar xfv ./deploy/${kernel_version}-modules.tar.gz -C /media/rootfs/
O último passo para configurar o cartão sd é configurar o FSTAB e remover o cartão com segurança.
1: sudo sh -c "echo '/dev/mmcblk0p2 / auto errors=remount-ro 0 1' >> /media/rootfs/etc/fstab"
2: sync
3: sudo umount /media/rootfs
Pronto, agora você tem um cartão SD com Ubuntu pronto para funcionar na DE10 Nano.
Agora só resta testar.
Como queremos monitorar o andamento da sequência de boot, conecte o cabo usb na porta uart da DE10 Nano e utilize um software como o PUTTY utilizando o baud de 115200.
Insira o cartão SD na DE10 Nano e conecte o cabo de alimentação. No PUTTY você poderá observar o SPL carregando o U-BOOT e em seguida o U-Boot carregando o kernel.
Espero que tenha ajudado, caso tenha alguma dúvida ficarei feliz em tentar ajudar.
No próximo post pretendo compilar um segundo kernel e utilizar um menu para selecionar qual kernel será utilizado.