yuyy1989 发表于 2023-6-20 19:00

【凌鸥创芯电机开发板LKS32MC071CBT8评测报告】4.外设测试

本帖最后由 yuyy1989 于 2023-6-23 10:57 编辑

#申请原创#
4.外设测试
4.1 按键
功率底板上的2个按键都做了上拉处理,低电平触发

这里写个简单的程序来测试一下这两个按键,按下START按键LED1亮松开熄灭,按下STOP按键LED2亮松开熄灭
查看MCU板的原理图,看看按键接到了哪个IO上

代码示例,省略了LED的部分,相关代码可以从新建工程那个帖子找到
#define KEY_GPIO GPIO2
#define KEY1_PIN GPIO_Pin_11
#define KEY2_PIN GPIO_Pin_12
#define KEY_PINS KEY1_PIN|KEY2_PIN

void key_init()
{
    GPIO_InitTypeDef GPIO_InitStruct;
    GPIO_StructInit(&GPIO_InitStruct);

    GPIO_StructInit(&GPIO_InitStruct);
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN;
    GPIO_InitStruct.GPIO_Pin =KEY_PINS;
    GPIO_Init(KEY_GPIO, &GPIO_InitStruct);
}

void mcu_init(void)
{
    __disable_irq();         /* 关闭中断 中断总开关 */
    SYS_WR_PROTECT = 0x7a83; /*使能系统寄存器写操作*/
    FLASH_CFG |= 0x00080000; /* enable prefetch ,FLASH预取加速使能*/      
    IWDG_DISABLE();                                                /*关闭清零看门狗*/
    SYS_MclkChoice(SYS_MCLK_96M_RC); /*选择当前主时钟频率*/
    SysClock = SYS_ReadMcuClk();
    led_init();      
    key_init();
    yuyy_delay_us(100);          /* 延时等待硬件初始化稳定 */
    SYS_WR_PROTECT = 0x0;    /*关闭系统寄存器写操作*/
    __enable_irq();          /* 开启总中断 */
}

int main(void)
{
    mcu_init();
      while (1)
      {
      if(Bit_RESET == GPIO_ReadInputDataBit(KEY_GPIO,KEY1_PIN))
            GPIO_SetBits(LED_GPIO, LED1_PIN);
      else
            GPIO_ResetBits(LED_GPIO, LED1_PIN);
      
      if(Bit_RESET == GPIO_ReadInputDataBit(KEY_GPIO,KEY2_PIN))
            GPIO_SetBits(LED_GPIO, LED2_PIN);
      else
            GPIO_ResetBits(LED_GPIO, LED2_PIN);
      }
}运行效果

4.2 PWM输出
通过使用Timer的比较模式可以输出PWM,查看原理图,LED1和LED2的引脚正好是Timer1两个通道的输出,用这两个LED写个呼吸灯测试一下PWM输出功能

工程需添加库文件lks32mc07x_tim.c

代码示例
void pwm_gpio_init()
{
    GPIO_InitTypeDef GPIO_InitStruct;
    GPIO_StructInit(&GPIO_InitStruct); //初始化结构体

    //配置P0.6 UTIMER1_CH0配置P0.7 UTIMER1_CH1
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT; //GPIO输出模式
    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7;
    GPIO_Init(GPIO0, &GPIO_InitStruct);
    GPIO_PinAFConfig(GPIO0,GPIO_PinSource_6,AF7_TIMER01); //P0.6复用为timer1的输出模式
    GPIO_PinAFConfig(GPIO0,GPIO_PinSource_7,AF7_TIMER01); //P0.7复用为timer1的输出模式
}

