orangepi _全志H616

1. 全志H616简介

1.1. 为什么学:

学习目标依然是Linux系统,平台是ARM架构

  • 蜂巢快递柜,配送机器人,这些应用场景用C51,STM32单片机无法实现 (UI界面,提高用户的体验感)
  • 第三方介入库的局限性,比如刷脸支付和公交车收费设备需要集成支付宝SDK,提供的libalipay.so
  • 是Linux的库,设备必须跑Linux系统
  • 图像识别,音频,视频等领域的技术支撑也无法脱离Linux系统
  • 人工智能型设备通常需要更好的系统和更高的算力,所以Linux也是必不可少
  • 能跑Linux的一般为 X86,ARM,MIPS,PowerPC等架构,而ARM市场占有率最大

综上所述就是一句话:嵌入式软件工程师如果技术栈不存在Linux-ARM的开发经验,那么面向的工作岗位就会带很多局限性,天花板有容易来的过早,在技术积累阶段对于这个知识的学习是必不可少的,但是这个方向水深,需要客观且科学的选择适合的角度学习。

1.2. 学什么:

这个领域的程序员一般分三个方向:

  • 应用开发,通过跟产品业务相关,比如智能家居中控板,可以是C++QT, 可以是C GTK, 也可以是Android页面,也可以是基于串口屏的UI交互,后台数据交互和系统交互都是基于Linux系统的,初级工程师以这个方向入行居多,也容易上手,招聘岗位也很多
  • 系统开发,主要任务是为硬件工程师设计的产品板操作系统,比如uboot,Linux内核,文件系统等,一般为中高级嵌入式工程师,新手如果以这个方向入行,压力相对更大,一般原厂公司会招聘,岗位相对少
  • 算法工程师,此类算法跟数学模型挂钩,比如人脸识别的图像不调库处理,语音识别算法如讯飞语音的工程师,一般博士一大堆,硕士满天飞的现状

根据现有就业案例,大专本科生以应用开发入行为主,在工作一两年可能会根据公司安排走系统开发,也可能一直做应用,薪资待遇并不完全由技术方向决定,还是看个人发展和公司的关系,当然还有城市,学校等因素。

1.3. 全志H616平台介绍:
  • 学习平台至于用树莓派,海思,全志都无所谓,初级工程师掌握的是Linux-ARM的软硬件架构开发,主要是Linux系统的学习,只有入职后的中高级工程师才会考虑算法或者协议对底层硬件的差异化,第一版本这部分的内容以树莓派讲解,就业学员入职海康威视,OPPO,全志,移远等公司可以完美过度,所以板材的选择根据教程就行,学的是Linux系统
  • OrangePi(香橙派)开发板不仅仅是一款消费品,同时也是给任何想用技术来进行创作创新的人设计的。它是一款简单、有趣、实用的工具,你可以用它去打造你身边的世界。

特性:

  • CPU 全志H616四核64位1.5GHz高性能Cortex-A53处理器 (51、32单片机通常是8位或32位处理器)
  • GPU MaliG31MP2 SupportsOpenGLES1.0/2.0/3.2、OpenCL2.0(可以图像处理,流媒体、视频处理有优点)
  • 运行内存 1GBDDR3(与GPU共享)
  • 存储 TF卡插槽_课程配套硬件16G,测试128G可支持、2MBSPIFlash
  • WIFI+蓝牙 AW859A芯片、支持IEEE802.11a/b/g/n/ac、BT5.0 (51、32通常需要额外的模块来实现网络功能)
  • 视频输出 MicroHDMI20a
  • 电源 USBTypeC接口输入
  • 外设带有I2Cx1、SPIx1、UARTx1以及多个GPIO口
  • 电源指示灯和状态指示灯

配套操作系统支持:

2. 刷机 系统启动和初始化配置

就像买了电脑,出厂带有windows操作系统,才算是正在的电脑,开发板需要烧写对应的系统固件,才

能正常发挥作用

2.1. 工具
  • Orangepi Zero2 全志H616开发板
  • PC机
  • TF卡及读卡器
  • 操作系统镜像
  • SDFormatter TF卡的格式化工具
  • Win32Diskimager 刷机工具
  • USB转TTL,用于系统烧写后的串口登录开发板
2.2. 工具安装
  • SDFormatter傻瓜式安装,

  • Win32Diskimager傻瓜式安装

2.3. 刷机

课程使用的镜像是 Orangepizero2_2.2.0_ubuntu_bionic_desktop_linux4.9.170.img

打开win32diskimager并选择映像文件,选择写入的盘(一定是刚刚格式化的那个),然后点击“写入”:

将SD卡取出,插入全志H6开发板:

2.4. 登录系统

供电

TypeC口,需要插到5V/2A或者5V/3A的电源适配头,特别是开发板有接多个外设模块的时候

平常USB供电用电脑可以,前提是不接多外设模块

后面做小车等项目,用电池供电可以参考如下供电方式

登录

使用USB转TTL模块,使用MobaXterm免费好用,类似的工具还有Putty-相对太简陋,SecurityCRT老牌

工具-需要付费或者破解

USB转TTL模块GND、TX和RX引脚需要通过杜邦线连接到开发板的调试串口上

  • a.USB转TTL模块的GND接到开发板的GND上
  • b.USB转TTL模块的RX接到开发板的TX上
  • c.USB转TTL模块的TX接到开发板的RX上

电脑安装ch340驱动,学到这个阶段,你们电脑应该都装好了,没装的话自己去网盘资料下载安装

使用mobaXterm登陆,默认登陆密码:

用户:orangepi 密码:orangepi

用户:root 密码:orangepi

板载LED灯测试说明

2.5. 修改登陆密码

默认密码是orangepi容易写错,为了课程方便,我改成密码为1

2.6. 网络配置

命令扫描周围的WIFI热点 nmcli dev wifi

命令接入网络 nmcli dev wifi connect TP-LINK_3E30 password 18650711783

查看IP地址 ip addr show wlan0

ifconfig也可以

2.7. SSH登陆开发板

这是企业开发调试必用方式,比串口来说不用接线,前提是接入网络并获得板子IP地址,且系统做了SSH

的服务器,本镜像自带SSH服务器,所以通过mobaXterm登陆就行

2.8. 安全退出:

当想要退出的时候,直接拔出香橙派的电源有些野蛮,总担心会数据丢失,安全退出的方法:

输入poweroff指令,然后观察香橙派的灯灭,代表断电成功

到这里全志H616开发板的刷机和初始化设置已经完毕,接下来就可以使用Linux系统,

2.9. 修改输出日志

sudo vi /boot/orangepiEnv.txt

verbosity=1 将1改为7

3. 基于官方外设开发

3.1. wiringPi库外设SDK安装
直接在ssh上;下载wiring库;但是有时间可能因为网络等问题;无法下载。
git clone https://github.com/orangepi-xunlong/wiringOP //下载源码
cd wiringOP //进入文件夹
sudo ./build clean //清除编译信息
sudo ./build //编译

    
通过windows浏览器打开https://github.com/orangepi-xunlong/wiringOP
下载压缩包
把压缩包通过xterm传到开发板
解压 unzip xxx.zip
cd xxx
sudo ./build
gpio readall

