初学者即将毕业实习,希望利用最后两个月的学习顺利找到工作,占个地方做些笔记O(∩_∩)O哈哈~

2019-07-20 23:27发布

本帖最后由 229382777@qq.com 于 2017-2-28 10:58 编辑

        买了原子的mini开发板快一年了,一直都是断断续续的学,学习进度经常被打断,导致现在连前面的知识也忘的差不多了,自身学习的条件可能没有大学生好,即将面临毕业实习,希望在最后这两个月能把mini教程全部过完并掌握,争取出去后能适应的了这方面的工作。在此借原子哥的宝地一用,也方便自己日后的复习,学习过程中可能会出现挺多错误的地方,也希望大家能帮忙指出其中错误的地方。为了保证自己的身体健康,不忙的情况下一般都会晚上9点前进行更新,每天会坚持去操场跑步。希望各位也注重身体健康,身体是革命的本钱O(∩_∩)O!

目录

所在页数 所在楼层楼层主题 1
1
1
1
1
1
1
1
2
2
2
2
2
2
2
2
3
3
3
3
3
3
3
3
4
4
4
4
4
4
4
4
4
4
4
42#  
20#
25#
32#
38#
41#
44#
46#
54#
59#
60#
63#
71#
78#
85#
86#
100#
106#
110#
117#
122#
134#
144#
148#
152#
154#
155#
158#
163#
164#
167#
171#
173#
176#
185#
189#之后硬件篇
SYSTEM文件部分(一)
SYSTEM文件部分(二)
LED灯
按键
串口
外部中断
独立、窗口看门狗
定时器中断
PWM互补输出及死区时间
输入捕获
OLED
LCD
LCD(二)
RTC
待机唤醒
ADC
内部温度传感器
DAC
DMA
IIC
SPI
触摸屏
FLASH模拟EEPROM
内存管理
SD卡
FATFS
汉字显示实验
图片显示
IAP
触控USB鼠标实验
M3内核基础知识
UCOS任务调度
UCOS信号量和邮箱
消息队列、信号量、软件定时器
知识点分享
友情提示: 此问题已得到解决,问题已经关闭,关闭后问题禁止继续编辑,回答。
该问题目前已经被作者或者管理员关闭, 无法添加新回复
55条回答
it_do_just
1楼-- · 2019-07-25 06:33
回复【27楼】正点原子:
---------------------------------
谢谢原子哥鼓励
it_do_just
2楼-- · 2019-07-25 10:01
回复【26楼】龙之谷:
---------------------------------
考虑有外部硬件设备的中断,8259A芯片是一个中断管理芯片,中断的来源除了来自于硬件自身的NMI中断和来自于软件的INT n指令造成的软件中断之外,还有来自于外部硬件设备的中断,这些中断是可屏蔽的。这些中断也都通过PIC(Programmable Interrupt Controller)进行控制,并传递给CPU。
龙之谷
3楼-- · 2019-07-25 12:28
 精彩回答 2  元偷偷看……
it_do_just
4楼-- · 2019-07-25 13:51

实战LED

 
LED灯发光原理
LED(Light Emitting Diode),发光二极管,是一种能够将电能转化为可见光的固态的半导体器件,它可以直接把电转化为光。LED的心脏是一个半导体的晶片,晶片的一端附在一个支架上,一端是负极,另一端连接电源的正极,使整个晶片被环氧树脂封装起来。半导体晶片由两部分组成,一部分是P型半导体,在它里面空穴占主导地位,另一端是N型半导体,在这边主要是电子。但这两种半导体连接起来的时候,它们之间就形成一个P-N结。当电流通过导线作用于这个晶片的时候,电子就会被推向P区,在P区里电子跟空穴复合,然后就会以光子的形式发出能量,这就是LED灯发光的原理。而光的波长也就是光的颜 {MOD},是由形成P-N结的材料决定的。

引用坛友的资料:IO口状态

