二、点亮LED(GPIO控制)
文件状态: [ ] 草稿 [ ] 正在修改 [ √ ] 正式发布 | 文件标识: | |
当前版本: | V1.1 | |
作 者: | Skymixos | |
完成日期: | 2022年10月02日 |
版 本 历 史
版本/状态 | 作者 | 参与者 | 起止日期 | 备注 |
---|---|---|---|---|
V1.0 | Skymixos | 2022年1月4日 | 创建文件 | |
V1.1 | Skymixos | 2022年1月22日 | 修改文件格式,细化实验过程,完成定稿 | |
V1.2 | Skymixos | 2022年02月22日 | 修改编码实现部分的思维导图 | |
V1.3 | Skymixos | 2022年02月23日 | 修改文档结构 |
每日一句:千里之行,始于足下
1. 目的
- 熟悉嵌入式开发的基本流程
- 基本了解I.MX6ULL SoC
- 熟悉I.MX6ULL的GPIO使用方法
2. 目标
- 掌握ARM裸机程序开发流程(编写,编译,下载,调试)
- 通过GPIO控制LED的亮灭
3. 嵌入式软件的执行流程
嵌入式设备开机上电后执行流程与PC对比:
设备上电后首先执行Bootloader(比如:BIOS,U-boot),Bootloader加载操作系统(比如:Windows,Linux,uCos等)到内存中执行。而应用程序往往在操作系统起来后被加载执行。目前写的裸机程序(点亮LED)在上述流程图中属于操作系统部分。
Bootloader往往支持从FLASH,SD卡,USB,网卡等介质将Kernel加载到DDR中运行,为了调试方便
我们的目标程序(microx.bin)将由U-boot加载到DDR中执行(可以省去底层的初始化以及频繁的FLASH擦写)。
U-boot支持网络下载功能,我们只需要在服务器上开启tffp服务,就可以轻松的将程序下载到开发板上执行。
4. I.MX6ULL GPIO操作的理论基础
4.1 硬件连接
4.1.1 正点原子I.MX6U-MINI LED部分原理图
4.1.2 100ASK_IMX6ULL Pro LED部分原理图
4.1.3 100ASK_IMX6ULL Pro vs I.MX6U-MINI
开发板 | 控制LED的GPIO | GPIO组 | 控制状态 |
---|---|---|---|
I.MX6U-MINI | GPIO1_IO03 | GPIO1 | * gpio输出低电平,点亮LED * gpio输出高电平,熄灭LED |
100ASK_IMX6ULL Pro | GPIO5_IO03 | GPIO5 | * gpio输出低电平,点亮LED * gpio输出高电平,熄灭LED |
4.2 GPIO理论基础
LED控制需要的理论基础知识涉及:复用引脚,时钟,GPIO操作。
详细分析见:i.MX6U/LL SoC
5. 软件设计
从aliyun服务器上克隆原始的项目工程并切换到dev_1.0分支:
$ git clone git@code.aliyun.com:luopinjing/microx.git
$ git checkout dev_1.0
5.1 目录结构
目录/文件名 | 用途/含义 | 备注 |
---|---|---|
3rd_party | 存放一些第三方的开源库 | 目前该目录只存放了一个内容为空的SConscript文件 |
bsp | 存放板级支持代码(这些代码一般参考官方实现) | 目前该目录只存放了一个内容为空的SConscript文件 |
driver | 存放microx系统的设备驱动代码 | 目前该目录只存放了一个内容为空的SConscript文件 |
fs | 存放microx系统文件系统相关代码 | 目前该目录只存放了一个内容为空的SConscript文件 |
include | 集中存放头文件 | |
init | 存放初始化代码 | 目前该目录只存放了一个内容为空的SConscript文件 |
kernel | 存放microx系统内核核心代码 | 目前该目录只存放了一个内容为空的SConscript文件 |
lib | 存放microx系统使用库 | 目前该目录只存放了一个内容为空的SConscript文件 |
prebuilt | 存放编译工具,kconfig工具等 | * gcc-linaro-6.2.1-2016.11-x86_64_arm-linux-gnueabihf.tbz2 * Kconfiglib-master.zip |
script | 存放编译脚本及配置 | * build_log.sh:shell脚本,实现log打印功能 * config.py:编译参数配置,被SConstruct使用 * gen_autoconf_header.py:python脚本,实现将.config转换为autoconf.h的功能 * imx6ull_defconfig:默认的编译配置 * Kconfiglib-master:开源工具,实现图形化配置功能 * build.sh:顶层build.sh的实现,控制整个项目的编译 * gen_autoconf.py:python脚本,实现将.config转换为autoconf.py的功能 * Kconfig * microx.lds:链接脚本 |
shell | 存放microx系统mini shell代码 | 目前该目录只存放了一个内容为空的SConscript文件 |
build.sh | 链接文件 | 指向script下的build.sh |
SConstruct | 顶层构建文件 | 类似顶层Makefile功能 |
README | 项目说明及使用介绍 | |
.git | 项目版本管理仓库 | |
.gitignore | 用于记录无需版本管理的内容 | 文件用来忽略被指定的文件或文件夹的改动,被记录在.gitignore文件里的文件或文件夹,是无法被git跟踪到的,即被忽略的文件是不会被放入到远程仓库里的 |
5.2 软件构件系统
5.2.1 Shell脚本
编译系统中含有两个Linux shell脚本文件:build.sh,build_log.sh。
- build_log.sh:实现编译菜单及脚本的日志打印功能
- build.sh:控制整个项目的编译(包括:编译环境配置,编译,清除等)
Linux shell脚本编程的基础知识见:Linux Shell
5.2.1.1 build_log.sh
#!/bin/bash
################################ Log Related ################################
LOG_PRO_OFF="\033[0m" # close all property
LOG_PRO_FONT_BLACK="\033[1;30m"
LOG_PRO_FONT_RED="\033[1;31m"
LOG_PRO_FONT_GREEN="\033[1;32m"
LOG_PRO_FONT_YELLOW="\033[1;33m"
LOG_PRO_FONT_BLUE="\033[1;34m"
LOG_PRO_FONT_PURPLE="\033[1;35m"
LOG_PRO_FONT_DARKGREEN="\033[1;36m"
LOG_PRO_FONT_WHITE="\033[1;37m"
#
# log <log_level> [log]
# example:
# log INFO "hello, this is a test"
#
function log()
{
if [ $# -lt 2 ]
then
build_help;
else
loglvel=$1
case $loglvel in
ver)
echo -e $LOG_PRO_FONT_GREEN"$2"$LOG_PRO_OFF
;;
dbg)
echo -e $LOG_PRO_FONT_BLUE"$2"$LOG_PRO_OFF
;;
info)
echo -e $LOG_PRO_FONT_DARKGREEN"$2"$LOG_PRO_OFF
;;
warn)
echo -e $LOG_PRO_FONT_PURPLE"$2"$LOG_PRO_OFF
;;
err)
echo -e $LOG_PRO_FONT_RED"$2"$LOG_PRO_OFF
;;
*)
echo -e $LOG_PRO_FONT_WHITE"$2"$LOG_PRO_OFF
;;
esac
fi
}
function build_help()
{
log info "------------------- microX OS Build Menu ----------------------------"
log info "usage: script/build.sh [option]"
log info "-s: setup build enviorment"
log info "-m: menuconfig"
log info "-b: build OS(microX), show less log."
log info "-bv: build OS(microX), show more log."
log info "-c: clean OS(microX)"
log info "----------------------------------------------------------------------"
}
5.2.1.2 build.sh
#!/bin/bash
export ENV_BUILD_TOP=`pwd`
export ENV_BUILD_TARGET=microx
export ENV_BUILD_TEXTBASE=0x8000
export ENV_BUILD_TFTPBOOT=/home/book/tftpboot
export ENV_BUILD_CROSS_TOOL=gcc-linaro-6.2.1-2016.11-x86_64_arm-linux-gnueabihf
export ENV_BUILD_TYPE=debug
export ENV_BUILD_OUT=$ENV_BUILD_TOP/out
export ENV_BUILD_SCRIPT_DIR=$ENV_BUILD_TOP/script
export ENV_BUILD_KCONFIGLIB_DIR=$ENV_BUILD_TOP/script/Kconfiglib-master
export ENV_BUILD_3RDPARTY=$ENV_BUILD_TOP/3rd_party
export ENV_BUILD_PYTHON=python3
export ENV_BUILD_CONFIG=$ENV_BUILD_TOP/.config
export ENV_BUILD_LDS=${ENV_BUILD_SCRIPT_DIR}/${ENV_BUILD_TARGET}.lds
export ENV_BUILD_TARGET_ELF=${ENV_BUILD_OUT}/${ENV_BUILD_TARGET}.elf
ENV_BUILD_TARGET_BIN=${ENV_BUILD_OUT}/${ENV_BUILD_TARGET}.bin
. $ENV_BUILD_SCRIPT_DIR/build_log.sh
function gen_autoconf_py()
{
log dbg "generate autoconf python script"
$ENV_BUILD_PYTHON $ENV_BUILD_SCRIPT_DIR/gen_autoconf.py > $ENV_BUILD_OUT/autogen.py
$ENV_BUILD_PYTHON $ENV_BUILD_SCRIPT_DIR/gen_autoconf_header.py > $ENV_BUILD_OUT/autogen.h
}
function menuconfig()
{
if [ ! -f ${ENV_BUILD_TOP}/.config ]; then
cp $ENV_BUILD_SCRIPT_DIR/imx6ull_defconfig ${ENV_BUILD_TOP}/.config
fi
$ENV_BUILD_PYTHON $ENV_BUILD_KCONFIGLIB_DIR/menuconfig.py $ENV_BUILD_SCRIPT_DIR/Kconfig
gen_autoconf_py
}
do_build_system()
{
if test $1 == "build"; then
log info "[ Build ] ---- microX ----"
if [ ! -d ${ENV_BUILD_TOP}/prebuilt/${ENV_BUILD_CROSS_TOOL} ]; then
log err "should exec: ./build.sh -s first"
else
export ENV_BUILD_VERBOSE=1
scons -Q -j8
fi
elif test $1 == "build_detail"; then
log info "[ Build ] ---- microX detail ----"
if [ ! -d ${ENV_BUILD_TOP}/prebuilt/${ENV_BUILD_CROSS_TOOL} ]; then
log err "should exec: ./build.sh -s first"
else
export ENV_BUILD_VERBOSE=0
scons -Q -j8
fi
elif test $1 == "setup"; then
log info "[ Setup ] ---- Enviorment ----"
if [ ! -d ${ENV_BUILD_OUT} ]; then
mkdir -p ${ENV_BUILD_OUT}
fi
if [ ! -d ${ENV_BUILD_TOP}/prebuilt/Kconfiglib-master ]; then
unzip -o ${ENV_BUILD_TOP}/prebuilt/Kconfiglib-master.zip -d $ENV_BUILD_SCRIPT_DIR
fi
if [ -f ${ENV_BUILD_TOP}/prebuilt/${ENV_BUILD_CROSS_TOOL}.tbz2 ]; then
tar jxf ${ENV_BUILD_TOP}/prebuilt/${ENV_BUILD_CROSS_TOOL}.tbz2 -C $ENV_BUILD_TOP/prebuilt/
else
log warn "${ENV_BUILD_TOP}/prebuilt/${ENV_BUILD_CROSS_TOOL}.tbz2 not exist, please check!!!"
fi
elif test $1 == "clean"; then
log info "[ Clean ] ---- microX ----"
if [ -f ${ENV_BUILD_TOP}/config.pyc ]; then
rm ${ENV_BUILD_TOP}/config.pyc -f
fi
if [ -f ${ENV_BUILD_TOP}/script/config.pyc ]; then
echo "${ENV_BUILD_TOP}/script/config.pyc exist"
rm ${ENV_BUILD_TOP}/script/config.pyc -f
else
echo "${ENV_BUILD_TOP}/script/config.pyc not exist"
fi
scons -c
else
log warn "do_build_system: unsupport action"
fi
}
do_export_env()
{
# 交叉编译工具链里的工具: CC,AS, CXX, STRIP, OBJCOPY, OBJDUMP
ENV_BUILD_TOOCHAIN_PREFIX=${ENV_BUILD_TOP}/prebuilt/${ENV_BUILD_CROSS_TOOL}/bin/arm-linux-gnueabihf-
export CC=${ENV_BUILD_TOOCHAIN_PREFIX}gcc
export AS=${ENV_BUILD_TOOCHAIN_PREFIX}gcc
export AR=${ENV_BUILD_TOOCHAIN_PREFIX}ar
export CXX=${ENV_BUILD_TOOCHAIN_PREFIX}g++
export LD=${ENV_BUILD_TOOCHAIN_PREFIX}ld
export STRIP=${ENV_BUILD_TOOCHAIN_PREFIX}strip
export OBJCOPY=${ENV_BUILD_TOOCHAIN_PREFIX}objcopy
export OBJDUMP=${ENV_BUILD_TOOCHAIN_PREFIX}objdump
}
if [ -f $ENV_BUILD_OUT/.config ]; then
. $ENV_BUILD_OUT/.config
fi
if [ $# -lt 1 ]; then
build_help;
else
do_export_env;
build_action=$1
case $build_action in
-s) # setup enviorment
do_build_system setup;
;;
-m) # menuconfig
menuconfig
;;
-b) # build system
do_build_system build;
;;
-bv) # build detail
do_build_system build_detail;
;;
-c) # clean system
do_build_system clean;
;;
*)
help;
;;
esac
fi
5.3 核心代码实现
5.3.1 head.S
.text // .text申明以下为代码段
.global _start // 定义一个全局符号 _start, 链接脚本microx.lds中会使用该符号
_start:
ldr sp, =0x80200000 // 设置栈
bl clean_bss // 清除BSS段
bl start_kernel // 跳转到 C函数入口: start_kernel执行,该函数在 main.c中实现
halt:
b halt // 如果执行到此处, 将在这里无限循环
clean_bss:
/* 清除BSS段, __bss_start/__bss_end符号在链接脚本microx.lds中定义 */
ldr r1, =__bss_start
ldr r2, =__bss_end
mov r3, #0
clean:
str r3, [r1]
add r1, r1, #4
cmp r1, r2
bne clean
mov pc, lr
主要功能:
- 设置栈
- 初始化BSS段
- 跳转到start_kernel处执行
5.3.2 led.h/led.c
led.h:
/*************************************************************************************************
** Copyright © microX OS 2020 - 2030. All rights reserved.
**
** 文件名 : led.h
** 作者 : skymixos.Luo
** 版本 : V1.0
** 描述 : LED驱动头文件
**
**
** 修改记录:
** 日期 版本 修改人 备注
** ---------------------------------------------------------------------------------------------
** 2022年10月16日 V1.0 skymixos.Luo 创建文件并添加注释
**
***************************************************************************************************/
#ifndef _MX_HW_LED_H_
#define _MX_HW_LED_H_
#include "sys/types.h"
typedef enum {
LED_DEVICE_ID1,
LED_DEVICE_IDMAX
} LED_DEVICE_ID;
typedef enum {
LED_CTRL_CMD_ON,
LED_CTRL_CMD_OFF,
} LED_CTRL_CMD;
typedef struct led_device {
void* base; // GPIO控制器基地址
mx_bool_t is_init; // 是否完成设备初始化
// 设备初始化,反初始化,控制
void (*pfn_init)(struct led_device* hndl);
void (*pfn_deinit)(struct led_device* hndl);
void (*pfn_ctl)(struct led_device* hndl, LED_DEVICE_ID id, LED_CTRL_CMD cmd);
} led_device_t, *led_device_hndl;
led_device_hndl get_led_instance(void);
#endif // !_MX_HW_LED_H_
led.c:
/*************************************************************************************************
** Copyright © microX OS 2020 - 2030. All rights reserved.
**
** 文件名 : led.c
** 作者 : skymixos.Luo
** 版本 : V1.0
** 描述 : LED驱动实现
**
**
** 修改记录:
** 日期 版本 修改人 备注
** ---------------------------------------------------------------------------------------------
** 2022年9月10日 V1.0 skymixos.Luo 创建文件并添加注释
**
***************************************************************************************************/
#include "sys/io.h"
#include "drv/led.h"
#include "autogen.h"
#include "bsp/imx6ull_soc.h"
static led_device_t g_led_device = {0};
MX_UNUSED static const mx_int8_t* get_led_id_string(LED_DEVICE_ID id)
{
switch (id) {
CONVNUMTOSTR(LED_DEVICE_ID1);
CONVNUMTOSTR(LED_DEVICE_IDMAX);
default: return "unknown led id";
}
}
MX_UNUSED static const char* get_led_cmd_string(LED_CTRL_CMD cmd)
{
switch (cmd) {
CONVNUMTOSTR(LED_CTRL_CMD_ON);
CONVNUMTOSTR(LED_CTRL_CMD_OFF);
default: return "unknown led cmd";
}
}
/*
* 对于 100ASK_IMX6ULL Pro开发板: GPIO5_3
* 对于 ATK-DL6Y2CM 开发板: GPIO1_4
*/
static void led_init(struct led_device* hndl)
{
mx_uint32_t reg_val = 0;
/* 1.使能GPIO部分的时钟
* set CCM to enable GPIO1/5
* GPIO5 bit[31:30] = 0b11
* GPIO1 bit[27:26] = 0b11
*/
reg_val = readl(SOC_CCM_BASE + CCM_CCGR1);
#ifdef CONFIG_PLATFORM_ATKDL6Y2CM_IMX6ULL
reg_val |= (3 << 26);
#else
reg_val |= (3 << 30);
#endif
writel(reg_val, SOC_CCM_BASE + CCM_CCGR1);
/*
* 2.复用引脚配置
* 100ASK_IMX6ULL Pro: IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 229_0014h
* ATK-DL6Y2CM: IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO04 20E_006Ch
*/
#ifdef CONFIG_PLATFORM_ATKDL6Y2CM_IMX6ULL
reg_val = readl(SOC_IOMUXC_BASE + 0x6c);
reg_val &= ~(0xf);
reg_val |= 5;
writel(reg_val, SOC_IOMUXC_BASE + 0x6c);
#else
reg_val = readl(SOC_IOMUXC_SNVS_BASE + 0x14);
reg_val &= ~(0xf);
reg_val |= 5;
writel(reg_val, SOC_IOMUXC_SNVS_BASE + 0x14);
#endif
/*
* 3.将GPIO配置为输出,默认输出高电平,熄灭LED
*/
reg_val = readl(hndl->base + GPIO_GDIR);
reg_val |= (1 << 3);
writel(reg_val, hndl->base + GPIO_GDIR);
// 将GPIO输出高电平,熄灭LED
reg_val = readl(hndl->base + GPIO_DR);
reg_val |= (1 << 3);
writel(reg_val, hndl->base + GPIO_DR);
}
static void led_deinit(struct led_device* hndl)
{
// nothing
}
static void led_ctl(struct led_device* hndl, LED_DEVICE_ID id, LED_CTRL_CMD cmd)
{
mx_uint32_t led_bit = 0;
mx_uint32_t led_val = readl(hndl->base + GPIO_DR);
if (id == LED_DEVICE_ID1) {
led_bit = 3;
} else {
return;
}
if (cmd == LED_CTRL_CMD_ON) {
led_val &= ~(1 << led_bit);
} else if (cmd == LED_CTRL_CMD_OFF) {
led_val |= (1 << led_bit);
} else {
return;
}
writel(led_val, hndl->base + GPIO_GR_OFFSET);
}
led_device_hndl get_led_instance(void)
{
if (g_led_device.is_init == false) {
#ifdef CONFIG_PLATFORM_100ASK_IMX6ULL
g_led_device.base = (void*)SOC_GPIO5_BASE;
#else
g_led_device.base = (void*)SOC_GPIO1_BASE;
#endif
g_led_device.pfn_init = led_init;
g_led_device.pfn_deinit = led_deinit;
g_led_device.pfn_ctl = led_ctl;
g_led_device.pfn_init(&g_led_device);
g_led_device.is_init = true;
}
return &g_led_device;
}
主要功能:
- 实现LED的控制(点亮,熄灭)
5.3.3 main.c
#include "led.h"
static void delay(volatile mx_uint32_t d)
{
while (d--);
}
mx_int32_t start_kernel(void)
{
led_device_hndl led = get_led_instance();
while (1) {
led->pfn_ctl(led, LED_DEVICE_ID1, LED_CTRL_CMD_OFF);
delay(10000000);
led->pfn_ctl(led, LED_DEVICE_ID1, LED_CTRL_CMD_ON);
delay(10000000);
}
return 0;
}
主要功能:
- 调用led驱动的控制接口,循环的点亮熄灭LED
5.5 编译测试
5.5.1 云服务器的代码仓库中下载测试代码(code.ayliyun.com)
5.5.1.1 配置git并将SSH公钥添加到aliyun服务器
- git配置用户名和email
$ git config --global user.name "your_name"
$ git config --global user.email "your_email"
注:将"your_name"和"your_email"替换成自己的用户名和邮箱
比如:
$ git config --global user.name “microx”
$ git config --global user.email “microx@163.com”
- 生成公钥:
$ ssh-keygen -t rsa -C "your_email"
执行该命令并一路回车之后,会在用户的根目录生成:id_rsa.pub文件,如:
- 将id_rsa.pub的内容添加到aliyun服务器
- 下载代码:git clone git@code.aliyun.com:luopinjing/microx.git
下载完成后,切换到dev_1.0分支并:git checkout dev_1.0
检出代码:git checkout v0.0.1
5.5.2 配置(根据自己的开发板选择对应配置)
-
执行 ./build.sh 会出现编译菜单:
-
首次下载代码,需先执行:./build.sh -s 来设置编译环境
-
执行 ./build.sh -m 来选择自己所使用的开发板
菜单使用:回车键(进入子菜单);上下方向键(选择菜单项);左右方向键(用于进入和返回);Esc键(退出菜单);字母Y:保存设置;字母N:不保存设置
5.5.3 编译
-
执行 ./build.sh -b 命令完成目标程序编译,并将编译产生的microx.bin拷贝到 /home/book/tftpboot/目录下。
-
执行 ./build.sh -bv 命令会显示详细的编译信息
编译产生的结果存放在 out 目录下:
- autogen.h:使用配置./build.sh -m 生成的配置最终会转换成autogen.h,里面包含定义的一系列宏,代码中使用这些宏达到条件编译的目的。
- autogen.py:使用配置./build.sh -m 生成的配置最终会转换成autogen.py,该文件被编译系统使用,达到选择需要编译哪些文件的目标。
- microx.elf:编译产生的elf文件。
- microx.bin:最终烧写到目标板上运行的文件,是由microx.elf转换生成的。
- microx.dis:反汇编文件,用于调试(当程序崩溃时)
5.5.4 下载程序运行测试
5.5.4.1 开发板和Windows的网络连接配置
开发板和Windows开发主机有两种网络连接方式:
- 方式一:开发板和Windows开发主机连接到同一个路由器上(前提是你得有一个路由器)
- 方式二:开发板和Windosw开发主机通过一根网线直连(如果你的电脑没有网口,可以购买一个USB转网口的转接器)
- 采用方式一时,路由器会给Windows动态分配IP地址,需要手动给开发板设置一个与Windows在同一网段的地址。
- 采用方式二时,需要给Windows对应的网卡设置一个与开发板同一网段的IP地址(开发板默认的IP地址为192.168.1.X)。
5.5.4.2 Windows下开启tftp服务
使用tftp服务的时候需要关闭Windows的防火墙,否则开发板不能ping通Windows!!!
- 开发板设置tftp服务器的ip为Windows的ip:setenv serverip xx.xx.xx.xx
注:这个ip地址以自己机器的ip地址为准
- 保持参数配置:saveenv
5.5.4.2 下载程序运行
开发板上电后,按下“回车”键进入u-boot的下载模式。通过tftp下载microx.bin到内存地址0x80008000处执行。
6. 小结
3.1 使用到汇编指令及伪指令
- MOV指令
- B指令
- LDR指令
- LDR伪指令
3.2 C语言知识点
- 基本的数据类型
- 结构体: struct
- 枚举类型:enum
- 指针,函数指针
- typedef的使用
- 位操作:按位与(&),按位或(|),按位取反(~)
- 关键字:static,volatile
7. 参考资料
- 链接脚本
- ARMv7汇编:
- IMX6ULL SOC:i.MX6U/LL SoC
- 构建工具SCons:SCons
https://blog.csdn.net/hes_c/article/details/71600471
https://www.cnblogs.com/deepworm/p/10452908.html