使用 wiringPi 有以下几个原因:

  1. 简化开发:wiringPi 提供了一套简单易用的 API,类似于 Arduino 的 wiring 库,这使得开发者可以更加专注于项目的逻辑部分,而不必深陷硬件细节。
  2. 跨平台:wiringPi 支持多种树莓派型号,包括基于 BCM2835、BCM2836 和 BCM2837 的板子,这意味着开发者可以在不同的硬件上使用相同的代码。
  3. 多语言支持:虽然 wiringPi 最初是为 C 语言编写的,但它也可以被其他语言所使用,如 Python、PHP、Perl、Ruby 等,这为不同语言背景的开发者提供了便利。
  4. 丰富的功能:wiringPi 提供了对 GPIO、I2C、SPI、UART 和 PWM 等硬件接口的支持,这些都是嵌入式开发中常用的功能。
  5. 社区支持:wiringPi 有一个活跃的社区,开发者可以在社区中找到大量的教程、示例和问题解答,这有助于加速开发过程。
  6. 易于上手:对于熟悉 Arduino 的开发者来说,wiringPi 提供的 API 非常熟悉,可以快速上手树莓派的开发。
  7. 命令行工具:wiringPi 提供了命令行工具,允许开发者直接在终端中控制 GPIO,这对于快速测试和脚本编写非常有用。
  8. 开源:wiringPi 是一个开源项目,这意味着它可以自由使用、修改和分发,同时也允许社区贡献代码,不断改进和更新。
  9. 硬件抽象层:wiringPi 提供了硬件抽象层,使得开发者不必关心底层的硬件实现,可以更加专注于应用层面的开发。
  10. 文档和资源:wiringPi 有详细的文档和丰富的资源,包括官方文档、社区论坛和GitHub仓库,这些都是学习和解决问题的宝贵资源。

总的来说,wiringPi 是一个功能强大、易于使用、社区支持良好的库,它为树莓派的硬件开发提供了极大的便利。

验证指令: gpio readall

如下方所示,外设库就完成安装了

3.2. 蜂鸣器开发
3.2.1. 蜂鸣器响的原理

基本IO口的应用

3.2.2. 蜂鸣器配合时间函数开发
  • 小插曲:

vim的设置,修改/etc/vim/vimrc文件,需要用超级用户权限

sudo vim /etc/vim/vimrc
set tabstop=4 //设置tab键缩进4个空格
set shiftwidth=4 //设置批量对齐时候的tab键空格数为
set nu  //设置vim编辑器显示行号

蜂鸣器I/O口对应的物理引脚的3号,其实对应的wPi的第0号

#include <stdio.h>
#include <wiringPi.h>
#include <unistd.h>
 
#define BEEP 0							//设置针脚0为蜂鸣器控制引脚
 
int main (void)
{
	wiringPiSetup();					//初始化wiringPi库
	pinMode (BEEP, OUTPUT) ;			//设置蜂鸣器IO口为输出模式

   
   /* pinMode(pin, mode)是一个函数,用于设置指定引脚的工作模式。
    pin是要设置的引脚号,mode可以是以下两种之一:

    INPUT:将引脚设置为输入模式,用于读取信号(例如,来自传感器或按钮)。
    OUTPUT:将引脚设置为输出模式,用于发送信号(例如,控制LED或蜂鸣器)。*/
 
	while(1){
        
		digitalWrite (BEEP, LOW);		//设置蜂鸣器IO口输出低电平蜂鸣器响	
		usleep(300000);					//延时300ms
		sleep(1);                       // 阻塞1秒
		digitalWrite (BEEP, HIGH);		//设置蜂鸣器IO口输出高电平蜂鸣器不响
		usleep(300000);					//延时300ms
		sleep(1);                       //等待1秒
        
        /*digitalWrite(pin, value)是一个函数,用于设置指定引脚的电平状态。
        pin是要设置的引脚号,value可以是HIGH或LOW:

        HIGH:将引脚设置为高电平(通常为3.3V或5V),使连接的设备(如蜂鸣器)开启。
        LOW:将引脚设置为低电平(0V),使连接的设备关闭。
            digitalWrite(BEEP, LOW)使蜂鸣器发声,
            digitalWrite(BEEP, HIGH)则使其静音。*/
	}
 
	return 0;
}
  • 代码写完之后我们编译,我们直接用gcc编译会出错:

还需要链接库:-lwiringPi -lwiringPiDev -lpthread -lm -lcrypt -lrt

  • shell脚本处理参数,可以通过$?来处理,这里的$1是要编译的文件
  • 为了方便,我们将这个写成一个脚本。名为build.sh
简易编译的shell脚本:
gcc $1 -lwiringPi -lwiringPiDev -lpthread -lm -lcrypt -lrt
  • 直接将shell脚本粘贴在其后面

  • 但是每次写这么一大串很麻烦,所以可以写一个脚本:build.sh
    • shell脚本应该适用于各种程序的名字:

$0 ./build.sh

shell脚本处理参数可以通过$来处理,这里的$1就是要编译的文件

gcc $1 -lwiringPi -lwiringPiDev -lpthread -lm -lcrypt -lrt

注意:一定要使用chmod 0777 build.sh 将build.h文件改成可执行权限

使用 vi 创建一个sh文件,将 shell脚本 放进文件中即可。

  • 此时按照对应的格式进行编译,就生成了可执行程序a.out ./build.sh + 文件名

  • 注意!生成的a.out可执行程序,需要使用sudo超级用户权限来运行,然后输入密码之后我们就听到了蜂鸣器滴滴响了

我们可以看到蜂鸣器对应的GPIO的Mode就变成了OUT输出模式,

这些库的意思:

在编译基于全志H616或其他类似嵌入式平台的程序时,链接器选项如 -lwiringPi -lwiringPiDev -lpthread -lm -lcrypt -lrt 是为了包含特定的库,这些库提供了程序运行所需的额外功能和接口。下面是对每个链接器选项的解释:

  1. -lwiringPi
    • wiringPi 是一个用于Linux的GPIO控制库,它提供了一个类似于Arduino的API来简化GPIO引脚的访问和控制。如果你的程序需要直接控制H616的GPIO引脚,那么就需要链接 wiringPi 库。
  1. -lwiringPiDev
    • wiringPiDev 通常是 wiringPi 库的一部分,或者是与之紧密相关的库,用于提供对特定硬件设备的访问。在一些情况下,它可能提供了额外的设备级接口或是对 wiringPi 的扩展。如果你的程序需要这些特定的设备级功能,就需要链接这个库。
  1. -lpthread
    • pthread 是POSIX线程库,它提供了多线程编程的接口。如果你的程序需要并发执行多个任务或线程,就需要链接 pthread 库。
  1. -lm
    • m 是数学库的缩写,它提供了各种数学函数,如三角函数、对数函数等。如果你的程序需要进行数学计算,就需要链接这个库。
  1. -lcrypt
    • crypt 库提供了加密和解密功能。如果你的程序需要处理加密数据或实现安全功能,就需要链接这个库。不过,需要注意的是,crypt 库可能不是所有系统都提供,或者可能已经被更现代、更安全的加密库(如OpenSSL)所取代。
  1. -lrt
    • rt 是实时库的缩写,它提供了一些实时编程的特性,如高精度计时器和共享内存。在需要实时性能或特定实时功能的系统中,可能需要链接这个库。不过,值得注意的是,在较新的Linux发行版中,实时库的功能可能已经被整合进了标准的C库中,因此可能不再需要显式链接 -lrt

在编译过程中,链接这些库是为了确保你的程序能够使用它们提供的特定功能。每个库都对应着一组特定的功能或接口,而链接这些库是为了让编译器知道在哪里找到这些功能的实现。在编写Makefile或手动编译命令时,正确地指定这些链接器选项是非常重要的,以确保程序的正确编译和链接。

  • SDA.3 为0,属于低电平,蜂鸣器响

  • SDA.3 为1,属于高电平,蜂鸣器停止鸣响

3.3. 超声波测距
3.3.1. 测距原理基本说明

超声波测距模块是用来测量距离的一种产品,通过发送和收超声波,利用时间差和声音传播速度,