1、浮空输入:浮空(floating)就是逻辑器件的输入引脚即不接高电平,也不接低电平。由于逻辑器件的内部结构,当它输入引脚悬空时,相当于该引脚接了高电平。一般实际运用时,引脚不建议悬空,易受干扰。 通俗讲就是让管脚什么都不接,浮空着。

2、模拟输入:模拟输入是指传统方式的输入.数字输入是输入PCM数字信号,即0,1的二进制数字信号,通过数模转换,转换成模拟信号,经前级放大进入功率放大器,功率放大器还是模拟的。

3、推挽输出:可以输出高,低电平,连接数字器件; 推挽结构一般是指两个三极管分别受两互补信号的控制,总是在一个三极管导通的时候另一个截止。高低电平由IC的电源低定。

4、开漏输出:输出端相当于三极管的集电极. 要得到高电平状态需要上拉电阻才行. 适合于做电流型的驱动,其吸收电流的能力相对强(一般20ma以内).

5、复用开漏输出、复用推挽输出:可以理解为GPIO口被用作第二功能时的配置情况(即并非作为通用IO口使用)。

 

STM32中选用IO模式

(1)浮空输入_IN_FLOATING ——浮空输入,可以做KEY识别,RX

(2)带上拉输入_IPU——IO内部上拉电阻输入

(3)带下拉输入_IPD—— IO内部下拉电阻输入

(4)模拟输入_AIN ——应用ADC模拟输入,或者低功耗下省电

(5)开漏输出_OUT_OD ——IO输出0接GND,IO输出1,悬空,需要外接上拉电阻,才能实现输出高电平。当输出为1时,IO口的状态由上拉电阻拉高电平,但由于是开漏输出模式,这样IO口也就可以由外部电路改变为低电平或不变。可以读IO输入电平变化,实现C51的IO双向功能

(6)推挽输出_OUT_PP ——IO输出0-接GND, IO输出1 -接VCC,读输入值是未知的

(7)复用功能的推挽输出_AF_PP ——片内外设功能(I2C的SCL,SDA)(8)复用功能的开漏输出_AF_OD——片内外设功能(TX1,MOSI,MISO.SCK.SS)

灌电流,拉电流

拉电流和灌电流是衡量电路输出驱动能力(注意:拉、灌都是对输出端而言的,所以是驱动能力)的参数,这种说法一般用在数字电路中。
由于数字电路的输出只有高、低(0,1)两种电平值,高电平输出时,一般是输出端对负载提供电流,其提供电流的数值叫“拉电流”;低

电平输出时,一般是输出端要吸收负载的电流,其吸收电流的数值叫“灌(入)电流”。

1、逻辑门输出为高电平时的负载电流(为拉电流)。
2、逻辑门输出为低电平时的负载电流(为灌电流)。
3、逻辑门输入为高电平时的电流(为灌电流)。
4、逻辑门输入为低电平时的电流(为拉电流)。

LED灯电路



原子哥板子上的LED接的限流电阻为510欧,通过欧姆定律可以得出加在LED灯上的电流为:3.3/510约等于0.006A即6mA,一般的LED灯5~10mA就能正常亮,所以6mA刚刚好。



以下IO口的电流限制,如果有很多灯的话需要加驱动电路



Flash、RAM(下面有关于ROM、RAM、EEPROM、FLASH区别的资料,附件中)

以下是大神的提供的资料(但是自己运行了一下主函数里面什么都不放也会存在RW=8的情况,这8个字节是Flash的选项字节?)


Code : 代码的大小

RO:常量所占空间

RW:程序中已经初始化的变量所占空间

ZI:未初始化的static变量和全局变量以及堆栈所占的空间

上述参数和芯片Flash以及SRAM的对应关系是

Flash占用大小=Code+RO+RW

SRAM占用大小=RW+ZI


