北京合众恒跃科技有限公司
直播中

jf_50393217

5年用户 227经验值
擅长:可编程逻辑 嵌入式技术
私信 关注
[经验]

【HZ-T536开发板免费体验】7、开发板和esp32设备通信的准备工作

espnow是乐鑫的自定义通信协议,乐鑫的模组都可以使用这个协议。一般乐鑫的esp32模组都支持wifi,蓝牙和espnow协议,但是同一个时间只能使用其中一种无线协议。

在我考虑的espnow自定义组网协议里,默认是只使用espnow协议,其他的协议就暂时不使用。

所以如果这个esp32设备要和Linux开发板通信,就得用串口或者spi口了。目前我手头上常用的是esp32c6开发板,最简单的办法就是用usb jtag/serial 通信的方式。

把c6的usb插到HZ-T536开发板上,可以看到能正确识别到设备。
image.png
image.png

能正确识别到设备的话,但是没有出现ttyACM0设备,重新编译内核,添加了cdc_acm模块之后,就可以正常识别到设备了。

接下来,我们需要让esp32c6可以通过jtag/serial口接收数据。默认情况下是可以通过jtag/serial输出串口数据的(默认情况下,串口输出和jtag/serial都是启用了的)。

以下是官方对esp32c6的USB jtag/serial 串口的一些描述。

image.png

大部分情况下,我们并不会在意,因为默认的串口输出,在未明确不使用jtag serial输出的情况下,都会转发一份给jtag serial。

所以很自然的,我们也会觉得输入也不需要进行任何设置,直接当做常规串口输入,就能使用。实际上并不是这样,想要准确的使用jtag serial的话,需要使用特定的函数。

首先需要使用以下的代码,初始化usb jtag serial设备。

// Configure USB SERIAL JTAG
usb_serial_jtag_driver_config_t usb_serial_jtag_config = {
.rx_buffer_size = UART_BUF_SIZE,
.tx_buffer_size = UART_BUF_SIZE,
};

ESP_ERROR_CHECK(usb_serial_jtag_driver_install(&usb_serial_jtag_config));
ESP_LOGI(TAG, "usb serial/jtag init done");

然后读取jtag串口的部分,也需要使用对应的函数才可以。

while (true) {
    int len = usb_serial_jtag_read_bytes(data, (UART_BUF_SIZE - 1),
                                         20 / portTICK_PERIOD_MS);
    // 串口数据帧解包,校验长度和CRC没问题就丢给protobuf解包
    if (len && data[UART_FRAME_OFFSET] == UART_FRAME_START) {
      uint8_t *offset = data + UART_FRAME_DATA_OFFSET;
      // 校验成功则调用解包函数
      smn_packet_t *packet = (smn_packet_t *)offset;

      uint16_t buff_len = packet->frame_head.payload_len + sizeof(smn_packet_t);
      // 计算CRC值
      uint16_t head_len = sizeof(smn_addr_t) + sizeof(smn_frame_head_t);
      if (len <= (head_len + buff_len + UART_FRAME_DATA_OFFSET)) {
        // TODO CRC校验还需要验证
        uint16_t crc_result = crc16_le(0, offset,
                                       head_len); // 初始值 0,小端模式

        crc_result = crc16_le(crc_result, offset + head_len + sizeof(uint16_t),
                              packet->frame_head.payload_len);
        if (packet->crc16 == crc_result) {
          if (UART_ADDR_TARGET(packet->addr.dest_addr, packet->addr.scr_addr)) {
            process_local_frame(offset, buff_len);
          } else {
            forward_remote_frame(data + 3, buff_len);
          }
        } else {
          ESP_LOGE(TAG, "packet crc count err");
        }
      } else {
        ESP_LOGE(TAG, "uart recv len %d not fit %d", len - 6, buff_len);
      }
    }
    if (debug_config->uart) {
      ESP_LOG_BUFFER_HEX("Recv str: ", data, len);
    }
  }

输出的话,也是需要使用这个usb jtag串口输出函数就可以了。

/**
 * @brief Send data to the USB-UART port from a given buffer and length,
 *
 * Please ensure the `tx_buffer_size is larger than 0`, if the 'tx_buffer_size' > 0, this function will return after copying all the data to tx ring buffer,
 * USB_SERIAL_JTAG ISR will then move data from the ring buffer to TX FIFO gradually.
 *
 * @param src   data buffer address
 * @param size  data length to send
 * @param ticks_to_wait Maximum timeout in RTOS ticks
 *
 * @return
 *     - The number of bytes pushed to the TX FIFO
 */
int usb_serial_jtag_write_bytes(const void* src, size_t size, TickType_t ticks_to_wait);

这样就可以正常的使用/dev/ttyACM0这个设备,用来和esp32c6通信了。上位机这边可以使用相同的数据结构打包好数据发送给esp32。例如我这边初步定义好的数据包结构。

#ifndef SMN_PACKET_H_INCLUDE
#define SMN_PACKET_H_INCLUDE
/* clang-format off */
#include "SMN_err.h"
#include "SMN_port.h"
#include "../lib/SMN_proto.pb.h"
#include "../lib/nanopb/pb_decode.h"
#include "../lib/nanopb/pb_encode.h"

typedef struct smn_addr {
  uint8_t scr_addr[ESPNOW_ADDR_LEN];            // 高8位: scr_group, 低8位: scr_id
  uint8_t dest_addr[ESPNOW_ADDR_LEN];           // 高8位: dest_group, 低8位: dest_id
} __attribute__((packed)) smn_addr_t;

typedef struct smn_frame_head {
  SMNCommand type : 7;          //  指令类型
  uint32_t is_response : 1;     //  是不是响应包
  uint32_t payload_len : 8;     //  包长度
  uint32_t packet_seq : 8;      //  包编号
  uint32_t reserve : 8;         //  保留字段
} smn_frame_head_t;

typedef struct smn_device_status {
  uint32_t net_status : 8;      //  网络状态
  uint32_t error : 8;           //  错误状态
  uint32_t debug_mode : 16;     //  调试位
} smn_device_status_t;

typedef struct smn_packet {
  smn_addr_t addr;
  smn_frame_head_t frame_head;
  uint16_t crc16;               //  CRC校验值
  uint8_t payload[0];           //  数据指针
} __attribute__((packed)) smn_packet_t;

通过串口发送的数据基本上也是这样构成的,只是需要增加数据帧头和尾部标识符(用于准确判断数据帧的开头,例如常见的0xAA开头,0x55结尾),这样就可以了。

image.png

按固定的帧头和尾取出帧数据后,就可以根据数据包的结构进行解析,例如addr地址段的地址是否本机的mac地址(又或者是源、目的都是0xFF,就默认是上位机直连发送之类的)。取到地址后,就可以根据目的地址进行转发或者处理本机数据包的工作。

然后根据帧头数据结构,就可以判断出后续的是什么指令,接下来再使用对应的protobuf解序列函数,对payload数据进行解包,获得准确的指令的数据。

通过这种方式,上位机也好,web也好,esp32设备也好,都只需要简单的做好地址段,帧头,crc校验的这些内容,其他的指令和数据都可以通过标准化的protobuf协议来序列化和解序列化(也就是封包和解包),这样我们就可以使用最少的开发时间,实现多平台的自定义数据传输了。

更多回帖

发帖
×
20
完善资料,
赚取积分