开发板 时间 同步_近万字试用报告!RISCV开发板GD32VF103-程序员宅基地

技术标签: 开发板 时间 同步  选择的串口 _ 不存在或开发板没有连接  

f2f4ff0a70832439a1fd7a89f4c6353e.png

作者:yinwuqing

来源:EEPW论坛

01 开箱测评

周六继续上班,明天调班接着上,今天来给大家秀一下开箱靓照。开发板是昨天下班收到的,顺丰快递包装还是挺周到的,里面装好了空气袋,防止运输中撞坏。

然后摘开空气袋,有一个精美的盒子,上面标识兆易创新公司GD32与RISC-V的logo,猎豹很健壮,像是在飞奔着。

b611f5d11f72ee0efda48c4c9c8752ae.png

然后打开盒子,里面有我们此次用到的核心开发板,板卡属于入门RISC-V的基本类型,采用3个mini usb接口与PC端进行通讯,还配送了1根mini usb接口的数据线,这也许是后续不需要同时对这3个mini接口进行通讯吧。

67f60721578f6c5f6bcae4329e07d21d.png

正面靓照如下所示:

eabbdd0c8751f49ed12babf148db7c15.png

然后迫不及待的将排针焊接上去(当然这是自备的排针),为后续使用IO口做好准备。

61f38b00a68d61613a277430f1941f7c.png

再来一张焊接后背面的痕迹:

b9752ad130a3bf16c526ca2763c644ad.png

此块开发板比我想象的要小很多,没有带OLED屏,板卡四个角有预留铜柱孔,方便采用铜柱垫高,以免底面与金属物导通造成短路。该板卡自带GD-Link模块,因此无需采用第三方调试下载工具,给GD32F103C8T6来张特写。

4f7fb06564b10f40a4b5f1be34bf0e02.png

接下来我们需要通过官方给定的SDK包,了解RISC-V架构的知识,熟悉Nuclei Studio集成开发工具的使用。Nuclei Studio是芯来科技基于Eclipse开发的一款支持RISC-V的IDE,貌似操作起来与Keil还是有点区别的哦。再回眸一下我们的核心主控MCU:

7598139d7229efe908b26f3415600b82.png

02 简单操作-点灯

五一小长假不知道大伙有没有出行,疫情期间,还是居家调调开发板比较靠谱。今天与大家来分享一下入门的点灯实验。

首先我们从GD32的官网下载好针对GD32VF103开发的资源包,解压后在“GD32VF103\GD32VF103_Demo_Suites_V1.0.3\GD32VF103C_START_Demo_Suites\Docs\User Guide”目录下有《GD32VF103C-START评估板用户指南_V1.1》,我们可以根据文档来了解常用接口硬件原理,方便进一步实施我们的第一个小实验。

搭建基于芯来科技的NucleiStudio集成开发环境,首先我们得在“NucleiStudio_IDE_201909”目录下安装“jdk-8u152-windows-x64.exe”,安装jdk、jre后才能进入NucleiStudio目录下打开eclipse应用,当然前提是你的电脑之前没有安装过java开发环境。

b8cb355065777d1c09164d0365ce1bec.png

然后进入NucleiStudio目录下打开eclipse应用,首先设置我们将来创建工程的保存路径,最好不要选择默认C盘路径,增加系统盘的负担,我这里选择到F盘。

d216f8655e06ff4b9df45982b5b06b5a.png

然后新建工程,选择创建C工程,如下图所示:

9d96a65ac1c8d76e614d6fd1c19bbd09.png

然后再给工程命名,该工程将会保存到上述软件启动时弹出的设置工作目录当中。

931c4f831e487f15f0e6b58385cc65b6.png

选择好RISC-V C工程后,一路默认选择即可。

04dfb4f057f4a844da2eb482ee49a4e4.png

最后直到Finish按钮不再是灰色,点击Finish按钮,打开新创建的工程,直击小锤子编译一下。

2aef6aced0266160622dcc369ebbaf91.png

没有错误,没有警告,说明工程创建没问题。然后来看看开发板上的LED1灯硬件电路原理图:

28aa16a25e8b72311e46d28332008a5e.png

由此可知,LED1与MCU的GPIOA_7相连接,因此需要在工程中修改相对应的代码。

4e7688340eb869756d134b9d218331e7.png

在Main函数中,将自动生成原始工程的四盏灯控制改成一盏灯,编译、下载。

198f8d4063d5b872437a231a7bf73b4f.png

然后我们能看到控制打印台会有下载的进度条和打印信息,log对话框中显示红色字体属正常情况,习惯使用Keil工具的伙计们可能会感到惊讶,还以为是报错了。

0c823e70071243cc2afe3ff6a3f674a4.png

熟悉芯来科技的NucleiStudio集成开发工具需要慢慢养成习惯,经常使用eclipse开发的java工程师应该比较熟练咯,此次实验实现控制LED1灯以500ms的频率在不停的闪烁,现象结果如下。

1ff99aba76ef78086238dcef8d017566.gif

03 串口功能检测

