0%

树莓派外设开发

树莓派外设开发的一些记录。

mmap 操作 IO 的同步问题

在用树莓派 IO 模拟 SPI,频率高于 1MHz 就经常读取错误,逻辑分析仪抓波发现是时钟有异常:

image-20201208213936798

如红框内,SCK 应该在箭头处拉低,正确的波形如红色线标出,但是实际上却延迟了一段时间才拉低,导致了波形异常。

可以用 msync 函数来把共享内容的改变直接写回到磁盘文件,但是此函数效率太低,所以用了个折中的办法,在拉低/高 IO 后,循环读取 IO 电平直到生效为止。

IO 反转速度问题

写了一个 SPI bitbang 驱动,发现 IO 翻转速度达不到理想,测试看是代码运行花费时间较长,默认的编译是 -O0,也就是不优化,看了 OpenOCD 是 -O2,于是也设置为 -O2,速度就提高了 2 倍。

WiringPi

https://github.com/WiringPi

https://blog.csdn.net/messidona11/article/details/71514329?fps=1&locationNum=11

http://www.waveshare.net/study/article-742-1.html

RPi.GPIO目前还不支持I2C、SPI等接口,且缺乏高精度定时功能。wiringpi除了提供C语言接口之外,目前已经支持Python等语言的扩展。对于GPIO的操作非常强大

安装 WiringPi Library:

1
sudo apt-get install wiringpi

安装 WiringPi Python:

1
pip install wiringpi

读取树莓派引脚表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
$ gpio readall
+-----+-----+---------+------+---+---Pi 3---+---+------+---------+-----+-----+
| BCM | wPi | Name | Mode | V | Physical | V | Mode | Name | wPi | BCM |
+-----+-----+---------+------+---+----++----+---+------+---------+-----+-----+
| | | 3.3v | | | 1 || 2 | | | 5v | | |
| 2 | 8 | SDA.1 | IN | 1 | 3 || 4 | | | 5v | | |
| 3 | 9 | SCL.1 | IN | 1 | 5 || 6 | | | 0v | | |
| 4 | 7 | GPIO. 7 | IN | 1 | 7 || 8 | 0 | IN | TxD | 15 | 14 |
| | | 0v | | | 9 || 10 | 1 | IN | RxD | 16 | 15 |
| 17 | 0 | GPIO. 0 | IN | 0 | 11 || 12 | 0 | IN | GPIO. 1 | 1 | 18 |
| 27 | 2 | GPIO. 2 | IN | 0 | 13 || 14 | | | 0v | | |
| 22 | 3 | GPIO. 3 | IN | 0 | 15 || 16 | 0 | IN | GPIO. 4 | 4 | 23 |
| | | 3.3v | | | 17 || 18 | 0 | IN | GPIO. 5 | 5 | 24 |
| 10 | 12 | MOSI | IN | 0 | 19 || 20 | | | 0v | | |
| 9 | 13 | MISO | IN | 0 | 21 || 22 | 0 | IN | GPIO. 6 | 6 | 25 |
| 11 | 14 | SCLK | IN | 0 | 23 || 24 | 1 | IN | CE0 | 10 | 8 |
| | | 0v | | | 25 || 26 | 1 | IN | CE1 | 11 | 7 |
| 0 | 30 | SDA.0 | IN | 1 | 27 || 28 | 1 | IN | SCL.0 | 31 | 1 |
| 5 | 21 | GPIO.21 | IN | 1 | 29 || 30 | | | 0v | | |
| 6 | 22 | GPIO.22 | IN | 1 | 31 || 32 | 0 | IN | GPIO.26 | 26 | 12 |
| 13 | 23 | GPIO.23 | IN | 0 | 33 || 34 | | | 0v | | |
| 19 | 24 | GPIO.24 | IN | 0 | 35 || 36 | 0 | IN | GPIO.27 | 27 | 16 |
| 26 | 25 | GPIO.25 | IN | 0 | 37 || 38 | 0 | IN | GPIO.28 | 28 | 20 |
| | | 0v | | | 39 || 40 | 0 | IN | GPIO.29 | 29 | 21 |
+-----+-----+---------+------+---+----++----+---+------+---------+-----+-----+
| BCM | wPi | Name | Mode | V | Physical | V | Mode | Name | wPi | BCM |
+-----+-----+---------+------+---+---Pi 3---+---+------+---------+-----+-----+

GPIO

http://wiringpi.com/examples/blink/

配置上下拉:

1
2
wiringPiSetup();
pullUpDnControl(13, PUD_UP);

SPI

https://blog.csdn.net/messidona11/article/details/73957275?utm_source=gold_browser_extension

官方似乎没有提供详细的 API 文档,但是可以直接看源码:https://github.com/WiringPi/WiringPi/blob/master/wiringPi/wiringPiSPI.c

1
2
3
4
int wiringPiSPIGetFd     (int channel) ;
int wiringPiSPIDataRW (int channel, unsigned char *data, int len) ;
int wiringPiSPISetupMode (int channel, int speed, int mode) ;
int wiringPiSPISetup (int channel, int speed) ;

Raspian 默认不使能 SPI,需要在 raspi-config 内 enable SPI interface。

在 /dev 内会出现两个 SPI 设备 spidev0.0 和 spidev0.1,这两个设备共用 MOSI,MISO 和 SCLK,NSS 分别是 CE0 和 CE1,引脚序号如下:

1
2
3
4
5
6
7
+-----+-----+---------+------+---+---Pi 3---+---+------+---------+-----+-----+
| BCM | wPi | Name | Mode | V | Physical | V | Mode | Name | wPi | BCM |
+-----+-----+---------+------+---+----++----+---+------+---------+-----+-----+
| 10 | 12 | MOSI | IN | 0 | 19 || 20 | | | 0v | | |
| 9 | 13 | MISO | IN | 0 | 21 || 22 | 0 | IN | GPIO. 6 | 6 | 25 |
| 11 | 14 | SCLK | IN | 0 | 23 || 24 | 1 | IN | CE0 | 10 | 8 |
| | | 0v | | | 25 || 26 | 1 | IN | CE1 | 11 | 7 |

注意,wiringpi 的作者 Gordon 只提供了 C/C++ 版本实现,其他语言的 wrappers 由其他开发者提供,如 Python 是 Gadgetoid 实现的。

Other wiringPi resources:

Python 的 wiringpi 代码仓库:https://github.com/WiringPi/WiringPi-Python

使用 Python wiringpi 的 SPI 要特别注意,wiringPiSPIDataRW( channel ,data ) 的 data 参数是会被修改的,由于没有仔细看文档,我被这个坑了很久。

SPI:

The wiringPiSPIDataRW() function needs to be passed a bytes object in Python 3. In Python 2, it takes a string. The following should work in either Python 2 or 3:

1
2
3
wiringpi.wiringPiSPISetup(channel, speed)
buf = bytes([your data here])
retlen, retdata = wiringpi.wiringPiSPIDataRW(0, buf)

Now, retlen will contain the number of bytes received/read by the call. retdata will contain the data itself, and ==in Python 3, buf will have been modified to contain it as well== (that won’t happen in Python 2, because then buf is a string, and strings are immutable).

令人蛋痛的是不管用 bytes() 还是 copy(),最后 data 的值还是被修改了,只能用 buf =bytes.fromhex(data.hex()) 或者 buf = bytes(bytearray(data)) 这样的方法来避免问题了。

更蛋痛的是作者自己也说了,这个不是官方移植版本的,那就意味着可能会有很多碧游鸡,在使用过程中果然遇到了一些坑爹的地方,比如 wiringPiSPIDataRW 的 data 参数超过了 0x1000 就会 segment fault 等。

Note

This is an unofficial port of Gordon’s WiringPi library. Please do not email Gordon if you have issues, he will not be able to help.

For support, comments, questions, etc please join the WiringPi Discord channel: https://discord.gg/SM4WUVG

所以,在官方移植 Python之前,还是不要用这个坑爹的仓库了。

2018-04-06 更新

看来我是冤枉 Gadgetoid 这位大神了,wiringpi 中 SPI 一次发送的数据本来就最大只有 4K。

不过实际测试下来,读取 2M 的 SPI Flash,python 用 2.9s, C 用了 1.5s,几乎是 C 的 2 倍多时间,所以还是 C 的效率快。

SPI Flash

https://github.com/nopnop2002/Raspberry-W25Q64/blob/master/W25Q64.c

一个日本人用 wiringpi 实现的 SPI Flash 驱动。

树莓派安装 RTC 时钟

安装 RTC 时钟后,树莓派就不用网络时间来同步了(可以不接外网)。

https://www.raspberrypi-spy.co.uk/2015/05/adding-a-ds3231-real-time-clock-to-the-raspberry-pi/

DS1307 or DS3231?

Modules based on the DS1307 and DS3231 chips are popular devices and you’ll see them for sale from various retailers. I purchased both types and quickly realised my DS1307 modules were useless. My advice would be to go for a DS3231 based module. They are more accurate and run happily from 3.3V. My two “Tiny RTC” DS1307 modules went straight in the bin.

I2C Setup

As with all I2C devices you must configure the I2C interface. This is quite easy to do and explained in my Enabling The I2C Interface On The Raspberry Pi tutorial.

DS3231 Module Setup

Now we need to modify a system file using :

1
sudo nano /etc/modules

If it isn’t already there add “rtc-ds1307” to the bottom so it looks something like :

1
2
3
4
snd-bcm2835
i2c-bcm2835
i2c-dev
rtc-ds1307

Shutdown the Pi using “sudo halt” and remove the power when it has completed the process.

Interface Test

Power up the Pi and run the following command :

1
sudo i2cdetect -y 1

You should see something similar to this :

DS3231 RTC Module Config

In this example “68” is the hex address of the RTC module on the I2C interface.

I2C Device Setup

To ensure the DS1307 device is setup and the time synchronised when the Pi boots we need to edit another system file :

1
sudo nano /etc/rc.local

Add the following two lines before the exit 0 line :

1
2
echo ds1307 0x68 > /sys/class/i2c-adapter/i2c-1/new_device
hwclock -s

so it looks something like :

DS1307 RTC Module Config

Now reboot the Pi using :

1
sudo reboot

Now when you repeat the i2cdetect command (see above) the 68 will turn into UU :

DS3231 RTC Module Config

Reading The Date And Time

You can read the Pi’s system time using :

1
date

Once correct you can write the system date and time to the RTC module using :

1
sudo hwclock -w

You should be able to read the date and time back from the RTC using :

1
sudo hwclock -r

By separating the commands with a semi-colon you can read back the system time and the RTC time at the same time. Hopefully they should match and look something like this :

DS3231 RTC Module Config

The “hwclock -s” we added to “rc.local” sets the system time from the RTC module.

The Final Test

The final test is to determine if the RTC module is keeping time and that the Pi will use that time when it boots. The best way to do that is to :

  • Power down the Pi
  • Remove the power cable
  • Remove the network connection
  • Attach the Pi to a monitor and keyboard
  • Leave it overnight
  • Power it up and use “date” to see what time the Pi thinks it is
坚持原创技术分享,您的支持将鼓励我继续创作!