使用eFuse释放ESP32的IO12引脚

ESP32的IO12决定了VDD_SDIO,也就是内部的SPI,EXT RAM的IO工作电压,针对WROVER,他是1.8V的,针对WROOM,他是3.3V的,但是他是通过IO12判断,有没有办法释放IO12然后也能决定VDD_SDIO呢.

当然可以,非常简单,使用esp-idf里面的esptool_py工具集吧.首先切换到工具所在目录.

比如WROOM设置(默认ttyUSB0):

espefuse.py set_flash_voltage 3.3V

对于WROVER设置(默认ttyUSB0):

espefuse.py set_flash_voltage 1.8V

运行后会提示你输入BURN,然后确认才能写,因为是一次性的.

这样IO12就释放了.又多一个IO可以用了.

batocera.linux游戏上传和备份

游戏上传

在无法联网的板子上,我们只能通过直接对SD卡读写,拔下SD卡然后往里面复制粘贴进去游戏ROM文件,需要用Linux系统或者虚拟机来打开SD卡,ROM放置的目录在/recalbox/share/roms,里面有分开各种模拟器的目录,把相应的游戏rom或者压缩包粘贴到相应的模拟器目录下就好了。

网络上传

对于能联网的板子,尤其是树莓派3和ZERO w这类有无线WiFi的板子,完全可以通过网络的方式上传游戏文件,而不需要麻烦的插拔SD卡。

首先肯定需要联网,可以看上一篇文章中的联网的步骤,确保板子和PC在同一局域网里面

打开我的电脑中的网络,应该能看到RECALBOX的一个网络位置

进入这个位置,就能看到下面的目录结构

直接复制相应的ROM文件到对应的模拟器目录下面,然后在游戏机上,进入主菜单的游戏设置,然后选择更新游戏列表,之后再进入对应的模拟器目录,就能看到刚刚复制进去的游戏了。

有时候可能在网络中直接看不到这个网络位置,我们也能在我的电脑的地址栏里面,直接输入\\RECALBOX或者对用的ip地址,比如\\192.168.3.18,应该也能进入上面的那个目录结构,然后做类似的操作即可。

游戏备份

通过一个USB设备可以来储存游戏机的ROM文件和游戏存档。

首先肯定需要一个USB设备,比如U盘,推荐的文件系统是FAT32 、EXT4或者NTFS。

备份到U盘

  1. 把U盘插到跑着batocera的板子上
  2. 通过ssh登录到板子的系统里
  3. cd /recalbox/scripts
  4. # ./recalbox-sync.sh list 可以查看你当前连接的USB设备,比如下面的
# ./recalbox-sync.sh list
DEV 5075-67F1 1241 - 1.9G

现在能看到我的U盘,名字是1241,device id是5075-67F1。

# ./recalbox-sync.sh sync 5075-67F1
sending incremental file list
created directory /media/usb0/recalbox
./
bios/
bios/ProSystem.dat
bios/lisez-moi.txt
bios/readme.txt
bios/fba/
bios/fba/samples/
....
system/ssh/dropbear_rsa_host_key
system/udev/
system/udev/rules.d/

sent 66,232,322 bytes  received 5,500 bytes  3,080,828.93 bytes/sec
total size is 66,193,491  speedup is 1.00
synchronizing disk

到这里为止,U盘上就已经有备份好的数据了,这时候拔下来的话,就能看到一个recalbox的目录

完全使用U盘来存储

游戏机支持完全使用U盘来存储ROM和存档,也就意味着系统和数据的分离,你可以使用很小的SD卡来存着游戏机的核心系统,ROM和存档分离着放在U盘上。

坏处是每次要放游戏进入的时候,得断电之后把U盘拔下来,放到U盘里面。

好处是假如系统坏了,或者你有好几个游戏机,只要插上U盘就能跟之前一样的使用了。

具体操作很简单:

  1. 打开游戏机里面的主菜单
  2. 选择系统设置->存储设备
  3. 选择你的USB设备,重启即可

游戏存档

在游戏过程中,按下HotKey+Y就能存档,按下HotKey+X就能载入存档。

而且系统有好几个存档槽,通过HotKey+Up/Down就能切换当前选择的存档槽了。

batocera.linux基本配置

修改分辨率

batocera.linux适用于各种分辨率的屏幕,也就意味着除了各类常规的显示器,也适用于不同的小的屏幕,比如这个5寸触摸屏(系统不支持触摸)800×480的分辨率,就以这个屏为例:

首先需要修改/boot目录下的config.txt

将下面这段加到里面去

framebuffer_width=800
framebuffer_height=480
hdmi_force_hotplug=1
hdmi_group=2
hdmi_mode=87
hdmi_cvt  800  480  60  6  0  0  0 
device_tree=bcm2710-rpi-3-b.dtb
dtparam=spi=on

把/SHARE/system/recalbox.conf中的”CEA 4 HDMI” 改成 “DMT 87 HDMI”

 

在前端页面单击start键,进入这个主设置界面

系统设置

可以查看当前板子的信息,存储状态,修改语言,超频,备份和安全之类的设置。