根据前段时间老师的讲解,今天来分享一下基于串口的基本功能检测。首先我们需要了解一下USART的常用理论知识。通用同步异步收发器 (USART)是用于串行数据交换提供接口,数据帧可以通过全双工或半双工,同步或异步的方式进行传输。USART提供了可编程的波特率发生器,能对系统时钟进行分频产生USART发送器和接收器所需的特定频率。USART支持DMA功能,用以实现高速率的数据通信。

USART的重要引脚描述如下:

4eba52df63a2c57d148a5cc724bea80f.png

USART模块内部框图如下:

9c727edc375ad5f5b12e082fe16287ec.png

关于USART的发送器与接收器、同步通讯时序波形这里就不再赘述。demo工程中也提供了封装好的库函数,至于USART中的状态寄存器、数据寄存器、波特率寄存器、控制寄存器、保护时间和预分频器寄存器,在代码中也有相关的英文注释,这里也不再做过多讲解。关于USART的库函数汇总如下:

c3f47c57d878f41eb0a0501cef1e70d3.png

按照上回LED灯的简单操作,重新创建新的C工程,然后在主函数中,敲入代码:

int main(void){
     uint data = 'a';uint i;gd_eval_led_init(LED1);gd_eval_com_init(EVAL_COM0);while(1){
     gd_eval_led_on(LED1);for(i=0;i<26;i++){
     usart_data_transmit(EVAL_COM0,data);while(usart_flag_get(EVAL_COM0,USART_FLAG_TC)==RESET);data++;usart_data_transmit(USART0,0X0A);while(usart_flag_get(USART0,USART_FLAG_TC)==RESET);usart_data_transmit(USART0,0X0D);while(usart_flag_get(USART0,USART_FLAG_TC)==RESET);delay_1ms(300);}gd_eval_led_off(LED1);delay_1ms(500);data = 'a';}}

然后在NucleiStudio_IDE中点击编译、调试即下载到开发板中。编译通过,无报错,无警告。

f843fb8cfddadcb92cf7d466035d6070.png

将开发板的串口插针用跳线帽连接,并使用mini USB数据线与PC端连接,接线如下图所示:

7f6326f39187e8e30e530374b3c1365f.png

此时打开电脑的设备管理器,如果已经安装了CH340的USB转串口的驱动,则会显示相对应的串口号,如果没有则显示黄色的叹号,需要安装CH340的驱动。我的串口号显示的是10,因此在SecureCRT软件中打开串口10。

36c593b287acbbbce37d8fc3493e9918.png

SecureCRT中设置如下参数:

eab20f866f63a837ee7be24ed4746f2e.png

然后点击连接,则会打印如下信息

b442920e66111e106c3499538abc3c68.png

同样的办法,在main函数中,修改部分代码,实现通过串口工具发送数据给GD32VF103,然后GD32VF103将数据返回给PC端。

int main(void){
     uint i;uint data;gd_eval_led_init(LED1);gd_eval_com_init(EVAL_COM0);while(1){
     gd_eval_led_on(LED1);if(usart_flag_get(USART0,USART_FLAG_RBNE)!= RESET){
     data = usart_data_receive(USART0);usart_data_transmit(USART0,data);while(usart_flag_get(USART0,USART_FLAG_TC)==RESET);usart_data_transmit(USART0,0X0A);while(usart_flag_get(USART0,USART_FLAG_TC)==RESET);usart_data_transmit(USART0,0X0D);while(usart_flag_get(USART0,USART_FLAG_TC)==RESET);}gd_eval_led_off(LED1);delay_1ms(500);}}

然后开发板重新上电,代码编译ok,运行下载到开发板中,打开SSCOM V5.13.1串口调试助手,这里说明一下,如使用SecureCRT软件则现象不是很明显,因为它的发送窗口与输出窗口在同一界面下,因此建议采用SSCOM工具,而且不支持十六进制显示的低版本效果也不是很好,这里推荐V5.13.1版本。通过键盘在发送窗口输入相应字符,则会收到GD32VF103返回的相同字符,并加上换行回车字符。

bd2c2db20a774262ce3108026162f8b3.png

提供ASCII码对照表如下:

a635034f7aeda54c13c94409681a033e.png

根据官方提供的参考示例,关于串口的操作还有DMA应用,串口中断响应的使用。总而言之,串口是比较常见的通信接口方式,根据其通信协议,同步、异步的特性,熟悉相关寄存器的设置是非常必要的。

04 ADC功能检测

今天周末,抽点时间研究了一下GD32VF103的ADC检测功能,结合视频讲解,简单谈谈ADC。ADC即模数转换,12位ADC是一种采用逐次逼近方式的模拟数字转换器。GD32VF103有18个多路复用通道,可以转换来自16个外部通道和2个内部通道的模拟信号。模拟看门狗允许应用程序来检测输入电压是否超出用户设定的高低阈值。各种通道的A/D转换可以配置成单次、连续、扫描或间断转换模式。ADC转换的结果可以按照左对齐或右对齐的方式存储在16位数据寄存器中。片上的硬件过采样机制可以通过减少来自MCU的相关计算负担来提高性能。

ADC模块框图如下图所示:

0eb21c203ab5eeb2cdfcbb7b88ca584d.png