void pwm_utimer_init(void)
{
    TIM_TimerInitTypeDef TIM_InitStruct;

    TIM_TimerStrutInit(&TIM_InitStruct);                  /* Timer结构体初始化*/
    TIM_InitStruct.Timer_CH0_WorkMode = TIMER_OPMode_CMP;   /* 设置Timer CH0 为比较模式 */
    TIM_InitStruct.Timer_CH0Output = 0;                     /* 计数器回零时,比较模式输出极性控制 */
    TIM_InitStruct.Timer_CH1_WorkMode = TIMER_OPMode_CMP;   /* 设置Timer CH1 为比较模式 */
    TIM_InitStruct.Timer_CH1Output = 0;                     /* 计数器回零时,比较模式输出极性控制 */
    TIM_InitStruct.Timer_TH = 6000;                         /* 定时器计数门限初始值6000*/
    TIM_InitStruct.Timer_CMP0 = 0;                        /* 设置比较模式的CH0比较初始值0 */
    TIM_InitStruct.Timer_CMP1 = 0;                        /* 设置比较模式的CH1比较初始值0 */
    TIM_InitStruct.Timer_FLT = 0;                           /* 设置捕捉模式或编码器模式下对应通道的数字滤波值 */
    TIM_InitStruct.Timer_ClockDiv = TIMER_CLK_DIV16;      /* 设置Timer模块时钟分频系数 */
    TIM_TimerInit(UTIMER1, &TIM_InitStruct);
    TIM_TimerCmd(UTIMER1, ENABLE);                        /* Timer1 模块使能 */      
}

void mcu_init(void)
{
    __disable_irq();         /* 关闭中断 中断总开关 */
    SYS_WR_PROTECT = 0x7a83; /*使能系统寄存器写操作*/
    FLASH_CFG |= 0x00080000; /* enable prefetch ,FLASH预取加速使能*/      
    IWDG_DISABLE();                                                /*关闭清零看门狗*/
    SYS_MclkChoice(SYS_MCLK_96M_RC); /*选择当前主时钟频率*/
    SysClock = SYS_ReadMcuClk();
    pwm_gpio_init();
    pwm_utimer_init();
    yuyy_delay_us(100);          /* 延时等待硬件初始化稳定 */
    SYS_WR_PROTECT = 0x0;    /*关闭系统寄存器写操作*/
    __enable_irq();          /* 开启总中断 */
}

int main(void)
{
    uint16_t pwm = 0;
    uint8_t dir = 0;
    mcu_init();
      while (1)
      {
      if(dir == 0)
      {
            pwm++;
            if(pwm == 6000)
                dir = 1;
      }
      else
      {
            pwm--;
            if(pwm == 0)
                dir = 0;
      }
      TIM_CMP0(UTIMER1,pwm);
      TIM_CMP1(UTIMER1,6000-pwm);
      yuyy_delay_us(200);
      }
}运行效果

4.3 SPI驱动LCD屏幕
LKS32MC071有一路 SPI,支持主从模式,手里有个12864的LCD屏幕可以用SPI驱动
驱动这个屏幕需要用到5个IO CS RST A0 SCK MOSI,在原理图上尽量找没有被用到的IO
P0.14作为SPI_SCK P2.10作为SPI_MOSI P2.3作为CS P2.2作为RST P3.2作为A0

工程需添加库文件lks32mc07x_spi.c

代码示例,只列出了适配部分的代码
#include "yuyy_hs12864g18b_spi.h"
#include "yuyy_delay.h"
#include "lks32mc07x_gpio.h"
#if(!YUYY_HS12864G18B_USE_SOFT_SPI)
#include "lks32mc07x_spi.h"
#endif
//spi1 mosi P2.10
//spi1 clk P0.14
//CSPIN    P2.3
//RSTPIN    P2.2
//A0PIN   P3.2
#define HS12864G18B_SPI_MOSI(x)   x==YUYY_GPIO_LEV0?GPIO_ResetBits(GPIO2,GPIO_Pin_10):GPIO_SetBits(GPIO2,GPIO_Pin_10)
#define HS12864G18B_SPI_SCK(x)    x==YUYY_GPIO_LEV0?GPIO_ResetBits(GPIO0,GPIO_Pin_14):GPIO_SetBits(GPIO0,GPIO_Pin_14)
#define HS12864G18B_SPI_CS(x)   x==YUYY_GPIO_LEV0?GPIO_ResetBits(GPIO2,GPIO_Pin_3):GPIO_SetBits(GPIO2,GPIO_Pin_3)
#define HS12864G18B_SPI_RST(x)    x==YUYY_GPIO_LEV0?GPIO_ResetBits(GPIO2,GPIO_Pin_2):GPIO_SetBits(GPIO2,GPIO_Pin_2)
#define HS12864G18B_SPI_A0(x)   x==YUYY_GPIO_LEV0?GPIO_ResetBits(GPIO3,GPIO_Pin_2):GPIO_SetBits(GPIO3,GPIO_Pin_2)

