手把手教你制作DAPLink

2023/12/28 19:47
阅读数 87

这篇文章主要描述利用RT-THREAD+CherryUSB制作DapLink调试器(R_DapLink)全流程。这里先感谢网友:sakumisu提供cherryUSB协议栈的技术支持。

  • 什么是下载调试器 简单来说,下载调试器是将PC(例如通过USB协议)发送的命令转换为MCU(负责MCU内部外围设备)理解的语言(例如SWD或JTAG协议)的设备,加载代码并精确控制执行。

  • 什么是标准 简单来说,标准是一组规则和协议,特定行业中的每个参与者都同意遵循并执行。符合某种内核的单片机Q,都可以使用这种协议来下载程序。JTAG和SWD其实都是一种标准的协议。比如JTAG和SWD,都支特下载ARMQ内核单片机的程序。

  • 各种调试器的区别「J-Link:」最有名气、各种渠道版本最多,号称支持芯片量最多。

  1. 适合场景:如果项目文件比较大,首选!下载又快又稳,仿真调试也是杠杠的神器。
  2. 优点:最快、稳如老狗。STM32F407芯片+109K代码实测8秒完成烧录过程。
  3. 缺点:最贵,和谐版都四五十元起步,正版上至数千元。注意:V9版本以下的J-Link大多数不支持STM8。

「ST-Link:」随着STM32这十年八年的垄断,ST-Link也跟着发大火了, 妥妥销量一哥。

  1. 适合场景:学校里开STM32课程的,几乎每个宿舍都有吧~
  2. 优点:便宜、便宜,便宜!x宝13元包邮。特别注意一下:驱动包就在KEIL本身的文件夹里头,你说它多火。
  3. 缺点:不够稳定! 可能不时的来个弹窗。

「CMSIS-DAP:」软硬件开源!这两三年,含量在火箭式起飞。很多人知道它是因为技小新和立创EDA的开源工程。比STLink稍贵。

  1. 适合场景:玩stm32的,这个是最优选择。
  2. 优点:开源、虚拟串口、免驱。大爱的虚拟串口,很爽、很爽, (有些下载器也有虚拟串口),更爱它开源没心病。
  3. 缺点:真没啥缺点,能拼JLink的稳定, 也能拼ST-Link的便宜,还没版权问题!109K代码烧录实测10秒,也算杠杠的吧。

「DAP-Link:」CMSIS-DAP的升级版。

  1. 适合场景:嗯,如果你的动手能力高超,这个最合适,软硬都开源,可玩性极高,比如做个拖拽的脱机下载
  2. 优点:拖拽烧录、升级固件。包括了CMSIS-DAP的优点:开源+虚拟串口+免驱。
  3. 缺点:真没啥缺点,能拼JLink的稳定, 也能拼ST-Link的便宜,还没版权问题!

R_DapLink说明

  • R_DapLink支持:DapLink+CDC(虚拟串口),采用USB2.1协议。采用GPIO模拟SWD时序

硬件准备

  • R_DapLink的硬件采用 stm32f103c8t6,内核:ARM Cortex-M3,主频:72MHz,Flash:64KB,RAM:20KB
  • 开发板我们需要做一些改动,由于SWD的数据线有输入和输出,我们这里采用两个引脚合并成一个引脚作为SWD的数据线。 stm32的GPIOB0作为SWD的数据线的输出,stm32的GPIOA7作为SWD的数据线的输入
  • 准备一个现成的调试器来调试我们的R_DapLink,这里采用正点原子的DapLink。
  • R_DapLink支持CDC(虚拟串口),所以我们还需要一个串口工具,这里采用CH340。

软件准备

  • R_DapLink采用RT-Thread作为我们实时系统,提供系统调度,IPC通信。选择RT-Thread的原因:RT-Thread已经包含了cherryUSB协议栈软件,这给我减少了很多移植的工作量。
  • R_DapLink的USB协议栈采用cherryUSB协议栈,其代码链接: https://github.com/cherry-embedded/CherryUSB。cherryUSB协议栈提供了对应的文档,其文档链接: https://cherryusb.readthedocs.io/zh-cn/latest/
  • DAPLink:Arm Mbed DAPLink是一个开源软件项目,可以对Arm Cortex CPU上运行的应用程序进行编程和调试。其链接: https://github.com/ARMmbed/DAPLink