ADC带有一个前置校准功能。在校准期间,ADC计算一个校准系数,这个系数是应用于ADC内部的,它直到ADC下次掉电才无效。在校准期间,应用不能使用ADC,它必须等到校准完成。在A/D转换前应执行校准操作。通过软件设置CLB=1来对校准进行初始化,在校准期间CLB 位会一直保持1,直到校准完成,该位由硬件清0。当ADC运行条件改变后建议重新执行一次校准操作。内部的模拟校准通过设置ADC_CTL1寄存器的RSTCLB位来重置。

关于ADC的信号同步,在有两个或者两个以上的ADC模块的产品中,可以使用ADC同步模式。在ADC同步模式下,根据ADC_CTL0寄存器中SYNCM[3:0]位所选的模式,转换的启动可以是ADC0主和ADC1从的交替触发或同步触发。在同步模式下,当配置由外部事件触发的转换时,从ADC必须通过软件来配置触发来,从而避免错误的触发引起不必要的转换。此外,对于主ADC和从ADC的外部触发必须被使能。

共有以下几种模式:

  • 独立模式

  • 规则并行模式注

  • 入并行模式快速

  • 交叉模式

  • 慢速交叉模式

  • 交替触发模式

  • 注入并行模式 规则并行模式

  • 规则并行模式 交替触发模式

  • 注入并行模式 交叉模式

在ADC同步模式下,即使DMA不用,也要将DMA置位,从ADC的转换数据可以通过主ADC 数据寄存器读取。ADC同步框图如下:

4e8470843d2a7e609df1dd089b37447b.png

关于ADC的寄存器如下:

d65e9c6cbf848a9af3351038535e3f41.png

ADC库函数如下:

6a9110647c8b1913d2467ab16a57c1eb.png

在原来的串口检测工程中,添加部分代码。

2e9da1b9af6ebeb9da8df4f3daeaa3ce.png

#include "gd32vf103.h"#include "gd32vf103v_eval.h"#include "systick.h"#include uint32_t adc_value[2];void rcu_config(void);void gpio_config(void);void dma_config(void);void timer_config(void);void adc_config(void);int main(void){
     uint i;uint data;/*配置系统时钟*/rcu_config();/*配置GPIO口 */gpio_config();/*配置TIMER*/timer_config();/*配置DMA*/dma_config();/*配置ADC*/adc_config();/*使能TIMER1*/timer_enable(TIMER1);gd_eval_led_init(LED1);/*配置EVAL_COM0*/gd_eval_com_init(EVAL_COM0);while(1){
     gd_eval_led_on(LED1);printf("ADC0 regular data0 = %d \r\n",adc_value[0]);delay_1ms(200);printf("ADC0 regular data0 = %4X \r\n",adc_value[0]);delay_1ms(200);printf("ADC0 regular data1 = %d \r\n",adc_value[1]);delay_1ms(200);printf("ADC0 regular data1 = %4X \r\n",adc_value[1]);delay_1ms(200);if(usart_flag_get(USART0,USART_FLAG_RBNE)!= RESET){
     data = usart_data_receive(USART0);usart_data_transmit(USART0,data);while(usart_flag_get(USART0,USART_FLAG_TC)==RESET);usart_data_transmit(USART0,0X0A);while(usart_flag_get(USART0,USART_FLAG_TC)==RESET);usart_data_transmit(USART0,0X0D);while(usart_flag_get(USART0,USART_FLAG_TC)==RESET);}gd_eval_led_off(LED1);delay_1ms(500);}}void rcu_config(void){
     /* enable GPIOA clock */rcu_periph_clock_enable(RCU_GPIOA);/* enable ADC0 clock */rcu_periph_clock_enable(RCU_ADC0);/* enable DMA0 clock */rcu_periph_clock_enable(RCU_DMA0);/* enable timer1 clock */rcu_periph_clock_enable(RCU_TIMER1);/* config ADC clock */rcu_adc_clock_config(RCU_CKADC_CKAPB2_DIV8);}void gpio_config(void){
     /* config the GPIO as analog mode */gpio_init(GPIOA, GPIO_MODE_AIN, GPIO_OSPEED_50MHZ, GPIO_PIN_1);}void dma_config(void){
     /* ADC_DMA_channel configuration */dma_parameter_struct dma_data_parameter;/* ADC DMA_channel configuration */dma_deinit(DMA0, DMA_CH0);/* initialize DMA data mode */dma_data_parameter.periph_addr  = (uint32_t)(&ADC_RDATA(ADC0));dma_data_parameter.periph_inc   = DMA_PERIPH_INCREASE_DISABLE;dma_data_parameter.memory_addr  = (uint32_t)(&adc_value);dma_data_parameter.memory_inc   = DMA_MEMORY_INCREASE_DISABLE;dma_data_parameter.periph_width = DMA_PERIPHERAL_WIDTH_16BIT;dma_data_parameter.memory_width = DMA_MEMORY_WIDTH_16BIT;dma_data_parameter.direction    = DMA_PERIPHERAL_TO_MEMORY;dma_data_parameter.number       = 1;dma_data_parameter.priority     = DMA_PRIORITY_HIGH;dma_init(DMA0, DMA_CH0, &dma_data_parameter);dma_circulation_enable(DMA0, DMA_CH0);/* enable DMA channel */dma_channel_enable(DMA0, DMA_CH0);}void timer_config(void){
     timer_oc_parameter_struct timer_ocintpara;timer_parameter_struct timer_initpara;/* TIMER1 configuration */timer_struct_para_init(&timer_initpara);timer_initpara.prescaler               = 5399;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);/* CH1 configuration in PWM mode1 */timer_channel_output_struct_para_init(&timer_ocintpara);timer_ocintpara.ocpolarity  = TIMER_OC_POLARITY_HIGH;timer_ocintpara.outputstate = TIMER_CCX_ENABLE;timer_channel_output_config(TIMER1, TIMER_CH_1, &timer_ocintpara);timer_channel_output_pulse_value_config(TIMER1, TIMER_CH_1, 3999);timer_channel_output_mode_config(TIMER1, TIMER_CH_1, TIMER_OC_MODE_PWM1);timer_channel_output_shadow_config(TIMER1, TIMER_CH_1, TIMER_OC_SHADOW_DISABLE);}void adc_config(void){
     /* reset ADC */adc_deinit(ADC0);/* ADC mode config */adc_mode_config(ADC_DAUL_REGULAL_FOLLOWUP_FAST);/* ADC continous function enable */adc_special_function_config(ADC0, ADC_SCAN_MODE, ENABLE);/* ADC data alignment config */adc_data_alignment_config(ADC0, ADC_DATAALIGN_RIGHT);/* ADC channel length config */adc_channel_length_config(ADC0, ADC_REGULAR_CHANNEL, 1);/* ADC regular channel config */adc_regular_channel_config(ADC0, 0, ADC_CHANNEL_1, ADC_SAMPLETIME_55POINT5);/* ADC trigger config */adc_external_trigger_source_config(ADC0, ADC_REGULAR_CHANNEL, ADC0_1_EXTTRIG_REGULAR_T1_CH1);/* ADC external trigger enable */adc_external_trigger_config(ADC0, ADC_REGULAR_CHANNEL, ENABLE);/* enable ADC interface */adc_enable(ADC0);/* ADC calibration and reset calibration */adc_calibration_enable(ADC0);delay_1ms(1);/* ADC DMA function enable */adc_dma_mode_enable(ADC0);}

