dirty

  • 2024-06-15
  • 发表了主题帖: 【兆易GD32H759I-EVAL】--14.OSPI Flash读写

    本帖最后由 dirty 于 2024-6-15 23:46 编辑       开发板板载64MB Nor Flash芯片GD25X512MEBIRY,该芯片支持标准SPI与OSPI(Octal-SPI interface).本篇讲述OSPI对Flash读写。   一.原理了解与准备       开发板板载NorFlash芯片原理图如下。由MCU GD32H759IMK6 datasheet可查阅主控支持两个OSPI,这里用到OSPI0.OSPI有三个功能模式:间接模式、状态轮询模式、内存映射模式;支持单线、双线、四线、八线通讯。硬件上,跳线帽JP64、JP67打到OSPI侧。 图1:OSPI Flash原理图 二.代码准备       这里主要实现单线、八线间接模式,内存映射模式对NorFlash写与读,并比较二者是否相同以判别写入是否成功。 1.OSPI初始化。这里包含引脚初始化,使能时钟与片选,OSPI外设口选择及OSPI参数初始化与使能。 /*! \brief initialize OSPI/OSPIM and GPIO \param[in] ospi_periph: OSPIx(x=0,1) \param[out] ospi_struct: OSPI parameter initialization stuct members of the structure and the member values are shown as below: prescaler: between 0 and 255 fifo_threshold: OSPI_FIFO_THRESHOLD_x (x = 1, 2, ..., 31, 32) sample_shift: OSPI_SAMPLE_SHIFTING_NONE, OSPI_SAMPLE_SHIFTING_HALF_CYCLE device_size: OSPI_MESZ_x_BYTES (x = 2, 4, 8, ..., 512, 1024) OSPI_MESZ_x_KBS (x = 2, 4, 8, ..., 512, 1024) OSPI_MESZ_x_MBS (x = 2, 4, 8, ..., 2048, 4096) cs_hightime: OSPI_CS_HIGH_TIME_x_CYCLE (x = 1, 2, ..., 63, 64) memory_type: OSPI_MICRON_MODE, OSPI_MACRONIX_MODE, OSPI_STANDARD_MODE OSPI_MACRONIX_RAM_MODE, wrap_size: OSPI_DIRECT, OSPI_WRAP_16BYTES, OSPI_WRAP_32BYTES OSPI_WRAP_64BYTES, OSPI_WRAP_128BYTES delay_hold_cycle: OSPI_DELAY_HOLD_NONE, OSPI_DELAY_HOLD_QUARTER_CYCLE \retval none */ void ospi_flash_init(uint32_t ospi_periph, ospi_parameter_struct *ospi_struct) { /* reset the OSPI and OSPIM peripheral */ ospi_deinit(ospi_periph); ospim_deinit(); /* enable OSPIM and GPIO clock */ rcu_periph_clock_enable(RCU_OSPIM); rcu_periph_clock_enable(RCU_GPIOA); rcu_periph_clock_enable(RCU_GPIOB); rcu_periph_clock_enable(RCU_GPIOC); rcu_periph_clock_enable(RCU_GPIOD); rcu_periph_clock_enable(RCU_GPIOE); /* configure OSPIM GPIO pin: OSPIM_P0_IO0(PD11) OSPIM_P0_IO1(PC10) OSPIM_P0_IO2(PE2) OSPIM_P0_IO3(PD13) OSPIM_P0_IO4(PD4) OSPIM_P0_IO5(PD5) OSPIM_P0_IO6(PD6) OSPIM_P0_IO7(PD7) OSPIM_P0_CLK(PA3) OSPIM_P0_NCS(PB10) */ gpio_af_set(GPIOA, GPIO_AF_12, GPIO_PIN_3); gpio_mode_set(GPIOA, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_3); gpio_output_options_set(GPIOA, GPIO_OTYPE_PP, GPIO_OSPEED_60MHZ, GPIO_PIN_3); gpio_af_set(GPIOB, GPIO_AF_9, GPIO_PIN_10); gpio_mode_set(GPIOB, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_10); gpio_output_options_set(GPIOB, GPIO_OTYPE_PP, GPIO_OSPEED_60MHZ, GPIO_PIN_10); gpio_af_set(GPIOC, GPIO_AF_9, GPIO_PIN_10); gpio_mode_set(GPIOC, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_10); gpio_output_options_set(GPIOC, GPIO_OTYPE_PP, GPIO_OSPEED_60MHZ, GPIO_PIN_10); gpio_af_set(GPIOD, GPIO_AF_10, GPIO_PIN_4 | GPIO_PIN_5 | GPIO_PIN_6 | GPIO_PIN_7); gpio_mode_set(GPIOD, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_4 | GPIO_PIN_5 | GPIO_PIN_6 | GPIO_PIN_7); gpio_output_options_set(GPIOD, GPIO_OTYPE_PP, GPIO_OSPEED_60MHZ, GPIO_PIN_4 | GPIO_PIN_5 | GPIO_PIN_6 | GPIO_PIN_7); gpio_af_set(GPIOD, GPIO_AF_9, GPIO_PIN_11 | GPIO_PIN_13); gpio_mode_set(GPIOD, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_11 | GPIO_PIN_13); gpio_output_options_set(GPIOD, GPIO_OTYPE_PP, GPIO_OSPEED_60MHZ, GPIO_PIN_11 | GPIO_PIN_13); gpio_af_set(GPIOE, GPIO_AF_9, GPIO_PIN_2); gpio_mode_set(GPIOE, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_2); gpio_output_options_set(GPIOE, GPIO_OTYPE_PP, GPIO_OSPEED_60MHZ, GPIO_PIN_2); /* enable SCK, CSN, IO[3:0] and IO[7:4] for OSPIM port0 */ ospim_port_sck_config(OSPIM_PORT0, OSPIM_PORT_SCK_ENABLE); ospim_port_csn_config(OSPIM_PORT0, OSPIM_PORT_CSN_ENABLE); ospim_port_io3_0_config(OSPIM_PORT0, OSPIM_IO_LOW_ENABLE); ospim_port_io7_4_config(OSPIM_PORT0, OSPIM_IO_HIGH_ENABLE); switch(ospi_periph) { case OSPI0: rcu_periph_clock_enable(RCU_OSPI0); /* configure OSPIM port0 */ ospim_port_sck_source_select(OSPIM_PORT0, OSPIM_SCK_SOURCE_OSPI0_SCK); ospim_port_csn_source_select(OSPIM_PORT0, OSPIM_CSN_SOURCE_OSPI0_CSN); ospim_port_io3_0_source_select(OSPIM_PORT0, OSPIM_SRCPLIO_OSPI0_IO_LOW); ospim_port_io7_4_source_select(OSPIM_PORT0, OSPIM_SRCPHIO_OSPI0_IO_HIGH); break; case OSPI1: rcu_periph_clock_enable(RCU_OSPI1); /* configure OSPIM port0 */ ospim_port_sck_source_select(OSPIM_PORT0, OSPIM_SCK_SOURCE_OSPI1_SCK); ospim_port_csn_source_select(OSPIM_PORT0, OSPIM_CSN_SOURCE_OSPI1_CSN); ospim_port_io3_0_source_select(OSPIM_PORT0, OSPIM_SRCPLIO_OSPI1_IO_LOW); ospim_port_io7_4_source_select(OSPIM_PORT0, OSPIM_SRCPHIO_OSPI1_IO_HIGH); break; default: break; } /* initialize the parameters of OSPI struct */ ospi_struct_init(ospi_struct); ospi_struct->prescaler = 9U; ospi_struct->sample_shift = OSPI_SAMPLE_SHIFTING_NONE; ospi_struct->fifo_threshold = OSPI_FIFO_THRESHOLD_5; ospi_struct->device_size = OSPI_MESZ_512_MBS; ospi_struct->wrap_size = OSPI_DIRECT; ospi_struct->cs_hightime = OSPI_CS_HIGH_TIME_3_CYCLE; ospi_struct->memory_type = OSPI_MICRON_MODE; ospi_struct->delay_hold_cycle = OSPI_DELAY_HOLD_NONE; /* initialize OSPI parameter */ ospi_init(ospi_periph, ospi_struct); /* enable OSPI */ ospi_enable(ospi_periph); } 2.复位OSPI Flash。这里对SPI模式和OSPI模式进行同时处理。 /* reset ospi flash */ ospi_flash_reset_enable(OSPI_INTERFACE, &ospi_struct, SPI_MODE); ospi_flash_reset_memory(OSPI_INTERFACE, &ospi_struct, SPI_MODE); ospi_flash_reset_enable(OSPI_INTERFACE, &ospi_struct, OSPI_MODE); ospi_flash_reset_memory(OSPI_INTERFACE, &ospi_struct, OSPI_MODE); 3.读取flash ID.这里使用SPI模式读出。在读出正确的情况下对Flash进行写、读后比较。 /* read flash ID */ flashid = ospi_flash_read_id(OSPI_INTERFACE, &ospi_struct, SPI_MODE); if(GD25X512ME_ID == flashid) { printf("\n\rThe device ID is 0x%X\n\r", flashid); } else { printf("\n\rFailed to read device ID\n\r"); while(1){ } 4.单线间接模式写、读后比较 /* 1 line in indirect mode read/write */ printf("\n\rThe data written with 1 line in indirect mode to flash is:"); printf("\n%s\n\r", tx_buffer1); /* erase specified address */ ospi_flash_autopolling_mem_ready(OSPI_INTERFACE, &ospi_struct, SPI_MODE); ospi_flash_write_enbale(OSPI_INTERFACE, &ospi_struct, SPI_MODE); ospi_flash_block_erase(OSPI_INTERFACE, &ospi_struct, SPI_MODE, GD25X512ME_3BYTES_SIZE, FLASH_WRITE_ADDRESS_1, GD25X512ME_ERASE_4K); ospi_flash_autopolling_mem_ready(OSPI_INTERFACE, &ospi_struct, SPI_MODE); /* write data of tx_buffer1 to flash */ ospi_flash_autopolling_mem_ready(OSPI_INTERFACE, &ospi_struct, SPI_MODE); ospi_flash_write_enbale(OSPI_INTERFACE, &ospi_struct, SPI_MODE); ospi_flash_page_program(OSPI_INTERFACE, &ospi_struct, SPI_MODE, GD25X512ME_3BYTES_SIZE, tx_buffer1, FLASH_WRITE_ADDRESS_1, buffersize1); ospi_flash_autopolling_mem_ready(OSPI_INTERFACE, &ospi_struct, SPI_MODE); /* read data from flash */ ospi_flash_read(OSPI_INTERFACE, &ospi_struct, SPI_MODE, GD25X512ME_3BYTES_SIZE, rx_buffer1, FLASH_WRITE_ADDRESS_1, buffersize1); if(ERROR != memory_compare(tx_buffer1, rx_buffer1, buffersize1)) { printf("The data read with 1 line in indirect mode from flash is:"); printf("\n%s\n", tx_buffer1); printf("OSPI read/write with 1 line in indirect test success!\r\n"); } else { printf("OSPI read/write with 1 line in indirect test failed!\r\n"); while(1){ } } 5.八线间接模式写、读后比较 /* 8 lines in indirect mode read/write */ printf("\n\rThe data written with 8 lines in indirect mode to flash is:\n"); printf("\n%s\n\r", tx_buffer2); /* configure OSPI FLASH dummy cycles */ ospi_flash_write_enbale(OSPI_INTERFACE, &ospi_struct, SPI_MODE); ospi_flash_write_volatilecfg_register(OSPI_INTERFACE, &ospi_struct, SPI_MODE, GD25X512ME_3BYTES_SIZE, GD25X512ME_CFG_REG1_ADDR, GD25X512ME_CFG_16_DUMMY_CYCLES); /* configure OSPI FLASH enter STR OSPI mode */ ospi_flash_write_enbale(OSPI_INTERFACE, &ospi_struct, SPI_MODE); ospi_flash_write_volatilecfg_register(OSPI_INTERFACE, &ospi_struct, SPI_MODE, GD25X512ME_3BYTES_SIZE, GD25X512ME_CFG_REG0_ADDR, GD25X512ME_CFG_OCTAL_STR_WO); ospi_flash_autopolling_mem_ready(OSPI_INTERFACE, &ospi_struct, OSPI_MODE); /* erase specified address */ ospi_flash_autopolling_mem_ready(OSPI_INTERFACE, &ospi_struct, OSPI_MODE); ospi_flash_write_enbale(OSPI_INTERFACE, &ospi_struct, OSPI_MODE); ospi_flash_block_erase(OSPI_INTERFACE, &ospi_struct, OSPI_MODE, GD25X512ME_3BYTES_SIZE, FLASH_WRITE_ADDRESS_2, GD25X512ME_ERASE_4K); ospi_flash_autopolling_mem_ready(OSPI_INTERFACE, &ospi_struct, OSPI_MODE); /* write data of tx_buffer to flash */ ospi_flash_autopolling_mem_ready(OSPI_INTERFACE, &ospi_struct, OSPI_MODE); ospi_flash_write_enbale(OSPI_INTERFACE, &ospi_struct, OSPI_MODE); ospi_flash_page_program(OSPI_INTERFACE, &ospi_struct, OSPI_MODE, GD25X512ME_3BYTES_SIZE, tx_buffer2, FLASH_WRITE_ADDRESS_2, buffersize2); ospi_flash_autopolling_mem_ready(OSPI_INTERFACE, &ospi_struct, OSPI_MODE); /* read data from flash */ ospi_flash_read(OSPI_INTERFACE, &ospi_struct, OSPI_MODE, GD25X512ME_3BYTES_SIZE, rx_buffer2, FLASH_WRITE_ADDRESS_2, buffersize2); if(ERROR != memory_compare(tx_buffer2, rx_buffer2, buffersize2)) { printf("\n\rThe data read with 8 lines in indirect mode from flash is:\n"); for(i = 0; i < buffersize2; i++) { printf("%c", rx_buffer2[i]); } printf("\r\n"); printf("OSPI read/write with 8 lines in indirect test success!\r\n"); } else { printf("OSPI read/write with 8 lines in indirect test failed!\r\n"); while(1){ } } 6.内存映射模式模式写、读后比较 /* memory mapped mode read/write */ printf("\n\rThe data written in indirect mode to flash is:\n"); for(i = 0; i < buffersize3; i++){ printf("%c", tx_buffer3[i]); } /* erase specified address */ ospi_flash_autopolling_mem_ready(OSPI_INTERFACE, &ospi_struct, OSPI_MODE); ospi_flash_write_enbale(OSPI_INTERFACE, &ospi_struct, OSPI_MODE); ospi_flash_block_erase(OSPI_INTERFACE, &ospi_struct, OSPI_MODE, GD25X512ME_3BYTES_SIZE, 0x400000, GD25X512ME_ERASE_4K); /* write data of tx_buffer to flash */ ospi_flash_autopolling_mem_ready(OSPI_INTERFACE, &ospi_struct, OSPI_MODE); ospi_flash_write_enbale(OSPI_INTERFACE, &ospi_struct, OSPI_MODE); ospi_flash_memory_map_mode_wrap_enable(OSPI_INTERFACE, &ospi_struct, OSPI_MODE, GD25X512ME_3BYTES_SIZE); ospi_flash_page_program(OSPI_INTERFACE, &ospi_struct, OSPI_MODE, GD25X512ME_3BYTES_SIZE, tx_buffer3, 0x400000, buffersize3); ospi_flash_autopolling_mem_ready(OSPI_INTERFACE, &ospi_struct, OSPI_MODE); ospi_flash_memory_map_mode_wrap_enable(OSPI_INTERFACE, &ospi_struct, OSPI_MODE, GD25X512ME_3BYTES_SIZE); memory_mapped_read(rx_buffer3, FLASH_WRITE_ADDRESS_3, buffersize3); if(ERROR != memory_compare(tx_buffer3, rx_buffer3, buffersize3)) { printf("\n\rThe data read in memory mapped mode from flash is:\n"); for(i = 0; i < buffersize3; i++) { printf("%c", rx_buffer3[i]); } printf("\r\n"); printf("OSPI read in memory mapped mode test success!\r\n"); } else { printf("OSPI read in memory mapped mode test failed!\r\n"); while(1){ } }   三.测验       编译烧录后,复位查看日志,可看到三种模式均成功写入与读出,且数据正确。 图2:OSPI Flash读写       至此,实现OSPI对Nor Flash的读写功能。

  • 回复了主题帖: 【兆易GD32H759I-EVAL】--13.LCD触摸功能

    lugl4313820 发表于 2024-6-14 11:38 看起来效果挺好的了,我原来就是没有把触摸搞定。 一步步来,花点时间,可以搞定的

  • 2024-06-13
  • 发表了主题帖: 【兆易GD32H759I-EVAL】--13.LCD触摸功能

    本帖最后由 dirty 于 2024-6-14 09:10 编辑       前面展示了LVGL的移植与界面显示,本篇承接其上讲述LCD触摸功能。 一.原理了解       开发板LCD接口带有触摸引脚如下图,显示屏上带有触摸芯片 XPT2046,通过SPI接口与主控制器通信。 图1:触摸原理引脚   引脚定义 LCD_Touch_PENIRQ-----------PG3      笔触中断信号。当触摸屏被按下,该引脚被拉为低电平。 LCD_SPI4_MOSI----------------PF9       SPI串行数据输出 LCD_SPI4_MISO----------------PH7      SPI串行数据输入 LCD_SPI4_SCK-----------------PH6      SPI时钟 LCD_SPI4_NSS-----------------PF6      片选。拉低输入输出数据有效 LCD_PWM_BackLight---------PG13   背光控制 LCD_Touch_Busy---------------PF8     忙输出信号   二.代码准备 1.触摸引脚初始化。 /* SPI SCK pin */ #define SPI_SCK_PIN GPIO_PIN_6 #define SPI_SCK_PORT GPIOH #define SPI_SCK_LOW() gpio_bit_reset(SPI_SCK_PORT, SPI_SCK_PIN) #define SPI_SCK_HIGH() gpio_bit_set(SPI_SCK_PORT, SPI_SCK_PIN) /* SPI MOSI pin */ #define SPI_MOSI_PIN GPIO_PIN_9 #define SPI_MOSI_PORT GPIOF #define SPI_MOSI_LOW() gpio_bit_reset(SPI_MOSI_PORT, SPI_MOSI_PIN) #define SPI_MOSI_HIGH() gpio_bit_set(SPI_MOSI_PORT, SPI_MOSI_PIN) /* SPI MISO pin */ #define SPI_MISO_PIN GPIO_PIN_7 #define SPI_MISO_PORT GPIOH #define SPI_MISO_READ() gpio_input_bit_get(SPI_MISO_PORT, SPI_MISO_PIN) /* SPI Chip select pin */ #define SPI_TOUCH_CS_PIN GPIO_PIN_6 #define SPI_TOUCH_CS_PORT GPIOF #define SPI_TOUCH_CS_LOW() gpio_bit_reset(SPI_TOUCH_CS_PORT,SPI_TOUCH_CS_PIN) #define SPI_TOUCH_CS_HIGH() gpio_bit_set(SPI_TOUCH_CS_PORT,SPI_TOUCH_CS_PIN) /* LCD touch interrupt request pin */ #define TOUCH_PEN_INT_PIN GPIO_PIN_3 #define TOUCH_PEN_INT_PORT GPIOG #define TOUCH_PEN_INT_READ() gpio_input_bit_get(TOUCH_PEN_INT_PORT,TOUCH_PEN_INT_PIN) void touch_panel_gpio_configure(void) { /* GPIO clock enable */ rcu_periph_clock_enable(RCU_GPIOG); rcu_periph_clock_enable(RCU_GPIOH); rcu_periph_clock_enable(RCU_GPIOF); gpio_af_set(SPI_SCK_PORT, GPIO_AF_5, SPI_SCK_PIN); gpio_mode_set(SPI_SCK_PORT, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE,SPI_SCK_PIN); gpio_output_options_set(SPI_SCK_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_60MHZ,SPI_SCK_PIN); gpio_af_set(SPI_MOSI_PORT, GPIO_AF_5, SPI_MOSI_PIN); gpio_mode_set(SPI_MOSI_PORT, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE,SPI_MOSI_PIN); gpio_output_options_set(SPI_MOSI_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_60MHZ,SPI_MOSI_PIN); gpio_af_set(SPI_MISO_PORT, GPIO_AF_5, SPI_MISO_PIN); gpio_mode_set(SPI_MISO_PORT, GPIO_MODE_INPUT, GPIO_PUPD_NONE,SPI_MISO_PIN); gpio_output_options_set(SPI_MISO_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_60MHZ,SPI_MISO_PIN); gpio_mode_set(SPI_TOUCH_CS_PORT, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE,SPI_TOUCH_CS_PIN); gpio_output_options_set(SPI_TOUCH_CS_PORT, GPIO_OTYPE_PP,GPIO_OSPEED_60MHZ, SPI_TOUCH_CS_PIN); /* touch pen IRQ pin PI3 configure */ gpio_mode_set(TOUCH_PEN_INT_PORT, GPIO_MODE_INPUT, GPIO_PUPD_NONE,TOUCH_PEN_INT_PIN); gpio_output_options_set(TOUCH_PEN_INT_PORT, GPIO_OTYPE_PP,GPIO_OSPEED_60MHZ, TOUCH_PEN_INT_PIN); /* set chip select pin high */ SPI_TOUCH_CS_HIGH(); }   2.触摸驱动。这部分驱动会被用在LVGL 关于触摸功能lv_port_indev.c里。 (1)触摸开始,写数据到触摸屏,读触摸ad值 /*! \brief touch start \param[in] none \param[out] none \retval none */ void touch_start(void) { spi_clk(0); spi_cs(1); spi_mosi(1); spi_clk(1); spi_cs(0); } /*! \brief write data to touch screen \param[in] d: the data to be written \param[out] none \retval none */ void touch_write(uint8_t d) { uint8_t buf, i ; spi_clk(0); for( i = 0; i < 8; i++) { buf = ((d >> (7-i)) & 0x1); spi_mosi(buf); spi_clk(0); spi_clk(1); spi_clk(0); } } /*! \brief read the touch AD value \param[in] None \param[out] none \retval the value of touch AD */ uint16_t touch_read(void) { uint16_t buf ; uint8_t i ; buf=0; for(i = 0; i < 12; i++) { buf = buf << 1 ; spi_clk(1); spi_clk(0); if(RESET != spi_miso()) { buf = buf + 1 ; } } return( buf ); } (2)获取触摸X轴、Y轴坐标及防抖平均值 /*! \brief get the AD sample value of touch location at X coordinate \param[in] none \param[out] none \retval channel X+ AD sample value */ uint16_t touch_ad_x_get(void) { if (RESET != touch_pen_irq()) { /* touch pen is inactive */ return 0; } touch_start(); touch_write(0x00); touch_write(CH_X); return (touch_read()); } /*! \brief get the AD sample value of touch location at Y coordinate \param[in] none \param[out] none \retval channel Y+ AD sample value */ uint16_t touch_ad_y_get(void) { if (RESET != touch_pen_irq()) { /* touch pen is inactive */ return 0; } touch_start(); touch_write(0x00); touch_write(CH_Y); return (touch_read()); } /*! \brief get channel X+ AD average sample value \param[in] none \param[out] none \retval channel X+ AD average sample value */ uint16_t touch_average_ad_x_get(void) { uint8_t i; uint16_t temp=0; for (i=0;i<8;i++) { temp+=touch_ad_x_get(); spi_delay(1000); } temp>>=3; return temp; } /*! \brief get channel Y+ AD average sample value \param[in] none \param[out] none \retval channel Y+ AD average sample value */ uint16_t touch_average_ad_y_get(void) { uint8_t i; uint16_t temp=0; for (i=0;i<8;i++) { temp+=touch_ad_y_get(); spi_delay(1000); } temp>>=3; return temp; } (3)获取映射到屏幕上的像素点X轴、Y轴坐标及数据过滤 /*! \brief get X coordinate value of touch point on LCD screen \param[in] adx : channel X+ AD average sample value \param[out] none \retval X coordinate value of touch point */ uint16_t touch_coordinate_x_get(uint16_t adx) { uint16_t sx = 0; uint32_t r = adx - AD_Left; r *= LCD_X - 1; sx = r / (AD_Right - AD_Left); if (sx <= 0 || sx > LCD_X) { return 0; } return sx; } /*! \brief get Y coordinate value of touch point on LCD screen \param[in] ady : channel Y+ AD average sample value \param[out] none \retval Y coordinate value of touch point */ uint16_t touch_coordinate_y_get(uint16_t ady) { uint16_t sy = 0; uint32_t r = ady - AD_Top; r *= LCD_Y - 1; sy = r / (AD_Bottom - AD_Top); if (sy <= 0 || sy > LCD_Y) { return 0; } return sy; } /*! \brief get a value (X or Y) for several times. Order these values, remove the lowest and highest and obtain the average value \param[in] channel_select: select channel X or Y \arg CH_X: channel X \arg CH_Y: channel Y \param[out] none \retval a value(X or Y) of touch point */ uint16_t touch_data_filter(uint8_t channel_select) { uint16_t i=0, j=0; uint16_t buf[FILTER_READ_TIMES]; uint16_t sum=0; uint16_t temp=0; /* Read data in FILTER_READ_TIMES times */ for(i=0; i < FILTER_READ_TIMES; i++) { if (CH_X == channel_select) { buf[i] = touch_ad_x_get(); } else { /* CH_Y == channel_select */ buf[i] = touch_ad_y_get(); } } /* Sort in ascending sequence */ for(i = 0; i < FILTER_READ_TIMES - 1; i++) { for(j = i + 1; j < FILTER_READ_TIMES; j++) { if(buf[i] > buf[j]) { temp = buf[i]; buf[i] = buf[j]; buf[j] = temp; } } } sum = 0; for(i = FILTER_LOST_VAL; i < FILTER_READ_TIMES - FILTER_LOST_VAL; i++) { sum += buf[i]; } temp = sum / (FILTER_READ_TIMES - 2 * FILTER_LOST_VAL); return temp; } (4)获取过滤处理后触摸位置 /* \brief get the AD sample value of touch location. get the sample value for several times,order these values,remove the lowest and highest and obtain the average value \param[in] channel_select: select channel X or Y \param[out] none \arg ad_x: channel X AD sample value \arg ad_y: channel Y AD sample value \retval ErrStatus: SUCCESS or ERROR */ ErrStatus touch_ad_xy_get(uint16_t *ad_x, uint16_t *ad_y) { uint16_t ad_x1=0, ad_y1=0, ad_x2=0, ad_y2=0; ad_x1 = touch_data_filter(CH_X); ad_y1 = touch_data_filter(CH_Y); ad_x2 = touch_data_filter(CH_X); ad_y2 = touch_data_filter(CH_Y); if((abs(ad_x1 - ad_x2) > AD_ERR_RANGE) || (abs(ad_y1 - ad_y2) > AD_ERR_RANGE)) { return ERROR; } *ad_x = (ad_x1 + ad_x2) / 2; *ad_y = (ad_y1 + ad_y2) / 2; return SUCCESS; } (5)触摸扫描。用来探测触摸事件 /*! \brief detect the touch event \param[in] none \param[out] none \retval ErrStatus: SUCCESS or ERROR */ ErrStatus touch_scan(void) { uint8_t invalid_count = 0; if (RESET == touch_pen_irq()) { /* touch pen is active */ while((SUCCESS != touch_ad_xy_get(&touch_ad_x, &touch_ad_y))&& (invalid_count <20)) { invalid_count++; } if(invalid_count >= 20) { touch_ad_x = 0; touch_ad_y = 0; return ERROR; } } else { touch_ad_x = 0; touch_ad_y = 0; return ERROR; } return SUCCESS; }   3.lvgl触摸功能源文件lv_port_indev.c代码移植 (1)lv_port_indev_init屏蔽除触摸功能外的外设,如下图。这里注册了触摸读函数touchpad_read。   图2:触摸外设裁剪 (2)回调触摸读函数实现。这里调用了touch_scan触摸扫描函数。 /*Will be called by the library to read the touchpad*/ static void touchpad_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data) { static lv_coord_t last_x = 0; static lv_coord_t last_y = 0; /*Save the pressed coordinates and the state*/ if(touch_scan()) { touchpad_get_xy(&last_x, &last_y); data->state = LV_INDEV_STATE_PR; } else { data->state = LV_INDEV_STATE_REL; } /*Set the last pressed coordinates*/ data->point.x = last_x; data->point.y = last_y; } (3)获取触摸的X、Y轴坐标 /*Get the x and y coordinates if the touchpad is pressed*/ static void touchpad_get_xy(lv_coord_t * x, lv_coord_t * y) { /*Your code comes here*/ // (*x) = 0; // (*y) = 0; (*x) = touch_coordinate_x_get(touch_ad_x); (*y) = LCD_Y - touch_coordinate_y_get(touch_ad_y); }       经过这几步lvgl 触摸移植完成。   4.设计触摸按钮居中,实现触摸后按钮颜色及数值循环递变。 static void btn_event_cb(lv_event_t* e) { static uint8_t cnt = 0; lv_event_code_t code = lv_event_get_code(e); lv_obj_t* btn = lv_event_get_target(e); if (code == LV_EVENT_CLICKED) { cnt++; lv_obj_t* label = lv_obj_get_child(btn, 0); if(cnt>=_LV_PALETTE_LAST) { cnt=0; } lv_label_set_text_fmt(label, "Button: %d", cnt); lv_obj_set_style_bg_color(btn, lv_palette_main(cnt) ,0);//设置背景颜色 } } static void lv_example(void) { lv_obj_t* btn = lv_btn_create(lv_scr_act()); lv_obj_set_pos(btn, 180, 111);//10,10 lv_obj_set_size(btn, 120, 50); lv_obj_set_style_bg_color(btn, lv_palette_main(LV_PALETTE_YELLOW) ,0);//设置背景颜色 lv_obj_add_event_cb(btn, btn_event_cb, LV_EVENT_ALL, NULL); lv_obj_t* label = lv_label_create(btn); lv_label_set_text(label, "Button"); lv_obj_center(label); } 5.main函数如下 /*! \brief main function \param[in] none \param[out] none \retval none */ int main(void) { ov2640_id_struct ov2640id; BaseType_t ret; /* enable the CPU cache */ cache_enable(); /* initialize the LEDs */ test_status_led_init(); /* configure systick */ systick_config(); /* flash the LEDs for 2 time */ led_flash(2); /* configure USART0 */ usart_config(); /* configure TAMPER key */ gd_eval_key_init(KEY_TAMPER, KEY_MODE_EXTI); /* output a message on hyperterminal using printf function */ printf("\r\n ================= LVGL ================= \r\n"); /**********************LCD Application**********************/ /* config the EXMC access mode */ exmc_synchronous_dynamic_ram_init(EXMC_SDRAM_DEVICE0); printf("\r\nSDRAM Init \r\n"); /* camera initialization */ dci_ov2640_init(); dci_ov2640_id_read(&ov2640id); nvic_configuration(); /* DMA interrupt and channel enable */ dma_interrupt_enable(DMA1, DMA_CH7, DMA_CHXCTL_FTFIE); dma_channel_enable(DMA1, DMA_CH7); /* DCI enable */ dci_enable(); dci_capture_enable(); delay_1ms(100); dma_interrupt_disable(DMA1, DMA_CH7, DMA_CHXCTL_FTFIE); dma_channel_disable(DMA1, DMA_CH7); dci_capture_disable(); timer_config(); lcd_config(); lcd_init(); /* configure the GPIO of SPI touch panel */ touch_panel_gpio_configure(); #if (LVGL_DMA) delay_1ms(50); dma_config(); delay_1ms(1000); #endif lv_init(); lv_port_disp_init(); lv_port_indev_init(); lv_example(); // lv_demo_music(); //lv_demo_widgets(); while(1) { delay_1ms(5); lv_task_handler(); } }   三.测验       编译烧录后,屏幕中间显示黄色按钮如下图,触摸后变色及附带数值循环递变,如下视屏。 图3:触摸按钮       至此实现LCD屏触摸功能。   [localvideo]a97d98a59d5a731fe596821c35c3e52d[/localvideo]  

  • 2024-06-12
  • 回复了主题帖: 【兆易GD32H759I-EVAL】--9.摄像头屏显

    Jacktang 发表于 2024-6-4 07:33   摄像头图像屏显可以的 是的,实现了

  • 回复了主题帖: 【全能小网关|CH32V208】--3.按键中断

    lugl4313820 发表于 2024-6-10 18:58 中断回调中打印日志,会不会拖慢中断的响应呢? 这个还好的,不放心在中断里可以把打印放在清标志之后。中断里不宜放阻塞比较久的应用程需,也可置标志main函数处理。

  • 2024-06-11
  • 发表了主题帖: 【兆易GD32H759I-EVAL】--12.USB虚拟串口

    本帖最后由 dirty 于 2024-6-12 09:13 编辑       开发板MCU集成有两个USBFS/HS接口,可用作USB设备/USB主机/OTG设备。本篇讲述USBHS1 被 USB 主机枚举为一个 USB 虚拟串口,可通过USB口回传数据。 一.硬件原理与准备       开发板硬件USB原理图如下图1。这里使用到CN3 USB_HS1接口,跳线帽JP65、JP70打到USB引脚端。从官网可下载虚拟串口驱动并安装如下图2. 图1:USB原理 图2:USB虚拟串口驱动   二.代码准备       虚拟串口 CDC 例程遵循了 USB 通信类相关子协议,可以将 USB 虚拟成 COM 口,可以像普通的串口一样进行操作。官方提供了USBFS固件库实现 USB 的数据通信。USBFS 固件库分为应用层和驱动层,用户层用户可以修改,驱动层包括 USB 主机或设备驱动和 USB 底层驱动。 1.关于CDC库。可以参阅下官方“GD32 USBFS&USBHS 固件库使用指南”文档,做更深入全面了解。       在GD32H7xx_Firmware_Library\GD32H7xx_usbhs_library\device\class\cdc下是CDC设备功能代码。包含了USBD_VID 和USBD_PID的定义、CDC 相关的描述符项。       CDC 本身包含两个接口,一个命令接口、一个数据接口,命令接口相关描述符如下: 图3:CDC命令接口描述符       CDC 设备类接口函数如下,这里主要关注后三个 图4:CDC 设备类接口函数       CDC 设备类请求类如下: 图5:CDC 设备类请求       CDC 用户接口函数如下,这个是我们应用需要的。 图6:CDC 用户接口函数 2.打开CDC_ACM工程,在Options里配置宏USE_USB_HS使用高速USB;配置USE_USBHS1,以使用USB_HS1。 图7:USB宏定义 3.配置USB时钟 void usb_rcu_config(void) { pmu_usb_regulator_enable(); pmu_usb_voltage_detector_enable(); while (pmu_flag_get(PMU_FLAG_USB33RF) != SET) { } #ifdef USE_USB_FS #ifndef USE_IRC48M #ifdef USE_USBHS0 rcu_usb48m_clock_config(IDX_USBHS0, RCU_USB48MSRC_PLL0R); #endif /* USE_USBHS0 */ #ifdef USE_USBHS1 rcu_usb48m_clock_config(IDX_USBHS1, RCU_USB48MSRC_PLL0R); #endif /* USE_USBHS1 */ #else /* enable IRC48M clock */ rcu_osci_on(RCU_IRC48M); /* wait till IRC48M is ready */ while (SUCCESS != rcu_osci_stab_wait(RCU_IRC48M)) { } #ifdef USE_USBHS0 rcu_usb48m_clock_config(IDX_USBHS0, RCU_USB48MSRC_IRC48M); #endif /* USE_USBHS0 */ #ifdef USE_USBHS1 rcu_usb48m_clock_config(IDX_USBHS1, RCU_USB48MSRC_IRC48M); #endif /* USE_USBHS1 */ #endif /* USE_IRC48M */ #endif /* USE_USB_FS */ #ifdef USE_USBHS0 rcu_periph_clock_enable(RCU_USBHS0); #endif /* USE_USBHS0 */ #ifdef USE_USBHS1 rcu_periph_clock_enable(RCU_USBHS1); #endif /* USE_USBHS1 */ #ifdef USE_ULPI_PHY rcu_periph_clock_enable(RCU_ULPI); #endif /* USE_ULPI_PHY */ } 4.配置USB delay定时器。这里使用到Timer2,在gd32h7xx_it.c里有其定时中断函数TIMER2_IRQHandler。 void usb_timer_init (void) { /* configure the priority group to 2 bits */ nvic_priority_group_set(NVIC_PRIGROUP_PRE2_SUB2); /* enable the TIM2 global interrupt */ nvic_irq_enable((uint8_t)TIMER2_IRQn, 1U, 0U); rcu_periph_clock_enable(RCU_TIMER2); } 5.初始化cdc参数。这里使用USBHS1 usb外设,选用高速USB_SPEED_HIGH,通过结构体指针,初始化后将cdc_acm赋值带出。 void usb_para_init(usb_core_driver *udev, uint32_t usb_periph, uint32_t usb_speed) { usb_core_basic *usb_basic = &udev->bp; /* configure USB default transfer mode as FIFO mode */ usb_basic->transfer_mode = (uint8_t)USB_USE_FIFO; /* USB default speed is full-speed */ usb_basic->core_speed = (uint8_t)USB_SPEED_FULL; /* USB basic register address setting */ switch(usb_periph){ case USBHS0: usb_basic->base_reg = (uint32_t)USBHS0_REG_BASE; break; case USBHS1: usb_basic->base_reg = (uint32_t)USBHS1_REG_BASE; break; default: break; } /* set the host channel numbers */ usb_basic->num_pipe = USBHS_MAX_CHANNEL_COUNT; /* set the device endpoint numbers */ usb_basic->num_ep = USBHS_MAX_EP_COUNT; if (USB_SPEED_HIGH == usb_speed) { #ifdef USB_EXTERNAL_ULPI_PHY_ENABLED usb_basic->phy_itf = USB_ULPI_PHY_EXTERNAL; #endif /* USB_EXTERNAL_ULPI_PHY_ENABLED */ #ifdef USB_EMBEDDED_HS_PHY_ENABLED usb_basic->phy_itf = USB_EMBEDDED_PHY_HS; #endif /* USB_EMBEDDED_HS_PHY_ENABLED */ } else if (USB_SPEED_FULL == usb_speed) { #ifdef USB_EMBEDDED_FS_PHY_ENABLED usb_basic->phy_itf = USB_EMBEDDED_PHY_FS; #endif /* USB_EMBEDDED_FS_PHY_ENABLED */ } else { /* no operation */ } #ifdef USB_INTERNAL_DMA_ENABLED usb_basic->transfer_mode = USB_USE_DMA; #endif /* USB_INTERNAL_DMA_ENABLED */ usb_basic->sof_enable = (uint8_t)USB_SOF_OUTPUT; usb_basic->low_power = (uint8_t)USB_LOW_POWER; #if (1U == LPM_ENABLED) usb_basic->lpm_enable = 1U; #endif /* LPM_ENABLED */ } 6.初始化USB设备模式,并加载驱动。这里使用到了前面初始化的cdc_acm,初始化usb描述符和USB内核。这里面引用到了CDC库。 void usbd_init(usb_core_driver *udev, usb_desc *desc, usb_class_core *class_core) { udev->dev.desc = desc; /* class callbacks */ udev->dev.class_core = class_core; /* create serial string */ serial_string_get(udev->dev.desc->strings[STR_IDX_SERIAL]); /* configure USB capabilities */ (void)usb_basic_init(&udev->bp, &udev->regs); /* initializes the USB core*/ (void)usb_core_init(udev->bp, &udev->regs); /* set device disconnect */ usbd_disconnect(udev); /* initializes device mode */ (void)usb_devcore_init(udev); /* set device connect */ usbd_connect(udev); udev->dev.cur_status = (uint8_t)USBD_DEFAULT; } 7.配置USB中断。宏定义使用USE_USBHS1。在gd32h7xx_it.c里有其中断函数USBHS1_IRQHandler,USBHS1_WKUP_IRQHandler,USBHS1_EP1_OUT_IRQHandler。 /*! \brief configure USB interrupt \param[in] none \param[out] none \retval none */ void usb_intr_config(void) { nvic_priority_group_set(NVIC_PRIGROUP_PRE2_SUB2); #ifdef USE_USBHS0 nvic_irq_enable((uint8_t)USBHS0_IRQn, 3U, 0U); #endif /* USE_USBHS0 */ #ifdef USE_USBHS1 nvic_irq_enable((uint8_t)USBHS1_IRQn, 3U, 0U); #endif /* USE_USBHS0 */ /* enable the power module clock */ rcu_periph_clock_enable(RCU_PMU); #ifdef USE_USBHS0 /* USB wakeup EXTI line configuration */ exti_interrupt_flag_clear(EXTI_31); exti_init(EXTI_31, EXTI_INTERRUPT, EXTI_TRIG_RISING); exti_interrupt_enable(EXTI_31); nvic_irq_enable((uint8_t)USBHS0_WKUP_IRQn, 1U, 0U); #endif /* USE_USBHS0 */ #ifdef USE_USBHS1 /* USB wakeup EXTI line configuration */ exti_interrupt_flag_clear(EXTI_32); exti_init(EXTI_32, EXTI_INTERRUPT, EXTI_TRIG_RISING); exti_interrupt_enable(EXTI_32); nvic_irq_enable((uint8_t)USBHS1_WKUP_IRQn, 1U, 0U); #endif /* USE_USBHS1 */ #ifdef USB_DEDICATED_EP1_ENABLED #ifdef USE_USBHS0 nvic_irq_enable((uint8_t)USBHS0_EP1_OUT_IRQn, 1U, 0U); nvic_irq_enable((uint8_t)USBHS0_EP1_IN_IRQn, 1U, 0U); #endif /* USE_USBHS0 */ #ifdef USE_USBHS1 nvic_irq_enable((uint8_t)USBHS1_EP1_OUT_IRQn, 1U, 0U); nvic_irq_enable((uint8_t)USBHS1_EP1_IN_IRQn, 1U, 0U); #endif /* USE_USBHS1 */ #endif /* USB_DEDICATED_EP1_ENABLED */ } 8.main函数实现USB虚拟串口接收完全后回显。 int main(void) { /* //USB HS1 CDC初始化 ...... */ while (1) { if (USBD_CONFIGURED == cdc_acm.dev.cur_status) { if (0U == cdc_acm_check_ready(&cdc_acm)) { cdc_acm_data_receive(&cdc_acm); } else { cdc_acm_data_send(&cdc_acm); } } } }   三.测验       编译烧录后,复位运行,可以在设备管理器找到虚拟串口。用串口工具打开,发送一串字符,在接收区会接收到一串相同的字符。如下图 图8:USB虚拟串口测试           至此,实现USB 虚拟串口功能。  

  • 回复了主题帖: 【兆易GD32H759I-EVAL】--11.ADC温度与电压测量

    Jacktang 发表于 2024-6-11 07:26 ADC温度与电压测量的功能已经测试实现,完美 是的,采样精度还是挺高的

  • 回复了主题帖: 【兆易GD32H759I-EVAL】--11.ADC温度与电压测量

    秦天qintian0303 发表于 2024-6-11 08:59 独立采集的还是连续采集的?实现的还是挺不错的,可以适当加一下ADC模式的介绍 独立的,在初始化里连续模式是禁能的,使用时候触发使能后采样。后面建议可以考虑,编写时候也存在不可能面面俱到,将关注点、重点地方会提一下。其次官方文档也有些介绍,想多些了解查阅下资料还是比较重要的

  • 2024-06-10
  • 回复了主题帖: 【兆易GD32H759I-EVAL】--10.以太网TCP/UDP通讯

    lugl4313820 发表于 2024-6-10 18:57 楼主,跑了RTOS吗?如果跑OS是采用什么样的OS呢? 上面第三项代码准备  开头那里有说,上的FreeRTOS

  • 发表了主题帖: 【兆易GD32H759I-EVAL】--11.ADC温度与电压测量

    本帖最后由 dirty 于 2024-6-11 09:11 编辑       本篇讲述GD32H759I ADC测温与板载ADC电路电压测量。 一.硬件原理       开发板板载ADC可调电路,如下图1,VR1为可调电阻。通过芯片手册ADC部分如下图2,可知ADC2为12bit ADC;内部自带温度传感器ADC2 Channel18.这里跳线帽JP76短接,VREF+参考电压3.3V. 图1:ADC电路 图2:ADC功能描述   二.代码准备 1.ADC配置初始化。这里测温度部分用到ADC2 Channel18, 侧外部板载可调电阻ADC接口电压使用到ADC2 Channel0. void adc_config(void) { /* reset ADC */ adc_deinit(ADC2); /* ADC clock config */ adc_clock_config(ADC2, ADC_CLK_SYNC_HCLK_DIV6); /* ADC contineous function enable */ adc_special_function_config(ADC2, ADC_CONTINUOUS_MODE, DISABLE); /* ADC scan mode enable */ adc_special_function_config(ADC2, ADC_SCAN_MODE, ENABLE); /* ADC resolution config */ adc_resolution_config(ADC2, ADC_RESOLUTION_12B); /* ADC data alignment config */ adc_data_alignment_config(ADC2, ADC_DATAALIGN_RIGHT); /* ADC channel length config */ adc_channel_length_config(ADC2, ADC_INSERTED_CHANNEL, 2); /* ADC temperature sensor channel config */ adc_inserted_channel_config(ADC2, 0, ADC_CHANNEL_18, 480); /* ADC internal reference voltage channel config */ adc_inserted_channel_config(ADC2, 1, ADC_CHANNEL_0, 480); /* enable ADC temperature channel */ adc_internal_channel_config(ADC_CHANNEL_INTERNAL_TEMPSENSOR, ENABLE); /* enable internal reference voltage channel */ adc_internal_channel_config(ADC_CHANNEL_INTERNAL_VREFINT, ENABLE); /* ADC trigger config */ adc_external_trigger_config(ADC2, ADC_INSERTED_CHANNEL, EXTERNAL_TRIGGER_DISABLE); /* enable ADC interface */ adc_enable(ADC2); /* wait for ADC stability */ delay_1ms(1); /* ADC calibration mode config */ adc_calibration_mode_config(ADC2, ADC_CALIBRATION_OFFSET); /* ADC calibration number config */ adc_calibration_number(ADC2, ADC_CALIBRATION_NUM1); /* ADC calibration and reset calibration */ adc_calibration_enable(ADC2); /* ADC software trigger enable */ adc_software_trigger_enable(ADC2, ADC_INSERTED_CHANNEL); } 2.内部通道温度计算使用到了校准值计算斜率,然后根据内部实际采样值取点线性计算。为与实际环境温度温和,这里计算公式做了下修正。 temperature_value_25 = ADC_TEMP_CALIBRATION_VALUE_25 & 0x0FFF; temperature_value_minus40 =ADC_TEMP_CALIBRATION_VALUE_MINUS40 & 0x0FFF; avg_slope = -(temperature_value_25 - temperature_value_minus40) / (25.0f + 40.0f); /* value convert */ temperature = (temperature_value_25 - (int32_t)ADC_IDATA0(ADC2))*3.3f/4096/avg_slope*1000 + 20 ;//25 3.main函数实现温度与ADC电路电压串口打印。这里参考电压3.3V,12bitadc,所以公式为 vres_value = (ADC_IDATA1(ADC2) * 3.3f / 4096);。代码如下 int main(void) { /* enable the CPU cache */ cache_enable(); /* peripheral clocks configuration */ rcu_config(); /* configure systick */ systick_config(); /* USART configuration */ gd_eval_com_init(EVAL_COM); printf("UART Init\r\n"); /* ADC configuration */ adc_config(); printf("ADC Init\r\n"); temperature_value_25 = ADC_TEMP_CALIBRATION_VALUE_25 & 0x0FFF; temperature_value_minus40 =ADC_TEMP_CALIBRATION_VALUE_MINUS40 & 0x0FFF; avg_slope = -(temperature_value_25 - temperature_value_minus40) / (25.0f + 40.0f); while(1) { /* ADC software trigger enable */ adc_software_trigger_enable(ADC2, ADC_INSERTED_CHANNEL); /* delay a time in milliseconds */ delay_1ms(3000); /* value convert */ temperature = (temperature_value_25 - (int32_t)ADC_IDATA0(ADC2))*3.3f/4096/avg_slope*1000 + 20 ;//25 vres_value = (ADC_IDATA1(ADC2) * 3.3f / 4096); /* value print */ printf(" the temperature data is %2.0f degrees Celsius\r\n", temperature); printf(" the reference voltage data is %5.3fV \r\n", vres_value); printf(" \r\n"); } }     三.测验       编译烧录后,可看到温度基本稳定为环境温度。旋转调节VR1分压阻值,可看到往一个方向旋时电压递减或递增,且范围在0--3.3V之间。ADC测试日志如下: 图3:ADC测试       至此,实现开发板ADC功能。  

  • 发表了主题帖: 【兆易GD32H759I-EVAL】--10.以太网TCP/UDP通讯

    本帖最后由 dirty 于 2024-6-10 11:53 编辑       本篇讲述板载以太网TCP/UDP通讯功能. 一.硬件原理与准备       开发板以太网模块功能原理如下 图1:以太网原理图       开发板外挂一颗以太网芯片DP83848CVV,芯片使用RMII(Reduced Media Independant Interface)精简介质无关接口。RMI收发使用2位数据进行传输,收发时钟均采用50MHz时钟源。信号定义如下: 图2:RMII信号定义       准备工作,将跳线帽JP48, JP51, JP57, JP59, JP60, JP70接到以太网侧。可以看到开发板以太网功能与LCD/摄像头引脚不兼容,开发板跳线帽灵活切换还是很方便的。 图3:以太网跳线帽选择   二.了解TCP/UDP       TCP(Transmission Control Protocol)和UDP(User Datagram Protocol)协议属于传输层协议。传输层是计算机网络中的一层,位于网络层和应用层之间。它主要负责在网络中的两个端系统之间提供可靠的、端到端的数据传输服务。简单理解,传输层就是负责在源主机和目标主机之间提供端到端的数据传输。       TCP提供IP环境下的数据可靠传输,它提供的服务包括数据流传送、可靠性、有效流控、全双工操作和多路复用。通过面向连接、端到端和可靠的数据包发送。通俗说,它是事先为所发送的数据开辟出连接好的通道,然后再进行数据发送;       UDP是一种无连接的协议,不提供可靠性保证,但传输效率较高。它将数据分割成数据报,每个数据报都是独立的,可以独立传输,因此没有复杂的连接建立和维护过程。   三.代码准备       lwip协议栈资源可在:http://download.savannah.nongnu.org/releases/lwip/网址下载,本工程上FreeRTOS系统,并会使用到该网络协议栈。 1.关于宏的定义使用。这里使能动态分配IP,使用NET0,并配置服务端IP(电脑作为服务端,通过控制台使用命令ipconfigj即可查到)。 #define USE_DHCP /* enable DHCP, if disabled static address is used */ /*The USE_ENET0 and USE_ENET1 macros cannot be opened at the same time*/ #define USE_ENET0 //#define USE_ENET1 #define IP_S_ADDR0 192 #define IP_S_ADDR1 168 #define IP_S_ADDR2 3 #define IP_S_ADDR3 5 1.网络硬件接口初始化。包括了引脚配置和网络事件中断使能 void enet_system_setup(void) { nvic_configuration(); /* configure the GPIO ports for ethernet pins */ enet_gpio_config(); /* configure the ethernet MAC/DMA */ enet_mac_dma_config(); if(0 == enet_init_status) { while(1) { } } #ifdef USE_ENET0 enet_interrupt_enable(ENET0, ENET_DMA_INT_NIE); enet_interrupt_enable(ENET0, ENET_DMA_INT_RIE); #ifdef SELECT_DESCRIPTORS_ENHANCED_MODE enet_desc_select_enhanced_mode(ENET0); #endif /* SELECT_DESCRIPTORS_ENHANCED_MODE */ #endif /* USE_ENET0 */ #ifdef USE_ENET1 enet_interrupt_enable(ENET1, ENET_DMA_INT_NIE); enet_interrupt_enable(ENET1, ENET_DMA_INT_RIE); #ifdef SELECT_DESCRIPTORS_ENHANCED_MODE enet_desc_select_enhanced_mode(ENET1); #endif /* SELECT_DESCRIPTORS_ENHANCED_MODE */ #endif /* USE_ENET1 */ } /*! \brief this function handles ethernet interrupt request \param[in] none \param[out] none \retval none */ void ENET0_IRQHandler(void) { portBASE_TYPE xHigherPriorityTaskWoken = pdFALSE; /* frame received */ if(SET == enet_interrupt_flag_get(ENET0, ENET_DMA_INT_FLAG_RS)){ /* give the semaphore to wakeup LwIP task */ xSemaphoreGiveFromISR(g_rx_semaphore, &xHigherPriorityTaskWoken); } //printf("ENET0_IRQHandler\r\n"); /* clear the enet DMA Rx interrupt pending bits */ enet_interrupt_flag_clear(ENET0, ENET_DMA_INT_FLAG_RS_CLR); enet_interrupt_flag_clear(ENET0, ENET_DMA_INT_FLAG_NI_CLR); /* switch tasks if necessary */ if(pdFALSE != xHigherPriorityTaskWoken){ portEND_SWITCHING_ISR(xHigherPriorityTaskWoken); } } 2.LwIP 协议栈初始化。包含了tcp ip任务栈初始化, IP分配,网络芯片寄存器配置,网络状态事件回调注册及设置生效网络。 /*! \brief initializes the LwIP stack \param[in] none \param[out] none \retval none */ void lwip_stack_init(void) { ip_addr_t ipaddr; ip_addr_t netmask; ip_addr_t gw; /* create tcp_ip stack thread */ tcpip_init( NULL, NULL ); /* IP address setting */ #ifdef USE_DHCP ipaddr.addr = 0; netmask.addr = 0; gw.addr = 0; #else IP4_ADDR(&ipaddr, IP_ADDR0, IP_ADDR1, IP_ADDR2, IP_ADDR3); IP4_ADDR(&netmask, NETMASK_ADDR0, NETMASK_ADDR1 , NETMASK_ADDR2, NETMASK_ADDR3); IP4_ADDR(&gw, GW_ADDR0, GW_ADDR1, GW_ADDR2, GW_ADDR3); #endif /* USE_DHCP */ #ifdef USE_ENET0 /* - netif_add(struct netif *netif, ip_addr_t *ipaddr, ip_addr_t *netmask, ip_addr_t *gw, void *state, err_t (* init)(struct netif *netif), err_t (* input)(struct pbuf *p, struct netif *netif)) Adds your network interface to the netif_list. Allocate a struct netif and pass a pointer to this structure as the first argument. Give pointers to cleared ip_addr structures when using DHCP, or fill them with sane numbers otherwise. The state pointer may be NULL. The init function pointer must point to a initialization function for your ethernet netif interface. The following code illustrates it's use.*/ netif_add(&g_mynetif0, &ipaddr, &netmask, &gw, NULL, &ethernetif_init, &tcpip_input); /* registers the default network interface */ netif_set_default(&g_mynetif0); netif_set_status_callback(&g_mynetif0, lwip_netif_status_callback); /* when the netif is fully configured this function must be called */ netif_set_up(&g_mynetif0); #endif /* USE_ENET0 */ #ifdef USE_ENET1 /* - netif_add(struct netif *netif, ip_addr_t *ipaddr, ip_addr_t *netmask, ip_addr_t *gw, void *state, err_t (* init)(struct netif *netif), err_t (* input)(struct pbuf *p, struct netif *netif)) Adds your network interface to the netif_list. Allocate a struct netif and pass a pointer to this structure as the first argument. Give pointers to cleared ip_addr structures when using DHCP, or fill them with sane numbers otherwise. The state pointer may be NULL. The init function pointer must point to a initialization function for your ethernet netif interface. The following code illustrates it's use.*/ netif_add(&g_mynetif1, &ipaddr, &netmask, &gw, NULL, &ethernetif_init, &tcpip_input); /* registers the default network interface */ netif_set_default(&g_mynetif1); netif_set_status_callback(&g_mynetif1, lwip_netif_status_callback); /* when the netif is fully configured this function must be called */ netif_set_up(&g_mynetif1); #endif /* USE_ENET1 */ } 3.网络状态回调。这里实现TCP/UDP 客户端服务端功能,每一个创建任务并予以实现. /*! \brief after the netif is fully configured, it will be called to initialize the function of telnet, client and udp \param[in] netif: the struct used for lwIP network interface \param[out] none \retval none */ void lwip_netif_status_callback(struct netif *netif) { if(((netif->flags & NETIF_FLAG_UP) != 0) && (0 != netif->ip_addr.addr)) { printf("%s\r\n",__func__); /* initilaize the tcp server: telnet 8000 */ hello_gigadevice_init(); printf("initilaize the tcp server: telnet 8000\r\n"); /* initilaize the tcp client: echo 10260 */ tcp_client_init(); printf("initilaize the tcp client: echo 10260\r\n"); /* initilaize the udp: echo 1025 */ udp_echo_init(); printf("initilaize the udp: echo 1025\r\n"); } }       这里以TCP Client为例,讲解并添加写日志,打印接收数据。开发板作为客户端,电脑作为服务端。ipaddr就是前面讲过电脑端IP,tcp 端口定义为10260,创建socket后,绑定IP及端口,然后连接。如连接上服务端后接收数据等待,如接收到数据,将接受的数据原样发回给服务端,这里添加了接收数据打印。其他,TCP Server 端口号为8000,UDP 端口1025. static void tcp_client_task(void *arg) { int ret, recvnum, sockfd = -1; int tcp_port = 10260; struct sockaddr_in svr_addr, clt_addr; char buf[100]; ip_addr_t ipaddr; IP4_ADDR(&ipaddr, IP_S_ADDR0, IP_S_ADDR1, IP_S_ADDR2, IP_S_ADDR3); /* set up address to connect to */ svr_addr.sin_family = AF_INET; svr_addr.sin_port = htons(tcp_port); svr_addr.sin_addr.s_addr = ipaddr.addr; clt_addr.sin_family = AF_INET; clt_addr.sin_port = htons(tcp_port); clt_addr.sin_addr.s_addr = htons(INADDR_ANY); while(1) { /* create the socket */ sockfd = socket(AF_INET, SOCK_STREAM, 0); if(sockfd < 0) { continue; } // printf("create tcp_client socket ok\r\n"); ret = bind(sockfd, (struct sockaddr *)&clt_addr, sizeof(clt_addr)); if(ret < 0) { lwip_close(sockfd); sockfd = -1; continue; } // printf("bind server ok\r\n"); /* connect */ ret = connect(sockfd, (struct sockaddr *)&svr_addr, sizeof(svr_addr)); if(ret < 0) { lwip_close(sockfd); sockfd = -1; continue; } printf("connect server ok\r\n"); while(-1 != sockfd) { /* reveive packets, and limit a reception to MAX_BUF_SIZE bytes */ recvnum = recv(sockfd, buf, MAX_BUF_SIZE, 0); if(recvnum <= 0) { lwip_close(sockfd); sockfd = -1; break; } printf("receive tcp server data ,len:%d\r\n",recvnum); for(int i=0;i<recvnum;i++) { printf("0x%02x ",buf[i]); } printf("\r\nreceive data and send to\r\n"); send(sockfd, buf, recvnum, 0); } lwip_close(sockfd); sockfd = -1; } } 4.dhcp实现。这里通过创建任务dhcp_task,启动dhcp配置,等待路由IP分配,获取到动态IP. void dhcp_task(void * pvParameters) { ip_addr_t ipaddr; ip_addr_t netmask; ip_addr_t gw; #ifdef USE_ENET0 struct dhcp *dhcp_client0; #endif /* USE_ENET0 */ #ifdef USE_ENET1 struct dhcp *dhcp_client1; #endif /* USE_ENET1 */ printf("dhcp task run\r\n"); for(;;){ #ifdef USE_ENET0 switch(dhcp_state){ case DHCP_START: dhcp_start(&g_mynetif0); /* IP address should be set to 0 every time we want to assign a new DHCP address*/ dhcp_state = DHCP_WAIT_ADDRESS; break; case DHCP_WAIT_ADDRESS: /* read the new IP address */ ip_address.addr = g_mynetif0.ip_addr.addr; if(0 != ip_address.addr){ dhcp_state = DHCP_ADDRESS_ASSIGNED; printf("\r\nDHCP -- eval board ip address: %d.%d.%d.%d \r\n", ip4_addr1_16(&ip_address), \ ip4_addr2_16(&ip_address), ip4_addr3_16(&ip_address), ip4_addr4_16(&ip_address)); }else{ /* DHCP timeout */ dhcp_client0 = netif_dhcp_data(&g_mynetif0); if (dhcp_client0->tries > MAX_DHCP_TRIES){ dhcp_state = DHCP_TIMEOUT; /* stop DHCP */ dhcp_stop(&g_mynetif0); /* static address used */ IP4_ADDR(&ipaddr, IP_ADDR0 ,IP_ADDR1 , IP_ADDR2 , IP_ADDR3 ); IP4_ADDR(&netmask, NETMASK_ADDR0, NETMASK_ADDR1, NETMASK_ADDR2, NETMASK_ADDR3); IP4_ADDR(&gw, GW_ADDR0, GW_ADDR1, GW_ADDR2, GW_ADDR3); netif_set_addr(&g_mynetif0, &ipaddr , &netmask, &gw); } } break; default: break; } #endif /* USE_ENET0 */ #ifdef USE_ENET1 switch(dhcp_state){ case DHCP_START: dhcp_start(&g_mynetif1); /* IP address should be set to 0 every time we want to assign a new DHCP address*/ dhcp_state = DHCP_WAIT_ADDRESS; break; case DHCP_WAIT_ADDRESS: /* read the new IP address */ ip_address.addr = g_mynetif1.ip_addr.addr; if(0 != ip_address.addr){ dhcp_state = DHCP_ADDRESS_ASSIGNED; printf("\r\nDHCP -- eval board ip address: %d.%d.%d.%d \r\n", ip4_addr1_16(&ip_address), \ ip4_addr2_16(&ip_address), ip4_addr3_16(&ip_address), ip4_addr4_16(&ip_address)); }else{ /* DHCP timeout */ dhcp_client1 = netif_dhcp_data(&g_mynetif1); if (dhcp_client1->tries > MAX_DHCP_TRIES){ dhcp_state = DHCP_TIMEOUT; /* stop DHCP */ dhcp_stop(&g_mynetif1); /* static address used */ IP4_ADDR(&ipaddr, IP_ADDR0 ,IP_ADDR1 , IP_ADDR2 , IP_ADDR3 ); IP4_ADDR(&netmask, NETMASK_ADDR0, NETMASK_ADDR1, NETMASK_ADDR2, NETMASK_ADDR3); IP4_ADDR(&gw, GW_ADDR0, GW_ADDR1, GW_ADDR2, GW_ADDR3); netif_set_addr(&g_mynetif1, &ipaddr , &netmask, &gw); } } break; default: break; } #endif /* USE_ENET1 */ /* wait 250 ms */ vTaskDelay(250); } }   四.调试测验       编译烧录。准备一根网线,一端接到路由器网口,一端接到开发板网口。准备网络调试助手工具,个人用的NetAssist。 图4:开发板与路由器连接好网线       上电,等待获取到开发板IP,开发板作为TCP Server,配置如下图所示. 图5:开发板作为TCP Server工具配置       点击连接,在工具发送数据,可以看到通讯如下 图6:开发板作为TCPServer通讯       开发板作为TCP Client,配置通讯如下图所示 图7:开发板作为TCPClient工具配置与通讯       开发板作为UDP,配置如下图所示 图8:UDP通讯                     至此,实现开发板以太网TCP/UDP通讯功能。  

  • 2024-06-09
  • 发表了主题帖: 【全能小网关|CH32V208】--3.按键中断

          本篇讲述按键IO外部中断。   一.按键原理与准备       由下原理图知用户按键下降沿低电平触发,我们这里用PA0作为按键检测IO口,并在杜邦线连接排针PA0与KEY引脚。 图1:按键原理   二.代码准备 1.初始化配置PA0外部中断,下降沿触发。 void EXTI0_INT_INIT(void) { GPIO_InitTypeDef GPIO_InitStructure = {0}; EXTI_InitTypeDef EXTI_InitStructure = {0}; NVIC_InitTypeDef NVIC_InitStructure = {0}; RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO | RCC_APB2Periph_GPIOA, ENABLE); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; GPIO_Init(GPIOA, &GPIO_InitStructure); /* GPIOA ----> EXTI_Line0 */ GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource0); EXTI_InitStructure.EXTI_Line = EXTI_Line0; EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; EXTI_InitStructure.EXTI_LineCmd = ENABLE; EXTI_Init(&EXTI_InitStructure); NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); } 2.中断函数。这里简单就打印下日志 /********************************************************************* * @fn EXTI0_IRQHandler * * [url=home.php?mod=space&uid=159083]@brief[/url] This function handles EXTI0 Handler. * * [url=home.php?mod=space&uid=784970]@return[/url] none */ void EXTI0_IRQHandler(void) { if(EXTI_GetITStatus(EXTI_Line0)!=RESET) { printf("Run at EXTI\r\n"); EXTI_ClearITPendingBit(EXTI_Line0); /* Clear Flag */ } } 3.主函数 int main(void) { NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1); SystemCoreClockUpdate(); Delay_Init(); USART_Printf_Init(115200); printf("Uart Initr\n"); printf("SystemClk:%d\r\n", SystemCoreClock); printf( "ChipID:%08x\r\n", DBGMCU_GetCHIPID() ); printf("EXTI0 Test\r\n"); EXTI0_INT_INIT(); printf("INT Init\n"); while(1) { Delay_Ms(1000); //printf("Run at main\r\n"); } }   三.测验       编译烧录,上电运行,查看日志有初始化,当按下用户按键S2,可以看到触发IO中断。 图2:按键中断日志       至此,实现按键IO外部中断功能。  

  • 2024-06-08
  • 发表了主题帖: 【全能小网关|CH32V208】--2.串口打印

          本篇讲述开发板串口打印功能。串口打印可以辅助开发阶段调试和测验。 一.硬件原理与准备       查阅datasheet可看到PA9、PA10可配置成串口USART1_TX、USART1_RX,如下图 图1:串口引脚选择       查阅原理图串口部分接口如下图2。同时,安装好WCH-LinkE具有串口功能,硬件连接好如下图3 图2:硬件连接原理 图3:串口连接   二.代码准备 1.初始化串口UART1,配置波特率115200. /* UART Printf Definition */ #define DEBUG_UART1 1 #define DEBUG_UART2 2 #define DEBUG_UART3 3 /* DEBUG UATR Definition */ #ifndef DEBUG #define DEBUG DEBUG_UART1 #endif void USART_Printf_Init(uint32_t baudrate) { GPIO_InitTypeDef GPIO_InitStructure; USART_InitTypeDef USART_InitStructure; #if(DEBUG == DEBUG_UART1) RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_Init(GPIOA, &GPIO_InitStructure); #elif(DEBUG == DEBUG_UART2) RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_Init(GPIOA, &GPIO_InitStructure); #elif(DEBUG == DEBUG_UART3) RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_Init(GPIOB, &GPIO_InitStructure); #endif USART_InitStructure.USART_BaudRate = baudrate; USART_InitStructure.USART_WordLength = USART_WordLength_8b; USART_InitStructure.USART_StopBits = USART_StopBits_1; USART_InitStructure.USART_Parity = USART_Parity_No; USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; USART_InitStructure.USART_Mode = USART_Mode_Tx; #if(DEBUG == DEBUG_UART1) USART_Init(USART1, &USART_InitStructure); USART_Cmd(USART1, ENABLE); #elif(DEBUG == DEBUG_UART2) USART_Init(USART2, &USART_InitStructure); USART_Cmd(USART2, ENABLE); #elif(DEBUG == DEBUG_UART3) USART_Init(USART3, &USART_InitStructure); USART_Cmd(USART3, ENABLE); #endif } 2.main函数打印系统时钟、芯片ID int main(void) { NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1); SystemCoreClockUpdate(); Delay_Init(); USART_Printf_Init(115200); printf("Uart Init\r\n"); printf("SystemClk:%d\r\n", SystemCoreClock); printf( "ChipID:%08x\r\n", DBGMCU_GetCHIPID() ); printf("Run printf success\r\n"); while(1) { } } 3.编译烧录   三.测试       烧录后复位,可看到串口打印如下 图4:串口打印       至此,实现串口打印功能。

  • 发表了主题帖: 【全能小网关|CH32V208】--1.开箱与点灯

    本帖最后由 dirty 于 2024-6-8 16:31 编辑       开发板使用沁恒CH32V208WBU6,RAM:64KB,Flash:128KB,基于 32 位 RISC-V 指令集及架构设计,芯片内部集成了ETH-10M(+PHY)以太网和蓝牙,可以说功能还是很丰富的。 一.了解开发板       开发板外设资源丰富,包含主控MCU、电源开关、按键、LED、USB口、网口、蓝牙天线和引脚扇出的排针等。详细布局介绍如下图。开发板做工精良,丝印标注很清晰,很方便拿来上手开发。 图1:CH32V208 EVT外设资源 二.开发环境搭建       沁恒 MCU可用MounRiver Studio IDE开发。WCH-LinkE是用于沁恒 RISCV MCU调试烧录工具。WCH-LinkUtility工具可以同步安装驱动后,可配置芯片。   三.点亮LED 1.首先了解开发板关于板载LED原理,可以查阅到LED1/LED2只是扇出引到了排针上,这里用户可以灵活配置用哪个IO驱动。 图2:LED原理       这里使用PA0脚驱动LED1,将两引脚用杜邦线连接起来即可。连接好LED线和WCH-LinkE硬件如下: 图3:LED及WCH-LinkE连接 2.代码准备 (1)引脚初始化 /********************************************************************* * @fn GPIO_Toggle_INIT * * [url=home.php?mod=space&uid=159083]@brief[/url] Initializes GPIOA.0 * * [url=home.php?mod=space&uid=784970]@return[/url] none */ void GPIO_Toggle_INIT(void) { GPIO_InitTypeDef GPIO_InitStructure = {0}; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); } (2)main函数实现LED1闪烁 int main(void) { u8 i = 0; NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1); SystemCoreClockUpdate(); Delay_Init(); GPIO_Toggle_INIT(); while(1) { Delay_Ms(250); GPIO_WriteBit(GPIOA, GPIO_Pin_0, (i == 0) ? (i = Bit_SET) : (i = Bit_RESET)); } } (3)编译烧录,如下。若烧录不成,用WCH-LinkUtility解保护一下即可。 图4:编译烧录 4.运行测试。烧录后复位或重上电,可看到开发板LED1闪烁。 [localvideo]2704d66937b19d19546253d1de6116de[/localvideo]         至此,对开发板有了初步全局了解,搭建了环境,点亮了LED,整个开发环节搭通,为后续开发奠定良好基础。

  • 2024-06-03
  • 回复了主题帖: 【兆易GD32H759I-EVAL】--8.LVGL移植

    Jacktang 发表于 2024-6-2 09:03 兆易GD32H759I-EVAL开发板.LVGL移植成功,赞一个 也是弄了一段时间,中间有遇到些问题,最终解决移植成功。工程代码稍晚点时候分享出来,可供大家参考学习。

  • 2024-06-02
  • 发表了主题帖: 【Beetle ESP32 C6迷你开发板】--6.HTTP Client

          本篇讲述开发板作为HTTP 客户端,与服务端通讯,获取消息文本。 一.了解HTTP       HTTP,超文本传输协议,是一个简单的请求-响应协议,它通常运行在TCP之上。它指定了客户端可能发送给服务器什么样的消息以及得到什么样的响应。请求和响应消息的头以ASCII形式给出;而消息内容则具有一个类似MIME的格式。   二.代码准备 1.打开HTTP Client工程。 图1:打开HTTP Client工程 2.初始化,添加路由账号密码 图2:初始化,添加路由AP账号密码 3.运行主程序。连接上WiFi后,设置服务端url,Get消息,获取成功则将消息打印出来,获取失败打印失败码。 void loop() { // wait for WiFi connection if ((wifiMulti.run() == WL_CONNECTED)) { USE_SERIAL.print("Wifi connected!\n"); HTTPClient http; USE_SERIAL.print("[HTTP] begin...\n"); // configure traged server and url //http.begin("https://www.howsmyssl.com/a/check", ca); //HTTPS http.begin("http://example.com/index.html"); //HTTP USE_SERIAL.print("[HTTP] GET...\n"); // start connection and send HTTP header int httpCode = http.GET(); // httpCode will be negative on error if (httpCode > 0) { // HTTP header has been send and Server response header has been handled USE_SERIAL.printf("[HTTP] GET... code: %d\n", httpCode); // file found at server if (httpCode == HTTP_CODE_OK) { String payload = http.getString(); USE_SERIAL.println(payload); } } else { USE_SERIAL.printf("[HTTP] GET... failed, error: %s\n", http.errorToString(httpCode).c_str()); } http.end(); } delay(5000); } 4.HTTP  Client码可在HTTPClient.h头文件中看到定义,包括经典的404 图3:HTTP Code 三.测验       编译烧录后,打开串口.可看到初始化连接WIFI成功,客户端GET服务端消息成功会将消息打印,失败会打印出原因。日志如下   图4       至此,成功运行HTTPClient功能。  

  • 回复了主题帖: 【Beetle ESP32 C6迷你开发板】--4.FreeRTOS任务与消息队列

    秦天qintian0303 发表于 2024-5-23 09:38 最近在用的AZURE_RTOS,就不太会注册,总是失败,要不就不运行,要不就只有它运行 看注册有没成功嘛,有没启动,参数等是否合理,异常的解决,资料这些多找找看看

  • 2024-06-01
  • 回复了主题帖: 【兆易GD32H759I-EVAL】--7.SDRAM读写

    wangerxian 发表于 2024-5-23 18:42 SDRAM配置完成了,就等于是单片机自己的RAM了~ 是的,地址映射下

  • 发表了主题帖: 【兆易GD32H759I-EVAL】--9.摄像头屏显

    本帖最后由 dirty 于 2024-6-1 19:56 编辑       本篇讲述摄像头驱动,显示到LCD屏。 一.了解摄像头原理       开发板配备有一颗OV2640摄像头,DVP接口,200W像素。开发板摄像头部分的原理如下。 图1:摄像头原理 引脚功能定义: PIXCLK输出时钟 XCLK外部时钟输入 VSYNC场同步 HSYNC行同步 D[0:7]并口数据(8bit数据位数大小) I2C接口:寄存器驱动配置 模块特点: 标准的SCCB接口,兼容12C接口: RaWRGB,RGB(GRB4:2:2,RGB565/555/444),YUV(4:2:2)和YCbCr(4:2:2)输出格式。 支持UXGA、SXGA、VGA、QVGA、QQVGA、CIF、QCIF等支持自动曝光控制、自动增益控制、自动白平衡,自动消除灯光条纹、自动黑电平校准.图像质量控制包括色饱和度、色相、伽玛、锐度ANTI BLOOM等设置。 支持图像缩放、平移和窗口设置。 支持图像压缩,即可输出JPEG图像数据。   二.软件代码       实现方式:使用DCI接口采集数据,使用前面用过的TLI接口显示采集图像。这里在前面LVGL工程基础上实现按键切换到摄像头图像显示到LCD屏。 1.添加sdk里关于摄像头驱动的源文件,如下图所示 图2:摄像头驱动文件添加 2.摄像头驱动初始化。这里初始化了摄像头sccb引脚,以及dci配置,包含I2C初始化,DMA初始化。之后通过写参数配置摄像头。最后定义了摄像头输出图像大小320*240。显示屏是480*272,因此后面涉及到转换。 /*! \brief DCI camera initialization \param[in] none \param[out] none \retval 0x00 or 0xFF */ uint8_t dci_ov2640_init(void) { uint8_t i; sccb_config(); dci_config(); ckout0_init(); delay_1ms(100); /* OV2640 reset */ if(dci_byte_write(0xFF, 0x01) != 0) { return 0xFF; } if(dci_byte_write(0x12, 0x80) != 0) { return 0xFF; } delay_1ms(10); for(i = 0; i < sizeof(ov2640_svga_init_reg_tbl) / 2; i++) { if(0 != dci_byte_write(ov2640_svga_init_reg_tbl[i][0], ov2640_svga_init_reg_tbl[i][1])) { return 0xFF; } } delay_1ms(100); for(i = 0; i < (sizeof(ov2640_rgb565_reg_tbl) / 2); i++) { if(0 != dci_byte_write(ov2640_rgb565_reg_tbl[i][0], ov2640_rgb565_reg_tbl[i][1])) { return 0xFF; } } delay_1ms(100); ov2640_outsize_set(320, 240); return 0; } 3.读出摄像头信息,包含厂商,版本和pid. /*! \brief read the ov2640 manufacturer identifier \param[in] ov2640id: pointer to the ov2640 manufacturer struct \param[out] none \retval 0x00 or 0xFF */ uint8_t dci_ov2640_id_read(ov2640_id_struct *ov2640id) { uint8_t temp; dci_byte_write(0xFF, 0x01); if(dci_byte_read(OV2640_MIDH, &temp) != 0) { return 0xFF; } ov2640id->manufacturer_id1 = temp; if(dci_byte_read(OV2640_MIDL, &temp) != 0) { return 0xFF; } ov2640id->manufacturer_id2 = temp; if(dci_byte_read(OV2640_VER, &temp) != 0) { return 0xFF; } ov2640id->version = temp; if(dci_byte_read(OV2640_PID, &temp) != 0) { return 0xFF; } ov2640id->pid = temp; return 0x00; } 4.配置DMA和 DCI,这里LVGL显示在LAYER0,摄像头画面显示在LAYER1,故这里先将摄像头禁能。 nvic_configuration(); /* DMA interrupt and channel enable */ dma_interrupt_enable(DMA1, DMA_CH7, DMA_CHXCTL_FTFIE); dma_channel_enable(DMA1, DMA_CH7); /* DCI enable */ dci_enable(); dci_capture_enable(); delay_1ms(3000); dma_interrupt_disable(DMA1, DMA_CH7, DMA_CHXCTL_FTFIE); dma_channel_disable(DMA1, DMA_CH7); dci_capture_disable(); 5.俘获照片数据保存到SDRAM,这里不停止视频流,如果拍照可以把屏蔽的打开 /*! \brief save image to sdram \param[in] none \param[out] none \retval none */ void image_save(void) { uint32_t i = 0; // dma_interrupt_disable(DMA1, DMA_CH7, DMA_CHXCTL_FTFIE); // dma_channel_disable(DMA1, DMA_CH7); // dci_capture_disable(); /* save image to sdram */ for(i = 0; i < 32640; i++) { *(uint32_t *)(0xC0800000 + 4 * i) = *(uint32_t *)(0xC1000000 + 4 * i); } } 6.摄像头图片显示,这里切换图层。 /*! \brief display image to lcd \param[in] display_image_addr: image display address \param[out] none \retval none */ void image_display(uint32_t display_image_addr) { tli_layer_parameter_struct tli_layer1_initstruct; /* input address configuration */ tli_layer1_initstruct.layer_frame_bufaddr = (uint32_t)display_image_addr; /* layer1 windowing configuration */ tli_layer1_initstruct.layer_window_leftpos = 162; tli_layer1_initstruct.layer_window_rightpos = (160 + 240 - 1); tli_layer1_initstruct.layer_window_toppos = 12; tli_layer1_initstruct.layer_window_bottompos = (12 + 272 - 1); /* pixel format configuration */ tli_layer1_initstruct.layer_ppf = LAYER_PPF_RGB565; /* alpha constant configuration : the constant alpha for layer 1 is decreased to see the layer 0 in the intersection zone*/ tli_layer1_initstruct.layer_sa = 255; /* default color configuration (configure A,R,G,B component values) */ tli_layer1_initstruct.layer_default_blue = 0xFF; tli_layer1_initstruct.layer_default_green = 0xFF; tli_layer1_initstruct.layer_default_red = 0xFF; tli_layer1_initstruct.layer_default_alpha = 0; /* blending factors */ tli_layer1_initstruct.layer_acf1 = LAYER_ACF1_PASA; tli_layer1_initstruct.layer_acf2 = LAYER_ACF1_PASA; // /* configure input address : frame buffer is located at memory */ // tli_layer1_initstruct.layer_frame_bufaddr = (uint32_t)0xC1000000; tli_layer1_initstruct.layer_frame_line_length = ((240 * 2) + 3); tli_layer1_initstruct.layer_frame_buf_stride_offset = (240 * 2); tli_layer1_initstruct.layer_frame_total_line_number = 272; tli_layer_init(LAYER1, &tli_layer1_initstruct); tli_dither_config(TLI_DITHER_ENABLE); tli_layer_init(LAYER1, &tli_layer1_initstruct); /* disenable layer0 */ tli_layer_disable(LAYER0); /* enable layer1 */ tli_layer_enable(LAYER1); /* reload configuration */ tli_reload_config(TLI_REQUEST_RELOAD_EN); /* enable TLI */ tli_enable(); } 7.在Project\gd32h7xx_it.c添加中断处理函数。EXTI10_15_IRQHandler是Tamper按键中断,保存照片数据和使能DMA1和打开摄像头DCI俘获.DMA1_Channel7_IRQHandler处理数据将320*240摄像头数据转为240*272屏显数据。 /*! \brief this function handles external lines 10 to 15 interrupt request \param[in] none \param[out] none \retval none */ void EXTI10_15_IRQHandler(void) { if(RESET != exti_interrupt_flag_get(EXTI_13)) { printf("Key Pressed\r\n"); exti_interrupt_flag_clear(EXTI_13); dma_interrupt_enable(DMA1, DMA_CH7, DMA_CHXCTL_FTFIE); dma_channel_enable(DMA1, DMA_CH7); dci_capture_enable(); image_save(); image_display((uint32_t)image_background1); delay_1ms(500); image_display((uint32_t)0XC1000000); //lv_demo_music(); } } /*! \brief this function handles DMA1_Channel7_IRQ interrupt request \param[in] none \param[out] none \retval none */ void DMA1_Channel7_IRQHandler(void) { /* 320*240 size image convert to 240*272 size image */ if(dma_interrupt_flag_get(DMA1, DMA_CH7, DMA_INT_FLAG_FTF)) { //printf("\r\nprocess image\r\n"); int i = 0, x = 0, y = 0; dma_channel_disable(DMA1, DMA_CH7); for(x = 0; x < 320; x++) { for(y = 0; y < 240; y++) { if(x < 272) { *(uint16_t *)(0xC1000000 + 2 * i) = *(uint16_t *)(0xC0000000 + 2 * ((320 * y) + x)); i++; } } } dma_interrupt_flag_clear(DMA1, DMA_CH7, DMA_INT_FLAG_FTF); dma_channel_enable(DMA1, DMA_CH7); } } 8.main函数 int main(void) { ov2640_id_struct ov2640id; BaseType_t ret; /* enable the CPU cache */ cache_enable(); /* initialize the LEDs */ test_status_led_init(); /* configure systick */ systick_config(); /* flash the LEDs for 2 time */ led_flash(2); /* configure USART0 */ usart_config(); /* configure TAMPER key */ gd_eval_key_init(KEY_TAMPER, KEY_MODE_EXTI); /* output a message on hyperterminal using printf function */ printf("\r\n ================= LVGL ================= \r\n"); /**********************LCD Application**********************/ /* config the EXMC access mode */ exmc_synchronous_dynamic_ram_init(EXMC_SDRAM_DEVICE0); printf("\r\nSDRAM Init \r\n"); /* camera initialization */ dci_ov2640_init(); dci_ov2640_id_read(&ov2640id); printf("\r\nMF1:0x%02x,MF2:0x%02x,version:0x%02x,pid:0x%02x\r\n",ov2640id.manufacturer_id1,\ ov2640id.manufacturer_id2,ov2640id.version,ov2640id.pid); nvic_configuration(); /* DMA interrupt and channel enable */ dma_interrupt_enable(DMA1, DMA_CH7, DMA_CHXCTL_FTFIE); dma_channel_enable(DMA1, DMA_CH7); /* DCI enable */ dci_enable(); dci_capture_enable(); delay_1ms(100); dma_interrupt_disable(DMA1, DMA_CH7, DMA_CHXCTL_FTFIE); dma_channel_disable(DMA1, DMA_CH7); dci_capture_disable(); timer_config(); lcd_config(); lcd_init(); /* configure the GPIO of SPI touch panel */ touch_panel_gpio_configure(); #if (LVGL_DMA) delay_1ms(50); dma_config(); delay_1ms(1000); #endif lv_init(); lv_port_disp_init(); lv_port_indev_init(); // lv_demo_music(); lv_demo_widgets(); while(1) { delay_1ms(5); lv_task_handler(); } } 三.测验       编译烧录后,可以看到LCD显示widgets,Tamper按键按下后,屏创建了一个240*272的摄像头视频显示.效果见视频。成像效果感觉一般,优化可以调整ov2640_svga_init_reg_tbl寄存器参数。       至此,实现摄像头图像屏显。 [localvideo]79440527048ba45cc9a2a01d8a9f8443[/localvideo]  

  • 发表了主题帖: 【兆易GD32H759I-EVAL】--8.LVGL移植

    本帖最后由 dirty 于 2024-6-1 10:15 编辑       本篇讲述兆易GD32H759I-EVAL开发板LVGL移植,并通过按键切换界面显示。 一.了解LVGL并做准备 1.了解LVGL       LVGL(轻量级和通用图形库)是一个免费和开源的图形库,它提供了创建嵌入式GUI所需的一切,具有易于使用的图形元素,美丽的视觉效果和低内存占用。 主要特性 丰富且强大的模块化图形组件:按钮 (buttons)、图表 (charts)、列表 (lists)、滑动条 (sliders)、图片 (images) 等 高级的图形引擎:动画、抗锯齿、透明度、平滑滚动、图层混合等效果 支持多种输入设备:触摸屏、 键盘、编码器、按键等 支持多显示设备 不依赖特定的硬件平台,可以在任何显示屏上运行 配置可裁剪(最低资源占用:64 kB Flash,16 kB RAM) 基于UTF-8的多语种支持,例如中文、日文、韩文、阿拉伯文等 可以通过类CSS的方式来设计、布局图形界面(例如:Flexbox、Grid) 支持操作系统、外置内存、以及硬件加速(LVGL已内建支持STM32 DMA2D、NXP PXP和VGLite) 即便仅有单缓冲区(frame buffer)的情况下,也可保证渲染如丝般顺滑 全部由C编写完成,并支持C++调用 支持Micropython编程,参见:LVGL API in Micropython 支持模拟器仿真,可以无硬件依托进行开发 丰富详实的例程 详尽的文档以及API参考手册,可线上查阅或可下载为PDF格式 在 MIT 许可下免费和开源 配置要求:       基本上,每个能够驱动显示器的现代控制器都适合运行 LVGL。 最低要求是: 16、32 或 64 位微控制器或处理器 建议使用 >16 MHz 时钟速度 闪存/ROM: > 64 kB 用于非常重要的组件 (> 建议使用 180 kB) RAM: 静态 RAM 使用量:~2 kB,取决于使用的功能和对象类型 堆: > 2kB (> 建议使用 8 kB) 动态数据(堆): > 2 KB (> 如果使用多个对象,建议使用 16 kB). 在 lv_conf.h 文件中配置 LV_MEM_SIZE 生效。 显示缓冲区:> “水平分辨率”像素(推荐 >10 × 10ד 水平分辨率”) MCU或外部显示控制器中的一个帧缓冲区 C99 或更新的编译器 2.LVGL资源获取。这里使用LVGL v8.3,获取地址https://github.com/lvgl/lvgl/tree/release/v8.3   二.LVGL移植与应用 1.LVGL使用到的文件资源如下: 图1:LVGL移植用到的文件 2.修改源文件       这里修改lv_conf_template.h为lv_conf.h,lv_port_disp_template.c/h为lv_port_disp.c/h,lv_port_indev_template.c/h为lv_port_indev.c/h,并使能#if 1  /**/ #endif。屏分辨率480*272,宏定义之。 #define LV_HOR_RES_MAX 480 #define LV_VER_RES_MAX 272   3.工程添加源文件 图2:LVGL文件工程添加 4.工程添加头文件路径 图3:LVGL头文件路径添加 5.添加定时器为 LVGL 提供心跳 void TIMER1_IRQHandler(void) { // static uint16_t count=0; if(SET == timer_interrupt_flag_get(TIMER1, TIMER_INT_FLAG_UP)) { /* clear channel 0 interrupt bit */ timer_interrupt_flag_clear(TIMER1, TIMER_INT_FLAG_UP); // count++; // if(count>=10000) // { // count=0; // printf("Run lv_tick_inc\r\n"); // } lv_tick_inc(1); } } void timer_config(void) { timer_parameter_struct timer_initpara; rcu_periph_clock_enable(RCU_TIMER1); timer_deinit(TIMER1); /* TIMER0 configuration */ timer_initpara.prescaler = 2999;//299; timer_initpara.alignedmode = TIMER_COUNTER_EDGE; timer_initpara.counterdirection = TIMER_COUNTER_UP; timer_initpara.period = 99; timer_initpara.clockdivision = TIMER_CKDIV_DIV1; timer_initpara.repetitioncounter = 0; timer_init(TIMER1, &timer_initpara); timer_enable(TIMER1); timer_interrupt_flag_clear(TIMER1, TIMER_INT_FLAG_UP); timer_interrupt_enable(TIMER1, TIMER_INT_UP); nvic_irq_enable(TIMER1_IRQn, 1, 1); } 6.DAM初始化。主要可用来将数据发送到TLIframe buff中。这里个人使用了用于不用DMA两种方式,后面会有所体现。 void dma_config(void) { dma_multi_data_parameter_struct dma_init_parameter; /* peripheral clock enable */ rcu_periph_clock_enable(RCU_DMA1); /* DMA peripheral configure */ dma_deinit(DMA1,DMA_CH0); dma_init_parameter.periph_width = DMA_PERIPH_WIDTH_16BIT; dma_init_parameter.periph_inc = DMA_PERIPH_INCREASE_ENABLE; dma_init_parameter.memory_width = DMA_MEMORY_WIDTH_16BIT; dma_init_parameter.memory_inc = DMA_MEMORY_INCREASE_ENABLE; dma_init_parameter.memory_burst_width = DMA_MEMORY_BURST_4_BEAT; dma_init_parameter.periph_burst_width = DMA_PERIPH_BURST_4_BEAT; dma_init_parameter.critical_value = DMA_FIFO_4_WORD; dma_init_parameter.circular_mode = DMA_CIRCULAR_MODE_DISABLE; dma_init_parameter.direction = DMA_MEMORY_TO_MEMORY; dma_init_parameter.priority = DMA_PRIORITY_ULTRA_HIGH; dma_multi_data_mode_init(DMA1,DMA_CH0,&dma_init_parameter); nvic_irq_enable(DMA1_Channel0_IRQn, 0, 0); } 7.LCD初始化       LCD初始化,图层使能加载。 void lcd_config(void) { /* configure the GPIO of TLI */ tli_gpio_config(); /* configure TLI peripheral */ tli_config(); } void lcd_init(void) { tli_layer_enable(LAYER0); tli_layer_disable(LAYER1); tli_reload_config(TLI_RL_FBR); /* enable TLI */ tli_enable(); }       图层参数配置如下 图4:图层参数配置 8.LCD显示驱动移植       LVGL 的显示驱动文件主要修改 lv_prot_disp.h 和 lv_prot_disp.c 文件.开发板外挂一颗32MB SDRAM,代码中将 TLI中 frame buffer 定义到地址SDRAM_DEVICE0_ADDR 即0x0c000000 lv_port_disp_init修改如下,这里选用第一个。 图5:lv_port_disp_init修改       disp_flush实现如下,这里通过宏LVGL_DMA选择用于不用DMA传输数据。 /*Flush the content of the internal buffer the specific area on the display *You can use DMA or any hardware acceleration to do this operation in the background but *'lv_disp_flush_ready()' has to be called when finished.*/ static void disp_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p) { if(disp_flush_enabled) { /*The most simple case (but also the slowest) to put all pixels to the screen one-by-one*/ #if (!LVGL_DMA) int32_t x; int32_t y; for(y = area->y1; y <= area->y2; y++) { for(x = area->x1; x <= area->x2; x++) { /*Put a pixel to the display. For example:*/ /*put_px(x, y, *color_p)*/ memcpy((uint32_t)&my_fb[y * LV_HOR_RES_MAX +x],(uint32_t)color_p,2);//1 // memcpy((&my_fb[y * LV_HOR_RES_MAX +x],color_p,2);//1 color_p++; } } #else if(area->x2 < 0) { return; } if(area->y2 < 0) { return; } if(area->x1 > LV_HOR_RES_MAX - 1) { return; } if(area->y1 > LV_VER_RES_MAX - 1) { return; } /*Truncate the area to the screen*/ int32_t act_x1 = area->x1 < 0 ? 0 : area->x1; int32_t act_y1 = area->y1 < 0 ? 0 : area->y1; int32_t act_x2 = area->x2 > LV_HOR_RES_MAX - 1 ? LV_HOR_RES_MAX - 1 : area->x2; int32_t act_y2 = area->y2 > LV_VER_RES_MAX - 1 ? LV_VER_RES_MAX - 1 : area->y2; x1_flush = act_x1; y1_flush = act_y1; x2_flush = act_x2; y2_fill = act_y2; y_fill_act = act_y1; buf_to_flush = color_p; dma_transfer((uint32_t)buf_to_flush, (uint32_t)&my_fb[y_fill_act * LV_HOR_RES_MAX +x1_flush],(x2_flush - x1_flush + 1)); #endif } /*IMPORTANT!!! *Inform the graphics library that you are ready with the flushing*/ lv_disp_flush_ready(disp_drv); } 9.DMA传输数据如下 图6:DMA传输数据 10.lv_conf.h配置       这里可将LV_MEM_SIZE适当改大,根据需要使能字体大小宏,根据需要使能组件例程,这里是能了LV_USE_DEMO_WIDGETS和LV_USE_DEMO_MUSIC。 11.main函数 int main(void) { BaseType_t ret; /* enable the CPU cache */ cache_enable(); /* initialize the LEDs */ test_status_led_init(); /* configure systick */ systick_config(); /* flash the LEDs for 2 time */ led_flash(2); /* configure USART0 */ usart_config(); /* configure TAMPER key */ gd_eval_key_init(KEY_TAMPER, KEY_MODE_EXTI); /* output a message on hyperterminal using printf function */ printf("\r\n ================= LVGL ================= \r\n"); /**********************LCD Application**********************/ /* config the EXMC access mode */ exmc_synchronous_dynamic_ram_init(EXMC_SDRAM_DEVICE0); printf("\r\nSDRAM Init \r\n"); timer_config(); lcd_config(); lcd_init(); /* configure the GPIO of SPI touch panel */ touch_panel_gpio_configure(); #if (LVGL_DMA) delay_1ms(50); dma_config(); delay_1ms(1000); #endif lv_init(); lv_port_disp_init(); lv_port_indev_init(); // lv_demo_music(); lv_demo_widgets(); while(1) { delay_1ms(5); lv_task_handler(); } } 12.按键切换显示。默认显示的经典画面lv_demo_widgets,按键切换显示音乐界面。 /*! \brief this function handles external lines 10 to 15 interrupt request \param[in] none \param[out] none \retval none */ void EXTI10_15_IRQHandler(void) { static uint8_t gui_show=0; if(RESET != exti_interrupt_flag_get(EXTI_13)) { printf("Key Pressed\r\n"); exti_interrupt_flag_clear(EXTI_13); lv_demo_music(); } } 三.测验       编译烧录后,LCD显示widgets界面见下图。按按键后,切换播放音乐动画界面,见下视频。 图7:LCD显示               至此,实现兆易GD32H759I-EVAL开发板.LVGL移植。         [localvideo]1fdd4666782d9a82cbf09f38441200c5[/localvideo]  

最近访客

< 1/4 >

统计信息

已有1037人来访过

  • 芯积分:425
  • 好友:23
  • 主题:97
  • 回复:280

留言

你需要登录后才可以留言 登录 | 注册


现在还没有留言