游戏设置

包括一些显示比例、回放功能、自动保存/加载、主题之类的设置,还可以针对某个模拟器进行深层的配置。

手柄设置

游戏中的按键和以前玩的映射位置一样,但是通过HotKey也会有一些特殊的结合键,也就是说一开始必须要设置一个HotKey(不然可能进入一个游戏之后就出不来了)。

Hotkey + Y → 储存进度
Hotkey + X → 载入进度
Hotkey + Up → 选择存储槽
Hotkey + Down → 选择存储槽

Hotkey + Start → 结束游戏并返回到主界面
Hotkey + A → 重置游戏
Hotkey + B → Retroarch菜单
Hotkey + L1 → 截屏

Hotkey + Right → 加速游戏
Hotkey + Left → 回放

Hotkey + R2 → 下一个 shader preset
Hotkey + L2 → 上一个 shader preset

在这个设置页面可以设置手柄,添加蓝牙手柄,配置1P、2P之类的多个玩家。

UI设置

屏幕保护时间、样式,帧率显示、主题设置都在这个页面完成。

声音设置

主要用来设置输出的接口是板上端口还是HDMI,比如用下面的这个配套功放板来测试

网络设置

进入网络设置之后,假如是WiFi的话,默认是没有开启的,需要选择enable,然后输入SSID和KEY,过段时间就能连接上了。

同时batocera的ssh是默认开启的,也就意味着我们可以通过Putty或者终端ssh连上去,默认root的密码是recalboxroot;除了ssh之外,我们通过网络也能可以非常方便地把一些游戏文件通过网络直接传上去,甚至还能通过网络使用虚拟键盘和虚拟手柄。

在浏览器打开batocera的ip地址,就能看到有三个选项,以虚拟手柄为例,在这个网页可以用鼠标点击来当作手柄用,但是当你在手机或者平板打开的时候,他就真的像一个手柄一样,而且还是无线手柄。

抓取

这个功能是用来抓取每个模拟器上每个游戏的信息和封面的,连接到一些网站可能用爬虫抓取。

抓取成功之后,在游戏选择界面也会有所不同。

Kodi媒体中心

系统附带的一个媒体中心,在前端通过X按键或者在设置界面可以进入这个媒体中心,里面可以看电影、听音乐,不过很多按键会和之前不同,用起来稍微有些麻烦。

batocera.linux——又一个复古游戏操作系统

batocera.linux是一个与retropie类似针对复古游戏的操作系统,在PC和一些开发板比如树莓派、Odroid上都可以运行。

batocera.linux

batocera.linux可以安装在一个U盘里面,对PC而言就是即插即玩的,对于树莓派和Odroid而言,就需要把整个系统烧写到SD卡里面去才能运行了。

batocera其实来源于recalbox,后者本身也是一个非常出色的复古游戏操作系统,但是recalbox这个系统里面有不少商用成分,于是有人把这些替换成开源的部件,换了名字叫batocera,所以整个项目而言,不少的底层模拟器等都是相似甚至相同的,性能和功能上都和recalbox保持一致。

目前支持的设备包括

  • 32位、64位PC
  • 树莓派Zero、1、2、3
  • Odroid XU4、C2

支持绝大多数的游戏模拟器,比如Game Boy,NES。

烧写

以树莓派为例,烧写方法与Raspbian等系统一样,首先根据板子的型号在这个网站下载对应的镜像文件。

下载并解压之后,用Win32DIskImager就能把整个镜像文件烧写到SD卡了。

初次配置

通过HDMI线连接电视机或者显示器

给树莓派正常供电

插上游戏手柄

 

按照上面的三个步骤后首次运行,如果不是正常分辨率的显示器,可能会出现一些显示问题;插上新的手柄之后必须配置之后才能使用。

按照提示长按某个键来进入配置手柄的界面

 

对应的按键和名字单击,假如不需要配置某个键,长按就会跳过

 

具体的试玩效果可以看上面的视频。

 

 

microPython高级应用

内部文件系统

microPython支持标准的Python的文件模块,可以使用open()这类原生函数。

需要注意的是esp32上实时资源少,需要及时关闭掉一些file、socket。

创建一个文件

>>> f = open('data.txt', 'w')
>>> f.write('some data')
9
>>> f.close()

其中这个9是指write()函数写进去的字节数

查看一个文件

>>> f = open('data.txt')
>>> f.read()
'some data'
>>> f.close()

文件目录操作

>>> import os   # 引用os模块
>>> os.listdir()    # 查看当前目录下的所有文件
['boot.py', 'port_config.py', 'data.txt']
>>> os.mkdir('dir') # 创建目录
>>> os.remove('data.txt')   # 删除文件

esp启动顺序

首先运行_boot.py这个脚本,把文件系统挂载上,这个部分一般是固定的,不推荐用户来修改,可能会出很多奇怪的问题。

当文件系统挂载成功后,运行boot.py,在这个脚本里面,用户可以设置一些在REPL里面需要使用的变量或者函数,每次重启esp32,这个脚本也会运行一次,但是如果这个地方写错了代码, 比如进入了死循环之类的,你就需要重新刷固件了。

