tinnu

  • 2020-10-08
  • 发表了主题帖: 【GD32450I-EVAL】简易示波演示

    淘了一块便宜的函数发生器回来,通过电阻分压后测算电压。通过液晶屏显示。 本来弄了个前置放大电路板子,可我模电水平实在不行,弄来弄去弄不好,只能直接分压了。 此时使用的是PA4引脚,ADC0通道4   1-使用计时器每100ms进行一次连续转换 timer_initpara.prescaler = 9999; timer_initpara.period = 999; timer_init(TIMER2,&timer_initpara); timer_interrupt_enable(TIMER2, TIMER_INT_UP); timer_interrupt_flag_clear(TIMER2, TIMER_INT_FLAG_UP); // timer_interrupt_flag_clear(TIMER2, TIMER_INT_FLAG_CH1); // dbg_periph_enable(DBG_TIMER2_HOLD); nvic_irq_enable(TIMER2_IRQn, 1U, 0U); /* enable TIMER2 */ timer_enable(TIMER2); 2-定时器中断服务函数 void TIMER2_IRQHandler(void) { if(timer_interrupt_flag_get(TIMER2, TIMER_INT_FLAG_UP)==SET) { adc_special_function_config(ADC0, ADC_CONTINUOUS_MODE, ENABLE); adc_software_trigger_enable(ADC0, ADC_REGULAR_CHANNEL); timer_interrupt_flag_clear(TIMER2, TIMER_INT_FLAG_UP); } } 3-ADC每次测试完480个点(刚好屏幕跨度像素)停止转换 void ADC_IRQHandler(void) { uint16_t i=0; char t_char[30] = ""; static int s_counter=0; static uint16_t s_recorder[480]; uint16_t *t_displaySize; if(adc_interrupt_flag_get(ADC0, ADC_INT_FLAG_EOC)==SET) { s_recorder[s_counter] = adc_regular_data_read(ADC0); adc_counter++; s_counter++; if(s_counter>=480){ s_counter = 0; adc_special_function_config(ADC0, ADC_CONTINUOUS_MODE, DISABLE); t_displaySize = EXT_BUFFER0_ADDR; memset((void *)EXT_BUFFER0_ADDR, 0, LCD_WIDTH * LCD_HEIGHT * LCD_FB_BYTE_PER_PIXEL * 2); for (i = 0; i < 480; i++) t_displaySize[s_recorder*272/0xFFF*480 + i] = 0xAAAA; } adc_interrupt_flag_clear(ADC0, ADC_INT_FLAG_EOC); } } 4-其中,停止转换的时候将点写入到显示缓存 for (i = 0; i < 480; i++) t_displaySize[s_recorder*272/0xFFF*480 + i] = 0xAAAA;     调整频率时的显示效果:

  • 发表了主题帖: 【GD32450I-EVAL】定时器 测试ADC速度

    本帖最后由 tinnu 于 2020-10-8 13:57 编辑 测试ADC速度需要定时器的加持,所以必须先将定时器功能摸清楚。   (一)定时器时钟 定时器的时钟是经过:AHB——APB——定时器 我们使用的是TIMER1 有意思的是CK_APB1总线最高只能到50M,而从CK_APB1出来的时钟却可以到达200M,这是中间有一个倍频的功能。   1-CK_APB1的时钟 关于这个时钟可以看:UART USART使用的是PCLK1时钟,目前CK_APB1的时钟是最高的50M   2-TIMER时钟 TIMER1使用的是CK_APB1倍频后的时钟。关于倍频系数可以参考 RCU_CFG1寄存器: 在复位状态下为0,即2倍频。这里2倍频是100M。   (二)设置 例程的配合代码: timer_parameter_struct timer_initpara; /* TIMER1 configuration */ timer_initpara.prescaler = 9999; timer_initpara.alignedmode = TIMER_COUNTER_EDGE; timer_initpara.counterdirection = TIMER_COUNTER_UP; timer_initpara.period = 9999; timer_initpara.clockdivision = TIMER_CKDIV_DIV1; timer_initpara.repetitioncounter = 0; timer_init(TIMER1,&timer_initpara); ") rgba(220, 220, 220, 0.5); top: -15px; left: 0px; display: block;"> 有一个预分频器和比较器。在100M的时钟下,一次更新的时间为(9999+1)*(9999+1)=100M,刚好1S。   使能中断: timer_interrupt_enable(TIMER1, TIMER_INT_UP); nvic_irq_enable(TIMER1_IRQn, 2U, 0U); ") rgba(220, 220, 220, 0.5); top: -15px; left: 0px; display: block;">   使能定时器: /* enable TIMER1 */ timer_enable(TIMER1); ") rgba(220, 220, 220, 0.5); top: -15px; left: 0px; display: block;">   实际上运行之后会发现无法触发外部中断,这是因为TIMER1的时钟要额外使能: rcu_periph_clock_enable(RCU_TIMER1); ") rgba(220, 220, 220, 0.5); top: -15px; left: 0px; display: block;">   (三)中断服务函数与测试ADC速度的代码 ADC这里需要失能中断,在主循环里面操作,因为在使能了ADC的连续读写功能后,ADC的转换速度是惊人的,中断的速度根本跟不上读写的速度,甚至主循环里面多一句代码都会严重影响测试的准确度。这也是为何ADC建议采用DMA方式读写的原因,但是DMA不方便测试速度,因此还是采用轮询的方式。 1-使能ADC连续转换功能 // ADC contineous function enable adc_special_function_config(ADC0, ADC_CONTINUOUS_MODE, ENABLE); ") rgba(220, 220, 220, 0.5); top: -15px; left: 0px; display: block;"> 2-在初始化的时候使能一次 adc_software_trigger_enable(ADC0, ADC_REGULAR_CHANNEL); ") rgba(220, 220, 220, 0.5); top: -15px; left: 0px; display: block;"> 3-主循环里面不能进行多余的操作 if (adc_interrupt_flag_get(ADC0, ADC_INT_FLAG_EOC) == SET) { adc_regular_data_read(ADC0); adc_counter++; adc_interrupt_flag_clear(ADC0, ADC_INT_FLAG_EOC); } 4-定时器的中断服务函数 void TIMER1_IRQHandler(void) { char t_char[30] = ""; gd_eval_led_toggle(LED1); sprintf(t_char, "ADC for one second:%d\r\n", adc_counter); PRINTF_UART0(t_char); adc_counter = 0; timer_interrupt_flag_clear(TIMER1, TIMER_INT_FLAG_UP); }     (四)效果 当采样速度为ADC_SAMPLETIME_112 ADC for one second:403225 ADC for one second:402292 ADC for one second:402292 ADC for one second:402291 ADC for one second:402291 ADC for one second:402291 ADC for one second:402292 ADC for one second:402292 一秒400K的速度。 当采样速度为ADC_SAMPLETIME_15 ADC for one second:1010101 ADC for one second:1007662 ADC for one second:1007662 ADC for one second:1007661 ADC for one second:1007661 已经有1M了。 但必须注意到,采用速度的增加并没有跟上转换速度增加的比例,这可能还是存在轮询耗时的问题。毕竟1M的转换速度下,实际上每次只有200个机器时钟。   (五)ADC执行也放进中断 之后尝试了把ADC的执行操作也放入ADC中断,这里设置定时器中断的抢断优先级更高一点: nvic_irq_enable(TIMER1_IRQn, 1U, 0U); 当采样速度为ADC_SAMPLETIME_112: ADC for one second:403225 ADC for one second:402292 ADC for one second:402292 竟然和轮询的时候一模一样,看来在400k低速情况下中断是不会成为转换的瓶颈 当采样速度为ADC_SAMPLETIME_15 ADC for one second:925925 ADC for one second:923779 ADC for one second:923778 这时候就明显小于轮询了

  • 发表了主题帖: 【GD32450I-EVAL】ADC:软件触发+中断+单次转换模式

    (一)ADC性能 GD32F450拥有3个ADC,每个ADC的采样速度最高可以达到2.6Mpsp,这还是在12位最高精度的条件下,如果降低精度还能更快。   (二)ADC概念 GD32的各种设计与国外大厂的很像:   1-注入组和规则组 规则组为常规转换,注入组类似于中断,中间插入一次计划外的转换 2-通道 有3个ADC,每个ADC有19路通道,每个ADC的每个通道都映射到固定的引脚,这可以在数据手册中查询到。 3-转换序列 相当于一个工作队列,序列第一位的转化完,第二位的接着,全部转化完后可以重新再转化一轮 规则组可以控制多达16个序列 注入组少一点,只能支持4个序列   (三)ADC与IO映射关系 每个ADC的每个通道都映射到固定的引脚 比如本次使用的PC3引脚 就是与ADC012三个ADC的IN13通道映射起来的,只要配置了PC3为模拟功能(AFIO),ADC任意一个使能了13通道就可以获取该引脚的模拟量值。 配置代码: // config the GPIO as analog mode, for ADC gpio_mode_set(GPIOC, GPIO_MODE_ANALOG, GPIO_PUPD_NONE, GPIO_PIN_3);   (四)ADC寄存器 ADC的寄存器看上去不少,但实际上只使用规则组进行单个通道转化的时候绝大部分都是用不到的。 看门狗、过采样、注入、摘要这些都不需要管。 因为转换的只有一个通道,就不需要涉及多个通道轮流转换的问题(即转换序列),因此同步相关配置也暂时无用。   所以需要配置的寄存器只有控制寄存器01、采样时间寄存器01、规则序列寄存器012   (五)ADC配置流程 如果采用最简单的单次转化的工作方式,参考手册给出了配置流程: 看着有8步,实际上: 第1步是设置ADC工作方式,在单次转化中,这些相关位都是保持为0,即复位值,如果是初始化配置根本不需要理睬。 第4步是配置硬件触发方式 第5-8步都不是初始化的工作 即实际最简单初始化只需要做第2、3步     (六)初始化 1-配置RSQ: 调用函数,设置该ADC有多少个序列,这里只有一个通道需要转换,只有一个序列: adc_channel_length_config(ADC0, ADC_REGULAR_CHANNEL, 1); 操作 ADC_RSQ0 的 RL位    规则通道序列长度    规则通道转换序列中的总的通道数目为 RL[3:0]+1。 操作 ADC_ISQ 的 TL 位    注入通道组长度    注入组总的通道数目为 IL[1:0]+1。 参数功能: 2-规则组或者注入组 3-多少个序列   2-配置SAMPT 设置具体哪个通道对应哪个序列 adc_regular_channel_config(ADC0, 0, ADC_CHANNEL_13, ADC_SAMPLETIME_15); 操作寄存器的过程比较复杂,同时操作了 ADC_RSQ2 寄存器 和操作 ADC_SAMPT1 寄存器    设置通道采样时间 函数参数功能: /*! \brief configure ADC regular channel \param[in] adc_periph: ADCx,x=0,1,2 \param[in] rank: the regular group sequencer rank,this parameter must be between 0 to 15 \param[in] adc_channel: the selected ADC channel only one parameter can be selected which is shown as below: \arg ADC_CHANNEL_x(x=0..18): ADC Channelx \param[in] sample_time: the sample time value only one parameter can be selected which is shown as below: \arg ADC_SAMPLETIME_3: 3 cycles \arg ADC_SAMPLETIME_15: 15 cycles \arg ADC_SAMPLETIME_28: 28 cycles \arg ADC_SAMPLETIME_56: 56 cycles \arg ADC_SAMPLETIME_84: 84 cycles \arg ADC_SAMPLETIME_112: 112 cycles \arg ADC_SAMPLETIME_144: 144 cycles \arg ADC_SAMPLETIME_480: 480 cycles \param[out] none \retval none */ 2-第三个参数配置的通道,在规则组哪个序列 3-哪个通道 4-转换多少个时钟   (七)使能中断功能 ADC的所有中断都在同一个中断向量里: 使能:   //ADC INT adc_interrupt_enable(ADC0, ADC_INT_EOC); nvic_irq_enable(ADC_IRQn, 2U, 0U); 服务函数: void ADC_IRQHandler(void) { uint16_t i=0; char t_char[30] = ""; if(adc_interrupt_flag_get(ADC0, ADC_INT_FLAG_EOC)==SET) { i = adc_regular_data_read(ADC0); sprintf(t_char, "adc:%x\t%f\r\n", i, (float)i/0xFFF*3.3); PRINTF_UART0(t_char); adc_interrupt_flag_clear(ADC0, ADC_INT_FLAG_EOC); } }   (八)软件启动转换 前面第五步提到,软件启动是置位 SWRCST ,这个位会硬件复位。对应库函数: adc_software_trigger_enable(ADC0, ADC_REGULAR_CHANNEL);   在主循环中调用: 1-使能ADC并校准 // enable ADC interface adc_enable(ADC0); // wait for ADC stability delay_ms(1); // ADC calibration and reset calibration adc_calibration_enable(ADC0); 2-主循环内 while(1){ delay_ms(4); if(t_ledshark >20){ t_ledshark = 0; gd_eval_led_toggle(LED1); adc_software_trigger_enable(ADC0, ADC_REGULAR_CHANNEL); } t_ledshark++; }   (九)效果 更清晰的视频等审核之后再传

  • 2020-10-06
  • 发表了主题帖: 【GD32450I-EVAL】TLI时钟与RGB接口时序

    本帖最后由 tinnu 于 2020-10-6 21:44 编辑 (一)查看帧率 通过电气信息查看目前RGB屏幕刷新帧率最简单的方法是查看VS引脚的脉冲,因为VS引脚一旦产生一个脉冲即意味着一帧的结束 以目前的配置,1s有53帧,还没达到60帧。   (二)时钟源 红色的四个框都是寄存器,可以配置的,其中后面三个是RCU_PLLSAI(本寄存器)中的位,第一个PLLPSC需要在其他寄存器里面配置。 下划线的五个是五条时钟线: 图片标注都省略了前缀PLLSAI   这时候有需要查看PLLSRC的时钟是从哪里来的?   相关位在RCU_PLL寄存器:   RCU_PLL寄存器本应该是PLL这个锁相环自己用的,但这个选择功能属于PLL_I2S和PLL_SAI,都放到这里来公用了。   (三)修改时钟 而系统要达到200M的速度,必须要PLL为系统提供时钟,所以这里的初始化必然是在systemInit里面进行的   初始化使用的函数是: system_clock_200m_25m_hxtal(void) 里面注释写明了: PSC=25,外部晶振也是25M,也就是说VCOSRC只有1M! 回到现在TLI的配置: rcu_pllsai_config(192, 2, 3) 也就是给了VCO 192M的频率,出来首先来了一波属于R的3分频 接着再来一波属于div的8分频: rcu_tli_clock_div_config(RCU_PLLSAIR_DIV8); 只剩下8M!   我们直接来一波2分频试试! 查看VS引脚,时序没有太大问题的样子: 但是屏幕一片空白,之后尝试了下4分频,依然不行。 看来翻倍还是太激进了,尝试修改下倍频: 修改时钟到219,这时候恰好超过60帧,也没有问题,看来超过一点60Hz是可以的,但想要120Hz,这种屏幕做不到。 操作上没感觉到什么不同,因为这个是与USB共用的时钟,如果不考虑USB应用是可以随便调整的。   (四)屏幕刷新闪屏问题 在移植SDRAM之前,屏幕有个闪屏的问题: 这是与刷新策略有关。 相关函数为LittleVGL刷新函数: static void DEMO_FlushDisplay(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p) 里面刷新调用的是 if (color_p == EXT_BUFFER0_ADDR) { tli_layer_disable(LAYER0); tli_layer_enable(LAYER1); tli_reload_config(TLI_FRAME_BLANK_RELOAD_EN); } else if (color_p == EXT_BUFFER1_ADDR) { tli_layer_disable(LAYER1); tli_layer_enable(LAYER0); tli_reload_config(TLI_FRAME_BLANK_RELOAD_EN); }   相关寄存器为重载层配置寄存器 (TLI_RL) FBR 帧消隐重载请求 此位通过软件置位,在重载之后由硬件清除。 0:禁止重载 1:层配置将在帧消隐时被重载进入真正寄存器。 RQR 立即重载请求 此位通过软件置位,在重载之后由硬件清除。 0:禁止重载 1:层配置将在该位置位之后被重载进入真正寄存器。 这里不应该立即刷新,应该等待一帧完成再刷新: if (color_p == EXT_BUFFER0_ADDR) { tli_layer_disable(LAYER0); tli_layer_enable(LAYER1); tli_reload_config(TLI_FRAME_BLANK_RELOAD_EN); } else if (color_p == EXT_BUFFER1_ADDR) { tli_layer_disable(LAYER1); tli_layer_enable(LAYER0); tli_reload_config(TLI_FRAME_BLANK_RELOAD_EN); }

  • 发表了主题帖: 【GD32450I-EVAL】板载SDRAM支持LittleVGL

    此前的程序都是只支持内部RAM,内部RAM对于480*272的屏幕来说终究还是小了点,为了支持整个屏幕,必须开辟480*272*4字节的空间,总共510K,板载芯片尾号IK,只有256K,就算是最大的那个II系列,也只有512K,其余的程序分配2K根本就不够,单论堆栈空间都需要8K。   (一)外部SDRAM驱动 GD系列单片机SDRAM的驱动总线叫做EXMC,这个外设还同时支持各种nand flash nor falsh 地址示意: SDRAM设备支持两个,起始地址分别是0xC0000000和0xD0000000,一个设备竟然有256Mb,一开始觉得这个配置是不是会空余很大的空白地址,后来看了下程序,发现整个256Mb都被使用了才发现。 这颗SDRAM型号是 MT48LC16M16A2P-6AIT ,里面有个16M的字样,一开始理所当然地以为是16M,后来看代码发现不太对,搜了一下才发现是一颗镁光的256M的内存!   驱动代码方面没什么值得注意的,因为配置完EXMC之后,就跟操作内部存储的差距不大了,屏蔽了底层,不需要调用什么函数。 驱动代码可以直接引用例程EXMC里面的SDRAM工程代码。 /* Define mode register content */ /* Burst Length */ #define SDRAM_MODEREG_BURST_LENGTH_1 ((uint16_t)0x0000) #define SDRAM_MODEREG_BURST_LENGTH_2 ((uint16_t)0x0001) #define SDRAM_MODEREG_BURST_LENGTH_4 ((uint16_t)0x0002) #define SDRAM_MODEREG_BURST_LENGTH_8 ((uint16_t)0x0003) /* Burst Type */ #define SDRAM_MODEREG_BURST_TYPE_SEQUENTIAL ((uint16_t)0x0000) #define SDRAM_MODEREG_BURST_TYPE_INTERLEAVED ((uint16_t)0x0008) /* CAS Latency */ #define SDRAM_MODEREG_CAS_LATENCY_2 ((uint16_t)0x0020) #define SDRAM_MODEREG_CAS_LATENCY_3 ((uint16_t)0x0030) /* Write Mode */ #define SDRAM_MODEREG_WRITEBURST_MODE_PROGRAMMED ((uint16_t)0x0000) #define SDRAM_MODEREG_WRITEBURST_MODE_SINGLE ((uint16_t)0x0200) #define SDRAM_MODEREG_OPERATING_MODE_STANDARD ((uint16_t)0x0000) #define SDRAM_TIMEOUT ((uint32_t)0x0000FFFF) /*! \brief sdram peripheral initialize \param[in] sdram_device: specifie the SDRAM device \param[out] none \retval none */ void exmc_synchronous_dynamic_ram_init(uint32_t sdram_device) { exmc_sdram_parameter_struct sdram_init_struct; exmc_sdram_timing_parameter_struct sdram_timing_init_struct; exmc_sdram_command_parameter_struct sdram_command_init_struct; uint32_t command_content = 0, bank_select; uint32_t timeout = SDRAM_TIMEOUT; /* enable EXMC clock*/ rcu_periph_clock_enable(RCU_EXMC); rcu_periph_clock_enable(RCU_GPIOB); rcu_periph_clock_enable(RCU_GPIOC); rcu_periph_clock_enable(RCU_GPIOD); rcu_periph_clock_enable(RCU_GPIOE); rcu_periph_clock_enable(RCU_GPIOF); rcu_periph_clock_enable(RCU_GPIOG); rcu_periph_clock_enable(RCU_GPIOH); /* common GPIO configuration */ /* SDNE0(PC2),SDCKE0(PC5) pin configuration */ gpio_af_set(GPIOC, GPIO_AF_12, GPIO_PIN_2 | GPIO_PIN_5); gpio_mode_set(GPIOC, GPIO_MODE_AF, GPIO_PUPD_PULLUP, GPIO_PIN_2 | GPIO_PIN_5); gpio_output_options_set(GPIOC, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_2 | GPIO_PIN_5); /* D2(PD0),D3(PD1),D13(PD8),D14(PD9),D15(PD10),D0(PD14),D1(PD15) pin configuration */ gpio_af_set(GPIOD, GPIO_AF_12, GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_8 | GPIO_PIN_9 | GPIO_PIN_10 | GPIO_PIN_14 | GPIO_PIN_15); gpio_mode_set(GPIOD, GPIO_MODE_AF, GPIO_PUPD_PULLUP, GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_8 | GPIO_PIN_9 | GPIO_PIN_10 | GPIO_PIN_14 | GPIO_PIN_15); gpio_output_options_set(GPIOD, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_8 | GPIO_PIN_9 | GPIO_PIN_10 | GPIO_PIN_14 | GPIO_PIN_15); /* NBL0(PE0),NBL1(PE1),D4(PE7),D5(PE8),D6(PE9),D7(PE10),D8(PE11),D9(PE12),D10(PE13),D11(PE14),D12(PE15) pin configuration */ gpio_af_set(GPIOE, GPIO_AF_12, GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_7 | GPIO_PIN_8 | GPIO_PIN_9 | GPIO_PIN_10 | GPIO_PIN_11 | GPIO_PIN_12 | GPIO_PIN_13 | GPIO_PIN_14 | GPIO_PIN_15); gpio_mode_set(GPIOE, GPIO_MODE_AF, GPIO_PUPD_PULLUP, GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_7 | GPIO_PIN_8 | GPIO_PIN_9 | GPIO_PIN_10 | GPIO_PIN_11 | GPIO_PIN_12 | GPIO_PIN_13 | GPIO_PIN_14 | GPIO_PIN_15); gpio_output_options_set(GPIOE, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_7 | GPIO_PIN_8 | GPIO_PIN_9 | GPIO_PIN_10 | GPIO_PIN_11 | GPIO_PIN_12 | GPIO_PIN_13 | GPIO_PIN_14 | GPIO_PIN_15); /* A0(PF0),A1(PF1),A2(PF2),A3(PF3),A4(PF4),A5(PF5),NRAS(PF11),A6(PF12),A7(PF13),A8(PF14),A9(PF15) pin configuration */ gpio_af_set(GPIOF, GPIO_AF_12, GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3 | GPIO_PIN_4 | GPIO_PIN_5 | GPIO_PIN_11 | GPIO_PIN_12 | GPIO_PIN_13 | GPIO_PIN_14 | GPIO_PIN_15); gpio_mode_set(GPIOF, GPIO_MODE_AF, GPIO_PUPD_PULLUP, GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3 | GPIO_PIN_4 | GPIO_PIN_5 | GPIO_PIN_11 | GPIO_PIN_12 | GPIO_PIN_13 | GPIO_PIN_14 | GPIO_PIN_15); gpio_output_options_set(GPIOF, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3 | GPIO_PIN_4 | GPIO_PIN_5 | GPIO_PIN_11 | GPIO_PIN_12 | GPIO_PIN_13 | GPIO_PIN_14 | GPIO_PIN_15); /* A10(PG0),A11(PG1),A12(PG2),A14(PG4),A15(PG5),SDCLK(PG8),NCAS(PG15) pin configuration */ gpio_af_set(GPIOG, GPIO_AF_12, GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_4 | GPIO_PIN_5 | GPIO_PIN_8 | GPIO_PIN_15); gpio_mode_set(GPIOG, GPIO_MODE_AF, GPIO_PUPD_PULLUP, GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_4 | GPIO_PIN_5 | GPIO_PIN_8 | GPIO_PIN_15); gpio_output_options_set(GPIOG, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_4 | GPIO_PIN_5 | GPIO_PIN_8 | GPIO_PIN_15); /* SDNWE(PH5) pin configuration */ gpio_af_set(GPIOH, GPIO_AF_12, GPIO_PIN_5); gpio_mode_set(GPIOH, GPIO_MODE_AF, GPIO_PUPD_PULLUP, GPIO_PIN_5); gpio_output_options_set(GPIOH, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_5); /* specify which SDRAM to read and write */ if(EXMC_SDRAM_DEVICE0 == sdram_device) { bank_select = EXMC_SDRAM_DEVICE0_SELECT; } else { bank_select = EXMC_SDRAM_DEVICE1_SELECT; } /* EXMC SDRAM device initialization sequence --------------------------------*/ /* Step 1 : configure SDRAM timing registers --------------------------------*/ /* LMRD: 2 clock cycles */ sdram_timing_init_struct.load_mode_register_delay = 2; /* XSRD: min = 67ns */ sdram_timing_init_struct.exit_selfrefresh_delay = 7; /* RASD: min=42ns , max=120k (ns) */ sdram_timing_init_struct.row_address_select_delay = 5; /* ARFD: min=60ns */ sdram_timing_init_struct.auto_refresh_delay = 6; /* WRD: min=1 Clock cycles +6ns */ sdram_timing_init_struct.write_recovery_delay = 2; /* RPD: min=18ns */ sdram_timing_init_struct.row_precharge_delay = 2; /* RCD: min=18ns */ sdram_timing_init_struct.row_to_column_delay = 2; /* step 2 : configure SDRAM control registers ---------------------------------*/ sdram_init_struct.sdram_device = sdram_device; sdram_init_struct.column_address_width = EXMC_SDRAM_COW_ADDRESS_9; sdram_init_struct.row_address_width = EXMC_SDRAM_ROW_ADDRESS_13; sdram_init_struct.data_width = EXMC_SDRAM_DATABUS_WIDTH_16B; sdram_init_struct.internal_bank_number = EXMC_SDRAM_4_INTER_BANK; sdram_init_struct.cas_latency = EXMC_CAS_LATENCY_3_SDCLK; sdram_init_struct.write_protection = DISABLE; sdram_init_struct.sdclock_config = EXMC_SDCLK_PERIODS_2_HCLK; sdram_init_struct.brust_read_switch = ENABLE; sdram_init_struct.pipeline_read_delay = EXMC_PIPELINE_DELAY_1_HCLK; sdram_init_struct.timing = &sdram_timing_init_struct; /* EXMC SDRAM bank initialization */ exmc_sdram_init(&sdram_init_struct); /* step 3 : configure CKE high command---------------------------------------*/ sdram_command_init_struct.command = EXMC_SDRAM_CLOCK_ENABLE; sdram_command_init_struct.bank_select = bank_select; sdram_command_init_struct.auto_refresh_number = EXMC_SDRAM_AUTO_REFLESH_1_SDCLK; sdram_command_init_struct.mode_register_content = 0; /* wait until the SDRAM controller is ready */ while((exmc_flag_get(sdram_device, EXMC_SDRAM_FLAG_NREADY) != RESET) && (timeout > 0)) { timeout--; } /* send the command */ exmc_sdram_command_config(&sdram_command_init_struct); /* step 4 : insert 10ms delay----------------------------------------------*/ delay_1ms(10); /* step 5 : configure precharge all command----------------------------------*/ sdram_command_init_struct.command = EXMC_SDRAM_PRECHARGE_ALL; sdram_command_init_struct.bank_select = bank_select; sdram_command_init_struct.auto_refresh_number = EXMC_SDRAM_AUTO_REFLESH_1_SDCLK; sdram_command_init_struct.mode_register_content = 0; /* wait until the SDRAM controller is ready */ timeout = SDRAM_TIMEOUT; while((exmc_flag_get(sdram_device, EXMC_SDRAM_FLAG_NREADY) != RESET) && (timeout > 0)) { timeout--; } /* send the command */ exmc_sdram_command_config(&sdram_command_init_struct); /* step 6 : configure Auto-Refresh command-----------------------------------*/ sdram_command_init_struct.command = EXMC_SDRAM_AUTO_REFRESH; sdram_command_init_struct.bank_select = bank_select; sdram_command_init_struct.auto_refresh_number = EXMC_SDRAM_AUTO_REFLESH_8_SDCLK; sdram_command_init_struct.mode_register_content = 0; /* wait until the SDRAM controller is ready */ timeout = SDRAM_TIMEOUT; while((exmc_flag_get(sdram_device, EXMC_SDRAM_FLAG_NREADY) != RESET) && (timeout > 0)) { timeout--; } /* send the command */ exmc_sdram_command_config(&sdram_command_init_struct); /* step 7 : configure load mode register command-----------------------------*/ /* program mode register */ command_content = (uint32_t)SDRAM_MODEREG_BURST_LENGTH_1 | SDRAM_MODEREG_BURST_TYPE_SEQUENTIAL | SDRAM_MODEREG_CAS_LATENCY_3 | SDRAM_MODEREG_OPERATING_MODE_STANDARD | SDRAM_MODEREG_WRITEBURST_MODE_SINGLE; sdram_command_init_struct.command = EXMC_SDRAM_LOAD_MODE_REGISTER; sdram_command_init_struct.bank_select = bank_select; sdram_command_init_struct.auto_refresh_number = EXMC_SDRAM_AUTO_REFLESH_1_SDCLK; sdram_command_init_struct.mode_register_content = command_content; /* wait until the SDRAM controller is ready */ timeout = SDRAM_TIMEOUT; while((exmc_flag_get(sdram_device, EXMC_SDRAM_FLAG_NREADY) != RESET) && (timeout > 0)) { timeout--; } /* send the command */ exmc_sdram_command_config(&sdram_command_init_struct); /* step 8 : set the auto-refresh rate counter--------------------------------*/ /* 64ms, 8192-cycle refresh, 64ms/8192=7.81us */ /* SDCLK_Freq = SYS_Freq/2 */ /* (7.81 us * SDCLK_Freq) - 20 */ exmc_sdram_refresh_count_set(761); /* wait until the SDRAM controller is ready */ timeout = SDRAM_TIMEOUT; while((exmc_flag_get(sdram_device, EXMC_SDRAM_FLAG_NREADY) != RESET) && (timeout > 0)) { timeout--; } }   (二)将缓冲放到SDRAM中 LittleVGL 和 TLI配置的缓冲区都要修改。 1-配置缓冲地址 定义SDRAM中的地址,初始化中使用了是SDRAM设备0,对应地址为 0xC0000000  定义地址: #define EXT_BUFFER0_ADDR 0xC0000000 #define EXT_BUFFER1_ADDR (EXT_BUFFER0_ADDR + LCD_WIDTH * LCD_HEIGHT * 2) *2是因为每个像素包含一个RGB565数据,5+6+5是16bit即两个8字节。   2-TLI 缓冲区 把TLI初始化中的缓冲位定向到SDRAM的地址中: tli_layer_init_struct.layer_frame_bufaddr = (uint32_t)EXT_BUFFER0_ADDR; tli_layer_init_struct.layer_frame_bufaddr = (uint32_t)EXT_BUFFER1_ADDR; 删去layer_window_leftpos、layer_window_rightpos、layer_window_toppos、layer_window_bottompos的偏移: tli_layer_init_struct.layer_window_leftpos = HORIZONTAL_SYNCHRONOUS_PULSE + HORIZONTAL_BACK_PORCH; tli_layer_init_struct.layer_window_rightpos = (LCD_WIDTH + HORIZONTAL_SYNCHRONOUS_PULSE + HORIZONTAL_BACK_PORCH - 1); tli_layer_init_struct.layer_window_toppos = VERTICAL_SYNCHRONOUS_PULSE + VERTICAL_BACK_PORCH; tli_layer_init_struct.layer_window_bottompos = (LCD_HEIGHT + VERTICAL_SYNCHRONOUS_PULSE + VERTICAL_BACK_PORCH - 1); 3-LittleVGL 缓冲 lv_port_disp_init 函数里,首先要在一开始调用EXMC的初始化函数,并且开辟内存: exmc_synchronous_dynamic_ram_init(EXMC_SDRAM_DEVICE0); delay_ms(1); memset((void *)EXT_BUFFER0_ADDR, 0, LCD_WIDTH * LCD_HEIGHT * LCD_FB_BYTE_PER_PIXEL*2); memset((void *)EXT_BUFFER1_ADDR, 0, LCD_WIDTH * LCD_HEIGHT * LCD_FB_BYTE_PER_PIXEL*2); 重新定向缓冲区: static lv_disp_buf_t disp_buf; lv_disp_buf_init(&disp_buf, EXT_BUFFER0_ADDR, EXT_BUFFER1_ADDR, LCD_WIDTH * LCD_HEIGHT * LCD_FB_BYTE_PER_PIXEL); disp_drv.buffer = &disp_buf;   4-重新配置触摸范围 DEMO_ReadTouch 函数中,把以下两行注释掉: // touch_x -= 20; // touch_y -= 30;   (三)显示效果 对比之前的局部显示效果:   代码: 源码查看本人gitee开源代码:LittleVGL_GD32F450

  • 回复了主题帖: 【GD32450I-EVAL】开箱上电,搭建开发环境

    宋元浩 发表于 2020-9-14 09:39 给的摄像头是OV2640?
    是啊,OV2640,感觉有点糊糊的

  • 发表了主题帖: 【GD32450I-EVAL】移植触摸到LittleVGL

    上一篇帖子进行了触摸驱动测试:SPI收发与触摸芯片XPT2046驱动及笔中断 接下来就是移植到 LittleVGL 。 上次移植完 LittleVGL 的显示时留下了一个: static bool DEMO_ReadTouch(lv_indev_drv_t * indev_drv, lv_indev_data_t *data) 这个函数里面没有进行什么操作,这时候只需要添加触摸位置点进去即可。 static bool DEMO_ReadTouch(lv_indev_drv_t * indev_drv, lv_indev_data_t *data) { static int touch_x = 0; static int touch_y = 0; uint16_t t_xx = 0, t_yy = 0; data->state = LV_INDEV_STATE_REL; if(gpio_input_bit_get(GPIOI, GPIO_PIN_3)==RESET) { ReadTPXYOver(&t_xx, &t_yy); t_yy = 4096-t_yy; touch_x = 480 * t_xx / 4096; touch_y = 272 * t_yy / 4096; touch_x -= 20; touch_y -= 30; if (touch_x<0 || touch_x > LCD_WIDTH){ touch_x =0; touch_y =0; } else if (touch_y<0 || touch_y > LCD_HEIGHT){ touch_x =0; touch_y =0; } else data->state = LV_INDEV_STATE_PR; } /*Set the last pressed coordinates*/ data->point.x = touch_x; data->point.y = touch_y; /*Return `false` because we are not buffering and no more data to read*/ return false; } 因为RAM容量的原因,只能支持270*180宽度显示,因此必须换算实际触摸点与 LittleVGL 内的位置,因此程序里加入了: touch_x -= 20; touch_y -= 30; 其中X显示的时候向左偏移了20像素,Y偏移了30像素,具体可以看显示部分的移植。   ReadTPXYOver 函数是进行多次读取,经过冒泡排序后取中位数: void ReadTPXY(uint16_t *t_x, uint16_t *t_y) { char t_char[30] = ""; gpio_bit_reset(GPIOF, GPIO_PIN_6); //X while (RESET == spi_i2s_flag_get(SPI3, SPI_FLAG_TBE)); spi_i2s_data_transmit(SPI4, 0xD0); while (RESET == spi_i2s_flag_get(SPI4, SPI_FLAG_RBNE)); spi_i2s_data_receive(SPI4); // delay_us(100); //read 1 while (RESET == spi_i2s_flag_get(SPI3, SPI_FLAG_TBE)); spi_i2s_data_transmit(SPI4, 0x00); while (RESET == spi_i2s_flag_get(SPI4, SPI_FLAG_RBNE)); *t_x = spi_i2s_data_receive(SPI4); *t_x = (*t_x& 0x7F)<<8 ; //read2 while (RESET == spi_i2s_flag_get(SPI3, SPI_FLAG_TBE)); spi_i2s_data_transmit(SPI4, 0x90); while (RESET == spi_i2s_flag_get(SPI4, SPI_FLAG_RBNE)); *t_x = *t_x|spi_i2s_data_receive(SPI4); // sprintf(t_char, "x:%x\t", *t_x); // PRINTF_UART0(t_char); *t_x = *t_x>>3; // delay_us(100); while (RESET == spi_i2s_flag_get(SPI3, SPI_FLAG_TBE)); spi_i2s_data_transmit(SPI4, 0x00); while (RESET == spi_i2s_flag_get(SPI4, SPI_FLAG_RBNE)); *t_y = spi_i2s_data_receive(SPI4); *t_y = (*t_y& 0x7F) <<8; while (RESET == spi_i2s_flag_get(SPI3, SPI_FLAG_TBE)); spi_i2s_data_transmit(SPI4, 0x00); while (RESET == spi_i2s_flag_get(SPI4, SPI_FLAG_RBNE)); *t_y = *t_y|spi_i2s_data_receive(SPI4); // sprintf(t_char, "y:%x\t", *t_y); // PRINTF_UART0(t_char); *t_y = *t_y>>3; } void ReadTPXYOver(uint16_t *t_x, uint16_t *t_y)   效果:

  • 回复了主题帖: 【GD32450I-EVAL】SPI收发与触摸芯片XPT2046驱动及笔中断

    源码查看本人gitee开源代码:LittleVGL_GD32F450

  • 发表了主题帖: 【GD32450I-EVAL】SPI收发与触摸芯片XPT2046驱动及笔中断

    本帖最后由 tinnu 于 2020-10-6 18:08 编辑 (一)XPT2046 官方配置的是一个电阻屏,屏幕拆开一看,果然是XPT2046,这是一个常见的电阻屏驱动IC,SPI接口,本质上就是一个AD芯片,不过针对触摸优化,能够直接输出触摸结果。 不过它之所以如此出名,主要还是因为它是正点原子电阻屏的触摸芯片,作为一个现象级品牌,带动诸多产品都使用了该款芯片。 虽然我手上有两个8080并口MCU屏和一个SPI串行屏幕都是使用这颗芯片作为触摸驱动,但实际上我根本没有调过它的程序,在此之前也没有对XPT2046有多少了解。曾经为矿渣画过一块扩展板,用了H2046代替电阻触摸驱动,结果没驱动起来,于是放弃了。这次重新再看数据手册着实迷糊了好久。   事实上XPT2046的SPI收发并不复杂,发送的指令只有一个: 【】 依次对行和列进行扫描,然后比例换算出触摸点。 按照格式发送指令之后可以依次读两个8位数据接收,其中第一个周期第二位开始才是有效数值。   (二)SPI硬件接口 LCD的触摸SPI接口如图: 使用的是SPI4,查看映射表: 映射到第五个功能,配置GPIO为AF5,同时使能片选口: rcu_periph_clock_enable(RCU_GPIOF); rcu_periph_clock_enable(RCU_SPI4); /*k SPI1 GPIO config */ gpio_af_set(GPIOF, GPIO_AF_5, GPIO_PIN_7 | GPIO_PIN_8 |GPIO_PIN_9); gpio_mode_set(GPIOF, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_7 | GPIO_PIN_8 |GPIO_PIN_9); gpio_output_options_set(GPIOF, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_7 | GPIO_PIN_8 |GPIO_PIN_9); /* set SPI4_NSS as GPIO*/ gpio_mode_set(GPIOF,GPIO_MODE_OUTPUT,GPIO_PUPD_NONE,GPIO_PIN_6); gpio_output_options_set(GPIOF, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_6); gpio_bit_set(GPIOF, GPIO_PIN_6);   (三)SPI软件配置 SPI软件配置的关键在于PL和PH,PL指哪个边沿读取数据,PH指空闲时时钟线拉高还是拉低。 网上不知为何出现大量误导的教程,以ST平台驱动XPT2046为例举出这样的配置: SPI SPI_InitStructure.SPI_CPOL = SPI_CPOL_High; //时钟悬空高电平 SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge; //在第二个时钟采集数据 事实上是错的,刚好反了过来,看数据手册的描述: DCLK时钟线在空闲时为低,即PH=low,并且在上升沿读取数据,即第一个边沿读取,即PL=high 因此配置应为: /* SPI4 parameter config */ spi_init_struct.trans_mode = SPI_TRANSMODE_FULLDUPLEX; spi_init_struct.device_mode = SPI_MASTER; spi_init_struct.frame_size = SPI_FRAMESIZE_8BIT; spi_init_struct.clock_polarity_phase = SPI_CK_PL_LOW_PH_1EDGE; spi_init_struct.nss = SPI_NSS_SOFT; spi_init_struct.prescale = SPI_PSC_256; spi_init_struct.endian = SPI_ENDIAN_MSB; spi_init(SPI4, &spi_init_struct);   (四)SPI收发 轮询收发函数: spi_i2s_data_transmit spi_i2s_data_receive 但实际测试发现,spi_i2s_data_receive 不具备操作硬件的功能,他只是从接收的寄存器里面读一个数据出来。 因此每次接收之前需要额外再写一次。   1-但这个时候出现了一个问题,就是从寄存器里面读取的数据是上上次接收的数据: 此时的代码: gpio_bit_reset(GPIOF, GPIO_PIN_6); //X while (RESET == spi_i2s_flag_get(SPI3, SPI_FLAG_TBE)); spi_i2s_data_transmit(SPI4, 0xD3); delay_ms(2); //read 1 while (RESET == spi_i2s_flag_get(SPI3, SPI_FLAG_TBE)); spi_i2s_data_transmit(SPI4, 0x00); while (RESET == spi_i2s_flag_get(SPI4, SPI_FLAG_RBNE)); t_char[1] = spi_i2s_data_receive(SPI4); //read2 delay_ms(1); while (RESET == spi_i2s_flag_get(SPI3, SPI_FLAG_TBE)); spi_i2s_data_transmit(SPI4, 0x00); while (RESET == spi_i2s_flag_get(SPI4, SPI_FLAG_RBNE)); t_char[2] = spi_i2s_data_receive(SPI4); usart_data_transmit(USART0, t_char[0]); while(RESET == usart_flag_get(USART0, USART_FLAG_TBE)); usart_data_transmit(USART0, t_char[1]); while(RESET == usart_flag_get(USART0, USART_FLAG_TBE)); usart_data_transmit(USART0, t_char[2]); while(RESET == usart_flag_get(USART0, USART_FLAG_TBE)); 排查良久,感觉导致错位的第一个数据是写命令时读的那个数据,猜测可能是有某种缓冲机制——数据写入之后必须立即读一次,如果没有立即读出来会导致数据滞后一位。 根据这个猜想排除bug: gpio_bit_reset(GPIOF, GPIO_PIN_6); //X while (RESET == spi_i2s_flag_get(SPI3, SPI_FLAG_TBE)); spi_i2s_data_transmit(SPI4, 0xD3); while (RESET == spi_i2s_flag_get(SPI4, SPI_FLAG_RBNE)); t_char[0] = spi_i2s_data_receive(SPI4); delay_ms(2); //read 1 while (RESET == spi_i2s_flag_get(SPI3, SPI_FLAG_TBE)); spi_i2s_data_transmit(SPI4, 0x00); while (RESET == spi_i2s_flag_get(SPI4, SPI_FLAG_RBNE)); t_char[1] = spi_i2s_data_receive(SPI4); //read2 delay_ms(1); while (RESET == spi_i2s_flag_get(SPI3, SPI_FLAG_TBE)); spi_i2s_data_transmit(SPI4, 0x00); while (RESET == spi_i2s_flag_get(SPI4, SPI_FLAG_RBNE)); t_char[2] = spi_i2s_data_receive(SPI4); usart_data_transmit(USART0, t_char[0]); while(RESET == usart_flag_get(USART0, USART_FLAG_TBE)); usart_data_transmit(USART0, t_char[1]); while(RESET == usart_flag_get(USART0, USART_FLAG_TBE)); usart_data_transmit(USART0, t_char[2]); while(RESET == usart_flag_get(USART0, USART_FLAG_TBE)); 收发果然就正常了。     2-实际上在排除上述的bug中间还出现过另一个问题,就是忘记获取读允许标志位立即就读取的话还会导致下一个写操作丢失: while (RESET == spi_i2s_flag_get(SPI3, SPI_FLAG_TBE)); spi_i2s_data_transmit(SPI4, 0x93); // while (RESET == spi_i2s_flag_get(SPI4, SPI_FLAG_RBNE)); spi_i2s_data_receive(SPI4); delay_us(100); while (RESET == spi_i2s_flag_get(SPI3, SPI_FLAG_TBE)); spi_i2s_data_transmit(SPI4, 0x00); while (RESET == spi_i2s_flag_get(SPI4, SPI_FLAG_RBNE)); t_char[0] = spi_i2s_data_receive(SPI4); while (RESET == spi_i2s_flag_get(SPI3, SPI_FLAG_TBE)); spi_i2s_data_transmit(SPI4, 0x00); while (RESET == spi_i2s_flag_get(SPI4, SPI_FLAG_RBNE)); t_char[1] = spi_i2s_data_receive(SPI4); usart_data_transmit(USART0, t_char[0]); while(RESET == usart_flag_get(USART0, USART_FLAG_TBE)); usart_data_transmit(USART0, t_char[1]); while(RESET == usart_flag_get(USART0, USART_FLAG_TBE));       (五)笔中断测试 XPT2046的笔中断实际上就相当于一个外部中断,甚至松手的时候还会有一个0.2ms左右的抖动,类似轻触按键那种抖动,需要延时去抖。 硬件部分可以参考一开始的贴图,使用的Pi3引脚,对应的是EXIT3中断,配置函数: void EXIT3_TPInit() { /* enable and set key EXTI interrupt to the lowest priority */ nvic_irq_enable(EXTI3_IRQn, 2U, 0U); /* connect key EXTI line to key GPIO pin */ syscfg_exti_line_config(EXTI_SOURCE_GPIOI, EXTI_SOURCE_PIN3); /* configure key EXTI line */ exti_init(EXTI_3, EXTI_INTERRUPT, EXTI_TRIG_FALLING); exti_interrupt_flag_clear(EXTI_3); } 中断函数,给一个1ms的延时实际上能排除大部分抖动带来的误触,但有一小部分的依旧会漏掉,需要起码延时100ms以上: void EXTI3_IRQHandler(void) { uint16_t t_data = 0x44, t_x=0, t_y=0; char t_char[30] = ""; if (RESET != exti_interrupt_flag_get(EXTI_3)) { delay_ms(100); if(gpio_input_bit_get(GPIOI, GPIO_PIN_3)==RESET) { gd_eval_led_toggle(LED2); PRINTF_UART0("TCH:"); ReadTPXYOver(&t_x, &t_y); sprintf(t_char, "x:%d\ty:%d\r\n", t_x, t_y); PRINTF_UART0(t_char); } // lvChange_sliderTest(); exti_interrupt_flag_clear(EXTI_3); } } 此时触摸一下屏幕,就会打印一次AD值。   (六)AD精度与SPI速度 调过程序才发现,原来AD的转化精度是取决于SPI速度的,换言之SPI还不能读得太快,一开始使用了8分频,即: spi_init_struct.prescale = SPI_PSC_8; 几乎整个屏幕只有屈指可数的几个分辨率,要滑很长的距离才会出现一个突变的数值,查看HEX码,只有第一位转化了,换言之精度不足4位。 而使用256的时候基本可以实现像素间的区分了,上图是256分频时的时钟间隔,一个周期2.5ms 因为发送的命令时0x90和0xD0,所以数据是12位的。 12位分辨极限为4096。     (七)接收的数据处理 接收到的是一个16位的数据,第一位是busy,AD正在转化,无效。从第二位开始算起12位:     t_x = spi_i2s_data_receive(SPI4);     t_x = (t_x& 0x7F)<<8 ;       t_x = t_x|spi_i2s_data_receive(SPI4);     t_x = t_x>>3;   完整的测试函数: void ReadTPTest() { char t_char[30]=""; uint16_t t_x=0,t_y=0; gpio_bit_reset(GPIOF, GPIO_PIN_6); //X while (RESET == spi_i2s_flag_get(SPI3, SPI_FLAG_TBE)); spi_i2s_data_transmit(SPI4, 0xD0); while (RESET == spi_i2s_flag_get(SPI4, SPI_FLAG_RBNE)); spi_i2s_data_receive(SPI4); delay_us(10); //read 1 while (RESET == spi_i2s_flag_get(SPI3, SPI_FLAG_TBE)); spi_i2s_data_transmit(SPI4, 0x00); while (RESET == spi_i2s_flag_get(SPI4, SPI_FLAG_RBNE)); t_x = spi_i2s_data_receive(SPI4); t_x = (t_x& 0x7F)<<8 ; //read2 while (RESET == spi_i2s_flag_get(SPI3, SPI_FLAG_TBE)); spi_i2s_data_transmit(SPI4, 0x90); while (RESET == spi_i2s_flag_get(SPI4, SPI_FLAG_RBNE)); t_x = t_x|spi_i2s_data_receive(SPI4); t_x = t_x>>3; delay_us(10); while (RESET == spi_i2s_flag_get(SPI3, SPI_FLAG_TBE)); spi_i2s_data_transmit(SPI4, 0x00); while (RESET == spi_i2s_flag_get(SPI4, SPI_FLAG_RBNE)); t_y = spi_i2s_data_receive(SPI4); t_y = (t_y& 0x7F) <<8; while (RESET == spi_i2s_flag_get(SPI3, SPI_FLAG_TBE)); spi_i2s_data_transmit(SPI4, 0x00); while (RESET == spi_i2s_flag_get(SPI4, SPI_FLAG_RBNE)); t_y = t_y|spi_i2s_data_receive(SPI4); t_y = t_y>>3; gpio_bit_set(GPIOF, GPIO_PIN_6); sprintf(t_char, "x:%d\ty:%d\r\n", t_x, t_y); PRINTF_UART0(t_char); }    

  • 2020-10-05
  • 回复了主题帖: KiCad 5.1.7于9月30号已经正式发布了

    qwqwqw2088 发表于 2020-10-5 16:49 国产EDA已经相当不错了,毕竟是免费的。 脚本一直转换失败,是怎么出现的呢 以前画小板用过,在网 ...
    有一个easyeda2kicad的工具,说是可以本地赚,结果执行脚本一直报错,最后只能网页转。 画小板LCEDA还可以,复杂一点的体验都不行,它那个阻挡布线简直反人类,布那种挨着的一簇过去的线烦得想砸电脑,还是导出altium或者kicad比较方便

  • 回复了主题帖: KiCad 5.1.7于9月30号已经正式发布了

    如果LCEDA支持直导KICAD就好了,脚本一直转换失败,只能网页转,又很麻烦

  • 2020-10-01
  • 发表了主题帖: 【GD32450I-EVAL】UART

    这块板子的串口比较粗暴,是直接引出至232,使用的max3232进行转接的。 使用的板载串口为USART0,从原理图可以看到接的是PA9和PA10。   (一)时钟 串口初始化首先要使能时钟: 包括GPIO时钟和串口外设时钟:     /* enable GPIO clock */     rcu_periph_clock_enable(RCU_GPIOA);     /* enable USART clock */     rcu_periph_clock_enable(RCU_USART0); 关于时钟源,参考手册提到 “由外设时钟分频产生,其中USART0/5由PCLK2分频得到,USART1/2和 UART3/4/6/7由PCLK1分频得到;” PCLK1并非指某一个总线,而是每个外设自己的时钟, rcu_periph_clock_enable(RCU_USART0); 即为USART专属的PCLK开关。 但前方的APB2还是需要使能的。但这部分在系统初始化处使能了。 首先我们看startup_gd32f450.s文件。 在初始化调用main之前调用了 SystemInit 函数。 里面又调用了一个 system_clock_config(); 再往里面调用了: system_clock_200m_25m_hxtal();   函数: static void system_clock_200m_25m_hxtal(void) { uint32_t timeout = 0U; uint32_t stab_flag = 0U; /* enable HXTAL */ RCU_CTL |= RCU_CTL_HXTALEN; /* wait until HXTAL is stable or the startup time is longer than HXTAL_STARTUP_TIMEOUT */ do{ timeout++; stab_flag = (RCU_CTL & RCU_CTL_HXTALSTB); }while((0U == stab_flag) && (HXTAL_STARTUP_TIMEOUT != timeout)); /* if fail */ if(0U == (RCU_CTL & RCU_CTL_HXTALSTB)){ while(1){ } } RCU_APB1EN |= RCU_APB1EN_PMUEN; PMU_CTL |= PMU_CTL_LDOVS; /* HXTAL is stable */ /* AHB = SYSCLK */ RCU_CFG0 |= RCU_AHB_CKSYS_DIV1; /* APB2 = AHB/2 */ RCU_CFG0 |= RCU_APB2_CKAHB_DIV2; /* APB1 = AHB/4 */ RCU_CFG0 |= RCU_APB1_CKAHB_DIV4; /* Configure the main PLL, PSC = 25, PLL_N = 400, PLL_P = 2, PLL_Q = 9 */ RCU_PLL = (25U | (400U << 6U) | (((2U >> 1U) - 1U) << 16U) | (RCU_PLLSRC_HXTAL) | (9U << 24U)); /* enable PLL */ RCU_CTL |= RCU_CTL_PLLEN; /* wait until PLL is stable */ while(0U == (RCU_CTL & RCU_CTL_PLLSTB)){ } /* Enable the high-drive to extend the clock frequency to 200 Mhz */ PMU_CTL |= PMU_CTL_HDEN; while(0U == (PMU_CS & PMU_CS_HDRF)){ } /* select the high-drive mode */ PMU_CTL |= PMU_CTL_HDS; while(0U == (PMU_CS & PMU_CS_HDSRF)){ } /* select PLL as system clock */ RCU_CFG0 &= ~RCU_CFG0_SCS; RCU_CFG0 |= RCU_CKSYSSRC_PLLP; /* wait until PLL is selected as system clock */ while(0U == (RCU_CFG0 & RCU_SCSS_PLLP)){ } } 这个函数对APB2进行了初始化。   (二)设置GPIO功能以及重映射 相关映射功能查阅数据手册 映射到AF7,设置GPIO上下拉等功能。 // configure the USART0 Tx pin and USART0 Rx pin gpio_af_set(GPIOA, GPIO_AF_7, GPIO_PIN_9); gpio_af_set(GPIOA, GPIO_AF_7, GPIO_PIN_10); // configure USART0 Tx as alternate function push-pull gpio_mode_set(GPIOA, GPIO_MODE_AF, GPIO_PUPD_PULLUP, GPIO_PIN_9); gpio_output_options_set(GPIOA, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_9); // configure USART0 Rx as alternate function push-pull gpio_mode_set(GPIOA, GPIO_MODE_AF, GPIO_PUPD_PULLUP, GPIO_PIN_10); gpio_output_options_set(GPIOA, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_10);   (三)配置USART /* USART configure */ usart_deinit(USART0); usart_baudrate_set(USART0, 115200U); usart_receive_config(USART0, USART_RECEIVE_ENABLE); usart_transmit_config(USART0, USART_TRANSMIT_ENABLE); usart_enable(USART0); 这个配置先不去深究,首先能够进行基本的收发再说,后面有机会搞DMA收发再详究寄存器。   (四)发送 编写一个发送函数: void PRINTF_UART0(char *data) { int i; for(i=0;i<strlen(data);i++){ usart_data_transmit(USART0, data); while(RESET == usart_flag_get(USART0, USART_FLAG_TBE)); } }   (五)硬件连接 板载只有一个232公头接口,相当麻烦,因为一般主机才会用公头接口,所以转串口线都是公头的,公头对公头无法连接。 恰逢放假,手上那条平时用开的母头对母头转接线不在身边。翻箱底找了很久,找了一条别的线,试了下竟然不行。 图中又是上逻辑分析仪又是上232转USB,所有收发都是没问题的。最后发现是这条转接线有问题! 一般的母头对母头转接线需要把RX和TX反过来,结果它是一对一接的,这下完犊子了,怎么办呢?这时候突然想起之前做的一块板子: 这里用的是一个比较便于实验的设计,打横和打竖接跳线码可以把TX和RX反过来。 主函数里轮询执行: delay_ms(100); PRINTF_UART0("welcome!\r\n");   效果:

  • 2020-09-13
  • 回复了主题帖: 【树莓派4B测评】树莓派4摄像头Opencv与picamera测试

    有没有兴趣跑一跑基本的opencv算法,像blur、canny之类的,看看提升有多大?我之前给3B跑过  我迟点用我的NANOPC-T4看看跟树莓派4B孰优孰劣

  • 回复了主题帖: 【树莓派4B测评】树莓派4不同操作系统跑分测试

    吼啊,不过不是有64位系统出来么,32位对性能限制还是有些大啊

  • 发表了主题帖: 【GD32450I-EVAL】LittleVGL显示部分移植

    本帖最后由 tinnu 于 2020-9-13 23:30 编辑 (一)Littlevgl Littlevgl是目前最火的开源嵌入式GUI之一,相比于emwin,其界面更加柔和,基于MIT协议,这个协议非常宽松,可以商用闭源,而emwin商用则是需要付费,并且源码也是封闭的。另外,在移植的便利性上Littlevgl也远比emwin更有优势。 LittleVGL最新已经更新到V7系列,我这次就移植最新的这个版本。 Littlevgl下载地址:https://gitee.com/mirrors/lvgl?_from=gitee_search WIKI(里面有API手册下载地址):https://docs.lvgl.io/v7/en/html/   (二)基本移植 1-把原本的littlevgl文件夹删掉,把Git下来的src里面的文件全部拷进去 #define LV_HOR_RES_MAX          (LCD_WIDTH) #define LV_VER_RES_MAX          (LCD_HEIGHT) 其中 LCD_WIDTH 的值在后面的驱动文件里面定义。   2-把GPU禁用掉: /* 1: Enable GPU interface*/ #define LV_USE_GPU              0   /*Only enables `gpu_fill_cb` and `gpu_blend_cb` in the disp. drv- */ #define LV_USE_GPU_STM32_DMA2D  0 /*If enabling LV_USE_GPU_STM32_DMA2D, LV_GPU_DMA2D_CMSIS_INCLUDE must be defined to include path of CMSIS header of target processor e.g. "stm32f769xx.h" or "stm32f429xx.h" */ #define LV_GPU_DMA2D_CMSIS_INCLUDE 3-添加宏 LV_CONF_INCLUDE_SIMPLE=1   4-添加一个 littlevgl_support.c 文件 添加显示刷新函数: void lv_port_disp_init(void) { /*------------------------- * Initialize your display * -----------------------*/ tli_gpio_config(); tli_config_twolayout(); tli_layer_enable(LAYER0); tli_reload_config(TLI_REQUEST_RELOAD_EN); tli_enable(); /*----------------------------------- * Register the display in LittlevGL *----------------------------------*/ lv_disp_drv_t disp_drv; /*Descriptor of a display driver*/ lv_disp_drv_init(&disp_drv); /*Basic initialization*/ /*Set up the functions to access to your display*/ static lv_disp_buf_t disp_buf_2; lv_disp_buf_init(&disp_buf_2, buf2_1, buf2_2, LCD_WIDTH * LCD_HEIGHT * LCD_FB_BYTE_PER_PIXEL); /*Initialize the display buffer*/ disp_drv.buffer = &disp_buf_2; disp_drv.hor_res = LCD_WIDTH; disp_drv.ver_res = LCD_HEIGHT; /*Used in buffered mode (LV_VDB_SIZE != 0 in lv_conf.h)*/ disp_drv.flush_cb = DEMO_FlushDisplay; /*Finally register the driver*/ lv_disp_drv_register(&disp_drv); }   (三)平台驱动移植 1-缓存设置 Littlevgl一般设置双缓冲,按照480*272*4=510K这个大小来说,本芯片IK结尾,只有256K SRAM肯定是不够了,如果是II结尾的那颗或许还可以。 如果按照256K理论上也可以设置双240*240*2大小的缓冲区,但这256K实际上是分片的。 keil上面也是默认分成两个区 经过试验,单个缓冲似乎不能跨区,粗略测试过后,最大的缓冲区就是270*180*2*2了。 因此在 littlevgl_support.h 文件中添加: #define LCD_WIDTH 270 #define LCD_HEIGHT 180 #define LCD_FB_BYTE_PER_PIXEL 1 在 littlevgl_support.c 文件中添加全局变量: static lv_color_t buf2_1[LCD_WIDTH * LCD_HEIGHT * LCD_FB_BYTE_PER_PIXEL]; /*A buffer for 10 rows*/ static lv_color_t buf2_2[LCD_WIDTH * LCD_HEIGHT * LCD_FB_BYTE_PER_PIXEL]; /*An other buffer for 10 rows*/ 如果是用memset方式在开辟,则需要设置LCD_WIDTH * LCD_HEIGHT * LCD_FB_BYTE_PER_PIXEL*2大小,因为现在使用的是RGB565格式,每位需要16位2字节空间。   2-初始化 gpio的初始化见上一贴: 【GD32450I-EVAL】TLI-RGB屏幕驱动初探 tli的初始化需要根据缓冲区做一点改变,刚好设置为 LCD_WIDTH * LCD_HEIGHT 的显示范围: static void tli_config_twolayout(void) { tli_parameter_struct tli_init_struct; tli_layer_parameter_struct tli_layer_init_struct; rcu_periph_clock_enable(RCU_TLI); tli_gpio_config(); /* configure PLLSAI to generate TLI clock */ if(ERROR == rcu_pllsai_config(192, 2, 3)){ while(1); } rcu_tli_clock_div_config(RCU_PLLSAIR_DIV8); rcu_osci_on(RCU_PLLSAI_CK); if(ERROR == rcu_osci_stab_wait(RCU_PLLSAI_CK)){ while(1); } /* configure TLI parameter struct */ tli_init_struct.signalpolarity_hs = TLI_HSYN_ACTLIVE_LOW; tli_init_struct.signalpolarity_vs = TLI_VSYN_ACTLIVE_LOW; tli_init_struct.signalpolarity_de = TLI_DE_ACTLIVE_LOW; tli_init_struct.signalpolarity_pixelck = TLI_PIXEL_CLOCK_TLI; /* LCD display timing configuration */ tli_init_struct.synpsz_hpsz = HORIZONTAL_SYNCHRONOUS_PULSE - 1; tli_init_struct.synpsz_vpsz = VERTICAL_SYNCHRONOUS_PULSE - 1; tli_init_struct.backpsz_hbpsz = HORIZONTAL_SYNCHRONOUS_PULSE + HORIZONTAL_BACK_PORCH - 1; tli_init_struct.backpsz_vbpsz = VERTICAL_SYNCHRONOUS_PULSE + VERTICAL_BACK_PORCH - 1; tli_init_struct.activesz_hasz = HORIZONTAL_SYNCHRONOUS_PULSE + HORIZONTAL_BACK_PORCH + ACTIVE_WIDTH - 1; tli_init_struct.activesz_vasz = VERTICAL_SYNCHRONOUS_PULSE + VERTICAL_BACK_PORCH + ACTIVE_HEIGHT - 1; tli_init_struct.totalsz_htsz = HORIZONTAL_SYNCHRONOUS_PULSE + HORIZONTAL_BACK_PORCH + ACTIVE_WIDTH + HORIZONTAL_FRONT_PORCH - 1; tli_init_struct.totalsz_vtsz = VERTICAL_SYNCHRONOUS_PULSE + VERTICAL_BACK_PORCH + ACTIVE_HEIGHT + VERTICAL_FRONT_PORCH - 1; /* configure LCD background R,G,B values */ tli_init_struct.backcolor_red = 0xFF; tli_init_struct.backcolor_green = 0xFF; tli_init_struct.backcolor_blue = 0xFF; tli_init(&tli_init_struct); /* TLI layer1 configuration */ /* TLI window size configuration */ tli_layer_init_struct.layer_window_leftpos = 20 + HORIZONTAL_SYNCHRONOUS_PULSE + HORIZONTAL_BACK_PORCH; tli_layer_init_struct.layer_window_rightpos = (20 + LCD_WIDTH + HORIZONTAL_SYNCHRONOUS_PULSE + HORIZONTAL_BACK_PORCH - 1); tli_layer_init_struct.layer_window_toppos = 30 + VERTICAL_SYNCHRONOUS_PULSE + VERTICAL_BACK_PORCH; tli_layer_init_struct.layer_window_bottompos = (30 + LCD_HEIGHT + VERTICAL_SYNCHRONOUS_PULSE + VERTICAL_BACK_PORCH - 1); /* TLI window pixel format configuration */ tli_layer_init_struct.layer_ppf = LAYER_PPF_RGB565; /* TLI window specified alpha configuration */ tli_layer_init_struct.layer_sa = 255; /* TLI window blend configuration */ tli_layer_init_struct.layer_acf1 = LAYER_ACF1_PASA; tli_layer_init_struct.layer_acf2 = LAYER_ACF2_PASA; /* TLI layer default alpha R,G,B value configuration */ tli_layer_init_struct.layer_default_alpha = 0; tli_layer_init_struct.layer_default_blue = 0xFF; tli_layer_init_struct.layer_default_green = 0xFF; tli_layer_init_struct.layer_default_red = 0xFF; /* TLI layer frame buffer base address configuration */ tli_layer_init_struct.layer_frame_bufaddr = (uint32_t)buf2_1; tli_layer_init_struct.layer_frame_line_length = ((LCD_WIDTH * 2) + 3); tli_layer_init_struct.layer_frame_buf_stride_offset = (LCD_WIDTH * 2); tli_layer_init_struct.layer_frame_total_line_number = LCD_HEIGHT; tli_layer_init(LAYER1, &tli_layer_init_struct); /* TLI layer0 configuration */ tli_layer_init_struct.layer_window_leftpos = 20 + HORIZONTAL_SYNCHRONOUS_PULSE + HORIZONTAL_BACK_PORCH; tli_layer_init_struct.layer_window_rightpos = (20 + LCD_WIDTH + HORIZONTAL_SYNCHRONOUS_PULSE + HORIZONTAL_BACK_PORCH - 1); tli_layer_init_struct.layer_window_toppos = 40 + VERTICAL_SYNCHRONOUS_PULSE + VERTICAL_BACK_PORCH; tli_layer_init_struct.layer_window_bottompos = (40 + LCD_HEIGHT + VERTICAL_SYNCHRONOUS_PULSE + VERTICAL_BACK_PORCH - 1); tli_layer_init_struct.layer_ppf = LAYER_PPF_RGB565; /* TLI window specified alpha configuration */ tli_layer_init_struct.layer_sa = 255; /* TLI layer default alpha R,G,B value configuration */ tli_layer_init_struct.layer_default_blue = 0xFF; tli_layer_init_struct.layer_default_green = 0xFF; tli_layer_init_struct.layer_default_red = 0xFF; tli_layer_init_struct.layer_default_alpha = 0xFF; /* TLI window blend configuration */ tli_layer_init_struct.layer_acf1 = LAYER_ACF1_PASA; tli_layer_init_struct.layer_acf2 = LAYER_ACF2_PASA; /* TLI layer frame buffer base address configuration */ tli_layer_init_struct.layer_frame_bufaddr = (uint32_t)buf2_2; tli_layer_init_struct.layer_frame_line_length = ((LCD_WIDTH * 2) + 3); tli_layer_init_struct.layer_frame_buf_stride_offset = (LCD_WIDTH * 2); tli_layer_init_struct.layer_frame_total_line_number = LCD_HEIGHT; tli_layer_init(LAYER0, &tli_layer_init_struct); tli_dither_config(TLI_DITHER_ENABLE); } 3-每帧回调刷新,通过显示不同的层实现 static void DEMO_FlushDisplay(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p) { if (color_p == buf2_1) { tli_layer_disable(LAYER0); tli_layer_enable(LAYER1); tli_reload_config(TLI_REQUEST_RELOAD_EN); } else if (color_p == buf2_2) { tli_layer_disable(LAYER1); tli_layer_enable(LAYER0); tli_reload_config(TLI_REQUEST_RELOAD_EN); } /* IMPORTANT!!! * Inform the graphics library that you are ready with the flushing*/ lv_disp_flush_ready(disp_drv); }   4-显示的图像,创建一个居中的进度条 //例程入口 void lvAdd_sliderTest() { lv_obj_t * slider = lv_slider_create(lv_scr_act(), NULL); lv_obj_set_width(slider, LV_DPI*1.5); lv_obj_align(slider, NULL, LV_ALIGN_CENTER, 0, 0); lv_obj_set_event_cb(slider, cb_sliderTest); lv_slider_set_range(slider, 0, 50); // Create a label below the slider slider_label = lv_label_create(lv_scr_act(), NULL); lv_label_set_text(slider_label, "0"); lv_obj_set_auto_realign(slider_label, true); lv_obj_align(slider_label, slider, LV_ALIGN_OUT_BOTTOM_MID, 0, 0); } 5-主函数调用 int main(void) { static __IO uint32_t timingdelaylocal = 0U;     gd_eval_led_init(LED1);     systick_config();     lv_init();     lv_port_disp_init();     lv_port_indev_init();     lvAdd_sliderTest();     while(1){         delay_ms(10);         lv_tick_inc(10);     lv_task_handler();     } }   (四)堆栈问题 第一次编译通过之后,在板子上运行始终卡死在初始化阶段,具体是卡死在theme的初始化配置上: lv_theme_t * th = LV_THEME_DEFAULT_INIT(LV_THEME_DEFAULT_COLOR_PRIMARY, LV_THEME_DEFAULT_COLOR_SECONDARY,                                             LV_THEME_DEFAULT_FLAG,                                             LV_THEME_DEFAULT_FONT_SMALL, LV_THEME_DEFAULT_FONT_NORMAL, LV_THEME_DEFAULT_FONT_SUBTITLE, LV_THEME_DEFAULT_FONT_TITLE); 这个问题困扰了很久没能解决,非常郁闷,之前也曾经成功地移植过LittleVGL到其他平台,但都是基于eclipse的开发环境,很多配置并不相同,在修改过编译选项无果后,偶然之下发现LittleVGL 的主页有这么一点: 仔细一看,startup文件下默认配置的栈堆都只有0x400,都改成0x2000后就能成功运行了。   (五)效果 现在显示出来了,后面移植触摸驱动   工程:

  • 发表了主题帖: 【GD32450I-EVAL】TLI-RGB屏幕驱动初探

    (一)概述 GD32F4系列是我知道的国内最早的M4F内核单片机了,另外的几个系列诸如F3、E1都是不带硬件FPU的M4内核,在一些运算的场景上劣势相当明显,当然,F4最终还是在今年被新推出的M33内核E5系列给摩擦了,后者超高精度定时器和TMU数学加速功能简直让人垂涎,连NXP的POWERQUAD都被不支持的arctan运算都给加速了! 但E5强大的数学能力并不足以让他处处压制F4系列,F4系列有一项重头戏功能——TLI借口,能够直驱RGB接口!而F450更是F4系列其中其中的最强者,主频高达200M,比E5系列还要高20M。   (二)TLI TLI外设除了提供一般的RGB888接口还,还支持双显示层功能,能够通过DMA发送。 另外,直驱过RGB和MCU的一定能够感受到,RGB有一个重大的劣势,就是无法单独刷新某一块区域,这导致在设置DMA缓冲区时必须要一个完整的480*272*2(如果是使用RGB565 16位色)缓冲区,而双缓冲更是需要480*272*2*2,算下来足足有510K,目前F4作为兆易创新SRAM最大的片子也就512K,本芯片则是只有256K,只要程序有一点大小SRAM开销就放不下哪怕一个缓冲,如果不使用外部SDRAM的前提下根本不可能驱动一个最基本的480*272屏幕。 TLI的特性给了一个选择,可以设置填充空缺的位置,而使用一个较小的分辨率和较小的内存开销,这在开发测试阶段很意义。   (三)TLI设置 1-时钟源设置 TLI的时钟完全是由一个独立的PLL(PLLSAI)锁相环输出的,与系统时钟来源不同。值得一提的是I2S也是另一个独立的PLL时钟源(PLLI2S),总共三个PLL锁相环,让音频应用时钟也可以更准确。 TLI的时钟从(PLLSAI)锁相环输出: 通过rcu_pllsai_config设置锁相环参数,rcu_osci_on使能锁相环,rcu_tli_clock_div_config使能锁相环后的分频参数 /* configure PLLSAI to generate TLI clock */ if(ERROR == rcu_pllsai_config(192, 2, 3)){ while(1); } rcu_tli_clock_div_config(RCU_PLLSAIR_DIV8); rcu_osci_on(RCU_PLLSAI_CK); if(ERROR == rcu_osci_stab_wait(RCU_PLLSAI_CK)){ while(1); }   2-使能本身设备时钟 rcu_periph_clock_enable(RCU_TLI);   3-使能GPIO void tli_gpio_config(void) { /* enable GPIO clock */ rcu_periph_clock_enable(RCU_GPIOB); rcu_periph_clock_enable(RCU_GPIOE); rcu_periph_clock_enable(RCU_GPIOH); rcu_periph_clock_enable(RCU_GPIOI); rcu_periph_clock_enable(RCU_GPIOG); /* configure HSYNC(PI10), VSYNC(PI9), PCLK(PG7) */ /* configure LCD_R7(PG6), LCD_R6(PH12), LCD_R5(PH11), LCD_R4(PH10), LCD_R3(PH9),LCD_R2(PH8), LCD_R1(PH3), LCD_R0(PH2), LCD_G7(PI2), LCD_G6(PI1), LCD_G5(PI0), LCD_G4(PH15), LCD_G3(PH14), LCD_G2(PH13),LCD_G1(PE6), LCD_G0(PE5),LCD_B7(PI7), LCD_B6(PI6), LCD_B5(PI5), LCD_B4(PI4), LCD_B3(PG11),LCD_B2(PG10), LCD_B1(PG12), LCD_B0(PE4) */ /* configure TLI pins AF function */ gpio_af_set(GPIOE,GPIO_AF_14,GPIO_PIN_5); gpio_af_set(GPIOE,GPIO_AF_14,GPIO_PIN_6); gpio_af_set(GPIOE,GPIO_AF_14,GPIO_PIN_4); gpio_af_set(GPIOH,GPIO_AF_14,GPIO_PIN_2); gpio_af_set(GPIOH,GPIO_AF_14,GPIO_PIN_3); gpio_af_set(GPIOH,GPIO_AF_14,GPIO_PIN_8); gpio_af_set(GPIOH,GPIO_AF_14,GPIO_PIN_9); gpio_af_set(GPIOH,GPIO_AF_14,GPIO_PIN_10); gpio_af_set(GPIOH,GPIO_AF_14,GPIO_PIN_11); gpio_af_set(GPIOH,GPIO_AF_14,GPIO_PIN_12); gpio_af_set(GPIOH,GPIO_AF_14,GPIO_PIN_13); gpio_af_set(GPIOH,GPIO_AF_14,GPIO_PIN_14); gpio_af_set(GPIOH,GPIO_AF_14,GPIO_PIN_15); gpio_af_set(GPIOI,GPIO_AF_14,GPIO_PIN_0); gpio_af_set(GPIOI,GPIO_AF_14,GPIO_PIN_1); gpio_af_set(GPIOI,GPIO_AF_14,GPIO_PIN_2); gpio_af_set(GPIOI,GPIO_AF_14,GPIO_PIN_4); gpio_af_set(GPIOI,GPIO_AF_14,GPIO_PIN_5); gpio_af_set(GPIOI,GPIO_AF_14,GPIO_PIN_6); gpio_af_set(GPIOI,GPIO_AF_14,GPIO_PIN_7); gpio_af_set(GPIOI,GPIO_AF_14,GPIO_PIN_9); gpio_af_set(GPIOI,GPIO_AF_14,GPIO_PIN_10); gpio_af_set(GPIOG,GPIO_AF_14,GPIO_PIN_6); gpio_af_set(GPIOG,GPIO_AF_14,GPIO_PIN_7); gpio_af_set(GPIOG,GPIO_AF_14,GPIO_PIN_10); gpio_af_set(GPIOG,GPIO_AF_14,GPIO_PIN_11); gpio_af_set(GPIOG,GPIO_AF_14,GPIO_PIN_12); /* configure TLI GPIO */ gpio_mode_set(GPIOE, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_4|GPIO_PIN_5|GPIO_PIN_6); gpio_output_options_set(GPIOE, GPIO_OTYPE_PP, GPIO_OSPEED_200MHZ,GPIO_PIN_4|GPIO_PIN_5|GPIO_PIN_6); gpio_mode_set(GPIOH, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_2|GPIO_PIN_3|GPIO_PIN_8|GPIO_PIN_9|GPIO_PIN_10 |GPIO_PIN_11|GPIO_PIN_12|GPIO_PIN_13|GPIO_PIN_14|GPIO_PIN_15); gpio_output_options_set(GPIOH, GPIO_OTYPE_PP, GPIO_OSPEED_200MHZ,GPIO_PIN_2|GPIO_PIN_3|GPIO_PIN_8|GPIO_PIN_9 |GPIO_PIN_10|GPIO_PIN_11|GPIO_PIN_12|GPIO_PIN_13|GPIO_PIN_14|GPIO_PIN_15); gpio_mode_set(GPIOI, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_4 |GPIO_PIN_5|GPIO_PIN_6|GPIO_PIN_7|GPIO_PIN_9|GPIO_PIN_10); gpio_output_options_set(GPIOI, GPIO_OTYPE_PP, GPIO_OSPEED_200MHZ,GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_4 |GPIO_PIN_5|GPIO_PIN_6|GPIO_PIN_7|GPIO_PIN_9|GPIO_PIN_10); gpio_mode_set(GPIOG, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_6|GPIO_PIN_7|GPIO_PIN_10|GPIO_PIN_11|GPIO_PIN_12); gpio_output_options_set(GPIOG, GPIO_OTYPE_PP,GPIO_OSPEED_200MHZ, GPIO_PIN_6|GPIO_PIN_7|GPIO_PIN_10|GPIO_PIN_11|GPIO_PIN_12); /* LCD PWM BackLight(PB15) */ gpio_mode_set(GPIOB, GPIO_MODE_OUTPUT, GPIO_PUPD_PULLUP, GPIO_PIN_15); gpio_output_options_set(GPIOB, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ,GPIO_PIN_15); gpio_bit_set(GPIOB,GPIO_PIN_15); }   4-TLI初始化 设置HS、VS、DE几个引脚空闲状态高低电平状态     /* configure TLI parameter struct */     tli_init_struct.signalpolarity_hs = TLI_HSYN_ACTLIVE_LOW;     tli_init_struct.signalpolarity_vs = TLI_VSYN_ACTLIVE_LOW;     tli_init_struct.signalpolarity_de = TLI_DE_ACTLIVE_LOW;     tli_init_struct.signalpolarity_pixelck = TLI_PIXEL_CLOCK_TLI; 配置前后空闲时序,可以通过bpsz实现局部范围内的图像显示     /* LCD display timing configuration */     tli_init_struct.synpsz_hpsz = HORIZONTAL_SYNCHRONOUS_PULSE - 1;     tli_init_struct.synpsz_vpsz = VERTICAL_SYNCHRONOUS_PULSE - 1;     tli_init_struct.backpsz_hbpsz = HORIZONTAL_SYNCHRONOUS_PULSE + HORIZONTAL_BACK_PORCH - 1;     tli_init_struct.backpsz_vbpsz = VERTICAL_SYNCHRONOUS_PULSE + VERTICAL_BACK_PORCH - 1;     tli_init_struct.activesz_hasz = HORIZONTAL_SYNCHRONOUS_PULSE + HORIZONTAL_BACK_PORCH + ACTIVE_WIDTH - 1;     tli_init_struct.activesz_vasz = VERTICAL_SYNCHRONOUS_PULSE + VERTICAL_BACK_PORCH + ACTIVE_HEIGHT - 1;     tli_init_struct.totalsz_htsz = HORIZONTAL_SYNCHRONOUS_PULSE + HORIZONTAL_BACK_PORCH + ACTIVE_WIDTH + HORIZONTAL_FRONT_PORCH - 1;     tli_init_struct.totalsz_vtsz = VERTICAL_SYNCHRONOUS_PULSE + VERTICAL_BACK_PORCH + ACTIVE_HEIGHT + VERTICAL_FRONT_PORCH - 1; 设置背景颜色     /* configure LCD background R,G,B values */     tli_init_struct.backcolor_red = 0xFF;     tli_init_struct.backcolor_green = 0xFF;     tli_init_struct.backcolor_blue = 0xFF;     tli_init(&tli_init_struct);   5-TLI单一层显示 通过层的POS实现单个层在局部范围内显示,比如下面这个例子就是指显示X:20->160; Y:30->90 配置这些参数对应的是设置TLI_LxHPOS和TLI_LxVPOS寄存器实现,外部像素由TLI_LxDC决定,对应的就是下面的layer_default_xx字段     /* TLI window size configuration */     tli_layer_init_struct.layer_window_leftpos = 20 + HORIZONTAL_SYNCHRONOUS_PULSE + HORIZONTAL_BACK_PORCH;     tli_layer_init_struct.layer_window_rightpos = (20 + 140 + HORIZONTAL_SYNCHRONOUS_PULSE + HORIZONTAL_BACK_PORCH - 1);     tli_layer_init_struct.layer_window_toppos = 30 + VERTICAL_SYNCHRONOUS_PULSE + VERTICAL_BACK_PORCH;     tli_layer_init_struct.layer_window_bottompos = (30 + 60 + VERTICAL_SYNCHRONOUS_PULSE + VERTICAL_BACK_PORCH - 1); 设置像素格式RGB565     /* TLI window pixel format configuration */     tli_layer_init_struct.layer_ppf = LAYER_PPF_RGB565; 设置层的透明程度     /* TLI window specified alpha configuration */     tli_layer_init_struct.layer_sa = 255;  设置层混合模式【图】     /* TLI window blend configuration */     tli_layer_init_struct.layer_acf1 = LAYER_ACF1_PASA;     tli_layer_init_struct.layer_acf2 = LAYER_ACF2_PASA; 设置该层显示范围外的颜色     /* TLI layer default alpha R,G,B value configuration */     tli_layer_init_struct.layer_default_alpha = 0;     tli_layer_init_struct.layer_default_blue = 0xFF;     tli_layer_init_struct.layer_default_green = 0xFF;     tli_layer_init_struct.layer_default_red = 0xFF; 设置缓冲区及缓冲区数据组织形式,每行多少数据、多少行。     /* TLI layer frame buffer base address configuration */     tli_layer_init_struct.layer_frame_bufaddr = (uint32_t)&gImage_0;     tli_layer_init_struct.layer_frame_line_length = ((140 * 2) + 3);     tli_layer_init_struct.layer_frame_buf_stride_offset = (140 * 2);     tli_layer_init_struct.layer_frame_total_line_number = 60;     tli_layer_init(LAYER1, &tli_layer_init_struct);   6-开防抖动     tli_dither_config(TLI_DITHER_ENABLE);   可以看到,虽然数据手册里说了内部有DMA发送数据,但实际配置的时候是不需要额外配置DMA的,     tli_layer_init_struct.layer_frame_bufaddr = (uint32_t)&gImage_0; 这句实际上是把缓冲区基地址传进了一个寄存器就完事了   (四)TLI测试 例程中实现了两个层叠加显示,我就不再重复了,关键是要实现两个层轮流单独显示 1-使能层显示     tli_layer_enable(LAYER0);     tli_reload_config(TLI_REQUEST_RELOAD_EN);     tli_enable(); 2-在500ms周期轮流显示两个图案 static __IO uint32_t timingdelaylocal = 0U; if(timingdelaylocal){ if(timingdelaylocal < 500U){ gd_eval_led_on(LED1); tli_layer_disable(LAYER1); tli_layer_enable(LAYER0); tli_reload_config(TLI_REQUEST_RELOAD_EN); }else{ gd_eval_led_off(LED1); tli_layer_disable(LAYER0); tli_layer_enable(LAYER1); tli_reload_config(TLI_REQUEST_RELOAD_EN); } timingdelaylocal--; }else{ timingdelaylocal = 1000U; }     (五)效果

  • 发表了主题帖: 【GD32450I-EVAL】开箱上电,搭建开发环境

    本帖最后由 tinnu 于 2020-9-13 23:08 编辑 (一)GD32F4 GD32F4系列是我知道的国内最早的M4F内核单片机了,另外的几个系列诸如F3、E1都是不带硬件FPU的M4内核,在一些运算的场景上劣势相当明显,并且F450主频高达200MHz,在那些跨界处理器出来之前已经是主频最巅峰的单片系统了。   (二)GD32450I-EVAL 先上一张拆机照,本来我并不想拆屏幕下来的,因为每次拔排阵都容易弄弯针脚,可是当开发触摸的时候我才发现开发板手册里根本就没有触摸芯片的信息…… 1-板子上没有直接引出串口,也没有转成USB,而是转成232,用的是MAX3232芯片,只是我手上一时间竟没有母头转母头的232,串口实验没得做。 2-板载的GD-Link仿真器,一如既往的只有仿真功能,虚拟串口没有带上 3-SDRAM是一个16M的镁光48LC16M,中规中矩,大部分板子的外置SDRAM都是这类配置 4-轮到外置nandflash着实被震惊了一下,型号是GD9FU1G8F3A,兆易创新自加的1Gb nandflash,自家的东西用得就是豪爽啊,这时候就想起来兆易的主页本来就是flash…… 5-此外还有一个SPI flash,也是自家的产品,只要你的程序不是惊天动地,都不会不够地方放…… 6-其余还有TF卡槽、网口、DCI摄像头,可以说一应俱全,应有尽有啊 7-最重要的还是RGB接口的屏幕,国内基本见不到其他厂商的单片机带着个接口的,印象中华芯微特好像有,但他们家的M4统统不带FPU,剩下就是新塘的M481了。   本来有张光碟的,可是这年头哪去找个光驱呢……   (三)上电 一拿到手的时候上电还有些曲折,往GD-Link插USB,没反应,USB FS,也没反应。一度怀疑是不是刚拿到手就GG了。 不过仔细看了看手册,发现JP4要跳码,选择供电方式…… 一上电,是个摄像头显示的demo,按下按键还能拍照,不过看这成像质量不太好的样子,不知道是不是demo哪里还需要优化   (四)下载固件库、MDK的ADDON http://www.gd32mcu.com/cn/download/7?kw=GD32F4   (五)配置开发环境 添加ADDON之后创建工程,把下图文件拷到目录下: 添加路径: .\Application;.\Utilities;.\Lib\Include;.\CMSIS\Include;.\CMSIS;.\Application\littlevgl 添加宏定义: USE_STDPERIPH_DRIVER,GD32F450 我所使用的是一个通过systick中断闪烁 LED 的工程,下载之后板载LED1以1s为周期闪烁。   官方原本的工程挺神奇的,连target页下面的system viewer file都是自己定位的,即使没有安装addon都能够正常编译下载,看见有个应用手册(GD32F103xx-AN001-Software-Migration-Guide.pdf)说是因为以前MDK不支持F4,只能通过这种方式配置。 不过即便安装了addon也是找不到GD的器件的。   补充内容 (2020-10-6 20:38): 订正一下,这个镁光48LC16M的SDRAM是256M的……

  • 2020-09-04
  • 回复了主题帖: 【测评入选名单公布】GD32450I-EVAL免费测评试用

    已确认个人信息,并确定可以按时完成测评

  • 2020-08-18
  • 回复了主题帖: stm32F412-discovery探索板移植裸机littlevgl-v7

    RCSN 发表于 2020-8-17 17:01 V7版本的确实比V6的好看不少,炫了不少,接口也有些许改变
    是啊,丰富了很多,一开始在NXP那边用5.4来着,很多接口都没有,就去移植了个最新的

  • 回复了主题帖: stm32F412-discovery探索板移植裸机littlevgl-v7

    freebsder 发表于 2020-8-17 22:20 littlevgl标配了吗?我看nxp的sdk现在已经打包在一起了。
    很火啊,我看有张github开源ui的star表,LittleVGL和GUILITE这两个火箭式窜起,快要赶超老大openGFX了,我觉得主要还是移植简单,stemwin我移植了好久都不行…… NXP那边是集成了,我就是从NXP那边认识LittleVGL的,不过NXP集成的那个是5.4版本的,非常落后,如果你用原子的教程会发现基本例程都不能用,很多接口都没有,我给NXP的LPC54628移植过V7版本的: https://blog.csdn.net/qq_34917736/article/details/107738432 就是NXP那边移植得挺顺利的就准备把手上带屏幕的都移植一下

最近访客

< 1/4 >

统计信息

已有66人来访过

  • 芯币:109
  • 好友:2
  • 主题:30
  • 回复:31
  • 课时:--
  • 资源:--

留言

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


liangtom125 2020-6-16
加个微信?
查看全部