计算出模块到前方障碍物的距离

型号:HC-SR04

接线参考:模块除了两个电源引脚外,还有TRIG,ECHO引脚

  • 怎么让它发送波

Trig ,给Trig端口至少10us的高电平。

  • 怎么知道它开始发了

Echo信号,由低电平跳转到高电平,表示开始发送波 。

  • 怎么知道接收了返回波

Echo,由高电平跳转回低电平,表示波回来了 。

  • 怎么算时间

Echo引脚维持高电平的时间!

波发出去的那一下,开始启动定时器。

波回来的拿一下,我们开始停止定时器,计算出中间经过多少时间。

  • 怎么算距离

距离 = 速度 (340m/s)* 时间/2 。

时序

3.4. HC-SR04超声波模块硬件接线:

  • 超声波的VCC接到板子上的5V
  • 超声波的GND接到板子上的GND
  • 超声波的Trig口对应的物理引脚的3号,其对应的wPi的第0号
  • 超声波的Echo口对应的物理引脚的5号,其对应的wPi的第1号

4. 时间函数

4.1. 时间函数gettimeofday()原型和头文件:
#include <sys/time.h>
 
int gettimeofday(struct timeval *tv, struct timezone *tz);
 
int 					函数返回值,如果成功,gettimeofday 返回 0。如果失败,它返回 -1 并设置 errno 以指示错误。
    
struct timeval *tv		这是一个指向 timeval 结构体的指针,该结构体用于存储当前时间。timeval 的定义如下:



struct timeval {  
        time_t      tv_sec;   /* 秒 */  
        suseconds_t tv_usec;  /* 微秒 */  
};
 
time_t      tv_sec		是一个整数,表示自 Unix 纪元(1970年1月1日 00:00:00 UTC)以来的秒数。
suseconds_t tv_usec		是一个整数,表示微秒数(0 到 999,999)。
 
struct timezone *tz		这是一个指向 timezone 结构体的指针,用于存储时区信息。但在现代系统中,这个参数通常被忽略,因为大多					     
                        数系统都使用 UTC 时间,并且不再使用本地时区偏移。
这个参数我们通常设置为NULL
 
/*函数说明:
gettimeofday 是一个 Unix 和 Linux 系统调用,用于获取当前的时间(包括秒和微秒)和时区信息(尽管时区信息在大多数现代系统中可能不太常用)
*/
4.2. 使用gettimeofday()函数获取当前时间的秒数和微妙数:
#include <stdio.h>
#include <sys/time.h>



int main()
{
    struct timeval tv;
    /*	struct timeval {  
    //  	time_t      tv_sec;    秒 */  
    //  	suseconds_t tv_usec;  /* 微秒 };
    
 
    //int gettimeofday(struct timeval *tv, struct timezone *tz);

	if(gettimeofday(&tv,NULL)==0)
    {   printf("从1970-01-01 00:00:00到现在的秒数:%ld\n",tv.tv_sec);
        printf("从1970-01-01 00:00:00到现在的微秒数:%ld\n",tv.tv_usec);
		
	}else
    {
        printf(" 获取时间失败\n");
        exit(1);
    }

    return 0;
}

4.3. 使用gettimeofday()函数计算全志H616数10万次的耗时:
#include <stdio.h>
#include <sys/time.h>
 
void cntTest()                      //全志H616数数十万次
{
    int i;
    int j;
 
    for(i=0; i<100; i++)
        for(j=0; j<1000; j++);
}
 
int main()
{
    struct timeval timeStart;       
    struct timeval timeStop;
    long diffTime;
 
    //int gettimeofday(struct timeval *tv, struct timezone *tz);
    gettimeofday( &timeStart, NULL);    //获取数数之前的时间  (当前时间)
    cntTest();                          //数数 十万次    
    gettimeofday( &timeStop, NULL);     //获取数数之后的时间1	(当前时间)
 
    //秒之所以要乘以1000000是因为后面加了微妙,所以要将秒转化为微秒						//获取数数十万次的时间
    diffTime = 1000000 * (timeStop.tv_sec - timeStart.tv_sec) + (timeStop.tv_usec - timeStart.tv_usec); 
    printf("数十万次,需要 =%ldus\n",diffTime);  //%us是微秒
    return 0;
}
//	1秒等于1,000,000微秒。- -*

5. 实现超声波测距

5.1. 使用wiringOP库和时间函数实现超声波测距:
#include <stdio.h>                  // 包含标准输入输出库头文件
#include <sys/time.h>               // 包含时间相关函数的头文件
#include <wiringPi.h>               // 包含wiringPi库的头文件,用于GPIO控制
#include <unistd.h>                 // 包含标准库函数,如sleep
#include <stdlib.h>                 // 包含标准库函数,如malloc和free
 
#define Trig 0                      // 定义Trig引脚为0
#define Echo 1                      // 定义Echo引脚为1
 
void startHC()
{
    digitalWrite(Trig, LOW);        //给Trig端口发送低电平信号    
    usleep(5);                                                                                   
    digitalWrite(Trig, HIGH);       //给Trig端口发送高电平信号 
    usleep(10);                                                                                                 
    digitalWrite(Trig, LOW);        //给Trig端口发送低电平信号    
}
 
double getDistance()
{
    double dis;                     //距离
 
    struct timeval Start;           //开始时间  
    struct timeval Stop;            //结束时间
 
    startHC();                      //给Trig端口10us的高电平
    while(!digitalRead(Echo));      //Echo信号由低电平跳转到高电平
    gettimeofday(&Start, NULL);     //获取时间
    //只要一发波,接收端就变成高电平
    
    while(digitalRead(Echo));       //Echo信号由高电平跳转回低电平
    gettimeofday(&Stop, NULL);      //获取时间
      //只要波一返回,接收端就变成低电平
    long diffTime = 1000000 * (Stop.tv_sec - Start.tv_sec) + (Stop.tv_usec - Start.tv_usec);  //计算时间差   
    dis = (double)diffTime/1000000 * 34000 / 2;        //3400为cm      diffTime/1000000由微秒转换成秒
    
    //计算距离            
    
    return dis;
}
 
int main()
{
    double dis;                                     //距离
 
    if(wiringPiSetup() == -1){                      //初始化wiringPi库
        printf("初始化wiringPi库失败\n");
        exit(-1);
    }                                
 
    pinMode(Trig, OUTPUT);                          //设置超声波Trig引脚为输出模式
    pinMode(Echo, INPUT);                           //设置超声波Echo引脚为输入模式
 
    while(1){
        dis = getDistance();                        //获取超声波距离
        printf("当前超声波距离是:%.2lf\n",dis);
        usleep(500000);                             //每500ms获取一次
    }
    return 0;
}

5.2. 实现超声波测距(距离小于10cm时蜂鸣器发出警报):
#include <stdio.h>                  // 包含标准输入输出库头文件
#include <sys/time.h>               // 包含时间相关函数的头文件
#include <wiringPi.h>               // 包含wiringPi库的头文件,用于GPIO控制
#include <unistd.h>                 // 包含标准库函数,如sleep
#include <stdlib.h>                 // 包含标准库函数,如malloc和free
 
#define Trig 0                      // 定义Trig引脚为0
#define Echo 1                      // 定义Echo引脚为1
#define BEEP 2							//设置针脚0为蜂鸣器控制引脚
 

		
 
void startHC()
{
    digitalWrite(Trig, LOW);        //给Trig端口发送低电平信号    
    usleep(5);                                                                                   
    digitalWrite(Trig, HIGH);       //给Trig端口发送高电平信号 
    usleep(10);                                                                                                 
    digitalWrite(Trig, LOW);        //给Trig端口发送低电平信号    
}
 