串口打印信息如下:

b8a7527053b0c412958c17657f5f4d86.png

接线还是与之前一样。

43c11675b2134da5dfaebc27bd04fc1d.png

通过测试ADC通道0的数据采样,说明GD32VF103可配置12位、10位、8位或者6位分辨率,支持自校准,可编程采样时间,数据寄存器可配置数据对齐方式,支持规则数据转换的DMA请求。转换开始的发起有软件与硬件触发。

05 SPI与I2C通信

▼ 5.1 SPI通信

SPI模块可以通过SPI协议与外部设备进行通信。串行外设接口提供了基于SPI协议的数据发送和接收功能可以工作于主机或从机模式。SPI接口支持具有硬件CRC计算和校验的全双工和单工模式。

SPI主要特征如下:

  • 具有全双工和单工模式的主从操作;

  • 16位宽度,独立的发送和接收缓冲区;

  • 8位或16位数据帧格式;

  • 低位在前或高位在前的数据位顺序;

  • 软件和硬件NSS管理;

  • 硬件CRC计算、发送和校验;

  • 发送和接收支持DMA模式;

  • 支持SPI TI模式;

  • 支持SPI NSS脉冲模式。

SPI结构框图如下:

a6a53bd7dd3a8bc38d67ca24e38db8a3.png

SPI信号线描述详情如下:

1cf7c46f4621605339242f87f387b6da.png

典型的工作模式概括如下:

f7854de1755db1cc9ad25585ffd7e220.png

(1)发送流程:

在完成初始化过程之后,SPI模块使能并保持在空闲状态。在主机模式下,当软件写一个数据到发送缓冲区时,发送过程开始。在从机模式下,当SCK引脚上的SCK信号开始翻转且NSS引脚电平为低发送过程开始。所以在从机模式下,应用程序必须确保在数据发送开始前,数据已经写入发送缓冲区中。

当SPI开始发送一个数据帧时,首先将这个数据帧从数据缓冲区加载到移位寄存器中,然后开始发送加载的数据。在数据帧的第一位发送之后,TBE(发送缓冲区空)位置1。TBE标志位置1说明发送缓冲区为空,此时如果需要发送更多数据,软件应该继续写SPI_DATA寄存器。在主机模式下,若想要实现连续发送功能那么在当前数据帧发送完成前软件应该将下一个数据写入SPI_DATA寄存器中。

(2)接收流程:

在最后一个采样时钟边沿之后接收到的数据将从移位寄存器存入到接收缓冲区且RBNE(接收缓冲区非空)位置1。软件通过读SPI_DATA寄存器获得接收的数据,此操作会自动清除RBNE标志位。在MRU和MRB模式中,为了接收下一个数据帧,硬件需要连续发送时钟信号,而在全双工主机模式(MFD)中仅当发送缓冲区非空时,硬件才接收下一个数据帧。

