深入学习嵌入式课程:作业5

前言

这篇文章是关于第六章的作业,在开始作业之前,我们先来总结一下这个第六章我们具体学习了什么,这一章学的是有关串行通信模块以及中断处理程序的使用,首先是串行通信模块,不同于小灯泡的操作,这次我们要操作的是端口A,至于怎么操作嘛,则是跟之前的一样的,找到对应寄存器的地址,根据你的需求,进行对应寄存器的操作,就可以了。除此之外,我们还学习了中断处理程序的编写。

基础知识

在开始我们的作业之前,我们先复习一下课上讲的东西。

串行通信

接收:把单线输入的bit数据变成一个字节的并行数据通过接收引脚送入MCU内部

发送:把需要发送的一个字节的并行数据转换为bit数据通过发送引脚输出

我们的开发板子上面有3组UART(通用异步收发传输器),分别是UART1,UART2,UART3,具体的定义如下

我们编程要用的串行口为UART2,即要用的引脚为PTA2以及PTA3,这个时候我们看看手册,找一找他们的信息。

首先时钟使能这个不必多说,必定是要使用的,一个是UART的时钟使能,一个是GPIOA的时钟使能,这个我们就不展开了,先看看如何找到这两个口,首先是设置GPIOA的mode为复用模式,看引脚名为2 3说明在2 3号引脚,如下

其中可以看到10为复用模式,OK,那么设置了复用模式,那么自然而然就要设置复用功能了,那么复用功能在哪里设置呢,复用功能在哪里看呢?具体就是在参考手册中看以及数据手册中看,如下

这几幅图一放上来,就知道怎么设置了吧?我这里就不再过多说明了,这些是比较基础的工作,下面如果要使用这个模块,还需要进行各种小操作,想什么配置波特率啊,过采样因子等等等等,不过这个,具体来说就是在URAT寄存器组进行操作的,具体如何操作我这里就不放出来了,反正会查表,看得懂英语(会使用翻译软件就行了),数据手册和参考手册都有非常详细的描述的。

中断

中断这个概念在这一两年也是听的够多了,但凡是个操作系统的课,都能再学一遍,这里我就不细说了,那么作为一款芯片,我们开发板上面的确实也有能支持中断的硬件,翻看我们的参考手册,能够看到中断向量表,如下

同时,也能从工程头文件看到简单的中断号表b,如下

不过这些东西都是列出来让大家看看而已啦,我们其实关心的东西是如何写一个中断,总的来说,可以分为几个步骤

首先,我们需要一个中断向量表,这个倒是自然,因为对于CPU来说,中断号的传递过去就是收到中断向量,不过这个倒是不用我们自己写,因为大部分能想到的中断,工程文件都已经帮我们准备好了,如下

在这里我们能够找到UART2口的中断处理函数名称,或者是各式设备的中断处理函数名称,值得留意的是,还能看到默认的Default处理函数,不过这个处理函数就是空操作啦。

接下来就是了解一个东西了,就是提供给中断设置的寄存器组了,如下

为此,工程文件提供了一个结构体,用来方便地访问这些寄存器

OK,知道了这些表,我们就可以接着搞中断了,下一步就是把相关的中断功能给开放,这涉及到UART寄存器组以及NVIC寄存器组的操作了,这里的话我们用工程写好的接口函数来深究一下具体做了什么。如下

可以看到,从代码上看,先是做了UART_CR1寄存器的操作,开放了UART接收中断,然后是对NVIC寄存器组进行操作,具体对NVIC操作我们可以试着追踪一下这个函数,如下

可以看到,函数如上,大致就是设置这个中断使能控制寄存器内容的,至于上面的代码说了什么,我们在作业里细谈,这里只要知道这么一回事就行了。

OK,那么再往下做的就是写自己的中断处理程序了,如果自己没有写,那么会默认的跳到Default函数中,而这个函数是空操作,这样大致就能写好中断了。(别忘了写的时候要注意上面一系列操作都是原子操作,即要开关总中断)下面就是作业时间了。

作业

作业一

1、编写UART_2串口发送程序时,初始化要设置那些参数?