void yuyy_hs12864g18b_spi_delayus(uint16_t us)
{
      yuyy_delay_us(us);
}

void yuyy_hs12864g18b_spi_init(void)
{
    GPIO_InitTypeDef GPIO_InitStruct;
    GPIO_StructInit(&GPIO_InitStruct);

    GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_UP;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT; //设置为输出模式
    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_2 | GPIO_Pin_3 | GPIO_Pin_10;
    GPIO_Init(GPIO2, &GPIO_InitStruct);

    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_2;
    GPIO_Init(GPIO3, &GPIO_InitStruct);

    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_14;
    GPIO_Init(GPIO0, &GPIO_InitStruct);
      #if(YUYY_HS12864G18B_USE_SOFT_SPI)
    #else
    SPI_InitTypeDef SPI_InitStruct;
    GPIO_PinAFConfig(GPIO0, GPIO_PinSource_14, AF5_SPI); //GPIO0.14复用SPI_CLK功能
    GPIO_PinAFConfig(GPIO2, GPIO_PinSource_10, AF5_SPI); //GPIO2.10复用SPI_DO功能

    SPI_StructInit(&SPI_InitStruct);            // SPI结构体初始化
    SPI_InitStruct.Duplex = SPI_Full;             //双工模式设置
    SPI_InitStruct.Mode = SPI_Master;             //master主机模式
    SPI_InitStruct.EN = ENABLE;                   //使能SPI模块
    SPI_InitStruct.TRANS_MODE = SPI_DMA_DISABLE;// 选择SPI搬移方式,由MCU搬运数据到SPI
    SPI_InitStruct.Trig = 0;                      //内部自动触发传输
    SPI_InitStruct.ENDIAN = SPI_FIRSTSEND_MSB;   //该字节先发送 MSB
    SPI_InitStruct.CPHA = 1;                      //第2个沿为发送数据时刻
    SPI_InitStruct.CPOL = 1;                      //CLK默认高电平
    SPI_InitStruct.BaudRate = 11;               //传输速度96MHZ/(2*(11+1))= 4MHZ
    SPI_InitStruct.ByteLength = 8;                //发送零个字节
    SPI_InitStruct.IRQEna = DISABLE;            //关闭SPI各中断
    SPI_Init(SPI0, &SPI_InitStruct);               //SPI初始化程序
    #endif
    HS12864G18B_SPI_CS(YUYY_GPIO_LEV1);
}

void yuyy_hs12864g18b_spi_writebyte(uint8_t dat)
{
    #if(YUYY_HS12864G18B_USE_SOFT_SPI)
    uint8_t i = 0;
    while(i<8)
    {
      HS12864G18B_SPI_SCK(YUYY_GPIO_LEV0);
      if(dat & 0x80)
            HS12864G18B_SPI_MOSI(YUYY_GPIO_LEV1);
      else
            HS12864G18B_SPI_MOSI(YUYY_GPIO_LEV0);
      HS12864G18B_SPI_SCK(YUYY_GPIO_LEV1);
      dat <<= 1;
      i++;
    }
    #else
    SPI_SendData(SPI0,dat);
    #endif
}

void yuyy_hs12864g18b_spi_cs(YUYY_GPIO_LEV_TYPE lev)
{
    yuyy_hs12864g18b_spi_delayus(5);
    HS12864G18B_SPI_CS(lev);
    yuyy_hs12864g18b_spi_delayus(5);
}
void yuyy_hs12864g18b_spi_rst(YUYY_GPIO_LEV_TYPE lev)
{
    HS12864G18B_SPI_RST(lev);
}
void yuyy_hs12864g18b_spi_a0(YUYY_GPIO_LEV_TYPE lev)
{
    HS12864G18B_SPI_A0(lev);
}