自己在写灯程序的时候发现库函数和寄存器的操作基本一致,但是也看了下官方怎么打包寄存器的,发现这种方式打包方式非常值得学习,后期学着使用,首先最常出现的依旧是assert_param,这个已经有论坛的大神解释过了,以下附上他的解释

断言机制函数assert_param
      我们在分析库函数的时候,几乎每一个函数的原型有这个函数assert_param();下面以assert_param(IS_GPIO_ALL_PERIPH(GPIOx));为例说一下我的理解,函数的参数IS_GPIO_ALL_PERIPH(GPIOx),我们可以寻找到原型
     #define IS_GPIO_ALL_PERIPH(PERIPH) (((*(uint32_t*)&(PERIPH)) == GPIOA_BASE) ||
                                    ((*(uint32_t*)&(PERIPH)) == GPIOB_BASE) ||
                                    ((*(uint32_t*)&(PERIPH)) == GPIOC_BASE) ||
                                    ((*(uint32_t*)&(PERIPH)) == GPIOD_BASE) ||
                                    ((*(uint32_t*)&(PERIPH)) == GPIOE_BASE) ||
                                    ((*(uint32_t*)&(PERIPH)) == GPIOF_BASE) ||
                                    ((*(uint32_t*)&(PERIPH)) == GPIOG_BASE))
这个宏定义的作用就是检查参数PERIPH,判断参数PERIPH是否为GPIOX(A...G)基址中的一个,只要有一个为真则其值为真,否则为假,不用多说,这是C语言中基本的逻辑运算。当然这个库函数也用的很有意思,看:首先对PERIPH进行取址,也就是求地址,&ERIPH,然后对这个地址强制转化为32位的指针,即前面加(uint32_t *),然后通过*进行访问这个地址(指针)中的内容。不多说了,看几遍就能明白。
       下面我们再回到assert_param这个函数,这个函数是哪里的呢?在stm32f10x_conf.h寻找到原型如下:
#ifdef  USE_FULL_ASSERT

    #define assert_param(expr) ((expr) ? (void)0 : assert_failed((uint8_t *)__FILE__, __LINE__))
    void assert_failed(uint8_t* file, uint32_t line);
#else
    #define assert_param(expr) ((void)0)
#endif 
这是一个预编译文件,若是定义了USE_FULL_ASSERT这个文件,则执行后面的文件,我们在程序中一般都没什么定义,即执行后面这个语句((void)0),这个语句不用多想,没有定义USE_FULL_ASSERT就是什么也不执行。说的明白点,对上面的那个语句IS_GPIO_ALL_PERIPH(GPIOx)不执行任何操作。
   若是定义了USE_FULL_ASSERT它,我们调用这个函数assert_param时,及对参数IS_GPIO_ALL_PERIPH(GPIOx)的正确性进行检查,通过一个C语言中的三目运算符来判断,若是返回1,执行语句(void)0,跟上面一样,若是返回0,则执行后面的函数assert_failed((uint8_t *)__FILE__, __LINE__),函数的作用在库函数中有解释,用来指示出错的行数和文件。注意:__FILE__, __LINE__是标准库函数中的宏定义!切记
void assert_failed(uint8_t* file, uint32_t line);刚开始没看明白为什么加在这里,仔细一想是在头文件的函数声明。至于函数实体呢?我们从官方文件的模板中main.c中可以找到。如下:
   void assert_failed(u8* file, u32 line) 
{ /* User can add his own implementation to report the file name and line number, 
   ex: printf("Wrong parameters value: file %s on line %d ", file, line) */ 
/* Infinite loop */ 
while (1) { } 
} 英文注释也说明了怎么应用,通过输入参数来确定位置,最简单的方法就是串口打印了,这个函数的主要思想是在输入参数有问题的时候,但是有编译不出来,它可以帮你检查参数的有效性,好处不必多言,自己领悟就行。


自己看了下库函数打包寄存器的方式:(自己解析的,难免有错,还望指出)


先用#define打包了基地址

#define PERIPH_BASE           ((uint32_t)0x40000000)

