Handshake pin
This is a output pin for ESP peripheral. ESP peripheral makes use of this pin to convey its readiness for execution of SPI transaction. The host is not supposed to initiate SPI transaction if ESP peripheral has not indicated its readiness. This pin stays high till the end of SPI transaction.
Data ready pin
This is a output pin for ESP peripheral. This pin is used to indicate host that the ESP peripheral wants to send a data packet to it. This pin stays high till the host reads this data packet.
Reset/EN pin
This is a input pin for ESP peripheral. This pin resets ESP peripheral and is mandatory in SPI based ESP-Hosted solution.
Handshake pin
ESP 的输出引脚。高电平表示正在进行ESP32正在通过spi接口和主机进行交互。因此,当该引脚是高电平的时候,主机不应该启动spi数据传输。通俗一点的解释就是:host想给ESP32发送数据,先看一下esp32是否准备好接收数据了。
Data ready pin
ESP 的输出引脚。因为ESP是spi的从设备,不能主动发起传输。因此,该引脚用于弥补SPI协议的不足,告诉SPI主机,esp32想要向主机发送数据了。该引脚保持高电平,直到主机读取该数据包。
Reset/EN pin
ESP 外设的输入引脚。 用于复位 ESP 设备。
- This solution makes use of SPI full duplex commmunication mode. i.e. read and write operations are performed at the same time in same SPI transaction.
- hosted fg方案使用了spi 全双工模式。
- As a protocol, host is not supposed to start a transaction before ESP SPI peripheral device is ready for receiving data. Therefore, through Handshake pin, ESP peripheral indicates host when it is ready for SPI transaction.
- 当esp32端没有准备好接受数据时,host端不能发起传输。esp32端通过handshake管教来表明自己是否准备好。 (hosted端从代码的表现看handshake上升沿的时候会触发传输)。
- To allow seamless data traffic between host and ESP peripheral, ESP peripheral needs to be ready for data reception from host all the time. For that, after completion of every SPI transaction, ESP peripheral immediately queues next SPI transaction.
- esp32端需要随时准备好从host接受数据,因此,每次spi数据传输之后,esp32立即启动下一次传输
- The data transfer protocol works as below:
- Each SPI transaction has a TX buffer and a RX buffer.
- TX buffer contains data which ESP peripheral wants to send to host.
- RX buffer is an empty buffer space, which on the completion of SPI transaction will hold data received from host.
- ESP peripheral initiates SPI transaction by setting TX and RX buffers of size 1600 bytes. This is a maximum buffer size. i.e at a time host can send and receive at max 1600 bytes of data.
- There are two cases with respect to TX buffer here:
- In case if ESP peripheral has no data to transfer to host, a dummy TX buffer of size 1600 bytes is allocated and is set in SPI transaction. Packet length field in payload header of such buffer is set to 0.
- 当esp32没有有效数据发送给主机的时候,dummy数据会被设置到spi传输中。在get_next_tx_buffer函数中当检测到没有有效数据时,会分配dummy数据。
- If ESP peripheral has a valid data buffer to be sent to host, then TX buffer will point to that buffer.
- SPI transaction length is set to 1600 bytes [irrespective of size of TX buffer]
- Once this SPI transaction is submitted to SPI driver on ESP peripheral, Handshake pin is pulled high to indicate host that ESP peripheral is ready for transaction.
- 一旦spi传输提交到了驱动层,handshake引脚会被拉高。
- In case if TX buffer has valid data, Data ready pin is also pulled high by ESP peripheral.
- Host receives an interrupt through Handshake pin. On this interrupt, host needs to decide whether or not to perform SPI transaction.
- If Data ready pin is high, host performs SPI transaction
- Or if host has data to transfer, then host performs SPI transaction
- If both the above conditions are false, then host does not perform SPI transaction. This transaction is then performed later when host has data to be sent or interrupt is received on Data ready pin.
- During this SPI transaction, TX and RX buffers are exchanged on SPI data lines.
- Based on payload header in received buffer, both ESP peripheral and host processes the buffer.
- On completion of transaction, ESP peripheral pulls Handshake pin low. If completed transaction had a valid TX buffer, then it also pulls Data ready pin low.
gpio_hand_shake:
handshake拉高,表示esp32端装载了一个接受或者发送buf,然后host端检测handshake是高,就可以发送数据了,esp32在spi传输数据的时候,将handshake拉低。表示esp32正忙,不允许host发送新数据。
总结:handshake是高,表示esp32空闲,可以传输数据;传输过程中,handshake是低电平。
typedef struct {
int spics_io_num; ///< CS GPIO pin for this device
uint32_t flags; ///< Bitwise OR of SPI_SLAVE_* flags
int queue_size; ///< Transaction queue size. This sets how many transactions can be 'in the air' (queued using spi_slave_queue_trans but not yet finished using spi_slave_get_trans_result) at the same time
uint8_t mode; /**< SPI mode, representing a pair of (CPOL, CPHA) configuration:
- 0: (0, 0)
- 1: (0, 1)
- 2: (1, 0)
- 3: (1, 1)
*/
/* Callback called after the SPI registers are loaded with new data.
*/
slave_transaction_cb_t post_setup_cb;
/* Callback called after a transaction is done.
*/
slave_transaction_cb_t post_trans_cb;
} spi_slave_interface_config_t;
gpio_data_ready:
初始化的是低电平
spi_slave_api.c中,send_task(从to_host_queue中取出数据交给process_tx_pkt 处理) -> process_tx_pkt -> esp_spi_write函数中当向spi_tx_queue写入数据成功后,就会把gpio_data_ready拉高。
spi_transaction_post_process_task -> queue_next_transaction -> get_next_tx_buffer中从spi_tx_queue中取出数据,如果没有取出有效数据,就把这个gpio_data_ready管教拉低,同时,创建dummy数据,以便于host主动向esp32发送数据。
由于spi_transaction_post_process_task是一个task,总有没有取出有效数据的时候,所以gpio_data_ready常态是低电平。
rpi_init.sh文件分析:
#!/bin/sh
RESETPIN=""
BT_INIT_SET="0"
IF_TYPE="sdio"
MODULE_NAME="esp32_${IF_TYPE}.ko"
build_c_demo_app()
{
cd c_support/
make clean
make -j8
make -j8 stress
cd ..
}
build_python_demo_app()
{
cd python_support/
make clean
make -j8
cd ..
}
wlan_init()
{
build_c_demo_app
build_python_demo_app
cd ../host_driver/esp32/
if [ `lsmod | grep esp32 | wc -l` != "0" ]; then
sudo rm /dev/esps0
if [ `lsmod | grep esp32_sdio | wc -l` != "0" ]; then
sudo rmmod esp32_sdio &> /dev/null
else
sudo rmmod esp32_spi &> /dev/null
fi
fi
# For Linux other than Raspberry Pi, Please point
# CROSS_COMPILE -> <Toolchain-Path>/bin/arm-linux-gnueabihf-
# KERNEL -> Place where kernel is checked out and built
# ARCH -> Architecture
make -j8 target=$IF_TYPE CROSS_COMPILE=/usr/bin/arm-linux-gnueabihf- KERNEL="/lib/modules/$(uname -r)/build" ARCH=arm
if [ "$RESETPIN" = "" ] ; then
#By Default, BCM6 is GPIO on host. use resetpin=6
sudo insmod $MODULE_NAME resetpin=6
else
#Use resetpin value from argument
sudo insmod $MODULE_NAME $RESETPIN
fi
if [ `lsmod | grep esp32 | wc -l` != "0" ]; then
echo "esp32 module inserted "
sudo mknod /dev/esps0 c 221 0
sudo chmod 666 /dev/esps0
echo "/dev/esps0 device created"
echo "RPi init successfully completed"
fi
}
bt_init()
{
sudo raspi-gpio set 15 a0 pu
sudo raspi-gpio set 14 a0 pu
sudo raspi-gpio set 16 a3 pu
sudo raspi-gpio set 17 a3 pu
}
usage()
{
echo "This script prepares RPI for wlan and bt/ble operation over esp32 device"
echo "\nUsage: ./rpi_init.sh [arguments]"
echo "\nArguments are optional and are as below"
echo " spi: sets ESP32<->RPi communication over SPI"
echo " sdio: sets ESP32<->RPi communication over SDIO"
echo " btuart: Set GPIO pins on RPi for HCI UART operations"
echo " resetpin=6: Set GPIO pins on RPi connected to EN pin of ESP32, used to reset ESP32 (default:6 for BCM6)"
echo "\nExample:"
echo " - Prepare RPi for WLAN operation on SDIO. sdio is default if no interface mentioned"
echo " # ./rpi_init.sh or ./rpi_init.sh sdio"
echo "\n - Use spi for host<->ESP32 communication. sdio is default if no interface mentioned"
echo " # ./rpi_init.sh spi"
echo "\n - Prepare RPi for bt/ble operation over UART and WLAN over SDIO/SPI"
echo " # ./rpi_init.sh sdio btuart or ./rpi_init.sh spi btuart"
echo "\n - use GPIO pin BCM5 (GPIO29) for reset"
echo " # ./rpi_init.sh resetpin=5"
echo "\n - do btuart, use GPIO pin BCM5 (GPIO29) for reset over SDIO/SPI"
echo " # ./rpi_init.sh sdio btuart resetpin=5 or ./rpi_init.sh spi btuart resetpin=5"
}
parse_arguments()
{
while [ "$1" != "" ] ; do
case $1 in
--help | -h )
usage
exit 0
;;
sdio)
IF_TYPE=$1
;;
spi)
IF_TYPE=$1
;;
resetpin=*)
echo "Recvd Option: $1"
RESETPIN=$1
;;
btuart)
echo "Recvd Option: $1"
BT_INIT_SET="1"
;;
*)
echo "$1 : unknown option"
usage
exit 1
;;
esac
shift
done
}
parse_arguments $*
if [ "$IF_TYPE" = "" ] ; then
echo "Error: No protocol selected"
usage
exit 1
else
echo "Building for $IF_TYPE protocol"
MODULE_NAME=esp32_${IF_TYPE}.ko
fi
if [ "$IF_TYPE" = "spi" ] ; then
rm spidev_disabler.dtbo
# Disable default spidev driver
dtc spidev_disabler.dts -O dtb > spidev_disabler.dtbo
sudo dtoverlay -d . spidev_disabler
fi
if [ `lsmod | grep bluetooth | wc -l` = "0" ]; then
echo "bluetooth module inserted"
sudo modprobe bluetooth
fi
if [ `lsmod | grep bluetooth | wc -l` != "0" ]; then
wlan_init
fi
if [ "$BT_INIT_SET" = "1" ] ; then
bt_init
fi
1、解析输入参数 :parse_arguments $*
给全局变量赋值
RESETPIN:rst接口引脚配置 BT_INIT_SET:蓝牙是否采用串口控制,1表示是,0则使用spi或者sdio IF_TYPE:接口类型spi sdio
2、给MODULE_NAME赋值
MODULE_NAME=esp32_${IF_TYPE}.ko
3、如果采用spi接口的话,先禁用掉树莓派之前的spi设备树配置
rm spidev_disabler.dtbo
# Disable default spidev driver
dtc spidev_disabler.dts -O dtb > spidev_disabler.dtbo
sudo dtoverlay -d . spidev_disabler
4、代码编译
编译c_demo、python_demo,esp32内核模块
5、加载内核模块insmod,创建设备节点esps0
6、蓝牙接口初始化
note:内核模块并没有用到protobuf,只是传递数据。编译esp32固件和linux host应用层代码的时候需要下载protobuf
esp32代码内部维护了一个spi的rx_buffer和tx_buffer。每个buffer的最大是1600 字节,即主机一次最多可以发送和接收 1600 字节的数据。
在esp32和host交换数据的时候,使用protobuf对数据进行序列化和反序列化。
如果要增加一个新的命令交互:
1、在.proto文件中新增自定义命令,然后生成.c和.h文件:
cd <path/to/esp_hosted>/common/proto
protoc-c esp_hosted_config.proto --c_out=.
mv esp_hosted_config.pb-c.c ../
mv esp_hosted_config.pb-c.h ../include/
2、在 host/host_common/commands.c 中添加 C 函数
3、 在ESP 端,在esp/esp_driver/network_adapter/main/slave_commands.c 中添加相应的 C 函数来处理添加的消息字段。
当host给esp32发送命令的时候,先使用protobuf对命令进行序列化,然后调用transport_pserial_data_handler接口将数据组装成TLV格式(Type - Length - Value),
字段 | 说明 | bytes used |
Endpoint Type | 1:表示接下来传输的是endpoint name | 1 byte |
Endpoint Length | 表示接下来传输的Endpoint Value的大小 | 2 byte |
Endpoint Value | 目前只有一个value,固定是:"control" | 7 byte |
Data Type | 2:表示接下来传输的是数据内容 | 1 byte |
Data Length | 表示接下来传输的Data Value的大小 | 2 byte |
Data Value | payload | N bytes |
ref:
https://github.com/espressif/esp-hosted/blob/master/esp_hosted_fg/docs/spi_protocol.md
SPI Slave Driver - ESP32 - — ESP-IDF Programming Guide latest documentation
ESP-Hosted:降低物联网设备的部署成本与复杂性 | 乐鑫科技