double getDistance()
{
    double dis;                     //距离
 
    struct timeval Start;           //开始时间  
    struct timeval Stop;            //结束时间
    
 
    startHC();                      //给Trig端口10us的高电平
    while(!digitalRead(Echo));      //Echo信号由低电平跳转到高电平
    gettimeofday(&Start, NULL);     //获取时间
    
    while(digitalRead(Echo));       //Echo信号由高电平跳转回低电平
    gettimeofday(&Stop, NULL);      //获取时间
 
    long diffTime = 1000000 * (Stop.tv_sec - Start.tv_sec) + (Stop.tv_usec - Start.tv_usec);  //计算时间差   
    dis = (double)diffTime/1000000 * 34000 / 2;        //3400为cm      diffTime/1000000由微秒转换成秒
    
    //计算距离            
    
    return dis;
}
 
int main()
{
    double dis;                                     //距离
 
    if(wiringPiSetup() == -1){                      //初始化wiringPi库
        printf("初始化wiringPi库失败\n");
        exit(-1);
    }                                
 
    pinMode(Trig, OUTPUT);                          //设置超声波Trig引脚为输出模式
    pinMode(Echo, INPUT);                           //设置超声波Echo引脚为输入模式
    pinMode (BEEP, OUTPUT) ;	
 
    while(1){
        dis = getDistance();                        //获取超声波距离
        printf("当前超声波距离是:%.2lf\n",dis);
        usleep(500000);                             //每500ms获取一次
        if(dis<5){
            digitalWrite (BEEP, LOW);		//设置蜂鸣器IO口输出低电平蜂鸣器响	
    		usleep(300000);					//延时300ms
    		sleep(1);   
        }else{// 阻塞1秒
    		digitalWrite (BEEP, HIGH);		//设置蜂鸣器IO口输出高电平蜂鸣器不响
    		                
        }
    }
    return 0;
}

6. 舵机的基本认知和硬件接线

6.1. 舵机的基本认知:

如下图所示:是一个SG90舵机,常用三根或者四根接线,红色为电源VCC,棕色为电源GND,黄色为PWM信号。

控制用处:垃圾桶项目开盖用、智能小车的全比例转向、摄像头云台、机械臂等常见的有0-90°、0-180°、0-360°

6.2. 硬件接线:

怎么控制舵机旋转不同的角度:

向黄色信号线“灌入”PWM信号。PWM波的频率不能太高,50hz,即周期=1/频率=1/50=0.02s,20ms左右数据:不同的PWM波形对应不同的旋转角度,以20ms为周期,50hz为频率的PWM波

向黄色信号线“灌入”PWM信号。 PWM波的频率不能太高,50hz,即周期=1/频率=1/50=0.02s,20ms左右数据:不同的PWM波形对应不同的旋转角度,以20ms为周期,50hz为频率的PWM波

定时器需要定时20ms,关心的单位0.5ms, 20ms = 0.5ms * 40

7. Linux定时器

7.1. 定时器setitimer()函数原型和头文件:
/*	setitimer 是一个UNIX系统上的系统调用函数,用于设置和管理定时器。它通常用于定期触发信号或执行某些操作	*/
#include <sys/time.h>
 
int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value);
 
int 		函数返回值,成功执行时返回0,失败返回-1
    
int whic	指定要设置的定时器类型,可以是 ITIMER_REAL、ITIMER_VIRTUAL 或 ITIMER_PROF 中的一个
1. ITIMER_REAL 		//数值为0,计时器的值实时递减,发送的信号是SIGALRM。
2. ITIMER_VIRTUAL 	//数值为1,进程执行时递减计时器的值,发送的信号是SIGVTALRM。
3. ITIMER_PROF 		//数值为2,进程和系统执行时都递减计时器的值,发送的信号是SIGPROF。
    
struct itimerval *new_value		一个struct itimerval结构,用于指定新的定时器值。struct itimerval 结构定义如下:
    
struct itimerval {
        struct timeval it_interval;  // 定时器重复的间隔时间
        struct timeval it_value;     // 定时器的初始值
};
 
it_interval		计时器的初始值,一般基于这个初始值来加或者来减,看控制函数的参数配置
it_value		程序跑到这之后,多久启动定时器
    
struct timeval {  
        __time_t      tv_sec;   /* 秒 */  
        __suseconds_t tv_usec;  /* 微秒 */  
};
 
struct itimerval *old_value		一个struct itimerval 结构,用于存储旧的定时器值(可选参数)一般为NULL
    
/*
函数说明:
实现定时器,通过itimerval结构体以及函数setitimer产生的信号,系统随之使用signal信号处理函数来处理产生的定时信号。从而实现定时器。
*/
7.2. 信号处理函数signal()原型和头文件:
/*
	Linux下 man 2 signal查看手册
*/
#include <signal.h>
 
typedef void (*sighandler_t)(int);
 
sighandler_t signal(int signum, sighandler_t handler);
 
sighandler_t			函数返回值,返回信号处理程序的前一个值,或者在错误时SIG ERR。如果发生错误,则设置errno来指示原因。
 
int signum				指明了所要处理的信号类型,它可以取除了SIGKILL和SIGSTOP外的任何一种信号
 
sighandler_t handler	描述了与信号关联的动作,它可以取以下三种值:  
    
1. 一个无返回值的函数地址
此函数必须在signal()被调用前声明,handler中为这个函数的名字。当接收到一个类型为signum的信号时,就执行handler 所指定的函数。这个函数应有如下形式的定义:
void handler(int signum);
 
2. SIG_IGN
这个符号表示忽略该信号,执行了相应的signal()调用后,进程会忽略类型为sig的信号。
    
3. SIG_DFL
这个符号表示恢复系统对信号的默认处理。
7.3. 定时器setitimer()函数应用实例:

实现的功能是:1秒后打开定时器,然后每隔一秒打印一次Hello

#include <stdio.h>          // 包含标准输入输出库,用于输入输出操作
#include <sys/time.h>       // 包含时间处理库,用于获取系统时间
#include <stdlib.h>         // 包含标准库,用于内存分配和程序终止等操作
#include <signal.h>         // 包含信号处理库,用于处理系统信号
 
static int i = 0;                                                     //全局变量i 
 
void signal_handler(int signum)                                      //信号处理函数
{
    i++;
    if(i == 2000){                                                 //每2000次触发一次
        printf("Hello\n");
        i = 0;
    }//每隔一秒 发送一次   0.00005*2000 = 1
}
 
int main()
{
    struct itimerval itv;                                       //定义一个itimerval结构体变量
 
    //设定定时时间
    itv.it_interval.tv_sec = 0;
    itv.it_interval.tv_usec = 500;                                //每隔0.5毫秒循环触发一次 
    //设定启动定时器时间
    itv.it_value.tv_sec = 1;                                        //启动定时器1秒后触发    
    itv.it_value.tv_usec = 0;               
    //设定定时方式
    //int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value);
    if(setitimer(ITIMER_REAL, &itv, NULL) == -1){                     //设置定时器  
        perror("setitimer");
        exit(-1);
    }
    //	ITIMER_REAL:这种类型的定时器用于测量实际时间(wall clock time)。
       //             当定时器到期时,它会向进程发送 SIGALRM 信号。
         // 这种定时器与进程的 CPU 使用时间无关,即使进程在睡眠或阻塞状态下,它也会继续计时。
    //信号处理
    signal( SIGALRM, signal_handler);         //设置信号处理函数
     //当接收到 SIGALRM信号时,进行信号处理
    while(1);
    
    return 0;
}

  • 每隔一秒打印一次hello语句

