MIniCH32V103EVB学习手册

MIniCH32V103EVB学习手册

第一部分、硬件概述

1.1 MIniCH32V103EVB实物图

1.1.1仿真图

shiwu1.png

1.1.2实物图

shiwu.jpg

1.2 MIniCH32V103EVB原理图

MIniCH32V103EVB原理图下图所示,如看不清可打开Hardware目录下Sch的PDF文档查阅

MIniCH32V103EVB_Sch.png

第二部分、软件工具

2.1 软件概述

在 /Software 目录下是常用的工具软件:

  1.  MounRiver: 编译器;

2.2 MounRiver软件入门

大家访问以下链接:http://mounriver.com/help

2.3 新建工程

  • 打开MounRiver Studio;

  • 点击File->New->MounRiver Project后出现下图,键入Project Name,芯片选择CH32V103C8T6后点击Finish;

    Proj1.png

  • 完成工程创建后出现下图:

    Tree.png
    其中:

    • Includes: 包含的头文件;
    • __Core:__内核文件,存放core_riscv内核文件;
    • Debug: 存放串口打印和延迟函数相关的文件;
    • Ld: 链接文件;
    • Peripheral: 这是MCU厂商提供外设相关驱动;
    • Startup: ch32v103的启动文件;
    • __User: __ch32v103的配置文件,中断相关文件,main函数等;

至此,工程创建完成。

第三部分、实战训练

本节我们目标是实现两颗LED的循环闪烁效果;

3.1.1 硬件设计

如下图是板载LED原理图,其中LED2连接到PB0,LED3则与PB1相连;由于以上LED是共阳极接法,所以,PB0输出高电平,则灯灭,输出低电平则点亮;

LED.png

3.1.2 软件设计

3.1.2.1 启动代码

我们先打开startup_ch32v10x.S启动文件,我们大概看一下我们能理解的部分,其中NMI_Handler是不可屏蔽中断的处理器;HardFault_Handler是硬件异常中断(死机),在工程目录User下的ch32v10x_it.c种实现;SysTick_Handler是系统滴答中断函数;SW_handler是软件中断;

 j   NMI_Handler                 /* NMI Handler */
 j   HardFault_Handler           /* Hard Fault Handler */
 j   SysTick_Handler            /* SysTick Handler */
 j   SW_handler                 /* SW Handler */

然后是一堆的外部中断;

    /* External Interrupts */
        j   WWDG_IRQHandler            /* Window Watchdog */
        j   PVD_IRQHandler             /* PVD through EXTI Line detect */
        j   TAMPER_IRQHandler          /* TAMPER */
        j   RTC_IRQHandler             /* RTC */
        j   FLASH_IRQHandler           /* Flash */
        j   RCC_IRQHandler             /* RCC */
        j   EXTI0_IRQHandler           /* EXTI Line 0 */
        j   EXTI1_IRQHandler           /* EXTI Line 1 */
        j   EXTI2_IRQHandler           /* EXTI Line 2 */
        j   EXTI3_IRQHandler           /* EXTI Line 3 */
        j   EXTI4_IRQHandler           /* EXTI Line 4 */
        j   DMA1_Channel1_IRQHandler   /* DMA1 Channel 1 */
        j   DMA1_Channel2_IRQHandler   /* DMA1 Channel 2 */
        j   DMA1_Channel3_IRQHandler   /* DMA1 Channel 3 */
        j   DMA1_Channel4_IRQHandler   /* DMA1 Channel 4 */
        j   DMA1_Channel5_IRQHandler   /* DMA1 Channel 5 */
        j   DMA1_Channel6_IRQHandler   /* DMA1 Channel 6 */
        j   DMA1_Channel7_IRQHandler   /* DMA1 Channel 7 */
        j   ADC1_2_IRQHandler          /* ADC1_2 */
        .word   0
        .word   0
        .word   0
        .word   0
        j   EXTI9_5_IRQHandler         /* EXTI Line 9..5 */
        j   TIM1_BRK_IRQHandler        /* TIM1 Break */
        j   TIM1_UP_IRQHandler         /* TIM1 Update */
        j   TIM1_TRG_COM_IRQHandler    /* TIM1 Trigger and Commutation */
        j   TIM1_CC_IRQHandler         /* TIM1 Capture Compare */
        j   TIM2_IRQHandler            /* TIM2 */
        j   TIM3_IRQHandler            /* TIM3 */
        j   TIM4_IRQHandler            /* TIM4 */
        j   I2C1_EV_IRQHandler         /* I2C1 Event */
        j   I2C1_ER_IRQHandler         /* I2C1 Error */
        j   I2C2_EV_IRQHandler         /* I2C2 Event */
        j   I2C2_ER_IRQHandler         /* I2C2 Error */
        j   SPI1_IRQHandler            /* SPI1 */
        j   SPI2_IRQHandler            /* SPI2 */
        j   USART1_IRQHandler          /* USART1 */
        j   USART2_IRQHandler          /* USART2 */
        j   USART3_IRQHandler          /* USART3 */
        j   EXTI15_10_IRQHandler       /* EXTI Line 15..10 */
        j   RTCAlarm_IRQHandler        /* RTC Alarm through EXTI Line */
        j   USBWakeUp_IRQHandler       /* USB Wakeup from suspend */
        j   USBHD_IRQHandler           /* USBHD */