移植DapLink

准备rt-thread工程

  1. 下载rt-thread的源码,源码链接: https://gitee.com/rtthread/rt-thread,我们采用4.1.1的版本,所以下载完源码需要切换到4.1.1版本中。
  2. 下载完源码,进入stm32f103-blue-pill这个BSP,路径:xxx\bsp\stm32\stm32f103-blue-pill,通过env工具dist出来,
  1. 从dist目录下拷贝stm32f103-blue-pill工程出来,并修改名字为:r_daplink。

增加cherryUSB软件包

  1. 进入上面准备好的工程:r_daplink,在工程目录中打开env工具,输入menuconfig。
  1. 配置增加cherryUSB
  1. r_daplink的USB是作为device,所以选择选择Device mode
  1. r_daplink的usb的速度为全速,选择FS,stm32f103c86的USB IP为FSDEV,并选择上cdc,用于实现虚拟串口。

增加DAPLink源码

  1. 下载DAPLink代码,链接: https://github.com/ARMmbed/DAPLink
  2. DAPLink代码很多,但实际我们只用核心的部分,将DAPLink代码中:source\daplink\cmsis-dap目录拷贝到r_daplink工程中。
  1. r_daplink工程中增加两个文件:DAP_config.h和IO_Config.h文件。其中:DAP_config.h用于配置DAPLink的配置,并适配SWD时序模拟的GPIO,IO_Config.h用于配置SWD使用到的GPIO的描述。具体内容看我的开源仓: https://gitee.com/RiceChen0/r_daplink

USB适配

  1. r_daplink的daplink采用 winusb+cdc,其中包含3个接口,4个端点,其设备描述符适配:
const uint8_t cdc_winusb_descriptor[] = {
    USB_DEVICE_DESCRIPTOR_INIT(USB_2_1, 0xEF0x020x01, USBD_VID, USBD_PID, 0x01000x01),
    USB_CONFIG_DESCRIPTOR_INIT(USB_CONFIG_SIZE, 0x030x01, USB_CONFIG_BUS_POWERED, USBD_MAX_POWER),
    USB_INTERFACE_DESCRIPTOR_INIT(0x000x000x020xff0x000x000x02),
    USB_ENDPOINT_DESCRIPTOR_INIT(WINUSB_OUT_EP, 0x02, USB_MAX_MPS, 0x00),
    USB_ENDPOINT_DESCRIPTOR_INIT(WINUSB_IN_EP, 0x02, USB_MAX_MPS, 0x00),
    CDC_ACM_DESCRIPTOR_INIT(0x01, CDC_INT_EP, CDC_OUT_EP, CDC_IN_EP, USB_MAX_MPS, 0x00),
    ///////////////////////////////////////
    /// string0 descriptor
    ///////////////////////////////////////
    USB_LANGID_INIT(USBD_LANGID_STRING),
    ///////////////////////////////////////
    /// string1 descriptor
    ///////////////////////////////////////
    0x12,                       /* bLength */
    USB_DESCRIPTOR_TYPE_STRING, /* bDescriptorType */
    'R'0x00,                  /* wcChar0 */
    'i'0x00,                  /* wcChar1 */
    'c'0x00,                  /* wcChar2 */
    'e'0x00,                  /* wcChar3 */
    'C'0x00,                  /* wcChar4 */
    'h'0x00,                  /* wcChar5 */
    'e'0x00,                  /* wcChar6 */
    'n'0x00,                  /* wcChar7 */
    ///////////////////////////////////////
    /// string2 descriptor
    ///////////////////////////////////////
    0x1E,                       /* bLength */
    USB_DESCRIPTOR_TYPE_STRING, /* bDescriptorType */
    'R'0x00,                  /* wcChar0 */
    'i'0x00,                  /* wcChar1 */
    'c'0x00,                  /* wcChar2 */
    'e'0x00,                  /* wcChar3 */
    ' '0x00,                  /* wcChar4 */
    'C'0x00,                  /* wcChar5 */
    'M'0x00,                  /* wcChar6 */
    'S'0x00,                  /* wcChar7 */
    'I'0x00,                  /* wcChar8 */
    'S'0x00,                  /* wcChar9 */
    '-'0x00,                  /* wcChar10 */
    'D'0x00,                  /* wcChar11 */
    'A'0x00,                  /* wcChar12 */
    'P'0x00,                  /* wcChar13 */
    ///////////////////////////////////////
    /// string3 descriptor
    ///////////////////////////////////////
    0x1C,                       /* bLength */
    USB_DESCRIPTOR_TYPE_STRING, /* bDescriptorType */
    'R'0x00,                  /* wcChar0 */
    'i'0x00,                  /* wcChar1 */
    'c'0x00,                  /* wcChar2 */
    'e'0x00,                  /* wcChar3 */
    '-'0x00,                  /* wcChar4 */
    '2'0x00,                  /* wcChar5 */
    '0'0x00,                  /* wcChar6 */
    '2'0x00,                  /* wcChar7 */
    '3'0x00,                  /* wcChar8 */
    '0'0x00,                  /* wcChar9 */
    '1'0x00,                  /* wcChar10 */
    '0'0x00,                  /* wcChar11 */
    '1'0x00,                  /* wcChar12 */
    0x00
};
  1. winusb端点适配