最后系统会从文件系统运行main.py(如果不存在,就不会运行),这个文件就是用来每次启动的时候运行用户程序而不是进入REPL的,对于一些小的脚本,你可以直接写成一个main.py名字的文件,不过也会推荐你把一个大应用分散来写,写成多个小程序,在main.py里面这么写就好了:

import my_app

my_app.main()

设置开机自启动的脚本

对boot.py和main.py这两个文件进行修改都可以,比如对main.py进行修改:

>>> file = open("main.py", 'w')
>>> file.write("""import time
... for i in range(0,10):
...     time.sleep(1)
...     print(i)""")
64
>>> file.close()

通过快捷键ctrl+D,软启动esp32,就能看到上面的效果了

>>>
PYB: soft reboot
0
1
2
3
4
5
6
7
8
9
MicroPython v1.9.1-394-g79feb956 on 2017-08-03; ESP32 module with ESP32
Type "help()" for more information.
>>>

网络socket应用

简单的连接WiFi和设置热点可以看上一篇教程,成功之后就可以考虑TCP socket连接了。

在这里我们可以用socket模块,但其实有更加方便的模块,urequests(u表示这个模块和标准python的模块相比有许多没有方法没有实现):

import urequests

r = urequests.get('http://www.baidu.com')   # 发起HTTP的GET请求
r.text  # 查看服务器返回的内容<span id="mce_marker" data-mce-type="bookmark" data-mce-fragment="1">​</span>

urequests实现了主要的几个方法,比如get、post、put、delete这几种请求,在网络方面使用起来非常方便。 

microPython常用函数

microPython常用函数

microPython的函数很多

machine module

CPU主频

import machine

machine.freq()      # 获得当前CPU频率
machine.freq(160000000)     # 设置当前CPU频率

控制引脚

from machine import Pin

p0 = Pin(0, Pin.OUT)    # GPIO0设置为输出模式
p0.value(1)             # p0输出高电平
p0.value(0)             # p0输出低电平
p0.value()              # 当前p0设置的电平

p2 = Pin(2, Pin.IN)     # GPIO2设置为输入模式
p2.value()              # p2的电平
p3 = Pin(3, Pin.IN, Pin.PULL_UP)    # GPIO3设置为上拉的输入模式
p4 = Pin(4, Pin.OUT, value=1)   # 创建Pin对象同时设置初始电平

 

可以设置的GPIO有 0, 1, 2, 3, 4, 5, 12, 13, 14, 15, 16;其中1、3作为REPL的串口使用,16用于从睡眠状态唤醒,使用的时候都需要注意。

PWM

from machine import Pin, PWM

pwm0 = PWM(Pin(0))      # 通过Pin对象来创建PWM对象
pwm0.freq()             # 获得当前的PWM频率
pwm0.freq(1000)         # 设置PWM频率
pwm0.duty()             # 获得当前的PWM占空比
pwm0.duty(200)          # 设置占空比
pwm0.deinit()           # 关闭PWM

pwm2 = PWM(Pin(2), freq=500, duty=512) # 创建PWM同时设置参数

 

除了GPIO16都可以使用PWM,频率参数的范围是1到1000,占空比参数的范围是0到1023。

ADC

from machine import ADC

adc = ADC(0)    # 在ADC引脚上创建ADC对象
adc.read()      # 获取ADC值,范围是0-1024

 

软SPI

可以用于所有的引脚

from machine import Pin, SPI

spi = SPI(-1, baudrate=100000, polarity=1, phase=0, sck=Pin(0), mosi=Pin(2), miso=Pin(4))   # 创建SPI对象

spi.init(baudrate=200000) # 设置波特率

spi.read(10)            # 读取10字节
spi.read(10, 0xff)      # 读取十字节,并写出0xff

buf = bytearray(50)     # 创建一个缓冲字节流
spi.readinto(buf)       # 读入到这个字节流
spi.readinto(buf, 0xff) # 读入字节流并发送0xff

spi.write(b'12345')     # 发送5个字节

buf = bytearray(4)      
spi.write_readinto(b'1234', buf) # 发送并读取到buf
spi.write_readinto(buf, buf) # 发送buf并读取到buf

 

硬件SPI

硬件SPI更快(达到80Mhz),但是只适用于特定的引脚:
MISO是GPIO12,MOSI是GPIO13,SCK是GPIO14

和软串口一样有同样的方法函数,只是构造函数不同

from machine import Pin, SPI

hspi = SPI(1, baudrate=80000000, polarity=0, phase=0)

 

SPI(0)被用于FlashROM,不对用户开放

I2C

I2C适用于所有的引脚

i2c = I2C(scl=Pin(5), sda=Pin(4), freq=100000)

i2c.readfrom(0x3a, 4)   # 从0x3a读取4字节
i2c.writeto(0x3a, '12') # 发送12到0x3a

buf = bytearray(10)     # 创建十字节的缓冲字节流
i2c.writeto(0x3a, buf)  # 发送字节流到0x3a

 