并且继续往下,可以看到,这些中断都是弱定义的,也就是说,你可以重新定义并实现;

    .weak   NMI_Handler
    .weak   HardFault_Handler
    .weak   SysTick_Handler
    .weak   SW_handler
    .weak   WWDG_IRQHandler
    .weak   PVD_IRQHandler
3.1.2.2 系统时钟配置

最后是系统初始化和main函数,通过以下语句加载系统时钟初始化和main函数

  jal  SystemInit
	la t0, main

我们先看SystemInit,操作RCC相关的寄存器是进行初始化,具体的定义请打开\MIniCH32V103EVB\Document\ch32v103手册\CH32xRM.PDF 的3.5小节 寄存器描述;

R32表示32位的寄存器,RCC表示RCC时钟相关寄存器,

RCC-R.png

void SystemInit (void)
{
  RCC->CTLR |= (uint32_t)0x00000001;
  RCC->CFGR0 &= (uint32_t)0xF8FF0000;
  RCC->CTLR &= (uint32_t)0xFEF6FFFF;
  RCC->CTLR &= (uint32_t)0xFFFBFFFF;
  RCC->CFGR0 &= (uint32_t)0xFF80FFFF;
  RCC->INTR = 0x009F0000;    
  SetSysClock();
}
static void SetSysClock(void)
{
#ifdef SYSCLK_FREQ_HSE
  SetSysClockToHSE();
#elif defined SYSCLK_FREQ_24MHz
  SetSysClockTo24();
#elif defined SYSCLK_FREQ_48MHz
  SetSysClockTo48();
#elif defined SYSCLK_FREQ_56MHz
  SetSysClockTo56();  
#elif defined SYSCLK_FREQ_72MHz
  SetSysClockTo72();
#endif
 
 /* If none of the define above is enabled, the HSI is used as System clock
  * source (default after reset) 
	*/ 
}

这里我们以CTLR寄存器为例,讲解一些C语言操作寄存器的情况,RCC->CTLR |= (uint32_t)0x00000001;这段代码是CTLR最后一位置位,而该寄存器的最后一位如下图所示,为1,就是使能HSI振荡器;

RCC-R1.png

其他寄存器也同理;

在代码种,定义了SYSCLK_FREQ_72MHz,所以SetSysClockTo72()这个函数吧系统时钟设置为72MHz;

3.1.2.3 main函数实现

接下来我们来看看main函数,如下

int main(void)
{
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
    Delay_Init();
    USART_Printf_Init(115200);
    printf("SystemClk:%d\r\n", SystemCoreClock);
    printf("Blink TEST\r\n");
    LED_GPIO_INIT();

    while(1)
    {
        LED2_ON();
        LED3_OFF();
        printf("LED2_ON,LED3_OFF\r\n");
        Delay_Ms(250);
        LED2_OFF();
        LED3_ON();
        printf("LED2_OFF,LED3_ON\r\n");
        Delay_Ms(250);
    }
}

