参考《I.MX6U嵌入式 Linux驱动开发指南 V1.7》
带有wifi的笔记本的工作环境配置:
(1)网络连接:电脑使用无线上网,开发板网口直连电脑网口。
(2)VMwear 设置:设置网络适配器为桥接模式;添加一个网络适配器 2 为 NAT 模式。桥接模式是ubuntu和板卡以及电脑直接通信使用,NAT是ubuntu访问外网使用。并且在vmware中配置VMnet0连接到Realtek PCIe Gbe,即以太网。
(3)Ubuntu 设置:设置 NAT 模式的网络适配器为自动获取模式(DHCP);手动设置桥接模式的的网络适配器的 IP 信息。
Windows 设置:手动设置以太网 TCP/IPv4 属性中的 IP 信息。
开发板设置:手动设置开发板的 IP 信息。
注意: Ubuntu 里桥接模式的网络适配器、电脑的以太网、开发板的网口,三者要在同一网段,且不能与 NAT 模式的网络适配器、虚拟机子网在同一网段。 Windows 和 Ubuntu 通过 NAT模式的网络适配器(以太网)通信 。
电脑 192.168.10.200 板卡 192.168.10.50 虚拟机 192.168.10.100
(1)sudo apt-get install vsftpd
(2)sudo vi /etc/vsftpd.conf
修改local_enable=YES write_enable=YES
(3)sudo /etc/init.d/vsftpd restart
E: Could not get lock /var/lib/dpkg/lock-frontend - open (11: Resource temporarily unavailable)
E: Unable to acquire the dpkg frontend lock (/var/lib/dpkg/lock-frontend), is another process using it?
sudo killall apt apt-get
sudo rm /var/lib/apt/lists/lock
sudo rm /var/cache/apt/archives/lock
sudo rm /var/lib/dpkg/lock*
sudo dpkg --configure -a
sudo apt-get update
(1)sudo apt-get install nfs-kernel-server rpcbind
(2)sudo vi /etc/exports
(3)增加/home/yinliang/linux/nfs *(rw,sync,no_root_squash)
(4)sudo /etc/init.d/nfs-kernel-server restart
sudo apt-get install openssh-server
(1)sudo mkdir /usr/local/arm
(2)sudo cp gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf.tar.xz /usr/local/arm/ -f
(3)sudo tar -vxf gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf.tar.xz
(4)sudo vi /etc/profile
export PATH=$PATH:/usr/local/arm/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf/bin 重新启动
(5)sudo apt-get install lsb-core lib32stdc++6
(6)arm-linux-gnueabihf-gcc -v 查看编号
file led.o 查看文件格式,是32位的LSB的ELF格式文件,目标架构为ARM。
ARM芯片上电的时候SP指针(堆栈指针)还没有初始化,C环境还不好,所以不能运行C代码,必须先使用汇编语言来设置好C环境,比如初始化DDR,设置SP指针等等,必须在汇编设置好的环境下再去运行C代码。
MOV R0, R1 @将R1里面的数据复制到R0里面去
MOV R0, #0X12 @将立即数0X12传递给R0寄存器, 即R0 = 0X12
MRS R0,CPSR @MRS将特殊寄存器(如CPSR,当前程序状态寄存器 和 SPSR,备份程序状态寄存器)的数据传递给通用寄存器,如果要读取特殊寄存器的数值,必须使用MRS指令,这里将CPSR的寄存器数值传递给R0
MSR CPSR, R0 @MSR将通用寄存器的数据传递CPSR,当前程序状态寄存器 和 SPSR,备份程序状态寄存器,如果要写入特殊寄存器的数值,必须使用MSR指令,这里将R0中的数据传递给CPSR
ARM 不能直接访问存储器,比如 RAM 中的数据, I.MX6UL 中的寄存器就是 RAM 类型的,我们用汇编来配置 I.MX6UL 寄存器的时候需要借助存储器访问指令,一般先将要配置的值写入到 Rx(x=0~12)寄存器中,然后借助存储器访问指令将 Rx 中的数据写入到 I.MX6UL 寄存器 中。读取 I.MX6UL 寄存器也是一样的,只是过程相反。常用的存储器访问指令有两种: LDR 和STR
LDR 从存储器中加载数据到寄存器Rx中,也可以加载一个立即数到寄存器Rx,此时使用=而不是#符号。
LDR R0, =0X0209C004 @将寄存器地址0X0209C004加载到R0中,即R0 = 0X0209C004
LDR R1, [R0] @读取地址0X0209C004中的数据到R1寄存器中,此时的offset大小为0
STR是将数据写入到存储器中
STR R1, [R0] @将R1中的值写入到R0做保存的地址中
保存R0到R15寄存器的操作就叫做现场保护,恢复R0-R15寄存器的操作就叫做恢复现场。在进行现场保护的时候需要进行压栈操作,恢复现场就需要进行出栈操作。压栈的指令是PUSH,出栈的指令是POP。一次可以操作多个寄存器数据,利用当前的栈指针SP来生成地址,处理器的堆栈是向下增长的。
PUSH {R0~R3, R12} @将R0~R3和R12压栈
PUSH {LR} @将LR进行压栈
POP {LR} @先恢复LR
POP {R0~R3,R12} @在恢复R0-R3,R12
出栈从栈顶,也就是当前SP执行的位置来执行的,地址依次增大来提取堆栈中的数据到要恢复的寄存器中。
也可以写成
入栈 STMFD SP!, {R0~R3, R12}
出栈 LDMFD SP!, {R0~R3, R12}
前面的LDR和STR是数据加载和存储指令,但是每次只能读写存储器中的一个数据,STM和LDM则是多存储和多加载,可以连续读写存储器中的多个连续数据。
ARM 使用的 FD 类型的堆栈, SP 指向最后一个入栈的数值,堆栈是由高地址向下增长的,也就是前面说的向下增长的堆栈,因此最常用的指令就是 STMFD 和 LDMFD。 STM 和 LDM 的指令寄存器列表中编号小的对应低地址,编号高的对应高地址
直接使用跳转指令B BL BX 或者 直接向PC寄存器里面写入数据
B指令 会将PC寄存器的值设置为跳转目标地址,一旦执行B指令,就立即跳转指定的目标地址。如果调用的函数不会再返回原来的执行处,可以直接使用B指令。
_start
ldr sp, = 0X8020000 @设置栈指针
b main @跳转到main函数
这里只需要初始化SP指针,有的处理器还需要做其他的初始化,比如DDR的初始化,因为跳转到C文件以后再也不会回到汇编了,所以使用B指令来完成跳转。
BL指令 在跳转之前会在寄存器LR(R14)中保存当前的PC寄存器,可以通过LR寄存器中的值重新加载到PC中来继续跳转之前的代码运行处。比如 Cortex-A 处理器的 irq 中断服务函数都是汇编写的,主要用汇编来实现现场的保护和恢复、获取中断号等。但是具体的中断处理过程都是 C 函数,所以就会存在汇编中调用 C 函数的问题。而且当 C 语言版本的中断处理函数执行完成以后是需要返回到irq 汇编中断服务函数,因为还要处理其他的工作,一般是恢复现场。这个时候就不能直接使用B 指令了,因为 B 指令一旦跳转就再也不会回来了,这个时候要使用 BL 指令。
push {r0, r1} @保存r0和r1
cps #0x13 @进行svc模式,允许其他中断再次进去
bl system_irqhandler @加载c语言中断处理函数到r2寄存器中
cps #0x12 @进行IRQ模式
pop {r0, r1}
str r0, [r1, #0x10] @中断执行完成,写EOIR,str是字数据写入
.global _start /* 全局标号 */
/*
* 描述: _start函数,程序从此函数开始执行此函数完成时钟使能、
* GPIO初始化、最终控制GPIO输出低电平来点亮LED灯。
*/
_start:
/* 例程代码 */
/* 1、使能所有时钟 */
ldr r0, =0X020C4068 /* CCGR0 */
ldr r1, =0XFFFFFFFF
str r1, [r0]
ldr r0, =0X020C406C /* CCGR1 */
str r1, [r0]
ldr r0, =0X020C4070 /* CCGR2 */
str r1, [r0]
ldr r0, =0X020C4074 /* CCGR3 */
str r1, [r0]
ldr r0, =0X020C4078 /* CCGR4 */
str r1, [r0]
ldr r0, =0X020C407C /* CCGR5 */
str r1, [r0]
ldr r0, =0X020C4080 /* CCGR6 */
str r1, [r0]
/* 2、设置GPIO1_IO03复用为GPIO1_IO03 */
ldr r0, =0X020E0068 /* 将寄存器SW_MUX_GPIO1_IO03_BASE加载到r0中 */
ldr r1, =0X5 /* 设置寄存器SW_MUX_GPIO1_IO03_BASE的MUX_MODE为5 */
str r1,[r0]
/* 3、配置GPIO1_IO03的IO属性
*bit 16:0 HYS关闭
*bit [15:14]: 00 默认下拉
*bit [13]: 0 kepper功能
*bit [12]: 1 pull/keeper使能
*bit [11]: 0 关闭开路输出
*bit [7:6]: 10 速度100Mhz
*bit [5:3]: 110 R0/6驱动能力
*bit [0]: 0 低转换率
*/
ldr r0, =0X020E02F4 /*寄存器SW_PAD_GPIO1_IO03_BASE */
ldr r1, =0X10B0
str r1,[r0]
/* 4、设置GPIO1_IO03为输出 */
ldr r0, =0X0209C004 /*寄存器GPIO1_GDIR */
ldr r1, =0X0000008
str r1,[r0]
/* 5、打开LED0
* 设置GPIO1_IO03输出低电平
*/
ldr r0, =0X0209C000 /*寄存器GPIO1_DR */
ldr r1, =0
str r1,[r0]
/*
* 描述: loop死循环
*/
loop:
b loop
Makefile
led.bin:led.s
arm-linux-gnueabihf-gcc -g -c led.s -o led.o
arm-linux-gnueabihf-ld -Ttext 0X87800000 led.o -o led.elf
arm-linux-gnueabihf-objcopy -O binary -S -g led.elf led.bin
arm-linux-gnueabihf-objdump -D led.elf > led.dis
clean:
rm -rf *.o led.bin led.elf led.dis
烧写
ls /dev/sd*
./imxdownload led.bin /dev/sdb
# 生成一个 load.imx的文件, 是在led.bin文件前面添加了一些数据头以后生成的。
①、Image vector table,简称 IVT,IVT里面包含了一系列的地址信息,这些地址信息在
ROM中按照固定的地址存放着。
②、Boot data,启动数据,包含了镜像要拷贝到哪个地址,拷贝的大小是多少等等。
③、Device configuration data,简称 DCD,设备配置信息,重点是 DDR3的初始化配置。
④、用户代码可执行文件,比如 led.bin。
start.S
.global _start /* 全局标号 */
/* 全局标号设置C的运行环境 */
_start:
/* 进入SVC模式 */
mrs r0, cpsr
bic r0, r0, #0x1f /* 将r0的低五位清零,也就是cpsr的M0-M4 */
orr r0, r0, #0x13 /* r0或上0x13,表示使用SVC模式*/
msr cpsr, r0 /* 将r0的数据写入搭配cpsr_c中 */
ldr sp, =0x80200000 /*设置栈指针*/
b main /*跳转到main函数中*/
/* (1)I.MX6U-ALPHA开发板上的DDR3地址范围是0X80000000~0XA0000000(512MB)由于 Cortex-A7的堆栈是向下增长的,所以将 SP指针设置为 0X80200000,
因此 SVC模式的栈大小 0X80200000-0X80000000=0X200000=2MB,2MB的栈空间已经很大了,
如果做裸机开发的话绰绰有余
(2)为什么只需要切换到SVC模式下初始化SP指针,然后跳转到main函数就可以了呢?
这是因为在板卡LMX6U的Boot ROM会去读取DCD数据中的DDR配置参数来完成DDR初始化的。
*/
Makefile
objs := start.o main.o
# start.o一定要在main.o前面,因为链接的时候需要start.o
ledc.bin: $(objs)
arm-linux-gnueabihf-ld -Timx6ul.lds -o $^ ledc.elf
# arm-linux-gnueabihf-ld -Ttext 0X87800000 -o ledc.elf $^
# $^是所有依赖的文件 ,这里就是objs
# 等价于arm-linux-gnueabihf-ld -Ttext 0X87800000 -o ledc.elf start.o main.o
arm-linux-gnueabihf-objcopy -O binary -S ledc.elf $@
# $@是目标集合,这里指ledc.bin
# 等价于arm-linux-gnueabihf-objcopy -O binary -S ledc.elf ledc.bin
arm-linux-gnueabihf-objdump -D -m arm ledc.elf > ledc.dis
# 反汇编生成ledc.dis文件
%.o: %.s
arm-linux-gnueabihf-gcc -Wall -nostdlib -c -o $@ $<
# $<是指依赖的第一个文件,o是只编译不链接
# 等价于arm-linux-gnueabihf-gcc -Wall -nostdlib -c -o start.o start.s
%.o: %.S
arm-linux-gnueabihf-gcc -Wall -nostdlib -c -o $@ $<
%.o: %.c
arm-linux-gnueabihf-gcc -Wall -nostdlib -c -o $@ $<
clean:
rm -rf *.o ledc.bin ledc.elf ledc.dis
main.h
#ifndef __MAIN_H
#define __MAIN_H
// 时钟GPIO1_IO03相关寄存器地址定义
/*
* CMM相关寄存器地址
*/
#define CCM_CCGR0 *((volatile unsigned int *)0X020C4068)
#define CCM_CCGR1 *((volatile unsigned int *)0X020C406C)
#define CCM_CCGR2 *((volatile unsigned int *)0X020C4070)
#define CCM_CCGR3 *((volatile unsigned int *)0X020C4074)
#define CCM_CCGR4 *((volatile unsigned int *)0X020C4078)
#define CCM_CCGR5 *((volatile unsigned int *)0X020C407C)
#define CCM_CCGR6 *((volatile unsigned int *)0X020C4080)
/*
* IOMUX 相关寄存器地址
*/
#define SW_MUX_GPIO1_IO03 *((volatile unsigned int *)0X020E0068)
#define SW_PAD_GPIO1_IO03 *((volatile unsigned int *)0X020E02F4)
/*
* GPIO1相关寄存器地址
*/
#define GPIO1_DR *((volatile unsigned int *)0X0209C000)
#define GPIO1_GDIR *((volatile unsigned int *)0X0209C004)
#define GPIO1_PSR *((volatile unsigned int *)0X0209C008)
#define GPIO1_ICR1 *((volatile unsigned int *)0X0209C00C)
#define GPIO1_ICR2 *((volatile unsigned int *)0X0209C010)
#define GPIO1_IMR *((volatile unsigned int *)0X0209C014)
#define GPIO1_ISR *((volatile unsigned int *)0X0209C018)
#define GPIO1_EDGE_SEL *((volatile unsigned int *)0X0209C01C)
#endif
main.c
#include "main.h"
/*
* @desciption :使能I.MX6U所有外设时钟
* @param :无
* @return :无
*/
void clk_enable(void)
{
CCM_CCGR0 = 0xffffffff;
CCM_CCGR1 = 0xffffffff;
CCM_CCGR2 = 0xffffffff;
CCM_CCGR3 = 0xffffffff;
CCM_CCGR4 = 0xffffffff;
CCM_CCGR5 = 0xffffffff;
CCM_CCGR6 = 0xffffffff;
}
/*
* @desciption :初始化LED所对应的GPIO
* @param :无
* @return :无
*/
void led_init(void)
{
/* 1.初始化IO使用,复用为GPIO1_IO03,MODE为5对应GPIO功能 */
SW_MUX_GPIO1_IO03 = 0x5;
/* 2.配置GPIO1_IO03的IO属性
*bit 16:0 HYS关闭
*bit [15:14]: 00 默认下拉
*bit [13]: 0 kepper功能
*bit [12]: 1 pull/keeper使能
*bit [11]: 0 关闭开路输出
*bit [7:6]: 10 速度100Mhz
*bit [5:3]: 110 R0/6驱动能力
*bit [0]: 0 低转换率
*/
SW_PAD_GPIO1_IO03 = 0x10B0;
/* 3.初始化GPIO,GPIO1_IO03设置为输出 */
GPIO1_GDIR = 0x00000008;
/* 3.设置GPIO1_IO03输出为低电平,打开LED0 */
GPIO1_DR = 0x0;
}
/*
* @desciption :打开LED
* @param :无
* @return :无
*/
void led_on(void)
{
/*
*将GPIO1_DR的bit3清零
*/
GPIO1_DR &= ~(1<<3);
}
/*
* @desciption :关闭LED
* @param :无
* @return :无
*/
void led_off(void)
{
/*
*将GPIO1_DR的bit3置1
*/
GPIO1_DR |= (1<<3);
}
/*
* @desciption :短时间延时函数
* @param - n :要延时时循环次数(空操作循环次数,模式延时)
* @return :无
*/
void delay_short(volatile unsigned int n)
{
while(n--){}
}
/*
* @desciption :延时函数,在Boot ROM设置的396MHz的主频下延时时间大约为1ms
* @param - n :要延时的ms数
* @return :无
*/
void delay(volatile unsigned int n)
{
while(n--){
delay_short(0x7ff); // 0x7ff是2047,这个时候能够实现大约1ms的延时
}
}
/*
* @desciption :main函数
* @param - n :无
* @return :无
*/
int main()
{
clk_enable(); /*使能所有的时钟*/
led_init(); /*初始化led*/
while(1)
{
led_off(); /* 关闭LED */
delay(500);/* 延时大约500ms */
led_on(); /* 打开LED */
delay(500);/* 延时大约500ms */
}
return 0;
}
imx6ul.lds
注意段名和后面的冒号一定要有一个空格
SECTIONS{
. = 0X87800000; #起始地址
.text : {
start.o #第一个要执行的指令,要链接到最开始的位置
main.o
*(.text)
}
.rodata ALIGN(4): {*(.rodata*)}
.data ALIGN(4) : {*(.data) }
__bss_start = .;
#保存.bss段的起始地址和结束地址,.bss段是定义了但是没有初始化的变量,需要手动对.bss段进行清零,保存后可以直接在汇编或C文件里面使用这两个符号。
.bss ALIGN(4) : {*(.bss) *(COMMON) }
__bss_end = .;
}
烧录
chmod 777 imxdownload //给予 imxdownload可执行权限,一次即可
./imxdownload ledc.bin /dev/sdb //烧写到 SD卡中,不能烧写到/dev/sda或sda1设备里面
现象
闪烁的灯,1ms周期开关。、
可以借助 C语言里面的结构体成员地址递增的特点来将某个外设的所有寄存器写入到一个结构体里面,然后定义一个结构体指针指向这个外设的寄存器基地址,这样我们就可以通过这个结构体指针来访问这个外设的所有寄存器
// 1.定义一个结构体
typedef struct
{
volatile unsigned int BOOT_MODE0;
volatile unsigned int BOOT_MODE1;
volatile unsigned int SNVS_TAMPER0;
volatile unsigned int SNVS_TAMPER1;
volatile unsigned int CSI_DATA00;
volatile unsigned int CSI_DATA01;
volatile unsigned int CSI_DATA02;
volatile unsigned int CSI_DATA03;
volatile unsigned int CSI_DATA04;
volatile unsigned int CSI_DATA05;
volatile unsigned int CSI_DATA06;
volatile unsigned int CSI_DATA07;
volatile unsigned int RESERVED_3[1]; //加入保存寄存器,来消耗4个地址空间
}IOMUX_SW_MUX_Tpye;
// 2.定义寄存器基地址
#define IOMUX_SW_MUX_BASE (0X020E0014)
// 3.定义一个结构体指针指向寄存器基地址
#define IOMUX_SW_MUX ((IOMUX_SW_MUX_Type *)IOMUX_SW_MUX_BASE)
// 4.指针访问寄存器
IOMUX_SW_MUX->BOOT_MODE0 = 0x01;
IOMUXC_SetPinMux 设置IO复用功能 ,修改寄存器“IOMUXC_SW_MUX_CTL_PAD_XX”
static inline void IOMUXC_SetPinMux(
uint32_t muxRegister,
// muxRegister是IO复用寄存器地址,比如GPIO1_IO03 的 IO 复用寄存器SW_MUX_CTL_PAD_GPIO1_IO03的地址为 0X020E0068
uint32_t muxMode,
//IP复用值,也就是ALT0-ALT8,要将GPIO1_IO03设置为GPIO功能,需要设置为5
uint32_t inputRegister,
/* 外设输入IO选择寄存器,有些 IO在设置为其他的复用功能以后还需要设置IO输入寄存器,
* 比如 GPIO1_IO03要复用为 UART1_RX的话还需要设置寄存器UART1_RX_DATA_SELECT_INPUT,
* 此寄存器地址为 0X020E0624
*/
uint32_t inputDaisy,
//寄存器inputRegister的值,如果 GPIO1_IO03要作为 UART1_RX引脚的话此参数就是 1
uint32_t configRegister,
//未使用,函数IOMUXC_SetPinConfig将会使用这个寄存器
uint32_t inputOnfield)
/* IO软件输入使能,以 GPIO1_IO03 为例就是寄存器SW_MUX_CTL_PAD_GPIO1_IO03的SION位(bit4)。
* 如果需要使能 GPIO1_IO03的软件输入功能的话此参数应该为 1,否则的话就为 0
*/
{
*((volatile uint32_t *)muxRegister) =
IOMUXC_SW_MUX_CTL_PAD_MUX_MODE(muxMode) | IOMUXC_SW_MUX_CTL_PAD_SION(inputOnfield);
if (inputRegister)
{
*((volatile uint32_t *)inputRegister) = IOMUXC_SELECT_INPUT_DAISY(inputDaisy);
}
}
//设置GPIO1_IO03的IO复用功能为 GPIO1_IO03
#define IOMUXC_GPIO1_IO03_GPIO1_IO03 0x020E0068U, 0x5U, 0x00000000U, 0x0U, 0x020E02F4U
IOMUXC_SetPinMux(IOMUXC_GPIO1_IO03_GPIO1_IO03, 0); //这个0是inputOnfield,是IO软件输入使能
IOMUXC_SetPinConfig 设置IO的上下拉、速度等,修改寄存器“IOMUXC_SW_P AD_CTL_PAD_XX”
static inline void IOMUXC_SetPinConfig(uint32_t muxRegister,
uint32_t muxMode,
uint32_t inputRegister,
uint32_t inputDaisy,
uint32_t configRegister, //IO配置寄存器
uint32_t configValue) //写入寄存器configRegister的值
{
if (configRegister)
{
*((volatile uint32_t *)configRegister) = configValue;
}
}
#define IOMUXC_GPIO1_IO03_GPIO1_IO03 0x020E0068U, 0x5U, 0x00000000U, 0x0U, 0x020E02F4U
// 0x020E02F4U 是寄存器configRegister的数值,实际是IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03
IOMUXC_SetPinConfig(IOMUXC_GPIO1_IO03_GPIO1_IO03, 0X10B0);
//将configRegister的数值修改为0X10B0
Makefile
CROSS_COMPILE ?= arm-linux-gnueabihf-
NAME ?= ledc
CC := $(CROSS_COMPILE)gcc
LD := $(CROSS_COMPILE)ld
OBJCOPY := $(CROSS_COMPILE)objcopy
OBJDUMP := $(CROSS_COMPILE)objdump
OBJS := start.o main.o
$(NAME).bin: $(OBJS)
$(LD) -Timx6ul.lds -o $(NAME).elf $^
$(OBJCOPY) -O binary -S $(NAME).elf $@
$(OBJDUMP) -D -m arm $(NAME).elf > $(NAME).dis
%.o:%.s
$(CC) -Wall -nostdlib -c -O2 -o $@ $<
%.o:%.S
$(CC) -Wall -nostdlib -c -O2 -o $@ $<
%.o:%.c
$(CC) -Wall -nostdlib -c -O2 -o $@ $<
clean:
rm -rf *.o $(NAME).bin $(NAME).elf $(NAME).dis
bsp文件管理
bsp 用来存放驱动文件
imx6ul 用来存放跟芯片有关的文件,比如 NXP官方的SDK库文件
obj 用来存放编译生成的.o文件
project 存放start.S和main.c文件,也就是应用文件
文件树
yinliang@ubuntu:~/linux/driver/luoji/5_ledc_bsp$ tree
.
├── bsp
│ ├── clk
│ │ ├── bsp_clk.c
│ │ └── bsp_clk.h
│ ├── delay
│ │ ├── bsp_delay.c
│ │ └── bsp_delay.h
│ └── led
│ ├── bsp_led.c
│ └── bsp_led.h
├── imx6ul
│ ├── cc.h
│ ├── fsl_common.h
│ ├── fsl_iomuxc.h
│ ├── imx6ul.h
│ └── MCIMX6Y2.h
├── imx6ul.lds
├── Makefile
├── obj
└── project
├── main.c
└── start.S
Makefile
CROSS_COMPILE ?= arm-linux-gnueabihf-
TARGET ?= bsp #target是目标的名字
CC := $(CROSS_COMPILE)gcc
LD := $(CROSS_COMPILE)ld
OBJCOPY := $(CROSS_COMPILE)objcopy
OBJDUMP := $(CROSS_COMPILE)objdump
INCDIRS := imx6ul \ #INCDIRS包含整个工程的.h头文件目录 \表示换行符
bsp/clk \
bsp/led \
bsp/dleay \
SRCDIRS := project \ #SRCDIRS包含了整个工程的所有.c文件和.S文件
bsp/clk \
bsp/led \
bsp/delay \
INCLUDE := $(patsubst %, -I %, $(INCDIRS)) #给变量INCDIRS添加一个"-I"
SFILES := $(foreach dir, $(SRCDIRS), $(wildcard $(dir)/*.S)) #保存工程中的所有的.S汇编文件
CFILES := $(foreach dir, $(SRCDIRS), $(wildcard $(dir)/*.c)) #保存工程中的所有的.c文件
SFILENDIR := $(notdir $(SFILES)) #包含.S文件,不包括绝对路径
CFILENDIR := $(notdir $(CFILES)) #包含.c文件,不包括绝对路径
SOBJS := $(patsubst %, obj/%, $(SFILENDIR:.S=.o)) #编译后存放的.o文件
COBJS := $(patsubst %, obj/%, $(CFILENDIR:.c=.o))
OBJS := $(SOBJS) $(COBJS)
VPATH := $(SRCDIRS) #指定搜索目录
.PHONY: clean
$(TARGET).bin: $(OBJS)
$(LD) -Timx6ul.lds -o $(TARGET).elf $^
$(OBJCOPY) -O binary -S $(TARGET).elf $@
$(OBJDUMP) -D -m arm $(TARGET).elf > $(TARGET).dis
$(SOBJS): obj/%.o : %.S
$(CC) -Wall -nostdlib -c -O2 $(INCLUDE) -o $@ $<
$(COBJS): obj/%.o : %.c
$(CC) -Wall -nostdlib -c -O2 $(INCLUDE) -o $@ $<
clean:
rm -rf $(TARGET).bin $(TARGET).elf $(TARGET).dis $(COBJS) $(SOBJS)
有源振荡器 | 内部带有震荡源,只要通电就会叫 |
---|---|
无源振荡器 | 内部不带有震荡源,直流电无法驱动,需要2K-5K的方波去驱动 |
为什么不直接用I.MX6U的GPIO去驱动蜂鸣器工作,类似LED0一样,是因为蜂鸣器的工作电流比LED灯大,直接驱动会烧毁IO,需要通过一个三极管来间接的控制蜂鸣器的通断(供电电路),相当于加了一层隔离。
通过SNVS_TAMPER1这个IO来控制三极管Q1(PNP型的三极管)的导通,输出低电平的时候Q1导通,相当于蜂鸣器的正极连接到DCDC_3V3,正常工作。当输出高电平的时候,Q1不导通,没有形成一个通路,因此不会鸣叫。
查阅手册 《IMX6ULL参考手册 chapter4》
bsp/beep/bsp_beep.h
#ifndef __BSP_BEEP_H
#define __BSP_BEEP_H
#include "imx6ul.h"
void beep_init(void);
void beep_switch(int status);
#endif
bsp/beep/bsp_beep.c IOMUXC_SNVS_SNVS_TAMPER1_GPIO5_IO01 控制蜂鸣器
#include "bsp_beep.h"
/*
* @desciption :初始化蜂鸣器所对应的GPIO
* @param :无
* @return :无
*/
void beep_init(void)
{
/* 1.初始化IO使用,复用为GPIO5_IO01*/
IOMUXC_SetPinMux(IOMUXC_SNVS_SNVS_TAMPER1_GPIO5_IO01, 0);
/* 2.配置GPIO1_IO03的IO属性
*bit 16:0 HYS关闭
*bit [15:14]: 00 默认下拉
*bit [13]: 0 kepper功能
*bit [12]: 1 pull/keeper使能
*bit [11]: 0 关闭开路输出
*bit [7:6]: 10 速度100Mhz
*bit [5:3]: 110 R0/6驱动能力
*bit [0]: 0 低转换率
*/
IOMUXC_SetPinConfig(IOMUXC_SNVS_SNVS_TAMPER1_GPIO5_IO01, 0X10B0);
/* 3.初始化GPIO,GPIO5_IO01设置为输出 */
GPIO5->GDIR |= (1<<1);
/* 3.设置GPIO5_IO01输出为高电平,关闭蜂鸣器 */
GPIO5->DR |= (1<<1);
}
/*
* @desciption :蜂鸣器控制函数,控制蜂鸣器打开还是关闭
* @param -led :要控制的蜂鸣器灯编号
* @param -status : 0,关闭蜂鸣器;1,打开蜂鸣器
* @return :无
*/
void beep_switch(int status)
{
if(status == ON)
GPIO5->DR &= ~(1<<1); /*打开蜂鸣器*/
else if(status == OFF)
GPIO5->DR |= (1<<1); /*关闭蜂鸣器*/
}
Makefile
CROSS_COMPILE ?= arm-linux-gnueabihf-
TARGET ?= beep #输出文件的名字
CC := $(CROSS_COMPILE)gcc
LD := $(CROSS_COMPILE)ld
OBJCOPY := $(CROSS_COMPILE)objcopy
OBJDUMP := $(CROSS_COMPILE)objdump
INCDIRS := imx6ul \
bsp/clk \
bsp/led \
bsp/delay \
bsp/beep \ #添加新的bsp文件
SRCDIRS := project \
bsp/clk \
bsp/led \
bsp/delay \
bsp/beep \
INCLUDE := $(patsubst %, -I %, $(INCDIRS))
SFILES := $(foreach dir, $(SRCDIRS), $(wildcard $(dir)/*.S))
CFILES := $(foreach dir, $(SRCDIRS), $(wildcard $(dir)/*.c))
SFILENDIR := $(notdir $(SFILES))
CFILENDIR := $(notdir $(CFILES))
SOBJS := $(patsubst %, obj/%, $(SFILENDIR:.S=.o))
COBJS := $(patsubst %, obj/%, $(CFILENDIR:.c=.o))
OBJS := $(SOBJS) $(COBJS)
VPATH := $(SRCDIRS)
.PHONY: clean
$(TARGET).bin: $(OBJS)
$(LD) -Timx6ul.lds -o $(TARGET).elf $^
$(OBJCOPY) -O binary -S $(TARGET).elf $@
$(OBJDUMP) -D -m arm $(TARGET).elf > $(TARGET).dis
$(SOBJS): obj/%.o : %.S
$(CC) -Wall -nostdlib -c -O2 $(INCLUDE) -o $@ $<
$(COBJS): obj/%.o : %.c
$(CC) -Wall -nostdlib -c -O2 $(INCLUDE) -o $@ $<
clean:
rm -rf $(TARGET).bin $(TARGET).elf $(TARGET).dis $(COBJS) $(SOBJS)
按键按下或者弹起,将按键连接到一个IO口上,通过读取这个IO的数值就知道是按下还是弹起来的。开发板上面有一个KEY0,通过编写代码来控制开发板的蜂鸣器,按一下KEY0打开蜂鸣器,再按一下关闭蜂鸣器。
接了一个10K的上拉电阻,没有按下的时候UART1_CTS是高电平,按下后UART1_CTS为低电平。
https://www.eefocus.com/analog-power/454348
电源到器件引脚上的电阻叫上拉电阻,作用是平时使该引脚为高电平,地到器件引脚上的电阻叫下拉电阻,作用是平时使该引脚为低电平。低电平在IC内部与GND相连接;高电平在IC内部与超大电阻相连接。
上拉就是将不确定的信号通过一个电阻钳位在高电平,电阻同时起限流作用,下拉同理。
根据这张图,可以判断为UART1 CTS 的IO复用GPIO1_IO18。
运行正常的话LED0会以大约500ms周期闪烁,按下开发板的KEY0按键,蜂鸣器会被打开,再次按下蜂鸣器被关闭。LED0作为系统提示指示灯闪烁,闪烁周期大约为500ms。
bsp_gpio.h
#ifndef _BSP_GPIO_H
#define _BSP_GPIO_H
#include "imx6ul.h"
/* 枚举类型和结构体定义 */
typedef enum _gpio_pin_direction
{
kGPIO_DigitalInput = 0u, /* 输入 */
kGPIO_DigitalOutput = 1u, /* 输出 */
}gpio_pin_direction_t;
/* GPIO配置结构体 */
typedef struct _gpio_pin_config
{
gpio_pin_direction_t direction; /* GPIO方向:输入还是输出 */
uint8_t outputLogic; /* 如果是输出的话,默认输出电平*/
}gpio_pin_config_t;
/* 函数声明 */
void gpio_init(GPIO_Type *base, int pin, gpio_pin_config_t *config);
int gpio_pinread(GPIO_Type *base, int pin);
void gpio_pinwrite(GPIO_Type *base, int pin, int value);
#endif
bsp_gpio.c
#include "bsp_gpio.h"
void gpio_init(GPIO_Type *base, int pin, gpio_pin_config_t *config)
{
if(config->direction == kGPIO_DigitalInput) /*输入 */
{
base->GDIR &= ~(1<GDIR |= 1<outputLogic);
}
}
int gpio_pinread(GPIO_Type *base, int pin)
{
return ((base->DR)>>pin) & 0x1;
}
void gpio_pinwrite(GPIO_Type *base, int pin, int value)
{
if(value == 0U)
{
base->DR &= ~(1U << pin); /*输出低电平*/
}
else
{
base->DR |= (1U << pin); /*输出高电平*/
}
}
/*
base: 要初始化的 GPIO所属于的 GPIO组,比如 GPIO1_IO18就属于 GPIO1组。
pin:要初始化 GPIO在组内的标号,比如 GPIO1_IO18在组内的编号就是 18。
config: 要初始化的 GPIO配置结构体,用来指定 GPIO配置为输出还是输入。
*/
bsp_key.h
#ifndef _BSP_KEY_H
#define _BSP_KEY_H
#include "imx6ul.h"
/* 定义按键值 */
enum keyvalue{
KEY_NONE = 0,
KEY0_VALUE, //ALPHA开发板上面只有一个按键功能
};
/* 函数声明 */
void key_init(void);
int key_getvalue(void);
#endif
bsp_key.c
#include "bsp_key.h"
#include "bsp_gpio.h"
#include "bsp_delay.h"
void key_init(void)
{
gpio_pin_config_t key_config;
/* 1.初始化IO使用,设置UART1_CTS 复用为GPIO1_IO18 */
IOMUXC_SetPinMux(IOMUXC_UART1_CTS_B_GPIO1_IO18, 0);
/* 2.配置GPIO1_IO18的IO属性 这个IO速度为100MHz,默认22K上拉
*bit 16:0 HYS关闭
*bit [15:14]: 11默认22K上拉
*bit [13]: 1 pull功能
*bit [12]: 1 pull/keeper使能
*bit [11]: 0 关闭开路输出
*bit [7:6]: 10 速度100Mhz
*bit [5:3]: 000 关闭输出
*bit [0]: 0 低转换率
*/
IOMUXC_SetPinConfig(IOMUXC_UART1_CTS_B_GPIO1_IO18, 0XF080);
/* 3.初始化GPIO,GPIO1_IO03设置为输入 */
key_config.direction = kGPIO_DigitalInput;
gpio_init(GPIO1, 18, &key_config);
}
int key_getvalue(void)
{
int ret = 0;
static unsigned char release = 1; /* 按键松开 */
if((release == 1) && (gpio_pinread(GPIO1, 18) == 0)) /* KEY0按下 */
{
delay(10); /* 延时消抖10ms */
release = 0; /* 标记按键按下 */
if(gpio_pinread(GPIO1, 18) == 0) //按下是KEY0被按下
ret = KEY0_VALUE;
}
else if(gpio_pinread(GPIO1, 18) == 1) /* KEY0未按下 */
{
ret = 0; //返回值为0表示没有按键被按下来了
release = 1; /* 标记按键释放 */
}
return ret;
}
main.c
#include "bsp_clk.h"
#include "bsp_delay.h"
#include "bsp_led.h"
#include "bsp_beep.h"
#include "bsp_key.h"
int main(void)
{
int i = 0;
int keyvalue = 0;
unsigned char led_state = OFF;
unsigned char beep_state = OFF;
clk_enable(); /*使能所有的时钟*/
led_init(); /*初始化led*/
beep_init(); /*初始化beep*/
key_init(); /*初始化key*/
while(1)
{
keyvalue = key_getvalue();
if(keyvalue)
{
switch(keyvalue)
{
case KEY0_VALUE:
beep_state = !beep_state;
beep_switch(beep_state); //翻转beep的状态
break; //跳出switch语句
}
}
i++;
if(i == 50)
{
i = 0;
led_state = !led_state;
led_switch(LED0, led_state);
}
delay(10); //500ms,灯会被翻转一次
}
return 0;
}