深度睡眠模式

连接GPIO16和reset引脚

import machine

# 配置RTC.ALARM0来唤醒设备
rtc = machine.RTC()
rtc.irq(trigger=rtc.ALARM0, wake=machine.DEEPSLEEP)

# 检查是否reset是否是由唤醒引起的
if machine.reset_cause() == machine.DEEPSLEEP_RESET:
    print('woke from a deep sleep')

# 设置RTC.ALARM0在10秒后唤醒设备
rtc.alarm(rtc.ALARM0, 10000)

# 设备进入深度睡眠
machine.deepsleep()

 

定时器

from machine import Timer

tim = Timer(-1)
tim.init(period=5000, mode=Timer.ONE_SHOT, callback=lambda t:print(1))
tim.init(period=2000, mode=Timer.PERIODIC, callback=lambda t:print(2))

 

其中period的单位是毫秒

network module

连接WIFI

import network

wlan = network.WLAN(network.STA_IF) # 创建WLAN STA接口
wlan.active(True)       # 激活WLAN
wlan.scan()             # 扫描附近的WIFI
wlan.isconnected()      # 查看当前是否已经连上WIFI
wlan.connect('essid', 'password') # 连接到附近的WIFI
wlan.config('mac')      # 获得mac地址
wlan.ifconfig()         # 返回IP/子网掩码/网关/DNS地址

 

可以把下面这个函数放到boot.py里面,每次启动一行代码就能连接WIFI了

def do_connect(ssid, password):
    import network
    wlan = network.WLAN(network.STA_IF)
    wlan.active(True)
    if not wlan.isconnected():
        print('connecting to network...')
        wlan.connect(ssid, password)
        while not wlan.isconnected():
            pass
    print('network config:', wlan.ifconfig())

 

创建WIFI

ap = network.WLAN(network.AP_IF) # 创建一个AP
ap.active(True)         # 激活这个AP
ap.config(essid='ESP-AP') # 设置这个AP的SSID

 

连接上WIFI之后就能进一步使用socket库了

time module

延时和计时

import time

time.sleep(1)               # 延时一秒
time.sleep_ms(500)          # 延时500毫秒
time.sleep_us(10)           # 延时10微秒
start = time.ticks_ms()     # 得到内部计时的某个时间点
delta = time.ticks_diff(time.ticks_ms(), start) # 计算过去的时间段的长度

 

单总线

单总线协议适用于所有的引脚

from machine import Pin
import onewire

ow = onewire.OneWire(Pin(12)) # 在GPIO12上创建单总线协议
ow.scan()               # 返回总线上的设备列表
ow.reset()              # 重置总线
ow.readbyte()           # 读取一个字节
ow.writebyte(0x12)      # 往总线写0x12
ow.write('123')         # 往总线写'123'
ow.select_rom(b'12345678') # 选择特定设备

 

ds18x20

用于DS18S20和DS18B20的驱动库

import time, ds18x20
ds = ds18x20.DS18X20(ow)
roms = ds.scan()
ds.convert_temp()
time.sleep_ms(750)
for rom in roms:
    print(ds.read_temp(rom))

 

DHT驱动

DHT适用于所有的引脚

import dht
import machine

d = dht.DHT11(machine.Pin(4))
d.measure()
d.temperature() # eg. 23 (°C)
d.humidity()    # eg. 41 (% RH)

d = dht.DHT22(machine.Pin(4))
d.measure()
d.temperature() # eg. 23.6 (°C)
d.humidity()    # eg. 41.3 (% RH)

 

在esp32上配置运行microPython

microPython是该团队针对微处理器(一般指无法运行Linux操作性系统)做出的一个python的实现,官方有一些支持的板子,esp32作为一块性能高于esp8266,同时也具备很不错的wifi、蓝牙功能的开发板,也在microPython的支持之中,当前开发文档参考microPython on esp8266,毕竟是同一类板子。

下载固件并烧写

https://micropython.org/download#esp32

另外需要esptool.py,通过pip来安装。

pip install esptool.py

首先最好擦除原有的固件

sudo esptool.py --chip esp32 --port /dev/ttyUSB0 erase_flash

接着把刚刚下载的固件烧写上去

sudo esptool.py --chip esp32 --port /dev/ttyUSB0 write_flash -z 0x1000 ~/Downloads/esp32-20170803-v1.9.1-394-g79feb956.bin

连接ESP32

在Linux上推荐使用picocom来打开串口连接ESP32,Windows直接使用putty也行,注意要指定115200的波特率

$ sudo apt-get install picocom
$ sudo picocom -b 115200 /dev/ttyUSB0

使用Python

 

用picocom打开串口之后,按一下板子上的rst,可以看到串口出来的是一个python的REPL(交互解释器),当前一般的开发方法是通过串口的这个解释器逐行写代码进去,但是重启之后就会失效,想要断电保存并上电自启动也行,但操作相对会比较麻烦,不在本文的讨论之内。

注意这里的python是python3.4加上一点点3.5的特性,支持绝大部分的python核心数据类型和一些核心库。