NVIC_PriorityGroupConfig是配置优先级分组的,Delay_Init初始化延迟函数;USART2_Printf_Init初始化串口打印,在使用printf之前,需要做声明并实现以下函数以支持printf函数

__attribute__((used)) 
int _write(int fd, char *buf, int size)
{
    int i;

    for(i = 0; i < size; i++){
#if(DEBUG == DEBUG_UART1)
        while(USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET);
        USART_SendData(USART1, *buf++);
#elif(DEBUG == DEBUG_UART2)
        while(USART_GetFlagStatus(USART2, USART_FLAG_TC) == RESET);
        USART_SendData(USART2, *buf++);
#elif(DEBUG == DEBUG_UART3)
        while(USART_GetFlagStatus(USART3, USART_FLAG_TC) == RESET);
        USART_SendData(USART3, *buf++);
#endif
    }

    return size;
}
3.1.2.3 LED模块代码

LED_GPIO_INIT是对LED对应的PB0与PB1进行初始化,我们可以跳转到RCC_APB2PeriphClockCmd,不难发现RCC->APB2PCENR实际上这些库函数的本质就操作寄存器;GPIO_Init也是如此,即操作GPIOx->CFGLR和GPIOx->CFGHR寄存器,关于这些寄存器请直接参考参考手册;

void LED_GPIO_INIT(void)
{
    GPIO_InitTypeDef GPIO_InitStructure = {0};

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_1;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOB, &GPIO_InitStructure);
}

最后是LED的亮灭操作,LED2_ON()这个宏是亮灯,由于LED是共阳极接法,故低电平点亮GPIO_ResetBits(GPIOB,GPIO_Pin_0)使得PB0输出低电平,反之,LED2_OFF输出高电平,其他IO口同理可得;

#define     LED2_ON()         GPIO_ResetBits(GPIOB,GPIO_Pin_0)
#define     LED2_OFF()        GPIO_SetBits(GPIOB,GPIO_Pin_0)

#define     LED3_ON()         GPIO_ResetBits(GPIOB,GPIO_Pin_1)
#define     LED3_OFF()        GPIO_SetBits(GPIOB,GPIO_Pin_1)

while(1)
{
    LED2_ON();
    LED3_OFF();
    Delay_Ms(250);
    LED2_OFF();
    LED3_ON();
    Delay_Ms(250);
}

3.1.3 下载验证

我们把固件程序下载进去可以,可以看到板载的LED2和LED3交替点亮熄灭;

3.2 实例Eg2_USART

本节我们目标是实现串口2接收并回传数据;

3.2.1 硬件设计

如下图是板载排针,我们用到PA2作为USART2的TX,PA3作为USART2的RX;

pa23.png

3.2.2 软件设计

3.2.2.1 串口2初始化配置

我们配置USART2串口接收中断,一般的我们需要先进行GPIO Init,USART Init,NVIC Init;GPIO 配置为TX复用推挽模式RX为IN_FLOATING,USART波特率配置为115200,字长8位,1个停止位,无奇偶校验,无硬件流控,发送和接收模式;NVIC优先级配置为PreemptionPriority = 1,SubPriority = 1;

void USART2_CFG(void)
{
    GPIO_InitTypeDef  GPIO_InitStructure = {0};
    USART_InitTypeDef USART_InitStructure = {0};
    NVIC_InitTypeDef  NVIC_InitStructure = {0};

    RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2 , ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

    /* USART2 TX-->A.2   RX-->A.3 */
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    USART_InitStructure.USART_BaudRate = 115200;
    USART_InitStructure.USART_WordLength = USART_WordLength_8b;
    USART_InitStructure.USART_StopBits = USART_StopBits_1;
    USART_InitStructure.USART_Parity = USART_Parity_No;
    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
    USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;

    USART_Init(USART2, &USART_InitStructure);
    USART2->STATR = 0x00C0;
    USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);

    NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);

    USART_Cmd(USART2, ENABLE);
}
3.2.2.3 USART2全局中断请求

串口2全局中断请求回调函数声明与实现如下,在回调函数中USART_GetITStatus获取接收中断标志,USART_ClearITPendingBit清除中断标志,USART_ReceiveData接收数据,USART_SendData发送数据;