void mcu_init(void)
{
    __disable_irq();         /* 关闭中断 中断总开关 */
    SYS_WR_PROTECT = 0x7a83; /*使能系统寄存器写操作*/
    FLASH_CFG |= 0x00080000; /* enable prefetch ,FLASH预取加速使能*/      
    IWDG_DISABLE();                                                /*关闭清零看门狗*/
    SYS_MclkChoice(SYS_MCLK_96M_RC); /*选择当前主时钟频率*/
    SysClock = SYS_ReadMcuClk();
    yuyy_hs12864g18b_init();
    yuyy_delay_us(100);          /* 延时等待硬件初始化稳定 */
    SYS_WR_PROTECT = 0x0;    /*关闭系统寄存器写操作*/
    __enable_irq();          /* 开启总中断 */
}

int main(void)
{
    mcu_init();
    yuyy_hs12864g18b_clear_screen();
    yuyy_hs12864g18b_display_string_8x16(0,0,0,(uint8_t *)"LKS32MC071 SPI");
    yuyy_hs12864g18b_display_string_8x16(0,2,0,(uint8_t *)"12864LCD TEST");
    yuyy_hs12864g18b_display_string_8x16(0,4,0,(uint8_t *)"Code by yuyy1989");
    yuyy_hs12864g18b_display_finish();
      while (1)
      {
      }
}运行效果


4.4 UART串口通讯
LKS32MC071有2路UART,功率底板上引出的是UART0,做一个简单的通讯测试,将串口发送过来的字母数字显示在LCD屏幕上
工程需添加库文件lks32mc07x_uart.c

初始化的时候发现初始化UART的结构体中断位是8位的,空闲中断是bit8,也就是没办法通过初始化UART的结构体设置空闲中断

代码示例
#define UART_BUFF_LEN 16
uint8_t uart_buff;
uint8_t uart_rxlen = 0;

void uart_init()
{
    GPIO_InitTypeDef GPIO_InitStruct;
    GPIO_StructInit(&GPIO_InitStruct);
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN;
    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_15;
    GPIO_Init(GPIO0, &GPIO_InitStruct);

    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT;
    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0;
    GPIO_Init(GPIO1, &GPIO_InitStruct);
   
    GPIO_PinAFConfig(GPIO0, GPIO_PinSource_15, AF4_UART); //P0.15复用为UART_RX
    GPIO_PinAFConfig(GPIO1, GPIO_PinSource_0, AF4_UART); //P1.0复用为UART_TX
   
    UART_InitTypeDef UART_InitStruct;
   
    UART_StructInit(&UART_InitStruct);
    UART_InitStruct.BaudRate = 115200;               /* 设置波特率115200 */
    UART_InitStruct.WordLength = UART_WORDLENGTH_8b;/* 发送数据长度8位 */
    UART_InitStruct.StopBits = UART_STOPBITS_1b;      /* 停止位1位 */
    UART_InitStruct.FirstSend = UART_FIRSTSEND_LSB;   /* 先发送LSB */
    UART_InitStruct.ParityMode = UART_Parity_NO;      /* 无奇偶校验 */
    //UART_InitStruct.IRQEna = UART_IRQEna_RcvOver|UART_IRQEna_IDLE; /* 空闲中断还不能这么设置 */
    UART_Init(UART0, &UART_InitStruct);
    UART0->IE = UART_IRQEna_RcvOver|UART_IRQEna_IDLE;//接收中断和空闲中断
   
    NVIC_EnableIRQ(UART0_IRQn);      /* 使能UART_IRQn外部中断*/
    NVIC_SetPriority(UART0_IRQn, 0);   /* UART_IRQn外部中断优先级设置为0*/
}

void UART0_IRQHandler(void)
{
    if (UART_GetIRQFlag(UART0, UART_IF_RcvOver))   // 接收完成中断
    {
      UART_ClearIRQFlag(UART0, UART_IF_RcvOver);         // 清除接收完成标志位
      uart_buff = UART_ReadData(UART0); // 接收 1 Byte数据
      uart_rxlen++;
    }
    if (UART_GetIRQFlag(UART0, UART_IF_IDLE) || uart_rxlen == UART_BUFF_LEN)    // 空闲中断
    {
      UART_ClearIRQFlag(UART0, UART_IF_IDLE);// 清除空闲标志位
      while(uart_rxlen < UART_BUFF_LEN)
      {
            uart_buff = ' ';
            uart_rxlen++;
      }   
      uart_buff = '\0';
      yuyy_hs12864g18b_display_string_8x16(0,6,0,uart_buff);
      yuyy_hs12864g18b_display_finish();
      uart_rxlen = 0;
    }
}

