AHL(金葫芦)微控制器嵌入式系统学习笔记 | Week 5

一、前言

        本周的内容和上一周有点相似,但是涉及到发送数据触发亮灯与灭灯,还是挺有意思的,继续往下看吧!

二、本周学习内容

(1)编写UART_2串口发送程序时,初始化需要设置哪些参数?

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

(3)中断向量表在哪个文件中?表中有多少项?

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

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

实现UART_2串口的接收程序,收到字符时,进行如下操作:

①在电脑输出窗口显示下一个字符,如收到A显示B;

②亮灯:收到字符R,亮红灯;收到字符G,亮绿灯;收到字符B,亮蓝灯;收到其他字符,不亮灯。

实现方式:

采用构件调用方式UART部分用直接地址方式(即不调用uart.c中的函数,其他部分如GPIO、中断设置可调用函数)

本周用到的工程文件如下:

三、实验过程-part1

(1)编写UART_2串口发送程序时,初始化需要设置哪些参数?

①波特率

具体设置如下:

// 根据时钟源的不同,计算波特率寄存器的值,然后设置到波特率寄存器中
if (*uart_cr1 & (0x1UL << 15) == (0x1UL << 15))             
    usartdiv = (uint16_t)((SystemCoreClock / 115200) * 2);
else
    usartdiv = (uint16_t)(SystemCoreClock / 115200);
*uart_brr = usartdiv;

②时钟使能

启用了UART_2和相应的GPIO端口的时钟使能。

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

③引脚配置

将GPIO端口设置为串口功能,即复用功能。

//将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_enable_re_int(UART_User);  // 使能UART_User模块接收中断功能

⑤控制寄存器

设置控制寄存器,包括启用串口、设置发送和接收使能位等。

//暂时禁用UART功能,控制寄存器1的第0位对应的是UE—USART使能位。
//此位清零后,USART预分频器和输出将立即停止,并丢弃所有当前操作。
*uart_cr1 &= ~(0x1UL);
    
//暂时关闭串口发送与接收功能,控制寄存器1的发送器使能位(D3)、接收器使能位(D2)
 *uart_cr1 &= ~((0x1UL<<3U)|(0x1UL<<2U));
    
//初始化控制寄存器和中断状态寄存器、清标志位
//关中断
*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中的值应该是多少?

由图示公式可以得到:

①过采样位为1,系数为8:usartdiv=2*72000000/115200=1250,即寄存器BRR的值为1250

②过采样位为0,系数为16:usartdiv=72000000/115200=625,即寄存器BRR的值为625

(3)中断向量表在哪个文件中?表中有多少项?

在“03_MCU”目录下的“startup_stm32l431rctx.s”文件中找到g_pfnVectors,共有239-141+1=99

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

中断请求IRQ号定义在03_MCU\startup\stm32l431xx.h文件中

        通过查询,可以得到中断源TIM6的IRQ号为54。

        通过IRQ号比去54/32=1…22得出,即,将该函数将ISER[1]第22位设置为1,即允许中断源TIM6中断。     

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

        如果在中断向量表中交换了UART_2和TIM6的位置及其IRQ号,理论上UART_2仍然有可能正常处理中断,前提是其新的IRQ号没有被其他设备使用。只要UART_2的新IRQ号被正确地配置到了其ISR,并且这个IRQ号没有被其他中断源占用,UART_2就能够按预期响应中断。

四、实验过程-part2

(1)构件调用实现亮/灭灯

        构建调用和UART部分直接地址实现亮灭灯的实验结果一样,仅代码不同,下面展示实验结果和源码 

实验结果      


main.c源码

#define GLOBLE_VAR
#include "includes.h"      //包含总头文件

//———————————————————————-
//声明使用到的内部函数
//main.c使用的内部函数声明处

//———————————————————————-
//主函数,一般情况下可以认为程序从此开始运行(实际上有启动过程,参见书稿)
int main(void)
{
//(1)======启动部分(开头)==========================================
//(1.1)声明main函数使用的局部变量
//    uint32_t mMainLoopCount;  //主循环次数变量
//    uint8_t  mFlag;           //灯的状态标志
//    uint8_t  mTest;
//    uint32_t mLightCount;     //灯的状态切换次数

//(1.2)【不变】关总中断
    DISABLE_INTERRUPTS;

//(1.3)给主函数使用的局部变量赋初值
//    mMainLoopCount=0;    //主循环次数变量
//    mFlag='A';           //灯的状态标志
//    mLightCount=0;       //灯的闪烁次数
//    mTest='T';
//(1.4)给全局变量赋初值

//(1.5)用户外设模块初始化
    gpio_init(LIGHT_RED,GPIO_OUTPUT,1);    //初始化红灯
    gpio_init(LIGHT_GREEN,GPIO_OUTPUT,1);    //初始化绿灯
    gpio_init(LIGHT_BLUE,GPIO_OUTPUT,1);    //初始化蓝灯

    uart_init(UART_2,115200);     //初始化串口模块   
  
//(1.6)使能模块中断
     uart_enable_re_int(UART_2);  //使能UART_USER模块接收中断功能
//(1.7)【不变】开总中断
    ENABLE_INTERRUPTS;
  

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_2,&flag);  //调用接收一个字节的函数,清接收中断位
    //【4】根据flag判断是否真正收到一个字节的数据
    if(flag==1)                        //有数据
    {
        //ch=ch+2;
        uart_send1(UART_2,ch+1);  //回发接收到的字节
        uart_send_string(UART_2, " hello! \t");
        
        if(ch=='R')
        {
            //gpio_set(LIGHT_RED,1);
            gpio_set(LIGHT_GREEN,1);
            gpio_set(LIGHT_BLUE,1);
            gpio_set(LIGHT_RED,0);
        }else if(ch=='G')
        {
            gpio_set(LIGHT_RED,1);
            //gpio_set(LIGHT_GREEN,1);
            gpio_set(LIGHT_BLUE,1);
            gpio_set(LIGHT_GREEN,0);
        }else if(ch=='B')
        {    gpio_set(LIGHT_RED,1);
            gpio_set(LIGHT_GREEN,1);
            //gpio_set(LIGHT_BLUE,1);
            gpio_set(LIGHT_BLUE,0);
        }else
        {
            gpio_set(LIGHT_RED,1);
            gpio_set(LIGHT_GREEN,1);
            gpio_set(LIGHT_BLUE,1);
        }
    }
    
    //【5】开总中断
    ENABLE_INTERRUPTS;   
    
}