这里我用代码段的方式来呈现,总的来说,要设置GPIOA和UART2时钟,然后设置对应端口的复用功能,配置波特率,过采样因子,LIN模式,同步时钟,智能卡模式,半双工模式,等等等等,具体配置如下

    //设置的参数如下
	//uart寄存器相关地址
    volatile uint32_t* RCC_AHB2;     //GPIO的A口时钟使能寄存器地址
    volatile uint32_t* RCC_APB1;     //UART的2口时钟使能寄存器地址
    volatile uint32_t* gpio_ptr;       //GPIO的A口基地址
    volatile uint32_t* uart_ptr;       //uart2端口的基地址
    volatile uint32_t* gpio_mode;    //引脚模式寄存器地址=口基地址
    volatile uint32_t* gpio_afrl;      //GPIO复用功能低位寄存器
    volatile uint32_t* uart_brr;      //UART波特率寄存器地址
    volatile uint32_t* uart_isr;      // UART中断和状态寄存器基地址
    volatile uint32_t* uart_cr1;      //UART控制寄存器1基地址 
    volatile uint32_t* uart_cr2;      // UART控制寄存器2基地址
    volatile uint32_t* uart_cr3;      // UART控制寄存器3基地址
    volatile uint32_t* uart_tdr;      // UART发送数据寄存器
    uint16_t usartdiv;   //BRR寄存器应赋的值

	//变量赋值
	RCC_APB1=0x40021058UL;   //UART时钟使能寄存器地址
    RCC_AHB2=0x4002104CUL;   //GPIO的A口时钟使能寄存器地址
    gpio_ptr=0x48000000UL;   //GPIOA端口的基地址
    uart_ptr=0x40004400UL;  //UART2端口的基地址
    gpio_mode=0x48000000UL;              //引脚模式寄存器地址=口基地址
    gpio_afrl=0x48000020UL;           // GPIO复用功能低位寄存器
    uart_cr1=0x40004400UL;              //UART控制寄存器1基地址 
    uart_brr=0x4000440CUL;          // UART波特率寄存器地址
    uart_isr=0x4000441CUL;         // UART中断和状态寄存器基地址
    uart_tdr=0x40004428UL;         //UART发送数据寄存器
    uart_cr2=0x40004404UL;      // UART控制寄存器2基地址
    uart_cr3=0x40004408UL;      //UART控制寄存器3基地址

	//使能GPIOA和UART2的时钟
    *RCC_APB1|=(0x1UL<<17U);       //UART2时钟使能 
    *RCC_AHB2 |=(0x1UL<<0U);       //GPIOA时钟使能

	//将GPIO端口设置为复用功能
    //首先将D7、D6、D5、D4清零
    *gpio_mode &= ~((0x3UL<<4U)|(0x3UL<<6U)); 
    //然后将D7、D6、D5、D4设为1010,设置PTA2、PTA3为复用功能串行功能。
    *gpio_mode |=((0x2UL<<4U)|(0x2UL<<6U));

	//选择引脚的端口复用功能
    //首先将D15~D8清零
    *gpio_afrl &= ~((0xFUL<<8U)|(0xFUL<<12U));
    //然后将D15~D8设置为01110111,分别将PTA3、PTA2引脚设置为USART2_RX、USART2_TX 
    *gpio_afrl=(((0x1UL<<8U)|(0x2UL<<8U)|(0x4UL<<8U))|((0x1UL<<12U)
    |(0x2UL<<12U)|(0x4UL<<12U)));    
	
    //暂时禁用UART功能,控制寄存器1的第0位对应的是UE—USART使能位。
    //此位清零后,USART预分频器和输出将立即停止,并丢弃所有当前操作。
    *uart_cr1 &= ~(0x1UL);
    
    //暂时关闭串口发送与接收功能,控制寄存器1的发送器使能位(D3)、接收器使能位(D2)
    *uart_cr1 &= ~((0x1UL<<3U)|(0x1UL<<2U));     

    //配置波特率
    if(*uart_cr1&(0x1UL<<15) == (0x1UL<<15))             
    usartdiv = (uint16_t)((SystemCoreClock/115200)*2);
    else
    usartdiv = (uint16_t)((SystemCoreClock/115200));
    *uart_brr = usartdiv;

    //初始化控制寄存器和中断状态寄存器、清标志位
    //关中断
    *uart_isr = 0x0UL;    
    //将控制寄存器2的两个使能位清零。D14—LIN模式使能位、D11—时钟使能位 
    *uart_cr2 &= ~((0x1UL<<14U)|(0x1UL<<11U));
    //将控制寄存器3的三个使能位清零。D5 (SCEN) —smartcard模式使能位、
    //D3 (HDSEL) —半双工选择位、D1 (IREN) —IrDA 模式使能位
    *uart_cr3 &= ~((0x1UL<<5U) | (0x1UL<<3U) |(0x1UL<<1U));
    
    //启动串口发送与接收功能
    *uart_cr1 |= ((0x1UL<<3U)|(0x1UL<<2U)); 
    
    //开启UART功能
    *uart_cr1 |= (0x1UL<<0U); 

