找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
查看: 2689|回复: 0
收起左侧

(菜鸟笔记)类比STC51单片机的STM32串口中断纯寄存器操作

[复制链接]
ID:104497 发表于 2021-2-3 15:01 | 显示全部楼层 |阅读模式
      本菜文以STM32的USART1中断为例,类比STC系列的51单片机相对应的操作,粗浅地分析一下STM32串口中断各项配置的含义。本着知其所以然的理念,例程采用了直接访问寄存器的方式,未使用任何库函数。
    尽管STM32和STC相差很大,但它们的串口操作流程还是比较相近的,都包括引脚配置、通讯参数配置、中断配置和中断服务程序等步骤。为了节约时间,前两项内容就不多说了。
    例程的运行条件如下:
1.USART1_RX在PA10上,配置成上拉/下拉输入模式,USART1_TX在PA9上,配置成2MHz的复用推挽模式;
2.9600bps,8N1数据格式;
3.PB0接了一只LED。
    下面进入串口中断配置主题。
    先通过下图回顾一下STC单片机串口1中断逻辑,以及流程中涉及到的概念。
图1.jpg






    寄存器SCON的位1(TI发送中断请求)和位0(RI接收中断请求)是两个中断源,也就是说,在串口发送或接收数据时,硬件会使SCON.1或SCON.0置位,从而产生TI发送中断请求或接收中断请求,但这两个请求能不能被程序响应,还得过ES和EA两关,如果IE.4(ES串口中断使能)和IE.7(EA中断总开关)都为1,TI或RI的请求才能进入中断优先级评估模块。在这个模块里,TI或RI中断会根据寄存器IPH和IP相应位的值分为4个优先级。
    不管TI或RI中断是什么优先级,只要中断被CPU响应了,那就会把串口1中断向量赋值给程序计数器PC,也就是把程序存储器0023H和0024H两个单元的值赋给PC,从而进入串口的中断服务程序,直至执行到RETI指令为止才退出。
    这个过程至少涉及五个主要概念:中断源、中断使能、中断优先级、中断向量、中断服务程序。同样,STM32的串口1中断也有这五个概念。
    下图是STM32F1xx串口1中断逻辑:
图3.jpg
    现在开始直接面向寄存器操作。
1、中断源使能配置
目标寄存器:USART1_CR1串口1控制寄存器1
寄存器地址:0x4001380c
赋值:0x202c
    本例仅开放了接收中断,所以只用到了USART1_CR1寄存器。UE(USART1_CR1.13)、RXNEIE(USART1_CR1.5)、TE(USART1_CR1.3)和RE(USART1_CR1.2)同时置位,这就产生了0x202c的赋值。
图2.jpg
    这是USART1中断源及相应使能位的逻辑图,图中标红线路是可以产生中断的信号,绿色信号代表各个中断线路的使能信号。咱得承认,人家STM32F1xx的中断源还真是挺多的,而且都能单独使能,比STC灵活多了。
2、串口中断开放
    USART1没有像STC那样的中断总开关EA,但它有和ES类似的串口中断使能,所有的中断都有自己的使能开关,它们集合在一个寄存器族NVIC_ISER0~NVIC_ISER7里,每个中断各占其中的一个位。
    Cortex-M3在这方面的做法很有意思,它通过中断向量表为每个中断分配了一个向量号,然后再以这个向量号为索引,找到并操作NVIC寄存器组中相关的寄存器或位,包括后面要说的中断优先级设置也是如此,所以咱们先来看看STM32中断向量表:
图4.jpg
    USART1的向量号是37,请记住这个‘门牌号’。
    开放中断需要操作NVIC_ISERx寄存器:
图5.jpg
    NVIC_ISER共8个寄存器,即NVIC_ISER0---NVIC_ISER7,而STM32F1xx只排了60多个中断号(不同型号可用的中断数量不同),所以实际上只使用了NVIC_ISER0~NVIC_ISER2,其中在NVIC_ISER0占座的是0号窗口看门狗WWDG中断---31号I2C1事件中断,USART1中断在NVIC_ISER1的bit5上。所以,USART1中断使能的操作如下:
目标寄存器:NVIC_ISER1中断设置使能寄存器1
寄存器地址:0xe000e104
赋值:bit5置位
3、中断优先级分组设置
    STM32的中断优先级设置比STC复杂一些,它需要先设置所有中断的优先级分组,也就是通过SCB_AIRCR寄存器将中断优先级分为抢占级和响应级,这是两个与STC不太一样的优先级概念。
    首先需要明确的是,SCB_AIRCR设置的不是某个具体中断的抢占级和响应级,而是针对所有中断。
