MIniCH32V103EVB学习手册
MIniCH32V103EVB学习手册
第一部分、硬件概述
1.1 MIniCH32V103EVB实物图
1.1.1仿真图
1.1.2实物图
1.2 MIniCH32V103EVB原理图
MIniCH32V103EVB原理图下图所示,如看不清可打开Hardware目录下Sch的PDF文档查阅
第二部分、软件工具
2.1 软件概述
在 /Software 目录下是常用的工具软件:
1. MounRiver: 编译器;
2.2 MounRiver软件入门
大家访问以下链接:http://mounriver.com/help
2.3 新建工程
-
打开MounRiver Studio;
-
点击File->New->MounRiver Project后出现下图,键入Project Name,芯片选择CH32V103C8T6后点击Finish;
-
完成工程创建后出现下图:
其中:- Includes: 包含的头文件;
- __Core:__内核文件,存放core_riscv内核文件;
- Debug: 存放串口打印和延迟函数相关的文件;
- Ld: 链接文件;
- Peripheral: 这是MCU厂商提供外设相关驱动;
- Startup: ch32v103的启动文件;
- __User: __ch32v103的配置文件,中断相关文件,main函数等;
至此,工程创建完成。
第三部分、实战训练
3.1 实例Eg1_Blink
本节我们目标是实现两颗LED的循环闪烁效果;
3.1.1 硬件设计
如下图是板载LED原理图,其中LED2连接到PB0,LED3则与PB1相连;由于以上LED是共阳极接法,所以,PB0输出高电平,则灯灭,输出低电平则点亮;
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时钟相关寄存器,
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振荡器;
其他寄存器也同理;
在代码种,定义了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;
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个停止位,无奇偶校验,无硬件流控;然后发送任意字符串,串口终端回显相同字符串;
3.3 实例Eg3_DHT11
本节我们在上一节的基础上,目标是实现读取DHT11温湿度模块的温湿度数据并通过串口打印出来
3.3.1 硬件设计
如下图是DHT11模块原理图,我们将用PB3作为去读取DHT11的数据,这里用到单总线协议,详细资料请观看视频并打开
“Document\电子模块数据资料\DHT11 使用说明书”目录下的说明书;
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,并上电;
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:计数方向与计数值;
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:计数方向与计数值;