SPI的寄存器列表如下:

6e3ce29d4baa9b8991d27e8ff614520a.png

SPI的库函数列表如下:

c50f8a74cacba1cddce29adbac9332a6.png

SPI流程如下:

  • 初始化时钟

  • 配置管脚

  • cs管脚配置

  • spi参数配置

  • 是否使能crc

  • 使能spi

  • spi发送/接收

部分代码如下:

void spi_struct_para_init(spi_parameter_struct* spi_struct){
     /* set the SPI struct with the default values */spi_struct->device_mode = SPI_SLAVE;spi_struct->trans_mode = SPI_TRANSMODE_FULLDUPLEX;spi_struct->frame_size = SPI_FRAMESIZE_8BIT;spi_struct->nss = SPI_NSS_HARD;spi_struct->clock_polarity_phase = SPI_CK_PL_LOW_PH_1EDGE;spi_struct->prescale = SPI_PSC_2;}void spi_init(uint32_t spi_periph, spi_parameter_struct* spi_struct){
     uint32_t reg = 0U;reg = SPI_CTL0(spi_periph);reg &= SPI_INIT_MASK;/* select SPI as master or slave */reg |= spi_struct->device_mode;/* select SPI transfer mode */reg |= spi_struct->trans_mode;/* select SPI frame size */reg |= spi_struct->frame_size;/* select SPI NSS use hardware or software */reg |= spi_struct->nss;/* select SPI LSB or MSB */reg |= spi_struct->endian;/* select SPI polarity and phase */reg |= spi_struct->clock_polarity_phase;/* select SPI prescale to adjust transmit speed */reg |= spi_struct->prescale;/* write to SPI_CTL0 register */SPI_CTL0(spi_periph) = (uint32_t)reg;SPI_I2SCTL(spi_periph) &= (uint32_t)(~SPI_I2SCTL_I2SSEL);}void spi_enable(uint32_t spi_periph){
     SPI_CTL0(spi_periph) |= (uint32_t)SPI_CTL0_SPIEN;}void spi_disable(uint32_t spi_periph){
     SPI_CTL0(spi_periph) &= (uint32_t)(~SPI_CTL0_SPIEN);}uint16_t spi_crc_get(uint32_t spi_periph,uint8_t crc){
     if(SPI_CRC_TX == crc){
     return ((uint16_t)(SPI_TCRC(spi_periph)));}else{
     return ((uint16_t)(SPI_RCRC(spi_periph)));}}void spi_ti_mode_enable(uint32_t spi_periph){
     SPI_CTL1(spi_periph) |= (uint32_t)SPI_CTL1_TMOD;}void spi_ti_mode_disable(uint32_t spi_periph){
     SPI_CTL1(spi_periph) &= (uint32_t)(~SPI_CTL1_TMOD);}void spi_crc_error_clear(uint32_t spi_periph){
     SPI_STAT(spi_periph) &= (uint32_t)(~SPI_FLAG_CRCERR);}

▼ 5.2 I2C通信

接下来聊聊I2C控制总线。I2C(内部集成电路总线)模块提供了符合工业标准的两线串行制接口,可用于MCU和外部I2C设备的通讯。I2C总线使用两条串行线:串行数据线SDA和串行时钟线SCL。I2C接口模块实现了I2C协议的标速模式,快速模式以及快速模式,具备CRC计算和校验功能,支持SMBus(系统管理总线)和PMBus(电源管理总线)。此外还支持多主机I2C总线架构。I2C接口模块也支持DMA模式,可有效减轻CPU的负担。

I2C主要特征如下:

  • 并行总线至I2C总线协议的转换及接口;

  • 同一接口既可实现主机功能又可实现从机功能;

  • 主从机之间的双向数据传输;

  • 支持7位和10位的地址模式和广播寻址;

  • 支持I2C多主机模式;

  • 支持标速(最高100kHz),快速(最高400kHz)和快速模式(最高1MHz);

  • 从机模式下可配置的SCL主动拉低;

  • 支持DMA模式;

  • 兼容SMBus2.0和PMBus;

  • 两个中断:字节成功传输中断和错误事件中断;

  • 可选择的PEC(报文错误校验)生成和校验。

I2C结构框图如下:

5907676ef490f3e272a955ddc2bc63d2.png

I2C总线术语:

464d383ca4545bcabc3210ba425594e0.png

I2C模块有两条接口线:串行数据SDA线和串行时钟SCL线。连接到总线上的设备通过这两根线互相传递信息。SDA和SCL都是双向线,通过一个电流源或者上拉电阻接到电源正极。当总线空闲时,两条线都是高电平。连接到总线的设备输出极必须是开漏或者开集,以提供线与功能。I2C总线上的数据在标准模式下可以达到100Kbit/s,在快速模式下可以达到400Kbit/s,当I2C_FMPCFG寄存器中FMPEN位被置位时,在快速模式下可达1Mbit/s。由于I2C总线上可能会连接不同工艺的设备,逻辑‘0’和逻辑‘1’的电平并不是固定的,取决于VDD的实际电平。