void usbd_winusb_out(uint8_t ep, uint32_t nbytes)
{   
    usbd_ep_start_read(WINUSB_OUT_EP, usb2dap_buff[usb2dap_index], USB2DAP_PACK_SIZE);
}

void usbd_winusb_in(uint8_t ep, uint32_t nbytes)
{
    if ((nbytes % USB_MAX_MPS) == 0 && nbytes) {
        usbd_ep_start_write(WINUSB_IN_EP, NULL0);
    }
}

struct usbd_endpoint winusb_out_ep = {
    .ep_addr = WINUSB_OUT_EP,
    .ep_cb = usbd_winusb_out
};

struct usbd_endpoint winusb_in_ep = {
    .ep_addr = WINUSB_IN_EP,
    .ep_cb = usbd_winusb_in
};
  1. cdc端点适配
void usbd_cdc_acm_bulk_out(uint8_t ep, uint32_t nbytes)
{
    usbd_ep_start_read(CDC_OUT_EP, usb2uart_buff, USB2UART_PACK_SIZE);
}

void usbd_cdc_acm_bulk_in(uint8_t ep, uint32_t nbytes)
{
    if ((nbytes % USB_MAX_MPS) == 0 && nbytes) {
        usbd_ep_start_write(CDC_IN_EP, NULL0);
    }
}

struct usbd_endpoint cdc_out_ep = {
    .ep_addr = CDC_OUT_EP,
    .ep_cb = usbd_cdc_acm_bulk_out
};

struct usbd_endpoint cdc_in_ep = {
    .ep_addr = CDC_IN_EP,
    .ep_cb = usbd_cdc_acm_bulk_in
};
  1. usb初始化
int usb_service_init(void)
{
    usbd_desc_register(cdc_winusb_descriptor);
    usbd_bos_desc_register(&bos_desc);
    usbd_msosv2_desc_register(&msosv2_desc);

    usbd_add_interface(&intf0);
    usbd_add_endpoint(&winusb_out_ep);
    usbd_add_endpoint(&winusb_in_ep);

    usbd_add_interface(usbd_cdc_acm_init_intf(&intf0));
    usbd_add_interface(usbd_cdc_acm_init_intf(&intf1));
    usbd_add_endpoint(&cdc_out_ep);
    usbd_add_endpoint(&cdc_in_ep);

    usbd_initialize();
    return RT_EOK;
}
  1. 以上适配完将板子的USB插上电脑,通过设备管理器查看是否成功