void mcu_init(void)
{
    __disable_irq();         /* 关闭中断 中断总开关 */
    SYS_WR_PROTECT = 0x7a83; /*使能系统寄存器写操作*/
    FLASH_CFG |= 0x00080000; /* enable prefetch ,FLASH预取加速使能*/      
    IWDG_DISABLE();                                                /*关闭清零看门狗*/
    SYS_MclkChoice(SYS_MCLK_96M_RC); /*选择当前主时钟频率*/
    yuyy_hs12864g18b_init();
    uart_init();
    yuyy_delay_us(100);          /* 延时等待硬件初始化稳定 */
    SYS_WR_PROTECT = 0x0;    /*关闭系统寄存器写操作*/
    __enable_irq();          /* 开启总中断 */
}

int main(void)
{
    mcu_init();
    yuyy_hs12864g18b_clear_screen();
    yuyy_hs12864g18b_display_string_8x16(0,0,0,(uint8_t *)"LKS32MC071 SPI");
    yuyy_hs12864g18b_display_string_8x16(0,2,0,(uint8_t *)"12864LCD TEST");
    yuyy_hs12864g18b_display_string_8x16(0,4,0,(uint8_t *)"Code by yuyy1989");
    yuyy_hs12864g18b_display_finish();
    while (1)
    {
    }
}运行效果

4.5 ADC电位器
底板上有个可调电位器,用它来测试一下ADC功能,将电位器调节后的电压用串口和LCD屏显示出来
工程需添加库文件lks32mc07x_adc.c和lks32mc07x_nvr.lib

代码示例
void adc_init()
{
    ADC_InitTypeDef ADC_InitStructure;
   
    ADC_StructInit(&ADC_InitStructure);
    //ADC_InitStructure.IE         = ADC_SF1_IE | ADC_SF2_IE;          // 中断使能
    ADC_InitStructure.RE         = 0;                   // DMA请求使能
    ADC_InitStructure.NSMP       = DISABLE;            // 两段采样使能
    ADC_InitStructure.DATA_ALIGN = DISABLE;             // 禁用DAT右对齐使能
    ADC_InitStructure.CSMP       = DISABLE;             // 连续采样使能
    ADC_InitStructure.TCNT       = 1;                   // 触发一次采样所需的事件数
    ADC_InitStructure.TROVS      = ENABLE;             // 手动触发过采样使能,开启后一次采样需要多次触发
    ADC_InitStructure.OVSR       = 0;                   // 过采样率
    ADC_InitStructure.S1         = 4;                   // 第一段常规采样次数
    ADC_InitStructure.S2         = 0;                   // 第二段常规采样次数
    ADC_InitStructure.IS1      = 0;                   // 空闲采样次数
    ADC_InitStructure.LTH      = 0;                   // ADC 模拟看门狗 0 下阈值
    ADC_InitStructure.HTH      = 0;                   // ADC 模拟看门狗 0 上阈值
    ADC_InitStructure.GEN      = 0;                   // ADC 模拟看门狗 0 对应使能位
    ADC_Init(ADC0, &ADC_InitStructure);
    ADC_ClearIRQFlag(ADC0, ADC_ALL_IF);//清除所有中断标志位
      
    ADC_CHN_GAIN_CFG(ADC0,CHN0,ADC_CHANNEL_9,ADC_GAIN3V6);
    ADC_CHN_GAIN_CFG(ADC0,CHN1,ADC_CHANNEL_9,ADC_GAIN3V6);
    ADC_CHN_GAIN_CFG(ADC0,CHN2,ADC_CHANNEL_9,ADC_GAIN3V6);
    ADC_CHN_GAIN_CFG(ADC0,CHN3,ADC_CHANNEL_9,ADC_GAIN3V6);
}

