标签: 嵌入式

  • 八段数码管显示解析

    “8段数码管显示”工程解析

    现象:数码管显示“12345678”。

    源代码如下:

    int main()
    {
        Stm32_Clock_Init( 6 );  //传入一个8位二进制数,是倍频系数,使得PLL倍频。也对时钟进行了使能。
        delay_init( 72 );       //延时初始化,就是对系统滴答定时器的初始化。设置了延时倍乘数。
        LED_Init();             //LED使能和初始化。
        LED_SEL = 0;
        show_w1=1;
        show_w2=2;
        show_w3=3;
        show_w4=4;
        show_w5=5;
        show_w6=6;
        show_w7=7;
        show_w8=8;
        while(1)
        {
    
            SetLed(0, show_w1%10);
            delay_ms(1);
            SetLed(1, show_w2%10);
            delay_ms(1);
            SetLed(2, show_w3%10);
            delay_ms(1);
            SetLed(3, show_w4%10);
            delay_ms(1);
            SetLed(4, show_w5%10);
            delay_ms(1);
            SetLed(5, show_w6%10);
            delay_ms(1);
            SetLed(6, show_w7%10);
            delay_ms(1);
            SetLed(7, show_w8%10);
            delay_ms(1);
        }
    }

    此为库函数版。

    库函数解析:

    Stm32_Clock_Init();

    //定义
    //时钟初始化
    void Stm32_Clock_Init(u8 PLL)//传入一个8位的二进制数
    {
        unsigned char temp=0;   
        MYRCC_DeInit();       //复位并配置向量表
        RCC->CR|=0x00010000;  //外部高速时钟使能HSEON
        /*
        HSEON:外部高速时钟使能 (External high-speed clock enable) 
        由软件置’1’或清零。
        */
        while(!(RCC->CR>>17));//等待外部时钟就绪
        RCC->CFGR=0X00000400; //APB1=DIV2;APB2=DIV1;AHB=DIV1;
        PLL-=2;//抵消2个单位
        RCC->CFGR|=PLL<<18;   //设置PLL值 2~16
        RCC->CFGR|=1<<16;     //PLLSRC ON 
        FLASH->ACR|=0x32;     //FLASH 2个延时周期
        //repare
    
        RCC->CR|=0x01000000;  //PLLON
        while(!(RCC->CR>>25));//等待PLL锁定
        RCC->CFGR|=0x00000002;//PLL作为系统时钟    
        while(temp!=0x02)     //等待PLL作为系统时钟设置成功
        {   
            temp=RCC->CFGR>>2;
            temp&=0x03;
        }    
    }            

    总结:

    传入一个8位二进制数,是倍频系数(范围2~16),使得PLL倍频。也对时钟进行了使能和初始化。配置了向量表,复位和配置了相关外设。

    传参类型:u8

    u8是unsigned char类型,8位二进制。

    时钟树

    HSE(High Speed External Clock signal):高速外部时钟信号

    HSI(High Speed Internal Clock signal):高速内部时钟信号

    LSE(Low Speed External Clock signal):低速外部时钟信号

    LSI(Low Speed Internal Clock signal):低速内部时钟信号

    USB预分频器:48MHz,连接USB。

    APB1(低速)外设:UART2,TIMER2等。

    APB2(高速)外设:UART1,SPI1等。

    PLL锁相环:
    作用:

    ​ 抬高频率。

    输入:

    ​ HSE和HSI,通过PLLSRC选择(通过寄存器PLLCFGR的第22位决定:0:HSI;1:HSE)。

    输出:

    ​ PLLCLK(系统时钟)和PLL48CLK。PLLCLK在多路选择开关处,根据需要传给SYSCLK。

    MYRCC_DeInit(void)

    //把所有时钟寄存器复位
    void MYRCC_DeInit(void)
    {                                                               
        RCC->APB1RSTR = 0x00000000;//复位结束             
        RCC->APB2RSTR = 0x00000000; 
    
        RCC->AHBENR = 0x00000014;  //睡眠模式闪存和SRAM时钟使能.其他关闭.      
        RCC->APB2ENR = 0x00000000; //外设时钟关闭.               
        RCC->APB1ENR = 0x00000000;   
        RCC->CR |= 0x00000001;     //使能内部高速时钟HSION                                                                 
        RCC->CFGR &= 0xF8FF0000;   //复位SW[1:0],HPRE[3:0],PPRE1[2:0],PPRE2[2:0],ADCPRE[1:0],MCO[2:0]                     
        RCC->CR &= 0xFEF6FFFF;     //复位HSEON,CSSON,PLLON
        RCC->CR &= 0xFFFBFFFF;     //复位HSEBYP          
        RCC->CFGR &= 0xFF80FFFF;   //复位PLLSRC, PLLXTPRE, PLLMUL[3:0] and USBPRE 
        RCC->CIR = 0x00000000;     //关闭所有中断
        //配置向量表    
    
        #ifdef  VECT_TAB_RAM
        MY_NVIC_SetVectorTable(NVIC_VectTab_RAM, 0x0);
        #else   
        MY_NVIC_SetVectorTable(NVIC_VectTab_FLASH, 0x0);
        #endif
        //配置中断向量表基址和偏移量
    }

    实际上是设置了向量表的复位,外设的复位,关闭所有中断。。

    RCC结构体
    /*------------- Reset and Clock Control --------------*/
    typedef struct
    {
      vu32 CR;        //时钟控制寄存器
      vu32 CFGR;    //时钟配置寄存器
      vu32 CIR;        //时钟中断寄存器
      vu32 APB2RSTR;//APB2 外设复位寄存器
      vu32 APB1RSTR;//APB1 外设复位寄存器
      vu32 AHBENR;    //AHB外设时钟使能寄存器
      vu32 APB2ENR;    //APB2 外设时钟使能寄存器
      vu32 APB1ENR;    //APB1 外设时钟使能寄存器
      vu32 BDCR;    //备份域控制寄存器
      vu32 CSR;        //控制/状态寄存器
    } RCC_TypeDef;    

    向量表的定义。

    vu32(无符号长整型)
    typedef volatile unsigned long  vu32;

    注意到RCC结构体中寄存器的格式是 vu32 。vu32 即 volatile unsigned long

    volatile即“易变的”。

    C++会对代码进行优化,内部不影响IO的代码并不会被编译。

    int main() {
        int i = 0;
        i++;
        cout << "hello world" << endl;
    }

    按照代码,这个程序会在内存中预留int大小的空间,初始化这段内存为0,然后这段内存中的数据加1,最后输出“hello world”到标准输出中。

    但是根据这段代码编译出来的程序(加-O2选项),不会预留int大小的内存空间,更不会对内存中的数字加1。他只会输出“hello world”到标准输出中。

    编译器为了优化代码,修改了程序的逻辑。实际上C++标准是允许写出来的代码和实际生成的程序不一致的。

    虽说优化代码是件好事情,但是也不能让编译器任意修改程序逻辑,不然的话我们没办法写可靠的程序了。所以C++对这种逻辑的改写是有限制的,这个限制就是在编译器修改逻辑后,程序对外界的IO依旧是不变的。

    怎么理解呢?实际上我们可以把我们写出来的程序看做是一个黑匣子,如果按照相同的顺序输入相同的输入,他就每次都会以同样的顺序给出同样的输出。这里的输入输出包括了标准输入输出、文件系统、网络IO、甚至一些system call等等,所有程序外部的事物都包含在内。

    所以对于程序使用者来说,只要两个黑匣子的输入输出是完全一致的,那么这两个黑匣子是一致的,所以编译器可以在这个限制下任意改写程序的逻辑。这个规则又叫as-if原则。

    volatile关键字的作用

    volatile让该变量的地位相当于输入输出,不能改变顺序,不能忽略。所以类似取消优化。

    RCC->CR|=0x00010000;

    第16位:外部高速时钟使能HSEON

    HSEON:外部高速时钟使能 (External high-speed clock enable)

    由软件置’1’或清零。

    while(!(RCC->CR>>17));

    RCC_CR中第17位是HSERDY,即外部时钟就绪位,当外部时钟就绪时,HSERDY位就等于1了,RCC-CR>>17语句将第17位移到第0位,于是RCC-CR就等于0x00000001了,(!(RCC-CR>>17))值即为0了,while(!(RCC-CR>>17))就执行完了。

    RCC->CFGR=0X00000400;

    CFGR的第10位设为1。

    PPRE1[2:0]:低速APB预分频(APB1) (APB low-speed prescaler (APB1))

    由软件置’1’或清’0’来控制低速APB1时钟(PCLK1)的预分频系数。

    警告:软件必须保证APB1时钟频率不超过36MHz。

    0xx:HCLK不分频

    100:HCLK 2分频

    101:HCLK 4分频

    110:HCLK 8分频

    111:HCLK 16分频

    此处是置为100,即HCLK2分频。

    PLL-=2; RCC->CFGR|=PLL<<18;

    此两步是设置了倍频系数,倍频系数设置如下图

    RCC->CFGR|=1<<16;

    设置了CFGR第16位。

    PLLSRC:PLL输入时钟源 (PLL entry clock source)

    由软件置’1’或清’0’来选择PLL输入时钟源。

    只能在关闭PLL时才能写入此位。

    0:HSI振荡器时钟经2分频后作为PLL输入时钟

    1:HSE时钟作为PLL输入时钟。

    RCC->CR|=0x01000000;

    第24位置1,作用是PLL使能。

    PLLON:PLL使能 (PLL enable)

    由软件置’1’或清零。

    当进入待机和停止模式时,该位由硬件清零。

    当PLL时钟被用作或被选择将要作为系统时钟 时,该位不能被清零。

    0:PLL关闭;

    1:PLL使能。

    while(!(RCC->CR>>25));

    等待PLL时钟锁定。

    PLLRDY:PLL时钟就绪标志 (PLL clock ready flag)

    PLL锁定后由硬件置’1’。

    0:PLL未锁定;

    1:PLL锁定。

    RCC->CFGR|=0x00000002;

    PLL输出作为系统时钟;

    SW[1:0]:系统时钟切换 (System clock switch)

    由软件置’1’或清’0’来选择系统时钟源。

    在从停止或待机模式中返回时或直接或间接作为系统时钟的HSE出现故障时,由硬件强制选择 HSI作为系统时钟(如果时钟安全系统已经启动)

    00:HSI作为系统时钟;

    01:HSE作为系统时钟;

    10:PLL输出作为系统时钟;

    11:不可用

    delay_init();

    延时初始化,就是对系统滴答定时器的初始化。设置了延时倍乘数。

    传参:8位二进制数。

    推挽输出

    推挽输出既可以输出低电平,也可以输出高电平,可以直接驱动功耗不大的数字器件。可以理解成,输出0,1。

    CPU 写 1,外部引脚输出高电平。

    CPU 写 0,外部引脚输出低电平。

    开漏输出

    CPU 写 0,外部引脚接地。

    CPU 写 1,外部引脚悬空。

    LED_Init()

    LED初始化。

    LED_SEL = 0;

    总结:

    操作 用 GPIOB_ODR_Addr 和 n 初始化的地址 的内存空间。n = 3 。赋值为0

    BITBAND(addr, bitnum)

    #define BITBAND(addr, bitnum) ((addr & 0xF000 0000)+0x200 0000+((addr &0xFFFFF)<<5)+(bitnum<<2)) 
    1. 将 addr 高四位(28~31)设为1。第25位+1。
    2. 只保留addr的低20位(0-19),左移5位(5-24)。加上bitnum左移2位的数值。

    两步相加。为BITBAND(addr, bitnum)。是一个初始化的地址。

    MEM_ADDR(addr)

    #define MEM_ADDR(addr)  *((volatile unsigned long  *)(addr)) 

    MEM_ADDR(addr)表示addr地址处的值。

    BIT_ADDR(addr, bitnum)

    #define BIT_ADDR(addr, bitnum)   MEM_ADDR(BITBAND(addr, bitnum))

    BIT_ADDR(addr, bitnum) 表示用addr和bitnum初始化的地址的值。

    PBout(n)

    #define PBout(n)   BIT_ADDR(GPIOB_ODR_Addr,n)

    表示用 GPIOB_ODR_Addr 和 n 初始化的地址的值。或者说取地址处的内存空间进行操作。

    GPIOB_ODR_Addr是 (GPIOB_BASE+12) 即 0x40010C0C 。n仍然是一个“偏移量”,实际上是寄存器内的“地址”。

    什么是偏移量?

    ​ 例如GPIOA_BASE = (APB2PERIPH_BASE + 0x0800) 。而 GPIOA 又包含几个寄存器。

    ​ 所以每个寄存器都有一定偏移量。相当于寄存器在这个 GPIO 中的位置。

    LED_SEL

    #define LED_SEL PBout(3) 

    即 n = 3 ;

    show_w

    定义:无符号字符类型,8位二进制。

    void SetLed(u8 w, u8 value)

    void SetLed(u8 w, u8 value)
    {
        SEL0 = w%2;             //取w第0位
        SEL1 = w/2%2;           //取w第1位
        SEL2 = w/4;             //取w第2位
        LedValue(segTable[value]);
    }
    #define SEL0 PBout(0)
    #define SEL1 PBout(1)
    #define SEL2 PBout(2)
    /***************************数码管段选***************************/
    u8 segTable[] = {0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f};
    void LedValue(u8 value)
    {
        GPIOE->ODR &= ~(0xff<<8);       //清除数据,只保留低8位
        GPIOE->ODR |= value<<8;         //设置8~15位
    }

    SetLed 函数传入两个参数,第一个是位选,第二个是段选

    void delay_ms(u16 nms)

    void delay_ms(u16 nms)
    {                  
        u32 temp;          
        SysTick->LOAD=(u32)nms*fac_ms;//时间加载(SysTick->LOAD为24bit)
        SysTick->VAL =0x00;           //清空计数器
        SysTick->CTRL=0x01 ;          //开始倒数  
        do
        {
            temp=SysTick->CTRL;
        }
        while(temp&0x01&&!(temp&(1<<16)));//等待时间到达   
        SysTick->CTRL=0x00;       //关闭计数器
        SysTick->VAL =0X00;       //清空计数器           
    }

    延时函数,以ms为单位。