void USART2_IRQHandler(void) __attribute__((interrupt("WCH-Interrupt-fast")));
void USART2_IRQHandler(void)
{

    if(USART_GetITStatus(USART2, USART_IT_RXNE) != RESET)
    {
        USART_ClearITPendingBit(USART2,USART_IT_RXNE);    //清除中断标志
        USART_SendData(USART2,USART_ReceiveData(USART2));
    }
}

3.2.3 下载验证

我们把固件程序下载进去可以,可以看到板载的LED2和LED3交替点亮熄灭,连接PA2到USB转TTL串口调试工具上(如CH340、Wch-link)的RX,PA3接到串口工具的TX;打开串口调试工具;配置串口USART波特率配置为115200,字长8位,1个停止位,无奇偶校验,无硬件流控;然后发送任意字符串,串口终端回显相同字符串;

ssc.png

3.3 实例Eg3_DHT11

本节我们在上一节的基础上,目标是实现读取DHT11温湿度模块的温湿度数据并通过串口打印出来

3.3.1 硬件设计

如下图是DHT11模块原理图,我们将用PB3作为去读取DHT11的数据,这里用到单总线协议,详细资料请观看视频并打开

“Document\电子模块数据资料\DHT11 使用‌说明书”目录下的说明书;

DHT11.png

3.3.2 软件设计

3.3.2.1 初始化DHT11

我们配置PB3为推挽输出模式,并复位DHT11后等待其响应,代码如下;

//初始化DHT11的IO口 DQ 同时检测DHT11的存在
//返回1:不存在
//返回0:存在
u8 DHT11_Init(void)
{
 	GPIO_InitTypeDef  GPIO_InitStructure;

 	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);	 //使能PB端口时钟
	
 	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;				 //PB3端口配置
 	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; 		 //推挽输出
 	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
 	GPIO_Init(GPIOB, &GPIO_InitStructure);				 //初始化IO口
 	GPIO_SetBits(GPIOB,GPIO_Pin_3);						 //PG11 输出高

	DHT11_Rst();  //复位DHT11
	return DHT11_Check();//等待DHT11的回应
}
3.3.2.3 DHT11数据处理

查阅DHT11 使用‌说明书可知:

一次完整的数据传输为40bit,高位先出。
数据格式:8bit湿度整数数据+8bit湿度小数数据
+8bi温度整数数据+8bit温度小数数据
+8bit校验和
数据传送正确时校验和数据等于“ 8bit 湿度整数数据 +8bit 湿度小数数据+8bi 温度整数数据 +8bit 温度小数数据 ”所得结果的末8位。

根据以上数据格式实现如下代码:

//从DHT11读取一次数据
//temp:温度值(范围:0~50°)
//humi:湿度值(范围:20%~90%)
//返回值:0,正常;1,读取失败
u8 DHT11_Read_Data(u8 *temp,u8 *humi)
{
 	u8 buf[5];
	u8 i;
	DHT11_Rst();
	if(DHT11_Check()==0)
	{
		for(i=0;i<5;i++)//读取40位数据
		{
			buf[i]=DHT11_Read_Byte();
		}
		if((buf[0]+buf[1]+buf[2]+buf[3])==buf[4])
		{
			*humi=buf[0];
			*temp=buf[2];
		}
	}else return 1;
	return 0;
}

3.3.3 下载验证

我们把固件程序下载进去可以,可以看到板载的LED2和LED3交替点亮熄灭,连接PA2到USB转TTL串口调试工具上(如CH340、Wch-link)的RX,PA3接到串口工具的TX;打开串口调试工具;配置串口USART波特率配置为115200,字长8位,1个停止位,无奇偶校验,无硬件流控;

紧接着我们把PB3连接到DHT模块的DATA,并上电;

ssc.png

3.4 实例Eg4_TIMEncoder

本节我们目标是实现TIM编码接口模式实现编码器信号捕获分析

3.4.1 硬件设计

GPIO初始化配置 TIM2_CH1(PA0) TIM2_CH2(PA1);编码器接口模式

3.4.2 软件设计

首先是编码器接口的初始化Encoder_Init_TIM2,再有EnCoder_Handle确定计数方向和计数值;