所有的数据传输起始于一个START结束于一个STOP。START起始位定义为S,在SCL为高时,SDA线上出现一个从高到低的电平转换。STOP结束位定义为P,在SCL为高时,SDA线上出现一个从低到高的电平转换。

5568e3f34287c42740d35587abceb925.png

I2C寄存器如下:

c2d57c4d9f2817d4777f82df23000627.png

I2C库函数如下:

0437e60f44ac369ab32eefa428e43ed6.png

I2C流程如下:

  • 初始化时钟

  • 配置管脚

  • 使能,配置I2C时钟

  • I2C参数配置

  • 使能I2C

  • 使能应答

  • 数据发送/接收

部分代码如下:

/* I2C register bit mask */#define I2CCLK_MAX             ((uint32_t)0x00000048U)         /*!< i2cclk maximum value */#define I2CCLK_MIN              ((uint32_t)0x00000002U)         /*!< i2cclk minimum value */#define I2C_FLAG_MASK       ((uint32_t)0x0000FFFFU)           /*!< i2c flag mask */#define I2C_ADDRESS_MASK  ((uint32_t)0x000003FFU)        /*!< i2c address mask */#define I2C_ADDRESS2_MASK ((uint32_t)0x000000FEU)       /*!< the second i2c address mask */void i2c_mode_addr_config(uint32_t i2c_periph, uint32_t mode,uint32_t addformat, uint32_t addr){
     /* SMBus/I2C mode selected */uint32_t ctl = 0U;ctl = I2C_CTL0(i2c_periph);ctl &= ~(I2C_CTL0_SMBEN);ctl |= mode;I2C_CTL0(i2c_periph) = ctl;/* configure address */addr = addr & I2C_ADDRESS_MASK;I2C_SADDR0(i2c_periph) = (addformat | addr);}void i2c_clock_config(uint32_t i2c_periph, uint32_t clkspeed, uint32_t dutycyc){
     uint32_t pclk1, clkc, freq, risetime;uint32_t temp;pclk1 = rcu_clock_freq_get(CK_APB1);/* I2C peripheral clock frequency */freq = (uint32_t) (pclk1 / 1000000U);if (freq >= I2CCLK_MAX) {
     freq = I2CCLK_MAX;}temp = I2C_CTL1(i2c_periph);temp &= ~I2C_CTL1_I2CCLK;temp |= freq;I2C_CTL1(i2c_periph) = temp;if (100000U >= clkspeed) {
     /* the maximum SCL rise time is 1000ns in standard mode */risetime = (uint32_t) ((pclk1 / 1000000U) + 1U);if (risetime >= I2CCLK_MAX) {
     I2C_RT(i2c_periph) = I2CCLK_MAX;} else if (risetime <= I2CCLK_MIN) {
     I2C_RT(i2c_periph) = I2CCLK_MIN;} else {
     I2C_RT(i2c_periph) = risetime;}clkc = (uint32_t) (pclk1 / (clkspeed * 2U));if (clkc < 0x04U) {
     /* the CLKC in standard mode minmum value is 4 */clkc = 0x04U;}I2C_CKCFG(i2c_periph) |= (I2C_CKCFG_CLKC & clkc);} else if (400000U >= clkspeed) {
     /* the maximum SCL rise time is 300ns in fast mode */I2C_RT(i2c_periph) = (uint32_t) (((freq * (uint32_t) 300U)/ (uint32_t) 1000U) + (uint32_t) 1U);if (I2C_DTCY_2 == dutycyc) {
     /* I2C duty cycle is 2 */clkc = (uint32_t) (pclk1 / (clkspeed * 3U));I2C_CKCFG(i2c_periph) &= ~I2C_CKCFG_DTCY;} else {
     /* I2C duty cycle is 16/9 */clkc = (uint32_t) (pclk1 / (clkspeed * 25U));I2C_CKCFG(i2c_periph) |= I2C_CKCFG_DTCY;}if (0U == (clkc & I2C_CKCFG_CLKC)) {
     /* the CLKC in fast mode minmum value is 1 */clkc |= 0x0001U;}I2C_CKCFG(i2c_periph) |= I2C_CKCFG_FAST;I2C_CKCFG(i2c_periph) |= clkc;} else {
     }}void i2c_ack_config(uint32_t i2c_periph, uint32_t ack){
     if (I2C_ACK_ENABLE == ack) {
     I2C_CTL0(i2c_periph) |= I2C_CTL0_ACKEN;} else {
     I2C_CTL0(i2c_periph) &= ~(I2C_CTL0_ACKEN);}}void i2c_master_addressing(uint32_t i2c_periph, uint32_t addr,uint32_t trandirection){
     /* master is a transmitter or a receiver */if (I2C_TRANSMITTER == trandirection) {
     addr = addr & I2C_TRANSMITTER;} else {
     addr = addr | I2C_RECEIVER;}/* send slave address */I2C_DATA(i2c_periph) = addr;}void i2c_enable(uint32_t i2c_periph){
     I2C_CTL0(i2c_periph) |= I2C_CTL0_I2CEN;}void i2c_disable(uint32_t i2c_periph){
     I2C_CTL0(i2c_periph) &= ~(I2C_CTL0_I2CEN);}void i2c_start_on_bus(uint32_t i2c_periph){
     I2C_CTL0(i2c_periph) |= I2C_CTL0_START;}void i2c_stop_on_bus(uint32_t i2c_periph){
     I2C_CTL0(i2c_periph) |= I2C_CTL0_STOP;}void i2c_interrupt_enable(uint32_t i2c_periph, i2c_interrupt_enum interrupt){
     I2C_REG_VAL(i2c_periph, interrupt) |= BIT(I2C_BIT_POS(interrupt));}void i2c_interrupt_disable(uint32_t i2c_periph, i2c_interrupt_enum interrupt){
     I2C_REG_VAL(i2c_periph, interrupt) &= ~BIT(I2C_BIT_POS(interrupt));}FlagStatus i2c_interrupt_flag_get(uint32_t i2c_periph,i2c_interrupt_flag_enum int_flag){
     uint32_t intenable = 0U, flagstatus = 0U, bufie;/* check BUFIE */bufie = I2C_CTL1(i2c_periph) & I2C_CTL1_BUFIE;/* get the interrupt enable bit status */intenable = (I2C_REG_VAL(i2c_periph, int_flag) & BIT(I2C_BIT_POS(int_flag)));/* get the corresponding flag bit status */flagstatus = (I2C_REG_VAL2(i2c_periph, int_flag)& BIT(I2C_BIT_POS2(int_flag)));if ((I2C_INT_FLAG_RBNE == int_flag) || (I2C_INT_FLAG_TBE == int_flag)) {
     if (intenable && bufie) {
     intenable = 1U;} else {
     intenable = 0U;}}if ((0U != flagstatus) && (0U != intenable)) {
     return SET;} else {
     return RESET;}}void i2c_interrupt_flag_clear(uint32_t i2c_periph,i2c_interrupt_flag_enum int_flag){
     uint32_t temp;if (I2C_INT_FLAG_ADDSEND == int_flag) {
     /* read I2C_STAT0 and then read I2C_STAT1 to clear ADDSEND */temp = I2C_STAT0(i2c_periph);temp = I2C_STAT1(i2c_periph);} else {
     I2C_REG_VAL2(i2c_periph, int_flag) &= ~BIT(I2C_BIT_POS2(int_flag));}}