cdc适配

  1. 我们采用串口3作为我们USB到串口的转发。
  2. cdc虚拟串口的配置传给串口3,具体实现如下:
static void uart_config_set(uart_config_t *config)
{
    if(rt_memcmp(&uart_config, (rt_uint8_t *)config, sizeof(uart_config_t)) != 0)
    {
        rt_memcpy((rt_uint8_t *)&uart_config, config, sizeof(uart_config_t));
        uart_is_config = RT_TRUE;
    }
    if(uart_is_config)
    {
        struct serial_configure serial_config = RT_SERIAL_CONFIG_DEFAULT;
        if(uart_dev != RT_NULL) {
            rt_device_close(uart_dev);
            uart_dev = RT_NULL;
        }
        
        uart_is_config = RT_FALSE;
        uart_dev = rt_device_find(UART_NAME);
        serial_config.baud_rate = uart_config.baudrate;
        serial_config.stop_bits = uart_config.stopbit;
        serial_config.parity = uart_config.parity;
        serial_config.data_bits = uart_config.databit;
        serial_config.bufsz = UART_PACK_SIZE;
        rt_device_control(uart_dev, RT_DEVICE_CTRL_CONFIG, &serial_config);

        rt_device_open(uart_dev, RT_DEVICE_FLAG_DMA_RX);
        rt_device_set_rx_indicate(uart_dev, uart_recv_isr);
    }
}
  1. cdc虚拟串口数据到串口3的实现如下:
void usb2uart_handler(rt_uint8_t *data, rt_uint16_t len)
{
    if(uart_dev)
    {
        rt_device_write(uart_dev, 0, data, len);
    }
}
  1. 串口3数据到cdc虚拟串口的实现如下:
static rt_err_t uart_recv_isr(rt_device_t dev, rt_size_t size)
{
    if(size > 0)
    {
        rt_sem_release(&uart_rx_sem);
    }
    return RT_EOK;
}

static void uart2usb_handler(void *param)
{
    rt_uint16_t rx_size = 0;
    for(;;)
    {
        rt_sem_take(&uart_rx_sem, RT_WAITING_FOREVER);
        if(uart_dev)
        {
            rx_size = rt_device_read(uart_dev, 0, uart_rx_buff, UART_PACK_SIZE);
            usb_service_uart2usb(uart_rx_buff, rx_size);
        }
    }
}
  1. 测试验证:

daplink适配

  1. daplink的实现原理:将usb接收到的数据传输到DAP_ExecuteCommand()函数,并且从这个函数获取返回数据,将数据传输到usb上。

  2. 我们将usb接收到数据通过邮箱的方式传输到数据处理现成,具体实现如下:

static void usb2dap_handler(rt_uint8_t *data, rt_uint16_t len)
{
    rt_mb_send(&dap2usb_mb, (rt_ubase_t)data);
}

static void dap2usb_handler(void *param)
{
    char *rx_data = NULL;
    for(;;)
    {
        if(rt_mb_recv(&dap2usb_mb, (rt_ubase_t *)&rx_data, RT_WAITING_FOREVER) == RT_EOK)
        {
            if(rx_data[0] == ID_DAP_QueueCommands)
            {
                rx_data[0] = ID_DAP_ExecuteCommands;
            }
            dap2usb_size = DAP_ExecuteCommand((const uint8_t *)rx_data, dap2usb_buff);
            usb_service_dap2usb(dap2usb_buff, dap2usb_size);
        }
    }
}
  1. 验证:我们keil里面选择我们dap,可以正常的识别到DAP,并且能识别链接的设备

r_daplink的烧录验证

总结

r_daplink的开源链接:https://gitee.com/RiceChen0/r_daplink


———————End——————



👇 点击阅读原文报名

本文分享自微信公众号 - RTThread物联网操作系统(RTThread)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

展开阅读全文
加载中
点击引领话题📣 发布并加入讨论🔥
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部