首先,struct itimerval 结构体用于定义定时器的值,包括定时器的初始值(it_value)和重复间隔(it_interval)。

struct itimerval itv;

这行代码定义了一个名为 itvitimerval 结构体变量。

itv.it_interval.tv_sec = 0;
itv.it_interval.tv_usec = 500; // 每隔0.5毫秒触发一次

这两行代码设置了定时器的重复间隔。it_interval.tv_sec 设置为0,表示间隔的秒数为0。it_interval.tv_usec 设置为500,表示间隔的微秒数为500微秒(即0.5毫秒)。这意味着定时器每隔0.5毫秒就会触发一次。

itv.it_value.tv_sec = 1;
itv.it_value.tv_usec = 0;

这两行代码设置了定时器的初始值。it_value.tv_sec 设置为1, 设置第一次触发定时器的时间。在这里设置为 1 秒后开始触发。it_value.tv_usec 设置为0,表示在启动后的第1秒内不需要额外的微秒延迟。

setitimer(ITIMER_REAL, &itv, NULL);

这行代码调用 setitimer 函数来设置一个实时定时器(ITIMER_REAL)。&itv 是指向前面定义的 itimerval 结构体的指针,它包含了定时器的设置。最后一个参数是 NULL,表示不关心旧的定时器值,不需要保存。

signal(SIGALRM, signal_handler);

这行代码使用 signal 函数来设置当 SIGALRM 信号发生时的处理函数为 signal_handler。这意味着每当定时器触发并发送 SIGALRM 信号给进程时,signal_handler 函数将被调用。

void signal_handler(int signum)
{
    i++;
    if(i == 2000){
        printf("Hello\n");
        i = 0;
    }
}

这是信号处理函数 signal_handler 的定义。它接收一个信号编号作为参数(虽然在这个函数中没有使用)。每次 SIGALRM 信号被接收时,这个函数都会被调用,并且变量 i 会递增。当 i 达到2000时,它会打印 "Hello" 并重置 i 为0。

7.4. 使用wiringOP库配合定时器驱动舵机旋转0-180°:
#include <stdio.h>          // 包含标准输入输出库,用于输入输出操作
#include <sys/time.h>       // 包含时间处理库,用于获取系统时间
#include <stdlib.h>         // 包含标准库,用于内存分配和程序终止等操作
#include <signal.h>         // 包含信号处理库,用于处理系统信号
#include <wiringPi.h>       // 包含WiringPi库,用于控制GPIO


static int i = 0;                                                        	//全局变量i 
static int jd = 2;                                                                   	//全局变量jd 

void signal_handler(int signum)                                            	//信号处理函数
{
    while(1){
    if(i <= jd){
        digitalWrite(SG90Pin, HIGH);                    //设置SG90引脚为高电平
    } else{
        digitalWrite(SG90Pin, LOW);                   //设置SG90引脚为低电平
    }
    if(i == 40){                                      //每40次信号处理函数被调用一次,输出一次当前时间
        i = 0;                                         //i归零
    }
    i++;
    }
}
 


int main()
{ 
    wiringPiSetup();                                                     	//初始化WiringPi库
    pinMode(SG90Pin, OUTPUT);                                             	//设置SG90引脚为输出模式
     
    struct itimerval itv;                                       //定义一个itimerval结构体变量
 
    //设定定时时间
    itv.it_interval.tv_sec = 0;
    itv.it_interval.tv_usec = 500;                                //每隔0.5毫秒触发一次 
    //设定启动定时器时间
    itv.it_value.tv_sec = 1;                                        //启动定时器1秒后触发    
    itv.it_value.tv_usec = 0;               
    //设定定时方式
    //int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value);
    if(setitimer(ITIMER_REAL, &itv, NULL) == -1){                     //设置定时器  
        perror("setitimer");
        exit(-1);
    }
    //信号处理
    signal( SIGALRM, signal_handler);                                 //设置信号处理函数
 
    while(1);
    
    return 0;
}

舵机旋转45度;

#include <stdio.h>          // 包含标准输入输出库,用于输入输出操作
#include <sys/time.h>       // 包含时间处理库,用于获取系统时间
#include <stdlib.h>         // 包含标准库,用于内存分配和程序终止等操作
#include <signal.h>         // 包含信号处理库,用于处理系统信号
#include <wiringPi.h>       // 包含WiringPi库,用于控制GPIO
 
#define SG90Pin 5           // 定义SG90的引脚号
 
static int i = 0;                                                        	//全局变量i 
int jd;                                                                   	//全局变量jd 
 
void signal_handler(int signum)                                            	//信号处理函数
{
    if(i <= jd){
        digitalWrite(SG90Pin, HIGH);                        				//设置SG90引脚为高电平
    }else{
        digitalWrite(SG90Pin, LOW);                          				//设置SG90引脚为低电平
    }
    if(i == 40){                                                    //每40次信号处理函数被调用一次,输出一次当前时间
        i = 0;                                              				//i归零
    }
    i++;
}
 
int main()
{
    struct itimerval itv;                                                  	//定义一个itimerval结构体变量
    jd = 0;                                                                 //初始化jd为0    
    wiringPiSetup();                                                     	//初始化WiringPi库
    pinMode(SG90Pin, OUTPUT);                                             	//设置SG90引脚为输出模式
 
    //设定定时时间
    itv.it_interval.tv_sec = 0;
    itv.it_interval.tv_usec = 500;                                         	//每隔0.5毫秒触发一次 
    //设定启动定时器时间
    itv.it_value.tv_sec = 1;                                          		//启动定时器1秒后触发    
    itv.it_value.tv_usec = 0;               
    //设定定时方式
    //int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value);
    if(setitimer(ITIMER_REAL, &itv, NULL) == -1){                      		//设置定时器  
        perror("setitimer");
        exit(-1);
    }
    //信号处理
    signal( SIGALRM, signal_handler);                                      	//设置信号处理函数
 
    while(1){
        printf("请输入你想要的角度: 1-0 2-45 3-90 4-135 5-180\n");
        scanf("%d",&jd);                                                 	 //输入角度   
    }
    
    return 0;
}

7.5. 小项目:使用全志h616实现智能垃圾桶
  • 接线图标

  • 代码:
#include <stdio.h>          // 包含标准输入输出库,用于输入输出操作
#include <sys/time.h>       // 包含时间处理库,用于获取系统时间
#include <stdlib.h>         // 包含标准库,用于内存分配和程序终止等操作
#include <signal.h>         // 包含信号处理库,用于处理系统信号
#include <wiringPi.h>       // 包含WiringPi库,用于控制GPIO
#include <unistd.h>                 // 包含标准库函数,如sleep

#define Trig 0                      // 定义Trig引脚为0
#define Echo 1                      // 定义Echo引脚为1
#define BEEP 2						//设置针脚0为蜂鸣器控制引脚
#define SG90Pin 5          			// 定义SG90的引脚号
 
static int i = 0;                                                        	//全局变量i 
 int jd = 0;                                                                   	//全局变量jd 

void signal_handler(int signum)                                            	//信号处理函数
{
    
    if(i <= jd){
        digitalWrite(SG90Pin, HIGH);                    //设置SG90引脚为高电平
    } else{
        digitalWrite(SG90Pin, LOW);                   //设置SG90引脚为低电平
    }
    if(i == 40){                                      //每40次信号处理函数被调用一次,输出一次当前时间
        i = 0;                                         //i归零
    }
    i++;
   
}
void startHC()
  {
    digitalWrite(Trig, LOW);        //给Trig端口发送低电平信号
     usleep(5);
    digitalWrite(Trig, HIGH);       //给Trig端口发送高电平信号
     usleep(10);
    digitalWrite(Trig, LOW);        //给Trig端口发送低电平信号
 }


 