最简单的你可以测试一下一些简单的内建函数

这个REPL占用了esp32的UART0(GPIO1=TX,GPIO3=RX),不过它的tab补全非常给力,比pc上的iPython差一点,但比起PC上的python shell是好到哪里去都不知道了。

控制引脚

microPython通过一个叫machine的module来控制引脚

from machine import Pin

p0 = Pin(0, Pin.OUT)    # 设置GPIO0的output模式
p0.value(1)             # 设置IO0为高电平
p0.value(0)             # 设置IO0为低电平

 

比如下面的例子就能用一秒的间隔来闪烁LED

import time
from machine import Pin

p0 = Pin(0, Pin.OUT)
state = 0
for i in range(0, 15):
    p0.value(state)
    state = 1 - state
    time.sleep(1)

另外对于这种比较长的代码,可以事先在PC上写完,然后在REPL中通过CRTL+E进入粘贴模式,复制粘贴完之后CTRL+D就行了。

microPython还有WiFi、PWM、SPI、I2C等一系列功能,以后的文章中会一个一个讲过来。

#Raspbian C应用编程#strlen 和 sizeof 的区别

上一次已经做了个CPU温度频率记录器,细心的同学如果打开文件一看,就哭晕了.

跟想象中好大差别啊:

为什么这么多^@啊,原来这是0x00的转义字符啊,因为我们上一节只用到了sizeof,所以不管buf多大,我们都写了1K数据进去.简直是浪费.把sizeof换成strlen,每次生成字符串前都清空buf.

这样每次写入的数量就心中有数了,strlen获取的是字符串有效长度,sizeof获取的是字符串的整个容器大小.

经过上述调整的代码,看起来就不违和了.

要想学习更多Linux C知识,多多关注本站吧,本站所有调试基于树莓派学习,现在入手买个套件,更方便你更进一步哦.

程序下载:

cpu_logger (3KB)

#Raspbian C应用编程#Linux 文件I/O – 系统温度主频记录器

UNIX/Linux 的一个基本哲学是”一切皆文件”.不仅普通的文件,甚至连各种字符设备、 块设备、套接字等都被当成文件对待,尽管它们的类型差异很大,但 UNIX/Linux 为它们提 供的操作界面却是相同的. Linux 把大部分系统资源当作文件并呈现给用户,用户只需按照文件 I/O 的方式,就能完成数据的输入输出. Linux 文件按其代表的具体对象,可大致分类为:

  1. 普通文件,即一般意义上的文件、磁盘文件;
  2. 设备文件,代表的是系统中一个具体的设备;
  3. 管道文件、 FIFO 文件,一种特殊文件,常用于进程间通信;
  4. 套接字( socket)文件,主要用在网络通信方面.

文件 I/O 的常用操作方法有”打开”、”关闭”、”读”和”写”等.只要是文件,所有文件都有前面两种方法.系统提供了文件 I/O API,以函数的形式提供给应用程序调用.打开文件对应的函数是 open(),读文件对应的函数是 read(),写文件对应的函数是 write(),关闭文件对应的函数是 close()

文件描述符 是进程中用来表示某个文件的一个变量, 文件描述符的作用,类似于在排队取号,业务员(进程)通过叫号(文件描述符)就能找到来搞事的人(文件)

大多数 Raspbian系统中,可通过命令”ulimit -n”查询到这个数值的大小,但注意,不是所有Linux都有这个指令.

文件描述符 012 在Linux上有特殊意义,我们暂时可以不去理解他.只知道他们比较特殊就行了,所以实际上树莓派只有最多7312个同时打开文件.(难道还不够?)

另外文件操作要涉及几个头文件,死记硬背.要用到哪个函数,就用哪个头文件,就是这样的.

#include<sys/types.h> /* 定义数据类型,如 ssize_t, off_t 等 */
#include <fcntl.h> /* 定义 open, creat 等函数原型,创建文件权限的符号常量 S_IRUSR 等 */
#include <unistd.h> /* 定义 read, write, close, lseek 等函数原型 */
#include <errno.h> /* 与全局变量 errno 相关的定义 */
#include <sys/ioctl.h> /* 定义 ioctl 函数原型 */

我们这就上Pi实践下,还是使用VS + Visual GDB神奇组合.就算你不懂Linux各种繁琐指令也能写出不错的程序.如果还不是特别了解,就参考下之前的文章吧.

下面演示的例子,先以可写方式打开当前目录下的hello.txt文件,如果该文件不存在,则创建文件,再往文件中写入一个字符串Hello, welcome to 52pi.net!,把操作结果输出到终端后,关闭文件. 接着再次以只读模式打开该文件,读取刚才写入的数据,并把结果输出到终端最后关闭该文件.

代码:

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/stat.h>

