编辑推荐: |
本文主要介绍了基于Linux的嵌入式软件开发相关内容。
希望对您的学习有所帮助。
本文来自于微信公众号知睿嵌入式,由火龙果软件Linda编辑、推荐。 |
|
前言
课程目标
了解硬件平台
开发板的主处理器
实验所涉及的硬件外设:按键和串口
配置开发环境
了解交叉编译器
安装ST官方提供的开发工具包(包含了交叉编译器)
入门实验
运行第一个ARM板载程序,步入 ARM-Linux 的大门
在Ubuntu的MiniCom程序中获取ARM开发板通过串口发送的printf数据
基本实验:按键
了解Linux input子系统
编写程序:ARM开发板读取按键状态并通过串口打印到Ubuntu上
进阶实验:串口通讯
了解Linux串口设备
编写程序
ARM开发板通过UART与上位机进行通信
系统框架
本次课程的实验部分所涉及到的系统框架包含:
1)搭建运行在 Ubuntu 中开发 ARM 系统的开发环境,安装 ST 官方提供的 SDK 软件开发包。
2)在 Ubuntu 中安装 minicom,此软件是调试 ARM-Linux 系统所必备的串口软件,用做
ARM-Linux 系统的串口控制台。
3)基于 SDK 开发C语言应用程序,并通过 SDK 中的交叉开发工具链生成 ARM-Linux
系统的可执行文件,将文件拷贝到 ARM-Linux 板卡上运行。
4)学习使用 Linux input 系统对 ARM-Linux 板卡上的按键进行编程操作。
5)学习使用 Linux UART系统对 ARM-Linux 板卡上的 UART 进行编程操作。
1. ARM处理器硬件开发平台介绍
1.1 ST ARM处理器简介
上图为ST的处理器产品的整体概括,其中最顶端的 MPU 系列是ST在2019年推出的Cortex-A7内核的处理器,一般搭配嵌入式
Linux 系统使用。我们的学习也是基于STM32MP1处理器的 ARM-Linux 系统。
其中STM32MP157是STM32MP1系列的典型产品。它结合了Cortex-A7和Cortex-M4两个内核,可同时运行实时操作系统和Linux操作系统,适用于广泛的应用领域。
STM32MP157具有以下主要特点:
处理器核心:STM32MP157包含一个Cortex-A7内核和一个Cortex-M4内核。Cortex-A7是一个高性能的应用处理器,可运行Linux等操作系统,用于执行复杂的应用任务。Cortex-M4是一个低功耗的实时处理器,适合处理实时控制任务。
丰富的外设:该平台提供了多达多个外设接口,包括UART、SPI、I2C、USB、以太网、CAN等,用于连接其他设备和传感器,实现灵活的数据交互和通信。
强大的图像和音频处理能力:STM32MP157配备了硬件加速器,支持2D/3D图像渲染和多种视频解码格式,如H.264、H.265等,以及音频解码和编码。这使得它适用于需要高质量图像和音频处理的应用,如嵌入式显示器和音频播放器。
安全和可靠性:该平台提供了硬件加密引擎和安全引导功能,以确保系统中的数据和代码的安全性。此外,它还支持可靠的供电管理和温度监测,以提高系统的稳定性和可靠性。
软件支持:STMicroelectronics为STM32MP157提供了丰富的软件生态系统,包括开发工具链、操作系统(如Linux和实时操作系统)、驱动程序和中间件。这些软件资源可以帮助开发人员快速构建和调试嵌入式应用程序。
延伸性:STM32MP157硬件平台具有丰富的扩展接口,可支持外部存储器、显示器、摄像头等外围设备的连接。这使得开发人员可以根据特定需求扩展系统功能。
1.2 ARM-Linux开发板简介
①:USB_TTL 是开发板的调试接口,主要用于调试与供电。
②:USB_OTG 是开发板的通信接口,主要用于通信和数据传输。
③:拨码开关,用于控制开发板的启动模式,默认为 EMMC 启动,即拨码开关的值为010。
④:按键实验需要用到的两个按键。
⑤:电源开关拨向ON即可为开发板供电。
⑥:电源接口,USB_TTL接口也可为开发板供电,PC端与USB_TTL连接后电源接口可不接。
⑦:串口实验用到的接口。
2. 配置开发环境
2.1 SDK:软件开发工具包
SDK(Software Development Kit)是一套软件开发工具包,旨在帮助开发者创建特定类型的应用程序、框架或平台。SDK通常由一组工具、库、示例代码、文档和其他支持材料组成,为开发者提供了在特定平台上进行软件开发的必要资源和工具。
我们使用的 SDK 是 ST 提供的 OpenSTLinux SDK。
2.1.1 ST OpenSTLinux SDK 的内容
OpenSTLinux SDK 基于标准的 Yocto 项目 SDK,由以下部分组成:
交叉开发工具链:此工具链包含编译器、链接器、调试器和各种杂项工具
库、标头和符号(目标和本机 sysroot):库、标头和符号特定于映像(即,它们与映像匹配)
环境设置脚本:此 *.sh 文件一旦运行,就会通过定义变量并准备 SDK 使用来设置交叉开发环境
2.1.2 交叉开发工具链
交叉开发工具链是一套用于在一种计算机体系结构(通常是一种处理器架构)上生成能在另一种计算机体系结构上运行的代码的工具集合。在嵌入式系统的开发中,通常会使用交叉开发工具链来编译、链接和调试针对目标嵌入式设备的应用程序和驱动程序。
一个典型的交叉开发工具链通常包括以下组件:
交叉编译器:用于将源代码编译为目标体系结构上的可执行代码的编译器。由于目标体系结构与开发主机的体系结构不同,因此需要使用交叉编译器来生成针对目标体系结构的代码。
交叉链接器:用于将编译后的目标文件链接成可执行文件或库文件的链接器。交叉链接器能够理解目标体系结构的可执行文件格式,并将生成的代码转换为适合在目标设备上执行的格式。
调试器:用于在目标设备上调试应用程序和驱动程序的工具。交叉调试器能够与目标设备上的调试接口进行通信,允许开发者在目标设备上进行源代码级的调试。
标准库和头文件:针对目标体系结构的标准C库和头文件。这些库和头文件使开发者能够在目标设备上访问标准的C库函数和系统调用。
使用交叉编译工具链编译程序的优点有:
跨平台开发:交叉编译工具链可以在一种架构上编译运行另一种架构的代码,这使得开发人员可以在自己的主机环境上进行开发,而无需直接在目标嵌入式系统上进行编译,从而提高了开发效率和便利性。
性能优化:交叉编译工具链可以针对目标嵌入式系统的特定架构和处理器进行优化,生成更高效的机器码,从而提高程序的性能和响应速度。
环境隔离:通过使用交叉编译工具链,开发人员可以在熟悉的开发环境中进行开发,而无需直接在目标嵌入式系统上进行编译。这种环境隔离可以降低开发过程中出现的不可预测因素,并且可以更好地管理开发环境。
节约资源:由于交叉编译工具链是在主机机器上运行的,因此可以充分利用主机机器的计算资源,加快编译速度,节约开发时间。
开发调试:使用交叉编译工具链可以将编译后的程序直接部署到目标嵌入式系统上进行调试和测试,从而简化了开发调试的流程。
总的来说,使用交叉编译工具链可以方便地进行跨平台开发,提高开发效率,并节约资源。它是在不同体系结构或操作系统之间开发和构建应用程序的重要工具。本实验我们需要在ubuntu中编译出能够在开发板上运行的可执行文件,因此需要使用交叉编译工具链来编译源程序。
2.1.3 SDK安装步骤
1. 主机配置
必须安装必要的 Ubuntu 软件包才能执行基本开发任务、基本交叉编译(通过开发人员包)或更复杂的交叉编译(如
OpenEmbedded(通过分发包),请在 Ubuntu 终端中依次运行以下命令:
sudo apt-get update
sudo apt-get install gawk
wget git diffstat unzip texinfo gcc-multilib
chrpath socat cpio python3 python3-pip python3-pexpect
sudo apt-get install libssl-dev
libgmp-dev libmpc-dev zstd
sudo apt-get install build-essential
libncurses-dev libyaml-dev libssl-dev
sudo apt-get install coreutils
bsdmainutils sed curl bc lrzsz corkscrew cvs
subversion mercurial nfs-common nfs-kernel-server
libarchive-zip-perl dos2unix texi2html libxml2-utils
echo 'options mmc_block perdev_minors=16'
> /tmp/mmc_block.conf
sudo mv /tmp/mmc_block.conf
/etc/modprobe.d/mmc_block.conf
|
第1-3行:安装OpenEmbedded/Yocto 所需的软件包。
第4行 :安装“开发人员包”用例所需的包。
第5行 :安装SDK相关的工具。
第6-7 :MMC配置。必须安装MMC配置才能支持每个 MMC 最多 16 个分区。默认情况下,在
Linux 系统上,MMC 上最多允许 8 个分区。所有软件包(入门软件包等)都需要 10 个以上的分区供存储设备使用。为了将每个设备的分区数扩展到
16,必须将6、7行两个选项添加到 modprobe。
2. 安装SDK
01. 下载并解压SDK的安装文件
安装文件名为:en.SDK-x86_64-stm32mp1-openstlinux-5.4-dunfell-mp1-20-06-24.tar,大小约800M。
ST官网下载地址:https://www.st.com/en/embedded-software/stm32mp1dev.html#get-software
下载好之后,在 Ubuntu 终端中依次执行如下操作:(如果已经有下载好的SDK安装文件,直接操作即可)
mkdir -p $HOME/STM32MPU_workspace/tmp
cd $HOME/STM32MPU_workspace/tmp
tar xvf en.SDK-x86_64-stm32mp1-openstlinux-5.4-dunfell-mp1-20-06-24.tar
cd stm32mp1-openstlinux-5.4-dunfell-mp1-20-06-24/sdk/
|
第1行 :创建一个临时文件夹 tmp,用来存放 SDK 的压缩包。
第2行:进入到临时文件夹 tmp 中。
第3行:解压SDK的压缩包获取SDK安装脚本。
第4行:进入到解压出来的文件夹中。
02. 运行SDK安装脚本
在 Ubuntu 终端中依次执行以下命令,安装叉编译工具链、图像文件、驱动程序以及相关工具
mkdir -p $HOME/STM32MPU_workspace/STM32MP1-Ecosystem-v5.0.0/Developer-Package/SDK
chmod +x st-image-weston-openstlinux-weston-stm32mp1-x86_64-toolchain-3.1-openstlinux-5.4-dunfell-mp1-20-06-24.sh
./st-image-weston-openstlinux-weston-stm32mp1-x86_64-toolchain-3.1-openstlinux-5.4-dunfell-mp1-20-06-24.sh
-d $HOME/STM32MPU_workspace/STM32MP1-Ecosystem-v5.0.0/Developer-Package/SDK
|
第1行:在主机上创建STM32MP1开发包SDK目录
第2行:更改SDK安装脚本权限,使其可执行
第3行:执行开发工具包安装脚本
安装成功后会输出以下目录
启动SDK
SDK 安装完成之后,在 Ubuntu 终端中,输入以下命令运行 SDK,从而配置ARM开发环境。注意:
每次打开新终端,都需要重新配置ARM开发环境,即打开SDK。
cd $HOME/STM32MPU_workspace/STM32MP1-Ecosystem-v5.0.0/Developer-Package
source SDK/environment-setup-cortexa7t2hf-neon-vfpv4-ostl-linux-gnueabi
|
检查SDK安装状态
SDK启动之后,可以通过以下四个命令检查 SDK 的状态是否正确:
检查目标体系结构
正确结果:arm
检查目标工具的工具链二进制前缀
正确结果:arm-ostl-linux-gnueabi-
检查c编译器版本菜单
正确结果:
检查SDK版本是否为预期版本
正确结果:
2.2 安装minicom
minicom是一个用于串行通信的开源程序,通常用于在Linux系统上进行串口通信。它提供了一个简单的终端仿真程序,可以用于连接到其他设备,如嵌入式系统、路由器、交换机等,通过串口进行控制和数据传输。
我们在 Ubuntu 上运行 minicom,通过 USB 模拟串口与ARM开发板进行通信,此时
minicom 作为 ARM开发板上运行的嵌入式Linux系统的终端,可以对板上的嵌入式Linux进行shell命令交互以及其他的命令操作。
minicom的安装与配置过程
安装命令:
sudo
apt-get install minicom |
配置minicom:
使用 sudo minicom -s 命令打开minicom的配置页面
选择 Serial port setup 选项
按下 A 键,将 Serial Device 的内容修改为 /dev/ttyUSB0
修改完成后按两下回车键,然后选择 Save steup as dfl,保存刚才修改的配置。
保存好后选择 Exit from Minicom 退出配置界面,之后用 sudo minicom
打开 minicom 即可。
2.3 开发板与PC端接线
参照 “1.2 ARM-Linux开发板简介” 中的图片,使用两根 Type-C 接口的线将 USB_TTL
和 USB_OTG 与 PC 端连接。
2.3.1 串口控制台
ARM-Linux开发板 上的USB_TTL接口用做开发板的串口控制台,因 ARM-Linux 系统通常不带键盘和显示器,而是通过开发板上特定的
UART 作为控制台:开发板Linux系统通过UART发送系统打印信息,并接收 UART 传过来的命令。
与 PC 端连接后,PC端的VMware的 虚拟机->可移动设备处会出现一个 QinHeng
USB Serial 设备,点击连接即可。连接成功后会在 Ubuntu 的 /dev 目录下出现一个
ttyUSB0 设备。这个设备主要用于开发板与 minicom 进行连接通信和调试。USB_TTL
与 PC 端连接好后,将电源开关拨到 ON 即可启动开发板。
ARM-Linux开发板将 UART 作为标准输入输出并与PC连接进行系统控制的过程如下图所示,这里面涉及到标准输入和标准输出的重定向的系统设定,在“知睿嵌入式”公众号文章中,
有进一步的详细描述。
2.3.2 USB虚拟网卡
ARM-Linux开发板 USB_OTG 接口会在 ARM-Linux开发板 上产生一个 USB虚拟网卡,可以用做与开发板与PC间进行文件传输等功能。
与 PC 端连接后,需要等开发板成功启动后才会在 VMWare 的 虚拟机->可移动设备
中出现一个Linux Foundation STM32MP1设备,点击连接即可。连接后开发板的usb0网卡的ip被设置成192.168.7.1,PC端会出现一个与开发板在同一个网段的网卡:
在 minicom 虚拟控制台中,运行 ifconfig 命令, 可以获取开发板的 IP 地址信息:
在PC Ubuntu 终端中,运行 ifconfig 命令,可以获取 Ubuntu 的 IP 地址信息:
此时可以看到,Ubuntu 与 开发板处于同一个网段中,此时两个系统可以进行局域网网络通讯。而下面就会马上使用
scp 命令利用局域网网络通讯进行两个系统之间的文件传输。
2.4 运行板载程序
在 Ubuntu 中编写一个hello word程序first_arm.c
#include <stdio.h>
int main(void)
{
printf("Hello, this
is my first ARM program\n");
return 0;
}
|
在 Ubuntu 中打开终端,运行以下命令启动SDK,配置arm的开发环境
source
$HOME/STM32MPU_workspace/STM32MP1-Ecosystem-v5.0.0/Developer-Package/SDK/environment-setup-cortexa7t2hf-neon-vfpv4-ostl-linux-gnueabi
|
在 Ubuntu 中使用交叉编译器编译first_arm.c,生成可执行文件first_arm
arm-linux-gcc
first_arm.c -o first_arm |
通过 USB-OTG 所连接的 USB 网卡,在 Ubuntu 终端中执行如下命令,将可执行文件拷贝到ARM开发板上
scp
first_arm root@192.168.7.1:/home/root/test |
scp命令:安全复制协议,是一个用于在网络间传输文件的命令行工具,它基于SSH协议进行加密传输,可以安全地在本地主机和远程主机之间复制文件和目录
first_arm:需要拷贝的文件名
root:开发板的用户名
192.168.7.1:开发板的ip地址,每台开发板可能不同,可以使用 ifconfig 查看
/home/root/test:表示要将文件拷贝到 ARM开发板 的哪个路径下
在 Ubuntu 通过 minicom 虚拟终端与 ARM开发板 的 USB-TTL 连接,并输入如下命令,在
ARM开发板 运行程序
cd /home/root/test
./first_arm
|
此时在 Ubuntu minicom 中,可以看到 ARM开发板 上所运行的 first_arm 程序的执行结果
3. 按键实验
在Linux中有这么一个概念:一切皆文件。"一切皆文件"是Linux系统中的重要概念,它是指在Linux操作系统中,几乎所有设备、目录和数据都被视为文件。这包括硬件设备、文本文件、目录、网络连接等等。这个概念是Linux设计哲学的核心之一,也是Linux与其他操作系统的一大区别。
当我们说“一切皆文件”时,我们是指Linux将所有事物都抽象成了文件的形式,这使得我们可以使用相同的接口和工具来处理它们。无论是读取文本文件、发送网络数据还是控制硬件设备,我们都可以使用类似的方式来访问和操作它们。普通文件我们通常使用open、read、write、close等函数来操作或访问,而这些抽象出来的文件我们也可以使用这几个函数来操作或访问。
当一个设备在内核注册后,系统就会出现其对应的文件。我们使用read函数读取这个文件的数据即可获取这个设备的状态。通常一个GPIO引脚对应一个设备文件,而GPIO引脚连接着按键,因此我们使用read函数读取这个设备文件的数据即可获取按键的状态。
3.1 按键输入原理
本实验学习输入设备的应用编程,首先要知道什么是输入设备?输入设备其实就是能够产生输入事件的 设备就称为输入设备,常见的输入设备包括鼠标、键盘、触摸屏、按钮等等,它们都能够产生输入事件,产生输入数据给计算机系统。
3.1.1 input子系统
由上面的介绍可知,输入设备种类非常多,每种设备上报的数据类型又不一样,那么 Linux 系统如何管理呢?Linux
系统为了统一管理这些输入设备,实现了一套能够兼容所有输入设备的框架,那么这个框架就 是 input
子系统。驱动开发人员基于 input 子系统开发输入设备的驱动程序,input 子系统可以屏蔽硬件的差异,向应用层提供一套统一的接口。MP157开发板上的KEY0和KEY1也属于input子系统。
基于 input 子系统注册成功的输入设备,都会在/dev/input 目录下生成对应的设备节点(设备文件),设
备节点名称通常为 eventX(X 表示一个数字编号 0、1、2、3 等),譬如/dev/input/event0、/dev/input/event1、
/dev/input/event2 等,当输入设备的输入状态发生变化时,输入设备会主动上报自己的事件的类型、具体事件以及状态发生了何种变化。通过读取这些设备节点可以获取输入设备上报的数据。
input子系统分为input驱动层、input核心层和input事件层,它们在计算机系统中起着不同的作用。以下是各个层的功能解释:
Input驱动层(Input Driver Layer):Input驱动层是指计算机系统中负责输入设备驱动程序的层级。其主要作用是与硬件设备进行交互,将输入设备(如键盘、鼠标、触摸屏等)传递的信号转换成计算机可以理解的数据格式,并将其传递给上层的核心层。驱动程序通常需要和操作系统紧密配合,以确保设备的正确工作。
Input核心层(Input Core Layer):Input核心层是指计算机系统中处理输入数据的中间层。它接收来自驱动层的输入数据,并根据需要将其分发给相应的应用程序或模块进行进一步处理。该层的主要功能是为驱动层提供输入设备的注册和操作接口,通知事件层对输入事件进行处理。
Input事件层(Input Event Layer):Input事件层是指计算机系统中处理和管理输入事件的层级。它负责处理用户输入产生的事件,如鼠标点击、键盘按键、触摸事件等。该层的主要作用是将输入事件传递给相应的应用程序,并提供事件处理机制,实现用户与计算机系统的交互。通常,这一层还会提供事件队列、事件监听和事件分发等功能,以确保输入事件能够被准确地响应和处理。
下面为按键与input子系统的关系:
3.1.2 读取数据的流程
按键设备对应的设备节点为 /dev/input/event0,数据读取流程如下:
应用程序打开 /dev/input/event0 设备文件;
应用程序发起读操作(譬如调用 read),如果没有数据可读则会进入休眠(阻塞 I/O 情况下);
当有数据可读时,应用程序会被唤醒,读操作获取到数据返回;
应用程序对读取到的数据进行解析。
3.1.3 解析数据
首先我们要知道,应用程序打开输入设备对应的设备文件,向其发起读操作,那么这个读操作获取到的是什么样的数据呢?对于input子系统下的设备,每一次
read 操作获取的都是一个 struct input_event 结构体类型数据,该结构体定义在头文件中,它的定义如下:
结构体中的 time 成员变量是一个 struct timeval 类型的变量,用来表示时间值,内核会记录每个上报的事件发生的时间,并通过
time 返回给应用程序。时间参数通常不是那么重要,而其它 3 个成员变量 type、code、value
更为重要。
type:type 用于描述发生了哪一种类型的事件(对事件的分类),Linux 系统所支持的输入事件类型如下所示:
以上这些宏定义也是在头文件中,所以在应用程序中需要包含该头文件;一种输入设备 通常可以产生多种不同类型的事件,譬如点击鼠标按键(左键、右键,或鼠标上的其它按键)时会上报按键
类事件,移动鼠标时则会上报相对位移类事件。
code:code 表示该类事件中的哪一个具体事件,以上列举的每一种事件类型中都包含了一系列具 体事件,譬如一个键盘上通常有很多按键,譬如字母
A、B、C、D 或者数字 1、2、3、4 等,而 code 变量则告知应用程序是哪一个按键发生了输入事件。每一种事件类型都包含多种不同的事件,譬如
按键类事件:
由上图可知,code=114对应的是键盘上的KEY_VOLUMEDOWN按键,code=115对应的是键盘上的KEY_VOLUMEUP,这个是
MP157 开发板出厂系统已经配置好的,并且MP157在出厂系统中将KEY0与KEY_VOLUMEDOWN、KEY1与KEY_VOLUMEUP进行了绑定,当按键KEY0按下时即表示KEY_VOLUMEDOWN被按下,在应用程序中可读到code=114的事件数据。
value:内核每次上报事件都会向应用层发送一个数据 value,对 value 值的解释随着 code
的变化而 变化。对于按键事件(type=1)来说,如果 code=115(也就是 KEY1),那么如果
value 等于 1,则表示 KEY1 键按下;value 等于 0 表示 KEY1 键松开,如果
value 等于2则表示 KEY1 键长按。
3.2 硬件
3.2.1 芯片数据手册
STM32MP157是一款集成了Cortex-A7和Cortex-M4内核的SoC,其GPIO控制器包含多个IO端口,可以通过不同的寄存器进行控制。STM32MP157的GPIO控制器具有以下特点:
GPIO端口数量:STM32MP157具有多个GPIO端口,其中每个GPIO端口通常包括8个或16个GPIO引脚。这些引脚通过GPIO控制器的寄存器进行控制。
引脚编号:STM32MP157的GPIO引脚使用两个标识符进行编号:一个ID号和一个名称。ID标识符是硬件编号,每个ID都对应特定的引脚;而名称标识符则是软件定义的名称,用于在代码中引用引脚。通常,名称标识符更易于记忆和使用。
GPIO模式:每个GPIO端口可配置为输入模式(读取外部信号)或输出模式(向外部发送信号)。此外,可以设置一些其他属性,如上升/下降沿触发中断、开漏/推挽输出等。
GPIO状态:对于输出模式,可以使用相应的寄存器将GPIO引脚设置为高电平或低电平值。对于输入模式,则可以读取相应寄存器中的引脚状态值。
中断:可以通过使能和配置相应的中断寄存器来启用GPIO端口的中断功能。在输入模式下,可以设置中断沿(上升/下降)触发。
在STM32MP157中,每个GPIO端口的寄存器集合包括控制寄存器(CR)、状态寄存器(SR)、数据寄存器(ODR),以及中断配置和状态寄存器(ICR、ISR)。这些寄存器的详细说明可以在ST的文档中找到。
总之,STM32MP157的GPIO控制器是一种功能强大的IO设备,可以通过简单的寄存器访问方法对多个GPIO引脚进行操作。它提供了多种GPIO模式、中断功能和高低电平控制等功能,适用于各种应用场景。
下图为一个GPIO引脚的内部结构图,其内部有双向保护二极管,有可配置的是否使用的上拉和下拉电阻。
接下来我们介绍一下GPIO引脚的几种工作模式:
输入模式(Input Mode):引脚作为输入端,用于读取外部信号。在输入模式下,可以使用内部上拉或下拉电阻使引脚保持稳定的电平状态。
输出模式(Output Mode):引脚作为输出端,通过控制输出电平向外部设备发送信号。输出模式可以配置为推挽输出、开漏输出或者复用推挽/开漏输出。
复用模式(Alternate Function Mode):引脚既可以作为普通的GPIO引脚,也可以配置为特定功能的引脚,如UART、SPI、I2C等外设的引脚。在这种模式下,引脚具有相应外设的功能和特性。
模拟模式(Analog Mode):引脚以模拟方式工作,用于连接模拟传感器或其他模拟设备。在模拟模式下,引脚不具备数字IO功能。
中断模式(Interrupt Mode):引脚在输入模式下配置中断触发方式,当特定条件满足时,可以生成中断请求,通知处理器进行相应的中断处理。
在本实验中,按键所在的引脚已经被配置成了输入模式,不需要我们再次配置,直接读取数据就行。
3.2.2 按键电路原理图
从上述原理图中可以看出 KEY0 接到了开发板 PG3 这个IO口上。KEY0 接了一个 10K
的上拉电阻,因此 KEY0 没有按下的时候 PG3 应该是高电平,当 KEY0 按下以后 PG3 就是低电平。系统会根据按键所在引脚的电平进行判断,如果为从高电平变为低电平即为按下,如果从低电平变为高电平即为松开,如果一直为低电平即为长按。
3.3 软件
3.3.1 函数介绍及程序编写步骤:
调用open函数打开对应的按键的设备节点;
// 指定按键设备文件
const char *file = "/dev/input/event0";
/* 打开文件 */
fd = open(file, O_RDONLY);
if (0 > fd){
perror("open error");
exit(-1);
}
|
在while循环中调用read()函数读取文件,讲读取到的数据存放在struct input_event结构体对象中;当按键按下或松开(以及长按)动作发生时,read()
会读取到输入设备上报的数据;
/* 循环读取数据 */
int ret = read(fd, &in_ev,
sizeof(struct input_event));
if (sizeof(struct input_event)
!= ret)
{
perror("read error");
exit(-1);
}
|
解析读取到的数据,判断事件的类型和值;首先 type 值判断此次上报的事件是否是按键类事件(EV_KEY),如果是按键类事件则根据
code 值判断是哪一个按键,接着根据 value 值来判断按键当前的状态是松开、按下还是长按。
if (EV_KEY == in_ev.type)
{
printf("type = %d: 按键事件\n",in_ev.type);
// 按键事件
if (in_ev.code == 114) //
KEY0
{
printf("code = %d: KEY0\n",in_ev.code);
led_ds1_ctrl("on");//打开LED灯
}
else if (in_ev.code == 115)
// KEY1
{
printf("code = %d: KEY1\n",in_ev.code);
led_ds1_ctrl("off");//关闭LED灯
}
switch (in_ev.value)//判断按键状态
{
case 0:
printf("value = %d:
松开\n\n", in_ev.value);//按键松开
break;
case 1:
printf("value = %d:
按下\n\n", in_ev.value);//按键按下
break;
case 2:
printf("value = %d:
长按\n\n", in_ev.value);//按键长按
break;
}
}
|
3.3.2 完整代码:
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <linux/input.h>
extern int led_ds1_ctrl(const
char *cmd);
int main(int argc, char *argv[])
{
struct input_event in_ev
= {0};
int fd = -1;
int value = -1;
// 指定按键设备文件
const char *file = "/dev/input/event0";
/* 打开文件 */
fd = open(file, O_RDONLY);
if (0 > fd)
{
perror("open error");
exit(-1);
}
while (1)
{
/* 循环读取数据 */
int ret = read(fd, &in_ev,
sizeof(struct input_event));
if (sizeof(struct input_event)
!= ret)
{
perror("read error");
exit(-1);
}
if (EV_KEY == in_ev.type)
{
printf("type = %d: 按键事件\n",in_ev.type);
// 按键事件
if (in_ev.code == 114) //
KEY0
{
printf("code = %d: KEY0\n",in_ev.code);
led_ds1_ctrl("on");//打开LED灯
}
else if (in_ev.code == 115)
// KEY1
{
printf("code = %d: KEY1\n",in_ev.code);
led_ds1_ctrl("off");//关闭LED灯
}
switch (in_ev.value)//判断按键状态
{
case 0:
printf("value = %d:
松开\n\n", in_ev.value);//按键松开
break;
case 1:
printf("value = %d:
按下\n\n", in_ev.value);//按键按下
break;
case 2:
printf("value = %d:
长按\n\n", in_ev.value);//按键长按
break;
}
}
}
close(fd);
return 0;
}
|
3.3.3 运行步骤及运行效果
1. 运行步骤
01. 在 ubuntu 的终端中打开交叉编译器
source /home/linux/STM32MPU_workspace/STM32MP1-Ecosystem-v5.0.0/Developer-Package/SDK/environment-setup-cortexa7t2hf-neon-vfpv4-ostl-linux-gnueabi
|
02. 在 ubuntu 中编译代码
arm-linux-gcc
read_key.c -o read_key |
03. 在 ubuntu 中将编译生成的可执行文件拷贝到开发板上
scp
read_key root@192.168.7.1:/home/root |
root 表示开发板的用户名
192.168.7.1 为开发板的ip地址
/home/root 表示要拷贝到开发板的哪个路径下
第一次在自己的Ubuntu上使用的开发板在使用scp命令命令时可能会在ubuntu的终端上弹出一个选项,输入yes并且按下回车即可。
2. 运行效果
运行程序之后,我们可以进行相应的操作:(press)按下 KEY0、(release)松开 KEY0
或者按下 KEY1、松开 KEY1 以及(long press)长按按键等操作,终端会打印出相应的信息,如上图所示。code=114
表示 KEY0 按键对应的状态,code=115 表示 KEY1 按键对应的状态。
4. 串口实验
4.1 UART原理
串口是一种用于在电脑和外部设备之间进行数据通信的常见接口。串口全称叫做串行接口,串行接口指的是数据一位一位的按顺序传输,通信线路简单。使用两条线即可实现双向通信,一条用于发送,一条用于接收。串口通信距离远,但是速度相对会低,串口是一种很常用的工业接口。下面是关于串口的基础知识和通信原理的简要介绍:
4.1.1 串口硬件:
串口由发送器(Transmitter)和接收器(Receiver)组成,它们分别负责将数据发送到外部设备和从外部设备接收数据。
串口通常使用多个引脚来传输数据,包括数据线(TX、RX),控制线(例如RTS、CTS、DTR等)和地线(GND)。
常见的串口物理层接口包括TTL、RS-232和RS-485等。
4.1.2 串口通信原理:
串口通信使用一种称为异步串行通信的方式,其中数据被分割成位并按顺序传输。
在发送数据之前,发送器会发送起始位(Start Bit),表示数据传输开始。然后按照一定的速率(波特率)传输数据位(Data
Bits)。
数据位传输结束后,发送器发送一个或多个停止位(Stop Bit),表示数据传输结束。
接收器在接收数据时,会根据波特率和接收时钟同步,并逐位接收数据。
4.1.3 波特率(Baud Rate):
波特率是衡量串口通信速度的参数,它表示每秒传输的比特数。
常见的波特率包括9600、19200、38400等。比如9600表示每秒传输9600个比特。
发送方和接收方需要使用相同的波特率以确保数据能够正确地传输。
4.1.4 数据格式:
除了波特率外,串口通信还涉及数据位(Data Bits)、校验位(Parity Bit)和停止位(Stop
Bits)等数据格式参数。
数据位指定每个字节中的有效数据位数,通常为8位。
校验位用于数据的完整性检验,可以检测并纠正传输错误。常见的校验方式包括奇校验、偶校验和无校验。
停止位表示数据传输结束的标志,常见的停止位数为1位或2位。
UART并未定义具体的电平标准,它主要定义了数据传输格式和通信协议。在实际应用中,UART可以搭配不同的电平标准来进行串口通信。常见的电平标准包括TTL、RS-232和RS-485等。下面是关于这三种电平标准的介绍:
01. TTL(晶体管-晶体管逻辑):TTL指的是一种常见的数字电平标准,它使用0V表示逻辑低电平(0),使用约3.3V至5V表示逻辑高电平(1)。在串口通信中,TTL通常用于描述使用TTL电平标准进行通信的电路。在上述三种电平信号中,开发板能直接识别的只有TTL电平信号,另外两种需要使用转换器将信号转换成TTL电平信号才能被识别。
优点:TTL电平标准电压范围广泛,适用于大多数现代逻辑电路和微控制器。它具有简单、低成本、易于实现的优点。
缺点:TTL电平的传输距离较短,一般在几米范围内有效,容易受到干扰。
02. RS-232:RS-232是一种串口通信标准,它规定了数据通信时使用的电气特性和协议。RS-232使用正负12V的电平表示逻辑1和逻辑0。
优点:RS-232电平传输距离相对较远,可达数十米。它具有较高的抗干扰性和稳定性。
缺点:RS-232需要较多的引脚和外围电路支持,成本较高。同时,RS-232通信速率较低,一般不超过115.2kbps。
03. RS-485:RS-485也是一种串口通信标准,它与RS-232类似,但具备更强的抗干扰能力和支持多点通信的能力。
优点:RS-485支持多点通信,可以在同一总线上连接多个设备。它具有较高的传输距离,可达数千米,并且具备很好的抗干扰性能。
缺点:RS-485需要额外的驱动器和收发器芯片来实现通信,需要一定的配置和调试。
综合来说,UART是通信接口电路的概念,而TTL、RS-232和RS-485则是串口通信中涉及电平标准和协议的概念。TTL适用于短距离通信,成本低;RS-232适用于中距离通信,具备较高的抗干扰性;RS-485适用于长距离通信和多点通信,具备出色的抗干扰性能。选择合适的标准应根据通信距离、设备数量、成本以及抗干扰要求来决定。
4.2 硬件
4.2.1 芯片数据手册
UART是通用异步收发器(Universal Asynchronous Receiver/Transmitter)的缩写,它是一种常见的串行通信接口。它常用于将数据以串行方式传输,并能够连接到各种外部设备,如传感器、无线模块、显示屏等。下图为UART在芯片内部的原理图:
UART硬件通常由以下几个主要组件组成:
串行收发器:负责将并行数据转换为串行数据,并将串行数据转换为并行数据。它提供了数据的物理层传输。
波特率发生器:用于产生通信的时钟信号,控制数据的传输速率。波特率是指每秒传输的比特数,常见的波特率包括9600、115200等。
数据线:用于传输数据位的信号。通常有一个线路用于发送数据(TX),一个线路用于接收数据(RX)。
控制线:用于传输控制信号,如起始位、停止位、校验位等。
缓冲区:用于暂存发送和接收的数据。发送数据时,数据会先存储在发送缓冲区中,然后逐位发送出去。接收数据时,数据会存储在接收缓冲区中,等待外部设备读取。
4.2.2 串口电路原理图
SP3232是一款常用的RS-232到TTL/CMOS电平转换芯片,SP3232可以将来自计算机或其他RS-232设备的信号转换为逻辑电平,以供微控制器等设备进行数据接收和处理。同时,它也能将微控制器产生的逻辑电平信号转换为符合RS-232标准的电平,用于与计算机或其他RS-232设备进行通信。
USART3 TX和USART3 RX对应开发板上串口3的收发接口,USART5 TX和USART5
RX对应开发板上串口5的收发接口,它们都需要接外部的TTL电平信号。如果外设使用的是RS232电平标准,则需要将外设连接到SP3232芯片上,将其电平转化为TTL电平,再将转化完成的信号接到USART3
TX和USART3 RX上或者USART5 TX和USART5 RX。
4.3 软件
uart.c 里面是封装好的中间件,里面封装了串口初始化函数uart_init,用于初始化必要的串口参数。在
uart.h 中定义了一个 uart_info 的结构体,用于放置 uart 有关的参数。
4.3.1 函数介绍及程序编写步骤
01. 调用串口初始化函数,函数内部调用了open以及设置波特率、数据位、校验位、停止位和校验位的函数;
int uart_init(struct uart_info info);
功能:初始化串口
参数:info 串口初始化相关信息
uart_info结构体:
struct uart_info
{
char *device; //设备名字
int speed; //波特率,常见的波特率有9600、115200等
int databits; //数据位长度,常见的数据位数有7位和8位
int stopbits; //停止位长度,常见的停止位长度有1位和2位
int parity; //校验位,常见的校验方式有奇校验(O)、偶校验(E)和无校验(N)
};
|
返回值:成功返回int型文件描述符,失败返回-1
struct uart_info ttySTM1 =
{"/dev/ttySTM2", 9600, 8, 1, 'N'};
int fd = uart_init(ttySTM1);
if (fd < 0)
{
printf("Error opening
%s: %s\n", ttySTM1.device, strerror(errno));
return -1;
}
|
02. write函数发送串口信息或read函数读取串口信息
while (1)
{
char sendbuf[] = "abcdefg";
sprintf(xmit,"send from
%s : %s\r\n", ttySTM1.device, sendbuf
);
write(fd, xmit, strlen(xmit));
sleep(1);
nread = read(fd, buff, sizeof(buff));
if (nread > 0)
{
buff[nread] = '\0';
printf("%s RECV %d total\n",
ttySTM1.device, nread);
printf("%s RECV: %s\n",
ttySTM1.device, buff);
}
}
|
03. 使用close关闭串口设备文件
4.3.2 完整代码:
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include "uart.h"
int main(int argc, char *argv[])
{
int nread; /* Read the counts
of data */
char buff[512]; /* Recvice
data buffer */
char xmit[100];
struct uart_info ttySTM1
= {"/dev/ttySTM2", 9600, 8, 1, 'N'};
int fd = uart_init(ttySTM1);
if (fd < 0)
{
printf("Error opening
%s: %s\n", ttySTM1.device, strerror(errno));
return -1;
}
while (1)
{
char sendbuf[] = "abcdefg";
sprintf(xmit,"send from
%s : %s\r\n", ttySTM1.device, sendbuf
);
write(fd, xmit, strlen(xmit));
sleep(1);
nread = read(fd, buff, sizeof(buff));
if (nread > 0)
{
buff[nread] = '\0';
printf("%s RECV %d total\n",
ttySTM1.device, nread);
printf("%s RECV: %s\n",
ttySTM1.device, buff);
}
}
close(fd);
return 0;
}
|
4.3.3 运行步骤及运行效果
01. 运行步骤
a. 在 ubuntu 的终端中打开交叉编环境
source /home/linux/STM32MPU_workspace/STM32MP1-Ecosystem-v5.0.0/Developer-Package/SDK/environment-setup-cortexa7t2hf-neon-vfpv4-ostl-linux-gnueabi
|
b. 在 ubuntu 中使用交叉编译工具链编译代码
arm-linux-gcc
uart_test.c uart.c -o uart_test |
c. 在 ubuntu 中将编译生成的可执行文件拷贝到开发板上
scp
uart_test root@192.168.7.1:/home/root |
root表示开发板的用户名
192.168.7.1 为开发板的ip地址
/home/root 表示要拷贝到开发板的哪个路径下
第一次在自己的Ubuntu上使用的开发板在使用scp命令命令时可能会在ubuntu的终端上弹出一个选项,输入yes并且按下回车即可。
d. 在开发板上运行程序
$> ./uart_test
e. 在 windows 中打开串口调试助手,选择对应的COM串口,并且设置与开发板一致的波特率,停止位,数据位以及校验位。之后点击打开串口即可通信。
02. 运行效果
5. 实验要求
5.1 安装交叉编译器
安装完成后打开交叉编译器,并使用下列命令验证是否正确安装
检查目标体系结构
$> echo $ARCH 目标输出:arm
检查目标工具的工具链二进制前缀
$> echo $CROSS_COMPILE 目标输出:arm-ostl-linux-gnueabi-
检查c编译器版本菜单
$> $CC –version
目标输出:
检查SDK版本是否为预期版本
$> echo $OECORE_SDK_VERSION
目标输出:
5.2 按键实验
提供DS0和DS1两个LED灯的控制函数,使用两个按键控制两个灯的亮灭,KEY0控制DS0,KEY1控制DS1,按键按下灯亮,按键松开灯灭。
5.3 串口通信实验
实现开发板与pc端通过串口通信,开发板每隔1s向pc端发送一段数据,并且在pc端的XCOM串口调试助手上打印出来
|