SPI与I2C通信还是比较复杂,没有串口那么简单了,因为存在主从关系,官方也提供了相应的demo示例,这里不再赘述。

c3e20dae9f12688b7b816d04715223ba.png

06 最小系统设计

最后一堂课尽然是有关硬件电路设计方面的,最小系统板的设包括软硬件的整体设计。画板并不是我的专长。只是稀疏的了解硬件原理图,焊接元器件,至于画板,在学校学习过Protel99。通过该培训课程的视频教学,尝试使用了AD的20.0.13版本操作了一下PCB板的布线。

41b95d2ba6a9e13f43fca8e0311734da.png

6551605e3f8f5007a37dc1abd896a4c1.png

d02eb93396cf466b49935b87cc27a48b.png

学习了Altium Designer的基本操作,也算是有所收获。不管是硬件电路设计工程师,还是软件开发工程师,工作都不容易,只能一步一个脚印才能设计出一款优秀的,值得消费者认同的产品,您说呢。基于GD32VF103开发板学习的整套课程早已更新完毕,感兴趣的坛友可以回看,重温学习教程。这是一款入门RISC-V架构学习的开发板,感兴趣网友,极力推荐使用。

c1b232309eed62179656a2d5ff46f483.gif 369f98bb60579f31e09939b289d8da01.png 扫码入群 扫码添加管理员微信

加入“电子产品世界”粉丝交流群

↓↓↓↓点击,查看更多新闻

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_39671374/article/details/111049992

智能推荐

5个超厉害的资源搜索网站,每一款都可以让你的资源满满!_最全资源搜索引擎-程序员宅基地

文章浏览阅读1.6w次,点赞8次,收藏41次。生活中我们无时不刻不都要在网站搜索资源,但就是缺少一个趁手的资源搜索网站,如果有一个比较好的资源搜索网站可以帮助我们节省一大半时间!今天小编在这里为大家分享5款超厉害的资源搜索网站,每一款都可以让你的资源丰富精彩!网盘传奇一款最有效的网盘资源搜索网站你还在为找网站里面的资源而烦恼找不到什么合适的工具而烦恼吗?这款网站传奇网站汇聚了4853w个资源,并且它每一天都会持续更新资源;..._最全资源搜索引擎

Book类的设计(Java)_6-1 book类的设计java-程序员宅基地