double getDistance()
 {
     double dis;                     //距离
 
     struct timeval Start;           //开始时间
      struct timeval Stop;            //结束时间


     startHC();                      //给Trig端口10us的高电平
      while(!digitalRead(Echo));      //Echo信号由低电平跳转到高电平
     gettimeofday(&Start, NULL);     //获取时间

      while(digitalRead(Echo));       //Echo信号由高电平跳转回低电平
      gettimeofday(&Stop, NULL);      //获取时间

     long diffTime = 1000000 * (Stop.tv_sec - Start.tv_sec) + (Stop.tv_usec - Start.tv_usec);  //计算时间差
      dis = (double)diffTime/1000000 * 34000 / 2;        //3400为cm      diffTime/1000000由微秒转换成秒
 
      //计算距离
 
      return dis;
 }


int main()
{ 
    wiringPiSetup();                                                     	//初始化WiringPi库
    pinMode(SG90Pin, OUTPUT);                                             	//设置SG90引脚为输出模式
    pinMode(Trig, OUTPUT);                          //设置超声波Trig引脚为输出模式
    pinMode(Echo, INPUT);                           //设置超声波Echo引脚为输入模式
    pinMode (BEEP, OUTPUT) ;
    double dis;                                     //超声波距离
    struct itimerval itv;                                       //定义一个itimerval结构体变量
 
    //设定定时时间
    itv.it_interval.tv_sec = 0;
    itv.it_interval.tv_usec = 500;                                //每隔0.5毫秒触发一次 
    //设定启动定时器时间
    itv.it_value.tv_sec = 1;                                        //启动定时器1秒后触发    
    itv.it_value.tv_usec = 0;               
    //设定定时方式
    //int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value);
     if(wiringPiSetup() == -1){                      //初始化wiringPi库
          printf("初始化wiringPi库失败\n");
         exit(-1);
     }
    if(setitimer(ITIMER_REAL, &itv, NULL) == -1){                     //设置定时器  
        perror("setitimer");
        exit(-1);
    }
    //信号处理
    signal( SIGALRM, signal_handler);                                 //设置信号处理函数
 
     while(1){
          dis = getDistance();                        //获取超声波距离
         printf("当前超声波距离是:%.2lf\n",dis);
         usleep(500000);                             //每500ms获取一次
          if(dis<10){
            digitalWrite (BEEP, LOW);       //设置蜂鸣器IO口输出低电平蜂鸣器响
            usleep(300000);                 //延时300ms
            sleep(1);
            digitalWrite (BEEP, HIGH);
            jd = 3;
         }else{
            digitalWrite (BEEP, HIGH);
            jd = 1;
         }
     }

    while(1);
    
    return 0;
}
  • 代码详情:

是一个嵌入式程序,用于控制一个舵机和一个蜂鸣器,同时使用超声波传感器测量距离。它运行在支持WiringPi库的单片机(如树莓派或Orange Pi)上。下面是代码的总体功能和运行原理:

7.5.1. 功能:
  1. 控制舵机:通过周期性地改变SG90舵机引脚的电平,使舵机在两个位置之间切换。
  2. 超声波测距:使用超声波传感器测量距离,并通过getDistance函数返回测量结果。
  3. 蜂鸣器报警:当超声波传感器测量到的距离小于10厘米时,蜂鸣器会发出声音。
7.5.2. 运行原理:
  1. 初始化:程序首先初始化WiringPi库,设置舵机、超声波传感器的Trig和Echo引脚、蜂鸣器引脚的模式(输出或输入)。
  2. 设置定时器:使用setitimer函数设置一个定时器,每隔0.5毫秒触发一次SIGALRM信号。这个信号会调用signal_handler函数。
  3. 信号处理signal_handler函数根据全局变量ijd的值,控制SG90舵机引脚的电平。这个函数每40次调用会重置i变量。
  4. 主循环
    • 超声波测距:在主循环中,程序不断调用getDistance函数来获取当前的距离值。
    • 打印距离:将测量到的距离打印到控制台。
    • 蜂鸣器控制:如果测量到的距离小于10厘米,程序会使蜂鸣器响起(输出低电平),然后延时一段时间,再关闭蜂鸣器(输出高电平)。如果距离大于等于10厘米,程序会关闭蜂鸣器。
  1. 无限循环:主循环是一个无限循环,这意味着程序会持续运行,直到被外部中断(如断电或手动停止)。
  2. getDistance函数通过发送一个短暂的高电平脉冲到Trig引脚,然后测量Echo引脚上的高电平持续时间,来计算超声波从发射到返回的时间。根据声波在空气中的传播速度,可以计算出距离。

8. OLED屏幕

9. Orangepi的IIC接口及OLED屏幕硬件接线

9.1. Orangepi的IIC接口:

由 26pin 的原理图可知, Orange Pi Zero 2 可用的 i2c 为 i2c3

启动Linux系统后先确认一下在/dev/orangepiEnv.txt,目录下的IIC设备节点,全志H616用的是I2C-3设备节点,但是我们使用的是Linux5.16的内核系统,Orange Pi Zero 2的I2C-3接口默认是关闭的,需要通过修改配置文件并重启系统来启用。

在/boot/orangepiEnv.txt中加入overlays=i2c3这个配置,然后重启Linux系统就能打开i2c-3
    
sudo vim /boot/orangepiEnv.txt
 
overlays=i2c3

从命令运行结果能观察到,Orange Pi Zero 2)支持多个I2C接口,具体是I2C-3、I2C-4和I2C-5。这些是不同的I2C总线,每个都可以连接不同的设备。

而H616的外设我们看到只有一个IIC接口,用的是IIC-3,Linux一切皆文件,每个硬件设备“对应”一个文件,由驱动程序提供映射

  • 尽管系统支持多个I2C总线,但我的设备(H616)实际上需要使用的是I2C-3总线。
  • 开始测试I2C,首先安装i2c-tools:

sudo apt-get install i2c-tools

  • 有时间会报错,确保你的软件包列表是最新的

sudo apt-get update 更新软件包

sudo apt-get install i2c-tools 安装

  • 列出连接到I2C总线的设备的地址:

注意,线一定要接对,如果没有出现3c,就仔细检测一下线是否接对。

9.2. Orangepi与OLED屏幕硬件接线:

10. wiringPi库示例代码

10.1. wiringPi库OLED屏幕示例代码:

/*
 * Copyright (c) 2015, Vladimir Komendantskiy
 * MIT License
 *
 * SSD1306 demo of block and font drawing.
 */
 
//
// fixed for OrangePiZero by HypHop
//
 
#include <errno.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <stdint.h>
 
#include "oled.h"
#include "font.h"
 
int oled_demo(struct display_info *disp) {
	int i;
	char buf[100];
 
	//putstrto(disp, 0, 0, "Spnd spd  2468 rpm");
	//	oled_putstrto(disp, 0, 9+1, "Spnd cur  0.46 A");
	oled_putstrto(disp, 0, 9+1, "Welcome       to");
	disp->font = font1;
	//	oled_putstrto(disp, 0, 18+2, "Spnd tmp    53 C");
	oled_putstrto(disp, 0, 18+2, "----OrangePi----");
	disp->font = font2;
	//	oled_putstrto(disp, 0, 27+3, "DrvX tmp    64 C");
	oled_putstrto(disp, 0, 27+3, "This is 0.96OLED");
	oled_putstrto(disp, 0, 36+4, "");
	oled_putstrto(disp, 0, 45+5, "");
	disp->font = font1;
	//	oled_putstrto(disp, 0, 54, "Total cur  2.36 A");
	oled_putstrto(disp, 0, 54, "*****************");
	oled_send_buffer(disp);
 
	disp->font = font3;
	for (i=0; i<100; i++) {
		sprintf(buf, "Spnd spd  %d rpm", i);
		oled_putstrto(disp, 0, 0, buf);
		oled_putstrto(disp, 135-i, 36+4, "===");
		oled_putstrto(disp, 100, 0+i/2, ".");
		oled_send_buffer(disp);
	}
	//oled_putpixel(disp, 60, 45);
	//oled_putstr(disp, 1, "hello");
 
return 0;
}
 
