tinnu

  • 2020-10-28
  • 回复了主题帖: 关于逻辑分析仪设计的一些事

    w494143467 发表于 2020-10-26 22:00 那这样,上位机开发还比较麻烦,那协议有什么意见吗?
    逻辑分析仪上位机还是有些开源的软件吧可以参考下人家上位机接受走什么协议,加入别人的生态就最好了,其实逻辑分析仪的上位机最麻烦的反而是分析这部分

  • 回复了主题帖: 关于逻辑分析仪设计的一些事

    w494143467 发表于 2020-10-27 20:09 查了一下这个开发板,好像某宝上没有。。。
    EBAZ4205是矿卡FPGA,主控是ZYNQ7010 XILINX的FPGA芯片,这个要上某鱼才有。官方开发工具有个逻辑分析功能,老实说比较麻烦,而且没有波形协议分析,逻辑分析仪这玩意,除非需求超过8通道和24M,不然某宝上30来块的玩意就足够了,自己做还不一定能做到这个级别,就算做到了软件适配也是个问题。

  • 2020-10-26
  • 回复了主题帖: 关于逻辑分析仪设计的一些事

    串口速度不够吧,一般都走USB,而且还是HS的,FS的都拉垮

  • 回复了主题帖: 大家平时都用几个电脑屏幕?

    3个,感觉四面八方有股实质性的辐射冲来。都是便宜的便携屏幕,一个是刚开始工作时买的,150一套,TN屏,有点苍白辣眼,后来又买了个IPS 72色欲,一百度环绕,应用全摊在桌面,就是经常找不到鼠标,最近想用单片机DIY个鼠标定位器,用USB+HID,一键定位回屏幕中央

  • 2020-10-25
  • 回复了主题帖: 大家猜猜这是什么开发板

    这个可以掰开的结构有点熟悉,好像是NB还是CAT 4G的,反正是合宙之后出来的,当时票了合宙的CAT 4G板子之后对它就兴致缺缺了,话说合宙的板子还躺这积灰不知道啥时候才有时间玩

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

    补充个视频

  • 发表了主题帖: 【GD32450I-EVAL】USART与DMA不定长接收

    本帖最后由 tinnu 于 2020-10-25 23:18 编辑 (一)使能DMA 例程中推荐的DMA发送USART的模式: void USART_DMA_Init(void) { dma_single_data_parameter_struct dma_init_struct; rcu_periph_clock_enable(RCU_DMA1); dma_deinit(DMA1, DMA_CH2); dma_init_struct.direction = DMA_PERIPH_TO_MEMORY; dma_init_struct.memory0_addr = (uint32_t)rxbuffer; dma_init_struct.memory_inc = DMA_MEMORY_INCREASE_ENABLE; dma_init_struct.number = 10; dma_init_struct.periph_addr = (uint32_t)&USART_DATA(USART0); dma_init_struct.periph_inc = DMA_PERIPH_INCREASE_DISABLE; dma_init_struct.periph_memory_width = DMA_PERIPH_WIDTH_8BIT; dma_init_struct.priority = DMA_PRIORITY_ULTRA_HIGH; dma_single_data_mode_init(DMA1, DMA_CH2, &dma_init_struct); dma_channel_subperipheral_select(DMA1, DMA_CH2, DMA_SUBPERI4); /* enable DMA channel2 */ dma_channel_enable(DMA1, DMA_CH2); usart_dma_receive_config(USART0, USART_DENR_ENABLE); }   (二)问题1 但根据实际测试,这个模式在发送完缓冲区10个数据之后,如果不及时重启DMA的话会导致溢出。 针对溢出,在参考手册507页有这样的一段话: 当接收到一帧数据,而RBNE位还没有被清零,随后的数据帧将不会存储在数据接收缓冲区中。 USART_STAT0 寄存器中的溢出错误标志位 ORERR 将置位。如果使能 DMA 并置位 USART_CTL2寄存器中ERRIE位或者置位RBNEIE,将产生中断。 这样还得去处理 ORERR 这个错误标志位,非常繁琐。     (三)空闲中断 可以考虑用空闲中断来处理,我们默认每次空闲中断触发后都重启DMA : 使能空闲中断: usart_interrupt_enable(USART0, USART_INT_IDLE); nvic_irq_enable(USART0_IRQn, 2U, 0U); 处理函数: void USART0_IRQHandler() { static uint32_t g_counter1 = 0; if (RESET != usart_flag_get(USART0, USART_FLAG_IDLE)) { usart_flag_clear(USART0, USART_FLAG_IDLE); g_counter2++; USART_DMA_Init(); } }   (四)问题2 上述方法依然存在问题: 每次完成空闲中断都会触发好多次,这可能是兆易方面IP核的策略,针对这个问题可以通过查询当前DMA未发送的位还有多少来解决,如果一个位都没有发送,那就认为不需要重启。 因为空闲中断不是每个bit都会中断的,而是一串bit完成之后,多少时间内没有新的bit出现才会置位一段时间的 IDLE 标志位,我猜这也是为什么IDLE标志位会持续触发而无法被软件清除的原因。 void USART0_IRQHandler() { static uint32_t g_counter1 = 0; if (RESET != usart_flag_get(USART0, USART_FLAG_IDLE)) { usart_flag_clear(USART0, USART_FLAG_IDLE); if (dma_transfer_number_get(DMA1, DMA_CH2) == 10) return; g_counter2++; USART_DMA_Init(); } } 这样就解决了。事实上也可以通过查询缓存中的数据量,数据内容,在没有满的条件下重启。

  • 回复了主题帖: 【GD32450I-EVAL】简易示波演示

    w494143467 发表于 2020-10-12 14:32 感觉还不错,就是屏幕的UI设计的不太美观哈,功能挺不错的,但是这个个示波器的频率应该不会太高,可以简单 ...
    其实一开始是想用 littlevgl 来显示,那就漂亮了,可是看着看着忽然发现用 littlevgl 的图标刷新率太感人了……那个刷新率做出来也没意义

  • 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");   效果:

最近访客

< 1/4 >

统计信息

已有68人来访过

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

留言

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


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