2、假设速度为115200,系统时钟为72MHz,波特率寄存器BRR中的值应该是多少?

我们可以通过这个公式来计算其应该放进去的值

根据过采样因子的不同,有两种情况

  • 过采样因子为8:USARTDIV = 2*72MHz / 115200 = 1250
  • 过采样因子为16:USARTDIV = 72MHz / 115200 = 625
  • 3、中断向量表在哪个文件中?表中有多少项?给出部分截图

    中断向量表实际上我在上面就已经展现过了,这里再放一轮吧。

    我们能在工程目录中的startup/startup_stm32l431rctx.s下看到中断向量表,如下

    其中,我们能够发现有一些为word 0的值,这个实际上是被保留的未曾使用的地址,我在猜想是不是在这里是作者故意保留的,于是翻看了参考手册,发现参考手册这些地址也是被保留的,如下

    在我看来,中断向量表保留一些位置是为了满足硬件架构、处理器设计、扩展性、灵活性、安全性和稳定性等方面的需求。这些保留的位置可以根据具体的系统需求进行配置和使用。具体是什么我们也就不深究了。那么第一个问题就回答到这里吧,接下来就是数数了。

    在startup_stm32l431rctx.s下,共有74项中断向量(如果保留地址不算数的话),前提是我没数错数啊。

    4、以下是中断源使能函数,假设中断源为TIM6,将函数实例例化(写出各项具体数值)。

    呃,其实我并不太懂什么叫做实例例化啊,不过大概就是说假设中断源是TIM6,那么这个函数会传些什么数值进去吧,就大概流程是怎么样的。OK,那么一起去做一下吧。

    首先我们要知道,中断源为TIM6,那么我们先找一下TIM6的中断号,如下

    可以看到,是54号,那么带入到函数中,实例例化就在函数中(这里用代码+注释的方式呈现)

    __STATIC_INLINE void __NVIC_EnableIRQ(IRQn_Type IRQn)   //IRQn = 54
    {
      if ((int32_t)(IRQn) >= 0)     //判断54是否≥0,显然是的,进入if语句块
      {
        //首先我们先看赋值语句左边,这里是将54右移5位,实际上就是整除32,显然得到的是1……x的结果
        //那么实际上赋值语句的左边是NVIC->ISER[1]
        //那为什么是5位呢,因为从上面我们可以得知
        //实际上ISER寄存器组有8个寄存器,每个寄存器实际上是32位,因为我们的中断号是54
        //那么势必是不在0号寄存器的,肯定是1号寄存器的,因此操作的对象自然而然
        //就是是NVIC->ISER[1]了
        NVIC->ISER[(((uint32_t)IRQn) >> 5UL)] = (uint32_t)(1UL << (((uint32_t)IRQn) & 0x1FUL));
        //OK,接下来看赋值语句的右边,那么其实有了左边的详细解释,那么右边肯定是跟余数有关系啦
        //54/32=1……22
        //所以这里是什么呢
        //首先((uint32_t)IRQn) & 0x1FUL)实际上就是保留54的低5位,实际上就是0b10110,十进制就是22
        //然后1UL<<22
        //就是1左移22位呗
        //然后把这个uint32_t的结果赋值给NVIC->ISER[1]
        //实际上就是把寄存器ISER[1]寄存器设置成为"9个0  1 22个0"
        //实际上就是把这个54号中断打开嘛
        //即最后是NVIC->ISER[1]的第22位设为1
      }
    }

    5、假设将UART_2和TIM6交换其在中断向量表中的位置和IRQ号, UART_2可以正常中断吗?

    关于这个题目,我先不打算解释,我们先直接上手试一下。首先需要用到我们简单的工程文件进行操作。首先我们需要一个写好的UART中断处理函数的工程,如下

    这里大概就是,如果UART2串口接收到东西,就触发中断,中断处理函数就把这个东西会发到我们的PC端上,OK,那么我们把main.c函数中无关的东西注释掉,毕竟我们只测试中断。先试试看效果如何,如下

    可以看到是能正常触发中断处理函数的,OK,接下来我们做一个修改,交换中断号和中断向量,如下

    ok,上面的东西都已经交换好了,我们试着在搞一下,看看效果会是如何

    我们能发现,这玩意能够发送成功,但是没有触发那个期望的中断处理函数,这也就是说明,至少我发送这个字符串,至少是没有触发isr.c中重写的中断处理函数的,那么我们如何验证有没有产生中断呢?很简单,还记得上面讲的那个如果你自己没写那个中断处理函数,那么就会转到Default中处理,也就是停止。我们验证一下,把main.c的函数重新打开,然后运行的时候往板子发东西,看看还有没有继续发就知道了。如下

    显然肯定是产生了中断了,且中断处理函数一定是默认的,为什么,看上图,我点击发送数据,main函数的send函数停下来了,也就是说明进入到了默认的中断处理函数中,且没有出来。OK,这番操作之下,我们不禁思考一个问题,就是下图中

    这个中断处理函数,在这种情况下真的是USART2的中断处理函数吗?抱着这样的题目,我们在其下面追加了一个函数,如下

    没错,我们重写了TIM6中断处理函数,跟原本的有一点不同,就是发出来的字符是收到的下一位,运行看看效果(把main.c的无关注释掉)

    我们发现了一个很抽象的东西,也就是这个中断跑到TIM6的处理函数去了,但我还是想问一句,这个真的是TIM6的硬件产生的中断吗?哈哈,是不是稍微有点眉目了,OK,下面就揭晓。

    总的来说UART_2,这个硬件肯定是能产生中断信号的,但关键是硬件的东西早就是写死的,我们所定义的中断号啊,中断向量,这些个宏,仅仅就是对其包装而已,还记得我们做了什么吗,我们交换了中断号的名字(别名),假设说硬件产生的信号就是38,我们原本38的别名就叫“UART2”,自然而然中断向量表就叫的向量名就叫做“USART2_IRQHandler”,这个很符合直觉是吧,因为这是人为定义的,现在我们交换中断向量表的位置和IRQ号,本质上来说就是改了个名字而已,因此实际上再修改之后,USART2_IRQHandler处理函数实际上处理的是TIM6产生的中断,但因为我们本身就没有启动TIM6,那肯定不进入这个函数处理啊,然后本身也没有写TIM6(别名,本质上是处理UART2硬件的中断信号)的处理函数,自然而然就调用默认的处理函数了。也因此我重写了TIM6的中断处理函数,就能正常处理了。

    我们在正式点回答,如果UART_2和TIM6在中断向量表中的位置和IRQ号进行了交换,理论上UART_2仍然可以正常中断,前提是其新的中断号未被其他设备占用。扯完这个,再说一下中断向量表是怎么回事,中断向量表是处理器用来识别和处理中断请求的一个表格,每个中断源都有一个对应的中断号和中断服务程序(ISR)。当硬件设备需要处理器注意时,它会通过生成一个中断信号来通知处理器,处理器随后查找中断向量表来确定应当调用哪个ISR来处理该中断。因此,只要中断号正确无误地指向了UART_2的中断处理程序,并且该中断号没有被其他设备使用,UART_2就能够正常地进行中断处理。OK,那么这个问题的回答就到此结束了,不过说实话,研究这个问题还是花费了一点时间的,因为在此之前本人对于中断的认知还不是那么的熟悉。

    作业二

    1、实现UART_2串口的接收程序,当收到字符时①在电脑的输出窗口显示下一个字符,如收到A显示B;②亮灯:收到字符G,亮绿灯;收到字符R,亮红灯;收到字符B,亮蓝灯;收到其他字符,不亮灯。实现方式:用构件调用方式实现。

    经过上面的沉淀,做这种还不是手到擒来,直接上代码!代码分为两部分,一部分是main.c入口函数,一部分是isr.c中断处理函数

    以下是main.c

    #define GLOBLE_VAR
    #include "includes.h"      //包含总头文件
    
    int main(void)
    {
    	//关总中断
    	DISABLE_INTERRUPTS;
    
    	//用户外设模块初始化
    	gpio_init(LIGHT_BLUE,GPIO_OUTPUT,LIGHT_OFF);
        gpio_init(LIGHT_RED,GPIO_OUTPUT,LIGHT_OFF);
        gpio_init(LIGHT_GREEN,GPIO_OUTPUT,LIGHT_OFF);
    
    	uart_init(UART_User,115200);     //初始化串口模块   
      
    	//打开使能模块中断
    	uart_enable_re_int(UART_User);  //使能UART_USER模块接收中断功能
    	
    	//开总中断
    	ENABLE_INTERRUPTS;
    	
    	//提示进入串口工具进行操作
    	printf("=====================================\n");
    	printf("广州大学 wyw 32106100071!\n");
    	printf("请进入串口工具COM4进行操作!\n");
    	printf("=====================================\n");
    }

    以下是isr.c

    #include "includes.h"
    //======================================================================
    //程序名称:UART_User_Handler
    //触发条件:UART_User串口收到一个字节触发
    //======================================================================
    void UART_User_Handler(void)
    {
    	//【1】声明局部变量
    	uint8_t ch;
    	uint8_t flag;
        //【2】关总中断
    	DISABLE_INTERRUPTS; 
    	//【3】读取接到的一个字节
    	ch=uart_re1(UART_User,&flag);  //调用接收一个字节的函数,清接收中断位
    	//【4】根据flag判断是否真正收到一个字节的数据
    	if(flag)                        //有数据
    	{
    		if((ch!='G')&&(ch!='R')&&(ch!='B'))
    		{
    			uart_send1(UART_User,(ch+1));//回发接收到的字节后一个,加上姓名缩写
    			uart_send_string(UART_User,(uint8_t *)"	wyw   ");
    			gpio_set(LIGHT_RED,LIGHT_OFF);
    			gpio_set(LIGHT_BLUE,LIGHT_OFF);
    			gpio_set(LIGHT_GREEN,LIGHT_OFF);
    		}
    		if(ch == 'G')
    		{
    			uart_send_string(UART_User,(uint8_t *)"	GERRN ON!wyw	");
    			gpio_set(LIGHT_RED,LIGHT_OFF);
    			gpio_set(LIGHT_BLUE,LIGHT_OFF);
    			gpio_set(LIGHT_GREEN,LIGHT_ON);
    		}
    		if(ch == 'R')
    		{
    			uart_send_string(UART_User,(uint8_t *)"	RED ON!wyw	  ");
    			gpio_set(LIGHT_GREEN,LIGHT_OFF);
    			gpio_set(LIGHT_BLUE,LIGHT_OFF);
    			gpio_set(LIGHT_RED,LIGHT_ON);
    		}
    		if(ch == 'B')
    		{
    			uart_send_string(UART_User,(uint8_t *)"	BLUE ON!wyw   ");
    			gpio_set(LIGHT_GREEN,LIGHT_OFF);
    			gpio_set(LIGHT_RED,LIGHT_OFF);
    			gpio_set(LIGHT_BLUE,LIGHT_ON);
    		}
    			
    	}
    	//【5】开总中断
    	ENABLE_INTERRUPTS;   
     }

    可以看到,调用构件的方式,还是比较方便快捷的,相当于我们前面的设置uart中,一个uart_init干了我们很多的东西。代码并没有什么好说的,直接看运行结果吧,如下

    可以看到,输出除了RGB字符,输出下一个字符,我这里还加上了输入自身的姓名缩写

    可以看到,输入不同的'R','G','B',亮不同的灯,并显示什么灯亮了。完美!

    2、实现UART_2串口的接收程序,当收到字符时①在电脑的输出窗口显示下一个字符,如收到A显示B;②亮灯:收到字符G,亮绿灯;收到字符R,亮红灯;收到字符B,亮蓝灯;收到其他字符,不亮灯。实现方式:UART部分用直接地址方式实现(即不调用uart.c中的函数,其他部分如GPIO、中断设置可调用函数)

    这里的话,有关于uart的设置要求我们用直接地址方式实现,我的评价是一样的,直接按照步骤一步一步来而已

    下面是代码,代码分为两部分,一部分是main.c入口函数,一部分是isr.c中断处理函数

    以下是main.c

    #define GLOBLE_VAR
    #include "includes.h"      //包含总头文件
    
    int main(void)
    {
        //定义uart寄存器相关地址
        volatile uint32_t* RCC_AHB2;     //GPIO的A口时钟使能寄存器地址
        volatile uint32_t* RCC_APB1;     //UART的2口时钟使能寄存器地址
        volatile uint32_t* gpio_ptr;       //GPIO的A口基地址
        volatile uint32_t* uart_ptr;       //uart2端口的基地址
        volatile uint32_t* gpio_mode;    //引脚模式寄存器地址=口基地址
        volatile uint32_t* gpio_afrl;      //GPIO复用功能低位寄存器
        volatile uint32_t* uart_brr;      //UART波特率寄存器地址
        volatile uint32_t* uart_isr;      // UART中断和状态寄存器基地址
        volatile uint32_t* uart_cr1;      //UART控制寄存器1基地址 
        volatile uint32_t* uart_cr2;      // UART控制寄存器2基地址
        volatile uint32_t* uart_cr3;      // UART控制寄存器3基地址
        volatile uint32_t* uart_tdr;      // UART发送数据寄存器
        uint16_t usartdiv;   //BRR寄存器应赋的值
        
        //变量赋值
        RCC_APB1=0x40021058UL;   		//UART时钟使能寄存器地址
        RCC_AHB2=0x4002104CUL;   		//GPIO的A口时钟使能寄存器地址
        gpio_ptr=0x48000000UL;   		//GPIOA端口的基地址
        uart_ptr=0x40004400UL;  		//UART2端口的基地址
        gpio_mode=0x48000000UL;         //引脚模式寄存器地址=口基地址
        gpio_afrl=0x48000020UL;         //GPIO复用功能低位寄存器
        uart_cr1=0x40004400UL;          //UART控制寄存器1基地址 
        uart_brr=0x4000440CUL;          //UART波特率寄存器地址
        uart_isr=0x4000441CUL;          //UART中断和状态寄存器基地址
        uart_tdr=0x40004428UL;          //UART发送数据寄存器
        uart_cr2=0x40004404UL;     	 	//UART控制寄存器2基地址
        uart_cr3=0x40004408UL;      	//UART控制寄存器3基地址
        
        //关总中断
        DISABLE_INTERRUPTS;
        
        //用户外设模块初始化
        gpio_init(LIGHT_BLUE,GPIO_OUTPUT,LIGHT_OFF);
        gpio_init(LIGHT_RED,GPIO_OUTPUT,LIGHT_OFF);
        gpio_init(LIGHT_GREEN,GPIO_OUTPUT,LIGHT_OFF);
        
        //下面执行uart_init(UART_User,115200);的直接地址方式
        //使能GPIOA和UART2的时钟
        *RCC_APB1|=(0x1UL<<17U);       //UART2时钟使能 
        *RCC_AHB2 |=(0x1UL<<0U);       //GPIOA时钟使能
        
        //将GPIO端口设置为复用功能
        //首先将D7、D6、D5、D4清零
        *gpio_mode &= ~((0x3UL<<4U)|(0x3UL<<6U)); 
        //然后将D7、D6、D5、D4设为1010,设置PTA2、PTA3为复用功能串行功能。
        *gpio_mode |=((0x2UL<<4U)|(0x2UL<<6U));
        
        //选择引脚的端口复用功能
        //首先将D15~D8清零
        *gpio_afrl &= ~((0xFUL<<8U)|(0xFUL<<12U));
        //然后将D15~D8设置为01110111,分别将PTA3、PTA2引脚设置为USART2_RX、USART2_TX 
        *gpio_afrl=(((0x1UL<<8U)|(0x2UL<<8U)|(0x4UL<<8U))|((0x1UL<<12U)
        |(0x2UL<<12U)|(0x4UL<<12U)));         
        
        //暂时禁用UART功能,控制寄存器1的第0位对应的是UE—USART使能位。
        //此位清零后,USART预分频器和输出将立即停止,并丢弃所有当前操作。
        *uart_cr1 &= ~(0x1UL);
        
        //暂时关闭串口发送与接收功能,控制寄存器1的发送器使能位(D3)、接收器使能位(D2)
        *uart_cr1 &= ~((0x1UL<<3U)|(0x1UL<<2U));
        
        //一位起始位,八位数据位
        *uart_cr1 &= ~((0x1UL << 28U)|(0x1UL << 12U));
        
        //过采样因子为16
        *uart_cr1 &= ~(0x1UL << 15U);
        
        //配置波特率
        usartdiv = (uint16_t)((SystemCoreClock/115200));
        *uart_brr = usartdiv;
        
        //初始化控制寄存器和中断状态寄存器、清标志位
        //关中断
        *uart_isr = 0x0UL;    
        //将控制寄存器2的两个使能位清零。D14—LIN模式使能位、D11—时钟使能位 
        *uart_cr2 &= ~((0x1UL<<14U)|(0x1UL<<11U));
        //将控制寄存器3的三个使能位清零。D5 (SCEN) —smartcard模式使能位、
        //D3 (HDSEL) —半双工选择位、D1 (IREN) —IrDA 模式使能位
        *uart_cr3 &= ~((0x1UL<<5U) | (0x1UL<<3U) |(0x1UL<<1U));
        
        //启动串口发送与接收功能
        *uart_cr1 |= ((0x1UL<<3U)|(0x1UL<<2U)); 
        
        //开启UART功能
        *uart_cr1 |= (0x1UL<<0U); 
        
        
        //完成直接地址方式实现使能模块中断
        //uart_enable_re_int(UART_User);  //使能UART_User模块接收中断功能
        //两部分,开放UART接收中断,开中断控制器IRQ中断
        //开放UART接收中断(将RXNEIE置为1)
        *uart_cr1 |= (0x1UL<<5);
        //开中断控制器IRQ中断,这里不属于uart部分,直接调用函数
        NVIC_EnableIRQ(USART2_IRQn);
        
        //开总中断
        ENABLE_INTERRUPTS;
        
       	//提示进入串口工具进行操作
    	printf("=====================================\n");
    	printf("广州大学 wyw 32106100071!\n");
    	printf("请进入串口工具COM4进行操作!\n");
    	printf("=====================================\n");  
    }
    

    以下是isr.c

    #include "includes.h"
    #define GLOBLE_VAR
    
    volatile uint32_t* uart_isr = (volatile uint32_t*)0x4000441CUL;	// UART中断和状态寄存器基地址
    volatile uint32_t* uart_rdr = (volatile uint32_t*)0x40004424UL;	// UART接受数据寄存器
    volatile uint32_t* uart_tdr = (volatile uint32_t*)0x40004428UL;	// UART发送数据寄存器
    
    void User_SysFun(uint8_t ch);
    //======================================================================
    //程序名称:UART_User_Handler
    //触发条件:UART_User串口收到一个字节触发
    //备    注:进入本程序后,可使用uart_get_re_int函数可再进行中断标志判断
    //          (1-有UART接收中断,0-没有UART接收中断)
    //======================================================================
    void USART2_IRQHandler(void)
    {
    	uint8_t ch;
    	uint8_t flag;
    	uint32_t t;
    	uint32_t t1;
    	
    	DISABLE_INTERRUPTS;   //关总中断
    	//接收一个字节的数据
    	//ch=uart_re1(UART_User,&flag);  //调用接收一个字节的函数,清接收中断位
    	for(t = 0; t < 0xFBBB; t++)//一直查询缓冲区是否有数据
    	{	
    		//先判断isr状态位,再获取数据
    		if((*uart_isr)&(1<<5U))//第五位为1
    		{
    			ch = *uart_rdr;//从RDR寄存器取数
    			flag = 1;
    			*uart_isr &= ~(1<<5U);//第五位清零
    			break;
    		}
    	}
    	if(t>=0xFBBB)//超过指定次数
    	{
    		ch = 0XFF;
    		flag = 0;
    	}
    	
    	if(flag)                       //有数据
    	{
    		if((ch!='G')&&(ch!='R')&&(ch!='B'))
    		{
    			//uart_send1(UART_User,(ch+1));//回发接收到的字节
    			//uart_send_string(UART_User,(uint8_t *)"	wyw   ");
    			for (t1 = 0; t1 < 0xFBBB; t++)//查询指定次数
    			{
    				//发送缓冲区为空则发送数据
    				//先判断isr状态位,再获取数据
    				if ((*uart_isr)&(1<<7U))	//检查第7位是否为一
    				{
    					*uart_tdr = (ch+1);		//放到发送寄存器
    					break;
    				}
    			}
    			if (t1 >= 0xFBBB)
    				return 0; //发送超时,发送失败
    			gpio_set(LIGHT_RED,LIGHT_OFF);
    			gpio_set(LIGHT_BLUE,LIGHT_OFF);
    			gpio_set(LIGHT_GREEN,LIGHT_OFF);
    		}
    		if(ch == 'G')
    		{
    			//uart_send_string(UART_User,(uint8_t *)"	GERRN ON!wyw	");
    			gpio_set(LIGHT_RED,LIGHT_OFF);
    			gpio_set(LIGHT_BLUE,LIGHT_OFF);
    			gpio_set(LIGHT_GREEN,LIGHT_ON);
    		}
    		if(ch == 'R')
    		{
    			//uart_send_string(UART_User,(uint8_t *)"	RED ON!wyw	  ");
    			gpio_set(LIGHT_GREEN,LIGHT_OFF);
    			gpio_set(LIGHT_BLUE,LIGHT_OFF);
    			gpio_set(LIGHT_RED,LIGHT_ON);
    		}
    		if(ch == 'B')
    		{
    			//uart_send_string(UART_User,(uint8_t *)"	BLUE ON!wyw   ");
    			gpio_set(LIGHT_GREEN,LIGHT_OFF);
    			gpio_set(LIGHT_RED,LIGHT_OFF);
    			gpio_set(LIGHT_BLUE,LIGHT_ON);
    		}
    			
    	}
    	ENABLE_INTERRUPTS;    //开总中断
    	
     }
    
    //内部函数
    void User_SysFun(uint8_t ch)
    {
        //(1)收到的一个字节参与组帧
        if(gcRecvLen == 0)  gcRecvLen =useremuart_frame(ch,(uint8_t*)gcRecvBuf);
        //(2)字节进入组帧后,判断gcRecvLen=0?若为0,表示组帧尚未完成,
        //     下次收到一个字节,再继续组帧
        if(gcRecvLen == 0) goto User_SysFun_Exit;
        //(3)至此,gcRecvLen≠0,表示组帧完成,gcRecvLen为帧的长度,校验序列号后(与
        //     根据Flash中倒数一扇区开始的16字节进行比较)
        //     gcRecvBuf[16]进行跳转
        if(strncmp((char *)(gcRecvBuf),(char *)((MCU_SECTOR_NUM-1)*MCU_SECTORSIZE+
           MCU_FLASH_ADDR_START),16) != 0)
        {
            gcRecvLen = 0;         //恢复接收状态
            goto User_SysFun_Exit;
        }
        //(4)至此,不仅收到完整帧,且序号比较也一致, 根据命令字节gcRecvBuf[16]进行跳转
        //若为User串口程序更新命令,则进行程序更新
        switch(gcRecvBuf[16])  //帧标识
        {
            case 0:
                SYSTEM_FUNCTION((uint8_t *)(gcRecvBuf+17));
                gcRecvLen = 0;         //恢复接收状态
            break;
            default:
            break;
        }
    User_SysFun_Exit:
        return;
    }

    这里的话我就没有向上面构建那样搞什么花里胡哨的输出了,就是按照题目要求,输入一个字符,输出下一个字符,然后RGB就亮灯,不输出,其他字符就灭灯这样子,也就是说,我只用直接地址编程实现了uart.c中的一个uart_rel和uart_send1函数,仅此而已。那么接下来就看看运行效果吧

    可以看到,输入A,输出下一个字符B

    可以看到,输入RBG,亮对应的灯

    可以看到,输入H,输出I,同时灭灯。

    完美,成功!那么作业二就到这里为止了

    总结

    本次作业联动性比较强啊,基本上把串口通信,中断,还有那个之前的点灯全用上了,还是像上次一样,有通过直接地址编程实现,也有调用构件实现,总体下来成就感还是不错的,特别是研究作业一第五题的时候,当我最终把名为TIM6的中断处理程序写好并发现UART中断跑到这里来执行的时候,真的是豁然开朗,醍醐灌顶,哈,先不说这些了,总而言之,通过这次的学习和作业,是我对于中断的理解更加深刻了,同时也对于寄存器直接编程更加得心应手,之前刚开始弄的时候,还要慢吞吞的想如何把那个位置1,清0,现在直接一见到就会写位操作,同时也对于参考手册的使用更加得心应手了,能更快的找到想找的东西了,OK,那么本次作业就到此为止了,下次作业再见!

    作者:是伟仔11

    物联沃分享整理
    物联沃-IOTWORD物联网 » 深入学习嵌入式课程:作业5

    发表回复