void show_error(int err, int add) {
	//const gchar* errmsg;
	//errmsg = g_strerror(errno);
	printf("\nERROR: %i, %i\n\n", err, add);
	//printf("\nERROR\n");
}
 
void show_usage(char *progname) {
	printf("\nUsage:\n%s <I2C bus device node >\n", progname);
}
 
int main(int argc, char **argv) {
	int e;
	char filename[32];
	struct display_info disp;
 
	if (argc < 2) {
		show_usage(argv[0]);
		
		return -1;
	}
 
	memset(&disp, 0, sizeof(disp));
	sprintf(filename, "%s", argv[1]);
	disp.address = OLED_I2C_ADDR;
	disp.font = font2;
 
	e = oled_open(&disp, filename);
 
	if (e < 0) {
		show_error(1, e);
	} else {
		e = oled_init(&disp);
	if (e < 0) {
		show_error(2, e);
	} else {
		printf("---------start--------\n");
		if (oled_demo(&disp) < 0)
			show_error(3, 777);
			printf("----------end---------\n");
		}
	}
 
	return 0;
}

cp ../wiringPiFromwindows/wiringOP-next/examples/oled_demo.c .//拷贝oled_demo.c文件到当前路径下

./build.sh oled_demo.c /dev/i2c-3 //编译oled_demo.c

sudo ./a.out /dev/i2c-3 //运行程序

10.2. OLED显示自己想要的字符:
/*
 * Copyright (c) 2015, Vladimir Komendantskiy
 * MIT License
 *
 * SSD1306 demo of block and font drawing.
 */
#include <errno.h> 		// 包含错误处理相关的头文件
#include <string.h> 	// 包含字符串处理相关的头文件
#include <stdio.h> 		// 包含标准输入输出相关的头文件
#include <stdlib.h> 	// 包含标准库函数相关的头文件
#include <time.h> 		// 包含时间处理相关的头文件
#include <stdint.h> 	// 包含标准整数类型相关的头文件
#include "oled.h" 		// 包含OLED显示相关的头文件
#include "font.h" 		// 包含字体相关的头文件
 
// 在OLED显示屏上显示文本和图形
int oled_show(struct display_info *disp)
{
	int i;
	char buf[100];

     //oled_putstrto 函数将一个字符串显示在OLED显示屏的指定位置上。
	oled_putstrto(disp, 0, 0, "***  Hello World  ***");						// 显示欢迎信息
	disp->font = font2;	// X 左右;Y上下													// 设置字体为font2
 
	oled_putstrto(disp, 10, 20, "-----------------");						// 显示欢迎信息
	disp->font = font2;														// 设置字体为font2
 
	oled_putstrto(disp, 13, 30, "----OrangePi----");						// 显示欢迎信息
	disp->font = font2;														// 设置字体为font2
 
	oled_putstrto(disp, 35, 50, "-- Mr.shi LXL --");					// 显示欢迎信息
	disp->font = font2;														// 设置字体为font2

    //oled_send_buffer 函数将内存中的缓冲区数据发送到OLED显示屏的控制器,从而更新显示屏上的显示内容。
	oled_send_buffer(disp);													// 发送显示缓冲区到OLED显示屏
	return 0;
}
 
// 显示错误信息
void show_error(int err, int add)
{
 	printf("\nERROR: %i, %i\n\n", err, add);
}
 
// 显示程序使用方法
void show_usage(char *progname) 
{
	printf("\nUsage:\n%s <I2C bus device node >\n", progname);
}
 
// 主函数,程序入口
int main(int argc, char **argv) 
{
	int e;
	char filename[32];										// 定义文件名字符串	
	struct display_info disp;								// 定义显示信息结构体
    /*
    地址(Address):OLED屏幕的I2C地址,用于在I2C总线上唯一标识该设备。
    字体(Font):当前使用的字体类型,可能用于定义文本显示的样式。
    缓冲区(Buffer):可能包含要显示在屏幕上的像素数据。
    尺寸(Size):屏幕的宽度和高度。
    颜色(Color):当前的前景色和背景色。
    方向(Orientation):屏幕的方向,例如横向或纵向。
    亮度(Brightness):屏幕的亮度级别。*/
 
	if (argc < 2) {											// 参数个数不足
		show_usage(argv[0]);	// 如果输入错误,打印正确是输入方法		// 显示程序使用方法
		return -1;
	}
 
	memset(&disp, 0, sizeof(disp));								// 清空显示信息结构体
	sprintf(filename, "%s", argv[1]);							// 拷贝参数到文件名字符串
	disp.address = OLED_I2C_ADDR;								// 设置OLED显示屏的I2C地址
	disp.font = font2;											// 设置字体为font2
 
	e = oled_open(&disp, filename);								// 打开OLED显示屏
    if(e <0){
        show_error(1,e);
    }else{
        e = oled_int(&disp);								// 初始化OLED显示屏
    
    if(e<0){
        show_error(2,e);
    }else{
        printf("--start--\n");
        if(oled_show(&disp)<0)                // 调用函数,在oled屏上,显示欢迎信息
            show_error(3,777);
            printf("--end--\n");
        }
    }
   									
/*
    在 main 函数中,它被初始化,并且其成员被设置为特定的值。
    在 oled_open 函数中,它可能被用来打开与特定I2C地址关联的OLED显示屏。
    在 oled_init 函数中,它可能被用来初始化显示屏的配置,如设置字体或亮度。
    在 oled_show 函数中,它被用来显示文本信息,其中可能包括设置字体和发送缓冲区数据到显示屏。*/
 
				
    /*1错误检测:程序在执行特定功能时会进行错误检测。如果检测到问题,功能会返回一个错误代码,这个代码是一个整数,用于标识发生了哪种错误。

    2调用错误处理函数:一旦检测到错误,程序会调用一个错误处理函数,如 show_error,来响应这个错误。

    3传递错误信息:show_error 函数需要知道发生了什么类型的错误,所以它会接收至少一个参数(通常是错误代码),可能还会接收额外的信息(如错误发生的位置或错误详情)。

    4格式化错误信息:show_error 函数使用 printf 来格式化字符串,这个字符串包含了错误信息。格式化字符串 "\nERROR: %d, Additional Info: %d\n\n" 包含了两个 %d 占位符,它们会被替换为实际的整数值。

   5输出错误信息:printf 函数将格式化后的错误信息输出到控制台(通常是终端或命令行窗口),让用户知道发生了错误。
	*/
    return 0;
}

注意:编译需要使用build.sh,不然会报错

 好了。看到这里,我们orangepi基础入门就结束啦!书写不易。给个点赞,关注吧!!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/924759.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

信息收集之网站架构类型和目录扫描(一)

目录 前言 1.查看域名的基本信息 2.常见的网站架构类型 3.目录扫描 前言 最近也是到了期末周了,比较空闲,把信息收集的一些方式和思路简单总结一下,顺便学习一些新的工具和一些未接触到的知识面. 1.查看域名的基本信息 新学了一个工具,kali中的whois也可以进行查看,当然在…

消息中间件用途介绍