int main(int argc, char *argv [])
{
	char sz_str [] = "Hello, welcome to 52pi.net!";
	char sz_filename [] = "hello.txt";
	int fd = -1;
	int res = 0;
	char buf[128] = {0x00};
	fd = open(sz_filename, O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH);
	if (fd < 0)
	{
		printf("Open file %s failed,errno = %d.\n", sz_filename, errno);
		return -1;
	}
	
	res = write(fd, sz_str, sizeof(sz_str));
	printf("write %d bytes to %s \n", res, sz_filename);
	fsync(fd);
	close(fd);
	
	fd = open(sz_filename, O_RDONLY);
	if (fd < 0)
	{
		printf("Open file %s failed,errno = %d.\n", sz_filename, errno);
		return -1;		
	}
	
	res = read(fd, buf, sizeof(buf));
	buf[res] = '\0';
	printf("read %d bytes from file %s ,data = %s \n", res, sz_filename, buf);
	close(fd);
	
	return 0;
	
}

测试结果:

那么刚才代码里面的O_WRONLY都是什么定义呢,我们可以右键,转到定义去查看一下.


当然也可以看我解释:

  • O_RDONLY 以只读方式打开文件
  • O_WRONLY 以只写方式打开文件
  • O_RDWR 以可读写方式打开文件. 上述三种旗标是互斥的, 也就是不可同时使用, 但可与下列的旗标利用OR(|)运算符组合.
  • O_CREAT 若欲打开的文件不存在则自动建立该文件.
  • O_EXCL 如果O_CREAT 也被设置, 此指令会去检查文件是否存在. 文件若不存在则建立该文件, 否则将导致打开文件错误. 此外, 若O_CREAT 与O_EXCL 同时设置, 并且欲打开的文件为符号连接, 则会打开文件失败.
  • O_NOCTTY 如果欲打开的文件为终端机设备时, 则不会将该终端机当成进程控制终端机.
  • O_TRUNC 若文件存在并且以可写的方式打开时, 此旗标会令文件长度清为0, 而原来存于该文件的资料也会消失.
  • O_APPEND 当读写文件时会从文件尾开始移动, 也就是所写入的数据会以附加的方式加入到文件后面.
  • O_NONBLOCK 以不可阻断的方式打开文件, 也就是无论有无数据读取或等待, 都会立即返回进程之中.
  • O_NDELAY 同O_NONBLOCK.
  • O_SYNC 以同步的方式打开文件.
  • O_NOFOLLOW 如果参数pathname 所指的文件为一符号连接, 则会令打开文件失败.
  • O_DIRECTORY 如果参数pathname 所指的文件并非为一目录, 则会令打开文件失败.(Linux特有)

而第二项权限意思是这样的:

  • S_IRWXU00700 权限, 代表该文件所有者具有可读、可写及可执行的权限.
  • S_IRUSR 或S_IREAD, 00400 权限, 代表该文件所有者具有可读取的权限.
  • S_IWUSR 或S_IWRITE, 00200 权限, 代表该文件所有者具有可写入的权限.
  • S_IXUSR 或S_IEXEC, 00100 权限, 代表该文件所有者具有可执行的权限.
  • S_IRWXG 00070 权限, 代表该文件用户组具有可读、可写及可执行的权限.
  • S_IRGRP 00040 权限, 代表该文件用户组具有可读的权限.
  • S_IWGRP 00020 权限, 代表该文件用户组具有可写入的权限.
  • S_IXGRP 00010 权限, 代表该文件用户组具有可执行的权限.
  • S_IRWXO 00007 权限, 代表其他用户具有可读、可写及可执行的权限.
  • S_IROTH 00004 权限, 代表其他用户具有可读的权限
  • S_IWOTH 00002 权限, 代表其他用户具有可写入的权限.
  • S_IXOTH 00001 权限, 代表其他用户具有可执行的权限.

如果不指定,权限就是000了,000代表谁都对这个文件没办法.

从上述我们已经了解到文件基本读写,那么现在开始实践,已知Raspbian系统的核心0的主频是存在/sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq文件,而CPU温度是存在于/sys/class/thermal/thermal_zone0/temp文件.我们读取这两个文件,定时写入到一个文件里面做记录.

所以,记录工程应该长这个样子.

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/stat.h>

#include <time.h>


int main(int argc, char *argv [])
{
	int cpu_freq_fd = -1;
	int cpu_temp_fd = -1;
	int cpu_logger_fd = -1;	
	
	char freq_buf[128] = {0x00};
	char temp_buf[128] = {0x00};
	char log_buf[1024] = {0x00};
	
	time_t rawtime; 
	struct tm * timeinfo; 
	
	cpu_freq_fd = open("/sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq", O_RDONLY);
	cpu_temp_fd = open("/sys/class/thermal/thermal_zone0/temp", O_RDONLY);	
	cpu_logger_fd = open("cpu.log", O_WRONLY | O_CREAT | O_APPEND, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH);
	
	while (1)
	{
		lseek(cpu_freq_fd, 0, SEEK_SET); /* 回到文件的开始 */
		read(cpu_freq_fd, freq_buf, sizeof(freq_buf)); /* 读取文件 */
		
		lseek(cpu_temp_fd, 0, SEEK_SET); 
		read(cpu_temp_fd, temp_buf, sizeof(temp_buf)); 
		
		time(&rawtime); 
		timeinfo = localtime(&rawtime); 
		
		char *now_time = asctime(timeinfo);
		now_time[strlen(now_time) - 1] = 0;
		
		sprintf(log_buf,"%s - CPU = %d MHz - Temp = %d degC\n", now_time, atoi(freq_buf) / 1000, atoi(temp_buf) / 1000);		
		
		write(cpu_logger_fd, log_buf, sizeof(log_buf));
		fsync(cpu_logger_fd);
		
		sleep(1);
	}
	
	close(cpu_freq_fd);
	close(cpu_temp_fd);	
	close(cpu_logger_fd);		
	
	return 0;
	
}