目标寄存器:SCB_AIRCR系统控制模块应用程序中断和复位控制寄存器
寄存器地址:0xe000ed0c
赋值:0x05fa0400
图6.jpg
    书上说了,每次操作这个寄存器都必须在VECTKEY段上写0x05fa,否则它不认账,会忽略写入操作。SCB_AIRCR的复位值很‘聪明’,恰好是密钥0x05fa的反码。
    在写入密钥的同时对PRIGROUP[2:0]赋值才是核心目的,在英文手册上它的全称是interrupt priority grouping,字面意思是中断优先级分组,而按照它的功能来说,俺觉得叫‘中断优先级分隔位标志’比较贴切,因为它指示出了抢占级和响应级的分隔位置。
    STM32F1xx用1个叫IP[x]的字节来定义优先级,但只用了它的高4位,并且按照PRIGROUP[2:的值把这4位分成抢占和响应两组,以本例中PRIGROUP[2:0]=100(也就是4)为例,IP[x]从位4分隔开来,位7~位5成为抢占组,可形成23(8级)抢占级别,又因为位3~位0没有启用,所以只剩下位4作为响应组,也就只有0、1两个响应级别。
位7
位6
位5
位4
位3~位0
抢占级
响应级
未启用
    在后序的步骤当中,这个IP[x]字节将在具体中断优先级配置当中赋不同的数值,从而确定相应中断的优先级。
    回过头来简单说一说什么是抢占级(Group priority)和响应级(Sub priority)。
    抢占级的数值越低,优先级就越高,可以嵌入低级别中断;响应没有抢先嵌套权,但响应级别高的可以优先执行。假设正在执行一个抢占级为1/响应级为1的A中断,简称为A(g1s1),又来了3个中断,分别是B(g0s1)、C(g2s1)、D(g2s0),那么B就会打断A嵌套进去被执行,没办法,谁让B的抢占级别高呢。等B执行完了,A就会继续执行,C和D也只能在外面等着,因为它们的抢占级别都比A低。等A执行完了就会优先执行D,因为尽管C和D的抢占级是一样的,但D的响应优先级高于C,C只好眼巴巴了。
3、结合PRIGROUP[2:的具体中断优先级设定
    具体到USART1中断,刚才说的PRIGROUP[2:0]和优先级字节IP[x]该怎样落地呢?
目标寄存器:NVIC_IPR9中断优先级设置寄存器
寄存器地址:0xe000e409
赋值:对应IP[37]的字节赋值0x30
    STM32设置了一个叫做NVIC_IPRx的寄存器阵列,每个寄存器可以存放4个中断的优先级8位(1个字节)配置数据,这个阵列与NVIC_ISERx的布局方式类似,只不过NVIC_ISERx是位布局,而NVIC_IPRx则是字节布局:
寄存器
地址
字节4
bit[31:24]
字节3
bit[23:16]
字节2
bit[15:8]
字节0
bit[7:0]
NVIC_IPR0
0xe000e400
中断3
中断2
中断1
中断0
RTC
TAMPER
PVD
WWDG
IP[3]
IP[2]
IP[1]
IP[0]
NVIC_IPR1
0xe000e401
中断7
中断6
中断5
中断4
EXTI1
EXTI0
RCC
FLASH
IP[7]
IP[6]
IP[5]
IP[4]
……
NVIC_IPR9
0xe000e409
中断39
中断38
中断37
中断36
USART3
USART2
USART1
SPI2
IP[39]
IP[38]
IP[37]
IP[36]
……
    IP[37]就是USART1中断优先级配置的字节,
    PRIGROUP[2:0]=4已经确定了抢占级用3个位、响应级用1个位,那么IP[37]=0x30的意思就是配置USART1中断的抢占级为1,响应级也为1。

位7
位6
位5
位4
位3~位0
PRIGROUP
产生的分隔
抢占级
响应级
未启用
IP[37]
0
0
1
1
0000
4、例程
//串口1中断测试程序
//GPIOA/GPIOB相关寄存器定义
#define GPIOA_Base  0x40010800
#define GPIOB_Base  0x40010C00
#define GPIO_ConfigurationRegisterLow        0x00
#define GPIO_ConfigurationRegisterHigh        0x04
#define GPIO_OutputDataRegister              0x0c
#define GPIOA_CRH (*(volatile unsigned int*)(GPIOA_Base + GPIO_ConfigurationRegisterHigh))
#define GPIOB_CRL (*(volatile unsigned int*)(GPIOB_Base + GPIO_ConfigurationRegisterLow))
#define LED1  *(volatile unsigned int *)0x42218180        
//RCC相关寄存器定义
#define RCC_APB2ENR        (*(volatile unsigned int*)0x40021018)
//USART1相关寄存器定义
#define                USART1_Base        0x40013800
#define                USART_DataRegister                0x04
#define                USART_BaudRateRegister                0x08
#define                USART_ControlRegister1                0x0c
#define                USART_ControlRegister2                0x10
#define                USART1_DR        (*(volatile unsigned int*)(USART1_Base + USART_DataRegister))
#define                USART1_BRR        (*(volatile unsigned int*)(USART1_Base + USART_BaudRateRegister))
#define                USART1_CR1        (*(volatile unsigned int*)(USART1_Base + USART_ControlRegister1))
#define                USART1_CR2        (*(volatile unsigned int*)(USART1_Base + USART_ControlRegister2))
#define                ResetUSART1        *(volatile unsigned int*)0x424201b8
#define                USART1_RXNE        *(volatile unsigned int*)0x42270014
//NVIC相关寄存器定义
#define                SCB_AIRCR        *(volatile unsigned int*)0xe000ed0c
#define                NVIC_ISER1        *(volatile unsigned int*)0xe000e104
#define                NVIC_IPR9        *(volatile unsigned int*)0xe000e409
//---------------------------------------
void Delay_ms(unsigned short int MsCount)
{
          unsigned int i = 0;
   while(MsCount--)
   {
        i=8030;
        while(i--);
   }
}

void U1BaudRate(unsigned int PCLK2,unsigned int BaudRate)
//波特率设置        
{
    float UsartDiv;
    unsigned short int Mantissa;//波特率换算参数整数部分
    unsigned short int Fraction;//波特率换算参数小数部分
    UsartDiv = (float)(PCLK2*1000000)/(BaudRate*16);
    Mantissa = UsartDiv;//得到整数部分
    Fraction = (UsartDiv-Mantissa)*16;//得到小数部分
    Mantissa <<= 4;
    Mantissa = Mantissa + Fraction;
    USART1_BRR = Mantissa;
}

int main(void)
{
        /*为节约篇幅,就不把时钟初始化程序列出来了,PCLK2为72MHz.*/
    //开启USART1和GPIOA/GPIOB时钟
    RCC_APB2ENR |=0x0000400c;
    //配置USART1_RX(PA10)和USART1_TX(PA9)
    GPIOA_CRH = (GPIOA_CRH & 0xfffff00f) | 0x000008a0;
    //配置LED1(接在了PB0上)引脚状态
    GPIOB_CRL = (GPIOB_CRL & 0xfffffff0) | 0xee000006;
    //复位USART1
    ResetUSART1=1;
    ResetUSART1=0;
   //配置波特率为9600,数据格式为8N1
    U1BaudRate(72,9600);
   //使能USART1模块,使能发送和接收,开启接收中断
    USART1_CR1 |=0x202c;
    //写入VECTKEY,PRIGROUP赋值为4
    SCB_AIRCR &=0x05faf8ff;
    SCB_AIRCR |=0x05fa0400;
    //使能USART1中断
    NVIC_ISER1 |=1<<5;
    //配置USART1的抢占优先级和响应优先级
    NVIC_IPR9 &=0xffff00ff;
    NVIC_IPR9 |=0x3000;

    while(1)
    {
        LED1 = 0;
        Delay_ms(1000);
        LED1 = 1;
        Delay_ms(1000);               
     }
}

void USART1_IRQHandler(void)
//串口1中断服务程序        
{
    unsigned char tmp;
    if(USART1_RXNE ==1)
    {
        USART1_RXNE=0;
        tmp=USART1_DR;
        USART1_DR=tmp;
    }
}

评分

参与人数 1黑币 +100 收起 理由
admin + 100 共享资料的黑币奖励!

查看全部评分

回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

手机版|小黑屋|51黑电子论坛 |51黑电子论坛6群 QQ 管理员QQ:125739409;技术交流QQ群281945664

Powered by 单片机教程网

快速回复 返回顶部 返回列表