1. 解耦&#xff08;Decoupling&#xff09;&#xff1a; • 消息中间件能够将消息的生产者&#xff08;Producer&#xff09;和消费者&#xff08;Consumer&#xff09;分离开来&#xff0c;使它们不必直接相互依赖。这种设计降低了系统的耦合度&#xff0c;提升了系统的可扩展…

【Maven】Nexus私服

6. Maven的私服 6.1 什么是私服 Maven 私服是一种特殊的远程仓库&#xff0c;它是架设在局域网内的仓库服务&#xff0c;用来代理位于外部的远程仓库&#xff08;中央仓库、其他远程公共仓库&#xff09;。一些无法从外部仓库下载到的构件&#xff0c;如项目组其他人员开发的…

学习ASP.NET Core的身份认证(基于Cookie的身份认证3)

用户通过验证后调用HttpContext.SignInAsync函数将用户的身份信息保存在认证Cookie中,以便后续的请求可以验证用户的身份,该函数原型如下所示&#xff0c;其中properties参数的主要属性已在前篇文章中学习&#xff0c;本文学习scheme和principal的意义及用法。 public static …

【mac】终端左边太长处理,自定义显示名称(terminal路径显示特别长)

1、打开终端 2、步骤 &#xff08;1&#xff09;修改~/.zshrc文件 nano ~/.zshrc&#xff08;2&#xff09;添加或修改PS1&#xff0c;我是自定义了名字为“macminiPro” export PS1"macminiPro$ "&#xff08;3&#xff09;使用 nano: Ctrl o &#xff08;字母…

uniapp关闭sourceMap的生成,提高编译、生产打包速度

警告信息&#xff1a;[警告⚠] packageF\components\mpvue-echarts\echarts.min.js 文件体积超过 500KB&#xff0c;已跳过压缩以及 ES6 转 ES5 的处理&#xff0c;手机端使用过大的js库影响性能。 遇到问题&#xff1a;由于微信小程序引入了mpvue-echarts\echarts.min.js&…

PyTorch 模型转换为 ONNX 格式

PyTorch 模型转换为 ONNX 格式 在深度学习领域&#xff0c;模型的可移植性和可解释性是非常重要的。本文将介绍如何使用 PyTorch 训练一个简单的卷积神经网络&#xff08;CNN&#xff09;来分类 MNIST 数据集&#xff0c;并将训练好的模型转换为 ONNX 格式。我们还将讨论 PTH …

Three.js 和其他 WebGL 库 对比

在WebGL开发中&#xff0c;Three.js是一个非常流行的库&#xff0c;它简化了3D图形的创建和渲染过程。然而&#xff0c;市场上还有许多其他的WebGL库&#xff0c;如 Babylon.js、PlayCanvas、PIXI.js 和 Cesium&#xff0c;它们也有各自的特点和优势。本文将对Three.js 与这些常…

通过 JNI 实现 Java 与 Rust 的 Channel 消息传递

做纯粹的自己。“你要搞清楚自己人生的剧本——不是父母的续集&#xff0c;不是子女的前传&#xff0c;更不是朋友的外篇。对待生命你不妨再大胆一点&#xff0c;因为你好歹要失去它。如果这世上真有奇迹&#xff0c;那只是努力的另一个名字”。 一、crossbeam_channel 参考 cr…

摆脱复杂配置!使用MusicGPT部署你的私人AI音乐生成环境

文章目录 前言1. 本地部署2. 使用方法介绍3. 内网穿透工具下载安装4. 配置公网地址5. 配置固定公网地址 前言 今天给大家分享一个超酷的技能&#xff1a;如何在你的Windows电脑上快速部署一款文字生成音乐的AI创作服务——MusicGPT&#xff0c;并且通过cpolar内网穿透工具&…

挑战用React封装100个组件【001】

项目地址 https://github.com/hismeyy/react-component-100 组件描述 组件适用于需要展示图文信息的场景&#xff0c;比如产品介绍、用户卡片或任何带有标题、描述和可选图片的内容展示 样式展示 代码展示 InfoCard.tsx import ./InfoCard.cssinterface InfoCardProps {ti…

搭建帮助中心到底有什么作用?

在当今快节奏的商业环境中&#xff0c;企业面临着日益增长的客户需求和竞争压力。搭建一个有效的帮助中心对于企业来说&#xff0c;不仅是提升客户服务体验的重要途径&#xff0c;也是优化内部知识管理和提升团队效率的关键。以下是帮助中心在企业运营中的几个关键作用&#xf…

学习threejs,使用CubeCamera相机创建反光效果

&#x1f468;‍⚕️ 主页&#xff1a; gis分享者 &#x1f468;‍⚕️ 感谢各位大佬 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍⚕️ 收录于专栏&#xff1a;threejs gis工程师 文章目录 一、&#x1f340;前言1.1 ☘️CubeCamera 立方体相机 二、…

微前端-MicroApp

微前端即是由一个主应用来集成多个微应用&#xff08;可以不区分技术栈进行集成&#xff09; 下面是使用微前端框架之一 MicroApp 对 react微应用 的详细流程 第一步 创建主应用my-mj-app 利用脚手架 npx create-react-app my-mj-app 快速创建 安装 npm install --save rea…

深度学习—BP算法梯度下降及优化方法Day37

梯度下降 1.公式 w i j n e w w i j o l d − α ∂ E ∂ w i j w_{ij}^{new} w_{ij}^{old} - \alpha \frac{\partial E}{\partial w_{ij}} wijnew​wijold​−α∂wij​∂E​ α为学习率 当α过小时&#xff0c;训练时间过久增加算力成本&#xff0c;α过大则容易造成越过最…

wp the_posts_pagination 与分类页面搭配使用

<ul> <?php while( have_posts() ) : the_post(); <li > <a href"<?php the_permalink(); ?>"> <?php xizhitbu_get_thumbnail(thumb-pro); ?> </a> <p > <a href&q…

深度学习-49-AI应用实战之基于HyperLPR的车牌识别

文章目录 1 车牌识别系统1.1 识别原理1.1.1 车牌定位1.1.2 字符识别2 实例应用2.1 安装hyperlpr32.2 识别结果2.3 可视化显示2.4 结合streamlit3 附录3.1 PIL.Image转换成OpenCV格式3.2 OpenCV转换成PIL.Image格式3.3 st.image嵌入图像内容3.4 参考附录1 车牌识别系统 车牌识别…

ShuffleNet V2:高效卷积神经网络架构设计的实用指南

摘要 https://arxiv.org/pdf/1807.11164 当前&#xff0c;神经网络架构设计大多以计算复杂度的间接指标&#xff0c;即浮点运算数&#xff08;FLOPs&#xff09;为指导。然而&#xff0c;直接指标&#xff08;例如速度&#xff09;还取决于其他因素&#xff0c;如内存访问成本…

在C#中使用OpenCV的.net包装器EmguCV

Emgu.CV OpenCvSharp 两个库都是OpenCV的C#封装。这里不讨论优劣&#xff0c;两个都有相应的用途。 下载安装4.6.0.5131&#xff0c;执行文件exe https://sourceforge.net/projects/emgucv/files/emgucv/4.6.0/ 安装到一个目录下&#xff0c;这里安装到H:\Emgu\ 目录下。…

HarmonyOS:@Provide装饰器和@Consume装饰器:与后代组件双向同步

一、前言 Provide和Consume&#xff0c;应用于与后代组件的双向数据同步&#xff0c;应用于状态数据在多个层级之间传递的场景。不同于上文提到的父子组件之间通过命名参数机制传递&#xff0c;Provide和Consume摆脱参数传递机制的束缚&#xff0c;实现跨层级传递。 其中Provi…