执行效果,是不是很棒,这样就有一个监控程序了.通过程序不难看出,就是不断写cpu.log文件.然后休眠.

此处用到的两个工程:

cpu_temp_logger

file_wr

树莓派抓拍猫咪不良行为

来自52Pi社区的hotgarlic投稿~欢迎广大网友投稿至管理员

Hi, 树莓Pi的创客们,大家好。

今天跟大家分享一下一个很实用的例子,如果你已经玩过我的上一个入门教程,觉得不过瘾,那么就来试试今天的项目。

这次是用Pi制作智能摄像头,监视我家的猫咪,当它在台盆里喝水时就会被摄像头拍下来。当我们人的手或其他东西在台盆上时,摄像头则视而不见。因此要用深度学习技术,让摄像头能够将猫和其他东西区分出来。这里要在keras框架中运行inceptionV3模型,用到finetune技术。

技术介绍

InceptionV3

这是Google设计的深度学习网络框架,由很多层卷积层和一个分类器(全连接神经网络)组成。卷积层是核心,他负责从图片中提取特征,比如边缘,颜色,几何形状等等,还有人的五官,汽车的轮子等等,前一种叫浅特征,后一种叫深特征。而分类器,只做一些是非判断。所以卷积层相当于人类的感知,分类器则是纯粹的计算器。

这个网络特性优异,可以区分1000种类别的图片。因为他卷积层有很多,并且用数万张图片进行训练,从而学到了很多特征。特征被存放在权重文         件中,我们要用他来做摄像头程序。

finetune

inceptionV3的类别其实已经包含了猫。但是,我家的田园土猫,经常被识别成法国斗牛犬(图1),有时还会蹦出一些稀奇古怪的狗的名字(图2)。为了能让inceptionV3认出我家的猫,我必须让inceptionV3多识别几次,记录下它输出的一些奇特类别,然后把它们转换成猫咪类别。但有时猫咪站在台盆上,inceptionV3会输出浴缸,可能是台盆比猫清晰。要是每次这样也道倒算了,问题是台盆空着时,他也认成浴缸(图3),这就很麻烦了。

你可以到basin_cat/full_test/中运行recognize.ipynb,他会把我洗手的图认成皮搋子(图4)。

所以,最好能够训练一个自己的深度网络。但是训练卷积层需要昂贵的硬件设备和几周的时间,好在有了finetune技术。

finetune(图6)。就是不全部借用整个网络,而只是借用卷积层框架和它学到的特征,至于分类器,你必须重新设计,包括给出你自己的类别个数,当然训练用的图片也应该是你自己的。在训练过程中,卷积层的参数不会被修改,但是分类器的参数会改变,直到这个分类器能够分对图片。因为从全部模型做的识别中看出,卷积层学到的特征都还靠谱,他至少知道猫咪是个四条腿的动物,所以这些特征可以拿来利用。

教程

硬件

Pi3B+picamera2+Pi显示器(最好5寸以上),系统:Raspbian Jessie with PIXEL(2017-03-02)

PC/笔记本,系统:Ubuntu 14.04

软件包的安装

我希望你已经对python和Linux比较熟悉,知道如何安装缺少的包,熟悉从GitHub克隆安装软件包。然后在Pi和PC上都安装好下面的包。过程可能比较麻烦,有问题可以联系我hotgarlic@163.com

首先运行:

$sudo apt-update    #系统更新

$sudo apt-upgrade   #系统升级

$python3 get-pip.py  #安装python软件包安装器pip3,在basin_cat文件夹中

tensorflow

他是keras的后台程序。

  • Pi上安装:参考这里,选用第一种pip安装方式,以及python3对应的包。本教程所用

的代码,python包,和包的安装方式也都是针对python3的。

  • PC上安装:具体参考这里,如果打不开,请百度禾斗学上网。
$sudo apt-get install python-pip python-dev

$pip3 install tensorflow

Keras

$git clone https://github.com/fchollet/keras.git

$cd yourfloder/keras

$sudo python3 setup.py install

他操作起来比tensorflow简单许多,值得多多学习,其包含了很多例子和优秀的模型,甚至你可以在他的仓库里找到alpha go的相关算法,这是现在很多人工智能的开源项目里面第二大开发平台,第一就是比较难用的他的后台tensorflow。

jupyter notebook

$sudo pip3 install jupyter

$jupyter notebook     #进入jupyter界面

他是一个非常简洁易用的IDE,适合调试代码,现在越来越多的教程开始使用这个平台。具体用法很简单,这里就不细说了,请百度。

 