#define APB2PERIPH_BASE       (PERIPH_BASE + 0x10000)

GPIOA_BASE = (APB2PERIPH_BASE + 0x0800) = PERIPH_BASE + 0x10000 + 0x0800 = 0x40000000 + 0x10000 + 0x0800

                      = 0x40010800


STM32中文参考手册2.3可以查到寄存器组的起始地址



((GPIO_TypeDef *) GPIOA_BASE)   //这里将GPIOA基地址强制转为了GPIO_TypeDef这个类型的指针

//看看GPIO_TypeDef这个结构体,成员按照定义时的顺序依次存储在连续的内存空间。

typedef struct

{

  __IO uint32_t CRL;      //偏移量0x00

  __IO uint32_t CRH;      //偏移量0x04

  __IO uint32_t IDR;       //偏移量0x08

  __IO uint32_t ODR;      //偏移量0x0C

  __IO uint32_t BSRR;     //偏移量0x10

  __IO uint32_t BRR;      //偏移量0x14

  __IO uint32_t LCKR;     //偏移量0x18

} GPIO_TypeDef;

这样每操作一个结构体成员就达到了操作对应寄存器的地址跟寄存器可以对应上




先附上代码

GPIO_InitTypeDef  GPIO_InitStructure;

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;                         //LED0-->B.5 端口配置

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;          //推挽输出

GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;         //IO口速度为50MHz

GPIO_Init(GPIOA, &GPIO_InitStructure);                               //根据设定参数初始化GPIOB.5

GPIO_SetBits(GPIOA,GPIO_Pin_8);                                        //PB.5 输出高

// GPIO_InitTypeDef这个结构体用于定义个GPIO口,对应引脚、速度、模式

typedef struct

{

  uint16_t GPIO_Pin;          

  GPIOSpeed_TypeDef GPIO_Speed; 

  GPIOMode_TypeDef GPIO_Mode;  

}GPIO_InitTypeDef;


GPIO_Pin:

//只要引脚号不为0则宏定义的值为1,注:#define GPIO_Pin_0 ((uint16_t)0x0001)  引脚号从1开始

#define IS_GPIO_PIN(PIN) ((((PIN) & (uint16_t)0x00) == 0x00) && ((PIN) != (uint16_t)0x00))

 

//宏定义GPIO所有可能的值,如果不在这个范围内会通过assert_param报警

#define IS_GPIO_ALL_PERIPH(PERIPH) (((PERIPH) == GPIOA) ||

                               ((PERIPH) == GPIOB) ||

                               ((PERIPH) == GPIOC) ||

                               ((PERIPH) == GPIOD) ||

                               ((PERIPH) == GPIOE) ||

                               ((PERIPH) == GPIOF) ||

                               ((PERIPH) == GPIOG))

//宏定义引脚对应值

#define GPIO_Pin_0                 ((uint16_t)0x0001) //至于这里为什么是以移位设置而不是递增的方式设置跟库函数有关,下面程序有介绍

#define GPIO_Pin_1                 ((uint16_t)0x0002)

#define GPIO_Pin_2                 ((uint16_t)0x0004) 

#define GPIO_Pin_3                 ((uint16_t)0x0008)

#define GPIO_Pin_4                 ((uint16_t)0x0010)

…………

GPIO_Speed:是个枚举GPIOSpeed_TypeDef类型

typedef enum

{

  GPIO_Speed_10MHz = 1,

  GPIO_Speed_2MHz = 2,

  GPIO_Speed_50MHz = 3

}GPIOSpeed_TypeDef;

GPIO_Speed 只能是在这个枚举范围内选值

 

GPIO_Mode:同样是个枚举GPIOMode_TypeDef类型

typedef enum