void Encoder_Init_TIM2(void)
{
    GPIO_InitTypeDef        GPIO_InitStructure;
    TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
    TIM_ICInitTypeDef       TIM_ICInitStructure; 

    //使能相应时钟
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);   //使能AFIO复用功能模块时钟

    //GPIO初始化配置 TIM2_CH1(PA0)  TIM2_CH2(PA1)
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_1;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //浮空输入
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    //定时器初始化配置
    TIM_TimeBaseStructInit(&TIM_TimeBaseStructure);
    TIM_TimeBaseStructure.TIM_Period = 0xFFFF;                  //计数器自动重装载值
    TIM_TimeBaseStructure.TIM_Prescaler = 1;                    //预分频器值
    TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;     //时钟分频
    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //向上计数模式
    TIM_TimeBaseStructure.TIM_RepetitionCounter = 0;            //重复计数器值
    TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);             //初始化结构体

    TIM_EncoderInterfaceConfig(TIM2, TIM_EncoderMode_TI12, TIM_ICPolarity_Rising, TIM_ICPolarity_Rising); //使用编码器模式3

    //输入捕获配置
    TIM_ICInitStructure.TIM_Channel = TIM_Channel_1|TIM_Channel_2;
    TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;       //输入捕获极性设置,可用于配置编码器正反相
    TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;             //输入捕获预分频器设置
    TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;   //输入捕获通道选择,编码器模式需选用此配置
    TIM_ICInitStructure.TIM_ICFilter = 10;                            //输入捕获滤波器设置
    TIM_ICInit(TIM2, &TIM_ICInitStructure);
    
    TIM_ClearFlag(TIM2, TIM_FLAG_Update);        //清除TIM更新标志位
    TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);   //使能开启TIM中断
    //Reset counter
    TIM_SetCounter(TIM2,0);

    TIM_Cmd(TIM2, ENABLE);
}
void EnCoder_Handle(void)
{
    encoder.Dir=TIM_IS_TIM_COUNTING_DOWN();
    encoder.Count=TIM_GetCounter(TIM2);
    printf("Dir=%d,count=%d\r\n",encoder.Dir,encoder.Count);
    Delay_Ms(10);
}

3.4.3 下载验证

通过串口调试助手我们可以直观看到打印的log:计数方向与计数值;

image

3.5 实例Eg5_TM1640

本节我们目标是实现TM1640点亮16位数码管,显示“0~F”数字;

3.4.1 硬件设计

GPIO初始化配置 SCL(PB6) SDA(PB7);开漏输出,通过IO模拟驱动TM1640,TM1640IC驱动有点类似于I2C;

3.4.2 软件设计

首先是TM1640初始化TM1640_GPIO_INIT,设置为开漏极输出,如下:

/*********************************************************************
 * @fn      TM1640_GPIO_INIT
 *
 * @brief   Initializes GPIOB.6 GPIOB.7
 *
 * @return  none
 */
void TM1640_GPIO_INIT(void) {

    GPIO_InitTypeDef GPIO_InitStructure = { 0 };

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOB, &GPIO_InitStructure);
    GPIO_SetBits(GPIOB, GPIO_Pin_6);//SCL
    GPIO_SetBits(GPIOB, GPIO_Pin_7);//SDA
}

接着设置TM1640,并写入段码

void TM1640_Handle(void)
{
    u8 i;
    TM1640_Generate_START();
    TM1640_WriteData(0x40);//数据命令设置:普通模式,地址自动加一
    TM1640_Generate_STOP();

    TM1640_Generate_START();
    TM1640_WriteData(0xC0);////地址命令设置:初始地址00H
    for(i=0;i<16;i++)  //发送16位显示数据
    {
        TM1640_WriteData(CODE[i]);
    }
    TM1640_Generate_STOP();

    TM1640_Generate_START();
    TM1640_WriteData(0x8c);    //显示控制:显示开,脉冲宽度设为11/16
    TM1640_Generate_STOP();
    Delay_Ms(10);

}

3.4.3 下载验证

通过串口调试助手我们可以直观看到打印的log:计数方向与计数值;

image

posted @ 2022-06-07 14:56  LiJin_hh  阅读(419)  评论(0编辑  收藏  举报