文章浏览阅读4.5k次,点赞5次,收藏18次。阅读测试程序,设计一个Book类。函数接口定义:class Book{}该类有 四个私有属性 分别是 书籍名称、 价格、 作者、 出版年份,以及相应的set 与get方法;该类有一个含有四个参数的构造方法,这四个参数依次是 书籍名称、 价格、 作者、 出版年份 。裁判测试程序样例:import java.util.*;public class Main { public static void main(String[] args) { List <Book>_6-1 book类的设计java

基于微信小程序的校园导航小程序设计与实现_校园导航微信小程序系统的设计与实现-程序员宅基地

文章浏览阅读613次,点赞28次,收藏27次。相比于以前的传统手工管理方式,智能化的管理方式可以大幅降低学校的运营人员成本,实现了校园导航的标准化、制度化、程序化的管理,有效地防止了校园导航的随意管理,提高了信息的处理速度和精确度,能够及时、准确地查询和修正建筑速看等信息。课题主要采用微信小程序、SpringBoot架构技术,前端以小程序页面呈现给学生,结合后台java语言使页面更加完善,后台使用MySQL数据库进行数据存储。微信小程序主要包括学生信息、校园简介、建筑速看、系统信息等功能,从而实现智能化的管理方式,提高工作效率。

有状态和无状态登录

传统上用户登陆状态会以 Session 的形式保存在服务器上,而 Session ID 则保存在前端的 Cookie 中;而使用 JWT 以后,用户的认证信息将会以 Token 的形式保存在前端,服务器不需要保存任何的用户状态,这也就是为什么 JWT 被称为无状态登陆的原因,无状态登陆最大的优势就是完美支持分布式部署,可以使用一个 Token 发送给不同的服务器,而所有的服务器都会返回同样的结果。有状态和无状态最大的区别就是服务端会不会保存客户端的信息。

九大角度全方位对比Android、iOS开发_ios 开发角度-程序员宅基地

文章浏览阅读784次。发表于10小时前| 2674次阅读| 来源TechCrunch| 19 条评论| 作者Jon EvansiOSAndroid应用开发产品编程语言JavaObjective-C摘要:即便Android市场份额已经超过80%,对于开发者来说,使用哪一个平台做开发仍然很难选择。本文从开发环境、配置、UX设计、语言、API、网络、分享、碎片化、发布等九个方面把Android和iOS_ios 开发角度

搜索引擎的发展历史

搜索引擎的发展历史可以追溯到20世纪90年代初,随着互联网的快速发展和信息量的急剧增加,人们开始感受到了获取和管理信息的挑战。这些阶段展示了搜索引擎在技术和商业模式上的不断演进,以满足用户对信息获取的不断增长的需求。

随便推点

控制对象的特性_控制对象特性-程序员宅基地

文章浏览阅读990次。对象特性是指控制对象的输出参数和输入参数之间的相互作用规律。放大系数K描述控制对象特性的静态特性参数。它的意义是:输出量的变化量和输入量的变化量之比。时间常数T当输入量发生变化后,所引起输出量变化的快慢。(动态参数) ..._控制对象特性

FRP搭建内网穿透(亲测有效)_locyanfrp-程序员宅基地

文章浏览阅读5.7w次,点赞50次,收藏276次。FRP搭建内网穿透1.概述:frp可以通过有公网IP的的服务器将内网的主机暴露给互联网,从而实现通过外网能直接访问到内网主机;frp有服务端和客户端,服务端需要装在有公网ip的服务器上,客户端装在内网主机上。2.简单的图解:3.准备工作:1.一个域名(www.test.xyz)2.一台有公网IP的服务器(阿里云、腾讯云等都行)3.一台内网主机4.下载frp,选择适合的版本下载解压如下:我这里服务器端和客户端都放在了/usr/local/frp/目录下4.执行命令# 服务器端给执_locyanfrp

UVA 12534 - Binary Matrix 2 (网络流‘最小费用最大流’ZKW)_uva12534-程序员宅基地

文章浏览阅读687次。题目:http://acm.hust.edu.cn/vjudge/contest/view.action?cid=93745#problem/A题意:给出r*c的01矩阵,可以翻转格子使得0表成1,1变成0,求出最小的步数使得每一行中1的个数相等,每一列中1的个数相等。思路:网络流。容量可以保证每一行和每一列的1的个数相等,费用可以算出最小步数。行向列建边,如果该格子是_uva12534

免费SSL证书_csdn alphassl免费申请-程序员宅基地

文章浏览阅读504次。1、Let's Encrypt 90天,支持泛域名2、Buypass:https://www.buypass.com/ssl/resources/go-ssl-technical-specification6个月,单域名3、AlwaysOnSLL:https://alwaysonssl.com/ 1年,单域名 可参考蜗牛(wn789)4、TrustAsia5、Alpha..._csdn alphassl免费申请

测试算法的性能(以选择排序为例)_算法性能测试-程序员宅基地

文章浏览阅读1.6k次。测试算法的性能 很多时候我们需要对算法的性能进行测试,最简单的方式是看算法在特定的数据集上的执行时间,简单的测试算法性能的函数实现见testSort()。【思想】:用clock_t计算某排序算法所需的时间,(endTime - startTime)/ CLOCKS_PER_SEC来表示执行了多少秒。【关于宏CLOCKS_PER_SEC】:以下摘自百度百科,“CLOCKS_PE_算法性能测试

Lane Detection_lanedetectionlite-程序员宅基地

文章浏览阅读1.2k次。fromhttps://towardsdatascience.com/finding-lane-lines-simple-pipeline-for-lane-detection-d02b62e7572bIdentifying lanes of the road is very common task that human driver performs. This is important ..._lanedetectionlite

推荐文章

热门文章

相关标签