{ GPIO_Mode_AIN = 0x0,           //模拟输入模式

  GPIO_Mode_IN_FLOATING = 0x04, //浮空输入模式(复位后的状态)

  GPIO_Mode_IPD = 0x28,         //下拉输入模式

  GPIO_Mode_IPU = 0x48,         //上拉输入模式

  GPIO_Mode_Out_OD = 0x14,   //通用开漏输出模式,至于这里为什么第五位都为1,下面程序中介绍

  GPIO_Mode_Out_PP = 0x10,    //通用推挽输出模式

  GPIO_Mode_AF_OD = 0x1C,    //复用功能开漏输出模式

  GPIO_Mode_AF_PP = 0x18      //复用功能推挽输出模式

}GPIOMode_TypeDef;

GPIO_Mode 只能是在这个枚举范围内选值

对应模式设置如下寄存器



接来下解析一下void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct)这个函数

void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct)    

{   //形参为两个结构体指针类型,上面已经介绍了,一个用于指定GPIO,一个用于初始状态包括速度,模式设置

  uint32_t currentmode = 0x00, currentpin = 0x00, pinpos = 0x00, pos = 0x00;

  uint32_t tmpreg = 0x00, pinmask = 0x00;

//currentmode:模式设置(对应CRH和CRL的32个bit)

//currentpin:保存引脚号

//pinpos:引脚扫描变量,分为扫描GPIO的0~7和8~15引脚

//pos:循环扫描8次,与上面的currentpin比较,相同即对相应的引脚进行设置

//tmpreg:保存上次设置的值不变,跟这次引脚号的设置不冲突,进行&和|运算以后赋给寄存器CRL

//pinmask:对应指定IO口的CNF和MODE位

 

  /* Check the parameters */

  assert_param(IS_GPIO_ALL_PERIPH(GPIOx));                  //检测参数准确性,不在范围内会报错,上面有对assert_param的介绍

  assert_param(IS_GPIO_MODE(GPIO_InitStruct->GPIO_Mode));   //检测参数准确性

  assert_param(IS_GPIO_PIN(GPIO_InitStruct->GPIO_Pin));        //检测参数准确性

 

/*---------------------------- GPIO Mode Configuration -----------------------*/

  currentmode = ((uint32_t)GPIO_InitStruct->GPIO_Mode) & ((uint32_t)0x0F); //记录输入输出和模式

  if ((((uint32_t)GPIO_InitStruct->GPIO_Mode) & ((uint32_t)0x10)) != 0x00)

  { //如果为输出模式则记录下设置的速度,这就是为什么上面定义的时候第五位都是1的原因,用于分开输入和输出标志

    /* Check the parameters */

    assert_param(IS_GPIO_SPEED(GPIO_InitStruct->GPIO_Speed)); //检测参数准确性

    /* Output mode */

    currentmode |= (uint32_t)GPIO_InitStruct->GPIO_Speed;     //保存设置的速度

  }

/*---------------------------- GPIO CRL Configuration ------------------------*/

  /* Configure the eight low port pins */

  if (((uint32_t)GPIO_InitStruct->GPIO_Pin & ((uint32_t)0x00FF)) != 0x00)

  {//如果是GPIO的0~7端口,则设置CRL寄存器,0X00FF代表后面8位为1,这就是为什么上面#define那么定义引脚号的原因,而不用递增

    tmpreg = GPIOx->CRL;    //保存上次的设置

    for (pinpos = 0x00; pinpos < 0x08; pinpos++)//循环8次,依次对应GPIO口0到7

    {

      pos = ((uint32_t)0x01) << pinpos;   //对应8个IO,依次遍历

      /* Get the port pins position */

      currentpin = (GPIO_InitStruct->GPIO_Pin) & pos;  //获取引脚位置

      if (currentpin == pos) //找到了对应的引脚,执行下面的设置

      {

        pos = pinpos << 2;  //向左移两位为x2,即对应到指定IO的4个bit(CNF和MODE)的寄存器

        /* Clear the corresponding low control register bits */

        pinmask = ((uint32_t)0x0F) << pos;   //对应到所指定IO口的CNF和MODE,

        tmpreg &= ~pinmask;              //先清空对应的CNF和MODE

        /* Write the mode configuration in the corresponding bits */

        tmpreg |= (currentmode << pos);    //然后配置对应IO的寄存器模式

        /* Reset the corresponding ODR bit */

        if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPD) //如果对应为下拉输入模式

        {

          GPIOx->BRR = (((uint32_t)0x01) << pinpos);       //清除对应端口,即实现下拉的效果

        }

        else

        {

          /* Set the corresponding ODR bit */

          if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPU) //如果对应为上拉输入模式

          {

            GPIOx->BSRR = (((uint32_t)0x01) << pinpos);      //设置对应端口为1,即实现对应上拉的效果

          }

        }

      }

    }

    GPIOx->CRL = tmpreg;//将设置好的值赋给CRL寄存器

  }