h5py

$sudo apt-get install python3-h5py

是用来存取训练分类器权重的软件包。

 

skimage,imageio

$sudo pip3 install -U scikit-image

$sudo pip3 install imageio

他们都是图片处理包。其中Imageio可以将mp4转换成图片。

 

Matplotlib

$python -m pip install -U pip setuptools

$python -m pip install matplotlib

这是可以在IDE中查看图片的工具包,可以不用安装,特别是不需要在Pi上安装。

 

gpac

$sudo apt-get install gpac

$MP4Box -add path/file.264 path/file.mp4    #格式转换命令

只需Pi上安装的包,可以将h264视频转换成MP4格式。

 

操作流程

  • 将整个basin_cat文件夹复制到你的PC上。
  • 打开jupyter,在jupyter中打开ipynb,单步运行代码。
  • 先运行*和2.1之间的代码。之后sample文件夹里会多许多图片,请把图片中没有人,没有猫出现的图片放到basin_cat/label_2/sample中。

  • 再运行其余代码:

将分好类的图片进行扩增

加载inceptionV3模型及其学到的特征

让inceptionV3转换所有扩增好的图片

将inceptionV3转换好的数据作为分类器的训练数据

建立分类器模型

训练分类器

  • 最后h5生成,里面是权重参数,在train.ipynb同文件夹。

 

将ipynb和trial_0.h5一起复制到Pi的同一个文件夹中。

  • 打开jupyter,在jupyter中打开ipynb,单步运行代码。
  • 先是一段预览摄像头的代码,会在屏幕上显示摄像头拍到的图像,你有10秒中调整摄

像头的位置。

  • 然后是加载inceptionV3模型及权重、建立分类器模型的代码。注意这里的分类器一

定要和train.ipynb中一致!

  • 最后是主程序,它循环不断得抓取图片,然后进行识别,如果识别到0类图片,也就是猫咪在台盆里喝水的图,那么将会把这个图片保存在硬盘中,在monitor.ipynb所在的文件夹。逮住猫咪喝水的图片是这样的(图5)。

 

说明

  • random文件夹,是用来存放扩增图片的。扩增图片是从原始的图片中产生的,经过

了随机的几何变形和明暗变化后,原先的图片就可以成倍的增长,可以在样本不多的情况下让模型训练得更好。

  • 你可以拍摄mp4,并存放到sample文件夹,注意要用mp4, 1.mp4, 2.mp4 …等等作

为文件名。

  • 你也可以增加类别,先新建一个label_n文件夹,里面也要保含random和sample

文件夹。同时也要到train.ipynb中修改相应的代码。具体怎么改,请看代码里面的comment,这里就不细说了。当然,你最好也改一下分类器的模型和类别的个数。

  • 如何改进识别的准确度

我在第一次试运行中发现,虽然Pi可以捕捉到猫咪跳上台盆的证据,但有时也会误判。比如Pi会把台盆上的黑裤子当成猫咪。这是因为负样本(不是猫咪的图片)没有包含了黑色裤子的图片。

在这种情况下,从inceptionV3中获得的转换后数据,虽然包含了很多关于猫咪的深度特征,但分类器会故意忽略掉一些。

黑色裤子并不像猫咪那样有两个尖尖的耳朵(深度特征)。当缺乏黑裤子这样的训练样本时,分类器就只会根据颜色(浅特征)来分类。因为对分类器来说,只要看看图片里那块地方颜色是不是黑的,占了多大地方,连续度怎么样,就足以做出正确的判断了。

 

能否充分调动那些特征,使得分类器在判断时采纳这些信息,就取决于你能否给出足够数量的具有迷惑性的数据。

另外,将猫咪拖到其他背景中采集图片,也可以迫使分类器多多参考深度特征进行判断。

所以,你就要有意无意得对Pi进行干扰,让它拍下一些误判的图片,然后把这些图片加入到非猫咪图片的样本中去,再按照流程重新训练一遍。这里给出的代码也已经使得工作简化了许多,而且那些误判的图片并不会很多。

待完善

我还没有制作驱赶猫咪的机构,比如声光报警,喷水,来吓唬猫咪。希望有兴趣的并且同样讨厌猫咪不良行为的创客可以帮我完成。我并不擅长这些硬件方面的知识。

因为我用的是可见光摄像头,而不是红外摄像头,所以光线太暗就会导致误判,因

为很多细节的特征会被淹没。所以我就把昏暗的图片归类到了非猫咪类中,希望猫咪不会发现其中的漏洞。有兴趣的创客可以再加一个红外传感器来辅助一下。

题外话

也许你会认为,图片越多越准确,算什么智能。确实,现在有很多人提出这一的质疑。但是如果没有深度神经网络,卷积这样的算法,即便你拥有所有的图片,你都很难把这些图片抽象成可计算的特征,而现在的深度网络最成功的地方,就是自动提取特征,而且是深层次的特征。相比人工对特征参数化,就已经进步了不少。当然,还远远没有达到理想中的智能的目标。