void mcu_init(void)
{
    __disable_irq();         /* 关闭中断 中断总开关 */
    SYS_WR_PROTECT = 0x7a83; /*使能系统寄存器写操作*/
    FLASH_CFG |= 0x00080000; /* enable prefetch ,FLASH预取加速使能*/      
    IWDG_DISABLE();                                                /*关闭清零看门狗*/
    SYS_MclkChoice(SYS_MCLK_96M_RC); /*选择当前主时钟频率*/
    led_init();      
    yuyy_hs12864g18b_init();
    uart_init();
    adc_init();
    yuyy_delay_us(100);          /* 延时等待硬件初始化稳定 */
    SYS_WR_PROTECT = 0x0;    /*关闭系统寄存器写操作*/
    __enable_irq();          /* 开启总中断 */
}

int main(void)
{
    int32_t adcval = 0;
    float vol = 0;
    uint8_t lcdout = {0};
    mcu_init();
    yuyy_hs12864g18b_clear_screen();
    yuyy_hs12864g18b_display_string_8x16(0,0,0,(uint8_t *)"LKS32MC071 SPI");
    yuyy_hs12864g18b_display_string_8x16(0,2,0,(uint8_t *)"12864LCD TEST");
    yuyy_hs12864g18b_display_string_8x16(0,4,0,(uint8_t *)"Code by yuyy1989");
    yuyy_hs12864g18b_display_finish();
    while (1)
    {
      yuyy_delay_ms(500);
      ADC_SoftTrgEN(ADC0,ENABLE);
      //while(!ADC_GetIRQFlag(ADC0,ADC_SF1_IF));
      adcval = ADC_GetConversionValue(ADC0,DAT0) + ADC_GetConversionValue(ADC0,DAT1) + ADC_GetConversionValue(ADC0,DAT2)+ADC_GetConversionValue(ADC0,DAT3);
      adcval /= 64; //右移4位相当于/16,再取平均数/4
      vol = (float)adcval/2047 *3.6 / 2 * 3; //反推ZB点电压值
      printf("ADC %d %d %d %d\r\n",ADC_GetConversionValue(ADC0,DAT0),ADC_GetConversionValue(ADC0,DAT1),ADC_GetConversionValue(ADC0,DAT2),ADC_GetConversionValue(ADC0,DAT3));
      printf("ADC vol %.2fv",vol);
      sprintf((char *)lcdout,"ADC vol %.2fv",vol);
      yuyy_hs12864g18b_display_string_8x16(0,6,0,lcdout);
    }
}运行效果,我用daplink的5v输出对MCU板进行供电,用万用表实测电位器两端电源不到4.5v,ADC转换后的电压与用万用表测得电压相差0.02v左右



tpgf 发表于 2023-7-5 09:17

这种情况下是不是应该在软件上进行滤波呢

qcliu 发表于 2023-7-5 10:02

楼主在这个程序里边使用的是哪种采样方式啊

drer 发表于 2023-7-5 10:20

外部引脚的调试还是比较容易的 就是ad的精度不好弄

kxsi 发表于 2023-7-5 11:09

看了一下 这几个外设楼主用的就是顺序工作的方式吧

coshi 发表于 2023-7-5 18:00

串口通讯的最高速度能配置到多快呢

wiba 发表于 2023-7-5 18:41

传输间隔大概就是500毫秒一次了 出现过数据丢失的情况吗

caigang13 发表于 2023-7-7 08:07

这个还带电机驱动板吧

yuyy1989 发表于 2023-7-7 08:21

coshi 发表于 2023-7-5 18:00
串口通讯的最高速度能配置到多快呢

手册上的波特率范围是300~115200

yuyy1989 发表于 2023-7-7 08:22

wiba 发表于 2023-7-5 18:41
传输间隔大概就是500毫秒一次了 出现过数据丢失的情况吗

没长时间测试过

yuyy1989 发表于 2023-7-7 08:22

caigang13 发表于 2023-7-7 08:07
这个还带电机驱动板吧

对,下面的就是电机驱动板
页: [1]
查看完整版本: 【凌鸥创芯电机开发板LKS32MC071CBT8评测报告】4.外设测试