(2)UART部分直接地址实现亮/灭灯

        该部分的实验结果和构建调用的实验结果一致,下面仅放源码(此处的实现方式把isr.c中的代码搬到main.c中了,其实放哪里都可以,能编译通过即可)。


main.c

#define GLOBLE_VAR
#include "includes.h"      //包含总头文件

//———————————————————————-
//声明使用到的内部函数
//main.c使用的内部函数声明处

//———————————————————————-
//主函数,一般情况下可以认为程序从此开始运行(实际上有启动过程,参见书稿)
int main(void)
{
    //(1)======启动部分(开头)==========================================
    //(1.1)声明main函数使用的局部变量
    uint8_t  mTest;
    uint32_t mCount;
    
    //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基地址
    
    //(1.2)【不变】关总中断
    DISABLE_INTERRUPTS;
    
    //(1.3)给主函数使用的局部变量赋初值
    mCount=0;
    //(1.4)给全局变量赋初值
    
    //(1.5)用户外设模块初始化
    gpio_init(LIGHT_RED,GPIO_OUTPUT,1);    //初始化红灯
    gpio_init(LIGHT_GREEN,GPIO_OUTPUT,1);    //初始化绿灯
    gpio_init(LIGHT_BLUE,GPIO_OUTPUT,1);    //初始化蓝灯
    //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));
    
    //配置波特率
    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); 
    
    
    //(1.6)使能模块中断
    uart_enable_re_int(UART_User);  //使能UART_User模块接收中断功能
    //(1.7)【不变】开总中断
    ENABLE_INTERRUPTS;
    
    
    //(1)======启动部分(结尾)==========================================
    
    //(2)======主循环部分(开头)========================================
    
    for(;;)
    {
        //【1】声明局部变量
        uint8_t ch;
        uint8_t flag;
        //【2】关总中断
        DISABLE_INTERRUPTS; 
        //【3】读取接到的一个字节
        // 检查接收缓冲区是否满,并接收字符
        if (USART2->ISR & USART_ISR_RXNE_Msk)
        {
            ch = USART2->RDR; // 从UART2数据寄存器读取接收到的字节
            flag = 1; // 设置flag以指示已接收到数据
        }
        //【4】根据flag判断是否真正收到一个字节的数据
    
        if(flag==1)                        //有数据
        {
            //ch=ch+2;
            //uart_send1(UART_2,ch+1);  //回发接收到的字节
            //uart_send_string(UART_2, " hello! \t");
            
            if(ch=='R')
            {
                //gpio_set(LIGHT_RED,1);
                gpio_set(LIGHT_GREEN,1);
                gpio_set(LIGHT_BLUE,1);
                gpio_set(LIGHT_RED,0);
            }else if(ch=='G')
            {
                gpio_set(LIGHT_RED,1);
                //gpio_set(LIGHT_GREEN,1);
                gpio_set(LIGHT_BLUE,1);
                gpio_set(LIGHT_GREEN,0);
            }else if(ch=='B')
            {    gpio_set(LIGHT_RED,1);
                gpio_set(LIGHT_GREEN,1);
                //gpio_set(LIGHT_BLUE,1);
                gpio_set(LIGHT_BLUE,0);
            }else
            {
                gpio_set(LIGHT_RED,1);
                gpio_set(LIGHT_GREEN,1);
                gpio_set(LIGHT_BLUE,1);
            }
            // 发送下一个字符
            uint32_t t;
            for (t = 0; t < 0xFBBB; t++) {
                if (USART2->ISR & USART_ISR_TXE_Msk) {
                    USART2->TDR = ch + 1; // 发送下一个字符
                    break;
                }
            }
            // 发送字符串 " (直接地址)"
            const char* msg = " hello!";
            for (int i = 0; i < 14; i++) {
                for (t = 0; t < 0xFBBB; t++) {
                    if (USART2->ISR & USART_ISR_TXE_Msk) {
                        USART2->TDR = msg[i];
                        break;
                    }
                }
            }
            flag = 0; // 处理完毕后重置flag
        }  
        
    }
    //【5】开总中断
    ENABLE_INTERRUPTS;
    
    //(2)======主循环部分(结尾)========================================
    
}


       本周的学习任务就是这样了,如内容不正确或有疑问,欢迎评论区留言讨论,我们下周再见!(如有侵权,请联系作者删除)

作者:开飞机的舒克515

物联沃分享整理
物联沃-IOTWORD物联网 » AHL(金葫芦)微控制器嵌入式系统学习笔记 | Week 5

发表回复