|
前面写了一篇《从STC轻松转向STM32》,里面说了,按这个思路,在熟练掌握了STC之后,其它任何单片机都可以轻松掌握。 为了验证这个说法,我们这次换一种芯片,换哪个呢?南京沁恒的RISC-V中的CH32L103C8T6,这个芯片,可以说是完全国产化的,从内到外全部自研,开发工具也是他自己开发的,下载器和软件也是,他在技术自主可控方面可以说是做得最彻底的,而且,开发工具不要钱哦。选它的另一个重要原因,是因为看起来似乎探索起来会困难一点,这个困难一点,正好来验证我说的那个思路的正确性,事实上,探索过程一样非常简单,因为太简单,所以,这次,在点亮了它之后,加了一个用定时器闪烁的探索。 找宝宝要了一块评估板及下载器,这个板子很简洁,上面有两个共阳极LED,另一端空着,我们将其连到B端口的低八位的任意两个引脚。 下面就开始我们的探索之路吧。 第一步,点灯,这个过程,实在是乏善可陈,与上一篇用STM32的基本无区别,甚至因为桥2的使能寄存器地址、端口B的功能与模式设置寄存器、端口输出寄存器的地址完全一样,那一篇里的前两种程序完全照抄就行了,不用改任何一个字: 程序一: int main(void) { (*(volatile unsigned int *)0x40021018)|=(1<<3);//在桥2上打开端口B的电源开关 (*(volatile unsigned int *)0x40010C00)=0x33333333;//设置端口B的低八位的八个端口为推挽输出模式 (*(volatile unsigned int *)0x40010C0C)=0x0000;.//向端口B的所有端口输出低电位,以点亮连在其上的共阳极LED while(1); } 程序二: #define RCC_APB2ENR (*(volatile unsigned int *)0x40021018) #define GPIOB_CRL (*(volatile unsigned int *)0x40010C00) #define GPIOB_CDR (*(volatile unsigned int *)0x40010C0C) int main(void) { RCC_APB2ENR|=(1<<3); GPIOB_CRL=0x33333333; GPIOB_CDR=0x0000; while(1); } IDE用的是沁恒的MounRiver Studio,下载软件和下载器用的都是他家的,他的IDE用起来很简单很好上手,只是用时我把他的main.c的内容全删了全重写。 第二步,用定时器闪灯,选TIM3通用定时器。 我们的思路就是,编程嘛,不就是操作寄存器么,所以,我们具体的做法,就是细看所有可能与这个定时器有关的寄存器,然后编程操作。这些内容在哪里?手册中全有。 首先当然是打开TIM3定时器这个外设的电源开关啦,有了电它才可以工作嘛,看使用手册,它是挂在桥1下面的,于是,我们就直接找RCC类寄存器中桥1中的使能寄存器及其绝对地址,然后给它起个与手册中该寄存器同名的英文名,这样起名的目的当然是为了好记好查好统一方便移植: #define RCC_PB1PCENR (*(volatile unsigned int *)0x4002101C) 该寄存器中位1为使能TIM3,于是我们就该在主程序中写上这么一句RCC_PB1PCENR|=(1<<1)来打开TIM3的电源开关。 接下来我们就直扑TIM3的寄存器章节,一个一个地找与TIM3用作定时器中断时的相关寄存器及相关功能设置位,按上面打开TIM3电源开关同样的方法起名和操作。 第 15 章 通用定时器(GPTM),看一看本章及各节说明,然后是15.4 寄存器描述,里面这张表里的地址是各寄存器的绝对地址而不是表头说的偏移地址,有了这个地址,我们就很容易给我们要用到的寄存器起名字了。 在控制寄存器 1(TIMx_CTLR1)中,我们找到了位7为自动重装预装使能位,置1时使能自动重装值寄存器(ATRLR),那么这个寄存器就是我们要操作的TIM3的寄存之一;位0,使能计数器 (Counter enable),置1使能计数器,置0禁止计数器,在软件设置了该位后,外部时钟、门控模式 和编码器模式才能工作,所以位0也是我们要操作的内容,与STC一样,用它来启动计数器。实际上这个寄存器的[7:0]都用得上,只不过因[1:6]复位值都是0而已经符合我们的要求,所以我们就偷个懒,不在程序中进行配置操作。 控制寄存器 2(TIMx_CTLR2),这个里面没找到与TIM3作为定时器有关的项目,不管。 继续往下,在DMA/中断使能寄存器(TIMx_DMAINTENR)中找到一个位0更新中断使能位,为1时允许更新中断,所以这个寄存器我们也得用上,同上处理。 中断状态寄存器(R16_TIMx_INTFR),其中的位0为更新中断标志位,当产生更新事件时该位由硬件置 位,由软件清零,为1时表示更新中断产生,为0时无更新事件产生,这一位的用法,对于我们用过STC的人来说都很面熟悉,用法也一样,只是有一点,记得在程序初始化时就将其清零。 通用定时器的计数器(TIMx_CNT),这个肯定在用,但我们不干预的话,就不管了。 计数时钟预分频器(TIMx_PSC),这个在本章的说明里将其用途说得很清楚,是肯定要用的,它的作用是依据定时器的工作频率,将这个频率按这个预分频器里面的值+1作为除数进行分降频,得到的频率值就是定时器计数的频率。 自动重装值寄存器(TIMx_ATRLR),这个我们STC人很熟悉,就不用说了。在这里,我们定时器的工作频率如果是96MHZ,设置TIMx_PSC为9600-1后,计数频率就变成了10KHZ,再设定TIMx_ATRLR为5000-1,则完成一次计数并发生中断就是500ms。 再往下都是无关的寄存器。 按我们用STC的经验,既然是中断,那就要在有关中断的章节里找需要用到的寄存器了,这里面要找的,最重要的是TIM3的中断使能寄存器。 第 9 章 中断和事件(PFIC),还是先看说明,再就看到了那张向量表,表中找到了TIM3的中断编号为45。 看看看,看到了9.5.2 PFIC 寄存器描述,这一节很重要,理应仔细看,然后在里面看到了PFIC 中断使能设置寄存器 2(PFIC_IENR2),以及里面的说明38#-63#中断使能控制,任一位为1时为其对应编号中断使能。其实这个寄存器是32#到63#(中间有空缺未用到的中断编号)中断使能控制寄存器,我们要用到的TIM3的中断编号为45,推算出它对应位为位13。 其实这里面相关的寄存器不少,不过为了简单点,我们现在不用他们也行。 再就是中断优先配置的相关寄存器了,但是,为了偷懒,因为只有一个TIM3中断并不存在争权的事,所以,这些就用复位值算了。 中断弄完了,再就是时钟的配置,这个也偷个懒,不找了,也用复位值,如果有问题,大概无非是最终闪闪的时间不符合你的预设嘛,不过,这部分,总还是要补上课的。 好了,相关的寄存器就算找全了,下面就是编程,这个也简单,但是千万不能出错,特别是地址,弄错一个就过不了啊,千万耐心。 全部程序如下: #define RCC_PB2PCENR (*(volatile unsigned int *)0x40021018) //桥2使能控制寄存器起名 #define GPIOB_CFGLR (*(volatile unsigned int *)0x40010C00) #define GPIOB_OUTDR (*(volatile unsigned int *)0x40010C0C) #define RCC_PB1PCENR (*(volatile unsigned int *)0x4002101C) //桥1使能控制寄存器 #define TIM3_CTLR1 (*(volatile unsigned int *)0x40000400) //TIM3 控制寄存器 1 #define TIM3_DMAINTENR (*(volatile unsigned int *)0x4000040C ) //DMA/中断使能寄存器 #define TIM3_INTFR (*(volatile unsigned int *)0x40000410 ) //中断状态寄存器 #define TIM3_PSC (*(volatile unsigned int *)0x40000428) // 计数器频率预分频器 #define TIM3_ATRLR (*(volatile unsigned int *)0x4000042C) //自动重装值寄存器 #define PFIC_IENR2 (*(volatile unsigned int *)0xE000E104) //32#-63#中断使能寄存器 int main(void) { RCC_PB2PCENR|=(1<<3);//打开端口PB2这个外设的电源开关 GPIOB_CFGLR=0x33333333;//设置PB端口的低八位的八个端口的功能及电路模式为推挽输出模式 GPIOB_OUTDR=0x0;//PB端口的十六个端口均输出低电位,点亮其上的共阳极LED灯。这句可以不要 RCC_PB1PCENR|=(1<<1);//打开TIM3这个外设的电源开关 TIM3_CTLR1|=(1<<7); //使能自动重装值寄存器(ATRLR) TIM3_DMAINTENR|=(1<<0); //允许更新中断 TIM3_INTFR&=~(1<<0); //清除更新中断标志位 TIM3_PSC=9600-1; TIM3_ATRLR=5000-1; PFIC_IENR2|=(1<<13); //使能TIM3的中断,中断号45 TIM3_CTLR1|=(1<<0); //使能计数器即启动计数器 while(1); } //TIM3中断子程序 //RISC-V内核必须添加中断属性声明,硬件压栈中断使用__attribute__((interrupt("WCH-Interrupt-fast"))) void TIM3_IRQHandler(void) __attribute__((interrupt("WCH-Interrupt-fast"))); void TIM3_IRQHandler(void) { if(TIM3_INTFR&(1<<0)) //如果更新中断标志位为1 { GPIOB_OUTDR=~GPIOB_OUTDR; } TIM3_INTFR&=~(1<<0); //清除更新中断标志位 } 是不是特简单特容易?但是本人觉得特别枯燥,完全是耐着性子断断续续才做完的。 不管什么单片机,其原理大致都差不多,其功能的实现都是靠众多的寄存器,只要搞懂了原理,你就知道要找类寄存器哪些功能的设置,知道了这个,你就只管去找在哪个寄存器中就行了,比如说定时器,你总得给它个初始值吧,你总得让它动起来吧——计数器启动,你总得让它在计数到点了吱个声吧——更新中断和标志位,还有到点了需不需要再来——自动重装使能,到点了你得允许起身去处理这时候该处理的事吧——中断使能,等等的。 再比如说,中断优先级这类的,设计这个优先级处理机制的目的,它是为了处理两种情况:一是处理多个中断发生的先后时间不同的情况,这个时候要决定正在执行的中断要不要先放一边——抢占机制;一是多个事情同时发生,要决定处理顺序——优先级机制。你在编程中要做的事,无非是设定中断的这两个级数。知道了这个原理,你再去找实现这两个机制的所有相关寄存器就是了,至于它的寄存器是如何实现这个机制的,各类芯片也许不同,但也没什么不好懂的,比如说STM32的,它的特色是分了组,这个分组也就无非是各组里抢占嵌套层数与优先级数的不同,而CH32L103就没有这个分组设计,它只有两级嵌套,采用的是使能位来选择是否采用嵌套。 说明一下,以上程序的写法,既不完整也很不规范很不正经,但是,根本不是我想偷懒,而是为了简洁让你们好懂知道么,你们千万别认为这是学习的榜样。我这里要给出的是学习探索的思路,而不是完整规范的程序。 关于编程软件、下载软件和下载器的使用,太简单,很好上手的,简单说几句,使用说明在软件帮助菜单里面都有,安装目录里面也很全搜pdf文件就出来了,评估板的资料在官网上下载参考例程中,其它资料官网都有。吐槽的是它的例程注释太简单,而且只有英文的,不过好在它对我来说可有可无,因为是库函数编程,不想看,库函数这东西,我看不懂它是怎么干的。 最后说一句很重要的经验,手册一定要看,要看懂,如果看不懂,那仅凭手册你是编不了程序的;看懂就够了,不需要记,用时知道在哪查就行了。
|