精华内容
下载资源
问答
  • STM32学习之FSMC

    2021-11-20 21:34:03
    学习资料来自:STM32F407最小系统板开发指南-库函数版本_V1.1.pdf 正点原子,感谢原子哥的开源奉献 正点原子资料下载中心 硬件: STM32F407ZGT6 2.8 LCD MODULE 一个摄像头 注意: 共分为3篇: 如果仅仅想要实现,...

    前言

    上一篇介绍了TFTLCD的基础知识,这一篇来看看,FSMC的基本原理。

    学习资料来自:STM32F407最小系统板开发指南-库函数版本_V1.1.pdf
    正点原子,感谢原子哥的开源奉献
    正点原子资料下载中心

    硬件:
    STM32F407ZGT6
    2.8 LCD MODULE
    一个摄像头

    注意:
    共分为3篇:

    1. STM32学习之TFTLCD
    2. STM32学习之FSMC
    3. STM32学习之使用TFTLCD

    如果仅仅想要实现,可以直接去看最后一篇的使用,前边的基础知识可以跳过

    STM32单片机学习资料均来自 正点原子 ,仅用于学习,如有侵权请联系我删除
    本博客内容原创,创作不易,转载请注明

    基础知识

    STM32F407 或 STM32F417 系列芯片都带有 FSMC 接口,本人的芯片为 STM32F407ZGT6,是带有 FSMC 接口的。
    FSMC,即灵活的静态存储控制器,能够与同步或异步存储器和 16 位 PC 存储器卡连接,STM32F4 的 FSMC 接口支持包括 SRAM、NAND FLASH、NOR FLASH 和 PSRAM 等存储器。

    SRAM与TFTLCD

    STM32F4 的 FSMC 将外部设备分为 2 类:NOR/PSRAM 设备、NAND/PC 卡设备。他们共用地址数据总线等信号,他们具有不同的 CS 以区分不同的设备,比如我们用到的 TFTLCD 就是用的 FSMC_NE4 做片选,其实就是将 TFTLCD 当成 SRAM 来控制。
    这里介绍下为什么可以把 TFTLCD 当成 SRAM 设备用:
    首先我们了解下外部 SRAM的连接,外部 SRAM 的控制一般有:地址线(如 A0~A18)、数据线(如 D0~D15)、写信号(WE)、读信号(OE)、片选信号(CS),如果 SRAM 支持字节控制,那么还有 UB/LB 信号。而 TFTLCD 的信号在前边有介绍,包括:RS、D0~D15、WR、RD、CS、RST 和 BL 等,其中真正在操作 LCD 的时候需要用到的就只有:RS、D0~D15、WR、RD 和 CS。其操作时序和 SRAM的控制完全类似,唯一不同就是 TFTLCD 有 RS 信号,但是没有地址信号。
    TFTLCD 通过 RS 信号来决定传送的数据是数据还是命令,本质上可以理解为一个地址信号,比如我们把 RS 接在 A0 上面,那么当 FSMC 控制器写地址 0 的时候,会使得 A0 变为 0,对 TFTLCD 来说,就是写命令。而 FSMC 写地址 1 的时候,A0 将会变为 1,对 TFTLCD 来说,就是写数据了。这样,就把数据和命令区分开了,他们其实就是对应 SRAM 操作的两个连续地址。当然 RS 也可以接在其他地址线上, STM32F407 最小系统板是把 RS 连接在 A6 上面的。
    STM32F4 的 FSMC 支持 8/16/32 位数据宽度,我们这里用到的 LCD 是 16 位宽度的,所以在设置的时候,选择 16 位宽就 OK 了。

    映射原理

    以下内容涉及到计算机组成原理中存储器的相关知识
    我们再来看看 FSMC 的外部设备地址映像,STM32F4的 FSMC 将外部存储器划分为固定大小为 256M 字节的四个存储块,,如图 16.1.2.2 所示:
    在这里插入图片描述
    从上图可以看出,FSMC 总共管理 1GB 空间,拥有 4 个存储块(Bank),因为我们的屏幕需要 240*320*18/8 =172800 仅用到的是块 1即可,所以仅讨论块 1 的相关配置,其他块的配置,请参考《STM32F4xx 中文参考手册》第 32 章(1191 页)的相关介绍。
    STM32F4 的 FSMC 存储块 1(Bank1)被分为 4 个区,每个区管理 64M 字节空间,每个区都有独立的寄存器对所连接的存储器进行配置。Bank1 的 256M 字节空间由 28 根地址线(HADDR[27:0])寻址。这 里 HADDR 是内 部 AHB 地址总 线,其 中 HADDR[25:0]来自外部存储器地址FSMC_A[25:0],而 HADDR[26:27]对 4 个区进行寻址。如表 16.1.2.1 所示:
    在这里插入图片描述
    表 16.1.2.1 中,我们要特别注意 HADDR[25:0]的对应关系:

    • 当 Bank1 接的是 16 位宽度存储器的时候:HADDR[25:1] -> FSMC_A[24:0]
    • 当 Bank1 接的是 8 位宽度存储器的时候:HADDR[25:0] -> FSMC_A[25:0]

    这里为什么这样接,可以参考博客:https://www.cnblogs.com/tiange-137/p/11820412.html
    不论外部接 8 位/16 位宽设备,FSMC_A[0]永远接在外部设备地址 A[0]。
    这里,TFTLCD使用的是 16 位数据宽度,所以 HADDR[0]并没有用到,只有 HADDR[25:1]是有效的,对应关系变为:HADDR[25:1] -> FSMC_A[24:0],相当于右移了一位,这里请大家特别留意。
    另外,HADDR[27:26]的设置,是不需要我们干预的,比如:当你选择使用 Bank1 的第三个区,即使用 FSMC_NE3 来连接外部设备的时候,即对应了 HADDR[27:26]=10,我们要做的就是配置对应第 3区的寄存器组,来适应外部设备即可。STM32F4 的 FSMC 各Bank配置寄存器如表 16.1.2.2所示:
    在这里插入图片描述

    控制寄存器

    总述

    对于 NOR FLASH 控制器,主要是通过 FSMC_BCRx、FSMC_BTRx 和 FSMC_BWTRx 寄存器设置(其中 x=1~4,对应 4 个区)。通过这 3 个寄存器,可以设置 FSMC 访问外部存储器的时序参数,拓宽了可选用的外部存储器的速度范围。
    FSMC 的 NOR FLASH 控制器支持同步和异步突发两种访问方式。

    • 选用同步突发访问方式时,FSMC 将 HCLK(系统时钟)分频后,发送给外部存储器作为同步时钟信号 FSMC_CLK。此时需要的设置的时间参数有 2 个:
      1. HCLK 与 FSMC_CLK 的分频系数(CLKDIV),可以为 2~16 分频;
      2. 同步突发访问中获得第 1 个数据所需要的等待延迟(DATLAT)。
    • 对于异步突发访问方式,FSMC 主要设置 3 个时间参数:地址建立时间(ADDSET)、数据建立时间(DATAST)和地址保持时间(ADDHLD)。FSMC 综合了 SRAM/ROM、PSRAM 和 NOR Flash 产品的信号特点,定义了 4 种不同的异步时序模型。
      在这里插入图片描述
      选用不同的时序模型时,需要设置不同的时序参数,在实际扩展时,根据选用存储器的特征确定时序模型,从而确定各时间参数与存储器读/写周期参数指标之间的计算关系;利用该计算关系和存储芯片数据手册中给定的参数指标,可计算出 FSMC 所需要的各时间参数,从而对时间参数寄存器进行合理的配置。本章,我们使用异步模式 A(ModeA)方式来控制 TFTLCD

    模式 A 支持独立的读写时序控制,这个对我们驱动 TFTLCD 来说非常有用,因为 TFTLCD在读的时候,一般比较慢,而在写的时候可以比较快,如果读写用一样的时序,那么只能以读的时序为基准,从而导致写的速度变慢,或者在读数据的时候,重新配置 FSMC 的延时,在读操作完成的时候,再配置回写的时序,这样虽然也不会降低写的速度,但是频繁配置,比较麻烦。而如果有独立的读写时序控制,那么我们只要初始化的时候配置好,之后就不用再配置,既可以满足速度要求,又不需要频繁改配置。

    接下来我们讲解一下 Bank1 的几个控制寄存器

    FSMC_BCRx

    首先,我们介绍 SRAM/NOR 闪存片选控制寄存器:FSMC_BCRx(x=1~4),该寄存器各位描述如图 16.1.2.5 所示:
    在这里插入图片描述
    该寄存器我们在本章用到的设置有:EXTMOD、WREN、MWID、MTYP 和 MBKEN 这几
    个设置,我们将逐个介绍。

    • EXTMOD:扩展模式使能位,也就是是否允许读写不同的时序,很明显,我们本章需要读写不同的时序,故该位需要设置为 1。
    • WREN:写使能位。我们需要向 TFTLCD 写数据,故该位必须设置为 1。
    • MWID[1:0]:存储器数据总线宽度。00,表示 8 位数据模式;01 表示 16 位数据模式;10 和 11 保留。我们的 TFTLCD 是 16 位数据线,所以设置 WMID[1:0]=01。
    • MTYP[1:0]:存储器类型。00 表示 SRAM、ROM;01 表示 PSRAM;10 表示 NOR FLASH;11保留。前面提到,我们把 TFTLCD 当成 SRAM 用,所以需要设置 MTYP[1:0]=00。
    • MBKEN:存储块使能位。这个容易理解,我们需要用到该存储块控制 TFTLCD,当然要使能这个存储块了。

    FSMC_BTRx

    我们看看 SRAM/NOR 闪存片选时序寄存器:FSMC_BTRx(x=1~4),该寄存器各位描述如图 16.1.2.6 所示
    在这里插入图片描述
    这个寄存器包含了每个存储器块的控制信息,可以用于 SRAM、ROM 和 NOR 闪存存储器。如果 FSMC_BCRx 寄存器中设置了 EXTMOD 位,则有两个时序寄存器分别对应(本寄存器)和写操作(FSMC_BWTRx 寄存器)。因为我们要求读写分开时序控制,所以 EXTMOD 是使能了的,也就是本寄存器是读操作时序寄存器,控制读操作的相关时序。本章我们要用到的设置有:
    ACCMOD、DATAST 和 ADDSET 这三个设置。

    • ACCMOD[1:0]:访问模式。00 表示访问模式 A;01 表示访问模式 B;10 表示访问模式 C;11 表示访问模式 D,本章我们用到模式 A,故设置为 00。
    • DATAST[7:0]:数据保持时间。0 为保留设置,其他设置则代表保持时间为: DATAST 个 HCLK 时钟周期,最大为 255 个 HCLK 周期。对 ILI9341 来说,其实就是 RD 低电平持续时间,一般为 355ns。而一个 HCLK 时钟周期为 6ns 左右(1/168Mhz),为了兼容其他屏,我们这里设置 DATAST 为 60,也就是 60 个 HCLK 周期,时间大约是 360ns。
    • ADDSET[3:0]:地址建立时间。其建立时间为:ADDSET 个 HCLK 周期,最大为 15 个 HCLK周期。对 ILI9341 来说,这里相当于 RD 高电平持续时间,为 90ns,我们设置 ADDSET 为 15,即 15*6=90ns。

    FSMC_BWTRx

    我们再来看看 SRAM/NOR 闪写时序寄存器:FSMC_BWTRx(x=1~4),该寄存器各
    位描述如图 16.1.2.7 所示:
    在这里插入图片描述
    该寄存器在本章用作写操作时序控制寄存器,需要用到的设置同样是:ACCMOD、DATAST和 ADDSET 这三个设置。这三个设置的方法同 FSMC_BTRx 一模一样,只是这里对应的是写操作的时序,ACCMOD 设置同 FSMC_BTRx 一模一样,同样是选择模式 A,另外 DATAST 和ADDSET 则对应低电平和高电平持续时间,对 ILI9341 来说,这两个时间只需要 15ns 就够了,比读操作快得多。所以我们这里设置 DATAST 为 2,即 3 个 HCLK 周期,时间约为 18ns。然后ADDSET 设置为 3,即 3 个 HCLK 周期,时间为 18ns。

    至此,我们对 STM32F4 的 FSMC 介绍就差不多了,通过以上两个小节的了解,我们可以开始写 LCD 的驱动代码了。不过,这里还要给大家做下科普,在 MDK 的寄存器定义里面,并没有定义 FSMC_BCRx、FSMC_BTRx、FSMC_BWTRx 等这个单独的寄存器,而是将他们进行了一些组合。

    FSMC_BCRx 和 FSMC_BTRx,组合成 BTCR[8]寄存器组,他们的对应关系如下:
    BTCR[0]对应 FSMC_BCR1,BTCR[1]对应 FSMC_BTR1
    BTCR[2]对应 FSMC_BCR2,BTCR[3]对应 FSMC_BTR2
    BTCR[4]对应 FSMC_BCR3,BTCR[5]对应 FSMC_BTR3
    BTCR[6]对应 FSMC_BCR4,BTCR[7]对应 FSMC_BTR4
    FSMC_BWTRx 则组合成 BWTR[7],他们的对应关系如下:
    BWTR[0]对应 FSMC_BWTR1,BWTR[2]对应 FSMC_BWTR2,
    BWTR[4]对应 FSMC_BWTR3,BWTR[6]对应 FSMC_BWTR4,
    BWTR[1]、BWTR[3]和 BWTR[5]保留,没有用到。
    通过上面的讲解,通过对 FSMC 相关的寄存器的描述,大家对 FSMC 的原理有了一个初步的认识,如果还不熟悉的朋友,请一定要搜索网络资料理解 FSMC 的原理。只有理解了原理,使用库函数才可以得心应手。

    库函数讲解

    FSMC 初始化函数

    总述

    根据前面的讲解,初始化 FSMC 主要是初始化三个寄存器 FSMC_BCRx,FSMC_BTRx,FSMC_BWTRx,固件库提供了 3 个 FSMC 初始化函数初始化这三个参数,分别为

        FSMC_NORSRAMInit();
        FSMC_NANDInit();
        FSMC_PCCARDInit();
    

    这三个函数分别用来初始化 4 种类型存储器。这里根据名字就很好判断对应关系。用来初始化NOR 和 SRAM 使用同一个函数 FSMC_NORSRAMInit()。我们使用的 FSMC 初始化函数为 FSMC_NORSRAMInit()。下面我们看看函数定义:

    void FSMC_NORSRAMInit(FSMC_NORSRAMInitTypeDef* FSMC_NORSRAMInitStruct);
    

    这个函数只有一个入口参数,也就是 FSMC_NORSRAMInitTypeDef 类型指针变量,这个结构体的成员变量非常多,因为 FSMC 相关的配置项非常多。

    typedef struct
    {
     uint32_t FSMC_Bank;
     uint32_t FSMC_DataAddressMux;
     uint32_t FSMC_MemoryType;
     uint32_t FSMC_MemoryDataWidth;
     uint32_t FSMC_BurstAccessMode;
     uint32_t FSMC_AsynchronousWait;
     uint32_t FSMC_WaitSignalPolarity;
     uint32_t FSMC_WrapMode;
     uint32_t FSMC_WaitSignalActive;
     uint32_t FSMC_WriteOperation;
     uint32_t FSMC_WaitSignal;
     uint32_t FSMC_ExtendedMode;
     uint32_t FSMC_WriteBurst;
     FSMC_NORSRAMTimingInitTypeDef* FSMC_ReadWriteTimingStruct;
     FSMC_NORSRAMTimingInitTypeDef* FSMC_WriteTimingStruct;
    }FSMC_NORSRAMInitTypeDef;
    

    从这个结构体我们可以看出,前面有 13 个基本类型(unit32_t)的成员变量,这 13 个参数是用来配置片选控制寄存器 FSMC_BCRx
    最后面还有两个SMC_NORSRAMTimingInitTypeDef 指针类型的成员变量。前面我们讲到,FSMC 有读时序和写时序之分,所以这里就是用来设置读时序和写时序的参数了, 也就是说,这两个参数是用来配置寄存器 FSMC_BTRxFSMC_BWTRx,后面我们会讲解到。
    下面我们主要来看看模式 A
    下的相关配置参数:

    • 参数 FSMC_Bank 用来设置使用到的存储块标号和区号,前面讲过,我们是使用的存储块 1 区号 4,所以选择值为 FSMC_Bank1_NORSRAM4
    • 参数 FSMC_MemoryType 用来设置存储器类型,我们这里是 SRAM,所以选择值为FSMC_MemoryType_SRAM
    • 参数 FSMC_MemoryDataWidth 用来设置数据宽度,可选 8 位还是 16 位,这里我们是 16 位数据宽度,所以选择值为 FSMC_MemoryDataWidth_16b
    • 参数 FSMC_WriteOperation 用来设置写使能,毫无疑问,我们前面讲解过我们要向 TFT 写数据,所以要写使能,这里我们选择 FSMC_WriteOperation_Enable
    • 参数 FSMC_ExtendedMode 是设置扩展模式使能位,也就是是否允许读写不同的时序,这里我们采取的读写不同时序,所以设置值为 FSMC_ExtendedMode_Enable
      其余参数可以参考中文参考手册了解相关参数的意思。

    读写时序参数

    接 下 来 我 们 看 看 设 置 读 写 时 序 参 数 的 两 个 变 量 FSMC_ReadWriteTimingStructFSMC_WriteTimingStruct,他们都是 FSMC_NORSRAMTimingInitTypeDef 结构体指针类型,这两个参数在初始化的时候分别用来初始化片选控制寄存器 FSMC_BTRx 和写操作时序控制寄存器 FSMC_BWTRx。 下面我们看看 FSMC_NORSRAMTimingInitTypeDef 类型的定义:

    typedef struct
    {
     uint32_t FSMC_AddressSetupTime;
     uint32_t FSMC_AddressHoldTime;
     uint32_t FSMC_DataSetupTime;
     uint32_t FSMC_BusTurnAroundDuration;
     uint32_t FSMC_CLKDivision;
     uint32_t FSMC_DataLatency;
     uint32_t FSMC_AccessMode;
    }FSMC_NORSRAMTimingInitTypeDef;
    

    这个结构体有 7 个参数用来设置 FSMC 读写时序。其实这些参数的意思我们前面在讲解 FSMC 的时序的时候有提到,主要是设计地址建立保持时间,数据建立时间等等配置,对于我们的实验中,读写时序不一样,读写速度要求不一样,所以对于参数 FSMC_DataSetupTime 设置了不同的值,大家可以对照理解一下。记住,这些参数的意义在前面讲解 FSMC_BTRx 和 FSMC_BWTRx 寄存器的时候都有提到,大家可以翻过去看看。

    FSMC 使能函数

    FSMC 对不同的存储器类型同样提供了不同的使能函数:

    void FSMC_NORSRAMCmd(uint32_t FSMC_Bank, FunctionalState NewState);
    void FSMC_NANDCmd(uint32_t FSMC_Bank, FunctionalState NewState);
    void FSMC_PCCARDCmd(FunctionalState NewState);
    

    这个就比较好理解,我们这里不讲解,我们是 SRAM,所以使用的是第一个函数。

    总结

    FSMC部分的知识比较枯燥和困难,和计算机组成原理中的存储器有很多联系,最后一篇是用 原子哥 的代码了,终于到实战部分了!未完待续,=w=

    展开全文
  • STM32学习心得

    2021-03-14 13:10:46
    32常用的定时器功能有 PWM波输出, PWM波输入判断占空比和频率, 输出比较功能(可以输出变频率的PWM) 输入捕获高低电平(超声波一系列的模块) 使用定时器进行延时的功能 定时器的编码器模式(如

    从点亮LED灯开始的入坑之路
    建议点完灯之后立即学习怎么使用
    小屏幕OLED(几个脚都好用)
    接下来可以开始串口通信的学习(尤其是收发中断)
    ADC的使用非常重要:
    很多接触到的传感器都是输出模拟量的,而且如果需要快速固定频率采集数据的话,建议学习一下定时器触发数据通过DMA传递数据(或者说ADC的定时器中断触发模式)
    接下来可以学习一下定时器的使用了
    32常用的定时器功能有
    1.PWM波输出,
    2.PWM波输入判断占空比和频率,
    3.输出比较功能(可以输出变频率的PWM)
    4.输入捕获高低电平(超声波一系列的模块)
    5.使用定时器进行延时的功能
    6.定时器的编码器模式(如果准备使用编码器电机一定要会用)

    定时器这些功能掌握了基本上常见问题都可以解决。
    接下来该学习一下中断怎么使用了
    当然中断包括定时器中断(上面提到定时器的好多功能都需要使用中断的)
    外部中断很重要,尤其是需要时刻反应不断变化的自动控制系统,学会外部中断才能控制自如

    当然这些的基础建立在对C语言基础规则的灵活掌握,如果掌握不到位会遇到很多困难的突然兴致来了写了一下,不懂勿喷勿扰欢迎大神评论补充

    展开全文
  • STM32学习之使用TFTLCD

    2021-11-20 21:36:48
    前言 ...学习资料来自:STM32F407最小系统板开发指南-库函数版本_V1.1.pdf 正点原子,感谢原子哥的开源奉献 正点原子资料下载中心 硬件: STM32F407ZGT6 2.8 LCD MODULE 一个摄像头 注意: 共分为3篇

    前言

    终于到了代码部分, 前边的所有知识铺垫都是为了这部分的代码,能得心应手,其实我个人认为,原子哥的驱动代码已经写的很好很完善了,所以这部分主要是学习原子哥的代码怎么用,我手里刚好也有一个 原子哥 的LED屏幕,也是用FSMC控制TFTLCD,就按照原子哥的教程一步一步来了。

    学习资料来自:STM32F407最小系统板开发指南-库函数版本_V1.1.pdf
    正点原子,感谢原子哥的开源奉献
    正点原子资料下载中心

    硬件:
    STM32F407ZGT6
    2.8 LCD MODULE
    一个摄像头

    注意:
    共分为3篇:

    1. STM32学习之TFTLCD
    2. STM32学习之FSMC
    3. STM32学习之使用TFTLCD

    如果仅仅想要实现,可以直接去看最后一篇的使用,前边的基础知识可以跳过
    如果不想看代码解释,直接调到最后一部分直接看应用即可

    STM32单片机学习资料均来自 正点原子 ,仅用于学习,如有侵权请联系我删除
    本博客内容原创,创作不易,转载请注明

    硬件

    这里我们介绍一下 TFTLCD 模块与 ALIETEK STM32F407最小系统板的连接, ALIENTEK TFTLCD 模块接口关系如图 16.2.1 所示:
    在这里插入图片描述
    图 16.2.1 中圈出来的部分就是连接 TFTLCD 模块的接口,在硬件上,TFTLCD 模块与STM32F407 最小系统板的 IO 口对应关系如下:

    • LCD_BL(背光控制)对应 PB0;
    • LCD_CS 对应 PG12 即 FSMC_NE4;
    • LCD _RS 对应 PF12 即 FSMC_A6;
    • LCD _WR 对应 PD5 即 FSMC_NWE;
    • LCD _RD 对应 PD4 即 FSMC_NOE;
    • LCD _D[15:0]则直接连接在 FSMC_D15~FSMC_D0;
      STM32F407 最小系统板通过 FPC 排线及转接板和 TFTLCD 模块连接。

    软件讲解

    接下来就是代码讲解了
    打开下载好的资料中的 TFTLCD 显示实验工程
    可以看到我们添加了两个文件 lcd.c 和头文件 lcd.h
    同时,FSMC 相关的库函数分布在 stm32f4xx_fsmc.c 文件和头文件 stm32f4xx_fsmc.h 中。所以我们在工程中要引入 stm32f4xx_fsmc.c 源文件。
    在 lcd.c 里面代码比较多,只针对几个重要的函数进行讲解。

    基础数据结构

    本实验,我们用到 FSMC 驱动 LCD,通过前面的介绍,我们知道 TFTLCD 的 RS 接在 FSMC的 A6 上面,CS 接在 FSMC_NE4 上,并且是 16 位数据总线。即我们使用的是 FSMC 存储器 1的第 4 区,我们定义如下 LCD 操作结构体(在 lcd.h 里面定义):

    //LCD 操作结构体
    typedef struct
    {
        vu16 LCD_REG;
        vu16 LCD_RAM;
    } LCD_TypeDef;
    //使用 NOR/SRAM 的 Bank1.sector4,地址位 HADDR[27,26]=11 A6 作为数据命令区分线
    //注意 16 位数据总线时,STM32 内部地址会右移一位对齐!
    #define LCD_BASE ((u32)(0x6C000000 | 0x0000007E))
    #define LCD ((LCD_TypeDef *) LCD_BASE)
    
    

    其中 LCD_BASE,必须根据我们外部电路的连接来确定,我们使用 Bank1.sector4 就是从地址 0X6C000000 开始,而 0X0000007E,则是 A6 的偏移量,这里很多朋友不理解这个偏移量的概念,简单说明下:
    以 A6 为例,7E 转换成二进制就是:1111110,而 16 位数据时,地址右移一位对齐,那么实际对应到地址引脚的时候,就是:A6:A0=0111111,此时 A6 是 0,但是如果 16 位地址再加 1(注意:对应到 8 位地址是加 2,即 7E+0X02),那么:A6:A0=1000000,此时 A6 就是 1 了,即实现了对 RS 的 0 和 1 的控制。我们将这个地址强制转换为 LCD_TypeDef 结构体地址,那么可以得到 LCD->LCD_REG 的地址就是 0X6C00,007E,对应 A6 的状态为 0(即 RS=0),而 LCD-> LCD_RAM 的地址就是0X6C00,0080(结构体地址自增),对应 A6 的状态为 1(即 RS=1)。所以,有了这个定义,当我们要往 LCD 写命令/数据的时候,可以这样写:

    LCD->LCD_REG=CMD; //写命令
    LCD->LCD_RAM=DATA; //写数据
    

    而读的时候反过来操作就可以了,如下所示:

    CMD= LCD->LCD_REG;//读 LCD 寄存器
    DATA = LCD->LCD_RAM;//读 LCD 数据
    

    这其中,CS、WR、RD 和 IO 口方向都是由 FSMC 控制,不需要我们手动设置了。接下来,
    我们先介绍一下 lcd.h 里面的另一个重要结构体:

    //LCD 重要参数集
    typedef struct
    {
        u16 width; //LCD 宽度
        u16 height; //LCD 高度
        u16 id; //LCD ID
        u8 dir; //横屏还是竖屏控制:0,竖屏;1,横屏。
        u16 wramcmd; //开始写 gram 指令
        u16 setxcmd; //设置 x 坐标指令
        u16 setycmd; //设置 y 坐标指令
    }_lcd_dev;
    //LCD 参数
    extern _lcd_dev lcddev; //管理 LCD 重要参数
    

    该结构体用于保存一些 LCD 重要参数信息,比如 LCD 的长宽、LCD ID(驱动 IC 型号)、LCD 横竖屏状态等,这个结构体虽然占用了十几个字节的内存,但是却可以让我们的驱动函数支持不同尺寸的 LCD,同时可以实现 LCD 横竖屏切换等重要功能,所以还是利大于弊的。
    有了以上了解,下面我们开始介绍 lcd.c 里面的一些重要函数。先看 7 个简单,但是很重要的函数:

    7个基础函数

    //regval:寄存器值
    void LCD_WR_REG(vu16 regval)
    {
    	regval=regval;		//使用-O2优化的时候,必须插入的延时
    	LCD->LCD_REG=regval;//写入要写的寄存器序号
    }
    //写LCD数据
    //data:要写入的值
    void LCD_WR_DATA(vu16 data)
    {
    	data=data;			//使用-O2优化的时候,必须插入的延时
    	LCD->LCD_RAM=data;
    }
    //读LCD数据
    //返回值:读到的值
    u16 LCD_RD_DATA(void)
    {
    	vu16 ram;			//防止被优化
    	ram=LCD->LCD_RAM;
    	return ram;
    }
    //写寄存器
    //LCD_Reg:寄存器地址
    //LCD_RegValue:要写入的数据
    void LCD_WriteReg(u16 LCD_Reg,u16 LCD_RegValue)
    {
    	LCD->LCD_REG = LCD_Reg;		//写入要写的寄存器序号
    	LCD->LCD_RAM = LCD_RegValue;//写入数据
    }
    //读寄存器
    //LCD_Reg:寄存器地址
    //返回值:读到的数据
    u16 LCD_ReadReg(u16 LCD_Reg)
    {
    	LCD_WR_REG(LCD_Reg);		//写入要读的寄存器序号
    	delay_us(5);
    	return LCD_RD_DATA();		//返回读到的值
    }
    //开始写GRAM
    void LCD_WriteRAM_Prepare(void)
    {
     	LCD->LCD_REG=lcddev.wramcmd;
    }
    //LCD写GRAM
    //RGB_Code:颜色值
    void LCD_WriteRAM(u16 RGB_Code)
    {
    	LCD->LCD_RAM = RGB_Code;//写十六位GRAM
    }
    

    因为 FSMC 自动控制了WR/RD/CS 等这些信号,所以这 7 个函数实现起来都非常简单,我们就不多说,注意,上面有几个函数,我们添加了一些对 MDK –O2 优化的支持,去掉的话,在-O2 优化的时候会出问题。这些函数实现功能见函数前面的备注,通过这几个简单函数的组合,我们就可以对 LCD 进行各种操作了。

    常用函数

    void LCD_SetCursor(u16 Xpos, u16 Ypos)

    第七个要介绍的函数是坐标设置函数,该函数代码如下:

    //设置光标位置
    //Xpos:横坐标
    //Ypos:纵坐标
    void LCD_SetCursor(u16 Xpos, u16 Ypos)
    {
     	if(lcddev.id==0X9341||lcddev.id==0X5310)
    	{
    		LCD_WR_REG(lcddev.setxcmd);
    		LCD_WR_DATA(Xpos>>8);LCD_WR_DATA(Xpos&0XFF);
    		LCD_WR_REG(lcddev.setycmd);
    		LCD_WR_DATA(Ypos>>8);LCD_WR_DATA(Ypos&0XFF);
    	}else if(lcddev.id==0X6804)
    	{
    		if(lcddev.dir==1)Xpos=lcddev.width-1-Xpos;//横屏时处理
    		LCD_WR_REG(lcddev.setxcmd);
    		LCD_WR_DATA(Xpos>>8);LCD_WR_DATA(Xpos&0XFF);
    		LCD_WR_REG(lcddev.setycmd);
    		LCD_WR_DATA(Ypos>>8);LCD_WR_DATA(Ypos&0XFF);
    	}else if(lcddev.id==0X1963)
    	{
    		if(lcddev.dir==0)//x坐标需要变换
    		{
    			Xpos=lcddev.width-1-Xpos;
    			LCD_WR_REG(lcddev.setxcmd);
    			LCD_WR_DATA(0);LCD_WR_DATA(0);
    			LCD_WR_DATA(Xpos>>8);LCD_WR_DATA(Xpos&0XFF);
    		}else
    		{
    			LCD_WR_REG(lcddev.setxcmd);
    			LCD_WR_DATA(Xpos>>8);LCD_WR_DATA(Xpos&0XFF);
    			LCD_WR_DATA((lcddev.width-1)>>8);LCD_WR_DATA((lcddev.width-1)&0XFF);
    		}
    		LCD_WR_REG(lcddev.setycmd);
    		LCD_WR_DATA(Ypos>>8);LCD_WR_DATA(Ypos&0XFF);
    		LCD_WR_DATA((lcddev.height-1)>>8);LCD_WR_DATA((lcddev.height-1)&0XFF);
    
    	}else if(lcddev.id==0X5510)
    	{
    		LCD_WR_REG(lcddev.setxcmd);LCD_WR_DATA(Xpos>>8);
    		LCD_WR_REG(lcddev.setxcmd+1);LCD_WR_DATA(Xpos&0XFF);
    		LCD_WR_REG(lcddev.setycmd);LCD_WR_DATA(Ypos>>8);
    		LCD_WR_REG(lcddev.setycmd+1);LCD_WR_DATA(Ypos&0XFF);
    	}else
    	{
    		if(lcddev.dir==1)Xpos=lcddev.width-1-Xpos;//横屏其实就是调转x,y坐标
    		LCD_WriteReg(lcddev.setxcmd, Xpos);
    		LCD_WriteReg(lcddev.setycmd, Ypos);
    	}
    }
    

    该函数实现将 LCD 的当前操作点设置到指定坐标(x,y)。因为 9341/5310/6804/5510 等的设置同其他屏有些不太一样,所以进行了区别对待。

    void LCD_DrawPoint(u16 x,u16 y)

    接下来我们介绍第八个函数:画点函数。该函数实现代码如下:

    //画点
    //x,y:坐标
    //POINT_COLOR:此点的颜色
    void LCD_DrawPoint(u16 x,u16 y)
    {
    	LCD_SetCursor(x,y);		//设置光标位置
    	LCD_WriteRAM_Prepare();	//开始写入GRAM
    	LCD->LCD_RAM=POINT_COLOR;
    }
    

    该函数实现比较简单,就是先设置坐标,然后往坐标写颜色。其中 POINT_COLOR 是我们定义的一个全局变量,用于存放画笔颜色,顺带介绍一下另外一个全局变量:BACK_COLOR该变量代表 LCD 的背景色。
    LCD_DrawPoint 函数虽然简单,但是至关重要,其他几乎所有上层函数,都是通过调用这个函数实现的。

    u16 LCD_ReadPoint(u16 x,u16 y)

    有了画点,当然还需要有读点的函数,第九个介绍的函数就是读点函数,用于读取 LCD的 GRAM,这里说明一下,为什么 OLED 模块没做读 GRAM 的函数,而这里做了。因为 OLED 模块是单色的,所需要全部 GRAM 也就 1K 个字节,而 TFTLCD 模块为彩色的,点数也比 OLED 模块多很多,以 16 位色计算,一款 320×240 的液晶,需要 320×240×2 个字节来存储颜色值,也就是也需要 150K 字节,这对任何一款单片机来说,都不是一个小数目了。而且我们在图形叠加的时候,可以先读回原来的值,然后写入新的值,在完成叠加后,我们又恢复原来的值。这样在做一些简单菜单的时候,是很有用的。这里我们读取 TFTLCD 模块数据的函数为 LCD_ReadPoint,该函数直接返回读到的 GRAM 值。该函数使用之前要先设置读取的 GRAM地址,通过 LCD_SetCursor 函数来实现。LCD_ReadPoint 的代码如下:

    //读取个某点的颜色值
    //x,y:坐标
    //返回值:此点的颜色
    u16 LCD_ReadPoint(u16 x,u16 y)
    {
     	u16 r=0,g=0,b=0;
    	if(x>=lcddev.width||y>=lcddev.height)return 0;	//超过了范围,直接返回
    	LCD_SetCursor(x,y);
    	if(lcddev.id==0X9341||lcddev.id==0X6804||lcddev.id==0X5310||lcddev.id==0X1963)LCD_WR_REG(0X2E);//9341/6804/3510/1963 发送读GRAM指令
    	else if(lcddev.id==0X5510)LCD_WR_REG(0X2E00);	//5510 发送读GRAM指令
    	else LCD_WR_REG(0X22);      		 			//其他IC发送读GRAM指令
    	if(lcddev.id==0X9320)opt_delay(2);				//FOR 9320,延时2us
     	r=LCD_RD_DATA();								//dummy Read
    	if(lcddev.id==0X1963)return r;					//1963直接读就可以
    	opt_delay(2);
     	r=LCD_RD_DATA();  		  						//实际坐标颜色
     	if(lcddev.id==0X9341||lcddev.id==0X5310||lcddev.id==0X5510)		//9341/NT35310/NT35510要分2次读出
     	{
    		opt_delay(2);
    		b=LCD_RD_DATA();
    		g=r&0XFF;		//对于9341/5310/5510,第一次读取的是RG的值,R在前,G在后,各占8位
    		g<<=8;
    	}
    	if(lcddev.id==0X9325||lcddev.id==0X4535||lcddev.id==0X4531||lcddev.id==0XB505||lcddev.id==0XC505)return r;	//这几种IC直接返回颜色值
    	else if(lcddev.id==0X9341||lcddev.id==0X5310||lcddev.id==0X5510)return (((r>>11)<<11)|((g>>10)<<5)|(b>>11));//ILI9341/NT35310/NT35510需要公式转换一下
    	else return LCD_BGR2RGB(r);						//其他IC
    }
    

    LCD_ReadPoint 函数中,因为我们的代码不止支持一种 LCD 驱动器,所以,我们根据不同的 LCD 驱动器((lcddev.id)型号,执行不同的操作,以实现对各个驱动器兼容,提高函数的通用性。

    void LCD_ShowChar(u16 x,u16 y,u8 num,u8 size,u8 mode)

    第十个要介绍的是字符显示函数 LCD_ShowChar,该函数同前面 OLED 模块的字符显示函数差不多,但是这里的字符显示函数多了 1 个功能,就是可以以叠加方式显示,或者以非叠加方式显示。叠加方式显示多用于在显示的图片上再显示字符。非叠加方式一般用于普通的显示。
    该函数实现代码如下:

    //在指定位置显示一个字符
    //x,y:起始坐标
    //num:要显示的字符:" "--->"~"
    //size:字体大小 12/16/24
    //mode:叠加方式(1)还是非叠加方式(0)
    void LCD_ShowChar(u16 x,u16 y,u8 num,u8 size,u8 mode)
    {
        u8 temp,t1,t;
    	u16 y0=y;
    	u8 csize=(size/8+((size%8)?1:0))*(size/2);		//得到字体一个字符对应点阵集所占的字节数
     	num=num-' ';//得到偏移后的值(ASCII字库是从空格开始取模,所以-' '就是对应字符的字库)
    	for(t=0;t<csize;t++)
    	{
    		if(size==12)temp=asc2_1206[num][t]; 	 	//调用1206字体
    		else if(size==16)temp=asc2_1608[num][t];	//调用1608字体
    		else if(size==24)temp=asc2_2412[num][t];	//调用2412字体
    		else return;								//没有的字库
    		for(t1=0;t1<8;t1++)
    		{
    			if(temp&0x80)LCD_Fast_DrawPoint(x,y,POINT_COLOR);
    			else if(mode==0)LCD_Fast_DrawPoint(x,y,BACK_COLOR);
    			temp<<=1;
    			y++;
    			if(y>=lcddev.height)return;		//超区域了
    			if((y-y0)==size)
    			{
    				y=y0;
    				x++;
    				if(x>=lcddev.width)return;	//超区域了
    				break;
    			}
    		}
    	}
    }
    

    LCD_ShowChar 函数里面,我们采用快速画点函数 LCD_Fast_DrawPoint 来画点显示字符,该函数同 LCD_DrawPoint 一样,只是带了颜色参数,且减少了函数调用的时间,详见本例程源码。该代码中我们用到了三个字符集点阵数据数组 asc2_2412、asc2_1206 和 asc2_1608,
    这几个字符集的点阵数据的提取方式,同十七章介绍的提取方法是一模一样的。详细请参考第
    十七章。

    void LCD_Init(void)

    最后,我们再介绍一下 TFTLCD 模块的初始化函数 LCD_Init,该函数先初始化 STM32 与 TFTLCD 连接的 IO 口,并配置 FSMC 控制器,然后读取 LCD 控制器的型号,根据控制 IC 的型号执行不同的初始化代码,代码过多,不在再贴出
    从初始化代码可以看出,LCD 初始化步骤为①~⑥在代码中标注:

    • ① GPIO,FSMC 使能。
    • ② GPIO 初始化:GPIO_Init()函数。
    • ③ 设置引脚复用映射。
    • ④ FSMC 初始化:FSMC_NORSRAMInit()函数。
    • ⑤ FSMC 使能:FSMC_NORSRAMCmd()函数。
    • ⑥ 不同的 LCD 驱动器的初始化代码。
      该函数先对 FSMC 相关 IO 进行初始化,然后是 FSMC 的初始化,这个我们在前面都有介绍,最后根据读到的 LCD ID,对不同的驱动器执行不同的初始化代码,从上面的代码可以看出,这个初始化函数可以针对十多款不同的驱动 IC 执行初始化操作,这样大大提高了整个程序的通用性。大家在以后的学习中应该多使用这样的方式,以提高程序的通用性、兼容性。

    特别注意:本函数使用了 printf 来打印 LCD ID,所以,如果你在主函数里面没有初始化串口,那么将导致程序死在 printf 里面!!如果不想用 printf,那么请注释掉它。

    常用函数总结

    此部分类似于API,如果想要仔细了解,请见上边的软件详解部分

    lcd.h文件

    void LCD_Init(void);													   	//初始化
    void LCD_DisplayOn(void);													//开显示
    void LCD_DisplayOff(void);													//关显示
    void LCD_Clear(u16 Color);	 												//清屏
    void LCD_SetCursor(u16 Xpos, u16 Ypos);										//设置光标
    void LCD_DrawPoint(u16 x,u16 y);											//画点
    void LCD_Fast_DrawPoint(u16 x,u16 y,u16 color);								//快速画点
    u16  LCD_ReadPoint(u16 x,u16 y); 											//读点
    void LCD_Draw_Circle(u16 x0,u16 y0,u8 r);						 			//画圆
    void LCD_DrawLine(u16 x1, u16 y1, u16 x2, u16 y2);							//画线
    void LCD_DrawRectangle(u16 x1, u16 y1, u16 x2, u16 y2);		   				//画矩形
    void LCD_Fill(u16 sx,u16 sy,u16 ex,u16 ey,u16 color);		   				//填充单色
    void LCD_Color_Fill(u16 sx,u16 sy,u16 ex,u16 ey,u16 *color);				//填充指定颜色
    void LCD_ShowChar(u16 x,u16 y,u8 num,u8 size,u8 mode);						//显示一个字符
    void LCD_ShowNum(u16 x,u16 y,u32 num,u8 len,u8 size);  						//显示一个数字
    void LCD_ShowxNum(u16 x,u16 y,u32 num,u8 len,u8 size,u8 mode);				//显示 数字
    void LCD_ShowString(u16 x,u16 y,u16 width,u16 height,u8 size,u8 *p);		//显示一个字符串,12/16字体
    
    void LCD_WriteReg(u16 LCD_Reg, u16 LCD_RegValue);
    u16 LCD_ReadReg(u16 LCD_Reg);
    void LCD_WriteRAM_Prepare(void);
    void LCD_WriteRAM(u16 RGB_Code);
    void LCD_SSD_BackLightSet(u8 pwm);							//SSD1963 背光控制
    void LCD_Scan_Dir(u8 dir);									//设置屏扫描方向
    void LCD_Display_Dir(u8 dir);								//设置屏幕显示方向
    void LCD_Set_Window(u16 sx,u16 sy,u16 width,u16 height);	//设置窗口
    

    main函数示例

    LCD 驱动相关的函数就给大家讲解到这里。接下来,我们看看主函数代码如下:

    int main(void)
    {
        u8 x=0;
        u8 lcd_id[12]; //存放 LCD ID 字符串
        NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置系统中断优先级分组 2
        delay_init(168); //初始化延时函数
        uart_init(115200); //初始化串口波特率为 115200
        LED_Init(); //初始化 LED
        LCD_Init(); //初始化 LCD FSMC 接口
        POINT_COLOR=RED;
        sprintf((char*)lcd_id,"LCD ID:%04X",lcddev.id);//将 LCD ID 打印到 lcd_id 数组。
     while(1)
    {
        switch(x)
        {
            case 0:LCD_Clear(WHITE);break;
            case 1:LCD_Clear(BLACK);break;
            case 2:LCD_Clear(BLUE);break;
            case 3:LCD_Clear(RED);break;
            case 4:LCD_Clear(MAGENTA);break;
            case 5:LCD_Clear(GREEN);break;
            case 6:LCD_Clear(CYAN);break;
            case 7:LCD_Clear(YELLOW);break;
            case 8:LCD_Clear(BRRED);break;
            case 9:LCD_Clear(GRAY);break;
            case 10:LCD_Clear(LGRAY);break;
            case 11:LCD_Clear(BROWN);break;
        }
        POINT_COLOR=RED;
        LCD_ShowString(30,40,210,24,24,"Explorer STM32F4");
        LCD_ShowString(30,70,200,16,16,"TFTLCD TEST");
        LCD_ShowString(30,90,200,16,16,"ATOM@ALIENTEK");
        LCD_ShowString(30,110,200,16,16,lcd_id); //显示 LCD ID
        LCD_ShowString(30,130,200,12,12,"2014/5/4");
         x++;
        if(x==12)x=0;
        LED0=!LED0;delay_ms(1000);
     }
    }
    

    该部分代码将显示一些固定的字符,字体大小包括24*12、16*8 和 12*6等三种,同时显示LCD 驱动 IC 的型号,然后不停的切换背景颜色,每 1s 切换一次。而 LED0 也会不停的闪烁,指示程序已经在运行了。其中我们用到一个 sprintf 的函数,该函数用法同 printf,只是 sprintf把打印内容输出到指定的内存区间上,sprintf 的详细用法,请百度。
    另外特别注意:uart_init 函数,不能去掉,因为在 LCD_Init 函数里面调用了 printf,所以一旦你去掉这个初始化,就会死机了!实际上,只要你的代码有用到 printf,就必须初始化串口,否则都会死机,即停在 usart.c 里面的 fputc 函数,出不来。

    在编译通过之后,我们开始下载验证代码。

    最后经过板载测试,代码正确且运行正常,学到了学到了,终于点亮了这块屏幕。=w=

    展开全文
  • STM32 学习12蜂鸣器实验一、蜂鸣器简介二、程序开发1. 位操作定义2. 引脚定义3. GPIO 初始化4. 主函数输出脉冲三、音乐盒程序开发1. 项目架构2. 主程序入口3. beep.h 头文件4. beep.c 乐谱文件 一、蜂鸣器简介 蜂鸣...

    一、蜂鸣器简介

    蜂鸣器主要有: 压电式蜂鸣器和电磁式蜂鸣器。

    • 压电式蜂鸣器,由多谐振荡器、压电蜂鸣片、阻抗匹配器及共鸣箱、外壳等组成,一般是无源是蜂鸣器。
    • 电磁式蜂鸣器:由振荡器、电磁线圈、此贴、震动膜片及外壳等组成,一般要接电源。

    通过改变单片机引脚输出波形的频率,就可以控制蜂鸣器发出声音的银色、音调,脉冲一般1.5-5KHz。
    改变输出电平的高低电平占空比,可一控制蜂鸣器声音大小。

    本文使用普中开发板,其中:

    • P26806L使用的是无源蜂鸣器。
    • P2806D 使用的是有源蜂鸣器。

    本文使用6806L,无源蜂鸣器,电路如下:
    在这里插入图片描述

    如果使用PZ6806D,则使用有源蜂鸣器,电路如下:
    在这里插入图片描述
    有源蜂鸣器需要使用PNP 三极管对stm32引脚信号进行放大。

    二、程序开发

    1. 位操作定义

    system.h 里定义

    #ifndef _system_H
    #define _system_H
    
    
    #include "stm32f10x.h"
    
    
    //位带操作,实现51类似的GPIO控制功能
    //具体实现思想,参考<<CM3权威指南>>第五章(87页~92页).
    //IO口操作宏定义
    #define BITBAND(addr, bitnum) ((addr & 0xF0000000)+0x2000000+((addr &0xFFFFF)<<5)+(bitnum<<2)) 
    #define MEM_ADDR(addr)  *((volatile unsigned long  *)(addr)) 
    #define BIT_ADDR(addr, bitnum)   MEM_ADDR(BITBAND(addr, bitnum)) 
    //IO口地址映射
    #define GPIOA_ODR_Addr    (GPIOA_BASE+12) //0x4001080C 
    #define GPIOB_ODR_Addr    (GPIOB_BASE+12) //0x40010C0C 
    #define GPIOC_ODR_Addr    (GPIOC_BASE+12) //0x4001100C 
    #define GPIOD_ODR_Addr    (GPIOD_BASE+12) //0x4001140C 
    #define GPIOE_ODR_Addr    (GPIOE_BASE+12) //0x4001180C 
    #define GPIOF_ODR_Addr    (GPIOF_BASE+12) //0x40011A0C    
    #define GPIOG_ODR_Addr    (GPIOG_BASE+12) //0x40011E0C    
    
    #define GPIOA_IDR_Addr    (GPIOA_BASE+8) //0x40010808 
    #define GPIOB_IDR_Addr    (GPIOB_BASE+8) //0x40010C08 
    #define GPIOC_IDR_Addr    (GPIOC_BASE+8) //0x40011008 
    #define GPIOD_IDR_Addr    (GPIOD_BASE+8) //0x40011408 
    #define GPIOE_IDR_Addr    (GPIOE_BASE+8) //0x40011808 
    #define GPIOF_IDR_Addr    (GPIOF_BASE+8) //0x40011A08 
    #define GPIOG_IDR_Addr    (GPIOG_BASE+8) //0x40011E08 
     
    //IO口操作,只对单一的IO口!
    //确保n的值小于16!
    #define PAout(n)   BIT_ADDR(GPIOA_ODR_Addr,n)  //输出 
    #define PAin(n)    BIT_ADDR(GPIOA_IDR_Addr,n)  //输入 
    
    #define PBout(n)   BIT_ADDR(GPIOB_ODR_Addr,n)  //输出 
    #define PBin(n)    BIT_ADDR(GPIOB_IDR_Addr,n)  //输入 
    
    #define PCout(n)   BIT_ADDR(GPIOC_ODR_Addr,n)  //输出 
    #define PCin(n)    BIT_ADDR(GPIOC_IDR_Addr,n)  //输入 
    
    #define PDout(n)   BIT_ADDR(GPIOD_ODR_Addr,n)  //输出 
    #define PDin(n)    BIT_ADDR(GPIOD_IDR_Addr,n)  //输入 
    
    #define PEout(n)   BIT_ADDR(GPIOE_ODR_Addr,n)  //输出 
    #define PEin(n)    BIT_ADDR(GPIOE_IDR_Addr,n)  //输入
    
    #define PFout(n)   BIT_ADDR(GPIOF_ODR_Addr,n)  //输出 
    #define PFin(n)    BIT_ADDR(GPIOF_IDR_Addr,n)  //输入
    
    #define PGout(n)   BIT_ADDR(GPIOG_ODR_Addr,n)  //输出 
    #define PGin(n)    BIT_ADDR(GPIOG_IDR_Addr,n)  //输入
    
    
    #endif
    
    

    2. 引脚定义

    beep.h

    #ifndef _beep_H
    #define _beep_H
    
    #include "system.h"
    
    /*  蜂鸣器时钟端口、引脚定义 */
    #define BEEP_PORT 			GPIOB   
    #define BEEP_PIN 			GPIO_Pin_5
    #define BEEP_PORT_RCC		RCC_APB2Periph_GPIOB
    
    #define beep PBout(5)
    
    void BEEP_Init(void);
    
    #endif
    
    

    3. GPIO 初始化

    #include "beep.h"
    
    void BEEP_Init()	  //端口初始化
    {
    	GPIO_InitTypeDef GPIO_InitStructure;	//声明一个结构体变量,用来初始化GPIO
    
    	RCC_APB2PeriphClockCmd(BEEP_PORT_RCC,ENABLE);   /* 开启GPIO时钟 */
    
    	/*  配置GPIO的模式和IO口 */
    	GPIO_InitStructure.GPIO_Pin=BEEP_PIN;		//选择你要设置的IO口
    	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;		  //设置推挽输出模式
    	GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;	  //设置传输速率
    	GPIO_Init(BEEP_PORT,&GPIO_InitStructure); 	 /* 初始化GPIO */
    }
    
    
    
    

    4. 主函数输出脉冲

    main.c

    
    
    #include "system.h"
    #include "SysTick.h"
    #include "beep.h"
    
    int main()
    {
    	u16 i=0;
    	SysTick_Init(72);
    	BEEP_Init();
    	while(1)
    	{
    		i++;
    		if(i%10==0)
    		{
    			beep=!beep;
    		}
    		delay_us(10);  
    	}
    }
    
    

    可以通过改变延迟来修改音调:

    while(1){
    	beep=1;
    	delay_us(150);
    	beep=0;
    	delay_us(50);
    }
    

    三、音乐盒程序开发

    1. 项目架构

    在这里插入图片描述

    2. 主程序入口

    main.c

    #include "system.h"
    #include "SysTick.h"
    #include "beep.h"
    
    
    int main()
    {
    	SysTick_Init(72);
    	BEEP_Init();
    	while(1)
      {                
         play_music();        
      }    
    }
    

    3. beep.h 头文件

    #ifndef _beep_H
    #define _beep_H
    
    #include "SysTick.h"
    #include "system.h"
    
    /*  蜂鸣器时钟端口、引脚定义 */
    #define BEEP_PORT 			GPIOB   
    #define BEEP_PIN 			GPIO_Pin_5
    #define BEEP_PORT_RCC		RCC_APB2Periph_GPIOB
    
    #define beep PBout(5)
    
    void BEEP_Init(void);
    void play_music(void);
    #endif
    
    

    4. beep.c 乐谱文件

    #include "beep.h"
    
    
    /*******************************************************************************
    * 函 数 名         : BEEP_Init
    * 函数功能		   : 蜂鸣器初始化
    * 输    入         : 无
    * 输    出         : 无
    *******************************************************************************/
    
    void BEEP_Init()	  //端口初始化
    {
    	GPIO_InitTypeDef GPIO_InitStructure;	//声明一个结构体变量,用来初始化GPIO
    
    	RCC_APB2PeriphClockCmd(BEEP_PORT_RCC,ENABLE);   /* 开启GPIO时钟 */
    
    	/*  配置GPIO的模式和IO口 */
    	GPIO_InitStructure.GPIO_Pin=BEEP_PIN;		//选择你要设置的IO口
    	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;		  //设置推挽输出模式
    	GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;	  //设置传输速率
    	GPIO_Init(BEEP_PORT,&GPIO_InitStructure); 	 /* 初始化GPIO */
    }
    
    void Sound(u16 frq)
    {
        u32 time;
        if(frq != 1000)
        {
            time = 500000/((u32)frq);
            beep = 1;
            delay_us(time);
            beep = 0;
            delay_us(time);
        }else
            delay_us(1000);
    }
    
    void play_music(void)
    {    
        // 各音调频率表    
                    // 0   1   2   3   4   5   6   7  低1  低2 低3 低4 低5 低6 低7
        uc16 tone[] ={250,262,294,330,350,393,441,495,525,589,661,700,786,882,990}; //音调
    	  // 生日快乐乐谱
        u8 music[]={5,5,6,5,8,7,5,5,6,5,9,8,5,5,12,10,8,7,6,11,
                      11,10,8,9,8,5,5,8,5,5,12,10,8,7,6,11,11,10,8,9,8
        };
    	  // 节拍时间
        u8 time[] = {1,2,2,2,2,4,1,2,2,2,2,4,1,2,2,2,1,4,
                      4,1,2,2,2,2,4,1,2,4,1,2,2,2,1,4, 4,1,2,2,2,2,4,4
        }; 
                  
    		// velocity 越大,播放越快
        u32 velocity;
        u16 i,e;
        velocity = 20;
        for(i=0;i<sizeof(music)/sizeof(music[0]);i++){
            for(e=0;e<((u16)time[i]) * tone[music[i]] / velocity;e++){
                Sound((u32)tone[music[i]]);
            }    
        }
    }
    
    展开全文
  • 作为一名刚接触STM32的初学者,今天通过观看网站上的视频,对STM32有了最基础的了解,并学到了一些STM32学习方法,自我感觉收获不错。 一.学习方法 1.两个学习网址:开源电子网:www.openedv.com ST中国官方...
  • 今天的状态还可以,学习了新建工程模板(基于寄存器)和了解了GPIO工作原理,昨天基于库函数新建工程模板学习的并不牢固,今天还是无法自己创建出来,正好借此机会再巩固一下,然后GPIO工作原理听得迷迷糊糊的,感觉...
  • stm32学习总结:1、stm32学习开篇 文章目录stm32学习总结:1、stm32学习开篇1. 前言2. 补基础的书籍和视频3. stm324. 最后 1. 前言 最近项目上用到stm32开始多了起来,于是想要系统简单过一下stm32,并且这里再次...
  • stm32学习基本知识点

    2020-12-18 19:37:13
    1,意思是APB2接高速设备2、Stm32f10x.h相当于reg52.h(里面有基本的位操作定义),另一个为stm32f10x_conf.h专门控制外围器件的配置,也就是开关头文件的作用3、 HSE Osc(High Speed External Oscillator)高速外部...
  • STM32学习感悟笔记一

    2021-04-17 02:51:13
    自己从开始接触STM32单片机以来,最先接触的是学习一些单片机的硬件结构,时钟树,MDK软件以及USB转串口的驱动安装,工程的构建,再到MDK软件的配置这些算是开发单片机的准备工作,接下来是一些简单的工程实验。...
  • STM32学习

    2021-02-10 20:43:26
    STM32学习1——GPIO开发基础GPIO(General Purpose Input & Output)——通用输入/输出口GPIOSTM32的GPIOGPIO输出的HAL库函数GPIO电平输出HAL库函数GPIO电平翻转HAL库函数未完结…… GPIO(General Purpose Input &...
  • OLED作为STM32的一个重要外设模块,在程序代码的调试起着较为重要的作用,这篇文章就通过OLED的数据手册来分析下OLED的使用方法。 STM32——OLED的使用前言一、配置OLED的IO口1.8080串行输入2.SPI四线3.I2C二、阅读...
  • 2、定时器中断STM32CubeMX配置 1)使能中断(切记呀) 2)在定时器初始化函数中手动添加中断初始化函数: HAL_TIM_Base_Start_IT(&htim2);//以tim2为例 3)就是写回调函数了,回调函数包括: void HAL_TIM_...
  • 1.STM32中与穿行通信相关的寄存器 1.USART_SR 状态寄存器,主要用来放置某些标志位 2.USART_DR 数据寄存器,用于存放数据 3.USART_BRR 波特率寄存器,设置波特率 对于用库函数来编写串行通信相关的代码来说,...
  • STM32学习-4,通用同步异步收发器USRAT

    千次阅读 2021-02-18 15:01:13
    FractionalDivider = ((IntegerDivider - ((u32) IntegerDivider)) * 16) + 0.5 USART_WordLength USART_WordLength 提示了在一个帧中传输或者接收到的数据位数。Table 709. 给出了该参数可取的值。 USART_StopBits ...
  • 通过检测GPIO输入,让LED等实现亮灭的翻转效果
  • STM32学习笔记(四)蜂鸣器实验

    千次阅读 2021-01-11 21:17:41
    对于STM32学习可分为3个版本。1.寄存器版本2.库函数版本3.HAL库版本由于个人原因,选择库函数版本来进行STM32学习。 提示:软件安装等问题,不进行讲解!!! 一、原理图 原理图可知,PB8对应BEEP。 二、程序...
  • STM32学习路线

    2021-07-30 08:37:25
    此路线参照知乎STM32学习路线,学习此路线要领会贯通,写好博客 参照 野火 正点原子 江科大自化协 知乎部分文章 每个章节学习方法: 看视频,看文档,写博客 前一天安排好后一天的任务 任务计划每天一个章节左右,...
  • stm32学习笔记(2) 中断和异常的区别 外中断——就是我们指的中断——是指由于外部设备事件所引起的中断,如通常的磁盘中断、打印机中断等; 内中断——就是异常——是指由于 CPU 内部事件所引起的中断,如程序...
  • 目录:一、STM32中断系统二、NVIC中断管理三、中断配置相关函数 一、STM32中断系统 中断概念   • 中断是CPU对系统发生的某个事件作出的一种反应。   • 引起中断的事件称为中断源。   • 中断源向CPU提出处理的...
  • 文章目录 前言 一、程序下载 1.USB下载方式 2.J-LINK下载方式 前言 stm32学习过程,是从正点原子的开发板开始的,但是正点原子的开发板实在是太大了,而且不便宜,不适合一些实际项目制作,所以搞了一块最小系统板和...
  • STM32学习之TFTLCD

    2021-11-20 21:26:53
    之前的STM32博客学习了从0新建工程,C语言知识复习,总线时钟等基础知识,现在根据手册开始正式的开发项目,本篇博客主要是理解文档中的TFTLCD部分的学习 学习资料来自:STM32F407最小系统板开发指南-库函数版本_V...
  • STM32学习笔记-基础外设-USART 说明:这个文章综合了截至发布之前所有的博客,用于自己学习记录,顺便分享出来。谢谢!! 文章目录STM32学习笔记-基础外设-USART一、先贴代码1、USART基础配置2、启用printf函数二、...
  • STM32CubeMX安装1、STM32CubeMX介绍2、软件坏境安装3、STM32CubeMX软件下载4、安装步骤 1、STM32CubeMX介绍 ​ STM32CubeMX是ST公司主动原创的一款代码生成工具, 它可以减轻开发时间和费用。STM32CubeMX集成了一个...
  • STM32学习笔记3——GPIO的输入引脚写在前面一、代码对比1、原先代码2、最终代码三、经验总结1、怎么定位和修改代码的?2、为什么上拉输入引脚要置位?写在后面 写在前面 果然我还要学习很多东西,记录今天怎么解决...
  • STM32学习】基于STM32的USB储存设备 1. 实现动机 最近打算做一下lvgl在stm32上的移植,考虑到lvgl可能会用到一些图片资源,这样一来文件系统就很有必要了,于是想在手头的一块小板子上实现一下FatFs和USB Mass ...
  • 上一篇博客复习了C语言,这一篇博客主要是学习STM32F407中的总线与时钟,这一部分对计算机组成原理的知识有一定的要求, 这一部分极其枯燥,但是十分重要,望仔细学习 学习资料来自:STM32F407最小系统板开发指南-...
  • stm32学习笔记

    2020-12-18 19:37:52
    整理了一些STM32相关知识点,平时有空可以看看,如果是面试STM32相关工作的也可以看看。相信对你一定会有所帮助的。1、AHB系统总线分为APB1(36MHz)和APB2(72MHz),其中2>1,意思是APB2接高速设备;2、 HSE Osc...
  • STM32学习之新建工程模板

    千次阅读 2021-11-12 18:04:12
    之前的开发的工程都是在已有框架上的已有产品中增加新功能,这次我决定从0开始,学习STM32,从搭建环境开始,到烧录程序运行成功,系统的从零开始学习。 本人已经绘制成功一块STM32的电路版,已经将硬件焊接成功,...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 43,933
精华内容 17,573
关键字:

stm32学习