//下面代码几乎和上面没差,用于设置CRH

/*---------------------------- GPIO CRH Configuration ------------------------*/

  /* Configure the eight high port pins */

  if (GPIO_InitStruct->GPIO_Pin > 0x00FF)

  {

    tmpreg = GPIOx->CRH;

    for (pinpos = 0x00; pinpos < 0x08; pinpos++)

    {

      pos = (((uint32_t)0x01) << (pinpos + 0x08));

      /* Get the port pins position */

      currentpin = ((GPIO_InitStruct->GPIO_Pin) & pos);

      if (currentpin == pos)

      {

        pos = pinpos << 2;

        /* Clear the corresponding high control register bits */

        pinmask = ((uint32_t)0x0F) << pos;

        tmpreg &= ~pinmask;

        /* Write the mode configuration in the corresponding bits */

        tmpreg |= (currentmode << pos);

        /* Reset the corresponding ODR bit */

        if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPD)

        {

          GPIOx->BRR = (((uint32_t)0x01) << (pinpos + 0x08));

        }

        /* Set the corresponding ODR bit */

        if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPU)

        {

          GPIOx->BSRR = (((uint32_t)0x01) << (pinpos + 0x08));

        }

      }

    }

    GPIOx->CRH = tmpreg;

  }

}

附上小灯程序(在寄存器工程里面将官方的库函数移植了进来,没有添加任何的库函数包,下面是点灯的几种方式,以后也希望按着库函数这种编程方式写代码)


[mw_shl_code=c,true]#include "sys.h" #include "delay.h" #include "led.h" int main(void) { Stm32_Clock_Init(9); //系统时钟设置,9倍频,72M delay_init(72);//systick使用系统时钟72M的8分频,9Mhz LED_Init(); LED_Init1(); while(1) { /***************1**********************/ GPIOA->ODR &= 0XFFFFFEFF; GPIOA->ODR |= 0<<8; GPIOD->BSRR &= 0XFFFDFFFF; GPIOD->BSRR |= 1<<18; delay_ms(200); GPIOA->ODR &= 0XFFFFFEFF; GPIOA->ODR |= 1<<8; GPIOD->BSRR &= 0XFFFFFFFD; GPIOD->BSRR |= 1<<2; delay_ms(200); /***************2**********************/ LED0 = 0; LED1 = 0; delay_ms(200); LED0 = 1; LED1 = 1; delay_ms(200); /***************3**********************/ GPIO_ResetBits(GPIOA,GPIO_Pin_8); GPIO_SetBits(GPIOD,GPIO_Pin_2); delay_ms(300); GPIO_SetBits(GPIOA,GPIO_Pin_8); GPIO_ResetBits(GPIOD,GPIO_Pin_2); delay_ms(300); } } [/mw_shl_code]




Admin
5楼-- · 2019-07-25 15:57
回复【33楼】229382777@qq.com:
---------------------------------
支持楼主继续写,坚持下来
it_do_just
6楼-- · 2019-07-25 21:11
 精彩回答 2  元偷偷看……

一周热门 更多>